Task: Implement sparse-matrix/ilucg/silucg.f77 for EinsteinAnalysis/AHFinderDirect thorn

thorn_name: EinsteinAnalysis/AHFinderDirect
src_filename: sparse-matrix/ilucg/silucg.f77
interface: # Interface definition for thorn AHFinderDirect
# $Header$

implements: AHFinderDirect
inherits: Grid ADMBase StaticConformal SpaceMask SphericalSurface IO

# include file provided by SpaceMask
USES INCLUDE: SpaceMask.h

################################################################################

# we use a mask in order to find out whether we interpolate from the excised
# region.  (we need 3 time levels because Carpet might need to interpolate
# in time.)

REAL ahmask TYPE=gf TIMELEVELS=3 TAGS='tensortypealias="Scalar" Prolongation="None"'

################################################################################

# we use an aliased function maybe provided by DriftCorrect
SUBROUTINE SetDriftCorrectPosition					\
   (CCTK_POINTER_TO_CONST IN GH,					\
    CCTK_REAL IN x, CCTK_REAL IN y, CCTK_REAL IN z)
USES FUNCTION SetDriftCorrectPosition

################################################################################

#
# This thorn provides an aliased-function interface which other thorns
# can use to find out about the horizons.  Each function returns a status
# code which is >= 0 for ok, or < 0 for an error.
#

########################################

#
# This function returns the local coordinate origin for a given horizon.
#
CCTK_INT FUNCTION HorizonLocalCoordinateOrigin				\
   (CCTK_INT IN horizon_number,						\
    CCTK_REAL OUT origin_x, CCTK_REAL OUT origin_y, CCTK_REAL OUT origin_z)

#
# The following function queries whether or not the specified horizon
# was found the most recent time AHFinderDirect searched for it.
# The return value is:
#  1 if the horizon was found
#  0 if the horizon was not found
#  negative for an error
#
CCTK_INT FUNCTION HorizonWasFound(CCTK_INT IN horizon_number)

#
# The following function queries whether or not the specified horizon
# was found the most recent time AHFinderDirect searched for it, and
# if it was, returns the horizon centroid.  The returned value is the
# same as that of  HorizonWasFound()  (above), i.e.
#  1 if the horizon was found; in this case  centroid_[xyz]  are set
#    to the centroid position
#  0 if the horizon was not found; in this case  centroid_[xyz]  are
#    set to zeros
#  negative for an error
#
CCTK_INT FUNCTION HorizonCentroid					\
  (CCTK_INT IN horizon_number,						\
   CCTK_REAL OUT centroid_x, CCTK_REAL OUT centroid_y, CCTK_REAL OUT centroid_z)

#
# The following function computes the horizon radius in the direction
# of each (x,y,z) point, or -1.0 if this horizon wasn't found the most
# recent time AHFinderDirect searched for it.  More precisely, for each
# (x,y,z), consider the ray from the local coordinate origin through
# (x,y,z).  This function computes the Euclidean distance between the
# local coordinate origin and this ray's intersection with the horizon,
# or -1.0 if this horizon wasn't found the most recent time AHFinderDirect
# searched for it.  
#
CCTK_INT FUNCTION HorizonRadiusInDirection				\
   (CCTK_INT IN horizon_number,						\
    CCTK_INT IN N_points,						\
    CCTK_REAL IN ARRAY x, CCTK_REAL IN ARRAY y, CCTK_REAL IN ARRAY z,	\
    CCTK_REAL OUT ARRAY radius)

########################################

# all these functions are in src/driver/aliased_functions.cc
PROVIDES FUNCTION HorizonLocalCoordinateOrigin				\
	WITH AHFinderDirect_local_coordinate_origin			\
	LANGUAGE C
PROVIDES FUNCTION HorizonWasFound					\
	WITH AHFinderDirect_horizon_was_found				\
	LANGUAGE C
PROVIDES FUNCTION HorizonCentroid					\
	WITH AHFinderDirect_horizon_centroid				\
	LANGUAGE C
PROVIDES FUNCTION HorizonRadiusInDirection				\
	WITH AHFinderDirect_radius_in_direction				\
	LANGUAGE C

################################################################################

# Grid arrays to store the horizon shape, for easy output and checkpointing.
# These are similar and equivalent to SphericalSurface.
# Note that the horizons are numbered [0...N_horizon-1].

CCTK_REAL ah_radius[N_horizons] TYPE=array DIM=3 SIZE=max_N_zones_per_right_angle+1,max_N_zones_per_right_angle+1,6 DISTRIB=constant

CCTK_REAL ah_origin[N_horizons] TYPE=scalar
{
  ah_origin_x ah_origin_y ah_origin_z
}

CCTK_REAL ah_centroid[N_horizons] TYPE=scalar
{
  ah_centroid_x ah_centroid_y ah_centroid_z ah_centroid_t
  ah_centroid_x_p ah_centroid_y_p ah_centroid_z_p ah_centroid_t_p
}

CCTK_INT ah_flags[N_horizons] TYPE=scalar
{
  ah_initial_find_flag
  ah_really_initial_find_flag
  ah_search_flag
  ah_found_flag
  ah_centroid_valid
  ah_centroid_valid_p
  ah_centroid_iteration
  ah_centroid_iteration_p
}

################################################################################

# This IOUtil function is used to determine whether the diagnostics file
# should be truncated or appended (as in a recovery run )

CCTK_INT FUNCTION IO_TruncateOutputFiles \
  (CCTK_POINTER_TO_CONST IN GH)
USES FUNCTION IO_TruncateOutputFiles

################################################################################

# translate SphericalSurface name into ID
CCTK_INT \
FUNCTION sf_IdFromName (CCTK_INT IN id, CCTK_POINTER_TO_CONST IN name)
USES FUNCTION sf_IdFromName

################################################################################

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

storage: ah_radius ah_origin ah_centroid ah_flags

if (metric_timelevels == 1)
{
  storage: ahmask[1]
}
else if (metric_timelevels == 2)
{
  storage: ahmask[2]
}
else if (metric_timelevels == 3)
{
  storage: ahmask[3]
}

#
# setup
#
schedule AHFinderDirect_setup at CCTK_BASEGRID        \
   after SpatialCoordinates
   {
   lang: C
   options: global
   } "setup data structures"

#
# checkpoint/recover support
#
schedule AHFinderDirect_recover at CCTK_POST_RECOVER_VARIABLES
   {
   lang: C
   options: global
   } "import horizon data from Cactus variables"

#
# find horizons
#
if (run_at_CCTK_ANALYSIS != 0)
  {
  schedule AHFinderDirect_find_horizons at CCTK_ANALYSIS
     {
     lang: C
     options: global-early
     } "find apparent horizon(s) after this time step"
  }
if (run_at_CCTK_POSTSTEP != 0)
  {
  schedule AHFinderDirect_find_horizons at CCTK_POSTSTEP
     {
     lang: C
     options: global-early
     } "find apparent horizon(s) after this time step"
  }
if (run_at_CCTK_POSTINITIAL != 0)
  {
  schedule AHFinderDirect_find_horizons at CCTK_POSTINITIAL
     {
     lang: C
     options: global-early
     } "find apparent horizon(s) after this time step"
  }
if (run_at_CCTK_POSTPOSTINITIAL != 0)
  {
  schedule AHFinderDirect_find_horizons at CCTK_POSTPOSTINITIAL
     {
     lang: C
     options: global-early
     } "find apparent horizon(s) after this time step"
  }
if (run_at_CCTK_POST_RECOVER_VARIABLES != 0)
  {
  schedule AHFinderDirect_find_horizons at CCTK_POST_RECOVER_VARIABLES \
           after MoL_PostStep
     {
     lang: C
     options: global-early
     } "find apparent horizon(s) after this time step"
  }

#
# find horizons, set excision mask, maybe announce centroid to other thorns
#
# FIXME: the name  AHFinderDirect_maybe_do_masks  is archaic,
#        AHFinderDirect_do_masks  would be more apt
#
# We use two Cactus Groups here:
#  group_for_mask_stuff
#    group_where_mask_is_set
#      AHFinderDirect_maybe_do_masks
# this way other thorns can schedule routines which need to run immediately
# before/after we set the mask in group_for_mask_stuff before/after
# group_where_mask_is_set, without having to know in what higher-level
# Cactus schedule bins/groups this thorn runs.
#
if (run_at_CCTK_ANALYSIS != 0)
  {
  schedule AHFinderDirect_import_mask at CCTK_ANALYSIS \
     before AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early loop-local
     } "import the excision mask"

  schedule AHFinderDirect_store at CCTK_ANALYSIS \
     after AHFinderDirect_find_horizons \
     before SphericalSurface_HasBeenSet
     {
     lang: C
     options: global-early
     } "store apparent horizon(s) into spherical surface(s)"

  schedule AHFinderDirect_save at CCTK_ANALYSIS \
     after AHFinderDirect_find_horizons \
     {
     lang: C
     options: global-early
     } "save apparent horizon(s) into Cactus variables"

  if (which_horizon_to_announce_centroid != 0)
    {
    schedule AHFinderDirect_announce at CCTK_ANALYSIS \
       before DriftCorrect \
       after AHFinderDirect_find_horizons
       {
       lang: C
       options: global-early
       } "announce horizon position(s) to other thorns"
    }

  schedule AHFinderDirect_maybe_do_masks at CCTK_ANALYSIS \
     after AHFinderDirect_find_horizons
     {
     lang: C
     } "set mask(s) based on apparent horizon position(s)"
  }
if (run_at_CCTK_POSTSTEP != 0)
  {
  schedule AHFinderDirect_import_mask at CCTK_POSTSTEP \
     before AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early loop-local
     } "import the excision mask"

  schedule AHFinderDirect_store at CCTK_POSTSTEP \
     after AHFinderDirect_find_horizons \
     before SphericalSurface_HasBeenSet
     {
     lang: C
     options: global-early
     } "store apparent horizon(s) into spherical surface(s)"

  schedule AHFinderDirect_save at CCTK_POSTSTEP \
     after AHFinderDirect_find_horizons \
     {
     lang: C
     options: global-early
     } "save apparent horizon(s) into Cactus variables"

  if (which_horizon_to_announce_centroid != 0)
    {
    schedule AHFinderDirect_announce at CCTK_POSTSTEP \
       before DriftCorrect \
       after AHFinderDirect_find_horizons
       {
       lang: C
       options: global-early
       } "announce horizon position(s) to other thorns"
    }

  schedule AHFinderDirect_maybe_do_masks at CCTK_POSTSTEP \
     after AHFinderDirect_find_horizons
     {
     lang: C
     } "set mask(s) based on apparent horizon position(s)"
  }
if (run_at_CCTK_POSTINITIAL != 0)
  {
  schedule AHFinderDirect_import_mask at CCTK_POSTINITIAL \
     before AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early loop-local
     } "import the excision mask"

  schedule AHFinderDirect_store at CCTK_POSTINITIAL \
     after AHFinderDirect_find_horizons \
     before SphericalSurface_HasBeenSet
     {
     lang: C
     options: global-early
     } "store apparent horizon(s) into spherical surface(s)"

  schedule AHFinderDirect_save at CCTK_POSTINITIAL \
     after AHFinderDirect_find_horizons \
     {
     lang: C
     options: global-early
     } "save apparent horizon(s) into Cactus variables"

  if (which_horizon_to_announce_centroid != 0)
    {
    schedule AHFinderDirect_announce at CCTK_POSTINITIAL \
       before DriftCorrect \
       after AHFinderDirect_find_horizons
       {
       lang: C
       options: global-early
       } "announce horizon position(s) to other thorns"
    }

  schedule AHFinderDirect_maybe_do_masks at CCTK_POSTINITIAL \
     after AHFinderDirect_find_horizons
     {
     lang: C
     } "set mask(s) based on apparent horizon position(s)"
  }
if (run_at_CCTK_POSTPOSTINITIAL != 0)
  {
  schedule AHFinderDirect_import_mask at CCTK_POSTPOSTINITIAL \
     before AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early loop-local
     } "import the excision mask"

  schedule AHFinderDirect_store at CCTK_POSTPOSTINITIAL \
     after AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early
     } "store apparent horizon(s) into spherical surface(s)"

  schedule AHFinderDirect_save at CCTK_POSTPOSTINITIAL \
     after AHFinderDirect_find_horizons \
     {
     lang: C
     options: global-early
     } "save apparent horizon(s) into Cactus variables"

  if (which_horizon_to_announce_centroid != 0)
    {
    schedule AHFinderDirect_announce at CCTK_POSTPOSTINITIAL \
       before DriftCorrect \
       after AHFinderDirect_find_horizons
       {
       lang: C
       options: global-early
       } "announce horizon position(s) to other thorns"
    }

  schedule AHFinderDirect_maybe_do_masks at CCTK_POSTPOSTINITIAL \
     after AHFinderDirect_find_horizons
     {
     lang: C
     } "set mask(s) based on apparent horizon position(s)"
  }
if (run_at_CCTK_POST_RECOVER_VARIABLES != 0)
  {
  schedule AHFinderDirect_import_mask at CCTK_POST_RECOVER_VARIABLES \
     before AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early loop-local
     } "import the excision mask"

  schedule AHFinderDirect_store at CCTK_POST_RECOVER_VARIABLES \
     after AHFinderDirect_find_horizons \
     before SphericalSurface_HasBeenSet
     {
     lang: C
     options: global-early
     } "store apparent horizon(s) into spherical surface(s)"

  schedule AHFinderDirect_save at CCTK_POST_RECOVER_VARIABLES \
     after AHFinderDirect_find_horizons
     {
     lang: C
     options: global-early
     } "save apparent horizon(s) into Cactus variables"

  if (which_horizon_to_announce_centroid != 0)
    {
    schedule AHFinderDirect_announce at CCTK_POST_RECOVER_VARIABLES \
       before DriftCorrect \
       after AHFinderDirect_find_horizons
       {
       lang: C
       options: global-early
       } "announce horizon position(s) to other thorns"
    }

  schedule AHFinderDirect_maybe_do_masks at CCTK_POST_RECOVER_VARIABLES \
     after AHFinderDirect_find_horizons
     {
     lang: C
     } "set mask(s) based on apparent horizon position(s)"
  }

# TODO: Don't call this, since no horizon has been found yet
schedule AHFinderDirect_maybe_do_masks at CCTK_POSTREGRIDINITIAL \
   after (MaskOne MaskZero)
   {
   lang: C
   } "set mask(s) based on apparent horizon position(s)"

schedule AHFinderDirect_maybe_do_masks at CCTK_POSTREGRID \
   after (MaskOne MaskZero)
   {
   lang: C
   } "set mask(s) based on apparent horizon position(s)"

########################################

##
## uncomment this if you want to use the test driver
##  src/util/test_patch_system.cc
##
##schedule test_patch_system at CCTK_POSTINITIAL
##{
##LANG: C
##OPTIONS: global
##} "test driver to verify that src/util/ and src/jtutil/ code works properly"

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

################################################################################

#
# ***** interface to the rest of Cactus *****
#

# we may need to look at some grid:: parameters to choose our patch system type
# note our access is read-only
shares: grid
USES KEYWORD domain
USES KEYWORD bitant_plane
USES KEYWORD quadrant_direction
USES KEYWORD rotation_axis

# we need to look at ADMBase::metric_type and StaticConformal::conformal_state
# to check physical/conformal metric
# note our access is read-only
shares: ADMBase
USES KEYWORD metric_type
USES CCTK_INT metric_timelevels

# we need to look at SpaceMask::use_mask in order to find out about the
# excision region
shares: SpaceMask
USES BOOLEAN use_mask

# we need to look at IO::out_dir to set the default for our own
# output directory parameters
shares: IO
USES STRING out_dir

# we want to know how many surfaces there are, so that we don't access
# a non-existing one
shares: SphericalSurface
USES INT nsurfaces
USES INT maxntheta
USES INT maxnphi

# all remaining parameters are private to this thorn
private:

################################################################################

#
# ***** overall parameters *****
#

# set this to (try to) find apparent horizons at every time step
int find_every "how often should we try to find apparent horizons?" STEERABLE=always
{
0   :: "don't find AHs at all (this thorn is a no-op)"
1:* :: "any integer >= 1"
} 1

# set this to (try to) find individual apparent horizons at every time step
int find_every_individual[101] "how often should we try to find individual apparent horizons?  (overrides find_every)" STEERABLE=always
{
-1  :: "use the value of find_every"
0   :: "don't find this AH at all"
1:* :: "any integer >= 1"
} -1

# set this to (try to) find individual apparent horizons after a time step
int find_after_individual[101] "when should we start to find individual apparent horizons?" STEERABLE=always
{
0:* :: "after this iteration (inclusively)"
} 0

int dont_find_after_individual[101] "when should we stop finding individual apparent horizons?" STEERABLE=always
{
0:* :: "after this iteration (exclusively)"
-1  :: "do not use this parameter"
} -1

# set this to (try to) find individual apparent horizons after a certain time
real find_after_individual_time[101] "when should we start to find individual apparent horizons?" STEERABLE=always
{
*:* :: "after this time (inclusively)"
} 0.0

real dont_find_after_individual_time[101] "when should we stop finding individual apparent horizons?" STEERABLE=always
{
*:* :: "after this time (exclusively); ignore this value if it is less than or equal to find_after_individual_time"
} 0.0

# set this to disable finding individual horizons
boolean disable_horizon[101] "should this horizon be disabled?" STEERABLE=always
{
} "false"

#
# These parameters control which schedule bin (or even bins, if you
# want) AHFinderDirect runs in.  Historically, Erik's branch of
# AHFinderDirect has run at CCTK_POSTSTEP.  However, for some purposes
# it's more convenient to run at CCTK_ANALYSIS and/or CCTK_POSTINITIAL.
#
# In general we need to run at CCTK_POST_RECOVER_VARIABLES, since
# (a) parameters may have been steered at recovery, so we may need
#     to find a new horizon or horizons, and
# (b) we need to set the mask again to make sure it's correct
#     right away (since our next regular horizon-finding may not
#     be until some time steps later)
# Therefore the  run_at_CCTK_POST_RECOVER_VARIABLES  parameter should
# probably be left at its default setting of  true .
#
Boolean run_at_CCTK_ANALYSIS "should we run at CCTK_ANALYSIS?"
{
} false
Boolean run_at_CCTK_POSTSTEP "should we run at CCTK_POSTSTEP?"
{
} true
Boolean run_at_CCTK_POSTINITIAL "should we run at CCTK_POSTINITIAL?"
{
} false
Boolean run_at_CCTK_POSTPOSTINITIAL "should we run at CCTK_POSTPOSTINITIAL?"
{
} false
Boolean run_at_CCTK_POST_RECOVER_VARIABLES				\
  "should we run at CCTK_POST_RECOVER_VARIABLES?"
{
} false

keyword method "what should this thorn do for each apparent horizon?" \
  STEERABLE=recover
{
# these options are mostly for testing/debugging
# ... in a multiprocessor Cactus run, the horizons are done sequentually
#     on processor #0; the other processors do dummy computations
"evaluate expansions"	  :: "evaluate the LHS function Theta(h)"

# ... in a multiprocessor Cactus run, the Jacobian is computed on
#     processor #0; the other processors do dummy computations
"test expansion Jacobians" :: \
  "compute/print horizon 1's J[Theta(h)] Jacobian matrix (possibly in \
   multiple ways, depending on the  test_all_Jacobian_methods  parameter)"

# this is for normal apparent horizon finding
# ... in a multiprocessor Cactus run, the horizons are done in parallel
#     across processors; see src/driver/README.parallel for details
"find horizons"		  :: "find the apparent horizon"
} "find horizons"

#
# We support searching for up to N_horizons distinct apparent horizons
# (some of which may be nested inside others) in a slice.
# ***** IMPORTANT *****: The horizons are numbered 1 to N_horizons.
# Since Cactus arrays are 0-origin, we make the arrays be of size
# N_horizons+1, and don't use the [0] array elements.
#
# To change the N_horizons limit, just change the upper limit for
# N_horizons itself, change all the [N_horizons+1] array sizes in this
# paramter file, change the upper limit for which_horizon_to_announce_centroid,
# and recompile your configuration.  No changes are needed to the source code.
#
int N_horizons "number of apparent horizons to search for" \
  STEERABLE=recover
{
0     :: "turn this thorn into a fancy no-op :)"
1:100 :: "search for this many apparent horizons"
} 1

#
# This parameter controls whether or not (assuming we have found a
# horizon or horizons), we announce a selected horizon centroid to
# some other thorn.  In practice the other thorn will be DriftCorrect,
# which will use this information to adjust the coordinates to try to
# keep the horizon centroid at a constant coordinate position.
#
int which_horizon_to_announce_centroid \
  "for which horizon should we announce the centroid?" \
  STEERABLE=recover
{
0     :: "don't announce any centroid(s)"
1:100 :: "announce this horizon's centroid each time we find it"
} 0

#
# This parameter controls whether and (if) into which surface the horizon
# information will be stored.
#
int which_surface_to_store_info[101] \
  "into which surface should we store the info?" \
  STEERABLE=recover
{
-1  :: "don't store info"
0:* :: "store info into the corresponding surface"
} -1

string which_surface_to_store_info_by_name[101] \
  "into which surface should we store the info?" \
  STEERABLE=recover
{
""   :: "use which_surface_to_store_info"
".*" :: "any string"
} ""

#
# This parameter controls how verbose this thorn is in printing
# informational (non-error) messages describing what it's doing.
#
keyword verbose_level \
  "controls which (how many) messages to print describing AH finding" \
  STEERABLE=recover
{
# 1 line each time step giving number of horizons found and their masses
# ... this doesn't work yet :(
"physics highlights"   :: "just a few physics messages"

# 1 line for each horizon giving position/mass/area, + a summary line or two
"physics details"      :: "more detailed physics messages"

# 1 line giving Theta(h) norms at each Newton iteration
"algorithm highlights" :: \
  "physics details + a few messages about the AH-finding algorithm"

# lots of details tracing what the code is doing
"algorithm details"    :: \
  "physics details + lots of messages about the AH-finding algorithm"

# even more details tracing what the code is doing :)
"algorithm debug"    :: \
  "physics details + lots and lots of messages about the AH-finding algorithm"
} "physics details"

# n.b. printing timing stats is independent of  verbose_level
boolean print_timing_stats \
  "should we print timing stats for the whole apparent-horizon-finding process?" \
  STEERABLE=recover
{
} "false"

# Do we want the gradients of the surface expansion?
# (Increases run time)
boolean want_expansion_gradients "should we print the gradients of the expansions?" \
  STEERABLE=recover
{
} "false"

# By default, we find apparent horizons or CE surfaces.
# This parameter allows the equation to be change so that other surfaces
# are found instead.
keyword surface_definition[101] "search for what kind of surface?" STEERABLE=recover
{
"expansion"         :: "Theta_(l) (apparent horizons etc.)"
"inner expansion"   :: "Theta_(n) (expansion of ingoing null normal)"
"mean curvature"    :: "for CMC surfaces"
"expansion product" :: "Theta_(l) Theta_(n) (product of the expansions)"
} "expansion"

# By default, we find surfaces as defined above.
# This parameter allows the definition to be modified.
keyword surface_modification[101] "how to modify the surface definition" STEERABLE=recover
{
"none"         :: "no modification"
"radius"       :: "multiply with the coordinate radius"
"radius^2"     :: "multiply with the square of the coordinate radius"
"mean radius"  :: "multiply with the mean coordinate radius"
"areal radius" :: "multiply with the areal radius (does not converge nicely, because the Jacobian is only approximate)"
} "none"

# By default, we find apparent horizons by looking for a specified expansion.
# This parameter allows looking for a specified areal radius etc. instead.
keyword surface_selection[101] "search for a surface with this areal radius" STEERABLE=recover
{
"definition"                             :: "look for a surface as defined by 'surface_definition'"
"mean coordinate radius"                 :: "look for a surface with a certain mean coordinate radius (not covariant, but fast)"
"areal radius"                           :: "look for a surface with a certain areal radius"
"expansion times mean coordinate radius" :: "look for a surface with a certain product of expansion and mean coordiante radius"
"expansion times areal radius"           :: "look for a surface with a certain product of expansion and areal radius"
} "definition"

# By default, we find apparent horizons by solving the equation
#  Theta(h) = 0
# This parameter allows the RHS to be set to any specified constant,
# to find a surface of constant expansion.
real desired_value[101] "search for a surface with this (constant) value" STEERABLE=recover
{
*:* :: "any real number"
} 0.0



# By default, we just find apparent horizons.  This parameter uses
# pretracking to search for outermost apparent horizons instead.
boolean use_pretracking[101] "search for an outermost apparent horizon" STEERABLE=recover
{
} "no"

int pretracking_max_iterations[101] "maximum number of pretracking iterations" STEERABLE=recover
{
  0:* :: ""
} 100

real pretracking_value[101] "initial pretracking value" STEERABLE=recover
{
  *:* :: ""
} 1.0

real pretracking_minimum_value[101] "minimum pretracking value" STEERABLE=recover
{
  *:* :: ""
} 0.0

real pretracking_maximum_value[101] "maximum pretracking value (should be near the outer boundary)" STEERABLE=recover
{
  *:* :: ""
} 10.0

real pretracking_delta[101] "step size for value" STEERABLE=recover
{
  (0.0:* :: ""
} 1.0

real pretracking_minimum_delta[101] "minimum step size for value" STEERABLE=recover
{
  (0.0:* :: ""
} 1.0e-4

real pretracking_maximum_delta[101] "maximum step size for value" STEERABLE=recover
{
  (0.0:* :: ""
} 1.0

# Horizons can depend on other (usually pretracked) horizons
int depends_on[101] "the other horizon that this horizon depends on" STEERABLE=recover
{
  0     :: "the horizon is independent"
  1:100 :: "horizon index"
} 0

real desired_value_factor[101] "factor between this horizon's and the other horizon's desired value, applied before the offset" STEERABLE=recover
{
  *:* :: ""
} 1.0

real desired_value_offset[101] "difference between this horizon's and the other horizon's desired value, applied after the factor" STEERABLE=recover
{
  *:* :: ""
} 0.0



# By default, we keep the surface from the last time (if so desired).
# We can also modify it before each finding to try make it find inner
# or outer horizons.
real shiftout_factor[101] "enlarge the surface initial guess before finding" STEERABLE=recover
{
  (0:* :: "choose 1 for doing nothing, larger for enlarging, smaller for shrinking"
} 1.0

real smoothing_factor[101] "smoothen (remove higher multipole moments) the surface initial guess before finding" STEERABLE=recover
{
  *:* :: "choose 0 for no smoothing, 1 for complete smoothing, larger for over-smoothing, negative for roughening"
} 0.0

################################################################################

#
# ***** parameters for the initial guess for each apparent horizon shape *****
#

#
# (Note that if at any time we fail to find the (an) apparent horizon,
# then we reset our trial horizon surface to this initial guess before
# next attempting to find this horizon.)
#

keyword initial_guess_method[101] \
  "method used to set up initial guess for apparent horizon shape" \
  STEERABLE=recover
{
"read from named file"	:: "read from explicitly-named input file"
"read from h file"	:: \
  "read from input file named the same as the (later) h output file"
"Kerr/Kerr"		:: \
  "set to the (analytical) horizon of Kerr spacetime in Kerr coordinates \
   (n.b. Schwarzschild/EF is the special case spin=0 of this)"
"Kerr/Kerr-Schild"	:: \
  "set to the (analytical) horizon of Kerr spacetime in Kerr-Schild coordinates"
"coordinate sphere"	:: "set to a coordinate sphere"
"coordinate ellipsoid"	:: "set to a coordinate ellipsoid"
} "coordinate sphere"

# if a horizon could not be found, do we want to reset the horizon shape,
# or do we want to continue with the last shape?
boolean reset_horizon_after_not_finding[101] \
  "reset the horizon shape to the initial data if a horizon was not found" \
  STEERABLE=recover
{
} "yes"

# parameters for initial_guess_method = "read from named file"
string initial_guess__read_from_named_file__file_name[101] \
  "file name to read initial guess from" \
  STEERABLE=recover
{
".+" :: "file name to read initial guess from"
} "h.gp"

# parameters for initial_guess_method = "Kerr/Kerr"
real initial_guess__Kerr_Kerr__x_posn[101] "x coordinate of Kerr BH" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__Kerr_Kerr__y_posn[101] "y coordinate of Kerr BH" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__Kerr_Kerr__z_posn[101] "z coordinate of Kerr BH" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__Kerr_Kerr__mass[101] "mass of Kerr BH" STEERABLE=recover
{
(0.0:*		:: "BH mass = any real number > 0"
} 1.0
# n.b. my convention is that a=J/m^2 is dimensionless,
#      while MTW take a=J/m=m * (my a)
real initial_guess__Kerr_Kerr__spin[101] "dimensionless spin a=J/m^2 of Kerr BH" STEERABLE=recover
{
(-1.0:1.0)	:: \
  "dimensionless BH spin = J/m^2 = any real number with absolute value < 1"
} 0.6

# parameters for initial_guess_method = "Kerr/Kerr-Schild"
real initial_guess__Kerr_KerrSchild__x_posn[101] "x coordinate of Kerr BH" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__Kerr_KerrSchild__y_posn[101] "y coordinate of Kerr BH" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__Kerr_KerrSchild__z_posn[101] "z coordinate of Kerr BH" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__Kerr_KerrSchild__mass[101] "mass of Kerr BH" STEERABLE=recover
{
(0.0:*		:: "BH mass = any real number > 0"
} 1.0
# n.b. my convention is that a=J/m^2 is dimensionless,
#      while MTW take a=J/m=m * (my a)
real initial_guess__Kerr_KerrSchild__spin[101] "dimensionless spin a=J/m^2 of Kerr BH" STEERABLE=recover
{
(-1.0:1.0)	:: \
  "dimensionless BH spin = J/m^2 = any real number with absolute value < 1"
} 0.6

# parameters for initial_guess_method = "sphere"
real initial_guess__coord_sphere__x_center[101] "x coordinate of sphere center" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__coord_sphere__y_center[101] "y coordinate of sphere center" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__coord_sphere__z_center[101] "z coordinate of sphere center" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__coord_sphere__radius[101] "radius of sphere" STEERABLE=recover
{
(0.0:* :: "any real number > 0.0"
} 2.0

# parameters for initial_guess_method = "ellipsoid"
real initial_guess__coord_ellipsoid__x_center[101] \
  "x coordinate of ellipsoid center" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__coord_ellipsoid__y_center[101] \
  "y coordinate of ellipsoid center" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__coord_ellipsoid__z_center[101] \
  "z coordinate of ellipsoid center" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real initial_guess__coord_ellipsoid__x_radius[101] "x radius of ellipsoid" STEERABLE=recover
{
(0.0:* :: "any real number > 0.0"
} 2.0
real initial_guess__coord_ellipsoid__y_radius[101] "y radius of ellipsoid" STEERABLE=recover
{
(0.0:* :: "any real number > 0.0"
} 2.0
real initial_guess__coord_ellipsoid__z_radius[101] "z radius of ellipsoid" STEERABLE=recover
{
(0.0:* :: "any real number > 0.0"
} 2.0

################################################################################

#
# ***** I/O parameters *****
#

########################################

#
# parameters for BH diagnostics
#

#
# The file format is currently hard-wired to a simple ASCII format:
# - there is one file per AH
# - after some header comments (starting with '#'),
#   there is one line per successful-AH-finding
#   (there is *no* line if we fail to find an AH)
# - each line contains various tab-separated fields, as described by
#   the header comment
#
boolean output_BH_diagnostics \
  "should we output BH diagnostics to a data file for each AH found?" \
  STEERABLE=always
{
} "true"

#
# These file names are actually just "base" file names, with the full
# file names being given by a printf() format "%s/%s.ah%d.%s",
# where
# - the first %s is the BH diagnostics directory as set by the
#   BH_diagnostics_directory  parameter; this is automagically created
#   if it doesn't already exist
# - the second %s is the base file name,
# - the %d is the global Cactus time iteration number cctk_iteration,
# - the third %s is the file name extension as set by the
#   BH_diagnostics_file_name_extension  parameter
#
string BH_diagnostics_directory "directory for BH diagnostics output file(s)" \
  STEERABLE=always
{
".+" :: "any nonempty string"
"^$" :: "an empty string to default to IO::out_dir"
} ""
string BH_diagnostics_base_file_name \
  "base file name for BH diagnostics output file(s)" STEERABLE=always
{
".+" :: "any nonempty string"
} "BH_diagnostics"

string BH_diagnostics_file_name_extension \
  "extension for BH diagnostics data files" STEERABLE=always
{
".+" :: "any nonempty string"
} "gp"

########################################

#
# parameters for horizon-shape and other similar data files
#

# this parameter controls how often we write apparent-horizon-shape
# output files (format controlled by  horizon_file_format ); we still
# *find* apparent horizons at each time step, and compute BH diagnostics
# and (if desired) write them to the one-per-horizon BH diagnostics
# output files, but this parameter may be used to reduce the number
# of one-file-per-horizon-per-time-step apparent-horizon-shape output
# files
int output_h_every \
  "how often (in Cactus time steps) should we output h (0 to disable)?" \
  STEERABLE=always
{
0   :: "don't output h at all"
1:* :: "any integer >= 1"
} 1

# setting this > 0 is probably only of interest if the Newton iteration
# fails to converge, or if you're debugging AHFinderDirect internals
int output_Theta_every \
  "how often (in Cactus time steps) should we output the Theta(h) functions?" \
  STEERABLE=always
{
0   :: "don't output Theta(h) at all"
1:* :: "any integer >= 1"
} 0

int output_mean_curvature_every \
  "how often (in Cactus time steps) should we output the mean_curvature(h) functions?" \
  STEERABLE=always
{
0   :: "don't output mean_curvature(h) at all"
1:* :: "any integer >= 1"
} 0

boolean output_ASCII_files "output h and Theta(h) as ASCII files" STEERABLE=always
{
} "yes"

boolean output_HDF5_files "output h and Theta(h) as HDF5 files" STEERABLE=always
{
} "no"

# n.b. this refers to the patch system (angular) interpatch ghost zones,
#      *not* the Cactus interprocessor ghost zones
boolean output_ghost_zones_for_h \
  "should we include the patch system (angular) interpatch ghost zones \
   in h data files?" STEERABLE=always
{
} "false"

# n.b. making this "gnuplot" probably isn't a good idea,
#      since it could create confusion with gnuplot command files
string ASCII_gnuplot_file_name_extension \
  "extension for ASCII (gnuplot) data files" STEERABLE=always
{
".+" :: "any nonempty string"
} "gp"
string HDF5_file_name_extension "extension for HDF5 data files" STEERABLE=always
{
".+" :: "any nonempty string"
} "h5"

#
# These file names are actually just "base" file names, with the full
# file names being given by a printf() format "%s/%s.t%d.ah%d[.it%d].%s",
# where
# - the first %s is the output directory as set by the  h_directory
#   parameter (this same directory is used for all of these files);
#   this is automagically created if it doesn't already exist
# - the second %s is the base file name,
# - the first %d is the global Cactus time iteration number cctk_iteration,
# - the second %d is the apparent horizon number,
# - the optional third %d is the horizon finder iteration number
# - the third %s is the file name extension
#   {ASCII,HDF5}_data_file_name_extension
#
string h_directory \
  "directory for horizon shape and other similar output (or input) file(s)" \
   STEERABLE=always
{
".+" :: "any nonempty string"
"^$" :: "an empty string to default to IO::out_dir"
} ""

string h_base_file_name \
  "base file name for horizon shape h input/output file(s)" \
  STEERABLE=always
{
".+" :: "any nonempty string"
} "h"

string Theta_base_file_name "base file name for Theta(h) output file(s)" \
  STEERABLE=always
{
".+" :: "any nonempty string"
} "Theta"

string mean_curvature_base_file_name "base file name for mean_curvature(h) output file(s)" \
  STEERABLE=always
{
".+" :: "any nonempty string"
} "mean_curvature"

string Delta_h_base_file_name \
  "base file name for horizon-shape-update Delta_h output file(s)" \
  STEERABLE=always
{
".+" :: "any nonempty string"
} "Delta_h"

int h_min_digits \
  "minimum number of digits for the iteration number in the file name" STEERABLE=always
{
0:* :: ""
} 0

#
# Thomas Radke has written a set of OpenDX macros to read AHFinderDirect
# ASCII (gnuplot) format files.  These macros require a "control file"
# for each patch to specify some of the patch metadata.
#
# If we write them, the control file names are given by a printf() format
# "%s/%s.ah%d.%s", where
# - the first %s is the output directory as set by the  h_directory
#   parameter (this same directory is used for all of these files);
#   this is automagically created if it doesn't already exist
# - the second %s is h_base_file_name
# - the %d is the apparent horizon number
# - the third %s is the file name extension OpenDX_control_file_name_extension
#
boolean output_OpenDX_control_files \
  "should we output OpenDX control files to allow reading of AHFinderDirect \
   'ASCII (gnuplot)' format data files?" STEERABLE=always
{
} "true"
string OpenDX_control_file_name_extension \
  "file name extension for OpenDX control files" STEERABLE=always
{
".+" :: "any nonempty string"
} "dx"

########################################

#
# parameters mainly for debugging
#

# this is mainly useful for debugging purposes
boolean output_initial_guess \
  "should we output the initial guess back to the h data file?" \
  STEERABLE=always
{
} "false"

# for debugging convergence failures, we can optionally output
# h, Theta, and delta_h at each Newton iteration
# (the file names are the usual ones with ".it%d" appended)
boolean debugging_output_at_each_Newton_iteration \
  "should we output {h, Theta, delta_h} at each Newton iteration?" \
  STEERABLE=recover
{
} "false"

string Jacobian_base_file_name "base file name for Jacobian output file(s)" \
  STEERABLE=always
{
".+" :: "any valid file name"
} "Jacobian.dat"

################################################################################

#
# ***** parameters for setting a mask grid function ***
# ***** based on each apparent horizon shape *****
#

boolean set_mask_for_all_horizons \
  "should we set a mask grid function (or functions) for all horizons?" \
  STEERABLE=recover
{
} "false"

boolean set_mask_for_individual_horizon[101] \
  "should we set a mask grid function (or functions) for *this* horizon?" \
  STEERABLE=recover
{
} "false"

#
# If we set a mask, we do so based on partitioning the Cactus grid into
# 3 regions, an "inside", a "buffer", and an "outside" (typically the inside
# region is excised).  For a grid point a distance r[i] from horizon i's
# local coordinate origin, with horizon i's radius in this same direction
# (again, measured from its local coordinate origin) being r_horizon[i],
# the regions are defined as follows:
#	r[i] <= r_inner[i] for some i				==> inside
#	r[i] >  r_inner[i] for all i
#		and r[i] <= r_outer[i] for some i		==> buffer
#	r[i] >  r_outer[i] for all i				==> outside
# where
#	r_inner = mask_radius_multiplier * r_horizon
#		  +  mask_radius_offset * Delta_x
#	r_outer = r_innter  +  mask_buffer_thickness * Delta_x
# and where  Delta_x  is the Cactus base grid spacing (more precisely,
# the geometric mean of the base grid's x, y, and z Cactus grid spacings)
# (The "base grid" part here only matters if you're doing mesh refinement.)
#
real mask_radius_multiplier \
  "radius multiplier to define the 'inside' mask region" \
  STEERABLE=recover
{
(0:*) :: \
  "any positive real number; typically this will be slightly less than 1.0"
} 0.8
real mask_radius_offset \
  "radius offset (in Cactus base grid spacings) to define the 'inside' mask region" \
  STEERABLE=recover
{
*:* :: "any real number; typically this will be negative"
} -5.0
real mask_buffer_thickness \
  "thickness (in Cactus base grid spacings) of the 'buffer' mask region" \
  STEERABLE=recover
{
*:* :: "any real number; typically this will be positive"
} 5.0

#
# If this parameter is set to true, then we prevent the inside & buffer
#    regions from ever shrinking, i.e. if a point is ever set to the inside
#    value then we leave at that value forever, and if it's ever set to
#    the buffer value then we leave at that value or the inside value forever.
# If this parameter is set to false, then we set the inside/buffer/outside
#    values independently at each time step, so all three regions can grow
#    or shrink depending on the horizon positions/shapes.
#
boolean mask_is_noshrink \
  "should we prevent the inside & buffer regions from ever shrinking?" \
  STEERABLE=recover
{
} "true"

#
# If we're using mesh refinement (eg Carpet) it's possible to find a
# horizon on a fine grid, and then set a mask on a coarser grid where
# the masked-off region is only a few grid points across.  This then
# confuses various other thorns (eg LegoExcision) which require excised
# regions to be "not too small".
#
# So...  This parameter may be used to specify that the mask not be set
# for a given horizon (just as if that horizon wasn't found) on any grid
# where the inner mask radius  r_inner  is < this many grid spacings.
# More precisely, we skip masking for any horizon where
#	r_inner,min < min_horizon_radius_points_for_mask * Delta_x_current,max
# where
#	r_inner,min = the minimum over all angles of the inner mask radius
#		      r_inner
#	Delta_x_current,max = the maximum of the x,y,and z grid spacings
#			      in the *current* Cactus grid
#
# Note that since r_inner can be negative (if  mask_radius_offset < 0 ),
# the default setting for this parameter is effectively -infinity, to
# excise any horizon.
#
real min_horizon_radius_points_for_mask \
  "only set mask if min r_inner >= this number of local grid spacings" \
  STEERABLE=recover
{
*:* :: "any real number"
} -1.0e10

#
# This thorn supports two types of mask grid functions:
# * the "old" style is that defined by CactusEinstein/AHFinder:
#   the mask is a CCTK_REAL grid function, which is set to specified
#   (CCTK_REAL) values inside/outside the (adjusted) horizon
# * the "new" style is that defined by CactusEinstein/SpaceMask:
#   the mask is a CCTK_INT grid function, with a specified bit field
#   set to specified values inside/outside the (adjusted) horizon
#	[N.b. AHFinderDirect doesn't set up the bit fields --
#	you must arrange for some other thorn to do this.  At
#	present AHFinderDirect converts the names specified here
#	to integer bit masks only once, in its setup routine;
#	this may change to do this conversion each time AHFinderDirect
#	does horizon finding.]
# You can choose to set either (or even both!) of these.
#
boolean set_old_style_mask \
  "if we're setting a mask, should we set an old-style (CCTK_REAL) mask gridfn?" STEERABLE=recover
{
} "true"
boolean set_new_style_mask \
  "if we're setting a mask, should we set an new-style (CCTK_INT) mask gridfn?" STEERABLE=recover
{
} "false"

#
# mask info for  mask_type = "old-style (CCTK_REAL)"
# defaults match CactusEinstein/AHFinder
#
string old_style_mask_gridfn_name "name of the old-style mask grid function" STEERABLE=recover
{
".+" :: "any valid Cactus grid function name"
} "SpaceMask::emask"
real old_style_mask_inside_value \
  "set the old-style mask to this value in the 'inside' region" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real old_style_mask_buffer_value \
  "set the old-style mask to this value in the 'buffer' region" STEERABLE=recover
{
*:* :: "any real number"
} 0.5
real old_style_mask_outside_value \
  "set the old-style mask to this value in the 'outside' region" STEERABLE=recover
{
*:* :: "any real number"
} 1.0

#
# mask info for  mask_type = "new-style (CCTK_INT)"
#
string new_style_mask_gridfn_name "name of the new-style mask grid function" STEERABLE=recover
{
".+" :: "any valid Cactus grid function name"
} "SpaceMask::space_mask"
string new_style_mask_bitfield_name \
  "name of the new-style mask bit field ('type') as registered with SpaceMask" STEERABLE=recover
{
".+" :: "any valid bit field ('type') name registered with the SpaceMask thorn"
} "mask"
string new_style_mask_inside_value \
  "set the specified bit field of the new-style mask to this named state \
   at grid points in the 'inside' region" STEERABLE=recover
{
".+" :: "any state name registered with the SpaceMask thorn"
} "inside"
string new_style_mask_buffer_value \
  "set the specified bit field of the new-style mask to this named state \
   at grid points in the 'buffer' region" STEERABLE=recover
{
".+" :: "any state name registered with the SpaceMask thorn"
} "buffer"
string new_style_mask_outside_value \
  "set the specified bit field of the new-style mask to this named state \
   at grid points in the 'outside' region" STEERABLE=recover
{
".+" :: "any state name registered with the SpaceMask thorn"
} "outside"

################################################################################

#
# ***** parameters controlling how we handle errors/warnings *****
#

#
# We currently "handle" the following errors/warnings by CCTK_VWarn();
# these parameters give the warning levels (in the sense of the -W flag
# to Cactus, and the first argument to CCTK_VWarn()) for various conditions:
#

# point outside (or too close to boundary of) Cactus grid,
# i.e. geometry interpolator returns CCTK_ERROR_INTERP_POINT_OUTSIDE
# ... warning level if error occurs on first Newton iteration,
#     i.e. when evaluating expansion/Jacobian for initial guess
# ... this may mean that  driver::ghost_size  is too small, especially
#     if any horizon crosses a symmetry boundary
#     ==> visible message by default
int warn_level__point_outside__initial \
  "warning level for point outside (or too close to boundary of) Cactus grid \
   (error occurs on first Newton iteration)" STEERABLE=recover
{
-1:* :: "any valid Cactus warning level"
} 1
# ... warning level if error occurs on a subsequent Newton iteration
# ... this probably "just" means there's no horizon in the grid
#     ==> no visible message by default
int warn_level__point_outside__subsequent \
  "warning level for point outside (or too close to boundary of) Cactus grid \
   (error occurs on subsequent Newton iteration)" STEERABLE=recover
{
-1:* :: "any valid Cactus warning level"
} 2

# the Cactus configure process didn't find a finite() function,
# so we're skipping the "check that the geometry is finite" tests
# even though the user set  check_that_geometry_is_finite = "true"
int warn_level__skipping_finite_check \
  "warning level if the user sets  check_that_geometry_is_finite \
   but the Cactus configure process doesn't find a  finite()  function \
   so we have to skip the finite-geometry check" STEERABLE=recover
{
-1:* :: "any valid Cactus warning level"
} 3

# infinity and/or NaN in interpolated {g_ij, partial_k g_ij, K_ij}
int warn_level__nonfinite_geometry \
  "warning level if we find infinity and/or NaN in the interpolated geometry \
   values {g_ij, partial_k g_ij, K_ij}" STEERABLE=recover
{
-1:* :: "any valid Cactus warning level"
} 1

# interpolated g_{ij} isn't positive definite
# (usually this means we're too close to a singularity)
# ... warning level if error occurs on first Newton iteration,
#     i.e. when evaluating expansion/Jacobian for initial guess
int warn_level__gij_not_positive_definite__initial \
  "warning level if the interpolated g_{ij} isn't positive definite \
   (usually this means we're too close to a singularity) \
   (error occurs on first Newton iteration)" STEERABLE=recover
{
-1:* :: "any valid Cactus warning level"
} 2
# ... warning level if error occurs on a subsequent Newton iteration
int warn_level__gij_not_positive_definite__subsequent \
  "warning level if the interpolated g_{ij} isn't positive definite \
   (usually this means we're too close to a singularity) \
   (error occurs on subsequent Newton iteration)" STEERABLE=recover
{
-1:* :: "any valid Cactus warning level"
} 2

################################################################################

#
# ***** parameters for the Newton's-method solution of Theta(h) = 0 *****
#

#
# The first time we (try to) find a given horizon, our initial guess
# is likely to be rather inaccurate, so we may need a larger number of
# iterations.  But if we've found this horizon before, then we have its
# previous position as an initial guess, so (assuming that we've been
# scheduled at each time step) we shouldn't need as many iterations.
#
int max_Newton_iterations__initial \
  "maximum number of Newton iterations before giving up \
   when initially finding a given horizon" STEERABLE=recover
{
(0:* :: "any positive integer"
} 20
int max_Newton_iterations__subsequent \
  "maximum number of Newton iterations before giving up \
   when re-finding a given horizon after finding it before" STEERABLE=recover
{
(0:* :: "any positive integer"
} 10

#
# To avoid the Newton iteration diverging if there is no horizon present
# or if the initial guess is bad, we limit the distance the horizon may
# move in any single Newton iteration (i.e. the infinity-norm of Delta_h)
# to <= this fraction of the mean horizon radius
#
real max_allowable_Delta_h_over_h \
  "don't let horizon move > this fraction of mean radius in a Newton iteration" STEERABLE=recover
{
(0.0:* :: "any positive real number"
} 0.1

#
# To avoid the Newton iteration diverging (or more accurately, to detect
# this divergence faster) if there is no horizon present or if the initial
# guess is bad, we also deem any horizon with a mean radius (measured around
# its local coordinate origin) larger than this to be "not found".  The
# default is effectively "no limit"; AHFinderDirect will be more efficient
# if you (re)set this (for each horizon) to the coordinate radius of a
# sphere a bit larger than you expect the horizon to be (eg a sphere with
# areal radius 4m or so, where m is the ADM mass of the slice).
#
real max_allowable_horizon_radius[101] \
  "max mean-coordinate-radius allowed for any trial surface \
   before we give up and say we can't find this horizon" STEERABLE=recover
{
(0.0:* :: "any positive real number"
} 1.0e10

#
# convergence tolerance for the Newton iteration
#
real Theta_norm_for_convergence \
  "we declare the horizon to be found if ||Theta||_infinity <= this" STEERABLE=recover
{
(0.0:* :: "any positive real number"
} 1.0e-8

#
# To detect some divergence cases faster, we also deem any horizon where
# the infinity-norm of Theta exceeds this parameter, to be "not found".
# The default is effectively "no limit".
#
real max_allowable_Theta \
  "max ||Theta||_infinity allowed for any trial surface \
   before we give up and say we can't find this horizon" STEERABLE=recover
{
(0.0:* :: "any positive real number"
} 1.0e10

#
# To detect some divergence cases faster, we also do not allow the Theta
# norm to grow for more than a certain number of iterations.
#
int max_allowable_Theta_growth_iterations \
  "max number of consecutive iterations during which ||Theta||_infinity \
   is allowed to grow before we give up and say we can't find this horizon" STEERABLE=recover
{
0   :: "infinitly many"
1:* :: "that many"
} 0

#
# To detect some divergence cases faster, we also do not allow the Theta
# norm to oscillate without shrinking for more than a certain number of
# iterations.
#
int max_allowable_Theta_nonshrink_iterations \
  "max number of consecutive iterations during which ||Theta||_infinity \
   is allowed to oscillate without shrinking before we give up and say \
   we can't find this horizon" STEERABLE=recover
{
0   :: "infinitly many"
1:* :: "that many"
} 0

################################################################################

#
# ***** parameters to define the patch system(s) *****
#

#
# For each apparent horizon, you need to set these parameters to the
# Cactus xyz coordinates of a "local origin point" inside the horizon,
# which will serve as the origin for the apparent horizon finder's
# local angular coordinate system.
#
# The apparent horizon surface (and in fact all the trial surfaces the
# apparent horizon finder generates while iteratively solving the apparent
# horizon equation) is restricted to being a Strahlkoerper ("star-shaped
# region") about the origin point.  That is, each surface must be of
# the form r = h(angle) with h a single-valued function.
#
# If the origin point is too far from the actual horizon centroid, i.e.
# if it's too close to the horizon surface itself, then the apparent
# horizon finder's Newton iteration won't converge as quickly or robustly.
# However, in practice the tolerances on this are quite loose -- 1/4
# of the horizon radius is no problem, and even 1/2 the horizon radius
# only slows the convergence by an extra iteration or two.
#
real origin_x[101] "global x coordinate of patch system origin" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real origin_y[101] "global y coordinate of patch system origin" STEERABLE=recover
{
*:* :: "any real number"
} 0.0
real origin_z[101] "global z coordinate of patch system origin" STEERABLE=recover
{
*:* :: "any real number"
} 0.0

boolean move_origins "move the origins with the horizons" STEERABLE=recover
{
} "no"

boolean reshape_while_moving "reshape the horizons when moving them" STEERABLE=recover
{
} "no"

boolean predict_origin_movement "predict origin movement when moving the origins" STEERABLE=recover
{
} "no"

boolean track_origin_from_grid_scalar[101] "track horizon origin from given grid scalars" STEERABLE=recover
{
} "no"
string track_origin_source_x[101] "grid scalar containing the x component of the origin estimate" STEERABLE=recover
{
  "" :: "don't use this feature"
  "[a-zA-Z_][a-zA-Z0-9_]*[:][:][a-zA-Z_][a-zA-Z0-9_]*(\[0-9+\])" :: "name of a grid scalar"
} ""
string track_origin_source_y[101] "grid scalar containing the x component of the origin estimate" STEERABLE=recover
{
  "" :: "don't use this feature"
  "[a-zA-Z_][a-zA-Z0-9_]*[:][:][a-zA-Z_][a-zA-Z0-9_]*(\[0-9+\])" :: "name of a grid scalar"
} ""
string track_origin_source_z[101] "grid scalar containing the x component of the origin estimate" STEERABLE=recover
{
  "" :: "don't use this feature"
  "[a-zA-Z_][a-zA-Z0-9_]*[:][:][a-zA-Z_][a-zA-Z0-9_]*(\[0-9+\])" :: "name of a grid scalar"
} ""

#
# The "(rotating)" patch system types are ok for evaluating Theta(h),
# but alas they don't work yet for apparent horizon finding
# (the Jacobian computation doesn't yet grok the nonlocal rotation BCs).
#
keyword patch_system_type[101] "what type of patch system should we use?"
{
# choose this for normal use
"match Cactus grid symmetry" :: \
  "choose automagically based on grid symmetries and the patch system's origin"

"full sphere"	:: "full sphere, no symmetries"
"+z hemisphere"	:: "mirror symmetry across z=0 plane"

"+xy quadrant (rotating)" :: "90 degree periodic rotation symmetry about z axis"
"+xy quadrant (mirrored)" :: "mirror symmetry across x=0 and y=0 planes"

"+xz quadrant (mirrored)" :: "mirror symmetry across x=0 and z=0 planes"
"+xz quadrant (rotating)" :: \
  "180 degree periodic rotation symmetry about z axis \
   and mirror symmetry across z=0 plane"

"+xyz octant (rotating)"  :: \
  "90 degree periodic rotation symmetry about z axis \
   and mirror symmetry across z=0 plane"
"+xyz octant (mirrored)"  :: "mirror symmetry across x=0 and y=0 and z=0 planes"
} "match Cactus grid symmetry"

#
# This parameter sets the angular resolution of each patch systems:
# the angular grid spacing in degrees is 90.0/N_zones_per_right_angle.
#
# In practice the error in the horizon position is usually dominated
# by the errors from interpolating the Cactus gij and Kij to the horizon
# position, not by the angular finite differencing or interpatch interpolation
# errors.  Thus this parameter can be made fairly small (low resolution)
# for better performance, without seriously affecting the accuracy
# with which we can locate the horizon.
#
# *** IMPORTANT
# *** For any patch system type other than "full sphere", there are
# *** patches with 45 degree widths, so this parameter must be even.
#
# Normally we use Simpson's rule (in a variant which doesn't require the
# number of zones to be even) for angular integrations over the patch
# system.  However, if the number of angular zones in a patch is very
# small and odd (i.e. 3 or 5), then we have to use the trapezoid rule
# instead, so the integrations are less accurate.  This occurs for
# N_zones_per_right_angle = 3 or 5 for a full sphere patch system,
# or N_zones_per_right_angle = 6 or 10 for any other patch system type.
#
# For large values of this parameter, this thorn takes CPU time which
# scales roughly as the ...
# ... 6th power of this parameter
#     if Jacobian_store_solve_method = "dense matrix/LAPACK"
# ... 3rd power of this parameter
#     if Jacobian_store_solve_method = "row-oriented sparse matrix/ILUCG"
#
#int N_zones_per_right_angle[101] "sets angular resolution of patch systems" ACCUMULATOR-BASE max_N_zones_per_right_angle
int N_zones_per_right_angle[101] "sets angular resolution of patch systems"
{
1:* :: "any integer >= 1; must be even for patch systems other than full-sphere"
} 18

# Default parameter values do not trigger accumulator expressions.
# Therefore the default values here must be large enough for the default
# horizon shape.
#int max_N_zones_per_right_angle "the maximum of all N_zones_per_right_angle -- calculated automatically; do not set this parameter directly" ACCUMULATOR=((x>y)*x+!(x>y)*y)
int max_N_zones_per_right_angle "the maximum of all N_zones_per_right_angle"
{
1:* :: "must be at least the maximum of all N_zones_per_right_angle"
} 18

#
# This parameter sets the width of the interpatch ghost zones in the
# patch system.  Note that this thorn uses the terminology "ghost zone"
# for any of what Cactus in general now calls a "boundary zone" or a
# "symmetry zone" or a "patch zone".
#
# This parameter must be at least
# ... 2 if FINITE_DIFF_ORDER is set to 4 in "src/include/config.hh"
# ... 1 if FINITE_DIFF_ORDER is set to 2 in "src/include/config.hh"
# The code checks for this being too small, and reports a fatal error if so.
#
int ghost_zone_width "number of ghost zones on each side of a patch"
{
0:* :: "any integer >= 0"
} 2

#
# Our code that computes surface integrals over patches (used for
# computing BH diagnostics like centroids, areas, masses, etc) silently
# assumes that this parameter is == 1, so you should probably leave
# it at that setting.
#
int patch_overlap_width \
  "number of grid points that nominally-just-touching patches should overlap"
{
1:*:2 :: "any integer >= 0; current implementation requires that it be odd"
} 1

################################################################################

#
# ***** parameters for the Jacobian matrix *****
#

keyword Jacobian_compute_method "how do we compute the Jacobian matrix?" STEERABLE=recover
{
# for debugging only"
"numerical perturbation"    :: "n.b. this is *very* slow"

# use this for normal apparent horizon finding
"symbolic differentiation with finite diff d/dr" :: \
  "fast, tricky programming, uses only gij, dx gij, Kij"

# alas, this isn't implemented yet :(
"symbolic differentiation"  :: \
  "fast, tricky programming, uses gij, dx gij, dxx gij, Kij, dx Kij"
} "symbolic differentiation with finite diff d/dr"

########################################

#
# This parameter lists all known methods for storing the Jacobian and
# solving linear systems with the Jacobian as the LHS matrix.  See
# "src/include/config.hh" for which of these are actually compiled in at
# the moment.  N.b. each compiled-in method requires linking with the
# corresponding linear-solver library; see "src/make.configuration.defn"
# for details on these libraries.
#
keyword Jacobian_store_solve_method \
  "how do we store/linear-solve the Jacobian matrix?" STEERABLE=recover
{
# good for debugging, but very slow for large  N_zones_per_right_angle
"dense matrix/LAPACK"	:: \
  "store as (Fortran) dense matrix, solve with LAPACK routines"
# use this for normal runs
"row-oriented sparse matrix/ILUCG" :: \
  "store as sparse matrix (row-oriented storage format), \
   solve with ILUCG (incomplete LU decomposition - conjugate gradient) method"
# this will probably replace ILUCG, but it's not ready yet :(
"row-oriented sparse matrix/UMFPACK" :: \
  "store as sparse matrix (row-oriented storage format), \
   solve with UMFPACK (sparse LU decomposition) method"
} "row-oriented sparse matrix/UMFPACK"

#
# Extra parameters for individual linear solvers
#

# extra parameters for
#   Jacobian_store_solve_method == "row-oriented sparse matrix/ILUCG"
real ILUCG__error_tolerance "error tolerance for conjugate gradient iteration" STEERABLE=recover
{
(*:0.0)	:: \
  "negative ==> scale the absolute value by the floating point roundoff \
                threshold, e.g. -256.0 means to allow the last 8 bits of \
                the solution to be in error"
(0.0:*)	:: \
  "positive ==> error tolerance"
} 1.0e-10
boolean ILUCG__limit_CG_iterations \
  "should we limit the maximum number of conjugate gradient iterations allowed?" STEERABLE=recover
{
"false"	:: "no limit on CG iterations"
"true"	:: "limit to Neqns CG iterations"
} "true"

# extra parameters for
#   Jacobian_store_solve_method == "row-oriented sparse matrix/UMFPACK"
int UMFPACK__N_II_iterations \
  "number of iterative-improvement iterations to do inside UMFPACK \
   after the sparse LU decompose/solve, each time we solve a linear system" STEERABLE=recover
{
-1	:: "use the UMFPACK default"
0	:: "don't do iterative improvement"
1:*	:: "any positive integer \
	    (in practice a few iterations give almost all the benefit)"
} 0

########################################

#
# This parameter controls two different sorts of one-sided finite
# differencing:
# - numerical-perturbation Jacobian computations
# - the finite differencing part of the "symbolic differentiation
#   with finite diff d/dr" Jacobian computation
#
# Notes on this parameter:
# - don't set it too small or roundoff errors will become large
# - don't set it too large or finite differencing errors will become large
# In practice the default value should be fine
#
real Jacobian_perturbation_amplitude \
  "perturbation amplitude for 1-sided finite differencing for Jacobians" STEERABLE=recover
{
(0.0:* :: "any real number > 0"
} 1.0e-6

# if AHFinderDirect::method = "test Jacobian", should we test all
# known methods for computing the Jacobian, or just the numerical perturbation
# method (the latter may be useful of some other methods are broken)
boolean test_all_Jacobian_compute_methods \
  "should we test all Jacobian computation methods, or just NP?" STEERABLE=recover
{
} "true"

################################################################################

#
# ***** parameters for how we compute the slice's geometry *****
# ***** (gij, Kij, partial_k gij) *****
#

########################################

#
# parameters for geometry interpolation
#
# This 3D interpolator is used to interpolate gij and Kij from the
# Cactus grid to the position of each trial horizon surface, giving
# gij, Kij, and partial_x gij as outputs.  This interpolator must have
# the following properties:
# - It must support taking at least 1st derivatives as part of the
#   interpolation.
# - It should give at least $C^1$ interpolants for smooth data, otherwise
#   the Theta(h) function will have "spikes" and the Newton iteration may
#   fail to converge all the way down to tight error tolerances.  $C^2$
#   would be even better, but in practice a ($C^1$) Hermite interpolant
#   works well.
# In practice the default values for these parameters should work fine
# (with the AEIThorns/AEILocalInterp local interpolator).
#

string coordinate_system_name \
  "name under which the coordinate system is registered in Cactus" STEERABLE=recover
{
".+" :: "any nonempty string"
} "cart3d"

string geometry_interpolator_name \
  "name under which the geometry interpolation operator is registered in Cactus" STEERABLE=recover
{
".+" :: "any nonempty string"
} "Hermite polynomial interpolation"

#
# The defaults here are set up for 2nd order Hermite interpolation,
# which requires driver::ghost_size >= 2.
#
string geometry_interpolator_pars \
  "parameters for the geometry interpolator" STEERABLE=recover
{
".*" :: \
  "any string acceptable to Util_TableSetFromString() and to the interpolator"
} "order=2 \
   boundary_off_centering_tolerance={1.0e-10 1.0e-10 1.0e-10 1.0e-10 1.0e-10 1.0e-10} \
   boundary_extrapolation_tolerance={0.0 0.0 0.0 0.0 0.0 0.0}"

########################################

#
# for testing/debugging purposes, instead of interpolating the geometry
# from the Cactus grid, we can instead hardwire it to an Eddington-Finkelstein
# slice of Schwarzschild spacetime
#
boolean hardwire_Schwarzschild_EF_geometry \
  "should we hard-wire the geometry to Schwarzschild/EF instead of \
   interpolating from the Cactus grid?"
{
} "false"

real geometry__Schwarzschild_EF__mass "mass of Schwarzschild BH"
{
(0.0:*		:: "BH mass = any real number > 0"
} 1.0

real geometry__Schwarzschild_EF__x_posn "x coordinate of Schwarzschild BH"
{
*:* :: "any real number"
} 0.0
real geometry__Schwarzschild_EF__y_posn "y coordinate of Schwarzschild BH"
{
*:* :: "any real number"
} 0.0
real geometry__Schwarzschild_EF__z_posn "z coordinate of Schwarzschild BH"
{
*:* :: "any real number"
} 0.0

# some of the Schwarzschild/EF formulas have 0/0 limits on the z axis;
# this parameter controls where we switch from the generic formulas to
# the L'Hopital's-rule z axis limits
# - don't set this parameter too small or roundoff errors will be excessive
# - don't set this parameter too large or finite differencing errors will
#   be excessive
# in practice the default value should be fine
# n.b. this is used for centered finite differencing, unlike the Jacobian
real geometry__Schwarzschild_EF__epsilon \
  "threshold for sin^2 theta = (x^2+y^2)/r^2 below which we use z axis limits"
{
(0.0:*	:: "this should be somewhat above the floating-point roundoff level"
} 1.0e-9

# we compute partial_k g_ij by numerical finite differencing of the exact
# analytical g_ij values; this parameter sets the "grid spacing" for this
# - don't set this parameter too small or roundoff errors will be excessive
# - don't set this parameter too large or finite differencing errors will
#   be excessive
# in practice the default value should be fine
# ... n.b. this finite differencing is *centered*, unlike that in the
#          Jacobian computation
real geometry__Schwarzschild_EF__Delta_xyz \
  "finite diff pseuo-grid spacing for computing partial_k g_ij"
{
(0.0:* :: "any real number > 0"
} 1.0e-6

########################################

#
# These parameters control whether we check that various angular gridfns
# are finite (neither NaN nor infinity) at various points in evaluating
# the Theta(h) function.  These are pretty cheap tests, and they're quite
# useful in catching assorted wierdness, so it's probably worth leaving
# them enabled unless you're trying to squeeze every last nanosecond...
#
boolean check_that_h_is_finite \
  "should we check that horizon shape function h is finite?" \
  STEERABLE=recover
{
} "true"
boolean check_that_geometry_is_finite \
  "should we check the interpolated geometry variables are finite?" \
  STEERABLE=recover
{
} "true"

################################################################################

#
# ***** parameters for the interpatch interpolation *****
#

#
# This 1D local interpolator is used to interpolate the h function between
# angular patches.  Because any given patch boundary only interpolates
# from a single neighboring patch (this is to simplify the bookkeeping),
# near the patch corners this interpolator will have to be used off-centered.
# Thus it's desirable to use an interpolator which retains reasonable
# accuracy right up to the edge of the data range.  In practice a Lagrange
# polynomial interpolant works well.
#

string interpatch_interpolator_name \
  "name under which the interpatch interpolation operator is registered in Cactus" STEERABLE=recover
{
".+" :: "any string (in practice it should be nonempty)"
} "Lagrange polynomial interpolation"

string interpatch_interpolator_pars \
  "parameters for the interpatch interpolator" STEERABLE=recover
{
".*" :: \
  "any string acceptable to Util_TableSetFromString() and to the interpolator"
} "order=5"

################################################################################

#
# ***** parameters for the surface interpolation *****
#

#
# This 2D local interpolator is used to interpolate the horizon radius
# arbitrary angular coordinate, to find out whether a given xyz point is
# inside or outside the horizon.
#
# At present this interpolator is used only if  set_horizon_mask  is true.
# Otherwise, these parameters may both be set to empty strings.
#

string surface_interpolator_name \
  "name under which the surface interpolation operator is registered in Cactus" STEERABLE=recover
{
"^$" :: "the empty string if this interpolator isn't going to be used"
".+" :: "any string (in practice it should be nonempty)"
} "Lagrange polynomial interpolation"

string surface_interpolator_pars \
  "parameters for the surface interpolator" STEERABLE=recover
{
".*" :: \
  "any string acceptable to Util_TableSetFromString() and to the interpolator"
} "order=2 \
   boundary_off_centering_tolerance={1.0e-10 1.0e-10 1.0e-10 1.0e-10} \
   boundary_extrapolation_tolerance={0.0 0.0 0.0 0.0}"

################################################################################

#
# ***** parameters for computing line/surface integrals over the horizon *****
#

#
# In the descriptions, N is the number of grid zones in a patch
# in either the rho or the sigma direction, i.e. it's 45 or 90 degrees
# divided by delta_drho_dsigma.
#
keyword integral_method "how do we compute integrals over the horizon?" STEERABLE=recover
{
"trapezoid"		:: "alternate name for trapezoid rule"
"trapezoid rule"	:: "trapezoid rule (2nd order for smooth functions)"
"Simpson"		:: "alternate name for Simpson's rule"
"Simpson's rule"	:: \
  "Simpson's rule (4th order for smooth fns, requires N to be even)"
"Simpson (variant)"	:: "alternate name for Simpson's rule variant"
"Simpson's rule (variant)":: \
  "Simpson's rule variant (4th order for smooth fns, requires N >= 7)"

# choose this for normal use (assuming FINITE_DIFF_ORDER is set to 4
# in "src/include/config.hh")
"automatic choice"	:: \
  "choose Simpson's rule or variant if applicable, otherwise trapezoid rule"
} "automatic choice"

################################################################################

###
### ***** parameters for the test driver "src/patch/test_patch_system.cc" *****
###
### By default this test driver isn't compiled into the cactus executable,
### and these parameters are ignored.  To compile this test driver into
### the cactus executable (and have these parameters used),
### - edit the list of "source files in this directory" in
###   "src/patch/make.code.defn" to add  test_patch_system.cc
### - comment out the list of "subdirectories containing source files" in
###   "src/make.code.defn", and uncomment the alternate list which is
###   normally commented out there (this omits the higher-level directories
###   src/elliptic/, src/gr/, and src/driver/)
###
##keyword which_test "which test should we do?"
##{
##"gridfn"	:: "set up test fn(x,y,z), print it"
##"read gridfn"	:: "read in ghosted test fn(x,y,z), print it"
##"synchronize"	:: "set up test fn(x,y,z), synchronize it, print errors"
##"ghost zone Jacobian":: \
##  "set up test fn(x,y,z), compute Jacobian of gz.synchronize(), compare with NP"
##"derivatives"	:: "set up test fn(rho,sigma), take derivs, print errors"
##} "gridfn"
##
##int which_derivs "bit flags to specify which derivatives to test"
##{
##0:63 :: "any set of bit flags"
##} 63
##
### true ==> gives a more thorough test of the Jacobian,
###          but makes the test run much slower
### false ==> gives a slightly less thorough test, but runs faster
##boolean NP_Jacobian__perturb_all_y_patch_points \
##  "should we perturb at *all* points in the y patch, or just those with the \
##   iperp which is (supposedly) involved in the interpatch interpolation?"
##{
##} "true"
##
##################################################################################

configuration: REQUIRES THORNS: SpaceMask

OPTIONAL HDF5
{
}

context: patch/grid.hh:
```
// grid.hh -- classes for a 2D uniform tensor-product grid
// $Header$
//
// grid_arrays - data arrays for a 2D tensor-product grid
// grid - uniform 2D tensor-product grid
//

//
// prerequisites:
//    <stdio.h>
//    <assert.h>
//    <math.h>
//    "cctk.h" or "fake_cctk.h"
//    "stdc.h"
//    "config.hh"
//    "../jtutil/util.hh"
//    "../jtutil/array.hh"
//    "../jtutil/linear_map.hh"
//    "coords.hh"
//

// everything in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// grid_arrays - data arrays for a 2D tensor-product grid
//
// This is a helper class for class grid (below).  This class stores
// most of the actual grid function (gridfn) data arrays for a uniform
// tensor-product 2D grid.
//
// The integer grid coordinates are (irho,isigma).  This class deals
// with the grid solely at the level of arrays with integer subscripts;
// the derived class  grid  deals with the floating-point coordinates
// related to those subscripts.
//
// The grid has a nominal extent, surrounded by "ghost zones" on each
// side for finite differencing purposes.
//
// There are separate sets of nominal-grid and ghosted-grid gridfns.
// We identify a gridfn by a small-integer "grid function number", a.k.a.
// "gfn".  There are separate gfns for nominal and ghosted gridfns.
// In a very few places we refer to "unknown-grid" gridfns; these might
// be either nominal-grid or ghosted-grid.
//
// For our application (apparent horizon finding), it's useful for the
// storage for a single gridfn to be contiguous *across all patches*.
// (Note this means that the set of all our gridfns is *not* contiguous!)
// To accomplish this, we don't allocate the gridfns when we're created,
// but rather later, with a separate call  setup_gridfn_storage() .
// This way higher-level code can first create all patches, then count
// the total amount of storage used, allocate it, then finally call each
// patch again to set up its gridfns appropriately.
//

class	grid_arrays
	{
public:
	//
	// ***** {min,max}_{rho,sigma} "sides" of grid *****
	//

	//
	// A grid has 4 (angular) "sides", which we identify as
	// {min,max}_{rho,sigma}.  Given a side, we define coordinates
	// (perpendicular,parallel) to it, normally abbreviated to
	// (perp,par).
	//
	// As well as functions directly referring to a specific side,
	// we also support referring to one of these chosen at run-time,
	// via Boolean flags:
	//
	//	// generic (irho,isigma) coordinate
	//	iang = want_rho ? irho : isigma
	//
	//	// opposite (irho,isigma) coordinate
	//	ixang = want_rho ? isigma : irho
	//
	//	// generic (min,max) direction
	//	minmax = want_min ? min : max
	//
	// FIXME: This system of Boolean flags works ok, but it requires
	//	  a lot of repetitive code conditional-expression functions
	//	  in this class.  Is there a cleaner solution?

	// there are precisely this many possible sides
	enum { N_sides = 4 };

	// we specify {min,max} with a Boolean  want_min
	// ... values for want_min
	//     FIXME: these should really be bool, but then we couldn't
	//            use the "enum hack" for in-class constants
	enum { side_is_min = true, side_is_max = false };

	// we specify {rho,sigma} with a Boolean  want_rho
	// ... values for wanr_rho
	//     FIXME: these should really be bool, but then we couldn't
	//            use the "enum hack" for in-class constants
	enum { side_is_rho = true, side_is_sigma = false };

	// human-readable names for the sides (for debugging)
	static const char* minmax_name(bool minmax)
		{ return minmax ? "min" : "max"; }
	static const char* iang_name(bool want_rho)
		{ return want_rho ? "irho" : "isigma"; }


	//
	// ***** array info *****
	//
public:

	// nominal-grid min/max/sizes
	int min_irho() const { return min_irho_; }
	int max_irho() const { return max_irho_; }
	int min_isigma() const { return min_isigma_; }
	int max_isigma() const { return max_isigma_; }
	int min_iang(bool want_rho) const
		{ return want_rho ? min_irho() : min_isigma(); }
	int max_iang(bool want_rho) const
		{ return want_rho ? max_irho() : max_isigma(); }
	int minmax_iang(bool want_min, bool want_rho) const
		{ return want_min ? min_iang(want_rho) : max_iang(want_rho); }
	int N_irho() const
		{ return jtutil::how_many_in_range(min_irho(), max_irho()); }
	int N_isigma() const
		{
		return jtutil::how_many_in_range(min_isigma(), max_isigma());
		}
	int N_grid_points() const
		{ return N_irho() * N_isigma(); }

	// ghosted-grid min/max/sizes
	int ghosted_min_irho() const { return ghosted_min_irho_; }
	int ghosted_max_irho() const { return ghosted_max_irho_; }
	int ghosted_min_isigma() const
		{ return ghosted_min_isigma_; }
	int ghosted_max_isigma() const
		{ return ghosted_max_isigma_; }
	int ghosted_min_iang(bool want_rho) const
		{
		return want_rho ? ghosted_min_irho()
				: ghosted_min_isigma();
		}
	int ghosted_max_iang(bool want_rho) const
		{
		return want_rho ? ghosted_max_irho()
				: ghosted_max_isigma();
		}
	int ghosted_minmax_iang(bool want_min, bool want_rho) const
		{
		return want_min ? ghosted_min_iang(want_rho)
				: ghosted_max_iang(want_rho);
		}
	int ghosted_N_irho() const
		{
		return jtutil::how_many_in_range(ghosted_min_irho(),
						 ghosted_max_irho());
		}
	int ghosted_N_isigma() const
		{
		return jtutil::how_many_in_range(ghosted_min_isigma(),
						 ghosted_max_isigma());
		}
	int ghosted_N_grid_points() const
		{ return ghosted_N_irho() * ghosted_N_isigma(); }

	// "effective" grid min/max/sizes
	// (= dynamic select between nominal and full grids)
	int effective_min_irho(bool want_ghost_zones) const
		{
		return want_ghost_zones ? ghosted_min_irho() : min_irho();
		}
	int effective_max_irho(bool want_ghost_zones) const
		{
		return want_ghost_zones ? ghosted_max_irho() : max_irho();
		}
	int effective_min_isigma(bool want_ghost_zones) const
		{
		return want_ghost_zones ? ghosted_min_isigma() : min_isigma();
		}
	int effective_max_isigma(bool want_ghost_zones) const
		{
		return want_ghost_zones ? ghosted_max_isigma() : max_isigma();
		}
	int effective_N_irho(bool want_ghost_zones) const
		{ return want_ghost_zones ? ghosted_N_irho() : N_irho(); }
	int effective_N_isigma(bool want_ghost_zones) const
		{ return want_ghost_zones ? ghosted_N_isigma() : N_isigma(); }


	//
	// ***** ghost zones *****
	//
public:

	// ghost zone min/max perpendicular coordinates
	int min_rho_ghost_zone__min_iperp() const
		{ return ghosted_min_irho(); }
	int min_rho_ghost_zone__max_iperp() const
		{ return min_irho() - 1; }
	int max_rho_ghost_zone__min_iperp() const
		{ return max_irho() + 1; }
	int max_rho_ghost_zone__max_iperp() const
		{ return ghosted_max_irho(); }
	int min_sigma_ghost_zone__min_iperp() const
		{ return ghosted_min_isigma(); }
	int min_sigma_ghost_zone__max_iperp() const
		{ return min_isigma() - 1; }
	int max_sigma_ghost_zone__min_iperp() const
		{ return max_isigma() + 1; }
	int max_sigma_ghost_zone__max_iperp() const
		{ return ghosted_max_isigma(); }
	int minmax_ang_ghost_zone__min_iperp(bool want_min, bool want_rho) const
		{
		return want_min
		       ? (want_rho ? min_rho_ghost_zone__min_iperp()
				   : min_sigma_ghost_zone__min_iperp())
		       : (want_rho ? max_rho_ghost_zone__min_iperp()
				   : max_sigma_ghost_zone__min_iperp());
		}
	int minmax_ang_ghost_zone__max_iperp(bool want_min, bool want_rho) const
		{
		return want_min
		       ? (want_rho ? min_rho_ghost_zone__max_iperp()
				   : min_sigma_ghost_zone__max_iperp())
		       : (want_rho ? max_rho_ghost_zone__max_iperp()
				   : max_sigma_ghost_zone__max_iperp());
		}

	// ghost zone min/max parallel coordinates
	// ... not including corners
	int rho_ghost_zone_without_corners__min_ipar() const
		{ return min_isigma(); }
	int rho_ghost_zone_without_corners__max_ipar() const
		{ return max_isigma(); }
	int sigma_ghost_zone_without_corners__min_ipar() const
		{ return min_irho(); }
	int sigma_ghost_zone_without_corners__max_ipar() const
		{ return max_irho(); }
	int ang_ghost_zone_without_corners__min_ipar(bool want_rho) const
		{
		return want_rho ?   rho_ghost_zone_without_corners__min_ipar()
				: sigma_ghost_zone_without_corners__min_ipar();
		}
	int ang_ghost_zone_without_corners__max_ipar(bool want_rho) const
		{
		return want_rho ?   rho_ghost_zone_without_corners__max_ipar()
				: sigma_ghost_zone_without_corners__max_ipar();
		}
	// ... including corners
	int rho_ghost_zone_with_corners__min_ipar() const
		{ return ghosted_min_isigma(); }
	int rho_ghost_zone_with_corners__max_ipar() const
		{ return ghosted_max_isigma(); }
	int sigma_ghost_zone_with_corners__min_ipar() const
		{ return ghosted_min_irho(); }
	int sigma_ghost_zone_with_corners__max_ipar() const
		{ return ghosted_max_irho(); }
	int ang_ghost_zone_with_corners__min_ipar(bool want_rho) const
		{
		return want_rho ?   rho_ghost_zone_with_corners__min_ipar()
				: sigma_ghost_zone_with_corners__min_ipar();
		}
	int ang_ghost_zone_with_corners__max_ipar(bool want_rho) const
		{
		return want_rho ?   rho_ghost_zone_with_corners__max_ipar()
				: sigma_ghost_zone_with_corners__max_ipar();
		}


	//
	// ***** grid-point validity and membership predicates *****
	//
public:
	bool is_valid_irho(int irho) const
		{ return (irho >= min_irho()) && (irho <= max_irho()); }
	bool is_valid_isigma(int isigma) const
		{ return (isigma >= min_isigma()) && (isigma <= max_isigma()); }
	bool is_in_nominal_grid(int irho, int isigma) const
		{ return is_valid_irho(irho) && is_valid_isigma(isigma); }

	bool is_valid_ghosted_irho(int irho) const
		{
		return    (irho >= ghosted_min_irho())
		       && (irho <= ghosted_max_irho());
		}
	bool is_valid_ghosted_isigma(int isigma) const
		{
		return    (isigma >= ghosted_min_isigma())
		       && (isigma <= ghosted_max_isigma());
		}
	bool is_in_ghosted_grid(int irho, int isigma) const
		{
		return    is_valid_ghosted_irho(irho)
		       && is_valid_ghosted_isigma(isigma);
		}

	bool is_in_ghost_zone(int irho, int isigma) const
		{
		return     is_in_ghosted_grid(irho, isigma)
		       && !is_in_nominal_grid(irho, isigma);
		}


	//
	// ***** gfn ranges and validity predicates *****
	//
public:
	// gfn ranges
	int min_gfn() const
		{
		assert(gridfn_data_ != NULL);
		return (*gridfn_data_).min_i();
		}
	int max_gfn() const
		{
		assert(gridfn_data_ != NULL);
		return (*gridfn_data_).max_i();
		}
	int N_gridfns() const
		{ return jtutil::how_many_in_range(min_gfn(), max_gfn()); }
	int ghosted_min_gfn() const
		{
		assert(ghosted_gridfn_data_ != NULL);
		return (*ghosted_gridfn_data_).min_i();
		}
	int ghosted_max_gfn() const
		{
		assert(ghosted_gridfn_data_ != NULL);
		return (*ghosted_gridfn_data_).max_i();
		}
	int ghosted_N_gridfns() const
		{
		return jtutil::how_many_in_range(ghosted_min_gfn(),
						 ghosted_max_gfn());
		}

	// gfn validity predicates
	bool is_valid_gfn(int gfn) const
		{ return (gfn >= min_gfn()) && (gfn <= max_gfn()); }
	bool is_valid_ghosted_gfn(int gfn) const
		{
		return (gfn >= ghosted_min_gfn()) && (gfn <= ghosted_max_gfn());
		}


	//
	// ***** gridfns *****
	//
	// n.b. access to rvalue gridfn data must be via references
	//	in order to allow using  gridfn(...)  as the operand
	//	of a unary & (address-of) operator
	//
public:
	// access to nominal-grid gridfn data
	// ... rvalue
	const fp& gridfn(int gfn,   int irho, int isigma) const
		{
		assert(gridfn_data_ != NULL);
		return (*gridfn_data_)(gfn, irho, isigma);
		}
	// ... lvalue
	      fp& gridfn(int gfn,   int irho, int isigma)
		{
		assert(gridfn_data_ != NULL);
		return (*gridfn_data_)(gfn, irho, isigma);
		}

	// access to ghosted-grid gridfn data
	// ... rvalue
	const fp& ghosted_gridfn(int gfn,   int irho, int isigma) const
		{
		assert(gridfn_data_ != NULL);
		return (*ghosted_gridfn_data_)(gfn, irho, isigma);
		}
	// ... lvalue
	      fp& ghosted_gridfn(int gfn,   int irho, int isigma)
		{
		assert(gridfn_data_ != NULL);
		return (*ghosted_gridfn_data_)(gfn, irho, isigma);
		}

	// access to unknown-grid gridfn data
	// (either nominal or ghosted, depending on Boolean flag)
	// ... rvalue
	const fp& unknown_gridfn(bool ghosted_flag,
				 int unknown_gfn,   int irho, int isigma)
		const
		{
		return ghosted_flag ? ghosted_gridfn(unknown_gfn, irho,isigma)
				    :         gridfn(unknown_gfn, irho,isigma);
		}
	// ... lvalue
	      fp& unknown_gridfn(bool ghosted_flag,
				 int unknown_gfn,   int irho, int isigma)
		{
		return ghosted_flag ? ghosted_gridfn(unknown_gfn, irho,isigma)
				    :         gridfn(unknown_gfn, irho,isigma);
		}

	// subscripting info
	int gfn_stride() const
		{
		assert(gridfn_data_ != NULL);
		return gridfn_data_->subscript_stride_i();
		}
	int irho_stride() const
		{
		assert(gridfn_data_ != NULL);
		return gridfn_data_->subscript_stride_j();
		}
	int isigma_stride() const
		{
		assert(gridfn_data_ != NULL);
		return gridfn_data_->subscript_stride_k();
		}
	int iang_stride(bool want_rho) const
		{ return want_rho ? irho_stride() : isigma_stride(); }
	int ghosted_gfn_stride() const
		{
		assert(ghosted_gridfn_data_ != NULL);
		return ghosted_gridfn_data_->subscript_stride_i();
		}
	int ghosted_irho_stride() const
		{
		assert(ghosted_gridfn_data_ != NULL);
		return ghosted_gridfn_data_->subscript_stride_j();
		}
	int ghosted_isigma_stride() const
		{
		assert(ghosted_gridfn_data_ != NULL);
		return ghosted_gridfn_data_->subscript_stride_k();
		}
	int ghosted_iang_stride(bool want_rho) const
		{
		return want_rho ? ghosted_irho_stride()
				: ghosted_isigma_stride();
		}

	// validity predicates for 1-D 0-origin grid point number (gpn)
	bool is_valid_gpn(int gpn) const
		{ return (gpn >= 0) && (gpn < N_grid_points()); }
	bool is_valid_ghosted_gpn(int gpn) const
		{ return (gpn >= 0) && (gpn < ghosted_N_grid_points()); }

	// convert (irho,isigma) <--> 1-D 0-origin grid point number (gpn)
	int gpn_of_irho_isigma(int irho, int isigma) const
		{
		assert( is_valid_irho(irho) );
		assert( is_valid_isigma(isigma) );
		return   (irho   - min_irho()  ) * irho_stride()
		       + (isigma - min_isigma()) * isigma_stride();
		}
	int ghosted_gpn_of_irho_isigma(int irho, int isigma) const
		{
		assert( is_valid_ghosted_irho(irho) );
		assert( is_valid_ghosted_isigma(isigma) );
		return
		     (irho   - ghosted_min_irho()  ) * ghosted_irho_stride()
		   + (isigma - ghosted_min_isigma()) * ghosted_isigma_stride();
		}
	// ... current implementation assumes (& verifies) isigma is contiguous
	void irho_isigma_of_gpn(int gpn, int& irho, int& isigma) const
		{
		assert( is_valid_gpn(gpn) );
		assert( isigma_stride() == 1 );	// implementation restriction
		irho   = min_irho()   + gpn / N_isigma();
		isigma = min_isigma() + gpn % N_isigma();
		assert( is_valid_irho(irho) );
		assert( is_valid_isigma(isigma) );
		}
	// ... current implementation assumes (& verifies) isigma is contiguous
	void ghosted_irho_isigma_of_gpn(int gpn, int& irho, int& isigma) const
		{
		assert( is_valid_ghosted_gpn(gpn) );
		assert( ghosted_isigma_stride() == 1 );	// implementation
							// restriction
		irho   = ghosted_min_irho()   + gpn / ghosted_N_isigma();
		isigma = ghosted_min_isigma() + gpn % ghosted_N_isigma();
		assert( is_valid_ghosted_irho(irho) );
		assert( is_valid_ghosted_isigma(isigma) );
		}

	// low-level access to data arrays (!!dangerous!!)
	const fp* gridfn_data_array(int gfn) const
		{ return & gridfn(gfn, min_irho(),min_isigma()); }
	      fp* gridfn_data_array(int gfn)
		{ return & gridfn(gfn, min_irho(),min_isigma()); }
	const fp* ghosted_gridfn_data_array(int ghosted_gfn) const
		{
		return & ghosted_gridfn(ghosted_gfn, ghosted_min_irho(),
						     ghosted_min_isigma());
		}
	      fp* ghosted_gridfn_data_array(int ghosted_gfn)
		{
		return & ghosted_gridfn(ghosted_gfn, ghosted_min_irho(),
						     ghosted_min_isigma());
		}


	//
	// ***** argument structures for constructor et al *****
	//
public:
	// these structures bundle related arguments together so we don't
	// have 20+ (!) separate arguments to our top-level constructors
	struct	grid_array_pars
		{
		int min_irho,   max_irho;
		int min_isigma, max_isigma;
		int min_rho_ghost_zone_width,   max_rho_ghost_zone_width;
		int min_sigma_ghost_zone_width, max_sigma_ghost_zone_width;
		};
	struct	gridfn_pars
		{
		int min_gfn, max_gfn;

		// gridfn storage will be automatically allocated
		// if pointer is NULL; any 0 strides are automatically
		// set to C-style row-major subscripting
		fp* storage_array;
		int gfn_stride, irho_stride, isigma_stride;
		};

	//
	// ***** constructor, gridfn setup, destructor *****
	//
public:
	// construct with no gridfns
	grid_arrays(const grid_array_pars& grid_array_pars_in);

	// set up storage for gridfns
	void setup_gridfn_storage(const gridfn_pars& gridfn_pars_in,
				  const gridfn_pars& ghosted_gridfn_pars_in);

	~grid_arrays();

private:
	// we forbid copying and passing by value
	// by declaring the copy constructor and assignment operator
	// private, but never defining them
	grid_arrays(const grid_arrays& rhs);
	grid_arrays& operator=(const grid_arrays& rhs);

private:
	//
	// ***** the actual gridfn storage arrays *****
	//
	// n.b. these pointers are *first* data member in this class
	// ==> possibly slightly faster access (0 offset from pointer)
	// ... indices are (gfn, irho, isigma)
	jtutil::array3d<fp>* gridfn_data_;
	jtutil::array3d<fp>* ghosted_gridfn_data_;

	// gfn bounds
	const int min_gfn_,         max_gfn_;
	const int ghosted_min_gfn_, ghosted_max_gfn_;

	// nominal grid min/max bounds
	const int min_irho_,   max_irho_;
	const int min_isigma_, max_isigma_;

	// full grid min/max bounds
	const int ghosted_min_irho_,   ghosted_max_irho_;
	const int ghosted_min_isigma_, ghosted_max_isigma_;
	};

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

//
// grid - uniform 2D tensor-product grid
//
// The grid is uniform in the floating point grid coordinates (rho,sigma).
// There is also some (limited) support for expressing these coordinates
// in degrees (drho,dsigma); this is useful for humans trying to specify
// things in parameter files.
//
// The nominal (not including the ghost zones) angular grid boundaries
// may coincide with grid points, or they may be at "half-integer" grid
// coordinates.  That is, suppose we have a unit grid spacing, and a boundary
// at an angular coordinate of 0; then the grid may be either 0, 1, 2, ...,
// or 0.5, 1.5, 2.5, ... .
//

class	grid
	: public grid_arrays
	{
	//
	// ***** low-level access to coordinate maps *****
	//
public:
	// direct (read-only) access to the underlying linear_map objects
	// ... useful for (eg) passing to interpolators
	const jtutil::linear_map<fp>& rho_map() const { return rho_map_; }
	const jtutil::linear_map<fp>& sigma_map() const { return sigma_map_; }
	const jtutil::linear_map<fp>& ang_map(bool want_rho) const
		{ return want_rho ? rho_map() : sigma_map(); }


	//
	// ***** single-axis coordinate conversions *****
	//
public:
	// ... angles in radians
	fp rho_of_irho(int irho) const { return rho_map().fp_of_int(irho); }
	fp sigma_of_isigma(int isigma) const
		{ return sigma_map().fp_of_int(isigma); }
	fp ang_of_iang(bool want_rho, int iang) const
		{
		return want_rho ? rho_of_irho(iang)
				: sigma_of_isigma(iang);
		}

	fp fp_irho_of_rho(fp rho) const
		{ return rho_map().fp_int_of_fp(rho); }
	int irho_of_rho(fp rho, jtutil::linear_map<fp>::noninteger_action
				nia = jtutil::linear_map<fp>::nia_error)
		const
		{ return rho_map().int_of_fp(rho, nia); }
	fp fp_isigma_of_sigma(fp sigma) const
		{ return sigma_map().fp_int_of_fp(sigma); }
	int isigma_of_sigma(fp sigma, jtutil::linear_map<fp>::noninteger_action
				      nia = jtutil::linear_map<fp>::nia_error)
		const
		{ return sigma_map().int_of_fp(sigma, nia); }
	fp fp_iang_of_ang(bool want_rho, fp ang)
		const
		{
		return want_rho ? fp_irho_of_rho(ang)
				: fp_isigma_of_sigma(ang);
		}
	int iang_of_ang(bool want_rho,
			fp ang, jtutil::linear_map<fp>::noninteger_action
				nia = jtutil::linear_map<fp>::nia_error)
		const
		{
		return want_rho ? irho_of_rho(ang, nia)
				: isigma_of_sigma(ang, nia);
		}

	// ... angles in degrees
	fp rho_of_drho(fp drho) const
		{ return jtutil::radians_of_degrees(drho); }
	fp sigma_of_dsigma(fp dsigma) const
		{ return jtutil::radians_of_degrees(dsigma); }
	fp drho_of_rho(fp rho) const
		{ return jtutil::degrees_of_radians(rho); }
	fp dsigma_of_sigma(fp sigma) const
		{ return jtutil::degrees_of_radians(sigma); }
	fp drho_of_irho(int irho) const
		{ return jtutil::degrees_of_radians(rho_of_irho(irho)); }
	fp dsigma_of_isigma(int isigma) const
		{ return jtutil::degrees_of_radians(sigma_of_isigma(isigma)); }

	int irho_of_drho(fp drho, jtutil::linear_map<fp>::noninteger_action
				  nia = jtutil::linear_map<fp>::nia_error)
		const
		{ return irho_of_rho(jtutil::radians_of_degrees(drho), nia); }
	int isigma_of_dsigma(fp dsigma,
			     jtutil::linear_map<fp>::noninteger_action
			        nia = jtutil::linear_map<fp>::nia_error)
		const
		{
		return isigma_of_sigma(jtutil::radians_of_degrees(dsigma), nia);
		}


	//
	// ***** grid info *****
	//
public:
	// grid spacings
	fp delta_rho() const { return rho_map().delta_fp(); }
	fp delta_sigma() const { return sigma_map().delta_fp(); }
	fp delta_drho() const
		{ return jtutil::degrees_of_radians(delta_rho()); }
	fp delta_dsigma() const
		{ return jtutil::degrees_of_radians(delta_sigma()); }
	fp delta_ang(bool want_rho) const
		{ return want_rho ? delta_rho() : delta_sigma(); }
	fp delta_dang(bool want_rho) const
		{ return want_rho ? delta_drho() : delta_dsigma(); }

	// inverse grid spacings
	fp inverse_delta_rho() const { return rho_map().inverse_delta_fp(); }
	fp inverse_delta_sigma() const
		{ return sigma_map().inverse_delta_fp(); }

	// nominal grid min/max
	fp min_rho() const { return min_rho_; }
	fp max_rho() const { return max_rho_; }
	fp min_sigma() const { return min_sigma_; }
	fp max_sigma() const { return max_sigma_; }
	fp minmax_ang(bool want_min, bool want_rho) const
		{
		return want_min ? (want_rho ? min_rho() : min_sigma())
				: (want_rho ? max_rho() : max_sigma());
		}
	fp min_drho() const { return jtutil::degrees_of_radians(min_rho()); }
	fp max_drho() const { return jtutil::degrees_of_radians(max_rho()); }
	fp min_dsigma() const
		{ return jtutil::degrees_of_radians(min_sigma()); }
	fp max_dsigma() const
		{ return jtutil::degrees_of_radians(max_sigma()); }
	fp min_dang(bool want_rho) const
		{ return want_rho ? min_drho() : min_dsigma(); }
	fp max_dang(bool want_rho) const
		{ return want_rho ? max_drho() : max_dsigma(); }

	// ghosted-grid min/max
	fp ghosted_min_rho() const
		{ return rho_of_irho(ghosted_min_irho()); }
	fp ghosted_max_rho() const
		{ return rho_of_irho(ghosted_max_irho()); }
	fp ghosted_min_sigma() const
		{ return sigma_of_isigma(ghosted_min_isigma()); }
	fp ghosted_max_sigma() const
		{ return sigma_of_isigma(ghosted_max_isigma()); }

	// is a given (drho,dsigma) within the grid?
	bool is_valid_drho(fp drho) const
		{
		return    jtutil::fuzzy<fp>::GE(drho, min_drho())
		       && jtutil::fuzzy<fp>::LE(drho, max_drho());
		}
	bool is_valid_dsigma(fp dsigma) const
		{
		return    jtutil::fuzzy<fp>::GE(dsigma, min_dsigma())
		       && jtutil::fuzzy<fp>::LE(dsigma, max_dsigma());
		}

	// reduce a rho/sigma coordinate modulo 2*pi radians (360 degrees)
	// to be within the ghosted grid,
	// or error_exit() if no such value exists
	fp modulo_reduce_rho(fp rho_in) const
		{
		return local_coords
		       ::modulo_reduce_ang(rho_in, ghosted_min_rho(),
						   ghosted_max_rho());
		}
	fp modulo_reduce_sigma(fp sigma_in) const
		{
		return local_coords
		       ::modulo_reduce_ang(sigma_in, ghosted_min_sigma(),
						     ghosted_max_sigma());
		}
	fp modulo_reduce_ang(bool want_rho, fp ang_in) const
		{
		return want_rho ? modulo_reduce_rho(ang_in)
				: modulo_reduce_sigma(ang_in);
		}

	//
	// ***** misc stuff *****
	//
public:
	// human-readable names for the sides (for debugging)
	static const char* ang_name(bool want_rho)
		{ return want_rho ?  "rho" :  "sigma"; }
	static const char* dang_name(bool want_rho)
		{ return want_rho ? "drho" : "dsigma"; }


	//
	// ***** argument structure for constructor *****
	//

	// this structure bundles related arguments together so we don't
	// have 20+ (!) separate arguments to our top-level constructors
	struct	grid_pars		// *** note angles in degrees ***
		{
		fp min_drho,   delta_drho,   max_drho;
		fp min_dsigma, delta_dsigma, max_dsigma;
		};


	//
	// ***** constructor, destructor *****
	//
	grid(const grid_array_pars& grid_array_pars_in,
	     const grid_pars& grid_pars_in);
	// compiler-generated default destructor is ok

private:
	// we forbid copying and passing by value
	// by declaring the copy constructor and assignment operator
	// private, but never defining them
	grid(const grid& rhs);
	grid& operator=(const grid& rhs);

private:
	// range of these is the full grid (including ghost zones)
	const jtutil::linear_map<fp> rho_map_, sigma_map_;

	// angular boundaries of nominal grid
	const fp min_rho_,   max_rho_;
	const fp min_sigma_, max_sigma_;
	};

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

	  }	// namespace AHFinderDirect

```

patch/fd_grid.hh:
```
// fd_grid.hh -- grid with finite differencing operations
// $Header$
//
// *** Implementation Notes -- Overview ***
// *** Implementation Notes -- Techniques using C++ Templates ***
// *** Implementation Notes -- Techniques using the C/C++ Preprocessor ***
// *** Implementation Notes -- Run-Time Choice of Molecules ***
// *** finite difference molecules ***
// ***** fd_grid - grid with finite differencing operations *****
//

//
// prerequisites:
//    <stdio.h>
//    <assert.h>
//    <math.h>
//    "stdc.h"
//    "config.hh"
//    "../jtutil/util.hh"
//    "../jtutil/array.hh"
//    "../jtutil/linear_map.hh"
//    "coords.hh"
//    "grid.hh"
//

// everything in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// *** Implementation Notes -- Overview ***
//

//
// The key design problem for our finite differencing is how to
// implement an entire family of 5(9) finite difference operations in
// 2D(3D)
//
//	partial_rho		partial_sigma
//	partial_{rho,rho}	partial_{rho,sigma}
//				partial_{sigma,sigma}
//
//	partial_x		partial_y		partial_z
//	partial_xx		partial_xy		partial_xz
//				partial_yy		partial_yz
//							partial_zz
//
// without having to write out the finite differencing molecules multiple
// times, and while still preserving maximum inline-function efficiency.
// In particular, mixed 2nd-order derivative operations like partial_xy
// should be automatically composed from the two individual 1st derivative
// operations (partial_x and partial_y).
//

//
// Our basic approach is to define each finite difference molecule in
// a generic 1-dimensional form using an abstract "data(m)" interface.
// Here we use the terminology that a finite difference molecule is
// defined as
//	out[k] = sum(m) c[m] * in[k+m]
// where c[] is the vector/matrix of molecule coefficients, and m is
// the (integer) relative grid coordinate within a molecule.
//
// That is, for example, we define the usual 2nd order centered 1st
// derivative operator as
//	diff = 0.5*inv_delta_x*(data(+1) - data(-1))
// leaving unspecified just what the data source is.  We then use this
// with an appropriate data source (indexing along that gridfn array axis)
// for each directional derivative operation, and we compose two of
// these, using the first along x as the data source for the second
// along y, for the mixed 2nd-order derivative operation.
//

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

//
// *** Implementation Notes -- Techniques using C++ Templates ***
// 

//
// There are two plausible ways to use C++ templates
//	[C++ templates are described in detail in chapter 13 of
//	Stroustrup's "The C++ Programming Language" (3rd Edition),
//	hereinafter "C++PL", and chapter 15 of Stroustrup's
//	"The Design and Evolution of C++", hereinafter "D&EC++".]
// to write the sort of generic-at-compile-time code we want:
// - Template specializations for each axis, as discussed in D&EC++
//   section 15.10.3.
// - Overloaded functions for each axis, with an argument type
//   (possibly that of an extra unused argument) selecting the
//   appropriate axis and hence the appropriate function.  This
//   technique is discussed in D&EC++ section 15.6.3.1.
//
// Quoting from D&EC++ (section 15.6.3.1),
//
//	The fundamental observation is that every property
//	of a type or an algorithm can be represented by a
//	type (possibly defined specificaly to do exactly
//	that).  That done, such a type can be used to guide
//	the overload resolution to select a function that
//	depends on the desired property.  [...]
//
//	Please note that thanks to inlining this resolution
//	is done at compile-time, so the appropriate [...]
//	function will be called directly without any run-time
//	overhead.
//
// Quoting from C++PL3 (section 13.4),
//
//	Passing [...] operations as a template parameter has two
//	significant benefits compared to alternatives such as
//	passing pointers to functions.  Several operations can
//	be passed as a single argument with no run-time cost.
//	In addition, the [...] operators [passed this way] are
//	trivial to inline, whereas inlininkg a call through a
//	pointer to function requires exceptional attention from
//	a compiler.
//

//
// In my opinion the template-specialization design is cleaner, and it
// clearly has no run-time cost (whereas the overloaded-function design
// may have a run-time cost for constructing and passing unused objects),
// so we use it here.
//
// There are, however, two (non-fatal) problema with this approach:
// - Unfortunately, it appears C++ (or at least gcc 2.95.1) forbids
//   template specialization within a class, so some of the functions
//   which whould logically be class members, must instead be defined
//   outside any class.  We use the namespace  fd_stuff::  to hide
//   these from the outside world.
// - C++PL3, section C.13.3, states that
//	Only class templates can be template arguments.
//   so we have to use dummy classes around some of our template
//   functions.  To avoid extra constructor/destructor overhead, we
//   make these template functions static.
//

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

//
// *** Implementation Notes -- Techniques using the C/C++ Preprocessor ***
//

//
// The fundamental problem with the template approaches is portability:
// Although the C++ standard describes powerful template facilities, not
// all C++ compilers yet fully support these.  As an alternative, we can
// use the C/C++ preprocessor.  This is ugly and dangerous (global names!),
// but is probably simpler than any of the template approaches.  It can
// provide the same finite differencing functionality and efficiency as
// the template-based approaches.
//
// Because of its greater portability, we use the preprocessor-based
// approach here.
//

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


//
// *** Implementation Notes -- Run-Time Choice of Molecules ***
//
// *If* we want to allow the finite differencing scheme to be changed
// at run-time (e.g. from a parameter file), there are three plausible
// ways to do this:
// - Using  switch(molecule_type) , as is standard in C.  This is
//   simple, and for this particular application quite well-structured
//   and maintainable (there are only a few different molecule types,
//   all centralized in this file).
// - Using virtual functions, with  molecule  a virtual base class
//   and individual molecules derived from it.  This is elegant, but
//   may have some performance problems (below).  It also requires some
//   sort of switch-based "object factory" to interface with with the
//   molecule-choice parameters.
// - Write all the finite differencing code multiple times, once for
//   each finite differencing scheme.
//
// The typical use of these functions will be from within a loop over
// a whole grid.  In both cases we can expect excellent accuracy from
// modern hardware branch prediction (and thus minimal performance loss
// from the branching).  It's reasonable to expect a compiler to fully
// inline the switch-based code, exposing all the gridfn array subscriptings
// to strength reduction etc, but this is much trickier for the
// virtual-function--based code.  For this reason, the switch-based
// design seems superior to the virtual-function--based one.
//
// However, at present we don't implement any run-time selection: we
// "just" fix the finite differencing scheme at compile time via the
// preprocessor.
//

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

//
// *** finite difference molecules ***
//

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

//
// define the actual molecules
//
// In the following macros, we first define all the distinct floating-
// -point numbers appearing in a molecules as "K" constants (all > 0),
// then define the actual derivative and its molecule coefficients
// using +/- the "K" constants, with multiplies by 1.0 elided and 0
// terms skipped in computing the derivative.  This (hopefully) gives
// maximum efficiency by avoiding the generated code loading the same
// constants multiple times.
// 

//
// The molecule macros all take the following arguments:
// inv_delta_x_ = inverse of grid spacing in the finite differencing
//		  direction
// data_= a data-fetching function or macro: data_(ghosted_gfn, irho, isigma)
//	  is the data to be finite differenced
// irho_plus_m_ = a function or macro: irho_plus_m_(irho,m) returns the
//		  rho coordinate to be passed to data_() for the [m]
//		  molecule coefficient
// isigma_plus_m_ = same thing, for the sigma coordinate
//
// n.b. We grab the variables ghosted_gfn, irho, and isigma from the calling
//      environment, and we define assorted local variables as needed!
//

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

//
// 2nd order
//

#define FD_GRID__ORDER2__MOL_RADIUS	1
#define FD_GRID__ORDER2__MOL_DIAMETER	3

#define FD_GRID__ORDER2__DX__KPM1	0.5
#define FD_GRID__ORDER2__DX(inv_delta_x_, data_,			\
			    irho_plus_m_, isigma_plus_m_)		\
	const fp data_p1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,+1),		\
				 isigma_plus_m_(isigma,+1));		\
	const fp data_m1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,-1),		\
				 isigma_plus_m_(isigma,-1));		\
	const fp sum = FD_GRID__ORDER2__DX__KPM1 * (data_p1 - data_m1);	\
	return inv_delta_x_ * sum;				/* end macro */
#define FD_GRID__ORDER2__DX__COEFF_M1	(-FD_GRID__ORDER2__DX__KPM1)
#define FD_GRID__ORDER2__DX__COEFF_0	0.0
#define FD_GRID__ORDER2__DX__COEFF_P1	(+FD_GRID__ORDER2__DX__KPM1)

#define FD_GRID__ORDER2__DXX__K0		2.0
#define FD_GRID__ORDER2__DXX(inv_delta_x_, data_,			\
			     irho_plus_m_, isigma_plus_m_)		\
	const fp data_p1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,+1),		\
				 isigma_plus_m_(isigma,+1));		\
	const fp data_0  = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  , 0),		\
				 isigma_plus_m_(isigma, 0));		\
	const fp data_m1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,-1),		\
				 isigma_plus_m_(isigma,-1));		\
	const fp sum							\
		= data_m1 - FD_GRID__ORDER2__DXX__K0*data_0 + data_p1;	\
	return jtutil::pow2(inv_delta_x_) * sum;		/* end macro */
#define FD_GRID__ORDER2__DXX__COEFF_M1	1.0
#define FD_GRID__ORDER2__DXX__COEFF_0	(-FD_GRID__ORDER2__DXX__K0)
#define FD_GRID__ORDER2__DXX__COEFF_P1	1.0

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

//
// 4th order
//

#define FD_GRID__ORDER4__MOL_RADIUS	2
#define FD_GRID__ORDER4__MOL_DIAMETER	5

#define FD_GRID__ORDER4__DX__KPM2	(1.0/12.0)
#define FD_GRID__ORDER4__DX__KPM1	(8.0/12.0)
#define FD_GRID__ORDER4__DX(inv_delta_x_, data_,			\
			    irho_plus_m_, isigma_plus_m_)		\
	const fp data_p2 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,+2),		\
				 isigma_plus_m_(isigma,+2));		\
	const fp data_p1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,+1),		\
				 isigma_plus_m_(isigma,+1));		\
	const fp data_m1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,-1),		\
				 isigma_plus_m_(isigma,-1));		\
	const fp data_m2 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,-2),		\
				 isigma_plus_m_(isigma,-2));		\
	const fp sum							\
		=   FD_GRID__ORDER4__DX__KPM1 * (data_p1 - data_m1)	\
		  + FD_GRID__ORDER4__DX__KPM2 * (data_m2 - data_p2);	\
	return inv_delta_x_ * sum;				/* end macro */
#define FD_GRID__ORDER4__DX__COEFF_M2	(+FD_GRID__ORDER4__DX__KPM2)
#define FD_GRID__ORDER4__DX__COEFF_M1	(-FD_GRID__ORDER4__DX__KPM1)
#define FD_GRID__ORDER4__DX__COEFF_0	0.0
#define FD_GRID__ORDER4__DX__COEFF_P1	(+FD_GRID__ORDER4__DX__KPM1)
#define FD_GRID__ORDER4__DX__COEFF_P2	(-FD_GRID__ORDER4__DX__KPM2)

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

#define FD_GRID__ORDER4__DXX__KPM2	( 1.0/12.0)
#define FD_GRID__ORDER4__DXX__KPM1	(16.0/12.0)
#define FD_GRID__ORDER4__DXX__K0	(30.0/12.0)
#define FD_GRID__ORDER4__DXX(inv_delta_x_, data_,			\
			     irho_plus_m_, isigma_plus_m_)		\
	const fp data_p2 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,+2),		\
				 isigma_plus_m_(isigma,+2));		\
	const fp data_p1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,+1),		\
				 isigma_plus_m_(isigma,+1));		\
	const fp data_0  = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  , 0),		\
				 isigma_plus_m_(isigma, 0));		\
	const fp data_m1 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,-1),		\
				 isigma_plus_m_(isigma,-1));		\
	const fp data_m2 = data_(ghosted_gfn,				\
				   irho_plus_m_(irho  ,-2),		\
				 isigma_plus_m_(isigma,-2));		\
	const fp sum							\
		= - FD_GRID__ORDER4__DXX__K0 * data_0			\
		  + FD_GRID__ORDER4__DXX__KPM1 * (data_m1 + data_p1)	\
		  - FD_GRID__ORDER4__DXX__KPM2 * (data_m2 + data_p2);	\
	return jtutil::pow2(inv_delta_x_) * sum;		/* end macro */
#define FD_GRID__ORDER4__DXX__COEFF_M2	(-FD_GRID__ORDER4__DXX__KPM2)
#define FD_GRID__ORDER4__DXX__COEFF_M1	(+FD_GRID__ORDER4__DXX__KPM1)
#define FD_GRID__ORDER4__DXX__COEFF_0	(-FD_GRID__ORDER4__DXX__K0  )
#define FD_GRID__ORDER4__DXX__COEFF_P1	(+FD_GRID__ORDER4__DXX__KPM1)
#define FD_GRID__ORDER4__DXX__COEFF_P2	(-FD_GRID__ORDER4__DXX__KPM2)

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

//
// choose finite differencing order via preprocessor symbol FINITE_DIFF_ORDER
//
#ifndef FINITE_DIFF_ORDER
  #error "must define FINITE_DIFF_ORDER!"
#endif

#if   (FINITE_DIFF_ORDER == 2)
  #define FD_GRID__MOL_RADIUS	FD_GRID__ORDER2__MOL_RADIUS
  #define FD_GRID__MOL_DIAMETER	FD_GRID__ORDER2__MOL_DIAMETER
  #define FD_GRID__DX		FD_GRID__ORDER2__DX
  #define FD_GRID__DXX		FD_GRID__ORDER2__DXX
#elif (FINITE_DIFF_ORDER == 4)
  #define FD_GRID__MOL_RADIUS	FD_GRID__ORDER4__MOL_RADIUS
  #define FD_GRID__MOL_DIAMETER	FD_GRID__ORDER4__MOL_DIAMETER
  #define FD_GRID__DX		FD_GRID__ORDER4__DX
  #define FD_GRID__DXX		FD_GRID__ORDER4__DXX
#else
  #error "unknown value " FINITE_DIFF_ORDER " for FINITE_DIFF_ORDER!"
#endif

#define FD_GRID__MOL_AREA	(FD_GRID__MOL_DIAMETER * FD_GRID__MOL_DIAMETER)

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

//
// ***** fd_grid - grid with finite differencing operations *****
//
// An  fd_grid  is identical to a  grid  except that it also defines
// (rho,sigma)-coordinate finite differencing operations on gridfns.
//

class	fd_grid
	: public grid
	{
	//
	// molecule sizes
	//
public:
	// n.b. this interface implicitly assumes that all molecules
	//      are centered and are the same order and size
	static
	  int finite_diff_order() { return FINITE_DIFF_ORDER; }
	static
	  int molecule_radius()   { return FD_GRID__MOL_RADIUS; }
	static
	  int molecule_diameter() { return FD_GRID__MOL_DIAMETER; }
	static
	  int molecule_min_m() { return -FD_GRID__MOL_RADIUS; }
	static
	  int molecule_max_m() { return  FD_GRID__MOL_RADIUS; }

	//
	// helper functions to compute (irho,isigma) + [m]
	// along each axis
	//
private:
	static
	  int     rho_axis__irho_plus_m(int irho  , int m) { return irho  +m; }
	static
	  int   rho_axis__isigma_plus_m(int isigma, int m) { return isigma  ; }
	static
	  int   sigma_axis__irho_plus_m(int irho  , int m) { return irho    ; }
	static
	  int sigma_axis__isigma_plus_m(int isigma, int m) { return isigma+m; }


	//
	// ***** finite differencing *****
	//
public:

	// 1st derivatives
	fp partial_rho(int ghosted_gfn,  int irho, int isigma)
		const
		{
		FD_GRID__DX(inverse_delta_rho(),
			    ghosted_gridfn,
			    rho_axis__irho_plus_m,
			    rho_axis__isigma_plus_m);
		}
	fp partial_sigma(int ghosted_gfn,  int irho, int isigma)
		const
		{
		FD_GRID__DX(inverse_delta_sigma(),
			    ghosted_gridfn,
			    sigma_axis__irho_plus_m,
			    sigma_axis__isigma_plus_m);
		}

	// "pure" 2nd derivatives
	fp partial_rho_rho(int ghosted_gfn,  int irho, int isigma)
		const
		{
		FD_GRID__DXX(inverse_delta_rho(),
			     ghosted_gridfn,
			     rho_axis__irho_plus_m,
			     rho_axis__isigma_plus_m);
		}
	fp partial_sigma_sigma(int ghosted_gfn,  int irho, int isigma)
		const
		{
		FD_GRID__DXX(inverse_delta_sigma(),
			     ghosted_gridfn,
			     sigma_axis__irho_plus_m,
			     sigma_axis__isigma_plus_m);
		}

	// mixed 2nd partial derivative
	fp partial_rho_sigma(int ghosted_gfn,   int irho, int isigma)
		const
		{
		FD_GRID__DX(inverse_delta_rho(),
			    partial_sigma,
			    rho_axis__irho_plus_m,
			    rho_axis__isigma_plus_m);
		}


	//
	// ***** molecule coefficients *****
	//
public:
	// molecule coefficients
	// n.b. this interface implicitly assumes that all molecules
	//      are position-independent
	fp partial_rho_coeff        (int m) const
		{ return inverse_delta_rho()   * dx_coeff(m); }
	fp partial_sigma_coeff      (int m) const
		{ return inverse_delta_sigma() * dx_coeff(m); }
	fp partial_rho_rho_coeff    (int m) const
		{ return jtutil::pow2(inverse_delta_rho()) * dxx_coeff(m); }
	fp partial_sigma_sigma_coeff(int m) const
		{ return jtutil::pow2(inverse_delta_sigma()) * dxx_coeff(m); }
	fp partial_rho_sigma_coeff(int m_rho, int m_sigma) const
		{
		return partial_rho_coeff(m_rho) * partial_sigma_coeff(m_sigma);
		}

	// worker functions: molecule coefficients for unit grid spacing
private:
	static
	  fp dx_coeff(int m);
	static
	  fp dxx_coeff(int m);


	//
	// ***** constructor, destructor *****
	//
public:

	// constructor: pass through to grid:: constructor
	fd_grid(const grid_array_pars& grid_array_pars_in,
		const grid_pars& grid_pars_in)
		: grid(grid_array_pars_in, grid_pars_in)
		{ }
	// compiler-generated default destructor is ok


private:
        // we forbid copying and passing by value
        // by declaring the copy constructor and assignment operator
        // private, but never defining them
	fd_grid(const fd_grid& rhs);
	fd_grid& operator=(const fd_grid& rhs);
	};

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

	  }	// namespace AHFinderDirect

```

patch/patch.hh:
```
// patch.hh -- describes a coordinate/grid patch
// $Header$
//
// ***** how patch boundaries are handled *****
// patch - abstract base class to describe a coordinate/grid patch
//
// z_patch - derived class for a +/- z patch
// x_patch - derived class for a +/- x patch
// y_patch - derived class for a +/- y patch
//

//
// prerequisites:
//	<stdio.h>
//	<assert.h>
//	<math.h>
//	"stdc.h"
//	"config.hh"
//	"../jtutil/util.hh"
//	"../jtutil/array.hh"
//	"../jtutil/linear_map.hh"
//	"coords.hh"
//	"grid.hh"
//	"fd_grid.hh"
//

// everything in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// ***** how patch boundaries are handled *****
//

//
// Basically, we handle patch boundaries using the usual "ghost zone"
// technique, interpolating values from neighboring patches as necessary.
//
// In more detail, we use the following interrelated types of objects
// to handle patch boundaries:
//
// A  patch_edge  object represents the basic geometry of a min/max
// rho/sigma side of a patch, i.e. it provides which-side-am-I predicates,
// coordinate conversions between (perp,par) and (rho,sigma), etc.
// Every patch has (points to) 4  patch_edge  objects, one for each of
// the patch's sides.
//
// A  ghost_zone  object describes a patch's ghost zone, and knows how
// to fill in gridfns there based on either the patch system's symmetry
// or interpolation from a neighboring patch.  ghost_zone is an abstract
// base class, from which we derive two classes:
// * A  symmetry_ghost_zone  object describes a ghost zone which is a
//   (discrete) symmetry of spacetime, either mirror-image or periodic.
//   Such an object knows how to fill in ghost-zone gridfn data from
//   the "other side" of the symmetry.
// * An  interpatch_ghost_zone  object describes a ghost zone which
//   overlaps another patch.  Such an object knows how to get ghost
//   zone gridfn data from the other patch.  More accurately, it gets
//   the data by asking (calling) the appropriate one of the other
//   patch's  patch_interp  objects.
// Every patch has (points to) 4  ghost_zone  objects, one for each of
// the patch's sides.
//
// A  patch_interp  object does the actual interpolation of data from
// within a patch (for filling in data in another patch's ghost zone).
// A  patch_interp  object points to the patch and patch_edge where it
// will be interpolating.
//
// For example, suppose we have two patches p and q with a common
// angular boundary.  Then the desired network of pointers looks like
// this (omitting the  patch_edge  objects for simplicity):
//
// +-----+                                                       +-----+
// |     | <--> p.interpatch_ghost_zone ---> q.patch_interp ---> |     |
// |  p  |                                                       |  q  |
// |     | <--- p.patch_interp <--- q.interpatch_ghost_zone <--> |     |
// +-----+                                                       +-----+
//
// Because of the mutual pointers, we can't easily construct (say) p's
// interpatch_ghost_zone until after q itself has been constructed, and
// vice versa.  Moreover, the  patch_interp::  constructor needs the
// adjacent-side  ghost_zone  objects to already exist, and it needs to
// know the iperp range of the interpolation region, which can only be
// computed from the adjacent-patch  interpatch_ghost_zone  object.
//
// The solution adopted here is to use a 3-phase algorithm, ultimately
// driven by the patch_system constructor:
// * The patch constructors themselves construct the  patch_edge  objects
//   and links them to/from the patches.
// * The patch_system constructor calls the appropriate functions
//	patch::create_mirror_symmetry_ghost_zone()
//	patch::create_periodic_symmetry_ghost_zone()
//	patch::create_interpatch_ghost_zone()
//   to construct the  ghost_zone  objects and link them to/from the
//   patches.
// * The patch_system constructor calls the functions
//	interpatch_ghost_zone::finish_setup()
//   to finish setting up the  interpatch_ghost_zone  objects, construct
//   the other patch's  patch_interp  objects, and finish linking the
//    interpatch_ghost_zone  objects to the  patch_interp  objects.
//

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

//
// patch - abstract base class to describe a generic coordinate/grid patch
//

//
// There are 3 types of patches, z, x, and y.  Each type uses two of
// (mu,nu,phi) as its angular coordinates (rho,sigma); the remaining
// "unused" one of (mu,nu,phi) is tau.
//
//	z patch ==> (rho,sigma) = (mu,nu)    tau = phi
//	x patch ==> (rho,sigma) = (nu,phi)   tau = mu
//	y patch ==> (rho,sigma) = (mu,phi)   tau = nu
//

// forward declarations
class patch_edge;
class ghost_zone;
class symmetry_ghost_zone;
class interpatch_ghost_zone;
class patch_interp;
class patch_system;

//
// const qualifiers refer to the gridfn values
//
class	patch
	: public fd_grid
	{
	//
	// ***** patch system, type, and coordinate metadata *****
	//
public:

	// to which patch system do we belong?
	patch_system& my_patch_system() const
		{ return my_patch_system_; }

	// each patch has a unique 0-origin small-integer patch number,
	// usually denoted  pn
	int patch_number() const { return patch_number_; }

	// each patch has a unique human-readable patch name for debugging etc
	const char* name() const { return name_; }	// typically "+z" etc

	// are we a +[xyz] or -[xyz] patch?
	bool is_plus() const { return is_plus_; }

	// ... values for the  is_plus_in  constructor argument
	//     FIXME: these should really be bool, but then we couldn't
	//            use the "enum hack" for in-class constants
	enum { patch_is_plus = true, patch_is_minus = false };

	// are we a (+/-) x or y or z patch?
	// ... n.b. type is `char' because this is handy for both
	//	    switch() and human-readable printing
	char ctype() const { return ctype_; }		// 'z' or 'x' or 'y'

	// are two patches really the same patch?
	// n.b. this does *not* compare any of the gridfn data!
	bool operator==(const patch& other_patch) const
		{ return this == &other_patch; }
	bool operator!=(const patch& other_patch) const
		{ return ! operator==(other_patch); }

	// (rho,sigma,tau) coordinates as singleton coordinate sets
	local_coords::coords_set coords_set_rho() const
		{ return coords_set_rho_; } 
	local_coords::coords_set coords_set_sigma() const
		{ return coords_set_sigma_; }
	local_coords::coords_set coords_set_tau() const
		{ return coords_set_tau_; }

	// {rho,sigma} coordinate set
	local_coords::coords_set coords_set_rho_sigma() const
		{ return coords_set_rho() | coords_set_sigma(); }

	// (rho,sigma) coordinates as human-readable character strings
	// (for labelling output files etc)
	virtual const char* name_of_rho() const = 0;
	virtual const char* name_of_sigma() const = 0;


	//
	// ***** (rho,sigma,tau) coordinates *****
	//
public:

	// convert (rho,sigma) --> tau
	virtual fp tau_of_rho_sigma(fp rho, fp sigma) const = 0;

	// convert (rho,sigma) --> (mu,nu,phi)
	virtual fp  mu_of_rho_sigma(fp rho, fp sigma) const = 0;
	virtual fp  nu_of_rho_sigma(fp rho, fp sigma) const = 0;
	virtual fp phi_of_rho_sigma(fp rho, fp sigma) const = 0;

	// convert (rho,sigma) <--> usual polar spherical (theta,phi)
	virtual void theta_phi_of_rho_sigma(fp rho, fp sigma,
					    fp& ps_theta, fp& ps_phi)
		const = 0;
	virtual void rho_sigma_of_theta_phi(fp ps_theta, fp ps_phi,
					    fp& rho, fp& sigma)
		const = 0;

	// convert (r,rho,sigma) <--> local (x,y,z)
	virtual void xyz_of_r_rho_sigma(fp r, fp rho, fp sigma,
					fp& x, fp& y, fp& z)
		const = 0;
	virtual fp   rho_of_xyz(fp x, fp y, fp z) const = 0;
	virtual fp sigma_of_xyz(fp x, fp y, fp z) const = 0;

	// convert (rho,sigma) --> direction cosines (xcos,ycos,zcos)
	//                         with respect to the local coordinate system
	virtual void xyzcos_of_rho_sigma(fp rho, fp sigma,
					 fp& xcos, fp& ycos, fp& zcos)
		const = 0;

	// partial (x,y,z) / partial (rho,sigma)
	virtual void partial_xyz_wrt_r_rho_sigma
	   (fp r, fp rho, fp sigma,
	    fp& partial_x_wrt_r, fp& partial_x_wrt_rho, fp& partial_x_wrt_sigma,
	    fp& partial_y_wrt_r, fp& partial_y_wrt_rho, fp& partial_y_wrt_sigma,
	    fp& partial_z_wrt_r, fp& partial_z_wrt_rho, fp& partial_z_wrt_sigma)
		const = 0;

	// partial (rho,sigma) / partial (x,y,z)
	virtual fp partial_rho_wrt_x(fp x, fp y, fp z) const = 0;
	virtual fp partial_rho_wrt_y(fp x, fp y, fp z) const = 0;
	virtual fp partial_rho_wrt_z(fp x, fp y, fp z) const = 0;
	virtual fp partial_sigma_wrt_x(fp x, fp y, fp z) const = 0;
	virtual fp partial_sigma_wrt_y(fp x, fp y, fp z) const = 0;
	virtual fp partial_sigma_wrt_z(fp x, fp y, fp z) const = 0;

	// partial^2 (rho,sigma) / partial (xx,xy,xz,yy,yz)
	virtual fp partial2_rho_wrt_xx(fp x, fp y, fp z) const = 0;
	virtual fp partial2_rho_wrt_xy(fp x, fp y, fp z) const = 0;
	virtual fp partial2_rho_wrt_xz(fp x, fp y, fp z) const = 0;
	virtual fp partial2_rho_wrt_yy(fp x, fp y, fp z) const = 0;
	virtual fp partial2_rho_wrt_yz(fp x, fp y, fp z) const = 0;
	virtual fp partial2_rho_wrt_zz(fp x, fp y, fp z) const = 0;
	virtual fp partial2_sigma_wrt_xx(fp x, fp y, fp z) const = 0;
	virtual fp partial2_sigma_wrt_xy(fp x, fp y, fp z) const = 0;
	virtual fp partial2_sigma_wrt_xz(fp x, fp y, fp z) const = 0;
	virtual fp partial2_sigma_wrt_yy(fp x, fp y, fp z) const = 0;
	virtual fp partial2_sigma_wrt_yz(fp x, fp y, fp z) const = 0;
	virtual fp partial2_sigma_wrt_zz(fp x, fp y, fp z) const = 0;

	// compute (rho,sigma) 2-D induced metric from 3-D xyz metric
	// as per p.33 of my apparent horizon finding notes
	// ... returns Jacobian of (rho,sigma) 2-D induced metric
	fp rho_sigma_metric(fp r, fp rho, fp sigma,
			    fp partial_surface_r_wrt_rho,
			    fp partial_surface_r_wrt_sigma,
			    fp g_xx, fp g_xy, fp g_xz,
				     fp g_yy, fp g_yz,
					      fp g_zz,
			    fp& g_rho_rho, fp& g_rho_sigma,
					   fp& g_sigma_sigma)
		const;

	// plotting coordinates (dpx,dpy)
	// ... character string describing how (dpx,dpy) are
	//     defined in terms of (mu,nu,phi), eg "90 - drho = 90 - dphi"
	//     (used for labelling output files)
	virtual const char* name_of_dpx() const = 0;
	virtual const char* name_of_dpy() const = 0;
	// ... (irho,isimga) --> (px,py)
	virtual fp dpx_of_rho_sigma(fp rho, fp sigma) const = 0;
	virtual fp dpy_of_rho_sigma(fp rho, fp sigma) const = 0;


	//
	// ***** line/surface integrals *****
	//
public:

	//
	// The following enum describes the integration methods supported
	// by  integrate_gridfn() .
	//
	// For convenience of exposition we describe the methods as if for
	// 1-D integration, but  integrate_gridfn()  actually does 2-D
	// (surface) integration over the patch.
	//
	// Suppose we're computing $\int_{x_0}^{x^N} f(x) \, dx$, using the
	// equally spaced integration points $f_0$, $f_1$, \dots, $f_N$,
	// spaced $\Delta x$ apart.  Then the integration methods are as
	// follows, with the convention that $\langle X \rangle$ denotes
	// indefinite repetition of the "X" terms, depending on N:
	//
	enum	integration_method
		{
		// Trapezoid rule
		// ... character-string name "trapezoid" or "trapezoid rule"
		// ... 2nd order accurate for smooth functions
		// ... requires N >= 1
		// $$
		// \Delta x \left[
		//            \half f_0
		//          + \langle
		//            f_k
		//            \rangle
		//          + \half f_N
		//          \right]
		// $$
		integration_method__trapezoid,

		// Simpson's rule
		// ... character-string name "Simpson" or "Simpson's rule"
		// ... 4th order accurate for smooth functions
		// ... requires N >= 2 and N even
		// $$
		// \Delta x \left[
		//            \frac{1}{3} f_0
		//          + \frac{4}{3} f_1
		//          + \langle
		//            \frac{2}{3} f_{2k} + \frac{4}{3} f_{2k+1}
		//            \rangle
		//          + \frac{1}{3} f_N
		//          \right]
		// $$
		integration_method__Simpson,

		// Simpson's rule, variant form
		// ... characgter-string name "Simpson (variant)"
		//     or "Simpson's rule (variant)"
		// ... described in Numerical Recipes 1st edition (4.1.14)
		// ... 4th order accurate for smooth functions
		// ... requires N >= 7
		// $$
		// \Delta x \left[
		//            \frac{17}{48} f_0
		//          + \frac{59}{48} f_1
		//          + \frac{43}{48} f_2
		//          + \frac{49}{48} f_3
		//          + \langle
		//            f_k
		//            \rangle
		//          + \frac{49}{48} f_{N-3}
		//          + \frac{43}{48} f_{N-2}
		//          + \frac{59}{48} f_{N-1}
		//          + \frac{17}{48} f_N
		//          \right]
		// $$
		integration_method__Simpson_variant,

		// automatic choice of the "best" one of the above methods:
		// ... i.e. choose Simpson's rule or variant if applicable,
		//     otherwise trapezoid rule
		// N == 2	Simpson's rule
		// N == 3	trapezoid rule
		// N == 4	Simpson's rule
		// N == 5	trapezoid rule
		// N == 6	Simpson's rule
		// N >= 7	Simpson's rule, variant form
		integration_method__automatic_choice // no comma here!
		};

	// decode character string name into internal enum
	static
	  enum integration_method
	    decode_integration_method(const char method_string[]);

	// compute the arc length of a surface in the specified plane
	// (must be one of "xy", "xz", or "yz") over the patch's nominal bounds
	// ... error_exit() if  plane  is invalid and/or
	//     the patch doesn't contain that coordinate plane
	virtual fp plane_arc_length(const char plane[],
				    int ghosted_radius_gfn,
				    int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
						  int g_yy_gfn, int g_yz_gfn,
								int g_zz_gfn,
				    enum integration_method method)
		const = 0;

	// ... along the rho direction (i.e. in a dsigma=constant plane
	//     where dsigma is a multiple of 90 degrees)
	fp rho_arc_length(int ghosted_radius_gfn,
			  int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					int g_yy_gfn, int g_yz_gfn,
						      int g_zz_gfn,
			  enum integration_method method)
		const;
	// ... along the sigma direction (i.e. in a drho=constant plane
	//     where drho is a multiple of 90 degrees)
	fp sigma_arc_length(int ghosted_radius_gfn,
			    int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					  int g_yy_gfn, int g_yz_gfn,
							int g_zz_gfn,
			    enum integration_method method)
		const;

	// compute the surface integral of a gridfn over the patch's
	// nominal area,
	//	$\int f(\rho,\sigma) \, dA$
	//		= \int f(\rho,\sigma) \sqrt{|J|} \, d\rho \, d\sigma$
	// where $J$ is the Jacobian of $(x,y,z)$ with respect to $(rho,sigma)
	// ... integration method selected by  method  argument
	// ... src gridfn may be either nominal-grid or ghosted-grid
	//     (n.b. in the latter case the integral is still done
	//           only over the patch's nominal area)
	fp integrate_gridfn(int unknown_src_gfn,
			    int ghosted_radius_gfn,
			    int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					  int g_yy_gfn, int g_yz_gfn,
							int g_zz_gfn,
			    enum integration_method method)
		const;

	fp integrate_gridpoint(int unknown_src_gfn,
                               int ghosted_radius_gfn,
                               int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
                                             int g_yy_gfn, int g_yz_gfn,
                                                           int g_zz_gfn,
                               enum integration_method method,
                               int irho, int isigma)
		const;

	// compute integration coefficient $c_i$ where
	// $\int_{x_0}^{x_N} f(x) \, dx
	//	\approx \Delta x \, \sum_{i=0}^N c_i f(x_i)$
private:
	static
	  fp integration_coeff(enum integration_method method, int N, int i);


	//
	// ***** patch edges ****
	//
public:
	const patch_edge& min_rho_patch_edge() const
		{ return min_rho_patch_edge_; }
	const patch_edge& max_rho_patch_edge() const
		{ return max_rho_patch_edge_; }
	const patch_edge& min_sigma_patch_edge() const
		{ return min_sigma_patch_edge_; }
	const patch_edge& max_sigma_patch_edge() const
		{ return max_sigma_patch_edge_; }
	const patch_edge& minmax_ang_patch_edge(bool want_min, bool want_rho)
		const
		{
		return want_min ? (want_rho ? min_rho_patch_edge()
					    : min_sigma_patch_edge())
				: (want_rho ? max_rho_patch_edge()
					    : max_sigma_patch_edge());
		}

	// find which patch edge is adjacent to neighboring patch q,
	// or error_exit() if it's not actually a neighboring patch
	// ... computation done using only (rho,sigma) coordinate sets
	//     and min/max dang bounds ==> ok to use in setting up ghost zones
	// ... patch_overlap_width = number of grid points (grid spacings
	//     in the perpendicular direction) these patches' nominal grids
	//     overlap,
	//     ... if this is nonzero, then these patches must have
	//         the *same* grid spacing in the perpendicular direction
	//     ... e.g. delta_dang = 5, this patch max_dang = 50,
	//         other patch min_dang = 40 ==> patch_overlap_width = 3
	//		p   p   p   p   p
	//			q   q   q   q   q
	const patch_edge& edge_adjacent_to_patch(const patch& q,
						 int patch_overlap_width = 0)
		const;


	//
	// ***** ghost zones *****
	//
public:
	ghost_zone& min_rho_ghost_zone() const
		{
		assert(min_rho_ghost_zone_ != NULL);
		return *min_rho_ghost_zone_;
		}
	ghost_zone& max_rho_ghost_zone() const
		{
		assert(max_rho_ghost_zone_ != NULL);
		return *max_rho_ghost_zone_;
		}
	ghost_zone& min_sigma_ghost_zone() const
		{
		assert(min_sigma_ghost_zone_ != NULL);
		return *min_sigma_ghost_zone_;
		}
	ghost_zone& max_sigma_ghost_zone() const
		{
		assert(max_sigma_ghost_zone_ != NULL);
		return *max_sigma_ghost_zone_;
		}
	ghost_zone& minmax_rho_ghost_zone(bool want_min)
		const
		{
		return want_min ? min_rho_ghost_zone()
				: max_rho_ghost_zone();
		}
	ghost_zone& minmax_sigma_ghost_zone(bool want_min)
		const
		{
		return want_min ? min_sigma_ghost_zone()
				: max_sigma_ghost_zone();
		}

	ghost_zone& minmax_ang_ghost_zone(bool want_min, bool want_rho)
		const
		{
		return want_rho ? minmax_rho_ghost_zone(want_min)
				: minmax_sigma_ghost_zone(want_min);
		}

	ghost_zone& ghost_zone_on_edge(const patch_edge &e) const;

	// which of the two ghost zones at a specified corner,
	// contains a specified point?
	ghost_zone& corner_ghost_zone_containing_point
		(bool rho_is_min, bool sigma_is_min,	// specifies corner
		 int irho, int isigma)			// specifies point
		const;

	// which ghost zone contains a specified noncorner point?
	ghost_zone& ghost_zone_containing_noncorner_point(int irho, int isigma)
		const;


	//
	// ***** set up ghost zones
	//
public:

	// assert() that this ghost zone hasn't been set up yet,
	// then set it up as mirror-symmetry
	void create_mirror_symmetry_ghost_zone(const patch_edge& edge);

	// assert() that this ghost zone hasn't been set up yet,
	// then set it up as periodic-symmetry
	void create_periodic_symmetry_ghost_zone
		(const patch_edge& my_edge, const patch_edge& other_edge,
		 bool ipar_map_is_plus);

	// assert() that this ghost zone hasn't been set up yet,
	// then set it up as interpatch
	// ... this only sets up ghost zone in skeletal form; use
	//     interpatch_ghost_zone::finish_setup()  to complete
	//     the setup process
	void create_interpatch_ghost_zone
		(const patch_edge& my_edge, const patch_edge& other_edge,
		 int patch_overlap_width);

	// assert() that all ghost zones
	// are fully setup
	void assert_all_ghost_zones_fully_setup() const;

private:
	// helper function for setup_*_ghost_zone():
	// assert() that ghost zone pointer on specified edge is NULL
	// (i.e. that we haven't already setup this ghost zone),
	// then assign new value to it
	void set_ghost_zone(const patch_edge& edge, ghost_zone* gzp);


	//
	// ***** constructor, destructor, et al *****
	//
protected:
	// ... used only from derived classes
	// ... doesn't set up ghost zone info, since this depends on
	//     knowing our neighbouring patches, which might not exist yet
	// ... saves a pointer to name_in[], so this should have a
	//     lifetime at least as long as that of this object
	patch(patch_system &my_patch_system_in, int patch_number_in,
	      const char name_in[], bool is_plus_in, char ctype_in,
	      local_coords::coords_set coords_set_rho_in,
	      local_coords::coords_set coords_set_sigma_in,
	      local_coords::coords_set coords_set_tau_in,
	      const grid_arrays::grid_array_pars& grid_array_pars_in,
	      const grid::grid_pars& grid_pars_in);
public:
	// destructor must be virtual to allow destruction
	// of derived classes via ptr/ref to this class
	virtual ~patch();

private:
	// we forbid copying and passing by value
	// by declaring the copy constructor and assignment operator
	// private, but never defining them
	patch(const patch& rhs);
	patch& operator=(const patch& rhs);


	//
	// ***** data members *****
	//
private:

	// type/coordinate metadata
	patch_system &my_patch_system_;
	const int patch_number_;
	const char* name_;
	const bool is_plus_;
	const char ctype_;
	const local_coords::coords_set coords_set_rho_,
				       coords_set_sigma_,
				       coords_set_tau_;

	// edges
	const patch_edge& min_rho_patch_edge_;
	const patch_edge& max_rho_patch_edge_;
	const patch_edge& min_sigma_patch_edge_;
	const patch_edge& max_sigma_patch_edge_;

	// ghost zones
	// ... pointers are set to NULL by ctor,
	//     reset to non-NULL by set_ghost_zone(), which is called by
	//	  create_mirror_symmetry_ghost_zone()
	//	  create_periodic_symmetry_ghost_zone()
	//	  create_interpatch_ghost_zone()
	ghost_zone* min_rho_ghost_zone_;
	ghost_zone* max_rho_ghost_zone_;
	ghost_zone* min_sigma_ghost_zone_;
	ghost_zone* max_sigma_ghost_zone_;
	};

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

//
// This class describes a +/- z patch.  It doesn't define any new
// functions not already present in  class patch ; it "just" defines
// non-virtual versions of all the pure virtual functions defined there.
//
//	z patch ==> (rho,sigma) = (mu,nu)    tau = phi
//
class	z_patch
	: public patch
	{
public:
	// human-readable names of (rho,sigma)
	const char* name_of_rho() const { return "mu"; }
	const char* name_of_sigma() const { return "nu"; }

	// convert (rho,sigma) --> tau
	fp tau_of_rho_sigma(fp rho, fp sigma) const
		{ return local_coords::phi_of_mu_nu(rho,sigma); }

	// convert (rho,sigma) --> (mu,nu,phi)
	fp mu_of_rho_sigma(fp rho, fp sigma) const { return rho; }
	fp nu_of_rho_sigma(fp rho, fp sigma) const { return sigma; }
	fp phi_of_rho_sigma(fp rho, fp sigma) const
		{ return local_coords::phi_of_mu_nu(rho,sigma); }

	// convert (rho,sigma) <--> usual polar spherical (theta,phi)
	void theta_phi_of_rho_sigma(fp rho, fp sigma, fp& ps_theta, fp& ps_phi)
		const
		{
		local_coords::theta_phi_of_mu_nu(rho,sigma, ps_theta,ps_phi);
		}
	void rho_sigma_of_theta_phi(fp ps_theta, fp ps_phi, fp& rho, fp& sigma)
		const
		{
		local_coords::mu_nu_of_theta_phi(ps_theta,ps_phi, rho,sigma);
		}

	// convert (r,rho,sigma) <--> (x,y,z)
	void xyz_of_r_rho_sigma(fp r, fp rho, fp sigma, fp& x, fp& y, fp& z)
		const
		{ local_coords::xyz_of_r_mu_nu(r,rho,sigma, x,y,z); }
	fp rho_of_xyz(fp x, fp y, fp z) const
		{ return modulo_reduce_rho(local_coords::mu_of_yz(y,z)); }
	fp sigma_of_xyz(fp x, fp y, fp z) const
		{ return modulo_reduce_sigma(local_coords::nu_of_xz(x,z)); }

	// convert (rho,sigma) --> direction cosines (xcos,ycos,zcos)
	//                         with respect to the local coordinate system
	void xyzcos_of_rho_sigma(fp rho, fp sigma,
				 fp& xcos, fp& ycos, fp& zcos)
		const
		{ local_coords::xyzcos_of_mu_nu(rho,sigma, xcos,ycos,zcos); }

	// partial (x,y,z) / partial (rho,sigma)
	void partial_xyz_wrt_r_rho_sigma
	   (fp r, fp rho, fp sigma,
	    fp& partial_x_wrt_r, fp& partial_x_wrt_rho, fp& partial_x_wrt_sigma,
	    fp& partial_y_wrt_r, fp& partial_y_wrt_rho, fp& partial_y_wrt_sigma,
	    fp& partial_z_wrt_r, fp& partial_z_wrt_rho, fp& partial_z_wrt_sigma)
		const
		{
		local_coords::partial_xyz_wrt_r_mu_nu
		   (r, rho, sigma,
		    partial_x_wrt_r, partial_x_wrt_rho, partial_x_wrt_sigma,
		    partial_y_wrt_r, partial_y_wrt_rho, partial_y_wrt_sigma,
		    partial_z_wrt_r, partial_z_wrt_rho, partial_z_wrt_sigma);
		}

	// partial (rho,sigma) / partial (x,y,z)
	fp partial_rho_wrt_x(fp x, fp y, fp z) const { return 0.0; }
	fp partial_rho_wrt_y(fp x, fp y, fp z) const
		{ return local_coords::partial_mu_wrt_y(y,z); }
	fp partial_rho_wrt_z(fp x, fp y, fp z) const
		{ return local_coords::partial_mu_wrt_z(y,z); }
	fp partial_sigma_wrt_x(fp x, fp y, fp z) const
		{ return local_coords::partial_nu_wrt_x(x,z); }
	fp partial_sigma_wrt_y(fp x, fp y, fp z) const { return 0.0; }
	fp partial_sigma_wrt_z(fp x, fp y, fp z) const
		{ return local_coords::partial_nu_wrt_z(x,z); }

	// partial^2 (rho,sigma) / partial (xx,xy,xz,yy,yz)
	fp partial2_rho_wrt_xx(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_xy(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_xz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_yy(fp x, fp y, fp z) const
		{ return local_coords::partial2_mu_wrt_yy(y,z); }
	fp partial2_rho_wrt_yz(fp x, fp y, fp z) const
		{ return local_coords::partial2_mu_wrt_yz(y,z); }
	fp partial2_rho_wrt_zz(fp x, fp y, fp z) const
		{ return local_coords::partial2_mu_wrt_zz(y,z); }
	fp partial2_sigma_wrt_xx(fp x, fp y, fp z) const
		{ return local_coords::partial2_nu_wrt_xx(x,z); }
	fp partial2_sigma_wrt_xy(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_xz(fp x, fp y, fp z) const
		{ return local_coords::partial2_nu_wrt_xz(x,z); }
	fp partial2_sigma_wrt_yy(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_yz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_zz(fp x, fp y, fp z) const
		{ return local_coords::partial2_nu_wrt_zz(x,z); }

	// plotting coordinates (px,py)
	// ... character string describing how (dpx,dpy) are
	//     defined in terms of (mu,nu,phi), eg "90 - drho = 90 - dphi"
	//     (used for labelling output files)
	const char* name_of_dpx() const
		{ return "dsigma = dnu"; }
	const char* name_of_dpy() const
		{ return is_plus() ? "drho = dmu" : "180 - drho = 180 - dmu"; }
	// ... (irho,isimga) --> (px,py)
	fp dpx_of_rho_sigma(fp rho, fp sigma) const
		{ return jtutil::degrees_of_radians(sigma); }
	fp dpy_of_rho_sigma(fp rho, fp sigma) const
		{
		const fp drho = jtutil::degrees_of_radians(rho);
		return is_plus() ? drho : 180.0 - drho;
		}

	// compute the arc length of a surface in the specified plane
	// (must be one of "xz" or "yz") over the patch's nominal bounds
	// ... error_exit() if  plane  is invalid
	fp plane_arc_length(const char plane[],
			    int ghosted_radius_gfn,
			    int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					  int g_yy_gfn, int g_yz_gfn,
							int g_zz_gfn,
			    enum integration_method method)
		const;

	// constructor, destructor
	z_patch(patch_system &my_patch_system_in, int patch_number_in,
		const char* name_in, bool is_plus_in,
		const grid_arrays::grid_array_pars& grid_array_pars_in,
		const grid::grid_pars& grid_pars_in);
	~z_patch() { }

private:
        // we forbid copying and passing by value
        // by declaring the copy constructor and assignment operator
        // private, but never defining them
	z_patch(const z_patch& rhs);
	z_patch& operator=(const z_patch& rhs);
	};

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

//
// This class describes a +/- x patch.  It doesn't define any new
// functions not already present in  class patch ; it "just" defines
// non-virtual versions of all the pure virtual functions defined there.
//
//	x patch ==> (rho,sigma) = (nu,phi)   tau = mu
//
class	x_patch
	: public patch
	{
public:
	// human-readable names of (rho,sigma)
	const char* name_of_rho() const { return "nu"; }
	const char* name_of_sigma() const { return "phi"; }

	// convert (rho,sigma) --> tau
	fp tau_of_rho_sigma(fp rho, fp sigma) const
		{ return local_coords::mu_of_nu_phi(rho, sigma); }

	// convert (rho,sigma) --> (mu,nu,phi)
	fp nu_of_rho_sigma(fp rho, fp sigma) const { return rho; }
	fp phi_of_rho_sigma(fp rho, fp sigma) const { return sigma; }
	fp mu_of_rho_sigma(fp rho, fp sigma) const
		{ return local_coords::mu_of_nu_phi(rho, sigma); }

	// convert (rho,sigma) <--> usual polar spherical (theta,phi)
	void theta_phi_of_rho_sigma(fp rho, fp sigma, fp& ps_theta, fp& ps_phi)
		const
		{
		local_coords::theta_phi_of_nu_phi(rho, sigma, ps_theta, ps_phi);
		}
	void rho_sigma_of_theta_phi(fp ps_theta, fp ps_phi, fp& rho, fp& sigma)
		const
		{
		local_coords::nu_phi_of_theta_phi(ps_theta, ps_phi, rho, sigma);
		}

	// convert (r,rho,sigma) <--> (x,y,z)
	void xyz_of_r_rho_sigma(fp r, fp rho, fp sigma, fp& x, fp& y, fp& z)
		const
		{ local_coords::xyz_of_r_nu_phi(r, rho, sigma, x, y, z); }
	fp rho_of_xyz(fp x, fp y, fp z) const
		{ return modulo_reduce_rho(local_coords::nu_of_xz(x, z)); }
	fp sigma_of_xyz(fp x, fp y, fp z) const
		{ return modulo_reduce_sigma(local_coords::phi_of_xy(x, y)); }

	// convert (rho,sigma) --> direction cosines (xcos,ycos,zcos)
	//                         with respect to the local coordinate system
	void xyzcos_of_rho_sigma(fp rho, fp sigma,
				 fp& xcos, fp& ycos, fp& zcos)
		const
		{ local_coords::xyzcos_of_nu_phi(rho,sigma, xcos,ycos,zcos); }

	// partial (x,y,z) / partial (rho,sigma)
	void partial_xyz_wrt_r_rho_sigma
	   (fp r, fp rho, fp sigma,
	    fp& partial_x_wrt_r, fp& partial_x_wrt_rho, fp& partial_x_wrt_sigma,
	    fp& partial_y_wrt_r, fp& partial_y_wrt_rho, fp& partial_y_wrt_sigma,
	    fp& partial_z_wrt_r, fp& partial_z_wrt_rho, fp& partial_z_wrt_sigma)
		const
		{
		local_coords::partial_xyz_wrt_r_nu_phi
		   (r, rho, sigma,
		    partial_x_wrt_r, partial_x_wrt_rho, partial_x_wrt_sigma,
		    partial_y_wrt_r, partial_y_wrt_rho, partial_y_wrt_sigma,
		    partial_z_wrt_r, partial_z_wrt_rho, partial_z_wrt_sigma);
		}

	// partial (rho,sigma) / partial (x,y,z)
	fp partial_rho_wrt_x(fp x, fp y, fp z) const
		{ return local_coords::partial_nu_wrt_x(x,z); }
	fp partial_rho_wrt_y(fp x, fp y, fp z) const { return 0.0; }
	fp partial_rho_wrt_z(fp x, fp y, fp z) const
		{ return local_coords::partial_nu_wrt_z(x,z); }
	fp partial_sigma_wrt_x(fp x, fp y, fp z) const
		{ return local_coords::partial_phi_wrt_x(x,y); }
	fp partial_sigma_wrt_y(fp x, fp y, fp z) const
		{ return local_coords::partial_phi_wrt_y(x,y); }
	fp partial_sigma_wrt_z(fp x, fp y, fp z) const { return 0.0; }

	// partial^2 (rho,sigma) / partial (xx,xy,xz,yy,yz)
	fp partial2_rho_wrt_xx(fp x, fp y, fp z) const
		{ return local_coords::partial2_nu_wrt_xx(x,z); }
	fp partial2_rho_wrt_xy(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_xz(fp x, fp y, fp z) const
		{ return local_coords::partial2_nu_wrt_xz(x,z); }
	fp partial2_rho_wrt_yy(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_yz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_zz(fp x, fp y, fp z) const
		{ return local_coords::partial2_nu_wrt_zz(x,z); }
	fp partial2_sigma_wrt_xx(fp x, fp y, fp z) const
		{ return local_coords::partial2_phi_wrt_xx(x,y); }
	fp partial2_sigma_wrt_xy(fp x, fp y, fp z) const
		{ return local_coords::partial2_phi_wrt_xy(x,y); }
	fp partial2_sigma_wrt_xz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_yy(fp x, fp y, fp z) const
		{ return local_coords::partial2_phi_wrt_yy(x,y); }
	fp partial2_sigma_wrt_yz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_zz(fp x, fp y, fp z) const { return 0.0; }

	// plotting coordinates (px,py)
	// ... character string describing how (dpx,dpy) are
	//     defined in terms of (mu,nu,phi), eg "90 - drho = 90 - dphi"
	//     (used for labelling output files)
	const char* name_of_dpx() const { return "drho = dnu"; }
	const char* name_of_dpy() const
		{
		return is_plus() ? "dsigma = dphi"
				 : "180 - dsigma = 180 - dphi";
		}
	// ... (irho,isimga) --> (px,py)
	fp dpx_of_rho_sigma(fp rho, fp sigma) const
		{ return jtutil::degrees_of_radians(rho); }
	fp dpy_of_rho_sigma(fp rho, fp sigma) const
		{
		const fp dsigma = jtutil::degrees_of_radians(sigma);
		return is_plus() ? dsigma : 180.0 - dsigma;
		}

	// compute the arc length of a surface in the specified plane
	// (must be one of "xy" or "xz") over the patch's nominal bounds
	// ... error_exit() if  plane  is invalid
	fp plane_arc_length(const char plane[],
			    int ghosted_radius_gfn,
			    int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					  int g_yy_gfn, int g_yz_gfn,
							int g_zz_gfn,
			    enum integration_method method)
		const;

	// constructor, destructor
	x_patch(patch_system &my_patch_system_in, int patch_number_in,
		const char* name_in, bool is_plus_in,
		const grid_arrays::grid_array_pars& grid_array_pars_in,
		const grid::grid_pars& grid_pars_in);
	~x_patch() { }

private:
        // we forbid copying and passing by value
        // by declaring the copy constructor and assignment operator
        // private, but never defining them
	x_patch(const x_patch& rhs);
	x_patch& operator=(const x_patch& rhs);
	};

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

//
// This class describes a +/- y patch.  It doesn't define any new
// functions not already present in  class patch ; it "just" defines
// non-virtual versions of all the pure virtual functions defined there.
//
//	y patch ==> (rho,sigma) = (mu,phi)   tau = nu
//
class	y_patch
	: public patch
	{
public:
	// human-readable names of (rho,sigma)
	const char* name_of_rho() const { return "mu"; }
	const char* name_of_sigma() const { return "phi"; }

	// convert (rho,sigma) --> tau
	fp tau_of_rho_sigma(fp rho, fp sigma) const
		{ return local_coords::nu_of_mu_phi(rho, sigma); }

	// convert (rho,sigma) --> (mu,nu,phi)
	fp mu_of_rho_sigma(fp rho, fp sigma) const { return rho; }
	fp phi_of_rho_sigma(fp rho, fp sigma) const { return sigma; }
	fp nu_of_rho_sigma(fp rho, fp sigma) const
		{ return local_coords::nu_of_mu_phi(rho, sigma); }

	// convert (rho,sigma) <--> usual polar spherical (theta,phi)
	void theta_phi_of_rho_sigma(fp rho, fp sigma, fp& ps_theta, fp& ps_phi)
		const
		{
		local_coords::theta_phi_of_mu_phi(rho, sigma, ps_theta, ps_phi);
		}
	void rho_sigma_of_theta_phi(fp ps_theta, fp ps_phi, fp& rho, fp& sigma)
		const
		{
		local_coords::mu_phi_of_theta_phi(ps_theta, ps_phi, rho, sigma);
		}

	// convert (r,rho,sigma) <--> (x,y,z)
	void xyz_of_r_rho_sigma(fp r, fp rho, fp sigma, fp& x, fp& y, fp& z)
		const
		{ local_coords::xyz_of_r_mu_phi(r, rho, sigma, x, y, z); }
	fp rho_of_xyz(fp x, fp y, fp z) const
		{ return modulo_reduce_rho(local_coords::mu_of_yz(y, z)); }
	fp sigma_of_xyz(fp x, fp y, fp z) const
		{ return modulo_reduce_sigma(local_coords::phi_of_xy(x, y)); }

	// convert (rho,sigma) --> direction cosines (xcos,ycos,zcos)
	//                         with respect to the local coordinate system
	void xyzcos_of_rho_sigma(fp rho, fp sigma,
				 fp& xcos, fp& ycos, fp& zcos)
		const
		{ local_coords::xyzcos_of_mu_phi(rho,sigma, xcos,ycos,zcos); }

	// partial (x,y,z) / partial (rho,sigma)
	void partial_xyz_wrt_r_rho_sigma
	   (fp r, fp rho, fp sigma,
	    fp& partial_x_wrt_r, fp& partial_x_wrt_rho, fp& partial_x_wrt_sigma,
	    fp& partial_y_wrt_r, fp& partial_y_wrt_rho, fp& partial_y_wrt_sigma,
	    fp& partial_z_wrt_r, fp& partial_z_wrt_rho, fp& partial_z_wrt_sigma)
		const
		{
		local_coords::partial_xyz_wrt_r_mu_phi
		   (r, rho, sigma,
		    partial_x_wrt_r, partial_x_wrt_rho, partial_x_wrt_sigma,
		    partial_y_wrt_r, partial_y_wrt_rho, partial_y_wrt_sigma,
		    partial_z_wrt_r, partial_z_wrt_rho, partial_z_wrt_sigma);
		}

	// partial (rho,sigma) / partial (x,y,z)
	fp partial_rho_wrt_x(fp x, fp y, fp z) const { return 0.0; }
	fp partial_rho_wrt_y(fp x, fp y, fp z) const
		{ return local_coords::partial_mu_wrt_y(y,z); }
	fp partial_rho_wrt_z(fp x, fp y, fp z) const
		{ return local_coords::partial_mu_wrt_z(y,z); }
	fp partial_sigma_wrt_x(fp x, fp y, fp z) const
		{ return local_coords::partial_phi_wrt_x(x,y); }
	fp partial_sigma_wrt_y(fp x, fp y, fp z) const
		{ return local_coords::partial_phi_wrt_y(x,y); }
	fp partial_sigma_wrt_z(fp x, fp y, fp z) const { return 0.0; }

	// partial^2 (rho,sigma) / partial (xx,xy,xz,yy,yz)
	fp partial2_rho_wrt_xx(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_xy(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_xz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_rho_wrt_yy(fp x, fp y, fp z) const
		{ return local_coords::partial2_mu_wrt_yy(y,z); }
	fp partial2_rho_wrt_yz(fp x, fp y, fp z) const
		{ return local_coords::partial2_mu_wrt_yz(y,z); }
	fp partial2_rho_wrt_zz(fp x, fp y, fp z) const
		{ return local_coords::partial2_mu_wrt_zz(y,z); }
	fp partial2_sigma_wrt_xx(fp x, fp y, fp z) const
		{ return local_coords::partial2_phi_wrt_xx(x,y); }
	fp partial2_sigma_wrt_xy(fp x, fp y, fp z) const
		{ return local_coords::partial2_phi_wrt_xy(x,y); }
	fp partial2_sigma_wrt_xz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_yy(fp x, fp y, fp z) const
		{ return local_coords::partial2_phi_wrt_yy(x,y); }
	fp partial2_sigma_wrt_yz(fp x, fp y, fp z) const { return 0.0; }
	fp partial2_sigma_wrt_zz(fp x, fp y, fp z) const { return 0.0; }

	// plotting coordinates (px,py)
	// ... character string describing how (dpx,dpy) are
	//     defined in terms of (mu,nu,phi), eg "90 - drho = 90 - dphi"
	//     (used for labelling output files)
	const char* name_of_dpx() const
		{
		return is_plus() ? "90 - dsigma = 90 - dphi"
				 : "90 + dsigma = 90 + dphi";
		}
	const char* name_of_dpy() const { return "drho = dmu"; }
	// ... (rho,simga) --> (px,py)
	fp dpx_of_rho_sigma(fp rho, fp sigma) const
		{
		const fp dsigma = jtutil::degrees_of_radians(sigma);
		return is_plus() ? 90.0 - dsigma : 90.0 + dsigma;
		}
	fp dpy_of_rho_sigma(fp rho, fp sigma) const
		{ return jtutil::degrees_of_radians(rho); }

	// compute the arc length of a surface in the specified plane
	// (must be one of "xy" or "yz") over the patch's nominal bounds
	// ... error_exit() if  plane  is invalid
	fp plane_arc_length(const char plane[],
			    int ghosted_radius_gfn,
			    int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					  int g_yy_gfn, int g_yz_gfn,
							int g_zz_gfn,
			    enum integration_method method)
		const;

	// constructor, destructor
	y_patch(patch_system &my_patch_system_in, int patch_number_in,
		const char* name_in, bool is_plus_in,
		const grid_arrays::grid_array_pars& grid_array_pars_in,
		const grid::grid_pars& grid_pars_in);
	~y_patch() { }

private:
        // we forbid copying and passing by value
        // by declaring the copy constructor and assignment operator
        // private, but never defining them
	y_patch(const y_patch& rhs);
	y_patch& operator=(const y_patch& rhs);
	};

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

	  }	// namespace AHFinderDirect

```

patch/grid.cc:
```
// grid.cc -- classes for a 2D uniform tensor-product grid
// $Header$
//
// grid_arrays::grid_arrays
// grid_arrays::setup_gridfn_storage
// grid_arrays::~grid_arrays
//
// grid::grid
//

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

#ifdef STANDALONE_TEST
  #include "fake_cctk.h"
#else
  #include "cctk.h"
#endif

#include "config.h"
#include "stdc.h"
#include "../jtutil/util.hh"
#include "../jtutil/array.hh"
#include "../jtutil/linear_map.hh"

#include "coords.hh"
#include "grid.hh"

// all the code in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// This function constructs a  grid_arrays  object.
//
grid_arrays::grid_arrays(const grid_array_pars& grid_array_pars_in)

	: gridfn_data_(NULL),
	  ghosted_gridfn_data_(NULL),

	  // these are all set properly by setup_gridfn_storage()
	  min_gfn_(0), max_gfn_(0),
	  ghosted_min_gfn_(0), ghosted_max_gfn_(0),

	  min_irho_(grid_array_pars_in.min_irho),
	  max_irho_(grid_array_pars_in.max_irho),
	  min_isigma_(grid_array_pars_in.min_isigma),
	  max_isigma_(grid_array_pars_in.max_isigma),

	  ghosted_min_irho_(grid_array_pars_in.min_irho
			    - grid_array_pars_in.min_rho_ghost_zone_width),
	  ghosted_max_irho_(grid_array_pars_in.max_irho
			    + grid_array_pars_in.max_rho_ghost_zone_width),
	  ghosted_min_isigma_(grid_array_pars_in.min_isigma
			      - grid_array_pars_in.min_sigma_ghost_zone_width),
	  ghosted_max_isigma_(grid_array_pars_in.max_isigma
			      + grid_array_pars_in.max_sigma_ghost_zone_width)
								     // no comma
{ }

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

//
// This function sets up the gridfn storage arrays in a  grid_arrays  object.
//
void grid_arrays::setup_gridfn_storage
	(const gridfn_pars& gridfn_pars_in,
	 const gridfn_pars& ghosted_gridfn_pars_in)
{
assert(gridfn_data_ == NULL);
gridfn_data_ = new jtutil::array3d<fp>(gridfn_pars_in.min_gfn,
				       gridfn_pars_in.max_gfn,
				       min_irho(), max_irho(),
				       min_isigma(), max_isigma(),
				       gridfn_pars_in.storage_array,
				       gridfn_pars_in.gfn_stride,
				       gridfn_pars_in.irho_stride,
				       gridfn_pars_in.isigma_stride);

assert(ghosted_gridfn_data_ == NULL);
ghosted_gridfn_data_
	= new jtutil::array3d<fp>
			(ghosted_gridfn_pars_in.min_gfn,
			 ghosted_gridfn_pars_in.max_gfn,
			 ghosted_min_irho(), ghosted_max_irho(),
			 ghosted_min_isigma(), ghosted_max_isigma(),
			 ghosted_gridfn_pars_in.storage_array,
			 ghosted_gridfn_pars_in.gfn_stride,
			 ghosted_gridfn_pars_in.irho_stride,
			 ghosted_gridfn_pars_in.isigma_stride);
}

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

//
// This function destroys a  grid_arrays  object.
//
grid_arrays::~grid_arrays()
{
delete ghosted_gridfn_data_;
delete gridfn_data_;
}

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

//
// This function constructs a  grid  object.
//
grid::grid(const grid_array_pars& grid_array_pars_in,
	   const grid_pars& grid_pars_in)

	: grid_arrays(grid_array_pars_in),

	  rho_map_(grid_array_pars_in.min_irho
		    - grid_array_pars_in.min_rho_ghost_zone_width,
		   grid_array_pars_in.max_irho
		    + grid_array_pars_in.max_rho_ghost_zone_width,
		   jtutil::radians_of_degrees(
		      grid_pars_in.min_drho
		       - grid_array_pars_in.min_rho_ghost_zone_width
			 * grid_pars_in.delta_drho
					     ),
		   jtutil::radians_of_degrees(grid_pars_in.delta_drho),
		   jtutil::radians_of_degrees(
		      grid_pars_in.max_drho
		       + grid_array_pars_in.max_rho_ghost_zone_width
			 * grid_pars_in.delta_drho
					     )),

	  sigma_map_(grid_array_pars_in.min_isigma
		      - grid_array_pars_in.min_sigma_ghost_zone_width,
		     grid_array_pars_in.max_isigma
		      + grid_array_pars_in.max_sigma_ghost_zone_width,
		     jtutil::radians_of_degrees(
			grid_pars_in.min_dsigma
			 - grid_array_pars_in.min_sigma_ghost_zone_width
			   * grid_pars_in.delta_dsigma
					       ),
		     jtutil::radians_of_degrees(grid_pars_in.delta_dsigma),
		     jtutil::radians_of_degrees(
			grid_pars_in.max_dsigma
			 + grid_array_pars_in.max_sigma_ghost_zone_width
			   * grid_pars_in.delta_dsigma
					       )),

	  min_rho_(jtutil::radians_of_degrees(grid_pars_in.min_drho)),
	  max_rho_(jtutil::radians_of_degrees(grid_pars_in.max_drho)),
	  min_sigma_(jtutil::radians_of_degrees(grid_pars_in.min_dsigma)),
	  max_sigma_(jtutil::radians_of_degrees(grid_pars_in.max_dsigma))
								     // no comma
{ }

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

	  }	// namespace AHFinderDirect

```

patch/fd_grid.cc:
```
// fd_grid.cc -- grid with finite differencing operations
// $Header$
//
// fd_grid::dx_coeff
// fd_grid::dxx_coeff
//

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

#ifdef STANDALONE_TEST
  #include "fake_cctk.h"
#else
  #include "cctk.h"
#endif

#include "config.h"
#include "stdc.h"
#include "../jtutil/util.hh"
#include "../jtutil/array.hh"
#include "../jtutil/linear_map.hh"
using jtutil::error_exit;

#include "coords.hh"
#include "grid.hh"
#include "fd_grid.hh"

// all the code in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// This function computes a single coefficient of a 1st derivative
// molecule, for unit grid spacing.
//
//static
  fp fd_grid::dx_coeff(int m)
{
#ifndef FINITE_DIFF_ORDER
  #error "must define FINITE_DIFF_ORDER!"
#endif

switch	(m)
	{
#if   (FINITE_DIFF_ORDER == 2)
  case -1:	return FD_GRID__ORDER2__DX__COEFF_M1;
  case  0:	return FD_GRID__ORDER2__DX__COEFF_0;
  case +1:	return FD_GRID__ORDER2__DX__COEFF_P1;
#elif (FINITE_DIFF_ORDER == 4)
  case -2:	return FD_GRID__ORDER4__DX__COEFF_M2;
  case -1:	return FD_GRID__ORDER4__DX__COEFF_M1;
  case  0:	return FD_GRID__ORDER4__DX__COEFF_0;
  case +1:	return FD_GRID__ORDER4__DX__COEFF_P1;
  case +2:	return FD_GRID__ORDER4__DX__COEFF_P2;
#else
  #error "unknown value " FINITE_DIFF_ORDER " for FINITE_DIFF_ORDER!"
#endif
default:
	error_exit(ERROR_EXIT,
"***** fd_grid::dx_coeff(): m=%d is outside order=%d molecule radius=%d\n",
		   m, FINITE_DIFF_ORDER, FD_GRID__MOL_RADIUS);	/*NOTREACHED*/
	}
}

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

//
// This function computes a single coefficient of a 2nd derivative
// molecule, for unit grid spacing.
//
//static
  fp fd_grid::dxx_coeff(int m)
{
#ifndef FINITE_DIFF_ORDER
  #error "must define FINITE_DIFF_ORDER!"
#endif

switch	(m)
	{
#if   (FINITE_DIFF_ORDER == 2)
  case -1:	return FD_GRID__ORDER2__DXX__COEFF_M1;
  case  0:	return FD_GRID__ORDER2__DXX__COEFF_0;
  case +1:	return FD_GRID__ORDER2__DXX__COEFF_P1;
#elif (FINITE_DIFF_ORDER == 4)
  case -2:	return FD_GRID__ORDER4__DXX__COEFF_M2;
  case -1:	return FD_GRID__ORDER4__DXX__COEFF_M1;
  case  0:	return FD_GRID__ORDER4__DXX__COEFF_0;
  case +1:	return FD_GRID__ORDER4__DXX__COEFF_P1;
  case +2:	return FD_GRID__ORDER4__DXX__COEFF_P2;
#else
  #error "unknown value " FINITE_DIFF_ORDER " for FINITE_DIFF_ORDER!"
#endif
default:
	error_exit(ERROR_EXIT,
"***** fd_grid::dxx_coeff(): m=%d is outside order=%d molecule radius=%d\n",
		   m, FINITE_DIFF_ORDER, FD_GRID__MOL_RADIUS);	/*NOTREACHED*/
	}
}

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

	  }	// namespace AHFinderDirect

```

patch/patch.cc:
```
// patch.cc -- describes a coordinate/grid patch
// $Header$

//
// patch::patch
// patch::~patch
// z_patch::z_patch
// x_patch::x_patch
// y_patch::y_patch
//
// patch::rho_sigma_metric
//
// patch::decode_integration_method
// patch::rho_arc_length
// patch::sigma_arc_length
// z_patch::plane_arc_length
// x_patch::plane_arc_length
// y_patch::plane_arc_length
// patch::integrate_gridfn
/// patch::integration_coeff
//
// patch::ghost_zone_on_edge
// patch::corner_ghost_zone_containing_point
// patch::ghost_zone_containing_noncorner_point
// patch::create_mirror_symmetry_ghost_zone
// patch::create_periodic_symmetry_ghost_zone
// patch::create_interpatch_ghost_zone
/// patch::set_ghost_zone
// patch::edge_adjacent_to_patch
// patch::assert_all_ghost_zones_fully_setup
//

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

#include "cctk.h"

#include "config.h"
#include "stdc.h"
#include "../jtutil/util.hh"
#include "../jtutil/array.hh"
#include "../jtutil/cpm_map.hh"
#include "../jtutil/linear_map.hh"
using jtutil::error_exit;

#include "coords.hh"
#include "grid.hh"
#include "fd_grid.hh"
#include "patch.hh"
#include "patch_edge.hh"
#include "patch_interp.hh"
#include "ghost_zone.hh"

// all the code in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// This function constructs a  patch  object.
//
patch::patch(patch_system &my_patch_system_in, int patch_number_in,
	     const char name_in[], bool is_plus_in, char ctype_in,
	     local_coords::coords_set coords_set_rho_in,
	     local_coords::coords_set coords_set_sigma_in,
	     local_coords::coords_set coords_set_tau_in,
	     const grid_arrays::grid_array_pars& grid_array_pars_in,
	     const grid::grid_pars& grid_pars_in)

	: fd_grid(grid_array_pars_in, grid_pars_in),

	  my_patch_system_(my_patch_system_in),
	  patch_number_(patch_number_in),
	  name_(name_in),
	  is_plus_(is_plus_in), ctype_(ctype_in),

	  coords_set_rho_  (coords_set_rho_in  ),
	  coords_set_sigma_(coords_set_sigma_in),
	  coords_set_tau_  (coords_set_tau_in  ),

	  min_rho_patch_edge_(*new patch_edge(*this, side_is_min, side_is_rho)),
	  max_rho_patch_edge_(*new patch_edge(*this, side_is_max, side_is_rho)),
	  min_sigma_patch_edge_
	  	(*new patch_edge(*this, side_is_min, side_is_sigma)),
	  max_sigma_patch_edge_
		(*new patch_edge(*this, side_is_max, side_is_sigma)),

	  min_rho_ghost_zone_(NULL),
	  max_rho_ghost_zone_(NULL),
	  min_sigma_ghost_zone_(NULL),
	  max_sigma_ghost_zone_(NULL) // no comma

{ }

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

//
// This function destroys a  patch  object.
//
patch::~patch()
{
// no need to check for null pointers, since  delete NULL  is a silent no-op

delete max_sigma_ghost_zone_;
delete min_sigma_ghost_zone_;
delete max_rho_ghost_zone_;
delete min_rho_ghost_zone_;

delete & max_sigma_patch_edge_;
delete & min_sigma_patch_edge_;
delete & max_rho_patch_edge_;
delete & min_rho_patch_edge_;
}

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

//
// This function constructs a  z_patch  object.
//
z_patch::z_patch(patch_system &my_patch_system_in, int patch_number_in,
		 const char* name_in, bool is_plus_in,
		 const grid_arrays::grid_array_pars& grid_array_pars_in,
		 const grid::grid_pars& grid_pars_in)
	: patch(my_patch_system_in, patch_number_in,
		name_in, is_plus_in, 'z',
		local_coords::coords_set_mu, local_coords::coords_set_nu,
		local_coords::coords_set_phi,
		grid_array_pars_in, grid_pars_in)
{ }

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

//
// This function constructs an  x_patch  object.
//
x_patch::x_patch(patch_system &my_patch_system_in, int patch_number_in,
		 const char* name_in, bool is_plus_in,
		 const grid_arrays::grid_array_pars& grid_array_pars_in,
		 const grid::grid_pars& grid_pars_in)
	: patch(my_patch_system_in, patch_number_in,
		name_in, is_plus_in, 'x',
		local_coords::coords_set_nu, local_coords::coords_set_phi,
		local_coords::coords_set_mu,
		grid_array_pars_in, grid_pars_in)
{ }

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

//
// This function constructs a  y_patch  object.
//
y_patch::y_patch(patch_system &my_patch_system_in, int patch_number_in,
		 const char* name_in, bool is_plus_in,
		 const grid_arrays::grid_array_pars& grid_array_pars_in,
		 const grid::grid_pars& grid_pars_in)
	: patch(my_patch_system_in, patch_number_in,
		name_in, is_plus_in, 'y',
		local_coords::coords_set_mu, local_coords::coords_set_phi,
		local_coords::coords_set_nu,
		grid_array_pars_in, grid_pars_in)
{ }

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

//
// This function computes the (rho,sigma) induced 2-D metric from the
// 3-D (x,y,z) metric of the space containing the patch, as per p.33 of
// my apparent horizon finding notes.
//
// Arguments:
// (r,rho,sigma) = The coordinates where the Jacobian is wanted.
// partial_surface_r_wrt_(rho,sigma)
//	= The partial derivatives of the surface radius with respect to
//	  the (rho,sigma) coordinates.
// g_{xx,xy,xz,yy,yz,zz} = The xyz 3-metric components $g_{ij}$.
// g_{rho_rho,rho_sigma,sigma_sigma} = The (rho,sigma) induced 2-D metric.
//
// Results:
// This function returns the Jacobian of the (rho,sigma) induced 2-D metric.
//
fp patch::rho_sigma_metric(fp r, fp rho, fp sigma,
			   fp partial_surface_r_wrt_rho,
			   fp partial_surface_r_wrt_sigma,
			   fp g_xx, fp g_xy, fp g_xz,
				    fp g_yy, fp g_yz,
					     fp g_zz,
			   fp& g_rho_rho, fp& g_rho_sigma,
					  fp& g_sigma_sigma)
	const
{
fp partial_x_wrt_r, partial_x_wrt_rho, partial_x_wrt_sigma;
fp partial_y_wrt_r, partial_y_wrt_rho, partial_y_wrt_sigma;
fp partial_z_wrt_r, partial_z_wrt_rho, partial_z_wrt_sigma;
partial_xyz_wrt_r_rho_sigma
	(r, rho, sigma,
	 partial_x_wrt_r, partial_x_wrt_rho, partial_x_wrt_sigma,
	 partial_y_wrt_r, partial_y_wrt_rho, partial_y_wrt_sigma,
	 partial_z_wrt_r, partial_z_wrt_rho, partial_z_wrt_sigma);

const fp dx_wrt_rho   = partial_x_wrt_rho
			+ partial_x_wrt_r*partial_surface_r_wrt_rho;
const fp dx_wrt_sigma = partial_x_wrt_sigma
			+ partial_x_wrt_r*partial_surface_r_wrt_sigma;
const fp dy_wrt_rho   = partial_y_wrt_rho
			+ partial_y_wrt_r*partial_surface_r_wrt_rho;
const fp dy_wrt_sigma = partial_y_wrt_sigma
			+ partial_y_wrt_r*partial_surface_r_wrt_sigma;
const fp dz_wrt_rho   = partial_z_wrt_rho
			+ partial_z_wrt_r*partial_surface_r_wrt_rho;
const fp dz_wrt_sigma = partial_z_wrt_sigma
			+ partial_z_wrt_r*partial_surface_r_wrt_sigma;

g_rho_rho     = +     dx_wrt_rho*dx_wrt_rho*g_xx
		+ 2.0*dx_wrt_rho*dy_wrt_rho*g_xy
		+ 2.0*dx_wrt_rho*dz_wrt_rho*g_xz
		+     dy_wrt_rho*dy_wrt_rho*g_yy
		+ 2.0*dy_wrt_rho*dz_wrt_rho*g_yz
		+     dz_wrt_rho*dz_wrt_rho*g_zz;
g_rho_sigma =   +  dx_wrt_rho*dx_wrt_sigma                           *g_xx
		+ (dx_wrt_rho*dy_wrt_sigma + dy_wrt_rho*dx_wrt_sigma)*g_xy
		+ (dx_wrt_rho*dz_wrt_sigma + dz_wrt_rho*dx_wrt_sigma)*g_xz
		+  dy_wrt_rho*dy_wrt_sigma                           *g_yy
		+ (dy_wrt_rho*dz_wrt_sigma + dz_wrt_rho*dy_wrt_sigma)*g_yz
		+  dz_wrt_rho*dz_wrt_sigma                           *g_zz;
g_sigma_sigma = + dx_wrt_sigma*dx_wrt_sigma*g_xx
		+ 2.0*dx_wrt_sigma*dy_wrt_sigma*g_xy
		+ 2.0*dx_wrt_sigma*dz_wrt_sigma*g_xz
		+     dy_wrt_sigma*dy_wrt_sigma*g_yy
		+ 2.0*dy_wrt_sigma*dz_wrt_sigma*g_yz
		+     dz_wrt_sigma*dz_wrt_sigma*g_zz;

return g_rho_rho*g_sigma_sigma - jtutil::pow2(g_rho_sigma);
}

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

//
// This function decodes the character-string name of an integration method
// into an  enum integration_method .  See the comments in "patch.hh" on the
// declaration of  enum integration_method  for details on the methods and
// their character-string names.
//
//static
  enum patch::integration_method
    patch::decode_integration_method(const char method_string[])
{
if	(    STRING_EQUAL(method_string, "trapezoid")
	  || STRING_EQUAL(method_string, "trapezoid rule")    )
   then return integration_method__trapezoid;
else if (    STRING_EQUAL(method_string, "Simpson")
	  || STRING_EQUAL(method_string, "Simpson's rule")    )
   then return integration_method__Simpson;
else if (    STRING_EQUAL(method_string, "Simpson (variant)")
	  || STRING_EQUAL(method_string, "Simpson's rule (variant)")    )
   then return integration_method__Simpson_variant;
else if (    STRING_EQUAL(method_string, "automatic choice")    )
   then return integration_method__automatic_choice;
else	error_exit(ERROR_EXIT,
"***** patch::decode_integration_method():\n"
"        unknown method_string=\"%s\"!\n"
,
		   method_string);				/*NOTREACHED*/
}

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

//
// This function computes an approximation to the arc length of a surface
// over the patch's nominal bounds along the rho direction (i.e. in a
// dsigma=constant plane where dsigma is a multiple of 90 degrees)
//
// Arguments:
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp patch::rho_arc_length(int ghosted_radius_gfn,
			 int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
				       int g_yy_gfn, int g_yz_gfn,
						     int g_zz_gfn,
			 enum integration_method method)
	const
{
fp dsigma;
if	(is_valid_dsigma(  0.0)) then dsigma =   0.0;
else if (is_valid_dsigma( 90.0)) then dsigma =  90.0;
else if (is_valid_dsigma(180.0)) then dsigma = 180.0;
else if (is_valid_dsigma(-90.0)) then dsigma = -90.0;
else	error_exit(PANIC_EXIT,
"***** patch::rho_arc_length(): can't find valid dsigma\n"
"                               which is a multiple of 90 degrees!\n"
"                               %s patch: [min,max]_dsigma()=[%g,%g]\n"
		   ,
		   name(), double(min_dsigma()), double(max_dsigma()));
const fp sigma = sigma_of_dsigma(dsigma);
const int isigma = isigma_of_sigma(sigma);

fp sum = 0.0;

	for (int irho = min_irho() ; irho <= max_irho() ; ++irho)
	{
	const fp rho = rho_of_irho(irho);
	const fp r = ghosted_gridfn(ghosted_radius_gfn, irho,isigma);
	const fp partial_surface_r_wrt_rho
		= partial_rho  (ghosted_radius_gfn, irho,isigma);
	const fp partial_surface_r_wrt_sigma
		= partial_sigma(ghosted_radius_gfn, irho,isigma);

	const fp g_xx = gridfn(g_xx_gfn, irho,isigma);
	const fp g_xy = gridfn(g_xy_gfn, irho,isigma);
	const fp g_xz = gridfn(g_xz_gfn, irho,isigma);
	const fp g_yy = gridfn(g_yy_gfn, irho,isigma);
	const fp g_yz = gridfn(g_yz_gfn, irho,isigma);
	const fp g_zz = gridfn(g_zz_gfn, irho,isigma);

	fp g_rho_rho, g_rho_sigma, g_sigma_sigma;
	rho_sigma_metric(r, rho, sigma,
			 partial_surface_r_wrt_rho,
			 partial_surface_r_wrt_sigma,
			 g_xx, g_xy, g_xz,
			       g_yy, g_yz,
				     g_zz,
			 g_rho_rho, g_rho_sigma,
				    g_sigma_sigma);

	const fp coeff = integration_coeff(method,
					   max_irho()-min_irho(),
					   irho      -min_irho());

	sum += coeff * sqrt(g_rho_rho);
	}

return delta_rho() * sum;
}

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

//
// This function computes an approximation to the arc length of a surface
// over the patch's nominal bounds along the sigma direction (i.e. in a
// drho=constant plane where drho is a multiple of 90 degrees)
//
// Arguments:
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp patch::sigma_arc_length(int ghosted_radius_gfn,
			   int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					 int g_yy_gfn, int g_yz_gfn,
						       int g_zz_gfn,
			   enum integration_method method)
	const
{
fp drho;
if	(is_valid_drho(  0.0)) then drho =   0.0;
else if (is_valid_drho( 90.0)) then drho =  90.0;
else if (is_valid_drho(180.0)) then drho = 180.0;
else if (is_valid_drho(-90.0)) then drho = -90.0;
else	error_exit(PANIC_EXIT,
"***** patch::sigma_arc_length(): can't find valid drho\n"
"                                 which is a multiple of 90 degrees!\n"
"                                 %s patch: [min,max]_drho()=[%g,%g]\n"
		   ,
		   name(), double(min_drho()), double(max_drho()));
const fp rho = rho_of_drho(drho);
const int irho = irho_of_rho(rho);

fp sum = 0.0;

	for (int isigma = min_isigma() ; isigma <= max_isigma() ; ++isigma)
	{
	const fp sigma = sigma_of_isigma(isigma);
	const fp r = ghosted_gridfn(ghosted_radius_gfn, irho,isigma);
	const fp partial_surface_r_wrt_rho
		= partial_rho  (ghosted_radius_gfn, irho,isigma);
	const fp partial_surface_r_wrt_sigma
		= partial_sigma(ghosted_radius_gfn, irho,isigma);

	const fp g_xx = gridfn(g_xx_gfn, irho,isigma);
	const fp g_xy = gridfn(g_xy_gfn, irho,isigma);
	const fp g_xz = gridfn(g_xz_gfn, irho,isigma);
	const fp g_yy = gridfn(g_yy_gfn, irho,isigma);
	const fp g_yz = gridfn(g_yz_gfn, irho,isigma);
	const fp g_zz = gridfn(g_zz_gfn, irho,isigma);

	fp g_rho_rho, g_rho_sigma, g_sigma_sigma;
	rho_sigma_metric(r, rho, sigma,
			 partial_surface_r_wrt_rho,
			 partial_surface_r_wrt_sigma,
			 g_xx, g_xy, g_xz,
			       g_yy, g_yz,
				     g_zz,
			 g_rho_rho, g_rho_sigma,
				    g_sigma_sigma);

	const fp coeff = integration_coeff(method,
					   max_isigma()-min_isigma(),
					   isigma      -min_isigma());

	sum += coeff * sqrt(g_sigma_sigma);
	}

return delta_sigma() * sum;
}

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

//
// This function computes the arc length of a surface in the specified
// plane ("xz" or "yz") over the patch's nominal bounds.
//
// Arguments:
// plane[] = (in) "xz" or "yz" to specify the plane.
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp z_patch::plane_arc_length(const char plane[],
			     int ghosted_radius_gfn,
			     int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					   int g_yy_gfn, int g_yz_gfn,
							 int g_zz_gfn,
			     enum integration_method method)
	const
{
if	((plane[0] == 'x') && (plane[1] == 'z'))
   then // xz-plane = rotation about y = nu arc = sigma sigma
	return sigma_arc_length(ghosted_radius_gfn,
				g_xx_gfn, g_xy_gfn, g_xz_gfn,
					  g_yy_gfn, g_yz_gfn,
						    g_zz_gfn,
				method);
else if ((plane[0] == 'y') && (plane[1] == 'z'))
   then // yz-plane = rotation about x = mu arc = rho arc
	return rho_arc_length(ghosted_radius_gfn,
			      g_xx_gfn, g_xy_gfn, g_xz_gfn,
					g_yy_gfn, g_yz_gfn,
						  g_zz_gfn,
			      method);
else	error_exit(ERROR_EXIT,
"***** z_patch::plane_arc_length(): %s patch, plane=\"%s\", but\n"
"                                   this patch doesn't contain that plane!\n"
,
		   name(), plane);				/*NOTREACHED*/
}

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

//
// This function computes the arc length of a surface in the specified
// plane ("xy" or "xz") over the patch's nominal bounds.
//
// Arguments:
// plane[] = (in) "xy" or "xz" to specify the plane.
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp x_patch::plane_arc_length(const char plane[],
			     int ghosted_radius_gfn,
			     int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					   int g_yy_gfn, int g_yz_gfn,
							 int g_zz_gfn,
			     enum integration_method method)
	const
{
if	((plane[0] == 'x') && (plane[1] == 'y'))
   then // xy-plane = rotation about z = phi arc = sigma arc
	return sigma_arc_length(ghosted_radius_gfn,
				g_xx_gfn, g_xy_gfn, g_xz_gfn,
					  g_yy_gfn, g_yz_gfn,
						    g_zz_gfn,
				method);
else if ((plane[0] == 'x') && (plane[1] == 'z'))
   then // xz-plane = rotation about y = nu arc = rho arc
	return rho_arc_length(ghosted_radius_gfn,
			      g_xx_gfn, g_xy_gfn, g_xz_gfn,
					g_yy_gfn, g_yz_gfn,
						  g_zz_gfn,
			      method);
else	error_exit(ERROR_EXIT,
"***** x_patch::plane_arc_length(): %s patch, plane=\"%s\", but\n"
"                                   this patch doesn't contain that plane!\n"
,
		   name(), plane);				/*NOTREACHED*/
}

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

//
// This function computes the arc length of a surface in the specified
// plane ("xy" or "yz") over the patch's nominal bounds.
//
// Arguments:
// plane[] = (in) "xy" or "yz" to specify the plane.
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp y_patch::plane_arc_length(const char plane[],
			     int ghosted_radius_gfn,
			     int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					   int g_yy_gfn, int g_yz_gfn,
							 int g_zz_gfn,
			     enum integration_method method)
	const
{
if	((plane[0] == 'x') && (plane[1] == 'y'))
   then // xy-plane = rotation about z = phi arc = sigma arc
	return sigma_arc_length(ghosted_radius_gfn,
				g_xx_gfn, g_xy_gfn, g_xz_gfn,
					  g_yy_gfn, g_yz_gfn,
						    g_zz_gfn,
				method);
else if ((plane[0] == 'y') && (plane[1] == 'z'))
   then // yz-plane = rotation about x = mu arc = rho arc
	return rho_arc_length(ghosted_radius_gfn,
			      g_xx_gfn, g_xy_gfn, g_xz_gfn,
					g_yy_gfn, g_yz_gfn,
						  g_zz_gfn,
			      method);
else	error_exit(ERROR_EXIT,
"***** y_patch::plane_arc_length(): %s patch, plane=\"%s\", but\n"
"                                   this patch doesn't contain that plane!\n"
,
		   name(), plane);				/*NOTREACHED*/
}

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

//
// This function computes an approximation to the (surface) integral of
// a gridfn over the patch's nominal area,
//	$\int f(\rho,\sigma) \, dA$
//		= \int f(\rho,\sigma) \sqrt{|J|} \, d\rho \, d\sigma$
// where $J$ is the Jacobian of $(x,y,z)$ with respect to $(rho,sigma).
//
// Arguments:
// unknown_src_gfn = (in) The gridfn to be integrated.  This may be
//			  either nominal-grid or ghosted-grid; n.b. in
//			  the latter case the integral is still done only
//			  over the patch's nominal area.
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp patch::integrate_gridfn(int unknown_src_gfn,
			   int ghosted_radius_gfn,
			   int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					 int g_yy_gfn, int g_yz_gfn,
						       int g_zz_gfn,
			   enum integration_method method)
	const
{
const bool src_is_ghosted = is_valid_ghosted_gfn(unknown_src_gfn);

fp sum = 0.0;
	for (int irho = min_irho() ; irho <= max_irho() ; ++irho)
	{
	for (int isigma = min_isigma() ; isigma <= max_isigma() ; ++isigma)
	{
	const fp fn = unknown_gridfn(src_is_ghosted,
				     unknown_src_gfn, irho,isigma);

	const fp rho   = rho_of_irho    (irho);
	const fp sigma = sigma_of_isigma(isigma);
	const fp r = ghosted_gridfn(ghosted_radius_gfn, irho,isigma);
	const fp partial_surface_r_wrt_rho
		= partial_rho  (ghosted_radius_gfn, irho,isigma);
	const fp partial_surface_r_wrt_sigma
		= partial_sigma(ghosted_radius_gfn, irho,isigma);

	const fp g_xx = gridfn(g_xx_gfn, irho,isigma);
	const fp g_xy = gridfn(g_xy_gfn, irho,isigma);
	const fp g_xz = gridfn(g_xz_gfn, irho,isigma);
	const fp g_yy = gridfn(g_yy_gfn, irho,isigma);
	const fp g_yz = gridfn(g_yz_gfn, irho,isigma);
	const fp g_zz = gridfn(g_zz_gfn, irho,isigma);

	fp g_rho_rho, g_rho_sigma, g_sigma_sigma;
	const fp Jac = rho_sigma_metric(r, rho, sigma,
					partial_surface_r_wrt_rho,
					partial_surface_r_wrt_sigma,
					g_xx, g_xy, g_xz,
					      g_yy, g_yz,
						    g_zz,
					g_rho_rho, g_rho_sigma,
						   g_sigma_sigma);

	const fp coeff_rho   = integration_coeff(method,
						 max_irho()-min_irho(),
						 irho      -min_irho());
	const fp coeff_sigma = integration_coeff(method,
						 max_isigma()-min_isigma(),
						 isigma      -min_isigma());

	sum += coeff_rho*coeff_sigma * fn * sqrt(jtutil::abs(Jac));
	}
	}

return delta_rho() * delta_sigma() * sum;
}

fp patch::integrate_gridpoint(int unknown_src_gfn,
                              int ghosted_radius_gfn,
                              int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					    int g_yy_gfn, int g_yz_gfn,
                                                          int g_zz_gfn,
                              enum integration_method method,
                              int irho, int isigma)
	const
{
const bool src_is_ghosted = is_valid_ghosted_gfn(unknown_src_gfn);

const fp fn = unknown_gridfn(src_is_ghosted,
			     unknown_src_gfn, irho,isigma);

const fp rho   = rho_of_irho    (irho);
const fp sigma = sigma_of_isigma(isigma);
const fp r = ghosted_gridfn(ghosted_radius_gfn, irho,isigma);
const fp partial_surface_r_wrt_rho
	= partial_rho  (ghosted_radius_gfn, irho,isigma);
const fp partial_surface_r_wrt_sigma
	= partial_sigma(ghosted_radius_gfn, irho,isigma);

const fp g_xx = gridfn(g_xx_gfn, irho,isigma);
const fp g_xy = gridfn(g_xy_gfn, irho,isigma);
const fp g_xz = gridfn(g_xz_gfn, irho,isigma);
const fp g_yy = gridfn(g_yy_gfn, irho,isigma);
const fp g_yz = gridfn(g_yz_gfn, irho,isigma);
const fp g_zz = gridfn(g_zz_gfn, irho,isigma);

fp g_rho_rho, g_rho_sigma, g_sigma_sigma;
const fp Jac = rho_sigma_metric(r, rho, sigma,
				partial_surface_r_wrt_rho,
				partial_surface_r_wrt_sigma,
				g_xx, g_xy, g_xz,
				      g_yy, g_yz,
					    g_zz,
				g_rho_rho, g_rho_sigma,
					   g_sigma_sigma);

const fp coeff_rho   = integration_coeff(method,
					 max_irho()-min_irho(),
					 irho      -min_irho());
const fp coeff_sigma = integration_coeff(method,
					 max_isigma()-min_isigma(),
					 isigma      -min_isigma());

const fp val = coeff_rho*coeff_sigma * fn * sqrt(jtutil::abs(Jac));

return delta_rho() * delta_sigma() * val;
}

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

//
// This function computes the integration coefficients for
//  integrate_gridfn() .  That is, if we write
//	$\int_{x_0}^{x_N} f(x) \, dx
//		\approx \Delta x \, \sum_{i=0}^N c_i f(x_i)$
// then this function computes $c_i$.
//
// For method == integration_method__automatic_choice the choices are
//	N=1		trapezoid
//	N=2		Simpson
//	N=3		trapezoid
//	N=4		Simpson
//	N=5		trapezoid
//	N=6		Simpson
//	N=7 and up	Simpson variant
//
// Arguments:
// method = Specifies the integration method.
// N = The number of integration *intervals*.  (The number of integration
//     *points* is N+1.)
// i = Specifies the point at which the coefficient is desired.
//
//static
  fp patch::integration_coeff(enum integration_method method, int N, int i)
{
assert(i >= 0);
assert(i <= N);

if (method == integration_method__automatic_choice)
   then {
	if	(N >= 7)
	   then method = integration_method__Simpson_variant;
	else if ((N % 2) == 0)
	   then method = integration_method__Simpson;
	else	method = integration_method__trapezoid;
	}

switch	(method)
	{
case integration_method__trapezoid:
	if ((i == 0) || (i == N))
	   then return 0.5;
	   else return 1.0;

case integration_method__Simpson:
	if ((N % 2) != 0)
	   then error_exit(ERROR_EXIT,
"***** patch::integration_coeff():\n"
"        Simpson's rule requires N to be even, but N=%d!\n",
			   N);					/*NOTREACHED*/
	if	((i == 0) || (i == N))
	   then return 1.0/3.0;
	else if ((i % 2) == 0)
	   then return 2.0/3.0;
	else	return 4.0/3.0;

case integration_method__Simpson_variant:
	if (N < 7)
	   then error_exit(ERROR_EXIT,
"***** patch::integration_coeff():\n"
"        Simpson's rule (variant) requires N >= 7, but N=%d!\n",
			   N);					/*NOTREACHED*/
	if	((i == 0) || (i == N))
	   then return 17.0/48.0;
	else if ((i == 1) || (i == N-1))
	   then return 59.0/48.0;
	else if ((i == 2) || (i == N-2))
	   then return 43.0/48.0;
	else if ((i == 3) || (i == N-3))
	   then return 49.0/48.0;
	else	return 1.0;

default:
	error_exit(ERROR_EXIT,
"***** patch::integration_coeff(): unknown method=(int)%d!\n"
"                                  (this should never happen!)\n"
,
		   int(method));				/*NOTREACHED*/
	}
}

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

//
// This function returns a reference to the ghost zone on a specified
// edge, after first assert()ing that the edge belongs to this patch.
//
// N.b. This function can't be inline in "patch.hh" because it needs
//	member functions of class patch_edge, which comes after class patch
//	in our #include order.
//
ghost_zone& patch::ghost_zone_on_edge(const patch_edge& e)
	const
{
assert(e.my_patch() == *this);
return minmax_ang_ghost_zone(e.is_min(), e.is_rho());
}

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

//
// This function determines which of the two adjacent ghost zones meeting
// at a specified corner, contains a specified point.  If the point isn't
// in either ghost zone, an error_exit() is done.  If the point is in both
// ghost zones, it's arbitrary which one will be chosen.
//
// Arguments:
// {rho,sigma}_is_min = Specify the corner (and implicitly the ghost zones).
// irho,isigma = Specify the point.
//
// Results:
// This function returns (a reference to) the desired ghost zone.
ghost_zone& patch::corner_ghost_zone_containing_point
	(bool rho_is_min, bool sigma_is_min,
	 int irho, int isigma)
	const
{
ghost_zone&   rho_gz =   minmax_rho_ghost_zone(  rho_is_min);
ghost_zone& sigma_gz = minmax_sigma_ghost_zone(sigma_is_min);

const patch_edge&   rho_edge =   rho_gz.my_edge();
const patch_edge& sigma_edge = sigma_gz.my_edge();

const int   rho_iperp =   rho_edge.iperp_of_irho_isigma(irho, isigma);
const int   rho_ipar  =   rho_edge. ipar_of_irho_isigma(irho, isigma);
const int sigma_iperp = sigma_edge.iperp_of_irho_isigma(irho, isigma);
const int sigma_ipar  = sigma_edge. ipar_of_irho_isigma(irho, isigma);

const bool is_in_rho_ghost_zone
	=   rho_gz.is_in_ghost_zone(  rho_iperp,   rho_ipar);
const bool is_in_sigma_ghost_zone
	= sigma_gz.is_in_ghost_zone(sigma_iperp, sigma_ipar);

// check that point is in at least one ghost zone
if (!is_in_rho_ghost_zone && !is_in_sigma_ghost_zone)
   then error_exit(ERROR_EXIT,
"***** patch::corner_ghost_zone_containing_point():\n"
"        neither ghost zone contains point (this should never happen)!\n"
"        patch=%s rho_is_min=(int)%d sigma_is_min=(int)%d\n"
"        irho=%d isigma=%d\n"
,
	   name(), int(rho_is_min), int(sigma_is_min),
	   irho, isigma);					/*NOTREACHED*/

return is_in_rho_ghost_zone ? rho_gz : sigma_gz;
}

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

//
// This function determines which ghost zone contains a specified
// noncorner point.
//
// If the point isn't in any ghost zone of this patch, or if the point
// is in the corner of a ghost zone, an error_exit() is done.
//
// Arguments:
// irho,isigma = Specify the point.
//
// Results:
// This function returns (a reference to) the desired ghost zone.
ghost_zone& patch::ghost_zone_containing_noncorner_point(int irho, int isigma)
	const
{
	// n.b. these loops must use _int_ variables for the loop
	//      to terminate!
	for (int want_min = false ; want_min <= true ; ++want_min)
	{
	for (int want_rho = false ; want_rho <= true ; ++want_rho)
	{
	const patch_edge& e = minmax_ang_patch_edge(want_min, want_rho);
	const int iperp = e.iperp_of_irho_isigma(irho, isigma);
	const int ipar  = e.ipar_of_irho_isigma (irho, isigma);

	ghost_zone& gz = minmax_ang_ghost_zone(want_min, want_rho);
	if ( gz.is_in_ghost_zone(iperp, ipar)
	     && gz.my_edge().ipar_is_in_noncorner(ipar) )
	   then return gz;
	}
	}

error_exit(ERROR_EXIT,
"***** patch::ghost_zone_containing_noncorner_point():\n"
"        no ghost zone contains point (this should never happen)!\n"
"        patch=%s irho=%d isigma=%d\n"
,
	   name(), irho, isigma);				/*NOTREACHED*/
}

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

//
// This function assert()s that a specified ghost zone of this patch
// hasn't already been set up, then constructs it as a mirror-symmetry
// ghost zone and properly links this to/from the patch.
//
void patch::create_mirror_symmetry_ghost_zone(const patch_edge& my_edge)
{
// make sure we belong to the right patch
assert(my_edge.my_patch() == *this);

symmetry_ghost_zone *temp = new symmetry_ghost_zone(my_edge);
set_ghost_zone(my_edge, temp);
}

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

//
// This function assert()s that a specified ghost zone of this patch
// hasn't already been set up, then creates it as a periodic-symmetry
// ghost zone and properly links this to/from the patch.
//
void patch::create_periodic_symmetry_ghost_zone
	(const patch_edge& my_edge, const patch_edge& other_edge,
	 bool is_ipar_map_plus)
{
// make sure we belong to the right patch
assert(my_edge.my_patch() == *this);

int my_sample_ipar = my_edge.min_ipar_without_corners();
int other_sample_ipar = is_ipar_map_plus
			? other_edge.min_ipar_without_corners()
			: other_edge.max_ipar_without_corners();

symmetry_ghost_zone *temp
	= new symmetry_ghost_zone(my_edge,        other_edge,
				  my_sample_ipar, other_sample_ipar,
				  is_ipar_map_plus);
set_ghost_zone(my_edge, temp);
}

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

//
// This function assert()s that a specified ghost zone of this patch
// hasn't already been set up, then creates it as an interpatch ghost
// zone (with lots of NULL pointers for info we can't compute yet)
// and properly links this to/from the patch.
//
void patch::create_interpatch_ghost_zone
	(const patch_edge& my_edge, const patch_edge& other_edge,
	 int patch_overlap_width)
{
// make sure we belong to the right patch
assert(my_edge.my_patch() == *this);

interpatch_ghost_zone *temp
	= new interpatch_ghost_zone(my_edge, other_edge,
				    patch_overlap_width);
set_ghost_zone(my_edge, temp);
}

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

//
// This is a helper function for  setup_*_ghost_zone().  This function
// assert()s that one of the ghost zone pointers (which one is selected
// by  edge ) is NULL, then stores a value in it.
//
void patch::set_ghost_zone(const patch_edge& edge, ghost_zone* gzp)
{
ghost_zone*& ghost_zone_ptr_to_set
	= edge.is_min()
	  ? (edge.is_rho() ? min_rho_ghost_zone_ : min_sigma_ghost_zone_)
	  : (edge.is_rho() ? max_rho_ghost_zone_ : max_sigma_ghost_zone_);

assert(ghost_zone_ptr_to_set == NULL);
ghost_zone_ptr_to_set = gzp;
}

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

//
// This function finds which patch edge is adjacent to a neighboring
// patch q, or does an error_exit() if q isn't actually a neighboring patch.
// The computation is done using only (rho,sigma) coordinate sets and
// min/max dang bounds ==> it's ok to use this function in setting up
// interpatch ghost zones.
//
// Arguments:
// q = The (supposedly) neighboring patch.
// patch_overlap_width = The number of grid points these patches overlap.
//		      If this is nonzero, then these patches must have the
//		      same grid spacing in the perpendicular direction.
//
const patch_edge& patch::edge_adjacent_to_patch(const patch& q,
						int patch_overlap_width /* = 0 */)
	const
{
const patch& p = *this;

// which (rho,sigma) coordinate do the patches have in common?
// ... this is the perp coordinate for the border
const local_coords::coords_set common_coord_set
	= p.coords_set_rho_sigma() & q.coords_set_rho_sigma();

// is this coordinate rho or sigma in each patch?
const bool common_is_p_rho   = (common_coord_set == p.coords_set_rho  ());
const bool common_is_p_sigma = (common_coord_set == p.coords_set_sigma());
if ((common_is_p_rho ^ common_is_p_sigma) != 0x1)
   then error_exit(ERROR_EXIT,
"***** patch::edge_adjacent_to_patch():\n"
"        common coordinate isn't exactly one of p.{rho,sigma}!\n"
"        p.name()=\"%s\" q.name()=\"%s\"\n"
"        common_coord_set=%s\n"
"        common_is_p_rho=%d common_is_p_sigma=%d\n"
,
		   p.name(), q.name(),
		   local_coords::name_of_coords_set(common_coord_set),
		   int(common_is_p_rho), int(common_is_p_sigma));
								/*NOTREACHED*/
const bool common_is_q_rho   = (common_coord_set == q.coords_set_rho  ());
const bool common_is_q_sigma = (common_coord_set == q.coords_set_sigma());
if ((common_is_q_rho ^ common_is_q_sigma) != 0x1)
   then error_exit(ERROR_EXIT,
"***** patch::edge_adjacent_to_patch():\n"
"        common coordinate isn't exactly one of q.{rho,sigma}!\n"
"        p.name()=\"%s\" q.name()=\"%s\"\n"
"        common_coord_set=%s\n"
"        common_is_q_rho=%d common_is_q_sigma=%d\n"
,
		   p.name(), q.name(),
		   local_coords::name_of_coords_set(common_coord_set),
		   int(common_is_q_rho), int(common_is_q_sigma));
								/*NOTREACHED*/

// how much do the patches overlap?
// ... eg patch_overlap_width = 3 would be
//	p   p   p   p   p
//		q   q   q   q   q
//     so the overlap would be (patch_overlap_width-1) * delta = 2 * delta
if ( (patch_overlap_width-1 != 0)
     && jtutil::fuzzy<fp>::NE(p.delta_dang(common_is_p_rho),
			      q.delta_dang(common_is_q_rho)) )
   then error_exit(ERROR_EXIT,
"***** patch::edge_adjacent_to_patch():\n"
"        patch_overlap_width != 0 must have same perp grid spacing in both patches!\n"
"        p.name()=\"%s\" q.name()=\"%s\"\n"
"        common_coord_set=%s\n"
"        common_is_p_rho=%d common_is_q_rho=%d\n"
"        p.delta_dang(common_is_p_rho)=%g\n"
"        q.delta_dang(common_is_q_rho)=%g\n"
,
		   p.name(), q.name(),
		   local_coords::name_of_coords_set(common_coord_set),
		   int(common_is_p_rho), int(common_is_q_rho),
		   double(p.delta_dang(common_is_p_rho)),
		   double(q.delta_dang(common_is_q_rho)));	/*NOTREACHED*/


const fp doverlap = fp(patch_overlap_width-1) * p.delta_dang(common_is_p_rho);

// where is the common boundary relative to the min/max sides of each patch?
const bool common_is_p_min_q_max
    = local_coords::fuzzy_EQ_dang(p.min_dang(common_is_p_rho),
				  q.max_dang(common_is_q_rho) - doverlap);
const bool common_is_p_max_q_min
    = local_coords::fuzzy_EQ_dang(p.max_dang(common_is_p_rho),
				  q.min_dang(common_is_q_rho) + doverlap);
if ((common_is_p_min_q_max ^ common_is_p_max_q_min) != 0x1)
   then error_exit(ERROR_EXIT,
"***** patch::edge_adjacent_to_patch():\n"
"        common coordinate isn't exactly one of {pmax/qmin, pmin/qmax}!\n"
"        p.name()=\"%s\" q.name()=\"%s\"\n"
"        common_coord_set=%s\n"
"        common_is_p_rho=%d common_is_q_rho=%d\n"
"        p.delta_dang(common_is_p_rho)=%g\n"
"        q.delta_dang(common_is_q_rho)=%g\n"
"        patch_overlap_width=%d doverlap=%g\n"
"        common_is_p_min_q_max=%d common_is_p_max_q_min=%d\n"
,
		   p.name(), q.name(),
		   local_coords::name_of_coords_set(common_coord_set),
		   int(common_is_p_rho), int(common_is_q_rho),
		   double(p.delta_dang(common_is_p_rho)),
		   double(q.delta_dang(common_is_q_rho)),
		   patch_overlap_width, double(doverlap),
		   int(common_is_p_min_q_max), int(common_is_p_max_q_min));
								/*NOTREACHED*/

return p.minmax_ang_patch_edge(common_is_p_min_q_max, common_is_p_rho);
}

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

//
// This function verifies (via assert()) that all ghost zones of this
// patch have been fully set up.
//
void patch::assert_all_ghost_zones_fully_setup() const
{
assert(min_rho_ghost_zone_ != NULL);
assert(max_rho_ghost_zone_ != NULL);
assert(min_sigma_ghost_zone_ != NULL);
assert(max_sigma_ghost_zone_ != NULL);

// these calls are no-ops for non-interpatch ghost zones
min_rho_ghost_zone().assert_fully_setup();
max_rho_ghost_zone().assert_fully_setup();
min_sigma_ghost_zone().assert_fully_setup();
max_sigma_ghost_zone().assert_fully_setup();
}

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

	  }	// namespace AHFinderDirect

```

patch/coords.cc:
```
// coords.cc - coordinate conversions etc
// $Header$

//
// local_coords::fuzzy_EQ_{ang,dang}
// local_coords::modulo_reduce_{ang,dang}
//
// (r,(mu,nu,phi)) <--> (x,y,z)
// ((mu,nu,phi)) --> the 3rd
//
// partial_{x,y,z}_wrt_{mu,nu,phi}
// partial_{mu,nu,phi}_wrt_{x,y,z}
// partial2_{mu,nu,phi}_wrt_{xx,xy,xz,yy,yz,zz}
//
// usual polar spherical (r,theta,phi) <--> (x,y,z)
// ((mu,nu,phi)) <--> usual polar spherical (theta,phi)
// ((mu,nu,phi)) --> direction cosines (xcos,ycos,zcos)
//
// local_coords::mu_nu_phi::name_of_coords_set
//

#include <math.h>
#include <float.h>
#include <assert.h>
#include <limits.h>

#ifdef STANDALONE_TEST
  #include "fake_cctk.h"
#else
  #include "cctk.h"
#endif

#include "config.h"
#include "stdc.h"
#include "../jtutil/util.hh"
using jtutil::error_exit;
using jtutil::arctan_xy;
using jtutil::signum;
using jtutil::pow2;
using jtutil::hypot3;

#include "coords.hh"

// all the code in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// these functions test if two angles are fuzzily equal mod 2*pi radians
// (360 degrees)
//

namespace local_coords
	  {

bool fuzzy_EQ_ang(fp ang1, fp ang2)
{
return jtutil::fuzzy<fp>::is_integer( (ang2 - ang1)/(2.0*PI) );
}

bool fuzzy_EQ_dang(fp dang1, fp dang2)
{
return jtutil::fuzzy<fp>::is_integer( (dang2 - dang1)/360.0 );
}

	  }

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

//
// modulo-reduce  {ang,dang}  to be (fuzzily) within the range
// [min,max]_{ang,dang}, or error_exit() if no such value exists
//

namespace local_coords
	  {

fp modulo_reduce_ang(fp ang, fp min_ang, fp max_ang)
{
return jtutil::modulo_reduce(ang, 2.0*PI, min_ang, max_ang);
}

fp modulo_reduce_dang(fp dang, fp min_dang, fp max_dang)
{
return jtutil::modulo_reduce(dang, 360.0, min_dang, max_dang);
}

	  }

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

//
// these functions convert ((mu,nu,phi)) <--> (x,y,z)
//

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

// valid in +/- z patch
// not valid in xy plane (z == 0, i.e. (cos(mu) == 0 || cos(nu) == 0))
// unless r == 0 (x == y == z == 0)
// c.f. page 9.2 of my mpe notes
namespace local_coords
	  {
void xyz_of_r_mu_nu(fp r, fp mu, fp nu,   fp& x, fp& y, fp& z)
{
const fp sign_y        = signum(sin(mu));
const fp sign_z_via_mu = signum(cos(mu));
assert( jtutil::fuzzy<fp>::NE(cos(mu), 0.0) );
const fp y_over_z = tan(mu);

const fp sign_x        = signum(sin(nu));
const fp sign_z_via_nu = signum(cos(nu));
assert( jtutil::fuzzy<fp>::NE(cos(nu), 0.0) );
const fp x_over_z = tan(nu);

// failure of next assert() ==> inconsistent input (mu,nu)
assert(sign_z_via_mu == sign_z_via_nu);
const fp sign_z = sign_z_via_mu;

const fp temp = 1.0 / sqrt(1.0 + pow2(y_over_z) + pow2(x_over_z));

z = sign_z * r * temp;
x = x_over_z * z;
y = y_over_z * z;

// if (jtutil::fuzzy<fp>::NE(r, 0.0))
//    then {
// 	if (jtutil::fuzzy<fp>::NE(x, 0.0))
//  	   then assert( signum(x) == sign_x );
// 	if (jtutil::fuzzy<fp>::NE(y, 0.0))
// 	   then assert( signum(y) == sign_y );
// 	}
}
	  }

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

// valid in +/- y patch
// not valid in xz plane (y == 0, i.e. (sin(mu) == 0 || sin(phi) == 0)
// unless r == 0 (x == y == z == 0)
// c.f. page 9.3 of my mpe notes
namespace local_coords
	  {
void xyz_of_r_mu_phi(fp r, fp mu, fp phi,   fp& x, fp& y, fp& z)
{
const fp  mu_bar = 0.5*PI - mu ;
const fp phi_bar = 0.5*PI - phi;

const fp sign_z            = signum(sin(mu_bar));
const fp sign_y_via_mu_bar = signum(cos(mu_bar));
assert( jtutil::fuzzy<fp>::NE(cos(mu_bar), 0.0) );
const fp z_over_y = tan(mu_bar);

const fp sign_x             = signum(sin(phi_bar));
const fp sign_y_via_phi_bar = signum(cos(phi_bar));
assert( jtutil::fuzzy<fp>::NE(cos(phi_bar), 0.0) );
const fp x_over_y = tan(phi_bar);

// failure of next assert() ==> inconsistent input (mu,phi)
assert(sign_y_via_mu_bar == sign_y_via_phi_bar);
const fp sign_y = sign_y_via_mu_bar;

const fp temp = 1.0 / sqrt(1.0 + pow2(z_over_y) + pow2(x_over_y));

y = sign_y * r * temp;
z = z_over_y * y;
x = x_over_y * y;

// if (jtutil::fuzzy<fp>::NE(r, 0.0))
//    then {
// 	if (jtutil::fuzzy<fp>::NE(z, 0.0))
// 	   then assert( signum(z) == sign_z );
// 	if (jtutil::fuzzy<fp>::NE(x, 0.0))
// 	   then assert( signum(x) == sign_x );
// 	}
}
	  }

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

// valid in +/- x patch
// not valid in yz plane (x == 0, i.e. (sin(nu) == 0 || cos(phi) == 0))
// unless r == 0 (x == y == z == 0)
// c.f. page 9.4 of my mpe notes
namespace local_coords
	  {
void xyz_of_r_nu_phi(fp r, fp nu, fp phi,   fp& x, fp& y, fp& z)
{
const fp nu_bar = 0.5*PI - nu ;

const fp sign_z            = signum(sin(nu_bar));
const fp sign_x_via_nu_bar = signum(cos(nu_bar));
assert( jtutil::fuzzy<fp>::NE(cos(nu_bar), 0.0) );
const fp z_over_x = tan(nu_bar);

const fp sign_y         = signum(sin(phi));
const fp sign_x_via_phi = signum(cos(phi));
assert( jtutil::fuzzy<fp>::NE(cos(phi), 0.0) );
const fp y_over_x = tan(phi   );

// failure of next assert() ==> inconsistent input (nu,phi)
assert(sign_x_via_nu_bar == sign_x_via_phi);
const fp sign_x = sign_x_via_nu_bar;

const fp temp = 1.0 / sqrt(1.0 + pow2(z_over_x) + pow2(y_over_x));

x = sign_x * r * temp;
z = z_over_x * x;
y = y_over_x * x;

// if (jtutil::fuzzy<fp>::NE(r, 0.0))
//    then {
// 	if (jtutil::fuzzy<fp>::NE(z, 0.0))
// 	   then assert( signum(z) == sign_z );
// 	if (jtutil::fuzzy<fp>::NE(y, 0.0))
// 	   then assert( signum(y) == sign_y );
// 	}
}
	  }

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

//
// these functions give any one of {mu,nu,phi} in terms of the other two
//

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

// ill-conditioned near z axis
// not valid in xy plane (cos(mu) == 0 || cos(nu) == 0)
namespace local_coords
	  {
fp phi_of_mu_nu(fp mu, fp nu)
{
fp x, y, z;
xyz_of_r_mu_nu(1.0,mu,nu, x,y,z);
return phi_of_xy(x, y);
}
	  }

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

// ill-conditioned near y axis
// not valid in xz plane (sin(mu) == 0 || sin(phi == 0)
namespace local_coords
	  {
fp nu_of_mu_phi(fp mu, fp phi)
{
fp x, y, z;
xyz_of_r_mu_phi(1.0,mu,phi, x,y,z);
return nu_of_xz(x, z);
}
	  }

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

// ill-conditioned near x axis
// not valid in yz plane (sin(nu) == 0 || cos(phi) == 0)
namespace local_coords
	  {
fp mu_of_nu_phi(fp nu, fp phi)
{
fp x, y, z;
xyz_of_r_nu_phi(1.0,nu,phi, x,y,z);
return mu_of_yz(y, z);
}
	  }


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

namespace local_coords
	  {
fp r_of_xyz(fp x, fp y, fp z) { return hypot3(x, y, z); }
fp mu_of_yz(fp y, fp z)  { return arctan_xy(z,y); }
fp nu_of_xz(fp x, fp z)  { return arctan_xy(z,x); }
fp phi_of_xy(fp x, fp y) { return arctan_xy(x,y); }
	  }

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

//
// these functions compute the partial derivatives
//	partial {x,y,z} / partial {mu,nu,phi}
//
// Bugs: they're slow :(
//

namespace local_coords
	  {
void partial_xyz_wrt_r_mu_nu
	(fp r, fp mu, fp nu,
	 fp& partial_x_wrt_r, fp& partial_x_wrt_mu, fp& partial_x_wrt_nu,
	 fp& partial_y_wrt_r, fp& partial_y_wrt_mu, fp& partial_y_wrt_nu,
	 fp& partial_z_wrt_r, fp& partial_z_wrt_mu, fp& partial_z_wrt_nu)
{
const fp tan_mu = tan(mu);
const fp tan_nu = tan(nu);
const fp tan2_mu = pow2(tan_mu);
const fp tan2_nu = pow2(tan_nu);

fp x, y, z;
xyz_of_r_mu_nu(r,mu,nu, x,y,z);

// Comment this out, accept a nan instead
// assert( jtutil::fuzzy<fp>::NE(r, 0.0) );
const fp rinv = 1.0/r;
partial_x_wrt_r = x*rinv;
partial_y_wrt_r = y*rinv;
partial_z_wrt_r = z*rinv;

const fp t = 1 + tan2_mu + tan2_nu;	// = $r^2/z^2$
const fp partial_t_wrt_mu = 2.0 * tan_mu * (1.0+tan2_mu);
const fp partial_t_wrt_nu = 2.0 * tan_nu * (1.0+tan2_nu);

const fp r2_over_zt2 = (r*r) / (z*t*t);
partial_z_wrt_mu = -0.5 * r2_over_zt2 * partial_t_wrt_mu;
partial_z_wrt_nu = -0.5 * r2_over_zt2 * partial_t_wrt_nu;

partial_x_wrt_mu = tan_nu*partial_z_wrt_mu;
partial_x_wrt_nu = tan_nu*partial_z_wrt_nu + z*(1.0+tan2_nu);
partial_y_wrt_mu = tan_mu*partial_z_wrt_mu + z*(1.0+tan2_mu);
partial_y_wrt_nu = tan_mu*partial_z_wrt_nu;
}
	  }

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

namespace local_coords
	  {
void partial_xyz_wrt_r_mu_phi
	(fp r, fp mu, fp phi,
	 fp& partial_x_wrt_r, fp& partial_x_wrt_mu, fp& partial_x_wrt_phi,
	 fp& partial_y_wrt_r, fp& partial_y_wrt_mu, fp& partial_y_wrt_phi,
	 fp& partial_z_wrt_r, fp& partial_z_wrt_mu, fp& partial_z_wrt_phi)
{
const fp  mu_bar = 0.5*PI - mu ;
const fp phi_bar = 0.5*PI - phi;

const fp tan_mu_bar  = tan(mu_bar);
const fp tan_phi_bar = tan(phi_bar);
const fp tan2_mu_bar  = pow2(tan_mu_bar);
const fp tan2_phi_bar = pow2(tan_phi_bar);

fp x, y, z;
xyz_of_r_mu_phi(r,mu,phi, x,y,z);

// Comment this out, accept a nan instead
// assert( jtutil::fuzzy<fp>::NE(r, 0.0) );
const fp rinv = 1.0/r;
partial_x_wrt_r = x*rinv;
partial_y_wrt_r = y*rinv;
partial_z_wrt_r = z*rinv;

const fp t = 1 + tan2_mu_bar + tan2_phi_bar;	// = $r^2/y^2$
const fp partial_t_wrt_mu_bar  = 2.0 * tan_mu_bar  * (1.0+tan2_mu_bar );
const fp partial_t_wrt_phi_bar = 2.0 * tan_phi_bar * (1.0+tan2_phi_bar);

const fp r2_over_yt2 = (r*r) / (y*t*t);
partial_y_wrt_mu  = 0.5 * r2_over_yt2 * partial_t_wrt_mu_bar;
partial_y_wrt_phi = 0.5 * r2_over_yt2 * partial_t_wrt_phi_bar;

partial_x_wrt_mu  = tan_phi_bar*partial_y_wrt_mu;
partial_x_wrt_phi = tan_phi_bar*partial_y_wrt_phi - y*(1.0+tan2_phi_bar);
partial_z_wrt_mu  = tan_mu_bar *partial_y_wrt_mu  - y*(1.0+tan2_mu_bar );
partial_z_wrt_phi = tan_mu_bar *partial_y_wrt_phi;
}
	  }

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

namespace local_coords
	  {
void partial_xyz_wrt_r_nu_phi
	(fp r, fp nu, fp phi,
	 fp& partial_x_wrt_r, fp& partial_x_wrt_nu, fp& partial_x_wrt_phi,
	 fp& partial_y_wrt_r, fp& partial_y_wrt_nu, fp& partial_y_wrt_phi,
	 fp& partial_z_wrt_r, fp& partial_z_wrt_nu, fp& partial_z_wrt_phi)
{
const fp  nu_bar = 0.5*PI - nu ;

const fp tan_nu_bar = tan(nu_bar);
const fp tan_phi    = tan(phi);
const fp tan2_nu_bar = pow2(tan_nu_bar);
const fp tan2_phi    = pow2(tan_phi);

fp x, y, z;
xyz_of_r_nu_phi(r,nu,phi, x,y,z);

// Comment this out, accept a nan instead
// assert( jtutil::fuzzy<fp>::NE(r, 0.0) );
const fp rinv = 1.0/r;
partial_x_wrt_r = x*rinv;
partial_y_wrt_r = y*rinv;
partial_z_wrt_r = z*rinv;

const fp t = 1 + tan2_nu_bar + tan2_phi;	// = $r^2/x^2$
const fp partial_t_wrt_nu_bar  = 2.0 * tan_nu_bar * (1.0+tan2_nu_bar);
const fp partial_t_wrt_phi     = 2.0 * tan_phi    * (1.0+tan2_phi   );

const fp r2_over_xt2 = (r*r) / (x*t*t);
partial_x_wrt_nu  =  0.5 * r2_over_xt2 * partial_t_wrt_nu_bar;
partial_x_wrt_phi = -0.5 * r2_over_xt2 * partial_t_wrt_phi;

partial_y_wrt_nu  = tan_phi   *partial_x_wrt_nu;
partial_y_wrt_phi = tan_phi   *partial_x_wrt_phi + x*(1.0+tan2_phi   );
partial_z_wrt_nu  = tan_nu_bar*partial_x_wrt_nu  - x*(1.0+tan2_nu_bar);
partial_z_wrt_phi = tan_nu_bar*partial_x_wrt_phi;
}
	  }

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

//
// these functions compute the partial derivatives
//	partial {mu,nu,phi} / partial {x,y,z}
// as computed by the maple file "coord_derivs.{maple,out}" in this directory
//
namespace local_coords
	  {
fp partial_mu_wrt_y(fp y, fp z) { return  z / (y*y + z*z); }
fp partial_mu_wrt_z(fp y, fp z) { return -y / (y*y + z*z); }

fp partial_nu_wrt_x(fp x, fp z) { return  z / (x*x + z*z); }
fp partial_nu_wrt_z(fp x, fp z) { return -x / (x*x + z*z); }

fp partial_phi_wrt_x(fp x, fp y) { return -y / (x*x + y*y); }
fp partial_phi_wrt_y(fp x, fp y) { return  x / (x*x + y*y); }
	  }

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

//
// these functions compute the 2nd partial derivatives
//	partial {mu,nu,phi} / partial {xx,xy,xz,yy,yz,zz}
// as computed by the maple file "coord_derivs.{maple,out}" in this directory
//
namespace local_coords
	  {
fp partial2_mu_wrt_yy(fp y, fp z) {return   -2.0*y*z  / pow2(y*y + z*z);}
fp partial2_mu_wrt_yz(fp y, fp z) {return (y*y - z*z) / pow2(y*y + z*z);}
fp partial2_mu_wrt_zz(fp y, fp z) {return    2.0*y*z  / pow2(y*y + z*z);}

fp partial2_nu_wrt_xx(fp x, fp z) {return   -2.0*x*z  / pow2(x*x + z*z);}
fp partial2_nu_wrt_xz(fp x, fp z) {return (x*x - z*z) / pow2(x*x + z*z);}
fp partial2_nu_wrt_zz(fp x, fp z) {return    2.0*x*z  / pow2(x*x + z*z);}

fp partial2_phi_wrt_xx(fp x, fp y) {return    2.0*x*y  / pow2(x*x + y*y);}
fp partial2_phi_wrt_xy(fp x, fp y) {return (y*y - x*x) / pow2(x*x + y*y);}
fp partial2_phi_wrt_yy(fp x, fp y) {return   -2.0*x*y  / pow2(x*x + y*y);}
	  }

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

//
// these functions convert the usual polar spherical (theta,phi) <--> (x,y,z)
//

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

namespace local_coords
	  {
void xyz_of_r_theta_phi(fp r, fp theta, fp phi,   fp& x, fp& y, fp& z)
{
z = r * cos(theta);
x = r * sin(theta) * cos(phi);
y = r * sin(theta) * sin(phi);
}
	  }

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

namespace local_coords
	  {
void r_theta_phi_of_xyz(fp x, fp y, fp z,   fp& r, fp& theta, fp& phi)
{
r = r_of_xyz(x, y, z);
theta = theta_of_xyz(x, y, z);
phi = phi_of_xy(x, y);
}
	  }

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

namespace local_coords
	  {
fp theta_of_xyz(fp x, fp y, fp z)
{
return arctan_xy(z , hypot(x,y));
}
	  }

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

//
// these functions convert ((mu,nu,phi)) <--> usual polar spherical (theta,phi)
// ... note phi is the same coordinate in both systems
//

namespace local_coords
	  {
void theta_phi_of_mu_nu(fp mu, fp nu,   fp& ps_theta, fp& ps_phi)
{
fp x, y, z;
xyz_of_r_mu_nu(1.0,mu,nu, x,y,z);

ps_theta = theta_of_xyz(x, y, z);
ps_phi = phi_of_xy(x, y);
}
	  }

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

// Bugs: computes ps_phi via trig, even though it's trivially == phi
namespace local_coords
	  {
void theta_phi_of_mu_phi(fp mu, fp phi,   fp& ps_theta, fp& ps_phi)
{
fp x, y, z;
xyz_of_r_mu_phi(1.0,mu,phi, x,y,z);

ps_theta = theta_of_xyz(x, y, z);
ps_phi = phi_of_xy(x, y);
assert( fuzzy_EQ_ang(ps_phi, phi) );
}
	  }

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

// Bugs: computes ps_phi via trig, even though it's trivially == phi
namespace local_coords
	  {
void theta_phi_of_nu_phi(fp nu, fp phi,   fp& ps_theta, fp& ps_phi)
{
fp x, y, z;
xyz_of_r_nu_phi(1.0,nu,phi, x,y,z);

ps_theta = theta_of_xyz(x, y, z);
ps_phi = phi_of_xy(x, y);
assert( fuzzy_EQ_ang(ps_phi, phi) );
}
	  }

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

namespace local_coords
	  {
void mu_nu_of_theta_phi(fp ps_theta, fp ps_phi,   fp& mu, fp& nu)
{
fp x, y, z;
xyz_of_r_theta_phi(1.0,ps_theta,ps_phi, x,y,z);

mu = mu_of_yz(y, z);
nu = nu_of_xz(x, z);
}
	  }

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

// Bugs: computes phi via trig, even though it's trivially == ps_phi
namespace local_coords
	  {
void mu_phi_of_theta_phi(fp ps_theta, fp ps_phi,   fp& mu, fp& phi)
{
fp x, y, z;
xyz_of_r_theta_phi(1.0,ps_theta,ps_phi, x,y,z);

mu  =  mu_of_yz(y, z);
phi = phi_of_xy(x, y);
assert( fuzzy_EQ_ang(phi, ps_phi) );
}
	  }

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

// Bugs: computes phi via trig, even though it's trivially == ps_phi
namespace local_coords
	  {
void nu_phi_of_theta_phi(fp ps_theta, fp ps_phi,   fp& nu, fp& phi)
{
fp x, y, z;
xyz_of_r_theta_phi(1.0,ps_theta,ps_phi, x,y,z);

nu  =  nu_of_xz(x, z);
phi = phi_of_xy(x, y);
assert( fuzzy_EQ_ang(phi, ps_phi) );
}
	  }

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

//
// these functions convert ((mu,nu,phi)) to the direction cosines
// (xcos,ycos,zcos)
//

namespace local_coords
	  {
void xyzcos_of_mu_nu (fp mu, fp nu , fp& xcos, fp& ycos, fp& zcos)
{
xyz_of_r_mu_nu(1.0,mu,nu, xcos,ycos,zcos);
}
	  }

namespace local_coords
	  {
void xyzcos_of_mu_phi(fp mu, fp phi, fp& xcos, fp& ycos, fp& zcos)
{
xyz_of_r_mu_phi(1.0,mu,phi, xcos,ycos,zcos);
}
	  }

namespace local_coords
	  {
void xyzcos_of_nu_phi(fp nu, fp phi, fp& xcos, fp& ycos, fp& zcos)
{
xyz_of_r_nu_phi(1.0,nu,phi, xcos,ycos,zcos);
}
	  }

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

//
// This function computes a human-readable name from a (mu,nu,phi)
// coordinates set.
//
const char* local_coords::name_of_coords_set(coords_set S)
{
//
// we have to use an if-else chain because the  local_coords::set_*
// constants aren't compile-time constants and hence aren't eligible
// to be switch case labels
//
if	(S == coords_set_empty)
   then return "{}";
else if (S == coords_set_mu)
   then return "mu";
else if (S == coords_set_nu)
   then return "nu";
else if (S == coords_set_phi)
   then return "phi";
else if (S == (coords_set_mu|coords_set_nu))
   then return "{mu,nu}";
else if (S == (coords_set_mu|coords_set_phi))
   then return "{mu,phi}";
else if (S == (coords_set_nu|coords_set_phi))
   then return "{nu,phi}";
else if (S == (coords_set_mu|coords_set_nu|coords_set_phi))
   then return "{mu,nu,phi}";
else	error_exit(PANIC_EXIT,
"***** local_coords::mu_nu_phi::name_of_coords_set:\n"
"        S=0x%x isn't a valid  coords_set  bit vector!\n"
,
		   int(S));					/*NOTREACHED*/
}

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

	  }	// namespace AHFinderDirect

```

patch/patch_system.cc:
```
// patch_system.cc -- describes the (an) entire system of patches
// $Header$

//
// patch_system::patch_system
// patch_system::~patch_system
// patch_system::create_patches
// patch_system::setup_gridfn_storage
// patch_system::setup_ghost_zones__full_sphere
// patch_system::setup_ghost_zones__plus_z_hemisphere
// patch_system::setup_ghost_zones__plus_xy_quadrant_mirrored
// patch_system::setup_ghost_zones__plus_xy_quadrant_rotating
// patch_system::setup_ghost_zones__plus_xz_quadrant_mirrored
// patch_system::setup_ghost_zones__plus_xz_quadrant_rotating
// patch_system::setup_ghost_zones__plus_xyz_octant_mirrored
// patch_system::setup_ghost_zones__plus_xyz_octant_rotating
// patch_system::create_periodic_symmetry_ghost_zones
// patch_system::create_interpatch_ghost_zones
// patch_system::finish_interpatch_setup
// patch_system::assert_all_ghost_zones_fully_setup
//
// patch_system::N_patches_of_type
// patch_system::name_of_type
// patch_system::type_of_name
// patch_system::plus_or_minus_xyz_patch
// patch_system::patch_number_of_name
//
// patch_system::patch_irho_isigma_of_gpn
// patch_system::ghosted_patch_irho_isigma_of_gpn
//
// patch_system::set_gridfn_to_constant
// patch_system::gridfn_copy
// patch_system::ghosted_gridfn_copy
// patch_system::add_to_gridfn
// patch_system::add_to_ghosted_gridfn
// patch_system::gridfn_norms
// patch_system::ghosted_gridfn_norms
//
// patch_system::circumference
// patch_system::integrate_gridfn
// 
// patch_system::patch_containing_local_xyz
// patch_system::radius_in_local_xyz_direction
// patch_system::radii_in_local_xyz_directions
//
// patch_system::print_unknown_gridfn
// patch_system::read_unknown_gridfn
// patch_system::output_unknown_gridfn
//
// patch_system::synchronize
// patch_system::compute_synchronize_Jacobian
// patch_system::synchronize_Jacobian_global_minmax_ym
// patch_system::synchronize_Jacobian
/// patch_system::fold_Jacobian
/// patch_system::ghost_zone_Jacobian
//

#include <assert.h>
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/types.h>

#include <vector>
#include <sstream>
#include <string>

#include "cctk.h"
#include "CactusBase/IOUtil/src/ioGH.h"

#include "config.h"
#include "stdc.h"
#include "../gr/gfns.hh"
#include "../jtutil/util.hh"
#include "../jtutil/array.hh"
#include "../jtutil/cpm_map.hh"
#include "../jtutil/linear_map.hh"
using jtutil::error_exit;

#ifdef HAVE_CAPABILITY_HDF5
// Some macros to fix compatibility issues as long as 1.8.0 is in beta
// phase
#  define H5_USE_16_API 1
#  include <hdf5.h>
#endif

#include "coords.hh"
#include "grid.hh"
#include "fd_grid.hh"
#include "patch.hh"
#include "patch_edge.hh"
#include "patch_interp.hh"
#include "ghost_zone.hh"
#include "patch_info.hh"
#include "patch_system.hh"
#include "patch_system_info.hh"

// all the code in this file is inside this namespace
namespace AHFinderDirect
	  {

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

//
// This function constructs a  patch_system  object.
//
// Constructor arguments:
// ghost_zone_width = Width in grid points of all ghost zones.
// patch_overlap_width = Number of grid points that adjacent
//		      nominally-just-touching patches should overlap.
//		      For example, with patch_overlap_width == 3, here
//		      are the grid points of two neighboring patches:
//			x   x   x   x   x   X
//                                      |
//			            O   o   o   o   o   o
//		      Here | marks the "just touching" boundary,
//		      x and o the grid points before this extension,
//		      and X and O the extra grid points added by this
//		      extension.  For this example, the patch_extend_width
//		      parameter used by some other functions would
//		      be 1; in general
//			patch_overlap_width = 2*patch_extend_width + 1
// N_zones_per_right_angle = This sets the grid spacing (same in both
//			     directions) to 90.0 / N_zones_per_right_angle.
//			     It's a fatal error (error_exit()) if this
//			     doesn't evenly divide the grid sizes in both
//			     directions.
// ip_interp_handle = Cactus handle to the interpatch interpolation operator;
//		      this must be a 1-D interpolator
// ip_interp_par_table_handle = Cactus handle to the parameter table for the
//				interpatch interpolation operator
// surface_interp_handle = Cactus handle to the surface interpolation
//			   operator; this is optional, and is only used by
//			     radius_in_{local,global}_xyz_direction()
//			   If this is used, it must be a 2-D interpolator
// surface_interp_par_table_handle = Cactus handle to the parameter table
//				     for the surface interpolation operator;
//				     this is optional, and is only used by
//				       radius_in_local_xyz_direction()
// print_summary_msg_flag = true to print 2 lines of CCTK_VInfo messages
//				 giving the patch system type and origin
//			    false to skip this
// print_detailed_msg_flag = true to print extensive messages tracing the
//				  creation and initialization of various
//				  data structures
//			     false to skip this
//
patch_system::patch_system(fp origin_x_in, fp origin_y_in, fp origin_z_in,
			   enum patch_system_type type_in,
			   int ghost_zone_width, int patch_overlap_width,
			   int N_zones_per_right_angle,
			   int min_gfn_in, int max_gfn_in,
			   int ghosted_min_gfn_in, int ghosted_max_gfn_in,
			   int ip_interp_handle, int ip_interp_par_table_handle,
			   int surface_interp_handle_in,
			   int surface_interp_par_table_handle_in,
			   bool print_summary_msg_flag,
			   bool print_detailed_msg_flag)

	: global_coords_(origin_x_in, origin_y_in, origin_z_in),
	  type_(type_in),
	  N_patches_(N_patches_of_type(type_in)),
	  all_patches_(new patch*[N_patches_]),
	  starting_gpn_(new int[N_patches_+1]),
	  ghosted_starting_gpn_(new int[N_patches_+1]),
	  gridfn_storage_(NULL),		// set in setup_gridfn_storage()
	  ghosted_gridfn_storage_(NULL),	// set in setup_gridfn_storage()
	  global_min_ym_(0), global_max_ym_(0),
					// set in compute_synchronize_Jacobian()
	  surface_interp_handle_          (surface_interp_handle_in),
	  surface_interp_par_table_handle_(surface_interp_par_table_handle_in)
{
if (! jtutil::is_odd(patch_overlap_width))
   then error_exit(ERROR_EXIT,
"***** patch_system::patch_system(): implementation restriction:\n"
"        patch_overlap_width=%d, but we only support odd values!\n"
,
		   patch_overlap_width);			/*NOTREACHED*/
const int patch_extend_width = patch_overlap_width >> 1;

if (ghost_zone_width < fd_grid::molecule_radius())
   then error_exit(ERROR_EXIT,
"***** patch_system::patch_system():\n"
"        must have ghost_zone_width >= fd_grid::molecule_radius()\n"
"        but got ghost_zone_width=%d fd_grid::molecule_radius()=%d!\n"
"        FINITE_DIFF_ORDER=%d (see #define in src/include/config.hh)\n"
,
		   ghost_zone_width, fd_grid::molecule_radius(),
		   FINITE_DIFF_ORDER);				/*NOTREACHED*/

if (print_summary_msg_flag)
   then {
	CCTK_VInfo(CCTK_THORNSTRING,
		   "      constructing %s patch system",
		   name_of_type(type()));
	CCTK_VInfo(CCTK_THORNSTRING,
		   "                   at origin (%g,%g,%g)",
		   double(origin_x()), double(origin_y()), double(origin_z()));
	CCTK_VInfo(CCTK_THORNSTRING,
		   "                   with %d angular zones per right angle",
		   N_zones_per_right_angle);
	}

// construct/interlink the patches and ghost zones
switch	(type_in)
	{
case patch_system__full_sphere:
	create_patches(patch_system_info::full_sphere::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__full_sphere(patch_overlap_width,
				       ip_interp_handle,
				       ip_interp_par_table_handle,
				       print_detailed_msg_flag);
	break;

case patch_system__plus_z_hemisphere:
	create_patches(patch_system_info::plus_z_hemisphere::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_z_hemisphere(patch_overlap_width,
					     ip_interp_handle,
					     ip_interp_par_table_handle,
					     print_detailed_msg_flag);
	break;

case patch_system__plus_xy_quadrant_mirrored:
	create_patches(patch_system_info::plus_xy_quadrant::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_xy_quadrant_mirrored(patch_overlap_width,
						     ip_interp_handle,
						     ip_interp_par_table_handle,
						     print_detailed_msg_flag);
	break;

case patch_system__plus_xy_quadrant_rotating:
	create_patches(patch_system_info::plus_xy_quadrant::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_xy_quadrant_rotating(patch_overlap_width,
						     ip_interp_handle,
						     ip_interp_par_table_handle,
						     print_detailed_msg_flag);
	break;

case patch_system__plus_xz_quadrant_mirrored:
	create_patches(patch_system_info::plus_xz_quadrant::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_xz_quadrant_mirrored(patch_overlap_width,
						     ip_interp_handle,
						     ip_interp_par_table_handle,
						     print_detailed_msg_flag);
	break;

case patch_system__plus_xz_quadrant_rotating:
	create_patches(patch_system_info::plus_xz_quadrant::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_xz_quadrant_rotating(patch_overlap_width,
						     ip_interp_handle,
						     ip_interp_par_table_handle,
						     print_detailed_msg_flag);
	break;

case patch_system__plus_xyz_octant_mirrored:
	create_patches(patch_system_info::plus_xyz_octant::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_xyz_octant_mirrored(patch_overlap_width,
						    ip_interp_handle,
						    ip_interp_par_table_handle,
						    print_detailed_msg_flag);
	break;

case patch_system__plus_xyz_octant_rotating:
	create_patches(patch_system_info::plus_xyz_octant::patch_info_array,
		       ghost_zone_width, patch_extend_width,
		       N_zones_per_right_angle,
		       print_detailed_msg_flag);
	setup_gridfn_storage(min_gfn_in, max_gfn_in,
			     ghosted_min_gfn_in, ghosted_max_gfn_in,
			     print_detailed_msg_flag);
	setup_ghost_zones__plus_xyz_octant_rotating(patch_overlap_width,
						    ip_interp_handle,
						    ip_interp_par_table_handle,
						    print_detailed_msg_flag);
	break;

default:
	error_exit(ERROR_EXIT,
"***** patch_system::patch_system(): bad type_in=(int)%d!\n"
,
		   int(type_in));				/*NOTREACHED*/
	}

if (print_summary_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      ==> %d nominal, %d ghosted angular grid points",
		   N_grid_points(), ghosted_N_grid_points());
}

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

//
// This function destroys a  patch_system  object.
//
patch_system::~patch_system()
{
delete[] ghosted_gridfn_storage_;
delete[] gridfn_storage_;
delete[] ghosted_starting_gpn_;
delete[] starting_gpn_;

	for (int pn = N_patches()-1 ; pn >= 0 ; --pn)
	{
	delete &ith_patch(pn);
	}

delete[] all_patches_;
}

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

//
// This function is called from the patch_system:: constructor to
// construct a set of patches as described by an array of patch_info
// structures and associated arguments, and make these patches members
// of this patch system.  This function also correctly sets
//	N_grid_points_
//	N_ghosted_grid_points_
//	max_N_additional_points_
//	N_additional_points_
//	all_patches_[]
//	starting_gpn_[]
//	ghosted_starting_gpn_[]
// This function does *NOT* create any of the ghost zones, and does
// *NOT* set up any gridfns.
//
void patch_system::create_patches(const struct patch_info patch_info_in[],
				  int ghost_zone_width, int patch_extend_width,
				  int N_zones_per_right_angle,
				  bool print_msg_flag)
{
max_N_additional_points_ = 1;
// N_additional_points_ = 0;
N_additional_points_ = 1;
N_grid_points_ = 0;
ghosted_N_grid_points_ = 0;
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const struct patch_info& pi = patch_info_in[pn];
	const struct grid::grid_array_pars& grid_array_pars
		= pi.grid_array_pars(ghost_zone_width,
				     patch_extend_width,
				     N_zones_per_right_angle);
	const struct grid::grid_pars& grid_pars
		= pi.grid_pars(patch_extend_width,
			       N_zones_per_right_angle);

	if (print_msg_flag)
	   then CCTK_VInfo(CCTK_THORNSTRING,
			   "         constructing %s patch (%d x %d grid points)",
			   pi.name,
			   jtutil::how_many_in_range(grid_array_pars.min_irho,
						     grid_array_pars.max_irho),
			   jtutil::how_many_in_range(grid_array_pars.min_isigma,
						     grid_array_pars.max_isigma));

	struct patch *p;
	switch	(pi.ctype)
		{
	case 'z':
		p = new z_patch(*this, pn,
				pi.name, pi.is_plus,
				grid_array_pars, grid_pars);
		break;
	case 'x':
		p = new x_patch(*this, pn,
				pi.name, pi.is_plus,
				grid_array_pars, grid_pars);
		break;
	case 'y':
		p = new y_patch(*this, pn,
				pi.name, pi.is_plus,
				grid_array_pars, grid_pars);
		break;
	default:
		error_exit(ERROR_EXIT,
"***** patch_system::create_patches():\n"
"        unknown patch_info_in[pn=%d].ctype=0x%02d='%c'!\n"
,
			   pn, pi.ctype, pi.ctype);		/*NOTREACHED*/
		}

	// these record number of grid points in *previous* patches,
	// i.e. they do *not* include the number of grid points in this patch
	starting_gpn_[pn] = N_grid_points_;
	ghosted_starting_gpn_[pn] = ghosted_N_grid_points_;

	N_grid_points_+= p->N_grid_points();
	ghosted_N_grid_points_ += p->ghosted_N_grid_points();

	all_patches_[pn] = p;
	}

starting_gpn_        [N_patches_] = N_grid_points_;
ghosted_starting_gpn_[N_patches_] = ghosted_N_grid_points_;
}

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

//
// This function is called from the patch_system:: constructor to set
// up the storage for all gridfns in all patches, giving each gridfn a
// contiguous-across-all-patches storage array.  This function also makes
// a number of self-consistency checks to ensure that the gridfn storage
// subscripting is set up properly.
//
// This function assumes that all the patches have already been constructed
// before it is called.
//
// For example, given the patches {x,y,z}, the ghosted gridfns {H,J},
// and the nominal gridfns {a,b,c}, we might picture the storage like
// this:
//
//	xa xa xa ya ya za za za za
//	xb xb xb yb yb zb zb zb zb
//	xc xc xc yc yc zc zc zc zc
//
//	xH xH xH xH yH yH yH zH zH zH zH zH
//	xJ xJ xJ xJ yJ yJ yJ zJ zJ zJ zJ zJ
//
// Here the upper/lower blocks are for nominal/ghosted  gridfns.
// The storage is taken as being contiguous within each row (in fact
// within each block).  Thus the storage for all the nominal gridfns
// (or all the ghosted gridfns) in a single patch is *non*-contiguous.
//
// The creation of patches is done in several phases: first the patches
// are constructed with no gridfn storage, then we are called to set up
// the gridfn storage (taking into account the sizes of the other patches),
// then finally ghost zones are constructed and interlinked.
//
// FIXME: We should pad the gridfn storage as necessary to avoid cache
//        conflicts, but we don't do this at present.
//
void patch_system::setup_gridfn_storage
	(int min_gfn_in, int max_gfn_in,
	 int ghosted_min_gfn_in, int ghosted_max_gfn_in,
	 bool print_msg_flag)
{
const int N_gridfns_in = jtutil::how_many_in_range(min_gfn_in, max_gfn_in);
const int ghosted_N_gridfns_in
	= jtutil::how_many_in_range(ghosted_min_gfn_in, ghosted_max_gfn_in);

const int gfn_stride = N_grid_points() + max_N_additional_points();
const int ghosted_gfn_stride = ghosted_N_grid_points() + max_N_additional_points();

const int N_storage = gfn_stride * N_gridfns_in;
const int ghosted_N_storage = ghosted_gfn_stride * ghosted_N_gridfns_in;

if (print_msg_flag)
   then {
	CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up gridfn storage");
	CCTK_VInfo(CCTK_THORNSTRING,
		   "         gfn=[%d,%d] ghosted_gfn=[%d,%d]",
		   min_gfn_in, max_gfn_in,
		   ghosted_min_gfn_in, ghosted_max_gfn_in);
	CCTK_VInfo(CCTK_THORNSTRING,
		   "         N_grid_points()=%d ghosted_N_grid_points()=%d",
		   N_grid_points(), ghosted_N_grid_points());
	}

// storage arrays for all gridfns
gridfn_storage_         = new fp[N_storage];
ghosted_gridfn_storage_ = new fp[ghosted_N_storage];

// divide up the storage array among the patches
// and set up the storage in the individual patches themselves
	  {
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const int         posn =         starting_gpn_[pn];
	const int ghosted_posn = ghosted_starting_gpn_[pn];
	const struct grid_arrays::gridfn_pars gridfn_pars
		= {
		  min_gfn_in, max_gfn_in,
		  & gridfn_storage_[posn],
		  gfn_stride, 0, 0
		  };
	const struct grid_arrays::gridfn_pars ghosted_gridfn_pars
		= {
		  ghosted_min_gfn_in, ghosted_max_gfn_in,
		  & ghosted_gridfn_storage_[ghosted_posn],
		  ghosted_gfn_stride, 0, 0
		  };

	patch& p = ith_patch(pn);
	p.setup_gridfn_storage(gridfn_pars, ghosted_gridfn_pars);
	}
	  }

if (print_msg_flag)
   then {
	CCTK_VInfo(CCTK_THORNSTRING,
		   "         checking that storage is partitioned properly");
	}

// check to make sure storage for distinct gridfns
// forms a partition of the overall storage array
const patch& pfirst = ith_patch(0);
const patch& plast  = ith_patch(N_patches()-1);
	  {
	for (int gfn = min_gfn() ; gfn+1 < max_gfn() ; ++gfn)
	{
	// range of storage occupied by gridfns:
	//	gfn   --> [gfn_first, gfn_last]
	//	gfn+1 --> [gfn1_first, gfn1_last]
	const fp* const gfn_last_ptr
		= & plast.gridfn(gfn, plast.max_irho(),
				      plast.max_isigma());
	const fp* const gfn1_first_ptr
		= & pfirst.gridfn(gfn+1, pfirst.min_irho(),
					 pfirst.min_isigma());
	if (! (gfn1_first_ptr == gfn_last_ptr+max_N_additional_points()+1))
	   then error_exit(PANIC_EXIT,
"***** patch_system::setup_gridfn_storage():\n"
"        nominal-grid gridfns don't partition overall storage array!"
"        (this should never happen!)\n"
"        gfn=%d last point at gfn_last_ptr=%p\n"
"	 gfn+1=%d first point at gfn1_first_ptr=%p\n"
"        should have gfn1_first_ptr == gfn_last_ptr+1\n"
,
			   gfn, (const void *) gfn_last_ptr,
			   gfn+1, (const void *) gfn1_first_ptr); /*NOTREACHED*/
	}
	  }

	  {
	for (int ghosted_gfn = ghosted_min_gfn() ;
	     ghosted_gfn+1 < ghosted_max_gfn() ;
	     ++ghosted_gfn)
	{
	// range of storage occupied by ghosted gridfns:
	//	ghosted_gfn   --> [gfn_first, gfn_last]
	//	ghosted_gfn+1 --> [gfn1_first, gfn1_last]
	const fp* const ghosted_gfn_last_ptr
		= & plast.ghosted_gridfn(ghosted_gfn,
					 plast.ghosted_max_irho(),
					 plast.ghosted_max_isigma());
	const fp* const ghosted_gfn1_first_ptr
		= & pfirst.ghosted_gridfn(ghosted_gfn+1,
					  pfirst.ghosted_min_irho(),
					  pfirst.ghosted_min_isigma());
	if (! (ghosted_gfn1_first_ptr == ghosted_gfn_last_ptr+max_N_additional_points()+1))
	   then error_exit(PANIC_EXIT,
"***** patch_system::setup_gridfn_storage():\n"
"        ghosted-grid gridfns don't partition overall storage array!"
"        (this should never happen!)\n"
"        ghosted_gfn=%d last point at ghosted_gfn_last_ptr=%p\n"
"	 ghosted_gfn+1=%d first point at ghosted_gfn1_first_ptr=%p\n"
"        should have ghosted_gfn1_first_ptr == ghosted_gfn_last_ptr+1\n"
,
			   ghosted_gfn, (const void *) ghosted_gfn_last_ptr,
			   ghosted_gfn+1,
			      (const void *) ghosted_gfn1_first_ptr);
								/*NOTREACHED*/
	}
	  }

// check to make sure storage for distinct patches
// forms a partition of the storage for each gridfn
	  {
	for (int gfn = min_gfn() ; gfn < max_gfn() ; ++gfn)
	{
		for (int pn = 0 ; pn+1 < N_patches() ; ++pn)
		{
		const patch& p = ith_patch(pn);
		const patch& p1 = ith_patch(pn+1);

		// range of storage occupied by gridfn:
		//	p  --> [p_first, p_last]
		//	p1 --> [p1_first, p1_last]
		const fp* const p_last_ptr
			= & p.gridfn(gfn, p.max_irho(), p.max_isigma());
		const fp* const p1_first_ptr
			= & p1.gridfn(gfn, p1.min_irho(), p1.min_isigma());
		if (! (p1_first_ptr == p_last_ptr+1))
		   then error_exit(PANIC_EXIT,
"***** patch_system::setup_gridfn_storage():\n"
"        nominal-grid patches gridfns don't partition storage for gfn=%d!\n"
"        (this should never happen!)\n"
"        gfn=%d %s patch last point at p_last_ptr=%p\n"
"	 gfn=%d %s patch first point at p1_first_ptr=%p\n"
"        should have p1_first_ptr == p_last_ptr+1\n"
,
			   gfn,
			   gfn, p.name(), (const void *) p_last_ptr,
			   gfn+1, p1.name(), (const void *) p1_first_ptr);
								 /*NOTREACHED*/
		}
	}
	  }

	  {
	for (int ghosted_gfn = ghosted_min_gfn() ;
	     ghosted_gfn < ghosted_max_gfn() ;
	     ++ghosted_gfn)
	{
		for (int pn = 0 ; pn+1 < N_patches() ; ++pn)
		{
		const patch& p = ith_patch(pn);
		const patch& p1 = ith_patch(pn+1);

		// range of storage occupied by ghosted gridfn:
		//	p  --> [p_first, p_last]
		//	p1 --> [p1_first, p1_last]
		const fp* const p_last_ptr
			= & p.ghosted_gridfn(ghosted_gfn,
					     p.ghosted_max_irho(),
					     p.ghosted_max_isigma());
		const fp* const p1_first_ptr
			= & p1.ghosted_gridfn(ghosted_gfn,
					      p1.ghosted_min_irho(),
					      p1.ghosted_min_isigma());
		if (! (p1_first_ptr == p_last_ptr+1))
		   then error_exit(PANIC_EXIT,
"***** patch_system::setup_gridfn_storage():\n"
"        ghosted-grid patches gridfns don't partition storage for gfn=%d!\n"
"        (this should never happen!)\n"
"        %s patch (pn=%d) last point at p_last_ptr=%p\n"
"	 %s patch (pn=%d) first point at p1_first_ptr=%p\n"
"        should have p1_first_ptr == p_last_ptr+1\n"
,
			   ghosted_gfn,
			   p.name(), pn, (const void *) p_last_ptr,
			   p1.name(), pn+1, (const void *) p1_first_ptr);
								 /*NOTREACHED*/
		}
	}
	  }
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a full-sphere patch system.
//
void patch_system::setup_ghost_zones__full_sphere
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      seting up full sphere ghost zones");

patch& pz = ith_patch(patch_system_info::full_sphere::patch_number__pz);
patch& px = ith_patch(patch_system_info::full_sphere::patch_number__px);
patch& py = ith_patch(patch_system_info::full_sphere::patch_number__py);
patch& mx = ith_patch(patch_system_info::full_sphere::patch_number__mx);
patch& my = ith_patch(patch_system_info::full_sphere::patch_number__my);
patch& mz = ith_patch(patch_system_info::full_sphere::patch_number__mz);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(pz, mx, patch_overlap_width);
create_interpatch_ghost_zones(pz, my, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
create_interpatch_ghost_zones(py, mx, patch_overlap_width);
create_interpatch_ghost_zones(mx, my, patch_overlap_width);
create_interpatch_ghost_zones(my, px, patch_overlap_width);
create_interpatch_ghost_zones(mz, px, patch_overlap_width);
create_interpatch_ghost_zones(mz, py, patch_overlap_width);
create_interpatch_ghost_zones(mz, mx, patch_overlap_width);
create_interpatch_ghost_zones(mz, my, patch_overlap_width);

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, mx,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(py, mx,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mx, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(my, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, mx,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +z hemisphere patch system.
//
void patch_system::setup_ghost_zones__plus_z_hemisphere
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +z hemisphere ghost zones");

patch& pz = ith_patch(patch_system_info::plus_z_hemisphere::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_z_hemisphere::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_z_hemisphere::patch_number__py);
patch& mx = ith_patch(patch_system_info::plus_z_hemisphere::patch_number__mx);
patch& my = ith_patch(patch_system_info::plus_z_hemisphere::patch_number__my);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(pz, mx, patch_overlap_width);
create_interpatch_ghost_zones(pz, my, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
create_interpatch_ghost_zones(py, mx, patch_overlap_width);
create_interpatch_ghost_zones(mx, my, patch_overlap_width);
create_interpatch_ghost_zones(my, px, patch_overlap_width);
px.create_mirror_symmetry_ghost_zone(px.max_rho_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_rho_patch_edge());
mx.create_mirror_symmetry_ghost_zone(mx.min_rho_patch_edge());
my.create_mirror_symmetry_ghost_zone(my.min_rho_patch_edge());

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, mx,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(py, mx,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mx, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(my, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +xy quadrant (mirrored) patch system.
//
void patch_system::setup_ghost_zones__plus_xy_quadrant_mirrored
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +xy quadrant (mirrored) ghost zones");

patch& pz = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__py);
patch& mz = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__mz);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
create_interpatch_ghost_zones(mz, px, patch_overlap_width);
create_interpatch_ghost_zones(mz, py, patch_overlap_width);
pz.create_mirror_symmetry_ghost_zone(pz.min_rho_patch_edge());
pz.create_mirror_symmetry_ghost_zone(pz.min_sigma_patch_edge());
px.create_mirror_symmetry_ghost_zone(px.min_sigma_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_sigma_patch_edge());
mz.create_mirror_symmetry_ghost_zone(mz.max_rho_patch_edge());
mz.create_mirror_symmetry_ghost_zone(mz.max_sigma_patch_edge());

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +xy quadrant (rotating) patch system.
//
void patch_system::setup_ghost_zones__plus_xy_quadrant_rotating
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +xy quadrant (rotating) ghost zones");

patch& pz = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__py);
patch& mz = ith_patch(patch_system_info::plus_xy_quadrant::patch_number__mz);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
create_interpatch_ghost_zones(mz, px, patch_overlap_width);
create_interpatch_ghost_zones(mz, py, patch_overlap_width);
create_periodic_symmetry_ghost_zones(pz.min_rho_patch_edge(),
				     pz.min_sigma_patch_edge(),
				     true);
create_periodic_symmetry_ghost_zones(px.min_sigma_patch_edge(),
				     py.max_sigma_patch_edge(),
				     true);
create_periodic_symmetry_ghost_zones(mz.max_rho_patch_edge(),
				     mz.max_sigma_patch_edge(),
				     true);

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(mz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +xz quadrant (mirrored) patch system.
//
void patch_system::setup_ghost_zones__plus_xz_quadrant_mirrored
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +xz quadrant (mirrored) ghost zones");

patch& pz = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__py);
patch& my = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__my);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(pz, my, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
create_interpatch_ghost_zones(px, my, patch_overlap_width);
pz.create_mirror_symmetry_ghost_zone(pz.min_sigma_patch_edge());
px.create_mirror_symmetry_ghost_zone(px.max_rho_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_rho_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_sigma_patch_edge());
my.create_mirror_symmetry_ghost_zone(my.min_rho_patch_edge());
my.create_mirror_symmetry_ghost_zone(my.min_sigma_patch_edge());

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +xz quadrant (rotating) patch system.
//
void patch_system::setup_ghost_zones__plus_xz_quadrant_rotating
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +xz quadrant (rotating) ghost zones");

patch& pz = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__py);
patch& my = ith_patch(patch_system_info::plus_xz_quadrant::patch_number__my);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(pz, my, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
create_interpatch_ghost_zones(px, my, patch_overlap_width);
px.create_mirror_symmetry_ghost_zone(px.max_rho_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_rho_patch_edge());
my.create_mirror_symmetry_ghost_zone(my.min_rho_patch_edge());
create_periodic_symmetry_ghost_zones(pz.min_sigma_patch_edge(),
				     pz.min_sigma_patch_edge(),
				     false);
create_periodic_symmetry_ghost_zones(py.max_sigma_patch_edge(),
				     my.min_sigma_patch_edge(),
				     false);

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, my,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +xyz octant (mirrored) patch system.
//
void patch_system::setup_ghost_zones__plus_xyz_octant_mirrored
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +xyz octant (mirrored) ghost zones");

patch& pz = ith_patch(patch_system_info::plus_xyz_octant::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_xyz_octant::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_xyz_octant::patch_number__py);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
pz.create_mirror_symmetry_ghost_zone(pz.min_rho_patch_edge());
pz.create_mirror_symmetry_ghost_zone(pz.min_sigma_patch_edge());
px.create_mirror_symmetry_ghost_zone(px.max_rho_patch_edge());
px.create_mirror_symmetry_ghost_zone(px.min_sigma_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_rho_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_sigma_patch_edge());

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function sets up (constructs and interlinks) the ghost zones
// for a +xyz octant (rotating) patch system.
//
void patch_system::setup_ghost_zones__plus_xyz_octant_rotating
	(int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle,
	 bool print_msg_flag)
{
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "      setting up +xyz octant (rotating) ghost zones");

patch& pz = ith_patch(patch_system_info::plus_xyz_octant::patch_number__pz);
patch& px = ith_patch(patch_system_info::plus_xyz_octant::patch_number__px);
patch& py = ith_patch(patch_system_info::plus_xyz_octant::patch_number__py);

// create the ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         creating ghost zones");
create_interpatch_ghost_zones(pz, px, patch_overlap_width);
create_interpatch_ghost_zones(pz, py, patch_overlap_width);
create_interpatch_ghost_zones(px, py, patch_overlap_width);
px.create_mirror_symmetry_ghost_zone(px.max_rho_patch_edge());
py.create_mirror_symmetry_ghost_zone(py.max_rho_patch_edge());
create_periodic_symmetry_ghost_zones(pz.min_rho_patch_edge(),
				     pz.min_sigma_patch_edge(),
				     true);
create_periodic_symmetry_ghost_zones(px.min_sigma_patch_edge(),
				     py.max_sigma_patch_edge(),
				     true);

// finish setting up the interpatch ghost zones
if (print_msg_flag)
   then CCTK_VInfo(CCTK_THORNSTRING,
		   "         finishing interpatch setup");
finish_interpatch_setup(pz, px,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(pz, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);
finish_interpatch_setup(px, py,
			patch_overlap_width,
			ip_interp_handle, ip_interp_par_table_handle);

assert_all_ghost_zones_fully_setup();
}

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

//
// This function creates a pair of periodic-symmetry ghost zones.
//
//static
  void patch_system::create_periodic_symmetry_ghost_zones
	(const patch_edge& ex, const patch_edge& ey,
	 bool ipar_map_is_plus)
{
ex.my_patch()
  .create_periodic_symmetry_ghost_zone(ex, ey, ipar_map_is_plus);

if (ex == ey)
   then {
	// ex and ey are the same edge (i.e. the symmetry maps the edge
	// back to itself), so we only want to set up the edge once
	// ==> no-op here
	}
   else ey.my_patch()
	  .create_periodic_symmetry_ghost_zone(ey, ex, ipar_map_is_plus);
}

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

//
// This function automagically figures out which edges of two adjacent
// patches are adjacent, then creates both patches' ghost zones on those
// edges and interlinks them with their respective patches.
//
//static
  void patch_system::create_interpatch_ghost_zones(patch &px, patch &py,
						   int patch_overlap_width)
{
const patch_edge& ex = px.edge_adjacent_to_patch(py, patch_overlap_width);
const patch_edge& ey = py.edge_adjacent_to_patch(px, patch_overlap_width);

px.create_interpatch_ghost_zone(ex, ey, patch_overlap_width);
py.create_interpatch_ghost_zone(ey, ex, patch_overlap_width);
}

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

//
// This function automagically figures out which edges of two adjacent
// patches are adjacent, then finishes setting up both ghost zones.
//
//static
  void patch_system::finish_interpatch_setup
	(patch &px, patch &py,
	 int patch_overlap_width,
	 int ip_interp_handle, int ip_interp_par_table_handle)
{
const patch_edge& ex = px.edge_adjacent_to_patch(py, patch_overlap_width);
const patch_edge& ey = py.edge_adjacent_to_patch(px, patch_overlap_width);
px.ghost_zone_on_edge(ex)
  .cast_to_interpatch_ghost_zone()
  .finish_setup(ip_interp_handle, ip_interp_par_table_handle);
py.ghost_zone_on_edge(ey)
  .cast_to_interpatch_ghost_zone()
  .finish_setup(ip_interp_handle, ip_interp_par_table_handle);
}

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

//
// This function assert()s that all ghost zones of all patches have
// been fully set up.
//
void patch_system::assert_all_ghost_zones_fully_setup() const
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	ith_patch(pn).assert_all_ghost_zones_fully_setup();
	}
}

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

//
// This function decodes a patch system's type into N_patches.
//
//static
  int patch_system::N_patches_of_type(enum patch_system_type type_in)
{
switch	(type_in)
	{
case patch_system__full_sphere:
	return patch_system_info::full_sphere::N_patches;
case patch_system__plus_z_hemisphere:
	return patch_system_info::plus_z_hemisphere::N_patches;
case patch_system__plus_xy_quadrant_mirrored:
case patch_system__plus_xy_quadrant_rotating:
	return patch_system_info::plus_xy_quadrant::N_patches;
case patch_system__plus_xz_quadrant_mirrored:
case patch_system__plus_xz_quadrant_rotating:
	return patch_system_info::plus_xz_quadrant::N_patches;
case patch_system__plus_xyz_octant_mirrored:
case patch_system__plus_xyz_octant_rotating:
	return patch_system_info::plus_xyz_octant::N_patches;
default:
	error_exit(PANIC_EXIT,
"***** patch_system::N_patches_of_type(): bad type=(int)%d!\n"
"                                         (this should never happen!)\n",
		   int(type_in));				/*NOTREACHED*/
	}
}

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

//
// This function decodes a patch system's type into a human-readable
// type name (a C string).
//
//static
  const char* patch_system::name_of_type(enum patch_system_type type_in)
{
switch	(type_in)
	{
case patch_system__full_sphere:               return "full sphere";
case patch_system__plus_z_hemisphere:         return "+z hemisphere";
case patch_system__plus_xy_quadrant_mirrored: return "+xy quadrant (mirrored)";
case patch_system__plus_xy_quadrant_rotating: return "+xy quadrant (rotating)";
case patch_system__plus_xz_quadrant_mirrored: return "+xz quadrant (mirrored)";
case patch_system__plus_xz_quadrant_rotating: return "+xz quadrant (rotating)";
case patch_system__plus_xyz_octant_mirrored:  return "+xyz octant (mirrored)";
case patch_system__plus_xyz_octant_rotating:  return "+xyz octant (rotating)";
default:
	error_exit(PANIC_EXIT,
"***** patch_system::name_of_type(): bad type=(int)%d!\n"
"                                    (this should never happen!)\n",
		   int(type_in));				/*NOTREACHED*/
	}
}

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

//
// This function encodes a human-readable type name (a C string) into
// a patch system's type into.
//
//static
  enum patch_system::patch_system_type
    patch_system::type_of_name(const char* name_in)
{
if	(STRING_EQUAL(name_in, "full sphere"))
   then return patch_system__full_sphere;
else if (STRING_EQUAL(name_in, "+z hemisphere"))
   then return patch_system__plus_z_hemisphere;
else if (STRING_EQUAL(name_in, "+xy quadrant (mirrored)"))
   then return patch_system__plus_xy_quadrant_mirrored;
else if (STRING_EQUAL(name_in, "+xy quadrant (rotating)"))
   then return patch_system__plus_xy_quadrant_rotating;
else if (STRING_EQUAL(name_in, "+xz quadrant (mirrored)"))
   then return patch_system__plus_xz_quadrant_mirrored;
else if (STRING_EQUAL(name_in, "+xz quadrant (rotating)"))
   then return patch_system__plus_xz_quadrant_rotating;
else if (STRING_EQUAL(name_in, "+xyz octant (mirrored)"))
   then return patch_system__plus_xyz_octant_mirrored;
else if (STRING_EQUAL(name_in, "+xyz octant (rotating)"))
   then return patch_system__plus_xyz_octant_rotating;
else	error_exit(PANIC_EXIT,
"***** patch_system::type_of_name(): unknown name=\"%s\"!",
		   name_in);					/*NOTREACHED*/
}

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

//
// This function finds a (the) patch with a specified sign and xyz ctype.
// If no such patch exists, it does an  error_exit()  (and doesn't return
// to the caller).
//
// FIXME:
// - This function could be implemented to be very fast (using the
//   patch numbers in patch_system_info::), but right now it just does
//   a sequential search through all the patches, so it's pretty slow :(
//
const patch& patch_system::plus_or_minus_xyz_patch(bool is_plus, char ctype)
	const
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const patch& p = ith_patch(pn);
	if ((p.is_plus() == is_plus) && (p.ctype() == ctype))
	   then return p;
	}

error_exit(ERROR_EXIT,
"***** patch_system::plus_or_minus_xyz_patch():\n"
"        can't find any %c%c patch!"
,
	   (is_plus ? '+' : '-'), ctype);			/*NOTREACHED*/
}

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

//
// This function finds a patch from its human-readable name, and returns
// the patch number, or does an error_exit() if no patch is found with
// the specified name.
//
int patch_system::patch_number_of_name(const char* name) const
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	if (STRING_EQUAL(ith_patch(pn).name(), name))
	   then return pn;
	}

error_exit(ERROR_EXIT,
"***** patch_system::patch_number_of_name():\n"
"        no patch with name \"%s\"!\n",
	   name);						/*NOTREACHED*/
}

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

//
// This function decodes a 0-origin grid point number into a
// (patch,irho,isigma) triple.
//
// Arguments:
// gpn = The grid point number to decode.
// (irho,isigma) = (out) The decoded patch coordinates.
//
// Results:
// This function returns a reference to the decoded patch.  (An alternative
// design would be to return this via a  patch*&  argument, but the design
// used here seems slightly cleaner to use in practice.)
//
const patch&
  patch_system::patch_irho_isigma_of_gpn(int gpn, int& irho, int& isigma)
	const
{
assert( gpn >= 0 );
assert( gpn < N_grid_points() );

	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	// n.b. [pn+1] is ok since starting_gpn_[] has size N_patches()+1
	if ((gpn >= starting_gpn_[pn])  && (gpn < starting_gpn_[pn+1]))
	   then {
		const patch& p = ith_patch(pn);
		const int gpn_in_patch = gpn - starting_gpn_[pn];
		assert( gpn_in_patch >= 0 );
		assert( gpn_in_patch < p.N_grid_points() );
		p.irho_isigma_of_gpn(gpn_in_patch, irho,isigma);
		return p;
		}
	}

error_exit(PANIC_EXIT,
"***** patch_system::patch_irho_isigma_of_gpn(gpn=%d):\n"
"        couldn't find any patch! (this should never happen!)\n"
"        N_grid_points()=%d\n"
,
	   gpn,
	   N_grid_points());					/*NOTREACHED*/
}

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

//
// This function decodes a 0-origin grid point number into a *ghosted*
// (patch,irho,isigma) triple.
//
// Arguments:
// gpn = The grid point number to decode.
// (irho,isigma) = (out) The decoded patch coordinates.
//
// Results:
// This function returns a reference to the decoded patch.  (An alternative
// design would be to return this via a  patch*&  argument, but the design
// used here seems slightly cleaner to use in practice.)
//
const patch&
  patch_system::ghosted_patch_irho_isigma_of_gpn
	(int gpn, int& irho, int& isigma)
	const
{
assert( gpn >= 0 );
assert( gpn < ghosted_N_grid_points() );

	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	// n.b. [pn+1] is ok since ghosted_starting_gpn_[]
	//      has size N_patches()+1
	if (    (gpn >= ghosted_starting_gpn_[pn])
	     && (gpn <  ghosted_starting_gpn_[pn+1]))
	   then {
		const patch& p = ith_patch(pn);
		const int gpn_in_patch = gpn - ghosted_starting_gpn_[pn];
		assert( gpn_in_patch >= 0 );
		assert( gpn_in_patch < p.ghosted_N_grid_points() );
		p.ghosted_irho_isigma_of_gpn(gpn_in_patch, irho,isigma);
		return p;
		}
	}

error_exit(PANIC_EXIT,
"***** patch_system::ghosted_patch_irho_isigma_of_gpn(gpn=%d):\n"
"        couldn't find any patch! (this should never happen!)\n"
"        ghosted_N_grid_points()=%d\n"
,
	   gpn,
	   ghosted_N_grid_points());				/*NOTREACHED*/
}

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

//
// This function sets a (nominal-grid) gridfn to a constant value.
//
void patch_system::set_gridfn_to_constant(fp a, int dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.min_irho() ; irho <= p.max_irho() ; ++irho)
		{
		for (int isigma = p.min_isigma() ;
		     isigma <= p.max_isigma() ;
		     ++isigma)
		{
		p.gridfn(dst_gfn, irho,isigma) = a;
		}
		}
	}
}

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

//
// This function sets a ghosted gridfn to a constant value.
//
void patch_system::set_ghosted_gridfn_to_constant(fp a, int ghosted_dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.ghosted_min_irho() ;
                     irho <= p.ghosted_max_irho() ; ++irho)
		{
		for (int isigma = p.ghosted_min_isigma() ;
		     isigma <= p.ghosted_max_isigma() ;
		     ++isigma)
		{
		p.ghosted_gridfn(ghosted_dst_gfn, irho,isigma) = a;
		}
		}
	}
}

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

//
// This function copies one (nominal-grid) gridfn to another.
//
void patch_system::gridfn_copy(int src_gfn, int dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.min_irho() ; irho <= p.max_irho() ; ++irho)
		{
		for (int isigma = p.min_isigma() ;
		     isigma <= p.max_isigma() ;
		     ++isigma)
		{
		p.gridfn(dst_gfn, irho,isigma) = p.gridfn(src_gfn, irho,isigma);
		}
		}
	}
}

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

//
// This function copies one ghosted gridfn to another.
//
void patch_system::ghosted_gridfn_copy(int ghosted_src_gfn,
                                       int ghosted_dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.ghosted_min_irho() ;
                     irho <= p.ghosted_max_irho() ; ++irho)
		{
		for (int isigma = p.ghosted_min_isigma() ;
		     isigma <= p.ghosted_max_isigma() ;
		     ++isigma)
		{
		p.ghosted_gridfn(ghosted_dst_gfn, irho,isigma)
                  = p.ghosted_gridfn(ghosted_src_gfn, irho,isigma);
		}
		}
	}
}

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

//
// This function copies a ghosted gridfn to a nominal gridfn.
//
void patch_system::ghosted_gridfn_copy_to_nominal(int ghosted_src_gfn,
                                                  int         dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.min_irho() ; irho <= p.max_irho() ; ++irho)
		{
		for (int isigma = p.min_isigma() ; isigma <= p.max_isigma() ;
		     ++isigma)
		{
		p.gridfn(dst_gfn, irho,isigma)
                  = p.ghosted_gridfn(ghosted_src_gfn, irho,isigma);
		}
		}
	}
}

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

//
// This function adds a scalar to a (nominal-grid) gridfn.
//
void patch_system::add_to_gridfn(fp delta, int dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.min_irho() ;
		     irho <= p.max_irho() ;
		     ++irho)
		{
		for (int isigma = p.min_isigma() ;
		     isigma <= p.max_isigma() ;
		     ++isigma)
		{
		p.gridfn(dst_gfn, irho,isigma) += delta;
		}
		}
	}
}

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

//
// This function adds a scalar to a ghosted gridfn.
//
void patch_system::add_to_ghosted_gridfn(fp delta, int ghosted_dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.ghosted_min_irho() ;
		     irho <= p.ghosted_max_irho() ;
		     ++irho)
		{
		for (int isigma = p.ghosted_min_isigma() ;
		     isigma <= p.ghosted_max_isigma() ;
		     ++isigma)
		{
		p.ghosted_gridfn(ghosted_dst_gfn, irho,isigma) += delta;
		}
		}
	}
}

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

//
// This function scales a ghosted gridfn with a scalar.
//
void patch_system::scale_ghosted_gridfn(fp factor, int ghosted_dst_gfn)
{
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		for (int irho = p.ghosted_min_irho() ;
		     irho <= p.ghosted_max_irho() ;
		     ++irho)
		{
		for (int isigma = p.ghosted_min_isigma() ;
		     isigma <= p.ghosted_max_isigma() ;
		     ++isigma)
		{
		p.ghosted_gridfn(ghosted_dst_gfn, irho,isigma) *= factor;
		}
		}
	}
}

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

//
// This function computes norms of a nominal-grid gridfn.
//
void patch_system::gridfn_norms(int src_gfn, jtutil::norm<fp>& norms)
	const
{
if (! is_valid_gfn(src_gfn))
   then error_exit(ERROR_EXIT,
"***** patch_system::gridfn_norms(): invalid src_gfn=%d!\n",
		   src_gfn);					/*NOTREACHED*/

norms.reset();

	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const patch& p = ith_patch(pn);
		for (int irho = p.min_irho() ; irho <= p.max_irho() ; ++irho)
		{
		for (int isigma = p.min_isigma() ;
		     isigma <= p.max_isigma() ;
		     ++isigma)
		{
		norms.data(p.gridfn(src_gfn, irho,isigma));
		}
		}
	}
}

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

//
// This function computes norms of a ghosted-grid gridfn over the
// nominal grid.
//
void patch_system::ghosted_gridfn_norms(int ghosted_src_gfn,
					jtutil::norm<fp>& norms)
	const
{
if (! is_valid_ghosted_gfn(ghosted_src_gfn))
   then error_exit(ERROR_EXIT,
"***** patch_system::gridfn_norms(): invalid ghosted_src_gfn=%d!\n",
		   ghosted_src_gfn);				/*NOTREACHED*/
norms.reset();

	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const patch& p = ith_patch(pn);
		for (int irho = p.min_irho() ; irho <= p.max_irho() ; ++irho)
		{
		for (int isigma = p.min_isigma() ;
		     isigma <= p.max_isigma() ;
		     ++isigma)
		{
		norms.data(p.ghosted_gridfn(ghosted_src_gfn, irho,isigma));
		}
		}
	}
}

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

//
// This function computes an approximation to the circumference of a
// surface in the xy, xz, or yz plane.  Note that we compute the full
// circumference all around the sphere, even if the patch system only
// covers a proper subset of this.
//
// We assume that adjacent patches are butt-joined, i.e. that their
// nominal boundaries just touch.
//
// Arguments:
// plane[] = (in) "xy", "xz", or "yz" to specify the integration plane.
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp patch_system::circumference(const char plane[],
			       int ghosted_radius_gfn,
			       int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
					     int g_yy_gfn, int g_yz_gfn,
							   int g_zz_gfn,
			       enum patch::integration_method method)
	const
{
//
// compute arc length around the patch system
//
fp arc_length = 0.0;
    for (int pn = 0 ; pn < N_patches() ; ++pn)
    {
    const patch& p = ith_patch(pn);
    if ((p.ctype() == plane[0]) || (p.ctype() == plane[1]))
       then arc_length += p.plane_arc_length(plane,
					     ghosted_radius_gfn,
					     g_xx_gfn, g_xy_gfn, g_xz_gfn,
						       g_yy_gfn, g_yz_gfn,
								 g_zz_gfn,
					     method);
    }

//
// correct the arc length
// for the fact that the patch system may not cover the full 2-sphere
//
switch	(type())
	{
case patch_system__full_sphere:
	break;
case patch_system__plus_z_hemisphere:
	arc_length *= ((plane[0] == 'x') && (plane[1] == 'y')) ? 1.0 : 2.0;
	break;
case patch_system__plus_xy_quadrant_mirrored:
case patch_system__plus_xy_quadrant_rotating:
	arc_length *= ((plane[0] == 'x') && (plane[1] == 'y')) ? 4.0 : 2.0;
	break;
case patch_system__plus_xz_quadrant_mirrored:
case patch_system__plus_xz_quadrant_rotating:
	arc_length *= ((plane[0] == 'x') && (plane[1] == 'z')) ? 4.0 : 2.0;
	break;
case patch_system__plus_xyz_octant_mirrored:
case patch_system__plus_xyz_octant_rotating:
	arc_length *= 4.0;
	break;
default:
	error_exit(PANIC_EXIT,
"***** patch_system::circumference(): unknown patch system type()=(int)%d!\n"
"                                     (this should never happen!)\n",
		   int(type()));				/*NOTREACHED*/
	}

return arc_length;
}

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

//
// This function computes an approximation to the (surface) integral of
// a gridfn over the 2-sphere
//	$\int f(\rho,\sigma) \, dA$
//		= \int f(\rho,\sigma) \sqrt{|J|} \, d\rho \, d\sigma$
// where $J$ is the Jacobian of $(x,y,z)$ with respect to $(rho,sigma).
//
// We assume that adjacent patches are butt-joined, i.e. that their
// nominal boundaries just touch.
//
// Arguments:
// unknown_src_gfn = (in) The gridfn to be integrated.  This may be
//			  either nominal-grid or ghosted-grid.
// src_gfn_is_even_across_{xy,xz,yz}_plane
//	= (in) Boolean flags specifying whether the gridfn to be integrated
//	       is even (true) or odd (false) across the corresponding planes.
//	       Only the flags corresponding to boundaries of the patch system
//	       are used.  For example, for a  plus_z_hemisphere  patch system,
//	       only the  src_gfn_is_even_across_xy_plane  flag is used.
// ghosted_radius_gfn = (in) The surface radius.
// g_{xx,xy,xz,yy,yz,zz}_gfn = (in) The xyz 3-metric components.
// method = (in) Selects the integration scheme.
//
fp patch_system::integrate_gridfn(int unknown_src_gfn,
				  bool src_gfn_is_even_across_xy_plane,
				  bool src_gfn_is_even_across_xz_plane,
				  bool src_gfn_is_even_across_yz_plane,
				  int ghosted_radius_gfn,
				  int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
						int g_yy_gfn, int g_yz_gfn,
							      int g_zz_gfn,
				  enum patch::integration_method method)
	const
{
//
// compute integral over patch system
//
fp integral = 0.0;
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const patch& p = ith_patch(pn);
	integral += p.integrate_gridfn(unknown_src_gfn,
				       ghosted_radius_gfn,
				       g_xx_gfn, g_xy_gfn, g_xz_gfn,
						 g_yy_gfn, g_yz_gfn,
							   g_zz_gfn,
				       method);
	}

//
// correct the integral
// for the fact that the patch system may not cover the full 2-sphere
//
switch	(type())
	{
case patch_system__full_sphere:
	break;
case patch_system__plus_z_hemisphere:
	integral *= src_gfn_is_even_across_xy_plane ? 2.0 : 0.0;
	break;
case patch_system__plus_xy_quadrant_mirrored:
case patch_system__plus_xy_quadrant_rotating:
	integral *= src_gfn_is_even_across_xz_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_yz_plane ? 2.0 : 0.0;
	break;
case patch_system__plus_xz_quadrant_mirrored:
case patch_system__plus_xz_quadrant_rotating:
	integral *= src_gfn_is_even_across_xy_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_yz_plane ? 2.0 : 0.0;
	break;
case patch_system__plus_xyz_octant_mirrored:
case patch_system__plus_xyz_octant_rotating:
	integral *= src_gfn_is_even_across_xy_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_xz_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_yz_plane ? 2.0 : 0.0;
	break;
default:
	error_exit(PANIC_EXIT,
"***** patch_system::integrate_gridfn(): bad patch system type()=(int)%d!\n"
"                                        (this should never happen!)\n",
		   int(type()));				/*NOTREACHED*/
	}

return integral;
}

fp patch_system::integrate_gridpoint(int unknown_src_gfn,
                                     int ghosted_radius_gfn,
                                     int g_xx_gfn, int g_xy_gfn, int g_xz_gfn,
                                                   int g_yy_gfn, int g_yz_gfn,
                                                                 int g_zz_gfn,
                                     enum patch::integration_method method,
                                     int pn, int irho, int isigma)
	const
{
const patch& p = ith_patch(pn);
const fp val = p.integrate_gridpoint(unknown_src_gfn,
                                     ghosted_radius_gfn,
                                     g_xx_gfn, g_xy_gfn, g_xz_gfn,
                                               g_yy_gfn, g_yz_gfn,
                                                         g_zz_gfn,
                                     method,
                                     irho, isigma);

return val;
}
fp patch_system::integrate_correction(bool src_gfn_is_even_across_xy_plane,
                                      bool src_gfn_is_even_across_xz_plane,
                                      bool src_gfn_is_even_across_yz_plane)
	const
{
fp integral = 1.0;
//
// correct the integral
// for the fact that the patch system may not cover the full 2-sphere
//
switch	(type())
	{
case patch_system__full_sphere:
	break;
case patch_system__plus_z_hemisphere:
	integral *= src_gfn_is_even_across_xy_plane ? 2.0 : 0.0;
	break;
case patch_system__plus_xy_quadrant_mirrored:
case patch_system__plus_xy_quadrant_rotating:
	integral *= src_gfn_is_even_across_xz_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_yz_plane ? 2.0 : 0.0;
	break;
case patch_system__plus_xz_quadrant_mirrored:
case patch_system__plus_xz_quadrant_rotating:
	integral *= src_gfn_is_even_across_xy_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_yz_plane ? 2.0 : 0.0;
	break;
case patch_system__plus_xyz_octant_mirrored:
case patch_system__plus_xyz_octant_rotating:
	integral *= src_gfn_is_even_across_xy_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_xz_plane ? 2.0 : 0.0;
	integral *= src_gfn_is_even_across_yz_plane ? 2.0 : 0.0;
	break;
default:
	error_exit(PANIC_EXIT,
"***** patch_system::integrate_gridfn(): bad patch system type()=(int)%d!\n"
"                                        (this should never happen!)\n",
		   int(type()));				/*NOTREACHED*/
	}

return integral;
}

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

//
// This function finds what patch contains (the ray from the origin to)
// a given local (x,y,z) position.
//
// If there are multiple patches containing the position, we return the
// one which would still contain it if patches didn't overlap; if multiple
// patches satisfy this criterion then it's arbitrary which one we return.
//
// If no patch contains the position (this can only if the point as at
// the local coordinate origin, or for a non--full-sphere patch system),
// then we return a NULL pointer.
//
// Arguments:
// (x,y,z) = The local coordinates to be converted.
//
// Results:
// This function returns a reference to the containing patch.
//
const patch* patch_system::patch_containing_local_xyz(fp x, fp y, fp z)
	const
{
if ((x == 0.0) && (y == 0.0) && (z == 0.0))
   then return NULL;

// to which axis is (x,y,z) closest?
// ... or equivalently, which of |x|, |y|, and |z| is largest?
const fp abs_x = jtutil::abs(x);
const fp abs_y = jtutil::abs(y);
const fp abs_z = jtutil::abs(z);

if	((abs_z >= abs_x) && (abs_z >= abs_y))
   then return &plus_or_minus_xyz_patch(z > 0.0, 'z');	// +/- z patch
else if ((abs_x >= abs_y) && (abs_x >= abs_z))
   then return &plus_or_minus_xyz_patch(x > 0.0, 'x');	// +/- x patch
else if ((abs_y >= abs_x) && (abs_y >= abs_z))
   then return &plus_or_minus_xyz_patch(y > 0.0, 'y');	// +/- y patch
else	error_exit(ERROR_EXIT,
"***** patch_system::patch_containing_local_xyz():\n"
"        unknown (wierd!) ordering of |x|, |y|, and |z|!\n"
"        (this should never happen!)\n"
"        [local] (x,y,z)=(%g,%g,%g)\n"
,
		   double(x), double(y), double(z));		/*NOTREACHED*/
}

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

//
// This function computes the radius of a patch-system 2-surface in the
// direction of a specified local (x,y,z) point, taking into account any
// patch-system symmetries.  If the point coincides with the local origin,
// we return the dummy value 1.0.
//
// Due to the surface-interpolator overhead, repeatedly calling this
// function is rather inefficient.
//
fp patch_system::radius_in_local_xyz_direction(int ghosted_radius_gfn,
					       fp x, fp y, fp z)
	const
{
if ((x == 0.0) && (y == 0.0) && (z == 0.0))
   then return 1.0;

//
// apply symmetries to map (x,y,z) into that part of the 2-sphere
// which is covered by the patch system
//
switch	(type())
	{
case patch_system__full_sphere:
	break;
case patch_system__plus_z_hemisphere:
	z = fabs(z);
	break;
case patch_system__plus_xy_quadrant_mirrored:
case patch_system__plus_xy_quadrant_rotating:
	x = fabs(x);
	y = fabs(y);
	break;
case patch_system__plus_xz_quadrant_mirrored:
case patch_system__plus_xz_quadrant_rotating:
	x = fabs(x);
	z = fabs(z);
	break;
case patch_system__plus_xyz_octant_mirrored:
case patch_system__plus_xyz_octant_rotating:
	x = fabs(x);
	y = fabs(y);
	z = fabs(z);
	break;
default:
	error_exit(PANIC_EXIT,
"***** patch_system::radius_in_local_xyz_direction():\n"
"        unknown patch system type()=(int)%d!\n"
"        (this should never happen!)\n",
		   int(type()));				/*NOTREACHED*/
	}

const patch* p_ptr = patch_containing_local_xyz(x, y, z);
if (p_ptr == NULL)
   then error_exit(ERROR_EXIT,
"***** patch_system::radius_in_local_xyz_direction():\n"
"                    can't find containing patch!\n"
"                    (this should never happen!)\n"
"                    [local] (x,y,z)=(%g,%g,%g)\n"
		   ,
		   double(x), double(y), double(z));		/*NOTREACHED*/

const patch& p = *p_ptr;
const fp   rho = p.  rho_of_xyz(x,y,z);
const fp sigma = p.sigma_of_xyz(x,y,z);


//
// Set up the surface interpolator to interpolate the surface radius
// gridfn to the (rho,sigma) coordinates:
//
// Notes on the interpolator setup:
// * The interpolator assumes Fortran subscripting, so we take the
//   coordinates in the order (sigma,rho) to match our C subscripting
//   in the patch system.
// * To avoid having to set up min/max array subscripts in the parameter
//   table, we treat the patch as using 0-origin (integer) array subscripts
//   (irho - ghosted_min_irho(), isigma - ghosted_min_isigma()).  However,
//   we use the usual floating-point coordinates.
//

const int N_dims = 2;
const CCTK_REAL coord_origin[N_dims]
	= { p.ghosted_min_sigma(), p.ghosted_min_rho() };
const CCTK_REAL coord_delta[N_dims]
	= { p.delta_sigma(), p.delta_rho() };

const int N_interp_points = 1;
const int interp_coords_type_code = CCTK_VARIABLE_REAL;
const void* const interp_coords[N_dims]
	= { static_cast<const void*>(&sigma), static_cast<const void*>(&rho) };

const int N_input_arrays = 1;
const CCTK_INT input_array_dims[N_dims]
	= { p.ghosted_N_isigma(), p.ghosted_N_irho() };
const CCTK_INT input_array_type_codes[N_input_arrays] = { CCTK_VARIABLE_REAL };
const void* const input_arrays[N_input_arrays]
	= {
	  static_cast<const void*>(
	    p.ghosted_gridfn_data_array(ghosted_radius_gfn)
				  )
	  };

const int N_output_arrays = 1;
const CCTK_INT output_array_type_codes[N_output_arrays]
	= { CCTK_VARIABLE_REAL };
fp xyz_radius;
void* const output_arrays[N_output_arrays]
	= { static_cast<void*>(&xyz_radius) };

const int status
	= CCTK_InterpLocalUniform(N_dims,
				  surface_interp_handle_,
				  surface_interp_par_table_handle_,
				  coord_origin, coord_delta,
				  N_interp_points, interp_coords_type_code,
						   interp_coords,
				  N_input_arrays, input_array_dims,
						  input_array_type_codes,
						  input_arrays,
				  N_output_arrays, output_array_type_codes,
						   output_arrays);
if (status < 0)
   then error_exit(ERROR_EXIT,
"***** patch_system::radius_in_local_xyz_direction():\n"
"                    error return (status=%d) from surface interpolator!\n"
"                    [local] (x,y,z)=(%g,%g,%g)\n"
"                    %s patch (rho,sigma)=(%g,%g)\n"
"                             (drho,dsigma)=(%g,%g)\n"
		   ,
		   status,
		   double(x), double(y), double(z),
		   p.name(), double(rho), double(sigma),
			     double(p.drho_of_rho(rho)),
			     double(p.dsigma_of_sigma(sigma)));	/*NOTREACHED*/

return xyz_radius;
}

struct perpatch {
  std::vector<int> n;
  std::vector<fp> rho, sigma;
  std::vector<fp> radii;
  void reserve (int const npoints)
  {
    n.reserve (npoints);
    rho.reserve (npoints);
    sigma.reserve (npoints);
    radii.reserve (npoints);
  }
  void addpoint (int const n_, fp const rho_, fp const sigma_)
  {
    n    .push_back (n_);
    rho  .push_back (rho_);
    sigma.push_back (sigma_);
    radii.push_back (fp (0.0));
  }
};

void patch_system::radii_in_local_xyz_directions(int const ghosted_radius_gfn,
                                                 int const npoints,
                                                 fp const * const xp,
                                                 fp const * const yp,
                                                 fp const * const zp,
                                                 fp * const radii)
	const
{
  std::vector<perpatch> patchpoints (N_patches());
  for (int pn=0; pn<N_patches(); ++pn) {
    patchpoints.at(pn).reserve (npoints);
  }
  
  for (int n=0; n<npoints; ++n) {
    fp x = xp[n];
    fp y = yp[n];
    fp z = zp[n];
    
    if ((x == 0.0) && (y == 0.0) && (z == 0.0)) {
      radii[n] = 1.0;
    } else {
      //
      // apply symmetries to map (x,y,z) into that part of the
      // 2-sphere which is covered by the patch system
      //
      switch (type())
      {
      case patch_system__full_sphere:
	break;
      case patch_system__plus_z_hemisphere:
	z = fabs(z);
	break;
      case patch_system__plus_xy_quadrant_mirrored:
      case patch_system__plus_xy_quadrant_rotating:
	x = fabs(x);
	y = fabs(y);
	break;
      case patch_system__plus_xz_quadrant_mirrored:
      case patch_system__plus_xz_quadrant_rotating:
	x = fabs(x);
	z = fabs(z);
	break;
      case patch_system__plus_xyz_octant_mirrored:
      case patch_system__plus_xyz_octant_rotating:
	x = fabs(x);
	y = fabs(y);
	z = fabs(z);
	break;
      default:
	error_exit(PANIC_EXIT,
"***** patch_system::radii_in_local_xyz_directions():\n"
"        unknown patch system type()=(int)%d!\n"
"        (this should never happen!)\n",
		   int(type()));				/*NOTREACHED*/
      }
      
      const patch* p_ptr = patch_containing_local_xyz(x, y, z);
      if (p_ptr == NULL)
        then error_exit(ERROR_EXIT,
"***** patch_system::radius_in_local_xyz_direction():\n"
"                    can't find containing patch!\n"
"                    (this should never happen!)\n"
"                    [local] (x,y,z)=(%g,%g,%g)\n"
		   ,
		   double(x), double(y), double(z));		/*NOTREACHED*/
      
      const patch& p = *p_ptr;
      const fp   rho = p.  rho_of_xyz(x,y,z);
      const fp sigma = p.sigma_of_xyz(x,y,z);
      
      patchpoints.at(p.patch_number()).addpoint (n, rho, sigma);
    }
  } // for n
  
  for (int pn=0; pn<N_patches(); ++pn) {
    const patch& p = ith_patch(pn);
    perpatch& localpatchpoints = patchpoints.at(pn);
    
    // Set up the surface interpolator to interpolate the surface
    // radius gridfn to the (rho,sigma) coordinates:
    //
    // Notes on the interpolator setup:
    // * The interpolator assumes Fortran subscripting, so we take the
    //   coordinates in the order (sigma,rho) to match our C
    //   subscripting in the patch system.
    // * To avoid having to set up min/max array subscripts in the
    //   parameter table, we treat the patch as using 0-origin
    //   (integer) array subscripts (irho - ghosted_min_irho(), isigma
    //   - ghosted_min_isigma()).  However, we use the usual
    //   floating-point coordinates.
    //
    
    const int N_dims = 2;
    const CCTK_REAL coord_origin[N_dims]
      = { p.ghosted_min_sigma(), p.ghosted_min_rho() };
    const CCTK_REAL coord_delta[N_dims]
      = { p.delta_sigma(), p.delta_rho() };
    
    const int N_interp_points = localpatchpoints.radii.size();
    const int interp_coords_type_code = CCTK_VARIABLE_REAL;
    const void* const interp_coords[N_dims]
      = { static_cast<const void*>(& localpatchpoints.sigma.front()),
          static_cast<const void*>(& localpatchpoints.rho  .front()) };
    
    const int N_input_arrays = 1;
    const CCTK_INT input_array_dims[N_dims]
      = { p.ghosted_N_isigma(), p.ghosted_N_irho() };
    const CCTK_INT input_array_type_codes[N_input_arrays]
      = { CCTK_VARIABLE_REAL };
    const void* const input_arrays[N_input_arrays]
      = { static_cast<const void*>
          (p.ghosted_gridfn_data_array(ghosted_radius_gfn)) };
    
    const int N_output_arrays = 1;
    const CCTK_INT output_array_type_codes[N_output_arrays]
      = { CCTK_VARIABLE_REAL };
    void* const output_arrays[N_output_arrays]
      = { static_cast<void*>(& localpatchpoints.radii.front()) };
    
    const int status = CCTK_InterpLocalUniform
      (N_dims,
       surface_interp_handle_,
       surface_interp_par_table_handle_,
       coord_origin, coord_delta,
       N_interp_points,
       interp_coords_type_code, interp_coords,
       N_input_arrays,
       input_array_dims, input_array_type_codes, input_arrays,
       N_output_arrays,
       output_array_type_codes, output_arrays);
    if (status < 0)
      then error_exit(ERROR_EXIT,
"***** patch_system::radii_in_local_xyz_directions():\n"
"                    error return (status=%d) from surface interpolator!\n"
		   ,
                      status);	/*NOTREACHED*/
    
    for (size_t nn=0; nn<localpatchpoints.radii.size(); ++nn) {
      radii[localpatchpoints.n.at(nn)] = localpatchpoints.radii.at(nn);
    }
    
  } // for p
}

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

//
// This function prints an unknown-grid gridfn in ASCII format to a
// named output file.  The output format is suitable for a gnuplot
// 'splot' command.  (Individual patches may be selected with the
//  select.patch  program (perl script).)  The output format is either
//	# print_xyz_flag == false
//	dpx	dpy	gridfn
// or
//	# print_xyz_flag == true
//	dpx	dpy	gridfn   global_x   global_y   global_z
// where global_[xyz] are derived from the angular position and a
// specified (unknown-grid) radius gridfn.
//
void patch_system::print_unknown_gridfn
	(bool ghosted_flag, int unknown_gfn,
	 bool print_xyz_flag, bool radius_is_ghosted_flag,
			      int unknown_radius_gfn,
	 const char output_file_name[], bool want_ghost_zones)
	const
{
if (want_ghost_zones && !ghosted_flag)
   then error_exit(PANIC_EXIT,
"***** patch_system::print_unknown_gridfn(unknown_gfn=%d):\n"
"        can't have want_ghost_zones && !ghosted_flag !\n"
,
		   unknown_gfn);				/*NOTREACHED*/
if (want_ghost_zones && print_xyz_flag && !radius_is_ghosted_flag)
   then error_exit(PANIC_EXIT,
"***** patch_system::print_unknown_gridfn(unknown_gfn=%d):\n"
"        can't have want_ghost_zones && print_xyz_flag\n"
"        && !radius_is_ghosted_flag!\n"
"        unknown_radius_gfn=%d\n"
,
		   unknown_gfn,
		   unknown_radius_gfn);				/*NOTREACHED*/

FILE *output_fp = fopen(output_file_name, "w");
if (output_fp == NULL)
   then error_exit(ERROR_EXIT,
"***** patch_system::print_unknown_gridfn(unknown_gfn=%d):\n"
"        can't open output file \"%s\"\n!"
,
		   unknown_gfn,
		   output_file_name);				/*NOTREACHED*/

fprintf(output_fp, "# N_patches = %d\n", N_patches());
fprintf(output_fp, "# origin = %.15g %.15g %.15g\n",
        double(origin_x()), double(origin_y()), double(origin_z()));
fprintf(output_fp, "\n");

	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const patch& p = ith_patch(pn);

	fprintf(output_fp, "### %s patch\n", p.name());
	fprintf(output_fp, "# N_rho = %d\n",
		p.effective_N_irho(want_ghost_zones));
	fprintf(output_fp, "# N_sigma = %d\n",
		p.effective_N_isigma(want_ghost_zones));
	fprintf(output_fp, "# %s_gfn=%d\n",
		(ghosted_flag ? "ghosted" : "nominal"), unknown_gfn);
	fprintf(output_fp, "# dpx = %s\n", p.name_of_dpx());
	fprintf(output_fp, "# dpy = %s\n", p.name_of_dpy());
	fprintf(output_fp, "#\n");
	fprintf(output_fp,
		print_xyz_flag
		? "# dpx\tdpy\tgridfn\tglobal_x\tglobal_y\tglobal_z\n"
		: "# dpx\tdpy\tgridfn\n");

		for (int irho = p.effective_min_irho(want_ghost_zones) ;
		     irho <= p.effective_max_irho(want_ghost_zones) ;
		     ++irho)
		{
		for (int isigma = p.effective_min_isigma(want_ghost_zones) ;
		     isigma <= p.effective_max_isigma(want_ghost_zones) ;
		     ++isigma)
		{
		const fp rho = p.rho_of_irho(irho);
		const fp sigma = p.sigma_of_isigma(isigma);
		const fp dpx = p.dpx_of_rho_sigma(rho, sigma);
		const fp dpy = p.dpy_of_rho_sigma(rho, sigma);
		fprintf(output_fp,
			"%g\t%g\t%#.15g",
			double(dpx), double(dpy),
                        double(p.unknown_gridfn(ghosted_flag,
                                                unknown_gfn, irho,isigma)));
		if (print_xyz_flag)
		   then {
			const fp r = p.unknown_gridfn(radius_is_ghosted_flag,
						      unknown_radius_gfn,
						      irho,isigma);
			fp local_x, local_y, local_z;
			p.xyz_of_r_rho_sigma(r,rho,sigma,
					     local_x,local_y,local_z);
			const fp global_x = origin_x() + local_x;
			const fp global_y = origin_y() + local_y;
			const fp global_z = origin_z() + local_z;
			fprintf(output_fp,
				"\t%#.10g\t%#.10g\t%#.10g",
				double(global_x), double(global_y),
                                double(global_z));
			}
		fprintf(output_fp, "\n");
		}
		fprintf(output_fp, "\n");
		}
	fprintf(output_fp, "\n");
	}

fclose(output_fp);
}

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

//
// This function reads an unknown-grid gridfn in ASCII format from
// a named input file.  Comments ('#' in column 1) and blank lines
// are ignored, otherwise the input format matches that written by
// print_unknown_gridfn(): the first 3 numbers on each line are taken
// to be dpx, dpy, and the gridfn value; anything else on the line is
// ignored.
//
void patch_system::read_unknown_gridfn(bool ghosted_flag, int unknown_gfn,
				       const char input_file_name[],
				       bool want_ghost_zones)
{
if (want_ghost_zones && !ghosted_flag)
   then error_exit(PANIC_EXIT,
"***** patch_system::read_unknown_gridfn(unknown_gfn=%d):\n"
"        can't have want_ghost_zones && !ghosted_flag !\n"
,
		   unknown_gfn);				/*NOTREACHED*/

FILE *input_fp = fopen(input_file_name, "r");
if (input_fp == NULL)
   then error_exit(ERROR_EXIT,
"***** patch_system::read_unknown_gridfn(unknown_gfn=%d):\n"
"        can't open input file \"%s\"\n!"
,
		   unknown_gfn,
		   input_file_name);				/*NOTREACHED*/

int line_number = 1;
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);

	for (int irho = p.effective_min_irho(want_ghost_zones) ;
	     irho <= p.effective_max_irho(want_ghost_zones) ;
	     ++irho)
	{
	for (int isigma = p.effective_min_isigma(want_ghost_zones) ;
	     isigma <= p.effective_max_isigma(want_ghost_zones) ;
	     ++isigma)
	{
	const fp rho   = p.rho_of_irho(irho);
	const fp sigma = p.sigma_of_isigma(isigma);
	const fp dpx = p.dpx_of_rho_sigma(rho, sigma);
	const fp dpy = p.dpy_of_rho_sigma(rho, sigma);

	const int buffer_size = 250;
	char buffer[buffer_size];
	// read/discard comments and blank lines
		do
		{
		if (fgets(buffer, buffer_size, input_fp) == NULL)
		   then error_exit(ERROR_EXIT,
"***** patch::read_unknown_gridfn(%s patch, unknown_gfn=%d):\n"
"        I/O error or unexpected end-of-file on input!\n"
"        at irho=%d of [%d,%d], isigma=%d of [%d,%d]\n"
"        dpx=%g dpy=%g\n"
,
				   p.name(), unknown_gfn,
				   int(irho),
                                      p.effective_min_irho(want_ghost_zones),
				      p.effective_max_irho(want_ghost_zones),
				   int(isigma),
				      p.effective_min_isigma(want_ghost_zones),
				      p.effective_max_isigma(want_ghost_zones),
				   double(dpx), double(dpy));	/*NOTREACHED*/
		++line_number;
		} while ((buffer[0] == '#') || (buffer[0] == '\n'));

	double read_dpx, read_dpy, read_gridfn_value;
	if (sscanf(buffer, "%lf %lf %lf",
		   &read_dpx, &read_dpy, &read_gridfn_value) != 3)
	   then error_exit(ERROR_EXIT,
"***** patch::read_unknown_gridfn(%s patch, unknown_gfn=%d):\n"
"        bad input data at input line %d!\n"
,
			   p.name(), unknown_gfn,
			   line_number);			/*NOTREACHED*/
	if (! (    jtutil::fuzzy<fp>::EQ(read_dpx,dpx)
		&& jtutil::fuzzy<fp>::EQ(read_dpy,dpy)    ) )
	   then error_exit(ERROR_EXIT,
"***** patch::read_unknown_gridfn(%s patch, unknown_gfn=%d):\n"
"        wrong (dpx,dpy) at input line %d!\n"
"        expected (%g,%g)\n"
"        read     (%g,%g)\n"
,
			   p.name(), unknown_gfn,
			   line_number,
			   double(dpx), double(dpy),
			   double(read_dpx), double(read_dpy));	/*NOTREACHED*/

	p.unknown_gridfn(ghosted_flag,
			 unknown_gfn, irho,isigma) = read_gridfn_value;
	}
	}

	}

fclose(input_fp);
}

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

#ifdef HAVE_CAPABILITY_HDF5

static void WriteAttribute (const hid_t dataset, const char* name, char value);
static void WriteAttribute (const hid_t dataset, const char* name, const char* values);
static void WriteAttribute (const hid_t dataset, const char* name, const char* values, int nvalues);

static void WriteAttribute (const hid_t dataset, const char* const name, const char value)
{
  WriteAttribute (dataset, name, &value, 1);
}

static void WriteAttribute (const hid_t dataset, const char* const name, const char* const values)
{
  WriteAttribute (dataset, name, values, strlen(values));
}

static void WriteAttribute (const hid_t dataset, const char* const name, const char* const values, const int nvalues)
{
  assert (dataset>=0);
  assert (name);
  assert (values);
  assert (nvalues>=0);
  
  herr_t herr;
  
  const hid_t dataspace = H5Screate (H5S_SCALAR);
  assert (dataspace>=0);
  
  const hid_t datatype = H5Tcopy (H5T_C_S1);
  assert (datatype>=0);
  herr = H5Tset_size (datatype, nvalues);
  assert (!herr);
  
  const hid_t attribute = H5Acreate (dataset, name, datatype, dataspace, H5P_DEFAULT);
  assert (attribute>=0);
  herr = H5Awrite (attribute, datatype, values);
  assert (!herr);
  herr = H5Aclose (attribute);
  assert (!herr);
  
  herr = H5Tclose (datatype);
  assert (!herr);
  
  herr = H5Sclose (dataspace);
  assert (!herr);
}

#endif

//
// This function output an unknown-grid gridfn in HDF5 format to a
// named output file.
//
void patch_system::output_unknown_gridfn
	(bool ghosted_flag, int unknown_gfn,
         const char gfn_name[], const cGH *cctkGH,
	 bool output_xyz_flag, bool radius_is_ghosted_flag,
                               int unknown_radius_gfn,
	 const char output_file_name[], bool want_ghost_zones)
	const
{
if (want_ghost_zones && !ghosted_flag)
   then error_exit(PANIC_EXIT,
"***** patch_system::output_unknown_gridfn(unknown_gfn=%d):\n"
"        can't have want_ghost_zones && !ghosted_flag !\n"
,
		   unknown_gfn);				/*NOTREACHED*/
if (want_ghost_zones && output_xyz_flag && !radius_is_ghosted_flag)
   then error_exit(PANIC_EXIT,
"***** patch_system::output_unknown_gridfn(unknown_gfn=%d):\n"
"        can't have want_ghost_zones && output_xyz_flag\n"
"        && !radius_is_ghosted_flag!\n"
"        unknown_radius_gfn=%d\n"
,
		   unknown_gfn,
		   unknown_radius_gfn);				/*NOTREACHED*/

#ifdef HAVE_CAPABILITY_HDF5
  
  using std::ostringstream;
  using std::string;
  using std::vector;
    
  herr_t herr;
  
  hid_t writer;
  
  // Get grid hierarchy extentsion from IOUtil
  const ioGH * const iogh = (const ioGH *)CCTK_GHExtension (cctkGH, "IO");
  assert (iogh);
  
  struct stat fileinfo;
  if (! iogh->recovered || stat(output_file_name, &fileinfo) != 0) {
    writer = H5Fcreate (output_file_name, H5F_ACC_TRUNC, H5P_DEFAULT, H5P_DEFAULT);
  } else {
    writer = H5Fopen (output_file_name, H5F_ACC_RDWR, H5P_DEFAULT);
  }
  assert (writer >= 0);
  
  hid_t datatype;
  if (sizeof(fp) == sizeof(float)) {
    datatype = H5T_NATIVE_FLOAT;
  } else if (sizeof(fp) == sizeof(double)) {
    datatype = H5T_NATIVE_DOUBLE;
  } else if (sizeof(fp) == sizeof(long double)) {
    datatype = H5T_NATIVE_LDOUBLE;
  } else {
    assert (0);
  }
  assert (datatype >= 0);
  
  for (int pn=0; pn<N_patches(); ++pn) {
    const patch& p = ith_patch(pn);
    
    hsize_t shape[2];
    shape[0] = p.effective_N_isigma(want_ghost_zones);
    shape[1] = p.effective_N_irho(want_ghost_zones);
    
    hid_t dataspace = H5Screate_simple (2, shape, NULL);
    assert (dataspace >= 0);
    
    
    
    ostringstream datasetnamebuf;
    datasetnamebuf << gfn_name
                   << " it=" << cctkGH->cctk_iteration
                   << " patch=" << pn;
    string datasetnamestr = datasetnamebuf.str();
    assert (datasetnamestr.size() <= 256); // limit dataset name length
    const char * const datasetname = datasetnamestr.c_str();
    assert (datasetname);
    
    ostringstream datasetnamebufx;
    datasetnamebufx << "x"
                    << " it=" << cctkGH->cctk_iteration
                    << " patch=" << pn;
    string datasetnamestrx = datasetnamebufx.str();
    assert (datasetnamestrx.size() <= 256); // limit dataset name length
    const char * const datasetnamex = datasetnamestrx.c_str();
    assert (datasetnamex);
    
    ostringstream datasetnamebufy;
    datasetnamebufy << "y"
                    << " it=" << cctkGH->cctk_iteration
                    << " patch=" << pn;
    string datasetnamestry = datasetnamebufy.str();
    assert (datasetnamestry.size() <= 256); // limit dataset name length
    const char * const datasetnamey = datasetnamestry.c_str();
    assert (datasetnamey);
    
    ostringstream datasetnamebufz;
    datasetnamebufz << "z"
                    << " it=" << cctkGH->cctk_iteration
                    << " patch=" << pn;
    string datasetnamestrz = datasetnamebufz.str();
    assert (datasetnamestrz.size() <= 256); // limit dataset name length
    const char * const datasetnamez = datasetnamestrz.c_str();
    assert (datasetnamez);
    
    
    
    hid_t dataset = H5Dcreate (writer, datasetname, datatype, dataspace, H5P_DEFAULT);
    assert (dataset >= 0);
    
    hid_t datasetx;
    hid_t datasety;
    hid_t datasetz;
    if (output_xyz_flag) {
      
      datasetx = H5Dcreate (writer, datasetnamex, datatype, dataspace, H5P_DEFAULT);
      assert (datasetx >= 0);
      
      datasety = H5Dcreate (writer, datasetnamey, datatype, dataspace, H5P_DEFAULT);
      assert (datasety >= 0);
      
      datasetz = H5Dcreate (writer, datasetnamez, datatype, dataspace, H5P_DEFAULT);
      assert (datasetz >= 0);
      
    } // if output_xyf_flag
    
    
    
    vector<fp> tmpdata;
    vector<fp> tmpx, tmpy, tmpz;
    tmpdata.resize (shape[0] * shape[1]);
    if (output_xyz_flag) {
      tmpx.resize (shape[0] * shape[1]);
      tmpy.resize (shape[0] * shape[1]);
      tmpz.resize (shape[0] * shape[1]);
    }
    
    int elt = 0;
    for (int irho = p.effective_min_irho(want_ghost_zones);
         irho <= p.effective_max_irho(want_ghost_zones);
         ++irho)
    {
      for (int isigma = p.effective_min_isigma(want_ghost_zones);
           isigma <= p.effective_max_isigma(want_ghost_zones);
           ++isigma)
      {
        
        tmpdata.at(elt) = p.unknown_gridfn (ghosted_flag, unknown_gfn, irho,isigma);
        if (output_xyz_flag) {
          const fp r = p.unknown_gridfn(radius_is_ghosted_flag,
                                        unknown_radius_gfn,
                                        irho,isigma);
          const fp rho   = p.rho_of_irho(irho);
          const fp sigma = p.sigma_of_isigma(isigma);
          fp local_x, local_y, local_z;
          p.xyz_of_r_rho_sigma(r,rho,sigma,
                               local_x,local_y,local_z);
          const fp global_x = origin_x() + local_x;
          const fp global_y = origin_y() + local_y;
          const fp global_z = origin_z() + local_z;
          tmpx.at(elt) = global_x;
          tmpy.at(elt) = global_y;
          tmpz.at(elt) = global_z;
        }
        ++elt;
        
      }
    }
    
    
    
    herr = H5Dwrite (dataset, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tmpdata.front());
    assert (! herr);
    
    WriteAttribute (dataset, "name", gfn_name);
    
    herr = H5Dclose (dataset);
    assert (! herr);
    
    
    
    if (output_xyz_flag) {
      
      herr = H5Dwrite (datasetx, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tmpx.front());
      assert (! herr);
      
      WriteAttribute (datasetx, "name", "x");
      
      herr = H5Dclose (datasetx);
      assert (! herr);
      
      
      
      herr = H5Dwrite (datasety, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tmpy.front());
      assert (! herr);
      
      WriteAttribute (datasety, "name", "y");
      
      herr = H5Dclose (datasety);
      assert (! herr);
      
      
      
      herr = H5Dwrite (datasetz, datatype, H5S_ALL, H5S_ALL, H5P_DEFAULT, &tmpz.front());
      assert (! herr);
      
      WriteAttribute (datasetz, "name", "z");
      
      herr = H5Dclose (datasetz);
      assert (! herr);
      
    } // if output_xyz_flag
    
    
    
    herr = H5Sclose (dataspace);
    assert (! herr);
    
  } // for pn
  
  
  
  herr = H5Fclose (writer);
  assert (! herr);
  
#else
  error_exit(ERROR_EXIT,
"***** patch_system::output_unknown_gridfn(unknown_gfn=%d):\n"
"        no HDF5 support compiled in.  Cannot write output file \"%s\"\n!"
,
             unknown_gfn,
             output_file_name);					/*NOTREACHED*/

#endif
}

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

//
// This function "synchronizes" all ghost zones of all patches, i.e. it
// update the ghost-zone values of the specified gridfns via the appropriate
// sequence of symmetry operations and interpatch interpolations.  This
// process is described in detail in the header comments in "ghost_zone.hh".
//
void patch_system::synchronize(int ghosted_min_gfn_to_sync,
			       int ghosted_max_gfn_to_sync)
{
//
// Phase 1:
// Fill in gridfn data at all the non-corner points of symmetry ghost
// zones, using the symmetries to get this data from its "home patch"
// nominal grids.
//
	  {
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		// n.b. these loops must use _int_ variables for the loop
		//      to terminate!
		for (int want_min = false ; want_min <= true ; ++want_min)
		{
		for (int want_rho = false ; want_rho <= true ; ++want_rho)
		{
		ghost_zone& gz = p.minmax_ang_ghost_zone(want_min, want_rho);
		if (gz.is_symmetry())
		   then gz.synchronize(ghosted_min_gfn_to_sync,
				       ghosted_max_gfn_to_sync,
				       false,	// want corners?
				       true);	// want non-corner?
		}
		}
	}
	  }

//
// Phase 2:
// Fill in gridfn data in all the interpatch ghost zones, using interpatch
// interpolation from neighboring patches as described above.
//
	  {
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		// n.b. these loops must use _int_ variables for the loop
		//      to terminate!
		for (int want_min = false ; want_min <= true ; ++want_min)
		{
		for (int want_rho = false ; want_rho <= true ; ++want_rho)
		{
		ghost_zone& gz = p.minmax_ang_ghost_zone(want_min, want_rho);
		if (gz.is_interpatch())
		   then gz.synchronize(ghosted_min_gfn_to_sync,
				       ghosted_max_gfn_to_sync);
		}
		}
	}
	  }

//
// Phase 3:
// Fill in gridfn data at all the corner points of symmetry ghost zones,
// using the symmetries to get this data from its "home patch" nominal
// grids or ghost zones.
//
	  {
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	patch& p = ith_patch(pn);
		// n.b. these loops must use _int_ variables for the loop
		//      to terminate!
		for (int want_min = false ; want_min <= true ; ++want_min)
		{
		for (int want_rho = false ; want_rho <= true ; ++want_rho)
		{
		ghost_zone& gz = p.minmax_ang_ghost_zone(want_min, want_rho);
		if (gz.is_symmetry())
		   then gz.synchronize(ghosted_min_gfn_to_sync,
				       ghosted_max_gfn_to_sync,
				       true,	// want corners?
				       false);	// want non-corner?
		}
		}
	}
	  }
}

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

//
// This function does any precomputation necessary to compute the Jacobian
// of  synchronize() , taking into account synchronize()'s full 3-phase
// algorithm.  In practice, this means it computes the individual Jacobian
// of each ghost zone, and sets  global_{min,max}_ym_ .
//
void patch_system::compute_synchronize_Jacobian(int ghosted_min_gfn_to_sync,
						int ghosted_max_gfn_to_sync)
	const
{
global_min_ym_ = +INT_MAX;
global_max_ym_ = -INT_MAX;
	for (int pn = 0 ; pn < N_patches() ; ++pn)
	{
	const patch& p = ith_patch(pn);
		// n.b. these loops must use _int_ variables for the loop
		//      to terminate!
		for (int want_min = false ; want_min <= true ; ++want_min)
		{
		for (int want_rho = false ; want_rho <= true ; ++want_rho)
		{
		ghost_zone& gz = p.minmax_ang_ghost_zone(want_min, want_rho);
		gz.compute_Jacobian(ghosted_min_gfn_to_sync,
				    ghosted_max_gfn_to_sync);

		global_min_ym_ = jtutil::min(global_min_ym_,
					     gz.Jacobian_min_y_ipar_m());
		global_max_ym_ = jtutil::max(global_max_ym_,
					     gz.Jacobian_max_y_ipar_m());
		}
		}
	}
}

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

//
// Given that  compute_synchronize_Jacobian()  has been called, this
// function computes the global min/max  m  over all ghost zone points.
// This is useful for sizing the buffer for synchronize_Jacobian().
//
void patch_system::synchronize_Jacobian_global_minmax_ym
	(int& min_ym, int& max_ym)
	const
{
min_ym = global_min_ym_;
max_ym = global_max_ym_;
}

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

//
// Given that  compute_synchronize_Jacobian()  has been called, this
// function computes a single row of the Jacobian, taking into account
// synchronize()'s 3-phase algorithm:
// - It returns the edge to which the y point belongs (the caller can get
//   the patch from this edge).
// - It stores y_iperp and y_posn and min/max ym in the named arguments.
// - It stores the Jacobian elements
//	partial synchronize() gridfn(ghosted_gfn, px, x_iperp, x_ipar)
//	-------------------------------------------------------------
//	     partial gridfn(ghosted_gfn, py, y_iperp, y_posn+ym)
//   in the caller-supplied buffer
//	Jacobian_buffer(ym)
//   for each  ym  in the min/max ym range.
//
// In practice, the main task of this function is taking into account
// synchronize()'s 3-phase algorithm.  There are several cases:
// - ghost zone is symmetry && x point is in non-corner
//   ==> x value was computed by a phase 1 symmetry operation,
//       using (only) nominal-grid data
//   ==> overall Jacobian(x,y) = ghost zone Jacobian(x,y)
// - ghost zone is symmetry && x point is in corner
//   --> x value was computed by a phase 3 symmetry operation,
//       from some point (call it z),
//       ==> overall Jacobian(x,y) = overall Jacobian(z,y)
//       ==> call this function recursively to get z's Jacobian
//           (z must be in the noncorner part of some ghost zone,
//	      so this won't lead to infinite recursion)
// - ghost zone is interpatch
//   ==> x value was computed by a phase 2 interpatch interpolation
//       - using (only) nominal-grid data
//         ==> overall Jacobian(x,y) = ghost zone Jacobian(x,y)
//       - using a mixture of nominal-grid data
//         and data computed by a phase 1 symmetry operation
//         ==> overall Jacobian(x,y) = "fold" ghost zone Jacobian(x,y)
//                                     to take the phase 1 symmetry
//                                     operation into account
//
const patch_edge&
  patch_system::synchronize_Jacobian(const ghost_zone& xgz,
				     int x_iperp, int x_ipar,
				     int& y_iperp,
				     int& y_posn, int& min_ym, int& max_ym,
				     jtutil::array1d<fp>& Jacobian_buffer)
	const
{
const patch_edge& xe = xgz.my_edge();

if (xgz.is_symmetry() && xe.ipar_is_in_noncorner(x_ipar))
   then {
	// ghost zone is symmetry && x point is in non-corner
	// ==> x value was computed by a phase 1 symmetry operation,
	//     using (only) nominal-grid data
	// ==> overall Jacobian(x,y) = ghost zone Jacobian(x,y)
	return ghost_zone_Jacobian(xgz,
				   x_iperp, x_ipar,
				   y_iperp,
				   y_posn, min_ym, max_ym,
				   Jacobian_buffer);
	}

else if (xgz.is_symmetry() && xe.ipar_is_in_corner(x_ipar))
   then {
	// ghost zone is symmetry && x point is in corner
	// --> x value was computed by a phase 3 symmetry operation,
	//     from some point (call it z),
	//     ==> overall Jacobian(x,y) = overall Jacobian(z,y)
	//     ==> call this function recursively to get z's Jacobian
	//         (z must be in the noncorner part of some ghost zone,
	//          so this won't lead to infinite recursion)

	const patch&      zp = xgz.other_patch();
	const patch_edge& ze = xgz.other_edge();
	const symmetry_ghost_zone& xsgz = xgz.cast_to_symmetry_ghost_zone();
	const int z_iperp = xsgz.iperp_map_of_iperp(x_iperp);
	const int z_ipar  = xsgz.ipar_map_of_ipar  (x_ipar);

	//
	// Computing z's edge/ghost zone is tricky.  For example:
	//	                        |
	//	           p1         e3|e4          p2
	//	                        |
	//	                        |   z
	//	-----------e1-----------+------------e2---------
	//	                        |   x
	//	                        |
	// Here the point x in the corner of p1's e1 ghost zone,
	// is computed by the phase 3 symmetry operation (a reflection
	// about e1) from z.  Thus zp == p1 and ze == e1.
	//
	// But we need to "turn the corner" to compute z's "true" edge
	// e3 (so we can recursively call this function to compute z's
	// Jacobian).  Thus we explicitly check which ghost zone of p1
	// (here the e3 ghost zone) contains the point z.
	//
	const int z_irho   = ze.  irho_of_iperp_ipar(z_iperp, z_ipar);
	const int z_isigma = ze.isigma_of_iperp_ipar(z_iperp, z_ipar);
	const ghost_zone& true_zgz
		= zp.ghost_zone_containing_noncorner_point(z_irho, z_isigma);
	const patch_edge& true_ze = true_zgz.my_edge();
	const int true_z_iperp = true_ze.iperp_of_irho_isigma(z_irho, z_isigma);
	const int true_z_ipar  = true_ze. ipar_of_irho_isigma(z_irho, z_isigma);

	// make sure we have the right ghost zone!
	assert( true_zgz.is_in_ghost_zone(true_z_iperp, true_z_ipar) );

	return synchronize_Jacobian(true_zgz,
				    true_z_iperp, true_z_ipar,
				    y_iperp,
				    y_posn, min_ym, max_ym,
				    Jacobian_buffer);
	}

else if (xgz.is_interpatch())
   then {
	// ghost zone is interpatch
	// ==> x value was computed by a phase 2 interpatch interpolation
	//     - using (only) nominal-grid data
	//       ==> overall Jacobian(x,y) = ghost zone Jacobian(x,y)
	//     - using a mixture of nominal-grid data
	//       and data computed by a phase 1 symmetry operation
	//       ==> overall Jacobian(x,y) = "fold" ghost zone Jacobian(x,y)
	//                                   to take the phase 1 symmetry
	//                                   operation into account
	//
	// For example,
	//	                        |
	//	           xp         xe|ye a        yp
	//	                        |   b
	//	                        |  xc
	//	----------xse-----------+---d-------yse----------
	//	                        |   e
	//	                        |
	// here point x is computed by interpatch-interpolating in the
	// par direction from the 5 y points abcde.  e is outside the
	// nominal grid, so its Jacobian must be "folded" over to c.
	// Notice that this "folding" must be done about the edge yse,
	// *not* about ye itself.

	// Jacobian of the phase 2 interpatch interpolation
	const patch_edge& ye = ghost_zone_Jacobian(xgz,
						   x_iperp, x_ipar,
						   y_iperp,
						   y_posn, min_ym, max_ym,
						   Jacobian_buffer);
	const int min_y_ipar = y_posn + min_ym;
	const int max_y_ipar = y_posn + max_ym;

	// fold any points in the Jacobian outside the nominal grid
	if (ye.ipar_is_in_min_ipar_corner(min_y_ipar))
	   then {
		fold_Jacobian(ye, ye.min_par_adjacent_edge(),
			      y_iperp,
			      y_posn, min_ym, max_ym,
			      min_ym, ye.min_ipar_corner__max_ipar() - y_posn,
			      Jacobian_buffer);
		min_ym = ye.min_ipar_without_corners() - y_posn;
		}
	if (ye.ipar_is_in_max_ipar_corner(max_y_ipar))
	   then {
		fold_Jacobian(ye, ye.max_par_adjacent_edge(),
			      y_iperp,
			      y_posn, min_ym, max_ym,
			      ye.max_ipar_corner__min_ipar() - y_posn, max_ym,
			      Jacobian_buffer);
		max_ym = ye.max_ipar_without_corners() - y_posn;
		}

	return ye;
	}

else	error_exit(PANIC_EXIT,
"***** patch_system::synchronize_Jacobian():\n"
"        don't know what to do with ghost zone (this should never happen)!\n"
"        xgz.my_patch()=\"%s\"    xe=xgz.my_edge()=\"%s\"\n"
"        xgz.other_patch()=\"%s\"    xgz.other_edge()=\"%s\"\n"
"        xgz.is_symmetry()=(int)%d xgz.is_interpatch()=(int)%d\n"
"        x_iperp=%d x_ipar=%d\n"
"        xe.ipar_is_in_{min,max}_ipar_corner(x_ipar)=(int){%d,%d}\n"
"        xe.ipar_is_in_{corner,noncorner}(x_ipar)=(int){%d,%d}\n"
,
		   xgz.my_patch().name(),    xe.name(),
		   xgz.other_patch().name(), xgz.other_edge().name(),
		   int(xgz.is_symmetry()), int(xgz.is_interpatch()),
		   x_iperp, x_ipar,
		   xe.ipar_is_in_min_ipar_corner(x_ipar),
		   xe.ipar_is_in_max_ipar_corner(x_ipar),
		   xe.ipar_is_in_corner(x_ipar),
		   xe.ipar_is_in_noncorner(x_ipar));		/*NOTREACHED*/
}

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

//
// This function "folds" part of a(n interpatch) Jacobian row to take
// a symmetry operation into account.  For example:
//	         |
//	         |e_Jac
//	         |             p
//	         |   a
//	         |   b
//	         |   c=y
//	---------+---d-------e_fold-------
//	         |   e=x    sgz_fold
//	         |
// Here the Jacobian abcde is to be "folded", because e is outside the
// nominal grid (its Jacobian must be "folded" over to c).
//
// Notice that the folding (about the edge e_fold) is in the par direction
// with respect to e_Jac, but the perp direction with respect to e_fold.
// Since e_fold and e_Jac are adjacent edges, 
//	e_Jac (iperp,ipar) == e_fold (ipar,iperp)
//
// Arguments:
// e_Jac = edge which the Jacobian lies along
// e_fold = edge about which to fold; the corresponding ghost zone must be
//	    symmetry ghost zone, and at present we only support the case
//	    where this is a "local" (mirror-image) symmetry ghost zone
// iperp = iperp-wrt-e_Jac coordinate of Jacobian
// posn = ipar-wrt-e_Jac coordinate of Jacobian molecule reference point
// [min,max]_m = range of ipar-wrt-e_Jac molecule m in Jacobian
// [min,max]_fold_m = range of ipar-wrt-e_Jac molecule m which is to folded;
//		      this must be a subrange of [min,max]_m
//
void patch_system::fold_Jacobian(const patch_edge& e_Jac,
				 const patch_edge& e_fold,
				 int iperp,
				 int posn, int min_m, int max_m,
				 int min_fold_m, int max_fold_m,
				 jtutil::array1d<fp>& Jacobian_buffer)
	const
{
// check that [min,max]_fold_m is a subrange of [min,max]_m
assert( min_fold_m >= min_m );
assert( min_fold_m <= max_m );
assert( max_fold_m >= min_m );
assert( max_fold_m <= max_m );

const patch& p = e_fold.my_patch();
assert( e_Jac.my_patch() == p );

const symmetry_ghost_zone& sgz_fold = p.ghost_zone_on_edge(e_fold)
				       .cast_to_symmetry_ghost_zone();

//
// At present we only handle the case show in the comments above,
// where sgz_fold is a local (mirror-image) symmetry, i.e. where
// y is guaranteed to be within the molecule abcde.
//
if (sgz_fold.other_edge() != e_fold)
   then error_exit(ERROR_EXIT,
"***** patch_system::fold_Jacobian()\n"
"        implementation restriction: at present we only handle folding\n"
"        via \"local\" (mirror-image) symmetries!\n"
"        p=\"%s\" e_Jac=\"%s\" e_fold=\"%s\"\n"
"        but sgz_fold.other_edge()=\"%s\" != e_fold\n"
,
		   p.name(), e_Jac.name(), e_fold.name(),
		   sgz_fold.other_edge().name());		/*NOTREACHED*/

	for (int xm = min_fold_m ; xm <= max_fold_m ; ++xm)
	{
	const int x_Jac_ipar = posn + xm;	// x ipar wrt e_Jac
	const int x_fold_iperp = x_Jac_ipar;	// ... == iperp wrt e_fold

	const int y_fold_iperp = sgz_fold.iperp_map_of_iperp(x_fold_iperp);
						// y iperp wrt e_fold
	const int y_Jac_ipar = y_fold_iperp;	// ... == ipar wrt e_Jac
	const int ym = y_Jac_ipar - posn;

	// check that y is indeed within the molecule
	assert( ym >= min_m );
	assert( ym <= max_m );

	// actually "fold" the molecule
	Jacobian_buffer(ym) += Jacobian_buffer(xm);
	}
}

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

//
// Given that  compute_synchronize_Jacobian()  has been called, this
// function computes a single row of the Jacobian of a given ghost zone,
// *not* taking into account synchronize()'s 3-phase algorithm:
// - It returns the edge to which the y point belongs (the caller can get
//   the patch from this edge).
// - It stores y_iperp and y_posn and min/max ym in the named arguments.
// - It stores the Jacobian elements
//	partial synchronize() gridfn(ghosted_gfn, px, x_iperp, x_ipar)
//	-------------------------------------------------------------
//	     partial gridfn(ghosted_gfn, py, y_iperp, y_posn+ym)
//   in the caller-supplied buffer
//	Jacobian_buffer(ym)
//   for each  ym  in the min/max ym range
//
const patch_edge&
  patch_system::ghost_zone_Jacobian(const ghost_zone& xgz,
				    int x_iperp, int x_ipar,
				    int& y_iperp,
				    int& y_posn, int& min_ym, int& max_ym,
				    jtutil::array1d<fp>& Jacobian_buffer)
	const
{
y_iperp = xgz.Jacobian_y_iperp(x_iperp);

y_posn = xgz.Jacobian_y_ipar_posn(x_iperp, x_ipar);
min_ym = xgz.Jacobian_min_y_ipar_m();
max_ym = xgz.Jacobian_max_y_ipar_m();

	for (int ym = min_ym ; ym <= max_ym ; ++ym)
	{
	Jacobian_buffer(ym) = xgz.Jacobian(x_iperp, x_ipar, ym);
	}

return xgz.other_edge();
}

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

	  }	// namespace AHFinderDirect

```

patch/coord_derivs.out:
```
    |\^/|     Maple 7 (IBM INTEL LINUX)
._|\|   |/|_. Copyright (c) 2001 by Waterloo Maple Inc.
 \  MAPLE  /  All rights reserved. Maple is a registered trademark of
 <____ ____>  Waterloo Maple Inc.
      |       Type ? for help.
# coord_derivs.maple -- compute derivatives of (mu,nu,phi) wrt (x,y,z)
# $Id: coord_derivs.maple,v 1.1 2002/04/17 14:34:16 jthorn Exp $
> 
> mu  := arctan(y/z);
                               mu := arctan(y/z)

> nu  := arctan(x/z);
                               nu := arctan(x/z)

> phi := arctan(y/x);
                               phi := arctan(y/x)

> 
########################################
> 
> simplify(diff(mu,x));
                                       0

> simplify(diff(mu,y));
                                       z
                                    -------
                                     2    2
                                    z  + y

> simplify(diff(mu,z));
                                        y
                                   - -------
                                      2    2
                                     z  + y

> 
> simplify(diff(nu,x));
                                       z
                                    -------
                                     2    2
                                    z  + x

> simplify(diff(nu,y));
                                       0

> simplify(diff(nu,z));
                                        x
                                   - -------
                                      2    2
                                     z  + x

> 
> simplify(diff(phi,x));
                                        y
                                   - -------
                                      2    2
                                     x  + y

> simplify(diff(phi,y));
                                       x
                                    -------
                                     2    2
                                    x  + y

> simplify(diff(phi,z));
                                       0

> 
########################################
> 
> simplify(diff(mu,x,x));
                                       0

> simplify(diff(mu,x,y));
                                       0

> simplify(diff(mu,x,z));
                                       0

> simplify(diff(mu,y,y));
                                       z y
                                 -2 ----------
                                      2    2 2
                                    (z  + y )

> simplify(diff(mu,y,z));
                                      2    2
                                    -z  + y
                                   ----------
                                     2    2 2
                                   (z  + y )

> simplify(diff(mu,z,z));
                                       z y
                                  2 ----------
                                      2    2 2
                                    (z  + y )

> 
> simplify(diff(nu,x,x));
                                       z x
                                 -2 ----------
                                      2    2 2
                                    (z  + x )

> simplify(diff(nu,x,y));
                                       0

> simplify(diff(nu,x,z));
                                      2    2
                                     z  - x
                                  - ----------
                                      2    2 2
                                    (z  + x )

> simplify(diff(nu,y,y));
                                       0

> simplify(diff(nu,y,z));
                                       0

> simplify(diff(nu,z,z));
                                       z x
                                  2 ----------
                                      2    2 2
                                    (z  + x )

> 
> simplify(diff(phi,x,x));
                                       y x
                                  2 ----------
                                      2    2 2
                                    (x  + y )

> simplify(diff(phi,x,y));
                                      2    2
                                    -x  + y
                                   ----------
                                     2    2 2
                                   (x  + y )

> simplify(diff(phi,x,z));
                                       0

> simplify(diff(phi,y,y));
                                       y x
                                 -2 ----------
                                      2    2 2
                                    (x  + y )

> simplify(diff(phi,y,z));
                                       0

> simplify(diff(phi,z,z));
                                       0

> quit
bytes used=444120, alloc=458668, time=0.04

```

cctest/README:
```
This directory contains subdirectories with test programs for
various C++ compiler/library features:

namespace/	This directory tests how namespaces are handled for
		C standard library functions, eg <stdio.h> vs <cstdio>

vector/		This directory test how namespaces are handled for
		the STL vector class, eg <vector.h> vs <cvector>

inline-fns/	This directory tests how inline functions are handled,
		both within C++ classes and "globally"

```

cctest/inline-fns/mini-util.hh:
```
// mini-util.hh -- cut-down version of src/jtutil/util.hh for testing inline fns
// $Header$

namespace jtutil
          {
// how many integers are in the closed interval [low,high]
inline int how_many_in_range(int low, int high) { return high - low + 1; }
          }

```

cctest/inline-fns/Makefile:
```
# Makefile to test various C/C++ compiler features
# $Header: /usr/local/svn/cvs-repositories/numrelcvs/AEIThorns/AHFinderDirect/src/cctest/inline-fns/Makefile,v 1.1 2003-07-31 17:29:48 jthorn Exp $
#
# Targets:
#	default		inline-fn inline-class
#	inline-fn	test non-class inline function in .cc file
#	inline-fn2	test non-class inline function in header file
#	inline-class	test class with inline function
#	clean		remove all binaries
#
# Arguments:
# CXX		how to invoke the C++ compiler, including any desired flags
#

# default setting, may be overridden from command line
CXX := g++ -W -Wall -pedantic -ansi

.PHONY	: default
default	: inline-fn inline-fn2 inline-class

.PHONY	: inline-fn
inline-fn:
	$(CXX) -o inline-fn inline-fn.cc

.PHONY	: inline-fn2
inline-fn2:
	$(CXX) -o inline-fn2 inline-fn2.cc

.PHONY	: inline-class
inline-class:
	$(CXX) -o inline-class inline-class.cc

.PHONY	: clean
clean	:
	-rm inline-fn inline-fn2 inline-class

```

cctest/inline-fns/README:
```
$Header$

This directory contains test programs to see how (or if!) compilers
handle inline functions.

inline-fn.cc	Tests a trivial non-class inline function
		defined in the .cc file
inline-fn2.cc	Tests a trivial non-class inline function
		defined in a header file
inline-class.cc	Tests a trivial class containing an inline function
		(and an out-of-line constructor)

```

cctest/inline-fns/inline-class.cc:
```
// inline-class.cc -- test simple class with inline function
// $Header$
#include <stdio.h>

class	addx
	{
public:
	int operator()(int i) { return i+x_; }
	addx(int x);
private:
	int x_;
	};

addx::addx(int x)
	: x_(x)
{ }

int main(void)
{
addx add3(3);
printf("add3(42) = %d\n", add3(42));
return 0;
}

```

cctest/inline-fns/inline-fn.cc:
```
// inline-fn.cc -- test non-class inline function
// $Header$
#include <stdio.h>

inline int add3(int x) { return x+3; }

int main(void)
{
printf("add3(42) = %d\n", add3(42));
return 0;
}

```

cctest/inline-fns/inline-fn2.cc:
```
// inline-fn2.cc -- test non-class inline function in header file
// $Header$
#include <stdio.h>
#include "mini-util.hh"

int main(void)
{
printf("jtutil::how_many_in_range(69,105) = %d\n",
       jtutil::how_many_in_range(69,105));
return 0;
}

```

cctest/namespace/stdio_h-std--using-std-printf.cc:
```
// $Header$

#include <stdio.h>
using std::printf;

int main()
{
printf("testing <stdio.h> functions in std:: namespace:\n");
printf("==> #include <stdio.h>; using std::printf; printf() is ok\n");
return 0;
}

```

cctest/namespace/Makefile:
```
# Makefile to test various C/C++ compiler features
#
# Targets:
#	default		test namespace handling for <stdio.h> and <cstdio>
#	stdio_h		test namespace handling for <stdio.h>
#	cstdio		test namespace handling for <cstdio>
#	clean		remove all binaries
#
# Arguments:
# CXX		how to invoke the C++ compiler, including any desired flags
#

# default setting, may be overridden from command line
CXX := g++ -W -Wall -pedantic -ansi

.PHONY	: default
default	: stdio_h cstdio
	
.PHONY	: stdio_h
stdio_h	:
	-$(CXX) -o stdio_h-global \
                   stdio_h-global.cc
	        -./stdio_h-global
	-$(CXX) -o stdio_h-std--std-printf \
                   stdio_h-std--std-printf.cc
	        -./stdio_h-std--std-printf
	-$(CXX) -o stdio_h-std--using-std-printf \
                   stdio_h-std--using-std-printf.cc
	        -./stdio_h-std--using-std-printf
	-$(CXX) -o stdio_h-std--using-namespace-std \
                   stdio_h-std--using-namespace-std.cc
	        -./stdio_h-std--using-namespace-std

.PHONY	: cstdio
cstdio	:
	-$(CXX) -o cstdio-global \
                   cstdio-global.cc
	        -./cstdio-global
	-$(CXX) -o cstdio-std--std-printf \
                   cstdio-std--std-printf.cc
	        -./cstdio-std--std-printf
	-$(CXX) -o cstdio-std--using-std-printf \
                   cstdio-std--using-std-printf.cc
	        -./cstdio-std--using-std-printf
	-$(CXX) -o cstdio-std--using-namespace-std \
                   cstdio-std--using-namespace-std.cc
	        -./cstdio-std--using-namespace-std

.PHONY	: clean
clean	:
	-rm -f stdio_h-global \
               stdio_h-std--std-printf \
               stdio_h-std--using-std-printf \
               stdio_h-std--using-namespace-std
	-rm -f cstdio-global \
               cstdio-std--std-printf \
               cstdio-std--using-std-printf \
               cstdio-std--using-namespace-std

```

cctest/namespace/cstdio-std--std-printf.cc:
```
// $Header$

#include <cstdio>

int main()
{
std::printf("testing <cstdio> functions in std:: namespace:\n");
std::printf("==> #include <cstdio>; std::printf() is ok\n");
return 0;
}

```

cctest/namespace/stdio_h-std--std-printf.cc:
```
// $Header$

#include <stdio.h>

int main()
{
std::printf("testing <stdio.h> functions in std:: namespace:\n");
std::printf("==> #include <stdio.h>; std::printf() is ok\n");
return 0;
}

```

cctest/namespace/status:
```
osmium.aei.mpg.de	# OpenBSD 3.1
gcc version 2.95.3 20010125 (prerelease)
Sun Sep 29 11:32:19 EDT 2002
CXX='/usr/bin/g++ -W -Wall -ansi -pedantic'
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; std::printf() is ok
==> #include <stdio.h>; using std::printf; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using std::printf; printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

xeon21.aei.mpg.de
gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-98)
Sun Sep 29 18:24:39 CEST 2002
CXX='/usr/bin/g++ -W -Wall -pedantic -ansi'
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; std::printf() is ok
==> #include <stdio.h>; using std::printf; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using std::printf; printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

xeon21.aei.mpg.de
gcc version 3.2		# built by JT from release tarball
Sun Sep 29 18:27:28 CEST 2002
CXX='/scratch/jthorn/local/bin/g++ -Wall -pedantic -ansi'
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using std::printf; printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

xeon21.aei.mpg.de
Intel(R) C++ Compiler for 32-bit applications, Version 5.0.1   Build 010730D0
Sun Sep 29 18:28:37 CEST 2002
CXX='icc -Xc'
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using std::printf; printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

xeon21.aei.mpg.de
Intel(R) C++ Compiler for 32-bit applications, Version 5.0.1   Build 010730D0
Sun Sep 29 18:29:44 CEST 2002
CXX='icc -ansi -Xc'
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using std::printf; printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

Peter Diener's Laptop using icc version 6.0
options: -ansi -Xc
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using std::printf; printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

origin.aei.mpg.de
MIPSpro Compilers: Version 7.3.1.2m
Mon Oct  7 20:31:11 CEST 2002
CXX='CC -LANG:std'
==> #include <stdio.h>; printf() is ok
all others fail

Hitachi using sCC (native)
options:  -64 -stl -O3 -pvec -predicate -model=F1
also identical leaving out -64 (compare with ../vector/ results!)
==> #include <stdio.h>; printf() is ok
==> #include <stdio.h>; using namespace std; printf() is ok
==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)
==> #include <cstdio>; std::printf() is ok
==> #include <cstdio>; using namespace std; printf() is ok

Loslobos using pgCC version 3.2-3
options: -tused -O2
==> #include <stdio.h>; printf() is ok

```

cctest/namespace/README:
```
$Header$

This directory contains a set of "hello, world" programs to test how
the C++ namespaces are handled for standard library functions.

Each .cc file is a "hello, world\n" program using a particular set of
options for what header file to #include, what (if any) using declarations
or definitions to use, and whether or not to use an explicit std::
qualifier when calling printf().

According to standard C++, #include <cstdio> puts names *only* in the
std:: namespace, so the cstdio-global.cc program should fail to compile.
All the other programs should be legal in standard C++.

					what to		what namespace		how we call	status
					#include?	are we using		printf()?	in
							printf() in?				standard
								using?				C++

stdio_h-global.cc			<stdio.h>	global	--		printf()	ok
stdio_h-std:std-printf.cc		<stdio.h>	std	--		std::printf()	ok
stdio_h-std:using-std-printf.cc		<stdio.h>	std	std::printf;	printf()	ok
stdio_h-std:using-namespace-std.cc	<stdio.h>	std	namespace std;	printf()	ok

cstdio-global.cc			<cstdio>	global	--		printf()	WRONG
cstdio-std:std-printf.cc		<cstdio>	std	--		std::printf()	ok
cstdio-std:using-std-printf.cc		<cstdio>	std	std::printf;	printf()	ok
cstdio-std:using-namespace-std.cc	<cstdio>	std	namespace std;	printf()	ok

```

cctest/namespace/cstdio-std--using-std-printf.cc:
```
// $Header$

#include <cstdio>
using std::printf;

int main()
{
printf("testing <cstdio> functions in std:: namespace:\n");
printf("==> #include <cstdio>; using std::printf; printf() is ok\n");
return 0;
}

```

cctest/namespace/stdio_h-global.cc:
```
// $Header$

#include <stdio.h>

int main()
{
printf("testing <stdio.h> functions in global namespace:\n");
printf("==> #include <stdio.h>; printf() is ok\n");
return 0;
}

```

cctest/namespace/stdio_h-std--using-namespace-std.cc:
```
// $Header$

#include <stdio.h>
using namespace std;

int main()
{
printf("testing <stdio.h> functions in std:: namespace:\n");
printf("==> #include <stdio.h>; using namespace std; printf() is ok\n");
return 0;
}

```

cctest/namespace/cstdio-std--using-namespace-std.cc:
```
// $Header$

#include <cstdio>
using namespace std;

int main()
{
printf("testing <cstdio> functions in std:: namespace:\n");
printf("==> #include <cstdio>; using namespace std; printf() is ok\n");
return 0;
}

```

cctest/namespace/cstdio-global.cc:
```
// $Header$

#include <cstdio>

int main()
{
printf("testing <cstdio> functions in global namespace (THIS SHOULD FAIL):\n");
printf("==> #include <cstdio>; printf() is ok (THIS SHOULD FAIL)\n");
return 0;
}

```

cctest/vector/Makefile:
```
# Makefile to test various C/C++ compiler features
#
# Targets:
#	default		test namespace handling for <vector.h> and <vector>
#	vector_h	test namespace handling for <vector.h>
#	vector		test namespace handling for <vector>
#	clean		remove all binaries
#
# Arguments:
# CXX		how to invoke the C++ compiler, including any desired flags
#

# default setting, may be overridden from command line
CXX := g++ -W -Wall -pedantic -ansi

.PHONY	: default
default	: vector_h vector
	
.PHONY	: vector_h
vector_h	:
	-$(CXX) -o vector_h-global \
                   vector_h-global.cc
	        -./vector_h-global
	-$(CXX) -o vector_h-std--std-vector \
                   vector_h-std--std-vector.cc
	        -./vector_h-std--std-vector
	-$(CXX) -o vector_h-std--using-std-vector \
                   vector_h-std--using-std-vector.cc
	        -./vector_h-std--using-std-vector
	-$(CXX) -o vector_h-std--using-namespace-std \
                   vector_h-std--using-namespace-std.cc
	        -./vector_h-std--using-namespace-std

.PHONY	: vector
vector	:
	-$(CXX) -o vector-global \
                   vector-global.cc
	        -./vector-global
	-$(CXX) -o vector-std--std-vector \
                   vector-std--std-vector.cc
	        -./vector-std--std-vector
	-$(CXX) -o vector-std--using-std-vector \
                   vector-std--using-std-vector.cc
	        -./vector-std--using-std-vector
	-$(CXX) -o vector-std--using-namespace-std \
                   vector-std--using-namespace-std.cc
	        -./vector-std--using-namespace-std

.PHONY	: clean
clean	:
	-rm -f vector_h-global \
               vector_h-std--std-vector \
               vector_h-std--using-std-vector \
               vector_h-std--using-namespace-std
	-rm -f vector-global \
               vector-std--std-vector \
               vector-std--using-std-vector \
               vector-std--using-namespace-std

```

cctest/vector/vector-std--std-vector.cc:
```
// $Header$

#include <stdio.h>
#include <vector>

int main()
{
printf("testing <vector> functions in std:: namespace:\n");
std::vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector>; std::vector is ok\n");
return 0;
}

```

cctest/vector/status:
```
osmium.aei.mpg.de	# OpenBSD 3.1
gcc version 2.95.3 20010125 (prerelease)
Sun Oct  6 21:15:55 EDT 2002
CXX='/usr/bin/g++ -W -Wall -ansi -pedantic'
==> #include <vector.h>; vector is ok
==> #include <vector.h>; std::vector is ok
==> #include <vector.h>; using std::vector; vector is ok
==> #include <vector.h>; using namespace std; vector is ok
==> #include <vector>; vector is ok (THIS SHOULD FAIL)
==> #include <vector>; std::vector is ok
==> #include <vector>; using std::vector; vector is ok
==> #include <vector>; using namespace std; vector is ok

xeon21.aei.mpg.de
gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-98)
Mon Oct  7 18:38:20 CEST 2002
CXX='/usr/bin/g++ -W -Wall -pedantic -ansi'
==> #include <vector.h>; vector is ok
==> #include <vector.h>; std::vector is ok
==> #include <vector.h>; using std::vector; vector is ok
==> #include <vector.h>; using namespace std; vector is ok
==> #include <vector>; vector is ok (THIS SHOULD FAIL)
==> #include <vector>; std::vector is ok
==> #include <vector>; using std::vector; vector is ok
==> #include <vector>; using namespace std; vector is ok

xeon21.aei.mpg.de
g++ (GCC) 3.2
Mon Oct  7 18:34:53 CEST 2002
CXX='/scratch/jthorn/local/bin/g++ -W -Wall -ansi -pedantic'
==> #include <vector.h>; vector is ok
==> #include <vector.h>; std::vector is ok
==> #include <vector.h>; using std::vector; vector is ok
==> #include <vector.h>; using namespace std; vector is ok
==> #include <vector>; std::vector is ok
==> #include <vector>; using std::vector; vector is ok
==> #include <vector>; using namespace std; vector is ok

xeon21.aei.mpg.de
Intel(R) C++ Compiler for 32-bit applications, Version 5.0.1   Build 010730D0
Mon Oct  7 18:36:42 CEST 2002
CXX='icc -Xc -ansi'
==> #include <vector>; std::vector is ok
==> #include <vector>; using std::vector; vector is ok
==> #include <vector>; using namespace std; vector is ok

origin.aei.mpg.de
MIPSpro Compilers: Version 7.3.1.2m
Mon Oct  7 20:31:11 CEST 2002
CXX='CC -LANG:std'
==> #include <vector.h>; vector is ok
all others fail

Hitachi using sCC (native)
options:  -64 -stl -O3 -pvec -predicate -model=F1
all tests fail (internal compiler error)

Hitachi using sCC (native)
options:  -stl -O3 -pvec -predicate -model=F1
==> #include <vector>; std::vector is ok
==> #include <vector>; using std::vector; vector is ok
==> #include <vector>; using namespace std; vector is ok

Loslobos using pgCC version 3.2-3
options: -tused -O2
==> #include <vector>; vector is ok (THIS SHOULD FAIL)
==> #include <vector>; std::vector is ok
==> #include <vector>; using std::vector; vector is ok
==> #include <vector>; using namespace std; vector is ok

```

cctest/vector/README:
```
$Header$

This directory contains a set of "hello, world" programs to test how
C++ namespaces are handled for the STL vector class.

Each .cc file is a "hello, world\n" program using a particular set of
options for what header file to #include, what (if any) using declarations
or definitions to use, and whether or not to use an explicit std::
qualifier when referring to the vector class.

According to standard C++, #include <vector> puts names *only* in the
std:: namespace, so the vector-global.cc program should fail to compile.
All the other programs should be legal in standard C++.

					what to		what namespace		how we call	status
					#include?	are we using		vector()?	in
							vector() in?				standard
								using?				C++

vector_h-global.cc			<vector.h>	global	--		vector()	ok
vector_h-std:std-vector.cc		<vector.h>	std	--		std::vector()	ok
vector_h-std:using-std-vector.cc	<vector.h>	std	std::vector;	vector()	ok
vector_h-std:using-namespace-std.cc	<vector.h>	std	namespace std;	vector()	ok

vector-global.cc			<vector>	global	--		vector()	WRONG
vector-std:std-vector.cc		<vector>	std	--		std::vector()	ok
vector-std:using-std-vector.cc		<vector>	std	std::vector;	vector()	ok
vector-std:using-namespace-std.cc	<vector>	std	namespace std;	vector()	ok

```

cctest/vector/vector-std--using-std-vector.cc:
```
// $Header$

#include <stdio.h>
#include <vector>
using std::vector;

int main()
{
printf("testing <vector> functions in std:: namespace:\n");
vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector>; using std::vector; vector is ok\n");
return 0;
}

```

cctest/vector/vector_h-std--std-vector.cc:
```
// $Header$

#include <stdio.h>
#include <vector.h>

int main()
{
printf("testing <vector.h> functions in std:: namespace:\n");
std::vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector.h>; std::vector is ok\n");
return 0;
}

```

cctest/vector/vector_h-std--using-namespace-std.cc:
```
// $Header$

#include <stdio.h>
#include <vector.h>
using namespace std;

int main()
{
printf("testing <vector.h> functions in std:: namespace:\n");
vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector.h>; using namespace std; vector is ok\n");
return 0;
}

```

cctest/vector/vector_h-std--using-std-vector.cc:
```
// $Header$

#include <stdio.h>
#include <vector.h>
using std::vector;

int main()
{
printf("testing <vector.h> functions in std:: namespace:\n");
vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector.h>; using std::vector; vector is ok\n");
return 0;
}

```

cctest/vector/vector-std--using-namespace-std.cc:
```
// $Header$

#include <stdio.h>
#include <vector>
using namespace std;

int main()
{
printf("testing <vector> functions in std:: namespace:\n");
vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector>; using namespace std; vector is ok\n");
return 0;
}

```

cctest/vector/vector_h-global.cc:
```
// $Header$

#include <stdio.h>
#include <vector.h>

int main()
{
printf("testing <vector.h> functions in global namespace:\n");
vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector.h>; vector is ok\n");
return 0;
}

```

cctest/vector/vector-global.cc:
```
// $Header$

#include <stdio.h>
#include <vector>

int main()
{
printf("testing <vector> functions in global namespace (THIS SHOULD FAIL):\n");
vector<int> v(3);
v[0] = 42;
v[1] = 69;
v[2] = 105;
printf("%d %d %d should be 42 69 105... ", v[0], v[1], v[2]);
printf(((v[0] == 42) && (v[1] == 69) && (v[2] == 105)) ? "ok\n" : "FAIL\n");
printf("==> #include <vector>; vector is ok (THIS SHOULD FAIL)\n");
return 0;
}

```

sparse-matrix/README:
```
This directory contains sparse matrix routines imported from elsewhere.
See the individual files or subdirectories for details on authorship,
copyright, etc.

ilucg
	ILUCG (Incomplete LU-decomposition / conjugate gradiant)
	subroutines.

umfpack
	UMFPACK sparse LU decomposition package (version 4.0 of 11.Apr.2002).

y12m
	Sparse LU decomposition subroutines from the Harwell Y12M package.
	I haven't written the AHFinderDirect support code for this, so
	the Y12M code isn't compiled yet.

```

sparse-matrix/ilucg/ilucg.h:
```
/* ilucg.h -- C/C++ prototypes for [sd]ilucg() routines */
/* $Header$ */

/*
 * prerequisites:
 *	"cctk.h"
 *	"config.h"	// for "integer" = Fortran integer
 */

#ifdef __cplusplus
extern "C"
       {
#endif

/*
 * ***** ILUCG *****
 */
void CCTK_FCALL
  CCTK_FNAME(silucg)(const integer* N,
		     const integer IA[], const integer JA[], const float A[],
		     const float B[], float X[],
		     integer ITEMP[], float RTEMP[],
		     const float* EPS, const integer* ITER,
		     integer* ISTATUS);
void CCTK_FCALL
  CCTK_FNAME(dilucg)(const integer* N,
		     const integer IA[], const integer JA[], const double A[],
		     const double B[], double X[],
		     integer ITEMP[], double RTEMP[],
		     const double* EPS, const integer* ITER,
		     integer* ISTATUS);

#ifdef __cplusplus
       }	/* extern "C" */
#endif

```

sparse-matrix/ilucg/README:
```
This directory contains the ILUCG (Incomplete LU-decomposition /
conjugate gradiant) sparse matrix subroutines.

These subroutines were provided to me (JT) by Tom Nicol of the University
of British Columbia Computing Center in 1985; I believe they were originally
written by P. Madderom (also of the UBC Computing Center).

I have slightly modified the convergence criteria.

```

sparse-matrix/ilucg/make.code.defn:
```
# Cactus specification of things to be compiled in this directory
# $Header$

# Source files in this directory
## uncomment the appropriate line for single vs double precision
##SRCS = silucg.f77
SRCS = dilucg.f77

# Subdirectories containing source files
SUBDIRS =

```

sparse-matrix/ilucg/dilucg.f77:
```
      SUBROUTINE DILUCG(N,
     $                  IA,JA,A,
     $                  B,X,
     $                  ITEMP,RTEMP,
     $                  EPS,MAXITER,
     $                  ISTATUS)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(*),X(*),ITEMP(*),RTEMP(*)
C
C     INCOMPLETE LU DECOMPOSITION-CONJUGATE GRADIENT
C     -          --               -         -
C WHERE:
C     |N| IS THE NUMBER OF EQUATIONS.  IF N < 0, ITEMP AND
C       RTEMP CONTAIN THE ILU FROM A PREVIOUS CALL AND
C       B AND X ARE THE NEW RHS AND INITIAL GUESS.
C     IA IS AN INTEGER ARRAY DIMENSIONED |N|+1.  IA(I) IS THE
C       INDEX INTO ARRAYS JA AND A OF THE FIRST NON-ZERO
C       ELEMENT IN ROW I.  LET MAX=IA(|N|+1)-IA(1).
C     JA IS AN INTEGER ARRAY DIMENSIONED MAX.  JA(K) GIVES
C       THE COLUMN NUMBER OF A(K).
C     A IS A DOUBLE PRECISION ARRAY DIMENSIONED MAX.  IT CONTAINS THE
C       NONZERO ELEMENTS OF THE MATRIX STORED BY ROW.
C     B CONTAINS THE RHS VECTOR.
C     X IS A DOUBLE PRECISION ARRAY DIMENSIONED |N|.  ON ENTRY, IT CONTAINS
C       AN INITIAL ESTIMATE; ON EXIT, THE SOLUTION.
C     ITEMP IS AN INTEGER SCRATCH ARRAY DIMENSIONED 3*(|N|+MAX)+2.
C     RTEMP IS A DOUBLE PRECISION SCRATCH ARRAY DIMENSIONED 4*|N|+MAX.
C     EPS IS THE CONVERGENCE CRITERIA.  IT SPECIFIES THE RELATIVE
C       ERROR ALLOWED IN THE SOLUTION.  TO BE PRECISE, CONVERGENCE
C       IS DEEMED TO HAVE OCCURED WHEN THE INFINITY-NORM OF THE
C       CHANGE IN THE SOLUTION IN ONE ITERATION IS .LE. EPS * THE
C       INFINITY-NORM OF THE CURRENT SOLUTION.  HOWEVER, IF EPS
C       .LT. 0.0D0, IT IS INTERNALLY SCALED BY THE MACHINE PRECISION,
C       SO THAT, FOR EXAMPLE, EPS = -256.0D0 WILL ALLOW THE LAST 8 BITS
C       OF THE SOLUTION TO BE IN ERROR.
C     MAXITER GIVES THE REQUESTED NUMBER OF ITERATIONS,
C       OR IS 0 FOR "NO LIMIT".
C     ISTATUS IS AN INTEGER VARIABLE, WHICH IS SET TO:
C       -I IF THERE IS AN ERROR IN THE MATRIX STRUCTURE IN ROW I
C          (SUCH AS A ZERO ELEMENT ON THE DIAGONAL).
C        0 IF THE ITERATION FAILED TO REACH THE CONVERGENCE CRITERION
C          IN ITER ITERATIONS.
C       +I IF THE ITERATION CONVERGED IN I ITERATIONS.
C
C MODIFICATIONS
C * RETURN LOGICAL VALUE, J. THORNBURG, 13/MAY/85.
C * ADD CONVERGENCE CRITERIA, J. THORNBURG, 17/MAY/85.
C * CHANGED CONVERGENCE CRITERIA, J. THORNBURG, 4/AUG/89.
C * AVOID SKIP RETURNS, J. THORNBURG, 10/SEP/89.
C * CHANGE FUNCTION BACK TO SUBROUTINE, J. THORNBURG, 3.JUN.2003
C * CHANGE CONVERGENCE CRITERIA TO AVOID SPURIOUSLY STOPPING TOO SOON
C   ON SMALL RHS VECTORS -- J. THORNBURG, 11.JUN.2003
C
C REFERENCE:
C     D.S.KERSHAW,"THE INCOMPLETE CHOLESKY-CONJUGATE GRADIENT
C       METHOD FOR INTERATIVE SOLUTION OF LINEAR EQUATIONS",
C       J.COMPUT.PHYS. JAN 1978 PP 43-65
C
      LOGICAL DLU0
      NP=IABS(N)
      ISTATUS=0
      IF (NP.EQ.0) GO TO 20
C CALCULATE INDICES FOR BREAKING UP TEMPORARY ARRAYS.
      N1=NP+1
      MAX=IA(N1)-IA(1)
      ILU=1
      JLU=ILU+N1
      ID=JLU+MAX
      IC=ID+NP
      JC=IC+N1
      JCI=JC+MAX
      IR=1
      IP=IR+NP
      IS1=IP+NP
      IS2=IS1+NP
      IALU=IS2+NP
      IF (N.LT.0) GO TO 10
C DO INCOMPLETE LU DECOMPOSITION
      IF (DLU0(NP,IA,JA,A,ITEMP(IC),ITEMP(JC),ITEMP(JCI),RTEMP(IALU),
     *    ITEMP(ILU),ITEMP(JLU),ITEMP(ID),RTEMP(IR),IERROR)) GOTO 20
C AND DO CONJUGATE GRADIENT ITERATIONS
C CALL MODIFIED TO ADD ADJUSTABLE CONVERGENCE CRITERIA EPS
C - J. THORNBURG, 17/MAY/85.
10    CALL DNCG0(NP,IA,JA,A,B,X,ITEMP(ILU),ITEMP(JLU),ITEMP(ID),
     *  RTEMP(IALU),RTEMP(IR),RTEMP(IP),RTEMP(IS1),RTEMP(IS2),
     *  EPS,MAXITER,ITER)
C ITER IS ACTUAL NUMBER OF ITERATIONS (NEGATIVE IF NO CONVERGENCE)
      ISTATUS = ITER
      IF (ITER .LT. 0) ISTATUS = 0
      RETURN
C ERROR RETURN FROM INCOMPLETE LU DECOMPOSITION
20    ISTATUS = -IERROR
      RETURN
      END

      LOGICAL FUNCTION DLU0(N,IA,JA,A,IC,JC,JCI,ALU,ILU,JLU,ID,V,IE)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),IC(*),JC(*),JCI(*),
     *  ALU(*),ILU(*),JLU(*),ID(N),V(N)
      LOGICAL NODIAG
      COMMON /ICBD00/ ICBAD
C     INCOMPLETE LU DECOMPOSITION
C WHERE:
C     N,IA,JA, AND A ARE DESCRIBED IN SUBROUTINE ILUCG
C     IC IS AN INTEGER ARRAY DIMENSIONED N+1, IC(J) GIVES THE
C       INDEX OF THE FIRST NONZERO ELEMENT IN COLMN J IN
C       ARRAY JC.
C     JC IS AN INTEGER ARRAY WITH THE SAME DIMENSION AS A.
C       JC(K) GIVES THE ROW NUMBER OF THE K'TH ELEMENT IN
C       THE COLUMN STRUCTURE.
C     JCI IS AN INTEGER ARRAY WITH THE SAME DIMENSION AS A.
C       JCI(K) GIVES THE INDEX INTO ARRAY A OF THE K'TH ELEMENT
C       OF THE COLUMN STRUCTURE.
C     ALU HAS THE SAME DIMENSION AS A.  ON EXIT, IT WILL
C       CONTAIN THE INCOMPLETE LU DECOMPOSITION OF A WITH THE
C       RECIPROCALS OF THE DIAGONAL ELEMENTS OF U.
C     ILU AND JLU CORRESPONDS TO IA AND JA BUT FOR ALU.
C     ID IS AN INTEGER ARRAY DIMENSIONED N.  IT CONTAINS
C       INDICES TO THE DIAGONAL ELEMENTS OF U.
C     V IS A REAL SCRATCH VECTOR OF LENGTH N.
C     IE GIVES THE ROW NUMBER IN ERROR IF AN ERROR OCCURED
C       (RETURN VALUE .TRUE.), OR IS UNUSED IF ALL IS WELL
C       (RETURN VALUE .FALSE.).
C
C     RETURN VALUE = .FALSE. IF ALL IS WELL, .TRUE. IF ERROR.
C
C NOTE: DLU0 SETS ARGUMENTS IC THROUGH V.
C
      ICBAD=0
C ZERO COUNT OF ZERO DIAGONAL ELEMENTS IN U.
C
C FIRST CHECK STRUCTURE OF A AND BUILD COLUMN STRUCTURE
      DO 10 I=1,N
        IC(I)=0
10    CONTINUE
      DO 30 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        NODIAG=.TRUE.
        DO 20 K=KS,KE
          J=JA(K)
          IF (J.LT.1.OR.J.GT.N) GO TO 210
          IC(J)=IC(J)+1
          IF (J.EQ.I) NODIAG=.FALSE.
20      CONTINUE
        IF (NODIAG) GO TO 210
30    CONTINUE
C MAKE IC INTO INDICES
      KOLD=IC(1)
      IC(1)=1
      DO 40 I=1,N
        KNEW=IC(I+1)
        IF (KOLD.EQ.0) GO TO 210
        IC(I+1)=IC(I)+KOLD
        KOLD=KNEW
40    CONTINUE
C SET JC AND JCI FOR COLUMN STRUCTURE
      DO 60 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        DO 50 K=KS,KE
          J=JA(K)
          L=IC(J)
          IC(J)=L+1
          JC(L)=I
          JCI(L)=K
50      CONTINUE
60    CONTINUE
C FIX UP IC
      KOLD=IC(1)
      IC(1)=1
      DO 70 I=1,N
        KNEW=IC(I+1)
        IC(I+1)=KOLD
        KOLD=KNEW
70    CONTINUE
C FIND SORTED ROW STRUCTURE FROM SORTED COLUMN STRUCTURE
      NP=N+1
      DO 80 I=1,NP
        ILU(I)=IA(I)
80    CONTINUE
C MOVE ELEMENTS, SET JLU AND ID
      DO 100 J=1,N
        KS=IC(J)
        KE=IC(J+1)-1
        DO 90 K=KS,KE
          I=JC(K)
          L=ILU(I)
          ILU(I)=L+1
          JLU(L)=J
          KK=JCI(K)
          ALU(L)=A(KK)
          IF (I.EQ.J) ID(J)=L
90      CONTINUE
100   CONTINUE
C RESET ILU (COULD JUST USE IA)
      DO 110 I=1,NP
        ILU(I)=IA(I)
110   CONTINUE
C FINISHED WITH SORTED COLUMN AND ROW STRUCTURE
C
C DO LU DECOMPOSITION USING GAUSSIAN ELIMINATION
      DO 120 I=1,N
        V(I)=0.0D0
120   CONTINUE
      DO 200 IROW=1,N
        I=ID(IROW)
        PIVOT=ALU(I)
        IF (PIVOT.NE.0.0D0) GO TO 140
C THIS CASE MAKES THE ILU LESS ACCURATE
        ICBAD=ICBAD+1
        KS=ILU(IROW)
        KE=ILU(IROW+1)-1
        DO 130 K=KS,KE
          PIVOT=PIVOT+DABS(ALU(K))
130     CONTINUE
        IF (PIVOT.EQ.0.0D0) GO TO 220
140     PIVOT=1.0D0/PIVOT
        ALU(I)=PIVOT
        KKS=I+1
        KKE=ILU(IROW+1)-1
        IF (KKS.GT.KKE) GO TO 160
        DO 150 K=KKS,KKE
          J=JLU(K)
          V(J)=ALU(K)
150     CONTINUE
C FIX L IN COLUMN IROW AND DO PARTIAL LU IN SUBMATRIX
160     KS=IC(IROW)
        KE=IC(IROW+1)-1
        DO 190 K=KS,KE
          I=JC(K)
          IF (I.LE.IROW) GO TO 190
          LS=ILU(I)
          LE=ILU(I+1)-1
          DO 180 L=LS,LE
            J=JLU(L)
            IF (J.LT.IROW) GO TO 180
            IF (J.GT.IROW) GO TO 170
            AMULT=ALU(L)*PIVOT
            ALU(L)=AMULT
            IF (AMULT.EQ.0.0) GO TO 190
            GO TO 180
170         IF (V(J).EQ.0.0D0) GO TO 180
            ALU(L)=ALU(L)-AMULT*V(J)
180       CONTINUE
190     CONTINUE
C RESET V
        IF (KKS.GT.KKE) GO TO 200
        DO 195 K=KKS,KKE
          J=JLU(K)
          V(J)=0.0D0
195     CONTINUE
200   CONTINUE
C NORMAL RETURN
      DLU0 = .FALSE.
      RETURN
C ERROR RETURNS
210   IE=I
      DLU0 = .TRUE.
      RETURN
220   IE=IROW
      DLU0 = .TRUE.
      RETURN
      END

      SUBROUTINE DNCG0(N,IA,JA,A,B,X,ILU,JLU,ID,ALU,R,P,S1,S2,
     *  EPS,ITER,IE)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(N),X(N),ILU(*),JLU(*),ALU(*),ID(N),
     *  R(N),P(N),S1(N),S2(N)
C     NONSYMMETRIC CONJUGATE GRADIENT
C WHERE:
C     N,IA,JA,A,B, AND X ARE DESCRIBED IN SUBROUTINE DILUCG.
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW OF LU.
C     JLU GIVES COLUMN NUMBER.
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U.
C     ALU HAS NONZERO ELEMENTS OF LU MATRIX STORED BY ROW
C       WITH RECIPROCALS OF DIAGONAL ELEMENTS OF U.
C     R,P,S1, AND S2 ARE VECTORS OF LENGTH N USED IN THE
C       ITERATIONS.
C FOLLOWING PARAMETER ADDED BY J. THORNBURG, 17/MAY/85.
C     EPS IS CONVERGENCE CRITERIA.  (DESCRIBED IN SUBROUTINE
C       DILUCG).
C     ITER IS MAX NUMBER OF ITERATIONS, OR 0 FOR "NO LIMIT".
C     IE GIVES ACTUAL NUMBER OF ITERATIONS, NEGATIVE IF
C       NO CONVERGENCE.
C
C R0=B-A*X0
      CALL DMUL10(N,IA,JA,A,X,R)
      DO 10 I=1,N
        R(I)=B(I)-R(I)
10    CONTINUE
C P0=(UT*U)(-1)*AT*(L*LT)(-1)*R0
C FIRST SOLVE L*LT*S1=R0
      CALL DSUBL0(N,ILU,JLU,ID,ALU,R,S1)
C TIMES TRANSPOSE OF A
      CALL DMUL20(N,IA,JA,A,S1,S2)
C THEN SOLVE UT*U*P=S2
      CALL DSUBU0(N,ILU,JLU,ID,ALU,S2,P)
      IE=0
C INPROD IS DOT PRODUCT ROUTINE IN *LIBRARY (UBC MATRIX P 28)
C ALL CALLS ON IT COMMENTED OUT AND REPLACED WITH CALLS TO NEW
C ROUTINE DGVV(...); SOURCE CODE FOR LATTER ADDED TO END OF
C THIS FILE.  - J. THORNBURG, 10/MAY/85.
C     CALL INPROD(R,S1,EDOT,RDOT,N)
      RDOT = DGVV(R,S1,N)
C LOOP BEGINS HERE
20    CALL DMUL30(N,ILU,JLU,ID,ALU,P,S2)
C     CALL INPROD(P,S2,EDOT,PDOT,N)
      PDOT = DGVV(P,S2,N)
C
      IF (PDOT.EQ.0.0D0) RETURN
C
      ALPHA=RDOT/PDOT
C EQUATION 9PA                      ALPHA=(R,LINV*R)/(P,UT*U*P)
C 1 + ||X CHANGE|| CHANGED TO ||X CHANGE|| -- J. THORNBURG, 11.JUN.2003
      XMAX=0.0D0
      XDIF=0.0D0
      DO 30 I=1,N
        AP=ALPHA*P(I)
        X(I)=X(I)+AP
C EQUATION 9PB                      X=X+ALPHA*P
        AP=DABS(AP)
        XX=DABS(X(I))
        IF (AP.GT.XDIF) XDIF=AP
        IF (XX.GT.XMAX) XMAX=XX
30    CONTINUE
      IE=IE+1
C
C CONVERGENCE TEST (CHANGED BY J. THORNBURG, 17/MAY/85, 4/AUG/89)
C
      IF ((EPS .GT. 0.0D0) .AND. (XDIF .LE. EPS * XMAX)) RETURN
      IF ((EPS .LT. 0.0D0) .AND. (XMAX + XDIF/DABS(EPS) .EQ. XMAX))
     *   RETURN
C
C EXCEEDED ITERATION LIMIT?
C
      IF ((ITER .NE. 0) .AND. (IE .GE. ITER)) GO TO 60
      CALL DMUL10(N,IA,JA,A,P,S2)
      DO 40 I=1,N
        R(I)=R(I)-ALPHA*S2(I)
C EQUATION 9PC                      R=R-ALPHA*A*P
40    CONTINUE
      CALL DSUBL0(N,ILU,JLU,ID,ALU,R,S1)
C     CALL INPROD(R,S1,EDOT,RRDOT,N)
      RRDOT = DGVV(R,S1,N)
      BETA=RRDOT/RDOT
C EQUATION 9PD                      BETA=(R+,LINV*R+)/(R,LINV*R)
      RDOT=RRDOT
      CALL DMUL20(N,IA,JA,A,S1,S2)
      CALL DSUBU0(N,ILU,JLU,ID,ALU,S2,S1)
      DO 50 I=1,N
        P(I)=S1(I)+BETA*P(I)
C EQUATION 9PE                      P=(UT*U)(-1)*AT*(L*LT)(-1)*R+BETA*P
50    CONTINUE
      GO TO 20
60    IE=-IE
      RETURN
      END

      SUBROUTINE DMUL10(N,IA,JA,A,B,X)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(N),X(N)
C MULTIPLY A TIMES B TO GET X
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     IA GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW
C     JA GIVES COLUMN NUMBER
C     A CONTAINS THE NONZERO ELEMENTS OF THE NONSYMMETRIC
C       MATRIX STORED BY ROW
C     B IS THE VECTOR
C     X IS THE PRODUCT (MUST BE DIFFERENT FROM B)
C
      DO 20 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        SUM=0.0D0
        DO 10 K=KS,KE
          J=JA(K)
          SUM=SUM+A(K)*B(J)
10      CONTINUE
        X(I)=SUM
20    CONTINUE
      RETURN
      END

      SUBROUTINE DMUL20(N,IA,JA,A,B,X)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(N),X(N)
C MULTIPLY TRANSPOSE OF A TIMES B TO GET X
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     IA GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW
C     JA GIVES COLUMN NUMBER
C     A CONTAINS THE NONZERO ELEMENTS OF THE NONSYMMETRIC
C       MATRIX STORED BY ROW
C     B IS THE VECTOR
C     X IS THE PRODUCT (MUST BE DIFFERENT FROM B)
C
      DO 10 I=1,N
        X(I)=0.0D0
10    CONTINUE
      DO 30 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        BB=B(I)
        DO 20 K=KS,KE
          J=JA(K)
          X(J)=X(J)+A(K)*BB
20      CONTINUE
30    CONTINUE
      RETURN
      END

      SUBROUTINE DMUL30(N,ILU,JLU,ID,ALU,B,X)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION ILU(*),JLU(*),ID(*),ALU(*),B(N),X(N)
C MULTIPLY TRANSPOSE OF U TIMES U TIMES B TO GET X
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW OF LU
C     JLU GIVES COLUMN NUMBER
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U
C     ALU HAS NONZERO ELEMENTS OF LU MATRIX STORED BY ROW
C       WITH RECIPROCALS OF DIAGONAL ELEMENTS
C     B IS THE VECTOR
C     X IS THE PRODUCT UT*U*B (X MUST BE DIFFERENT FROM B)
C
      DO 10 I=1,N
        X(I)=0.0D0
10    CONTINUE
      DO 50 I=1,N
        KS=ID(I)+1
        KE=ILU(I+1)-1
        DIAG=1.0D0/ALU(KS-1)
        XX=DIAG*B(I)
        IF (KS.GT.KE) GO TO 30
        DO 20 K=KS,KE
          J=JLU(K)
          XX=XX+ALU(K)*B(J)
20      CONTINUE
30      X(I)=X(I)+DIAG*XX
        IF (KS.GT.KE) GO TO 50
        DO 40 K=KS,KE
          J=JLU(K)
          X(J)=X(J)+ALU(K)*XX
40      CONTINUE
50    CONTINUE
      RETURN
      END

      SUBROUTINE DSUBU0(N,ILU,JLU,ID,ALU,B,X)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION ILU(*),JLU(*),ID(*),ALU(*),B(N),X(N)
C DO FORWARD AND BACK SUBSTITUTION TO SOLVE UT*U*X=B
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW OF LU
C     JLU GIVES COLUMN NUMBER
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U
C     ALU HAS NONZERO ELMENTS OF LU MATRIX STORED BY ROW
C       WITH RECIPROCALS OF DIAGONAL ELEMENTS OF U
C     B IS THE RHS VECTOR
C     X IS THE SOLUTION VECTOR
C
      NP=N+1
      DO 10 I=1,N
        X(I)=B(I)
10    CONTINUE
C FORWARD SUBSTITUTION
      DO 30 I=1,N
        KS=ID(I)+1
        KE=ILU(I+1)-1
        XX=X(I)*ALU(KS-1)
        X(I)=XX
        IF (KS.GT.KE) GO TO 30
        DO 20 K=KS,KE
          J=JLU(K)
          X(J)=X(J)-ALU(K)*XX
20      CONTINUE
30    CONTINUE
C BACK SUBSTITUTION
      DO 60 II=1,N
        I=NP-II
        KS=ID(I)+1
        KE=ILU(I+1)-1
        SUM=0.0D0
        IF (KS.GT.KE) GO TO 50
        DO 40 K=KS,KE
          J=JLU(K)
          SUM=SUM+ALU(K)*X(J)
40      CONTINUE
50      X(I)=(X(I)-SUM)*ALU(KS-1)
60    CONTINUE
      RETURN
      END

      SUBROUTINE DSUBL0(N,ILU,JLU,ID,ALU,B,X)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION ILU(*),JLU(*),ID(*),ALU(*),B(N),X(N)
C DO FORWARD AND BACK SUBSTITUTION TO SOLVE L*LT*X=B
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW LU
C     JLU GIVES THE COLUMN NUMBER
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U
C     ALU HAS NONZERO ELEMENTS OF LU MATRIX STORED BY ROW
C       DIAGONAL ELEMENTS OF L ARE 1.0 AND NOT STORED
C     B IS THE RHS VECTOR
C     X IS THE SOLUTION VECTOR
C
      NP=N+1
      DO 10 I=1,N
        X(I)=B(I)
10    CONTINUE
C FORWARD SUBSTITUTION
      DO 30 I=1,N
        KS=ILU(I)
        KE=ID(I)-1
        IF (KS.GT.KE) GO TO 30
        SUM=0.0D0
        DO 20 K=KS,KE
          J=JLU(K)
          SUM=SUM+ALU(K)*X(J)
20      CONTINUE
        X(I)=X(I)-SUM
30    CONTINUE
C BACK SUBSTITUTION
      DO 50 II=1,N
        I=NP-II
        KS=ILU(I)
        KE=ID(I)-1
        IF (KS.GT.KE) GO TO 50
        XX=X(I)
        IF (XX.EQ.0.0) GO TO 50
        DO 40 K=KS,KE
          J=JLU(K)
          X(J)=X(J)-ALU(K)*XX
40      CONTINUE
50    CONTINUE
      RETURN
      END

      DOUBLE PRECISION FUNCTION DGVV(V,W,N)
      IMPLICIT DOUBLE PRECISION (A-H,O-Z)
      DIMENSION V(N),W(N)
C     SUBROUTINE TO COMPUTE DOUBLE PRECISION VECTOR DOT PRODUCT.
C
      SUM = 0.0D0
            DO 10 I = 1,N
            SUM = SUM + V(I)*W(I)
10          CONTINUE
      DGVV = SUM
      RETURN
      END

```

sparse-matrix/umfpack/umf_set_stats.c:
```
/* ========================================================================== */
/* === UMF_set_stats ======================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    Sets statistics in Info array.  Calculates everything in double precision,
    rather than Int or size_t, so that usage estimates can be computed even if
    the problem is so large that it would cause integer overflow.

    This routine has many double relop's, but the NaN case is ignored.
*/

#include "umf_internal.h"
#include "umf_symbolic_usage.h"

GLOBAL void UMF_set_stats
(
    double Info [ ],
    SymbolicType *Symbolic,
    double max_usage,		/* peak size of Numeric->Memory, in Units */
    double num_mem_size,	/* final size of Numeric->Memory, in Units */
    double flops,		/* "true flops" */
    double lnz,			/* nz in L */
    double unz,			/* nz in U */
    double maxfrsize,		/* largest front size */
    double ulen,		/* size of Numeric->Upattern */
    double npiv,		/* number of pivots found */
    Int what			/* ESTIMATE or ACTUAL */
)
{

    double sym_size, work_usage, nn, n_row, n_col, n_inner, num_On_size1,
	num_On_size2, num_usage, maxncols, maxnrows ;

    n_col = Symbolic->n_col ;
    n_row = Symbolic->n_row ;
    nn = MAX (n_row, n_col) ;
    n_inner = MIN (n_row, n_col) ;
    maxncols = Symbolic->maxncols ;
    maxnrows = Symbolic->maxnrows ;

    /* final Symbolic object size */
    sym_size = UMF_symbolic_usage (Symbolic->n_row, Symbolic->n_col,
	Symbolic->nchains, Symbolic->nfr) ;

    /* size of O(n) part of Numeric object during factorization, */
    /* except Numeric->Memory and Numeric->Upattern */
    num_On_size1 =
	DUNITS (NumericType, 1)		/* Numeric structure */
	+ DUNITS (Entry, n_inner+1)	/* D */
	+ 4 * DUNITS (Int, n_row+1)	/* Rperm, Lpos, Uilen, Uip */
	+ 4 * DUNITS (Int, n_col+1) ;	/* Cperm, Upos, Lilen, Lip */

    /* size of O(n) part of Numeric object after factorization, */
    /* except Numeric->Memory and Numeric->Upattern */
    num_On_size2 =
	DUNITS (NumericType, 1)		/* Numeric structure */
	+ DUNITS (Entry, n_inner+1)	/* D */
	+ DUNITS (Int, n_row+1)		/* Rperm */
	+ DUNITS (Int, n_col+1)		/* Cperm */
	+ 4 * DUNITS (Int, npiv+1) ;	/* Lpos, Uilen, Uip, Upos, Lilen, Lip */

    /* peak size of Numeric->Memory */
    Info [UMFPACK_VARIABLE_PEAK + what] = max_usage ;

    /* final size of Numeric->Memory */
    Info [UMFPACK_VARIABLE_FINAL + what] = num_mem_size ;

    /* final size of Numeric object, including Numeric->Memory and ->Upattern */
    Info [UMFPACK_NUMERIC_SIZE + what] =
	num_On_size2
	+ num_mem_size		/* final Numeric->Memory size */
	+ DUNITS (Int, ulen) ;	/* Numeric->Upattern (from Work->Upattern) */

    /* largest front size (Work->Fx size, or actual size used) */
    Info [UMFPACK_MAX_FRONT_SIZE + what] = maxfrsize ;

    /* UMF_kernel work usage */
    work_usage =
	/* Work-> arrays */
	DUNITS (Entry, Symbolic->maxfrsize)	/* Fx */
	+ 2 * DUNITS (Int, n_row+1)		/* Frpos, Lpattern */
	+ 2 * DUNITS (Int, n_col+1)		/* Fcpos, Upattern */
	+ DUNITS (Int, n_col+n_inner+1)		/* E */
	+ DUNITS (Int, nn + 1)			/* Wp */
	+ 3 * DUNITS (Int, maxncols + 1)	/* Fcols, Wio, Woi */
	+ 2 * DUNITS (Int, maxnrows + 1)	/* Frows, Wm */
	+ DUNITS (Int, MAX (maxnrows, maxncols) + 1)	/* Woo */
	+ DUNITS (Int, Symbolic->nfr + 1) ;	/* Front_new1strow */

    /* Peak memory for just UMFPACK_numeric.  This excludes Numeric->Upattern */
    /* since it includes the equivalenced Work->Upattern array. */
    num_usage = sym_size + num_On_size1 + work_usage + max_usage ;

    /* peak memory usage for both UMFPACK_*symbolic and UMFPACK_numeric. */
    Info [UMFPACK_PEAK_MEMORY + what] =
	MAX (Symbolic->peak_sym_usage, num_usage) ;

    Info [UMFPACK_FLOPS + what] = flops ;
    Info [UMFPACK_LNZ + what] = lnz ;
    Info [UMFPACK_UNZ + what] = unz ;
}

```

sparse-matrix/umfpack/umf_mem_init_memoryspace.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL void UMF_mem_init_memoryspace
(
    NumericType *Numeric
) ;


```

sparse-matrix/umfpack/umf_malloc.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

#if defined (UMF_MALLOC_COUNT) || !defined (NDEBUG)
GLOBAL extern Int UMF_malloc_count ;
#endif

GLOBAL void *UMF_malloc
(
    Int n_objects,
    size_t size_of_object
) ;


```

sparse-matrix/umfpack/umfpack_free_numeric.c:
```
/* ========================================================================== */
/* === UMFPACK_free_numeric ================================================= */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*  User-callable.  Free the entire Numeric object.
    See UMFPACK_free_numeric.h for details.
*/

#include "umf_internal.h"
#include "umf_free.h"

GLOBAL void UMFPACK_free_numeric
(
    void **NumericHandle
)
{

    NumericType *Numeric ;
    if (!NumericHandle)
    {
	return ;
    }
    Numeric = *((NumericType **) NumericHandle) ;
    if (!Numeric)
    {
	return ;
    }

    (void) UMF_free ((void *) Numeric->D) ;
    (void) UMF_free ((void *) Numeric->Rperm) ;
    (void) UMF_free ((void *) Numeric->Cperm) ;
    (void) UMF_free ((void *) Numeric->Lpos) ;
    (void) UMF_free ((void *) Numeric->Lilen) ;
    (void) UMF_free ((void *) Numeric->Lip) ;
    (void) UMF_free ((void *) Numeric->Upos) ;
    (void) UMF_free ((void *) Numeric->Uilen) ;
    (void) UMF_free ((void *) Numeric->Uip) ;
    (void) UMF_free ((void *) Numeric->Upattern) ;
    (void) UMF_free ((void *) Numeric->Memory) ;
    (void) UMF_free ((void *) Numeric) ;

    *NumericHandle = (void *) NULL ;
}


```

sparse-matrix/umfpack/umfpack_get_numeric.c:
```
/* ========================================================================== */
/* === UMFPACK_get_numeric ================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    User-callable.  Gets the LU factors and the permutation vectors held in the
    Numeric object.  L is returned in sparse row form with sorted rows, U is
    returned in sparse column form with sorted columns, and P and Q are
    returned as permutation vectors.  See umfpack_get_numeric.h for a more
    detailed description.

    Returns TRUE if successful, FALSE if the Numeric object is invalid or
    if out of memory.

    Dynamic memory usage:  calls UMF_malloc twice, for a total space of
    2*n integers, and then frees all of it via UMF_free when done.

*/

#include "umf_internal.h"
#include "umf_valid_numeric.h"
#include "umf_malloc.h"
#include "umf_free.h"

#ifndef NDEBUG
PRIVATE Int init_count ;
#endif

PRIVATE void get_L
(
    Int Lp [ ],
    Int Lj [ ],
    double Lx [ ],
#ifdef COMPLEX
    double Lz [ ],
#endif
    NumericType *Numeric,
    Int Pattern [ ],
    Int Wi [ ]
) ;

PRIVATE void get_U
(
    Int Up [ ],
    Int Ui [ ],
    double Ux [ ],
#ifdef COMPLEX
    double Uz [ ],
#endif
    NumericType *Numeric,
    Int Pattern [ ],
    Int Wi [ ]
) ;

/* ========================================================================== */

GLOBAL Int UMFPACK_get_numeric
(
    Int Lp [ ],
    Int Lj [ ],
    double Lx [ ],
#ifdef COMPLEX
    double Lz [ ],
#endif
    Int Up [ ],
    Int Ui [ ],
    double Ux [ ],
#ifdef COMPLEX
    double Uz [ ],
#endif
    Int P [ ],
    Int Q [ ],
    double Dx [ ],
#ifdef COMPLEX
    double Dz [ ],
#endif
    void *NumericHandle
)
{

    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    NumericType *Numeric ;
    Int getL, getU, *Rperm, *Cperm, k, nn, n_row, n_col, *Wi, *Pattern,
	n_inner ;

#ifndef NDEBUG
    init_count = UMF_malloc_count ;
#endif

    Wi = (Int *) NULL ;
    Pattern = (Int *) NULL ;

    /* ---------------------------------------------------------------------- */
    /* check input parameters */
    /* ---------------------------------------------------------------------- */

    Numeric = (NumericType *) NumericHandle ;
    if (!UMF_valid_numeric (Numeric))
    {
	return (UMFPACK_ERROR_invalid_Numeric_object) ;
    }

    n_row = Numeric->n_row ;
    n_col = Numeric->n_col ;
    nn = MAX (n_row, n_col) ;
    n_inner = MIN (n_row, n_col) ;

    /* ---------------------------------------------------------------------- */
    /* allocate workspace */
    /* ---------------------------------------------------------------------- */

#ifdef COMPLEX
    getL = Lp && Lj && Lx && Lz ;
    getU = Up && Ui && Ux && Uz ;
#else
    getL = Lp && Lj && Lx ;
    getU = Up && Ui && Ux ;
#endif

    if (getL || getU)
    {
	Wi = (Int *) UMF_malloc (nn, sizeof (Int)) ;
	Pattern = (Int *) UMF_malloc (nn, sizeof (Int)) ;
	if (!Wi || !Pattern)
	{
	    (void) UMF_free ((void *) Wi) ;
	    (void) UMF_free ((void *) Pattern) ;
	    ASSERT (UMF_malloc_count == init_count) ;
	    return (UMFPACK_ERROR_out_of_memory) ;
	}
	ASSERT (UMF_malloc_count == init_count + 2) ;
    }

    /* ---------------------------------------------------------------------- */
    /* get contents of Numeric */
    /* ---------------------------------------------------------------------- */

    if (P)
    {
	Rperm = Numeric->Rperm ;
	for (k = 0 ; k < n_row ; k++)
	{
	    P [k] = Rperm [k] ;
	}
    }

    if (Q)
    {
	Cperm = Numeric->Cperm ;
	for (k = 0 ; k < n_col ; k++)
	{
	    Q [k] = Cperm [k] ;
	}
    }

    if (getL)
    {
	get_L (Lp, Lj, Lx,
#ifdef COMPLEX
	    Lz,
#endif
	    Numeric, Pattern, Wi) ;
    }

    if (getU)
    {
	get_U (Up, Ui, Ux,
#ifdef COMPLEX
	    Uz,
#endif
	    Numeric, Pattern, Wi) ;
    }

    if (Dx)
    {
	for (k = 0 ; k < n_inner ; k++)
	{
	    Dx [k] = REAL_COMPONENT (Numeric->D [k]) ;
	}
    }

#ifdef COMPLEX
    if (Dz)
    {
	for (k = 0 ; k < n_inner ; k++)
	{
	    Dz [k] = IMAG_COMPONENT (Numeric->D [k]) ;
	}
    }
#endif

    /* ---------------------------------------------------------------------- */
    /* free the workspace */
    /* ---------------------------------------------------------------------- */

    (void) UMF_free ((void *) Wi) ;
    (void) UMF_free ((void *) Pattern) ;
    ASSERT (UMF_malloc_count == init_count) ;

    return (UMFPACK_OK) ;
}


/* ========================================================================== */
/* === get_L ================================================================ */
/* ========================================================================== */

/*
    The matrix L is stored in the following arrays in the Numeric object:

	Int Lpos [0..npiv]
	Int Lip [0..npiv], index into Numeric->Memory
	Int Lilen [0..npiv]
	Unit *(Numeric->Memory), pointer to memory space holding row indices
		and numerical values

    where npiv is the number of pivot entries found.  If A is n_row-by-n_col,
    then npiv <= MIN (n_row,n_col).

    Let L_k denote the pattern of entries in column k of L (excluding the
    diagonal).

    An Lchain is a sequence of columns of L whose nonzero patterns are related.
    The start of an Lchain is denoted by a negative value of Lip [k].

    To obtain L_k:

    (1)	If column k starts an Lchain, then L_k is stored in its entirety.
	|Lip [k]| is an index into Numeric->Memory for the integer row indices
	in L_k.  The number of entries in the column is |L_k| = Lilen [k].
	This defines the pattern of the "leading" column of this chain.
	Lpos [k] is not used for the first column in the chain.  Column zero
	is always a leading column.

    (2) If column k does not start an Lchain, then L_k is represented as a
	superset of L_k-1.  Define Lnew_k such that (L_k-1 - {k} union Lnew_k)
	= L_k, where Lnew_k and (L_k-1)-{k} are disjoint.  Lnew_k are the
	entries in L_k that are not in L_k-1.  Lpos [k] holds the position of
	pivot row index k in the prior pattern L_k-1 (if it is present), so
	that the set subtraction (L_k-1)-{k} can be computed quickly, when
	computing the pattern of L_k from L_k-1.  The number of new entries in
	L_k is stored in Lilen [k] = |Lnew_k|.

	Note that this means we must have the pattern L_k-1 to compute L_k.

    In both cases (1) and (2), we obtain the pattern L_k.

    The numerical values are stored in Numeric->Memory, starting at the index
    |Lip [k]| + Lilen [k].  It is stored in the same order as the entries
    in L_k, after L_k is obtained from cases (1) or (2), above.

    The advantage of using this "packed" data structure is that it can
    dramatically reduce the amount of storage needed for the pattern of L.
    The disadvantage is that it can be difficult for the user to access,
    and it does not match the sparse matrix data structure used in MATLAB.
    Thus, this routine is provided to create a conventional sparse matrix
    data structure for L, in sparse-row form.  A row-form of L appears to
    MATLAB to be a column-oriented from of the transpose of L.  If you would
    like a column-form of L, then use UMFPACK_transpose (an example of this
    is in umfpackmex.c).

*/
/* ========================================================================== */

PRIVATE void get_L
(
    Int Lp [ ],		/* of size n_row+1 */
    Int Lj [ ],		/* of size lnz, where lnz = Lp [n_row] */
    double Lx [ ],	/* of size lnz */
#ifdef COMPLEX
    double Lz [ ],	/* of size lnz */
#endif
    NumericType *Numeric,
    Int Pattern [ ],	/* workspace of size n_row */
    Int Wi [ ]		/* workspace of size n_row */
)
{
    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    Int deg, *ip, j, row, n_row, n_col, n_inner, *Lpos, *Lilen, *Lip, p, llen,
	lnz2, lp, newLchain, k, pos, npiv ;
    Entry *xp, value ;

    /* ---------------------------------------------------------------------- */
    /* get parameters */
    /* ---------------------------------------------------------------------- */

    DEBUG4 (("get_L start:\n")) ;
    n_row = Numeric->n_row ;
    n_col = Numeric->n_col ;
    n_inner = MIN (n_row, n_col) ;
    npiv = Numeric->npiv ;
    Lpos = Numeric->Lpos ;
    Lilen = Numeric->Lilen ;
    Lip = Numeric->Lip ;
    deg = 0 ;

    /* ---------------------------------------------------------------------- */
    /* count the nonzeros in each row of L */
    /* ---------------------------------------------------------------------- */

    for (row = 0 ; row < n_inner ; row++)
    {
	/* include the diagonal entry in the row counts */
	Wi [row] = 1 ;
    }
    for (row = n_inner ; row < n_row ; row++)
    {
	Wi [row] = 0 ;
    }

    /* count the nonzero entries in L */
    for (k = 0 ; k < npiv ; k++)
    {

	/* ------------------------------------------------------------------ */
	/* make column of L in Pattern [0..deg-1] */
	/* ------------------------------------------------------------------ */

	lp = Lip [k] ;
	newLchain = (lp < 0) ;
	if (newLchain)
	{
	    lp = -lp ;
	    deg = 0 ;
	    DEBUG4 (("start of chain for column of L\n")) ;
	}

	/* remove pivot row */
	pos = Lpos [k] ;
	if (pos != EMPTY)
	{
	    DEBUG4 (("  k "ID" removing row "ID" at position "ID"\n",
	    k, Pattern [pos], pos)) ;
	    ASSERT (!newLchain) ;
	    ASSERT (deg > 0) ;
	    ASSERT (pos >= 0 && pos < deg) ;
	    ASSERT (Pattern [pos] == k) ;
	    Pattern [pos] = Pattern [--deg] ;
	}

	/* concatenate the pattern */
	ip = (Int *) (Numeric->Memory + lp) ;
	llen = Lilen [k] ;
	for (j = 0 ; j < llen ; j++)
	{
	    row = *ip++ ;
	    DEBUG4 (("  row "ID"  k "ID"\n", row, k)) ;
	    ASSERT (row > k && row < n_row) ;
	    Pattern [deg++] = row ;
	}

	xp = (Entry *) (Numeric->Memory + lp + UNITS (Int, llen)) ;

	for (j = 0 ; j < deg ; j++)
	{
	    DEBUG4 (("  row "ID"  k "ID" value", Pattern [j], k)) ;
	    row = Pattern [j] ;
	    value = *xp++ ;
	    EDEBUG4 (value) ;
	    DEBUG4 (("\n")) ;
	    if (IS_NONZERO (value))
	    {
		Wi [row]++ ;
	    }
	}
    }

    /* ---------------------------------------------------------------------- */
    /* construct the final row form of L */
    /* ---------------------------------------------------------------------- */

    /* create the row pointers */
    lnz2 = 0 ;
    for (row = 0 ; row < n_row ; row++)
    {
	Lp [row] = lnz2 ;
	lnz2 += Wi [row] ;
	Wi [row] = Lp [row] ;
    }
    Lp [n_row] = lnz2 ;
    ASSERT (Numeric->lnz + n_inner == lnz2) ;

    /* add entries from the rows of L */
    for (k = 0 ; k < npiv ; k++)
    {

	/* ------------------------------------------------------------------ */
	/* make column of L in Pattern [0..deg-1] */
	/* ------------------------------------------------------------------ */

	lp = Lip [k] ;
	newLchain = (lp < 0) ;
	if (newLchain)
	{
	    lp = -lp ;
	    deg = 0 ;
	    DEBUG4 (("start of chain for column of L\n")) ;
	}

	/* remove pivot row */
	pos = Lpos [k] ;
	if (pos != EMPTY)
	{
	    DEBUG4 (("  k "ID" removing row "ID" at position "ID"\n",
	    k, Pattern [pos], pos)) ;
	    ASSERT (!newLchain) ;
	    ASSERT (deg > 0) ;
	    ASSERT (pos >= 0 && pos < deg) ;
	    ASSERT (Pattern [pos] == k) ;
	    Pattern [pos] = Pattern [--deg] ;
	}

	/* concatenate the pattern */
	ip = (Int *) (Numeric->Memory + lp) ;
	llen = Lilen [k] ;
	for (j = 0 ; j < llen ; j++)
	{
	    row = *ip++ ;
	    DEBUG4 (("  row "ID"  k "ID"\n", row, k)) ;
	    ASSERT (row > k) ;
	    Pattern [deg++] = row ;
	}

	xp = (Entry *) (Numeric->Memory + lp + UNITS (Int, llen)) ;

	for (j = 0 ; j < deg ; j++)
	{
	    DEBUG4 (("  row "ID"  k "ID" value", Pattern [j], k)) ;
	    row = Pattern [j] ;
	    value = *xp++ ;
	    EDEBUG4 (value) ;
	    DEBUG4 (("\n")) ;
	    if (IS_NONZERO (value))
	    {
		p = Wi [row]++ ;
		Lj [p] = k ;
		Lx [p] = REAL_COMPONENT (value) ;
#ifdef COMPLEX
		Lz [p] = IMAG_COMPONENT (value) ;
#endif
	    }
	}
    }

    /* add all of the diagonal entries (L is unit diagonal) */
    for (row = 0 ; row < n_inner ; row++)
    {
	p = Wi [row]++ ;
	Lj [p] = row ;
	Lx [p] = 1. ;
#ifdef COMPLEX
	Lz [p] = 0. ;
#endif
	ASSERT (Wi [row] == Lp [row+1]) ;
    }

#ifndef NDEBUG
    DEBUG6 (("L matrix (stored by rows):")) ;
    UMF_dump_col_matrix (Lx,
#ifdef COMPLEX
	Lz,
#endif
	Lj, Lp, n_inner, n_row, Numeric->lnz+n_inner) ;
#endif

    DEBUG4 (("get_L done:\n")) ;
}


/* ========================================================================== */
/* === get_U ================================================================ */
/* ========================================================================== */

/*
    The matrix U is stored in the following arrays in the Numeric object:

	Int Upos [0..npiv]
	Int Uip [0..npiv], index into Numeric->Memory
	Int Uilen [0..npiv]
	Unit *(Numeric->Memory), pointer to memory space holding column indices
		and numerical values

    where npiv is the number of pivot entries found.  If A is n_row-by-n_col,
    then npiv <= MIN (n_row,n_col).

    Let U_k denote the pattern of entries in row k of U (excluding the
    diagonal).

    A Uchain is a sequence of columns of U whose nonzero patterns are related.
    The start of a Uchain is denoted by a negative value of Uip [k].

    To obtain U_k-1:

    (1) If row k is the start of a Uchain then Uip [k] is negative and |Uip [k]|
	is an index into Numeric->Memory for the integer column indices in
	U_k-1.  The number of entries in the row is |U_k-1| = Uilen [k].  This
	defines the pattern of the "trailing" row of this chain that ends at
	row k-1.


    (2) If row k is not the start of a Uchain, then U_k-1 is a subset of U_k.
	The indices in U_k are arranged so that last Uilen [k] entries of
	U_k are those indices not in U_k-1.  Next, the pivot column index k is
	added if it appears in row U_k-1 (it never appears in U_k).  Upos [k]
	holds the position of pivot column index k in the pattern U_k-1 (if it
	is present), so that the set union (U_k-1)+{k} can be computed quickly,
	when computing the pattern of U_k-1 from U_k.

	Note that this means we must have the pattern U_k to compute L_k-1.

    In both cases (1) and (2), we obtain the pattern U_k.

    The numerical values are stored in Numeric->Memory.  If k is the start of a
    Uchain, then the offset is |Uip [k]| plus the size of the space needed to
    store the pattern U_k-1.  Otherwise, Uip [k] is the offset itself of the
    numerical values, since in this case no pattern is stored.
    The numerical values are stored in the same order as the entries in U_k,
    after U_k is obtained from cases (1) or (2), above.

    The advantage of using this "packed" data structure is that it can
    dramatically reduce the amount of storage needed for the pattern of U.
    The disadvantage is that it can be difficult for the user to access,
    and it does not match the sparse matrix data structure used in MATLAB.
    Thus, this routine is provided to create a conventional sparse matrix
    data structure for U, in sparse-column form.

*/
/* ========================================================================== */

PRIVATE void get_U
(
    Int Up [ ],		/* of size n_col+1 */
    Int Ui [ ],		/* of size unz, where unz = Up [n_col] */
    double Ux [ ],	/* of size unz */
#ifdef COMPLEX
    double Uz [ ],	/* of size unz */
#endif
    NumericType *Numeric,
    Int Pattern [ ],	/* workspace of size n_col */
    Int Wi [ ]		/* workspace of size n_col */
)
{
    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    Int deg, j, *ip, col, *Upos, *Uilen, *Uip, n_col, ulen,
	unz2, p, k, up, newUchain, pos, npiv ;
    Entry *xp, *D, value ;

#ifndef NDEBUG
    Int nnzpiv = 0 ;
#endif

    /* ---------------------------------------------------------------------- */
    /* get parameters */
    /* ---------------------------------------------------------------------- */

    DEBUG4 (("get_U start:\n")) ;
    n_col = Numeric->n_col ;
    npiv = Numeric->npiv ;
    Upos = Numeric->Upos ;
    Uilen = Numeric->Uilen ;
    Uip = Numeric->Uip ;
    D = Numeric->D ;

    /* ---------------------------------------------------------------------- */
    /* count the nonzeros in each column of U */
    /* ---------------------------------------------------------------------- */

    for (col = 0 ; col < npiv ; col++)
    {
	/* include the diagonal entry in the column counts */
	DEBUG4 (("D ["ID"] = ", col)) ;
	EDEBUG4 (D [col]) ;
	Wi [col] = IS_NONZERO (D [col]) ;
	DEBUG4 ((" is nonzero: "ID"\n", Wi [col])) ;
#ifndef NDEBUG
	nnzpiv += IS_NONZERO (D [col]) ;
#endif
    }
    DEBUG4 (("nnzpiv "ID" "ID"\n", nnzpiv, Numeric->nnzpiv)) ;
    ASSERT (nnzpiv == Numeric->nnzpiv) ;
    for (col = npiv ; col < n_col ; col++)
    {
	/* diagonal entries are zero for structurally singular part */
	Wi [col] = 0 ;
    }

    deg = Numeric->ulen ;
    if (deg > 0)
    {
	/* make last pivot row of U (singular matrices only) */
	DEBUG0 (("Last pivot row of U: ulen "ID"\n", deg)) ;
	for (j = 0 ; j < deg ; j++)
	{
	    Pattern [j] = Numeric->Upattern [j] ;
	    DEBUG0 (("    column "ID"\n", Pattern [j])) ;
	}
    }

    for (k = npiv-1 ; k >= 0 ; k--)
    {

	/* ------------------------------------------------------------------ */
	/* use row k of U */
	/* ------------------------------------------------------------------ */

	up = Uip [k] ;
	ulen = Uilen [k] ;
	newUchain = (up < 0) ;
	if (newUchain)
	{
	    up = -up ;
	    xp = (Entry *) (Numeric->Memory + up + UNITS (Int, ulen)) ;
	}
	else
	{
	    xp = (Entry *) (Numeric->Memory + up) ;
	}

	for (j = 0 ; j < deg ; j++)
	{
	    DEBUG4 (("  k "ID" col "ID" value\n", k, Pattern [j])) ;
	    col = Pattern [j] ;
	    ASSERT (col >= 0 && col < n_col) ;
	    value = *xp++ ;
	    EDEBUG4 (value) ;
	    DEBUG4 (("\n")) ;
	    if (IS_NONZERO (value))
	    {
		Wi [col]++ ;
	    }
	}

	/* ------------------------------------------------------------------ */
	/* make row k-1 of U in Pattern [0..deg-1] */
	/* ------------------------------------------------------------------ */

	if (newUchain)
	{
	    /* next row is a new Uchain */
	    deg = ulen ;
	    DEBUG4 (("end of chain for row of U "ID" deg "ID"\n", k-1, deg)) ;
	    ip = (Int *) (Numeric->Memory + up) ;
	    for (j = 0 ; j < deg ; j++)
	    {
		col = *ip++ ;
		DEBUG4 (("  k "ID" col "ID"\n", k-1, col)) ;
		ASSERT (k <= col) ;
		Pattern [j] = col ;
	    }
	}
	else
	{
	    deg -= ulen ;
	    DEBUG4 (("middle of chain for row of U "ID" deg "ID"\n", k-1, deg));
	    ASSERT (deg >= 0) ;
	    pos = Upos [k] ;
	    if (pos != EMPTY)
	    {
		/* add the pivot column */
		DEBUG4 (("k "ID" add pivot entry at position "ID"\n", k, pos)) ;
		ASSERT (pos >= 0 && pos <= deg) ;
		Pattern [deg++] = Pattern [pos] ;
		Pattern [pos] = k ;
	    }
	}
    }

    /* ---------------------------------------------------------------------- */
    /* construct the final column form of U */
    /* ---------------------------------------------------------------------- */

    /* create the column pointers */
    unz2 = 0 ;
    for (col = 0 ; col < n_col ; col++)
    {
	Up [col] = unz2 ;
	unz2 += Wi [col] ;
    }
    Up [n_col] = unz2 ;
    DEBUG1 (("Numeric->unz "ID"  npiv "ID" nnzpiv "ID" unz2 "ID"\n",
	Numeric->unz, npiv, Numeric->nnzpiv, unz2)) ;
    ASSERT (Numeric->unz + Numeric->nnzpiv == unz2) ;

    for (col = 0 ; col < n_col ; col++)
    {
	Wi [col] = Up [col+1] ;
    }

    /* add all of the diagonal entries */
    for (col = 0 ; col < npiv ; col++)
    {
	if (IS_NONZERO (D [col]))
	{
	    p = --(Wi [col]) ;
	    Ui [p] = col ;
	    Ux [p] = REAL_COMPONENT (D [col]) ;
#ifdef COMPLEX
	    Uz [p] = IMAG_COMPONENT (D [col]) ;
#endif
	}
    }

    /* add all the entries from the rows of U */

    deg = Numeric->ulen ;
    if (deg > 0)
    {
	/* make last pivot row of U (singular matrices only) */
	for (j = 0 ; j < deg ; j++)
	{
	    Pattern [j] = Numeric->Upattern [j] ;
	}
    }

    for (k = npiv-1 ; k >= 0 ; k--)
    {

	/* ------------------------------------------------------------------ */
	/* use row k of U */
	/* ------------------------------------------------------------------ */

	up = Uip [k] ;
	ulen = Uilen [k] ;
	newUchain = (up < 0) ;
	if (newUchain)
	{
	    up = -up ;
	    xp = (Entry *) (Numeric->Memory + up + UNITS (Int, ulen)) ;
	}
	else
	{
	    xp = (Entry *) (Numeric->Memory + up) ;
	}

	xp += deg ;
	for (j = deg-1 ; j >= 0 ; j--)
	{
	    DEBUG4 (("  k "ID" col "ID" value", k, Pattern [j])) ;
	    col = Pattern [j] ;
	    ASSERT (col >= 0 && col < n_col) ;
	    value = *(--xp) ;
	    EDEBUG4 (value) ;
	    DEBUG4 (("\n")) ;
	    if (IS_NONZERO (value))
	    {
		p = --(Wi [col]) ;
		Ui [p] = k ;
		Ux [p] = REAL_COMPONENT (value) ;
#ifdef COMPLEX
		Uz [p] = IMAG_COMPONENT (value) ;
#endif
	    }
	}

	/* ------------------------------------------------------------------ */
	/* make row k-1 of U in Pattern [0..deg-1] */
	/* ------------------------------------------------------------------ */

	if (newUchain)
	{
	    /* next row is a new Uchain */
	    deg = ulen ;
	    DEBUG4 (("end of chain for row of U "ID" deg "ID"\n", k-1, deg)) ;
	    ip = (Int *) (Numeric->Memory + up) ;
	    for (j = 0 ; j < deg ; j++)
	    {
		col = *ip++ ;
		DEBUG4 (("  k "ID" col "ID"\n", k-1, col)) ;
		ASSERT (k <= col) ;
		Pattern [j] = col ;
	    }
	}
	else
	{
	    deg -= ulen ;
	    DEBUG4 (("middle of chain for row of U "ID" deg "ID"\n", k-1, deg));
	    ASSERT (deg >= 0) ;
	    pos = Upos [k] ;
	    if (pos != EMPTY)
	    {
		/* add the pivot column */
		DEBUG4 (("k "ID" add pivot entry at position "ID"\n", k, pos)) ;
		ASSERT (pos >= 0 && pos <= deg) ;
		Pattern [deg++] = Pattern [pos] ;
		Pattern [pos] = k ;
	    }
	}
    }

#ifndef NDEBUG
    DEBUG6 (("U matrix:")) ;
    UMF_dump_col_matrix (Ux,
#ifdef COMPLEX
	Uz,
#endif
	Ui, Up, Numeric->n_row, n_col, Numeric->unz + Numeric->nnzpiv) ;
#endif

}


```

sparse-matrix/umfpack/umf_kernel_wrapup.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL void UMF_kernel_wrapup
(
    NumericType *Numeric,
    SymbolicType *Symbolic,
    WorkType *Work
) ;


```

sparse-matrix/umfpack/wrap_umf_utsolve.c:
```
#define	CONJUGATE_SOLVE
#include "umf_utsolve.c"

```

sparse-matrix/umfpack/umf_version.h:
```
/* ========================================================================== */
/* === umf_version ========================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
   Define routine names and integer type, depending on version being compiled.

   DINT:	double precision, int's as integers
   DLONG:	double precision, long's as integers

   ZLONG:	complex double precision, long's as integers
   ZINT:	complex double precision, int's as integers
*/

/* Set DINT as the default, if nothing is defined */
#if !defined (DLONG) && !defined (DINT) && !defined (ZLONG) && !defined (ZINT)
#define DINT
#endif

/* Determine if this is a real or complex version */
#if defined (ZLONG) || defined (ZINT)
#define COMPLEX
#endif

/* -------------------------------------------------------------------------- */
/* integer type */
/* -------------------------------------------------------------------------- */

#if defined (DLONG) || defined (ZLONG)

#define LONG_INTEGER
#define Int long
#define ID "%ld"
#define Int_MAX LONG_MAX

#else

#define Int int
#define ID "%d"
#define Int_MAX INT_MAX

#endif

/* -------------------------------------------------------------------------- */
/* Numerical relop macros for correctly handling the NaN case */
/* -------------------------------------------------------------------------- */

/*
SCALAR_IS_NAN(x):
    True if x is NaN.  False otherwise.  The commonly-existing isnan(x)
    function could be used, but it's not in Kernighan & Ritchie 2nd edition
    (ANSI C).  It may appear in <math.h>, but I'm not certain about
    portability.  The expression x != x is true if and only if x is NaN,
    according to the IEEE 754 floating-point standard.

SCALAR_IS_ZERO(x):
    True if x is zero.  False if x is nonzero, NaN, or +/- Inf.
    This is (x == 0) if the compiler is IEEE 754 compliant.

SCALAR_IS_NONZERO(x):
    True if x is nonzero, NaN, or +/- Inf.  False if x zero.
    This is (x != 0) if the compiler is IEEE 754 compliant.

SCALAR_IS_LTZERO(x):
    True if x is < zero or -Inf.  False if x is >= 0, NaN, or +Inf.
    This is (x < 0) if the compiler is IEEE 754 compliant.
*/

#if defined (MATHWORKS)

/* The MathWorks has their own macros in util.h that handle NaN's properly. */
#define SCALAR_IS_NAN(x)	(utIsNaN (x))
#define SCALAR_IS_ZERO(x)	(utEQZero (x))
#define SCALAR_IS_NONZERO(x)	(utNEZero (x))
#define SCALAR_IS_LTZERO(x)	(utLTZero (x))

#elif defined (UMF_WINDOWS)

/* Yes, this is exceedingly ugly.  Blame Microsoft, which hopelessly */
/* violates the IEEE 754 floating-point standard in a bizarre way. */
/* If you're using an IEEE 754-compliant compiler, then x != x is true */
/* iff x is NaN.  For Microsoft, (x < x) is true iff x is NaN. */
/* So either way, this macro safely detects a NaN. */
#define SCALAR_IS_NAN(x)	(((x) != (x)) || (((x) < (x))))
#define SCALAR_IS_ZERO(x)	(((x) == 0.) && !SCALAR_IS_NAN(x))
#define SCALAR_IS_NONZERO(x)	(((x) != 0.) || SCALAR_IS_NAN(x))
#define SCALAR_IS_LTZERO(x)	(((x) < 0.) && !SCALAR_IS_NAN(x))

#else

/* These all work properly, according to the IEEE 754 standard ... except on */
/* a PC with windows.  Works fine in Linux on the same PC... */
#define SCALAR_IS_NAN(x)	((x) != (x))
#define SCALAR_IS_ZERO(x)	((x) == 0.)
#define SCALAR_IS_NONZERO(x)	((x) != 0.)
#define SCALAR_IS_LTZERO(x)	((x) < 0.)

#endif

/* scalar absolute value macro. If x is NaN, the result is NaN: */
#define SCALAR_ABS(x) ((SCALAR_IS_LTZERO (x)) ? -(x) : (x))

/* true if an integer (stored in double x) would overflow (or if x is NaN) */
#define INT_OVERFLOW(x) ((!((x) * (1.0 + MAX_EPSILON) <= Int_MAX)) || SCALAR_IS_NAN (x))


/* -------------------------------------------------------------------------- */
/* Real floating-point arithmetic */
/* -------------------------------------------------------------------------- */

#ifndef COMPLEX

#define Entry double

#define REAL_COMPONENT(c) 	(c)
#define IMAG_COMPONENT(c)	(0.)
#define ASSIGN(c,s1,s2)		{ (c) = (s1) ; }
#define CLEAR(c)		{ (c) = 0. ; }
#define IS_ZERO(a)		SCALAR_IS_ZERO (a)
#define IS_NONZERO(a)		SCALAR_IS_NONZERO (a)
#define ASSEMBLE(c,a)		{ (c) += (a) ; }
#define DECREMENT(c,a)		{ (c) -= (a) ; }
#define MULT(c,a,b)		{ (c) = (a) * (b) ; }
#define MULT_CONJ(c,a,b)	{ (c) = (a) * (b) ; }
#define MULT_SUB(c,a,b)		{ (c) -= (a) * (b) ; }
#define MULT_SUB_CONJ(c,a,b)	{ (c) -= (a) * (b) ; }
#define DIV(c,a,b)		{ (c) = (a) / (b) ; }
#define DIV_CONJ(c,a,b)		{ (c) = (a) / (b) ; }
#define APPROX_ABS(s,a)		{ (s) = SCALAR_ABS (a) ; }
#define ABS(s,a)		{ (s) = SCALAR_ABS (a) ; }

/* print an entry (avoid printing "-0" for negative zero).  */
#define PRINT_ENTRY(a) \
{ \
    if (SCALAR_IS_NONZERO (a)) \
    { \
	PRINTF ((" (%g)", (a))) ; \
    } \
    else \
    { \
	PRINTF ((" (0)")) ; \
    } \
}

/* for flop counts */
#define MULTSUB_FLOPS	2.	/* c -= a*b */
#define DIV_FLOPS	1.	/* c = a/b */
#define ABS_FLOPS	0.	/* c = abs (a) */
#define ASSEMBLE_FLOPS	1.	/* c += a */
#define DECREMENT_FLOPS	1.	/* c -= a */
#define MULT_FLOPS	1.	/* c = a*b */

#else

/* -------------------------------------------------------------------------- */
/* Complex floating-point arithmetic */
/* -------------------------------------------------------------------------- */

/*
    Note:  An alternative to this DoubleComplex type would be to use a
    struct { double r ; double i ; }.  The problem with that method
    (used by the Sun Performance Library, for example) is that ANSI C provides
    no guarantee about the layout of a struct.  It is possible that the sizeof
    the struct above would be greater than 2 * sizeof (double).  This would
    mean that the complex BLAS could not be used.  The method used here avoids
    that possibility.  ANSI C *does* guarantee that an array of structs has
    the same size as n times the size of one struct.

    The ANSI C99 version of the C language includes a "double _Complex" type.
    It should be possible in that case to do the following:

    #define Entry double _Complex

    and remove the DoubleComplex struct.  The macros, below, could then be
    replaced with instrinsic operators.  Note that the #define Real and
    #define Imag should also be removed (they only appear in this file).

    For the MULT, MULT_SUB, MULT_SUB_CONJ, DIV, and DIV_CONJ macros,
    the output argument c cannot be the same as any input argument.

*/

typedef struct
{
    double component [2] ;	/* real and imaginary parts */

} DoubleComplex ;

#define Entry DoubleComplex
#define Real component [0]
#define Imag component [1]

/* for flop counts */
#define MULTSUB_FLOPS	8.	/* c -= a*b */
#define DIV_FLOPS	9.	/* c = a/b */
#define ABS_FLOPS	6.	/* c = abs (a), count sqrt as one flop */
#define ASSEMBLE_FLOPS	2.	/* c += a */
#define DECREMENT_FLOPS	2.	/* c -= a */
#define MULT_FLOPS	6.	/* c = a*b */

/* -------------------------------------------------------------------------- */

/* real part of c */
#define REAL_COMPONENT(c) ((c).Real)

/* -------------------------------------------------------------------------- */

/* imag part of c */
#define IMAG_COMPONENT(c) ((c).Imag)

/* -------------------------------------------------------------------------- */

/* c = (s1) + (s2)i */
#define ASSIGN(c,s1,s2) \
{ \
    (c).Real = (s1) ; \
    (c).Imag = (s2) ; \
}

/* -------------------------------------------------------------------------- */

/* c = 0 */
#define CLEAR(c) \
{ \
    (c).Real = 0. ; \
    (c).Imag = 0. ; \
}

/* -------------------------------------------------------------------------- */

/* True if a == 0 */
#define IS_ZERO(a) \
    (SCALAR_IS_ZERO ((a).Real) && SCALAR_IS_ZERO ((a).Imag))

/* -------------------------------------------------------------------------- */

/* True if a != 0 */
#define IS_NONZERO(a) \
    (SCALAR_IS_NONZERO ((a).Real) || SCALAR_IS_NONZERO ((a).Imag))

/* -------------------------------------------------------------------------- */

/* c += a */
#define ASSEMBLE(c,a) \
{ \
    (c).Real += (a).Real ; \
    (c).Imag += (a).Imag ; \
}

/* -------------------------------------------------------------------------- */

/* c -= a */
#define DECREMENT(c,a) \
{ \
    (c).Real -= (a).Real ; \
    (c).Imag -= (a).Imag ; \
}

/* -------------------------------------------------------------------------- */

/* c = a*b, assert because c cannot be the same as a or b */
#define MULT(c,a,b) \
{ \
    ASSERT (&(c) != &(a) && &(c) != &(b)) ; \
    (c).Real = (a).Real * (b).Real - (a).Imag * (b).Imag ; \
    (c).Imag = (a).Imag * (b).Real + (a).Real * (b).Imag ; \
}

/* -------------------------------------------------------------------------- */

/* c = a*conjugate(b), assert because c cannot be the same as a or b */
#define MULT_CONJ(c,a,b) \
{ \
    ASSERT (&(c) != &(a) && &(c) != &(b)) ; \
    (c).Real = (a).Real * (b).Real + (a).Imag * (b).Imag ; \
    (c).Imag = (a).Imag * (b).Real - (a).Real * (b).Imag ; \
}

/* -------------------------------------------------------------------------- */

/* c -= a*b, assert because c cannot be the same as a or b */
#define MULT_SUB(c,a,b) \
{ \
    ASSERT (&(c) != &(a) && &(c) != &(b)) ; \
    (c).Real -= (a).Real * (b).Real - (a).Imag * (b).Imag ; \
    (c).Imag -= (a).Imag * (b).Real + (a).Real * (b).Imag ; \
}

/* -------------------------------------------------------------------------- */

/* c -= a*conjugate(b), assert because c cannot be the same as a or b */
#define MULT_SUB_CONJ(c,a,b) \
{ \
    ASSERT (&(c) != &(a) && &(c) != &(b)) ; \
    (c).Real -= (a).Real * (b).Real + (a).Imag * (b).Imag ; \
    (c).Imag -= (a).Imag * (b).Real - (a).Real * (b).Imag ; \
}

/* -------------------------------------------------------------------------- */

/* c = a/b, be careful to avoid underflow and overflow */
#ifdef MATHWORKS
#define DIV(c,a,b) \
{ \
    (void) utDivideComplex ((a).Real, (a).Imag, (b).Real, (b).Imag, \
	&((c).Real), &((c).Imag)) ; \
}
#else
/* This uses ACM Algo 116, by R. L. Smith, 1962. */
/* c cannot be the same variable as a or b. */
/* Ignore NaN case for double relop br>=bi. */
#define DIV(c,a,b) \
{ \
    double r, den, br, bi ; \
    ASSERT (&(c) != &(a) && &(c) != &(b)) ; \
    br = SCALAR_ABS ((b).Real) ; \
    bi = SCALAR_ABS ((b).Imag) ; \
    if (br >= bi) \
    { \
        r = (b).Imag / (b).Real ; \
        den = (b).Real + r * (b).Imag ; \
        (c).Real = ((a).Real + (a).Imag * r) / den ; \
        (c).Imag = ((a).Imag - (a).Real * r) / den ; \
    } \
    else \
    { \
        r = (b).Real / (b).Imag ; \
        den = r * (b).Real + (b).Imag ; \
        (c).Real = ((a).Real * r + (a).Imag) / den ; \
        (c).Imag = ((a).Imag * r - (a).Real) / den ; \
    } \
}
#endif

/* -------------------------------------------------------------------------- */

/* c = a/conjugate(b), be careful to avoid underflow and overflow */
#ifdef MATHWORKS
#define DIV_CONJ(c,a,b) \
{ \
    (void) utDivideComplex ((a).Real, (a).Imag, (b).Real, (-(b).Imag), \
	&((c).Real), &((c).Imag)) ; \
}
#else
/* This uses ACM Algo 116, by R. L. Smith, 1962. */
/* c cannot be the same variable as a or b. */
/* Ignore NaN case for double relop br>=bi. */
#define DIV_CONJ(c,a,b) \
{ \
    double r, den, br, bi ; \
    ASSERT (&(c) != &(a) && &(c) != &(b)) ; \
    br = SCALAR_ABS ((b).Real) ; \
    bi = SCALAR_ABS ((b).Imag) ; \
    if (br >= bi) \
    { \
        r = (-(b).Imag) / (b).Real ; \
        den = (b).Real - r * (b).Imag ; \
        (c).Real = ((a).Real + (a).Imag * r) / den ; \
        (c).Imag = ((a).Imag - (a).Real * r) / den ; \
    } \
    else \
    { \
        r = (b).Real / (-(b).Imag) ; \
        den =  r * (b).Real - (b).Imag; \
        (c).Real = ((a).Real * r + (a).Imag) / den ; \
        (c).Imag = ((a).Imag * r - (a).Real) / den ; \
    } \
}
#endif

/* -------------------------------------------------------------------------- */

/* approximate absolute value, s = |r|+|i| */
#define APPROX_ABS(s,a) \
{ \
    (s) = SCALAR_ABS ((a).Real) + SCALAR_ABS ((a).Imag) ; \
}

/* -------------------------------------------------------------------------- */

/* exact absolute value, s = sqrt (a.real^2 + amag^2) */
#ifdef MATHWORKS
#define ABS(s,a) \
{ \
    (s) = utFdlibm_hypot ((a).Real, (a).Imag) ; \
}
#else
/* Ignore NaN case for the double relops ar>=ai and ar+ai==ar. */
#define ABS(s,a) \
{ \
    double r, ar, ai ; \
    ar = SCALAR_ABS ((a).Real) ; \
    ai = SCALAR_ABS ((a).Imag) ; \
    if (ar >= ai) \
    { \
	if (ar + ai == ar) \
	{ \
	    (s) = ar ; \
	} \
	else \
	{ \
	    r = ai / ar ; \
	    (s) = ar * sqrt (1.0 + r*r) ; \
	} \
    } \
    else \
    { \
	if (ai + ar == ai) \
	{ \
	    (s) = ai ; \
	} \
	else \
	{ \
	    r = ar / ai ; \
	    (s) = ai * sqrt (1.0 + r*r) ; \
	} \
    } \
}
#endif

/* -------------------------------------------------------------------------- */

/* print an entry (avoid printing "-0" for negative zero).  */
#define PRINT_ENTRY(a) \
{ \
    if (SCALAR_IS_NONZERO ((a).Real)) \
    { \
	PRINTF ((" (%g", (a).Real)) ; \
    } \
    else \
    { \
	PRINTF ((" (0")) ; \
    } \
    if (SCALAR_IS_LTZERO ((a).Imag)) \
    { \
	PRINTF ((" - %gi)", -(a).Imag)) ; \
    } \
    else if (SCALAR_IS_ZERO ((a).Imag)) \
    { \
	PRINTF ((" + 0i)")) ; \
    } \
    else \
    { \
	PRINTF ((" + %gi)", (a).Imag)) ; \
    } \
}

/* -------------------------------------------------------------------------- */

#endif	/* #ifndef COMPLEX */

/* -------------------------------------------------------------------------- */
/* Double precision, with int's as integers */
/* -------------------------------------------------------------------------- */

#ifdef DINT

#define UMF_analyze		 umfdi_analyze
#define UMF_apply_order		 umfdi_apply_order
#define UMF_assemble		 umfdi_assemble
#define UMF_blas3_update	 umfdi_blas3_update
#define UMF_build_tuples	 umfdi_build_tuples
#define UMF_build_tuples_usage	 umfdi_build_tuples_usage
#define UMF_colamd		 umfdi_colamd
#define UMF_colamd_set_defaults	 umfdi_colamd_set_defaults
#define UMF_create_element	 umfdi_create_element
#define UMF_extend_front	 umfdi_extend_front
#define UMF_free		 umfdi_free
#define UMF_garbage_collection	 umfdi_garbage_collection
#define UMF_get_memory		 umfdi_get_memory
#define UMF_init_front		 umfdi_init_front
#define UMF_is_permutation	 umfdi_is_permutation
#define UMF_kernel		 umfdi_kernel
#define UMF_kernel_init		 umfdi_kernel_init
#define UMF_kernel_init_usage	 umfdi_kernel_init_usage
#define UMF_kernel_wrapup	 umfdi_kernel_wrapup
#define UMF_local_search	 umfdi_local_search
#define UMF_lsolve		 umfdi_lsolve
#define UMF_ltsolve		 umfdi_ltsolve
#define UMF_lhsolve		 umfdi_lhsolve
#define UMF_malloc		 umfdi_malloc
#define UMF_mem_alloc_element	 umfdi_mem_alloc_element
#define UMF_mem_alloc_head_block umfdi_mem_alloc_head_block
#define UMF_mem_alloc_tail_block umfdi_mem_alloc_tail_block
#define UMF_mem_free_tail_block	 umfdi_mem_free_tail_block
#define UMF_mem_init_memoryspace umfdi_mem_init_memoryspace
#define UMF_order_front_tree	 umfdi_order_front_tree
#define UMF_realloc		 umfdi_realloc
#define UMF_report_perm		 umfdi_report_perm
#define UMF_report_vector	 umfdi_report_vector
#define UMF_row_search		 umfdi_row_search
#define UMF_scale_column	 umfdi_scale_column
#define UMF_set_stats		 umfdi_set_stats
#define UMF_solve		 umfdi_solve
#define UMF_symbolic_usage	 umfdi_symbolic_usage
#define UMF_transpose		 umfdi_transpose
#define UMF_tuple_lengths	 umfdi_tuple_lengths
#define UMF_usolve		 umfdi_usolve
#define UMF_utsolve		 umfdi_utsolve
#define UMF_uhsolve		 umfdi_uhsolve
#define UMF_valid_numeric	 umfdi_valid_numeric
#define UMF_valid_symbolic	 umfdi_valid_symbolic
#define UMF_triplet_map_x	 umfdi_triplet_map_x
#define UMF_triplet_map_nox	 umfdi_triplet_map_nox
#define UMF_triplet_nomap_x	 umfdi_triplet_nomap_x
#define UMF_triplet_nomap_nox	 umfdi_triplet_nomap_nox
#define UMFPACK_col_to_triplet	 umfpack_di_col_to_triplet
#define UMFPACK_defaults	 umfpack_di_defaults
#define UMFPACK_free_numeric	 umfpack_di_free_numeric
#define UMFPACK_free_symbolic	 umfpack_di_free_symbolic
#define UMFPACK_get_lunz	 umfpack_di_get_lunz
#define UMFPACK_get_numeric	 umfpack_di_get_numeric
#define UMFPACK_get_symbolic	 umfpack_di_get_symbolic
#define UMFPACK_numeric		 umfpack_di_numeric
#define UMFPACK_qsymbolic	 umfpack_di_qsymbolic
#define UMFPACK_report_control	 umfpack_di_report_control
#define UMFPACK_report_info	 umfpack_di_report_info
#define UMFPACK_report_matrix	 umfpack_di_report_matrix
#define UMFPACK_report_numeric	 umfpack_di_report_numeric
#define UMFPACK_report_perm	 umfpack_di_report_perm
#define UMFPACK_report_status	 umfpack_di_report_status
#define UMFPACK_report_symbolic	 umfpack_di_report_symbolic
#define UMFPACK_report_triplet	 umfpack_di_report_triplet
#define UMFPACK_report_vector	 umfpack_di_report_vector
#define UMFPACK_solve		 umfpack_di_solve
#define UMFPACK_symbolic	 umfpack_di_symbolic
#define UMFPACK_transpose	 umfpack_di_transpose
#define UMFPACK_triplet_to_col	 umfpack_di_triplet_to_col
#define UMFPACK_wsolve		 umfpack_di_wsolve

/* for debugging only: */
#define UMF_malloc_count	 umfdi_malloc_count
#define UMF_debug		 umfdi_debug
#define UMF_nbug		 umfdi_nbug
#define UMF_fbug		 umfdi_fbug
#define UMF_allocfail		 umfdi_allocfail
#define UMF_gprob		 umfdi_gprob
#define UMF_dump_dense		 umfdi_dump_dense
#define UMF_dump_element	 umfdi_dump_element
#define UMF_dump_rowcol		 umfdi_dump_rowcol
#define UMF_dump_matrix		 umfdi_dump_matrix
#define UMF_dump_current_front	 umfdi_dump_current_front
#define UMF_dump_lu		 umfdi_dump_lu
#define UMF_dump_memory		 umfdi_dump_memory
#define UMF_dump_packed_memory	 umfdi_dump_packed_memory
#define UMF_dump_col_matrix	 umfdi_dump_col_matrix
#define UMF_dump_chain		 umfdi_dump_chain
#define UMF_dump_start		 umfdi_dump_start
#define UMF_dump_rowmerge	 umfdi_dump_rowmerge

#endif


/* -------------------------------------------------------------------------- */
/* Double precision, with long's as integers */
/* -------------------------------------------------------------------------- */

#ifdef DLONG

#define UMF_analyze		 umfdl_analyze
#define UMF_apply_order		 umfdl_apply_order
#define UMF_assemble		 umfdl_assemble
#define UMF_blas3_update	 umfdl_blas3_update
#define UMF_build_tuples	 umfdl_build_tuples
#define UMF_build_tuples_usage	 umfdl_build_tuples_usage
#define UMF_colamd		 umfdl_colamd
#define UMF_colamd_set_defaults	 umfdl_colamd_set_defaults
#define UMF_create_element	 umfdl_create_element
#define UMF_extend_front	 umfdl_extend_front
#define UMF_free		 umfdl_free
#define UMF_garbage_collection	 umfdl_garbage_collection
#define UMF_get_memory		 umfdl_get_memory
#define UMF_init_front		 umfdl_init_front
#define UMF_is_permutation	 umfdl_is_permutation
#define UMF_kernel		 umfdl_kernel
#define UMF_kernel_init		 umfdl_kernel_init
#define UMF_kernel_init_usage	 umfdl_kernel_init_usage
#define UMF_kernel_wrapup	 umfdl_kernel_wrapup
#define UMF_local_search	 umfdl_local_search
#define UMF_lsolve		 umfdl_lsolve
#define UMF_ltsolve		 umfdl_ltsolve
#define UMF_lhsolve		 umfdl_lhsolve
#define UMF_malloc		 umfdl_malloc
#define UMF_mem_alloc_element	 umfdl_mem_alloc_element
#define UMF_mem_alloc_head_block umfdl_mem_alloc_head_block
#define UMF_mem_alloc_tail_block umfdl_mem_alloc_tail_block
#define UMF_mem_free_tail_block	 umfdl_mem_free_tail_block
#define UMF_mem_init_memoryspace umfdl_mem_init_memoryspace
#define UMF_order_front_tree	 umfdl_order_front_tree
#define UMF_realloc		 umfdl_realloc
#define UMF_report_perm		 umfdl_report_perm
#define UMF_report_vector	 umfdl_report_vector
#define UMF_row_search		 umfdl_row_search
#define UMF_scale_column	 umfdl_scale_column
#define UMF_set_stats		 umfdl_set_stats
#define UMF_solve		 umfdl_solve
#define UMF_symbolic_usage	 umfdl_symbolic_usage
#define UMF_transpose		 umfdl_transpose
#define UMF_tuple_lengths	 umfdl_tuple_lengths
#define UMF_usolve		 umfdl_usolve
#define UMF_utsolve		 umfdl_utsolve
#define UMF_uhsolve		 umfdl_uhsolve
#define UMF_valid_numeric	 umfdl_valid_numeric
#define UMF_valid_symbolic	 umfdl_valid_symbolic
#define UMF_triplet_map_x	 umfdl_triplet_map_x
#define UMF_triplet_map_nox	 umfdl_triplet_map_nox
#define UMF_triplet_nomap_x	 umfdl_triplet_nomap_x
#define UMF_triplet_nomap_nox	 umfdl_triplet_nomap_nox
#define UMFPACK_col_to_triplet	 umfpack_dl_col_to_triplet
#define UMFPACK_defaults	 umfpack_dl_defaults
#define UMFPACK_free_numeric	 umfpack_dl_free_numeric
#define UMFPACK_free_symbolic	 umfpack_dl_free_symbolic
#define UMFPACK_get_lunz	 umfpack_dl_get_lunz
#define UMFPACK_get_numeric	 umfpack_dl_get_numeric
#define UMFPACK_get_symbolic	 umfpack_dl_get_symbolic
#define UMFPACK_numeric		 umfpack_dl_numeric
#define UMFPACK_qsymbolic	 umfpack_dl_qsymbolic
#define UMFPACK_report_control	 umfpack_dl_report_control
#define UMFPACK_report_info	 umfpack_dl_report_info
#define UMFPACK_report_matrix	 umfpack_dl_report_matrix
#define UMFPACK_report_numeric	 umfpack_dl_report_numeric
#define UMFPACK_report_perm	 umfpack_dl_report_perm
#define UMFPACK_report_status	 umfpack_dl_report_status
#define UMFPACK_report_symbolic	 umfpack_dl_report_symbolic
#define UMFPACK_report_triplet	 umfpack_dl_report_triplet
#define UMFPACK_report_vector	 umfpack_dl_report_vector
#define UMFPACK_solve		 umfpack_dl_solve
#define UMFPACK_symbolic	 umfpack_dl_symbolic
#define UMFPACK_transpose	 umfpack_dl_transpose
#define UMFPACK_triplet_to_col	 umfpack_dl_triplet_to_col
#define UMFPACK_wsolve		 umfpack_dl_wsolve

/* for debugging only: */
#define UMF_malloc_count	 umfdl_malloc_count
#define UMF_debug		 umfdl_debug
#define UMF_nbug		 umfdl_nbug
#define UMF_fbug		 umfdl_fbug
#define UMF_allocfail		 umfdl_allocfail
#define UMF_gprob		 umfdl_gprob
#define UMF_dump_dense		 umfdl_dump_dense
#define UMF_dump_element	 umfdl_dump_element
#define UMF_dump_rowcol		 umfdl_dump_rowcol
#define UMF_dump_matrix		 umfdl_dump_matrix
#define UMF_dump_current_front	 umfdl_dump_current_front
#define UMF_dump_lu		 umfdl_dump_lu
#define UMF_dump_memory		 umfdl_dump_memory
#define UMF_dump_packed_memory	 umfdl_dump_packed_memory
#define UMF_dump_col_matrix	 umfdl_dump_col_matrix
#define UMF_dump_chain		 umfdl_dump_chain
#define UMF_dump_start		 umfdl_dump_start
#define UMF_dump_rowmerge	 umfdl_dump_rowmerge

#endif

/* -------------------------------------------------------------------------- */
/* Complex double precision, with int's as integers */
/* -------------------------------------------------------------------------- */

#ifdef ZINT

#define UMF_analyze		 umfzi_analyze
#define UMF_apply_order		 umfzi_apply_order
#define UMF_assemble		 umfzi_assemble
#define UMF_blas3_update	 umfzi_blas3_update
#define UMF_build_tuples	 umfzi_build_tuples
#define UMF_build_tuples_usage	 umfzi_build_tuples_usage
#define UMF_colamd		 umfzi_colamd
#define UMF_colamd_set_defaults	 umfzi_colamd_set_defaults
#define UMF_create_element	 umfzi_create_element
#define UMF_extend_front	 umfzi_extend_front
#define UMF_free		 umfzi_free
#define UMF_garbage_collection	 umfzi_garbage_collection
#define UMF_get_memory		 umfzi_get_memory
#define UMF_init_front		 umfzi_init_front
#define UMF_is_permutation	 umfzi_is_permutation
#define UMF_kernel		 umfzi_kernel
#define UMF_kernel_init		 umfzi_kernel_init
#define UMF_kernel_init_usage	 umfzi_kernel_init_usage
#define UMF_kernel_wrapup	 umfzi_kernel_wrapup
#define UMF_local_search	 umfzi_local_search
#define UMF_lsolve		 umfzi_lsolve
#define UMF_ltsolve		 umfzi_ltsolve
#define UMF_lhsolve		 umfzi_lhsolve
#define UMF_malloc		 umfzi_malloc
#define UMF_mem_alloc_element	 umfzi_mem_alloc_element
#define UMF_mem_alloc_head_block umfzi_mem_alloc_head_block
#define UMF_mem_alloc_tail_block umfzi_mem_alloc_tail_block
#define UMF_mem_free_tail_block	 umfzi_mem_free_tail_block
#define UMF_mem_init_memoryspace umfzi_mem_init_memoryspace
#define UMF_order_front_tree	 umfzi_order_front_tree
#define UMF_realloc		 umfzi_realloc
#define UMF_report_perm		 umfzi_report_perm
#define UMF_report_vector	 umfzi_report_vector
#define UMF_row_search		 umfzi_row_search
#define UMF_scale_column	 umfzi_scale_column
#define UMF_set_stats		 umfzi_set_stats
#define UMF_solve		 umfzi_solve
#define UMF_symbolic_usage	 umfzi_symbolic_usage
#define UMF_transpose		 umfzi_transpose
#define UMF_tuple_lengths	 umfzi_tuple_lengths
#define UMF_usolve		 umfzi_usolve
#define UMF_utsolve		 umfzi_utsolve
#define UMF_uhsolve		 umfzi_uhsolve
#define UMF_valid_numeric	 umfzi_valid_numeric
#define UMF_valid_symbolic	 umfzi_valid_symbolic
#define UMF_triplet_map_x	 umfzi_triplet_map_x
#define UMF_triplet_map_nox	 umfzi_triplet_map_nox
#define UMF_triplet_nomap_x	 umfzi_triplet_nomap_x
#define UMF_triplet_nomap_nox	 umfzi_triplet_nomap_nox
#define UMFPACK_col_to_triplet	 umfpack_zi_col_to_triplet
#define UMFPACK_defaults	 umfpack_zi_defaults
#define UMFPACK_free_numeric	 umfpack_zi_free_numeric
#define UMFPACK_free_symbolic	 umfpack_zi_free_symbolic
#define UMFPACK_get_lunz	 umfpack_zi_get_lunz
#define UMFPACK_get_numeric	 umfpack_zi_get_numeric
#define UMFPACK_get_symbolic	 umfpack_zi_get_symbolic
#define UMFPACK_numeric		 umfpack_zi_numeric
#define UMFPACK_qsymbolic	 umfpack_zi_qsymbolic
#define UMFPACK_report_control	 umfpack_zi_report_control
#define UMFPACK_report_info	 umfpack_zi_report_info
#define UMFPACK_report_matrix	 umfpack_zi_report_matrix
#define UMFPACK_report_numeric	 umfpack_zi_report_numeric
#define UMFPACK_report_perm	 umfpack_zi_report_perm
#define UMFPACK_report_status	 umfpack_zi_report_status
#define UMFPACK_report_symbolic	 umfpack_zi_report_symbolic
#define UMFPACK_report_triplet	 umfpack_zi_report_triplet
#define UMFPACK_report_vector	 umfpack_zi_report_vector
#define UMFPACK_solve		 umfpack_zi_solve
#define UMFPACK_symbolic	 umfpack_zi_symbolic
#define UMFPACK_transpose	 umfpack_zi_transpose
#define UMFPACK_triplet_to_col	 umfpack_zi_triplet_to_col
#define UMFPACK_wsolve		 umfpack_zi_wsolve

/* for debugging only: */
#define UMF_malloc_count	 umfzi_malloc_count
#define UMF_debug		 umfzi_debug
#define UMF_nbug		 umfzi_nbug
#define UMF_fbug		 umfzi_fbug
#define UMF_allocfail		 umfzi_allocfail
#define UMF_gprob		 umfzi_gprob
#define UMF_dump_dense		 umfzi_dump_dense
#define UMF_dump_element	 umfzi_dump_element
#define UMF_dump_rowcol		 umfzi_dump_rowcol
#define UMF_dump_matrix		 umfzi_dump_matrix
#define UMF_dump_current_front	 umfzi_dump_current_front
#define UMF_dump_lu		 umfzi_dump_lu
#define UMF_dump_memory		 umfzi_dump_memory
#define UMF_dump_packed_memory	 umfzi_dump_packed_memory
#define UMF_dump_col_matrix	 umfzi_dump_col_matrix
#define UMF_dump_chain		 umfzi_dump_chain
#define UMF_dump_start		 umfzi_dump_start
#define UMF_dump_rowmerge	 umfzi_dump_rowmerge

#endif


/* -------------------------------------------------------------------------- */
/* Complex double precision, with long's as integers */
/* -------------------------------------------------------------------------- */

#ifdef ZLONG

#define UMF_analyze		 umfzl_analyze
#define UMF_apply_order		 umfzl_apply_order
#define UMF_assemble		 umfzl_assemble
#define UMF_blas3_update	 umfzl_blas3_update
#define UMF_build_tuples	 umfzl_build_tuples
#define UMF_build_tuples_usage	 umfzl_build_tuples_usage
#define UMF_colamd		 umfzl_colamd
#define UMF_colamd_set_defaults	 umfzl_colamd_set_defaults
#define UMF_create_element	 umfzl_create_element
#define UMF_extend_front	 umfzl_extend_front
#define UMF_free		 umfzl_free
#define UMF_garbage_collection	 umfzl_garbage_collection
#define UMF_get_memory		 umfzl_get_memory
#define UMF_init_front		 umfzl_init_front
#define UMF_is_permutation	 umfzl_is_permutation
#define UMF_kernel		 umfzl_kernel
#define UMF_kernel_init		 umfzl_kernel_init
#define UMF_kernel_init_usage	 umfzl_kernel_init_usage
#define UMF_kernel_wrapup	 umfzl_kernel_wrapup
#define UMF_local_search	 umfzl_local_search
#define UMF_lsolve		 umfzl_lsolve
#define UMF_ltsolve		 umfzl_ltsolve
#define UMF_lhsolve		 umfzl_lhsolve
#define UMF_malloc		 umfzl_malloc
#define UMF_mem_alloc_element	 umfzl_mem_alloc_element
#define UMF_mem_alloc_head_block umfzl_mem_alloc_head_block
#define UMF_mem_alloc_tail_block umfzl_mem_alloc_tail_block
#define UMF_mem_free_tail_block	 umfzl_mem_free_tail_block
#define UMF_mem_init_memoryspace umfzl_mem_init_memoryspace
#define UMF_order_front_tree	 umfzl_order_front_tree
#define UMF_realloc		 umfzl_realloc
#define UMF_report_perm		 umfzl_report_perm
#define UMF_report_vector	 umfzl_report_vector
#define UMF_row_search		 umfzl_row_search
#define UMF_scale_column	 umfzl_scale_column
#define UMF_set_stats		 umfzl_set_stats
#define UMF_solve		 umfzl_solve
#define UMF_symbolic_usage	 umfzl_symbolic_usage
#define UMF_transpose		 umfzl_transpose
#define UMF_tuple_lengths	 umfzl_tuple_lengths
#define UMF_usolve		 umfzl_usolve
#define UMF_utsolve		 umfzl_utsolve
#define UMF_uhsolve		 umfzl_uhsolve
#define UMF_valid_numeric	 umfzl_valid_numeric
#define UMF_valid_symbolic	 umfzl_valid_symbolic
#define UMF_triplet_map_x	 umfzl_triplet_map_x
#define UMF_triplet_map_nox	 umfzl_triplet_map_nox
#define UMF_triplet_nomap_x	 umfzl_triplet_nomap_x
#define UMF_triplet_nomap_nox	 umfzl_triplet_nomap_nox
#define UMFPACK_col_to_triplet	 umfpack_zl_col_to_triplet
#define UMFPACK_defaults	 umfpack_zl_defaults
#define UMFPACK_free_numeric	 umfpack_zl_free_numeric
#define UMFPACK_free_symbolic	 umfpack_zl_free_symbolic
#define UMFPACK_get_lunz	 umfpack_zl_get_lunz
#define UMFPACK_get_numeric	 umfpack_zl_get_numeric
#define UMFPACK_get_symbolic	 umfpack_zl_get_symbolic
#define UMFPACK_numeric		 umfpack_zl_numeric
#define UMFPACK_qsymbolic	 umfpack_zl_qsymbolic
#define UMFPACK_report_control	 umfpack_zl_report_control
#define UMFPACK_report_info	 umfpack_zl_report_info
#define UMFPACK_report_matrix	 umfpack_zl_report_matrix
#define UMFPACK_report_numeric	 umfpack_zl_report_numeric
#define UMFPACK_report_perm	 umfpack_zl_report_perm
#define UMFPACK_report_status	 umfpack_zl_report_status
#define UMFPACK_report_symbolic	 umfpack_zl_report_symbolic
#define UMFPACK_report_triplet	 umfpack_zl_report_triplet
#define UMFPACK_report_vector	 umfpack_zl_report_vector
#define UMFPACK_solve		 umfpack_zl_solve
#define UMFPACK_symbolic	 umfpack_zl_symbolic
#define UMFPACK_transpose	 umfpack_zl_transpose
#define UMFPACK_triplet_to_col	 umfpack_zl_triplet_to_col
#define UMFPACK_wsolve		 umfpack_zl_wsolve

/* for debugging only: */
#define UMF_malloc_count	 umfzl_malloc_count
#define UMF_debug		 umfzl_debug
#define UMF_nbug		 umfzl_nbug
#define UMF_fbug		 umfzl_fbug
#define UMF_allocfail		 umfzl_allocfail
#define UMF_gprob		 umfzl_gprob
#define UMF_dump_dense		 umfzl_dump_dense
#define UMF_dump_element	 umfzl_dump_element
#define UMF_dump_rowcol		 umfzl_dump_rowcol
#define UMF_dump_matrix		 umfzl_dump_matrix
#define UMF_dump_current_front	 umfzl_dump_current_front
#define UMF_dump_lu		 umfzl_dump_lu
#define UMF_dump_memory		 umfzl_dump_memory
#define UMF_dump_packed_memory	 umfzl_dump_packed_memory
#define UMF_dump_col_matrix	 umfzl_dump_col_matrix
#define UMF_dump_chain		 umfzl_dump_chain
#define UMF_dump_start		 umfzl_dump_start
#define UMF_dump_rowmerge	 umfzl_dump_rowmerge

#endif

```

sparse-matrix/umfpack/umf_kernel.c:
```
/* ========================================================================== */
/* === UMF_kernel =========================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    Primary factorization routine.   Called by UMFPACK_numeric.
    Returns:
	UMFPACK_OK if successful,
	UMFPACK_ERROR_out_of_memory if out of memory, or
	UMFPACK_ERROR_different_pattern if pattern of matrix (Ap and/or Ai)
	   has changed since the call to UMFPACK_*symbolic.
*/

#include "umf_internal.h"
#include "umf_init_front.h"
#include "umf_assemble.h"
#include "umf_scale_column.h"
#include "umf_local_search.h"
#include "umf_create_element.h"
#include "umf_extend_front.h"
#include "umf_blas3_update.h"
#include "umf_kernel_init.h"
#include "umf_kernel_wrapup.h"


GLOBAL Int UMF_kernel
(
    const Int Ap [ ],
    const Int Ai [ ],
    const double Ax [ ],
#ifdef COMPLEX
    const double Az [ ],
#endif
    NumericType *Numeric,
    WorkType *Work,
    SymbolicType *Symbolic
)
{

    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    Int j, frontid1, frontid2, chain, nchains, *Chain_start, status,
	*Chain_maxrows, *Chain_maxcols, *Front_npivcol, jmax, nb ;

    /* ---------------------------------------------------------------------- */
    /* initialize memory space and load the matrix */
    /* ---------------------------------------------------------------------- */

    if (!UMF_kernel_init (Ap, Ai, Ax,
#ifdef COMPLEX
	Az,
#endif
	Numeric, Work, Symbolic))
    {
	/* UMF_kernel_init is guaranteed to succeed, since UMFPACK_numeric */
	/* either allocates enough space or if not, UMF_kernel does not get */
	/* called.  So running out of memory here is a fatal error, and means */
	/* that the user changed Ap and/or Ai since the call to */
	/* UMFPACK_*symbolic. */
	return (UMFPACK_ERROR_different_pattern) ;
    }

    /* ---------------------------------------------------------------------- */
    /* get the symbolic factorization */
    /* ---------------------------------------------------------------------- */

    nchains = Symbolic->nchains ;
    Chain_start = Symbolic->Chain_start ;
    Chain_maxrows = Symbolic->Chain_maxrows ;
    Chain_maxcols = Symbolic->Chain_maxcols ;
    Front_npivcol = Symbolic->Front_npivcol ;
    DEBUG0 (("Starting Kernel, nchains "ID"\n", nchains)) ;
    nb = Symbolic->nb ;
    Work->nextcand = 0 ;

    /* ---------------------------------------------------------------------- */
    /* factorize each chain of frontal matrices */
    /* ---------------------------------------------------------------------- */

    for (chain = 0 ; chain < nchains ; chain++)
    {
	ASSERT (Work->fnrows == 0 && Work->fncols == 0 && Work->fnpiv == 0) ;
	frontid1 = Chain_start [chain] ;
	frontid2 = Chain_start [chain+1] - 1 ;
	Work->fnrows_max = Chain_maxrows [chain] ;
	Work->fncols_max = Chain_maxcols [chain] ;
	DEBUG2 (("Starting chain "ID". start "ID" end "ID" maxrows "ID
	    " maxcols "ID"\n", chain, frontid1, frontid2, Work->fnrows_max,
	    Work->fncols_max)) ;

	/* ------------------------------------------------------------------ */
	/* factorize each front in the chain */
	/* ------------------------------------------------------------------ */

	for (Work->frontid = frontid1 ; Work->frontid <= frontid2 ; Work->frontid++)
	{

	    /* -------------------------------------------------------------- */
	    /* there are no 0-by-c or r-by-0 fronts, where c and r are > 0 */
	    /* -------------------------------------------------------------- */

	    /* a front is either 0-by-0, or r-by-c */
	    DEBUG2 (("\n\n::: "ID" : Npiv: "ID" size "ID"-by-"ID"\n",
	    	Work->frontid, Work->npiv,
		Work->fnrows, Work->fncols)) ;
	    ASSERT ((Work->fnrows == 0 && Work->fncols == 0)
		  ||(Work->fnrows != 0 && Work->fncols != 0)) ;

	    /* -------------------------------------------------------------- */
	    /* Initialize the pivot column candidate set */
	    /* -------------------------------------------------------------- */

	    /* next pivot (in range 0 to n-1) */
	    Work->ncand = Front_npivcol [Work->frontid] ;
	    jmax = MIN (MAX_CANDIDATES, Work->ncand) ;
	    for (j = 0 ; j < jmax ; j++)
	    {
		Work->Candidates [j] = Work->nextcand++ ;
		DEBUG3 ((""ID" Candidate: "ID"\n", j, Work->Candidates [j])) ;
	    }

	    /* -------------------------------------------------------------- */
	    /* Assemble and factorize the current frontal matrix */
	    /* -------------------------------------------------------------- */

	    while (Work->ncand > 0)
	    {

		/* ---------------------------------------------------------- */
		/* get the pivot row and column */
		/* ---------------------------------------------------------- */

		status = UMF_local_search (Numeric, Work, Symbolic) ;
		if (status == UMFPACK_ERROR_different_pattern)
		{
		    return (UMFPACK_ERROR_different_pattern) ;
		}
		if (status == UMFPACK_WARNING_singular_matrix)
		{
		    /* no pivot found, try again */
		    DEBUG0 (("No pivot found; try again, ncand: "ID"\n",
		    	Work->ncand)) ;
		    continue ;
		}

		/* ---------------------------------------------------------- */
		/* extend the frontal matrix, or start a new one */
		/* ---------------------------------------------------------- */

		if (Work->do_extend)
		{
		    /* apply pending updates if front would grow too much */
		    if (Work->do_update)
		    {
			UMF_blas3_update (Work) ;
		    }
		    /* extend the current front */
		    UMF_extend_front (Work) ;
		}
		else
		{
		    /* finish the current front (if any) and start a new one */
		    if (!UMF_create_element (Numeric, Work))
		    {
			return (UMFPACK_ERROR_out_of_memory) ;
		    }
		    UMF_init_front (Work) ;
		}

		/* ---------------------------------------------------------- */
		/* Numerical & symbolic assembly into current frontal matrix */
		/* ---------------------------------------------------------- */

		UMF_assemble (Numeric, Work) ;

		/* ---------------------------------------------------------- */
		/* scale the pivot column, and save row and column of U and L */
		/* ---------------------------------------------------------- */

		if (!UMF_scale_column (Numeric, Work))
		{
		    return (UMFPACK_ERROR_out_of_memory) ;
		}

		/* ---------------------------------------------------------- */
		/* Numerical update if enough pivots accumulated */
		/* ---------------------------------------------------------- */

		if (Work->fnpiv >= nb)
		{
		    UMF_blas3_update (Work) ;
		}

		Work->pivrow_in_front = FALSE ;
		Work->pivcol_in_front = FALSE ;

		/* ---------------------------------------------------------- */
		/* If front is empty, evaporate it */
		/* ---------------------------------------------------------- */

		if (Work->fnrows == 0 || Work->fncols == 0)
		{
		    /* This does not create an element, just evaporates. */
		    /* It ensures that a front is not 0-by-c or c-by-0. */
		    DEBUG1 (("Evaporate empty front:\n")) ;
		    (void) UMF_create_element (Numeric, Work) ;
		    Work->fnrows = 0 ;
		    Work->fncols = 0 ;
		}
	    }
	}

	/* ------------------------------------------------------------------ */
	/* Wrapup the current frontal matrix.  This is the last in a chain */
	/* in the column elimination tree.  The next frontal matrix */
	/* cannot overlap with the current one, which will be its sibling */
	/* in the column etree. */
	/* ------------------------------------------------------------------ */

	if (!UMF_create_element (Numeric, Work))
	{
	    return (UMFPACK_ERROR_out_of_memory) ;
	}

	/* ------------------------------------------------------------------ */
	/* current front is now empty */
	/* ------------------------------------------------------------------ */

	Work->fnrows = 0 ;
	Work->fncols = 0 ;
    }

    /* ---------------------------------------------------------------------- */
    /* end the last Lchain and Uchain and finalize the LU factors */
    /* ---------------------------------------------------------------------- */

    UMF_kernel_wrapup (Numeric, Symbolic, Work) ;

    /* note that the matrix may be singular */
    return (UMFPACK_OK) ;
}


```

sparse-matrix/umfpack/umfpack_report_perm.h:
```
/* ========================================================================== */
/* === umfpack_report_perm ================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

int umfpack_di_report_perm
(
    int np,
    const int Perm [ ],
    const double Control [UMFPACK_CONTROL]
) ;

long umfpack_dl_report_perm
(
    long np,
    const long Perm [ ],
    const double Control [UMFPACK_CONTROL]
) ;

int umfpack_zi_report_perm
(
    int np,
    const int Perm [ ],
    const double Control [UMFPACK_CONTROL]
) ;

long umfpack_zl_report_perm
(
    long np,
    const long Perm [ ],
    const double Control [UMFPACK_CONTROL]
) ;

/*
double int Syntax:

    #include "umfpack.h"
    int np, *Perm, status ;
    double Control [UMFPACK_CONTROL] ;
    status = umfpack_di_report_perm (np, Perm, Control) ;

double long Syntax:

    #include "umfpack.h"
    long np, *Perm, status ;
    double Control [UMFPACK_CONTROL] ;
    status = umfpack_dl_report_perm (np, Perm, Control) ;

complex int Syntax:

    #include "umfpack.h"
    int np, *Perm, status ;
    double Control [UMFPACK_CONTROL] ;
    status = umfpack_zi_report_perm (np, Perm, Control) ;

complex long Syntax:

    #include "umfpack.h"
    long np, *Perm, status ;
    double Control [UMFPACK_CONTROL] ;
    status = umfpack_zl_report_perm (np, Perm, Control) ;

Purpose:

    Verifies and prints a permutation vector.

Returns:

    UMFPACK_OK if Control [UMFPACK_PRL] <= 2 (the input is not checked).

    Otherwise:
    UMFPACK_OK if the permutation vector is valid (this includes that case
	when Perm is (Int *) NULL, which is not an error condition).
    UMFPACK_ERROR_n_nonpositive if np <= 0.
    UMFPACK_ERROR_out_of_memory if out of memory.
    UMFPACK_ERROR_invalid_permutation if Perm is not a valid permutation vector.

Arguments:

    Int np ;		Input argument, not modified.

	Perm is an integer vector of size np.  Restriction: np > 0.

    Int Perm [np] ;	Input argument, not modified.

	A permutation vector of size np.  If Perm is not present (an (Int *)
	NULL pointer), then it is assumed to be the identity permutation.  This
	is consistent with its use as an input argument to umfpack_*_qsymbolic,
	and is not an error condition.  If Perm is present, the entries in Perm
	must range between 0 and np-1, and no duplicates may exist.

    double Control [UMFPACK_CONTROL] ;	Input argument, not modified.

	If a (double *) NULL pointer is passed, then the default control
	settings are used.  Otherwise, the settings are determined from the
	Control array.  See umfpack_*_defaults on how to fill the Control
	array with the default settings.  If Control contains NaN's, the
	defaults are used.  The following Control parameters are used:

	Control [UMFPACK_PRL]:  printing level.

	    2 or less: no output.  returns silently without checking anything.
	    3: fully check input, and print a short summary of its status
	    4: as 3, but print first few entries of the input
	    5: as 3, but print all of the input
	    Default: 1
*/

```

sparse-matrix/umfpack/umfpack_solve.h:
```
/* ========================================================================== */
/* === umfpack_solve ======================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

int umfpack_di_solve
(
    int sys,
    const int Ap [ ],
    const int Ai [ ],
    const double Ax [ ],
    double X [ ],
    const double B [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO]
) ;

long umfpack_dl_solve
(
    long sys,
    const long Ap [ ],
    const long Ai [ ],
    const double Ax [ ],
    double X [ ],
    const double B [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO]
) ;

int umfpack_zi_solve
(
    int sys,
    const int Ap [ ],
    const int Ai [ ],
    const double Ax [ ], const double Az [ ],
    double Xx [ ],	 double Xz [ ],
    const double Bx [ ], const double Bz [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO]
) ;

long umfpack_zl_solve
(
    long sys,
    const long Ap [ ],
    const long Ai [ ],
    const double Ax [ ], const double Az [ ],
    double Xx [ ],	 double Xz [ ],
    const double Bx [ ], const double Bz [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO]
) ;

/*
double int Syntax:

    #include "umfpack.h"
    void *Numeric ;
    int status, *Ap, *Ai, sys ;
    double *B, *X, *Ax, Info [UMFPACK_INFO], Control [UMFPACK_CONTROL] ;
    status = umfpack_di_solve (sys, Ap, Ai, Ax, X, B, Numeric, Control, Info) ;

double long Syntax:

    #include "umfpack.h"
    void *Numeric ;
    long status, *Ap, *Ai, sys ;
    double *B, *X, *Ax, Info [UMFPACK_INFO], Control [UMFPACK_CONTROL] ;
    status = umfpack_dl_solve (sys, Ap, Ai, Ax, X, B, Numeric, Control, Info) ;

complex int Syntax:

    #include "umfpack.h"
    void *Numeric ;
    int status, *Ap, *Ai, sys ;
    double *Bx, *Bz, *Xx, *Xz, *Ax, *Az, Info [UMFPACK_INFO],
	Control [UMFPACK_CONTROL] ;
    status = umfpack_zi_solve (sys, Ap, Ai, Ax, Az, Xx, Xz, Bx, Bz, Numeric,
	Control, Info) ;

complex long Syntax:

    #include "umfpack.h"
    void *Numeric ;
    long status, *Ap, *Ai, sys ;
    double *Bx, *Bz, *Xx, *Xz, *Ax, *Az, Info [UMFPACK_INFO],
	Control [UMFPACK_CONTROL] ;
    status = umfpack_zl_solve (sys, Ap, Ai, Ax, Az, Xx, Xz, Bx, Bz, Numeric,
	Control, Info) ;

Purpose:

    Given LU factors computed by umfpack_*_numeric (PAQ=LU) and the
    right-hand-side, B, solve a linear system for the solution X.  Iterative
    refinement is optionally performed.  This routine dynamically allocates
    workspace of size O(n).  Only square systems are handled.  Singular matrices
    result in a divide-by-zero for all systems except those involving just the
    matrix L.  Iterative refinement is not performed for singular matrices.

    In the discussion below, n is equal to n_row and n_col, because only
    square systems are handled.

Returns:

    The status code is returned.  See Info [UMFPACK_STATUS], below.

Arguments:

    Int sys ;		Input argument, not modified.

	Defines which system to solve.  (') is the linear algebraic transpose
	(complex conjugate if A is complex), and (.') is the array transpose.

	    sys value	    system solved

	    UMFPACK_A       Ax=b
	    UMFPACK_At      A'x=b
	    UMFPACK_Aat     A.'x=b
	    UMFPACK_Pt_L    P'Lx=b
	    UMFPACK_L       Lx=b
	    UMFPACK_Lt_P    L'Px=b
	    UMFPACK_Lat_P   L.'Px=b
	    UMFPACK_Lt      L'x=b
	    UMFPACK_U_Qt    UQ'x=b
	    UMFPACK_U       Ux=b
	    UMFPACK_Q_Ut    QU'x=b
	    UMFPACK_Q_Uat   QU.'x=b
	    UMFPACK_Ut      U'x=b
	    UMFPACK_Uat     U.'x=b

	Iterative refinement can be optionally performed when sys is any of
	the following:

	    UMFPACK_A       Ax=b
	    UMFPACK_At      A'x=b
	    UMFPACK_Aat     A.'x=b

	For the other values of the sys argument, iterative refinement is not
	performed (Control [UMFPACK_IRSTEP], Ap, Ai, Ax, and Az are ignored).

	Earlier versions used a string argument for sys.  It was changed to an
	integer to make it easier for a Fortran code to call UMFPACK.

    Int Ap [n+1] ;	Input argument, not modified.
    Int Ai [nz] ;	Input argument, not modified.
    double Ax [nz] ;	Input argument, not modified.
    double Az [nz] ;	Input argument, not modified, for complex versions.

	If iterative refinement is requested (Control [UMFPACK_IRSTEP] >= 1,
	Ax=b, A'x=b, or A.'x=b is being solved, and A is nonsingular), then
	these arrays must be identical to the same ones passed to
	umfpack_*_numeric.  The umfpack_*_solve routine does not check the
	contents of these arguments, so the results are undefined if Ap, Ai, Ax,
	and/or Az are modified between the calls the umfpack_*_numeric and
	umfpack_*_solve.  These three arrays do not need to be present (NULL
	pointers can be passed) if Control [UMFPACK_IRSTEP] is zero, or if a
	system other than Ax=b, A'x=b, or A.'x=b is being solved, or if A is
	singular, since in each of these cases A is not accessed.

	Future complex version:  if Ax is present and Az is NULL, then both real
	and imaginary parts will be contained in Ax[0..2*nz-1], with Ax[2*k]
	and Ax[2*k+1] being the real and imaginary part of the kth entry.

    double X [n] ;	Output argument.
    or:
    double Xx [n] ;	Output argument, real part.
    double Xz [n] ;	Output argument, imaginary part.

	The solution to the linear system, where n = n_row = n_col is the
	dimension of the matrices A, L, and U.

	Future complex version:  if Xx is present and Xz is NULL, then both real
	and imaginary parts will be returned in Xx[0..2*n-1], with Xx[2*k] and
	Xx[2*k+1] being the real and imaginary part of the kth entry.

    double B [n] ;	Input argument, not modified.
    or:
    double Bx [n] ;	Input argument, not modified, real part.
    double Bz [n] ;	Input argument, not modified, imaginary part.

	The right-hand side vector, b, stored as a conventional array of size n
	(or two arrays of size n for complex versions).  This routine does not
	solve for multiple right-hand-sides, nor does it allow b to be stored in
	a sparse-column form.

	Future complex version:  if Bx is present and Bz is NULL, then both real
	and imaginary parts will be contained in Bx[0..2*n-1], with Bx[2*k]
	and Bx[2*k+1] being the real and imaginary part of the kth entry.

    void *Numeric ;		Input argument, not modified.

	Numeric must point to a valid Numeric object, computed by
	umfpack_*_numeric.

    double Control [UMFPACK_CONTROL] ;	Input argument, not modified.

	If a (double *) NULL pointer is passed, then the default control
	settings are used.  Otherwise, the settings are determined from the
	Control array.  See umfpack_*_defaults on how to fill the Control
	array with the default settings.  If Control contains NaN's, the
	defaults are used.  The following Control parameters are used:

	Control [UMFPACK_IRSTEP]:  The maximum number of iterative refinement
	    steps to attempt.  A value less than zero is treated as zero.  If
	    less than 1, or if Ax=b, A'x=b, or A.'x=b is not being solved, or
	    if A is singular, then the Ap, Ai, Ax, and Az arguments are not
	    accessed.  Default: 2.

    double Info [UMFPACK_INFO] ;	Output argument.

	Contains statistics about the solution factorization.  If a
	(double *) NULL pointer is passed, then no statistics are returned in
	Info (this is not an error condition).  The following statistics are
	computed in umfpack_*_solve:

	Info [UMFPACK_STATUS]: status code.  This is also the return value,
	    whether or not Info is present.

	    UMFPACK_OK

		The linear system was successfully solved.

	    UMFPACK_WARNING_singular_matrix

		A divide-by-zero occured.  Your solution will contain Inf's
		and/or NaN's.  Some parts of the solution may be valid.  For
		example, solving Ax=b with

		A = [2 0]  b = [ 1 ]  returns x = [ 0.5 ]
		    [0 0]      [ 0 ]              [ Inf ]

	    UMFPACK_ERROR_out_of_memory

		Insufficient memory to solve the linear system.

	    UMFPACK_ERROR_argument_missing

		One or more required arguments are missing.  The B, X, (or
		Bx, Bz, Xx and Xz for the complex versions) arguments
		are always required.  Info and Control are not required.  Ap,
		Ai, Ax (and Az for complex versions) are required if Ax=b,
		A'x=b, A.'x=b is to be solved, the (default) iterative
		refinement is requested, and the matrix A is nonsingular.

	    UMFPACK_ERROR_invalid_system

		The sys argument is not valid, or the matrix A is not square.

	    UMFPACK_ERROR_invalid_Numeric_object

		The Numeric object is not valid.

	Info [UMFPACK_NROW], Info [UMFPACK_NCOL]:
		The dimensions of the matrix A (L is n_row-by-n_inner and
		U is n_inner-by-n_col, with n_inner = min(n_row,n_col)).

	Info [UMFPACK_NZ]:  the number of entries in the input matrix, Ap [n],
	    if iterative refinement is requested (Ax=b, A'x=b, or A.'x=b is
	    being solved, Control [UMFPACK_IRSTEP] >= 1, and A is nonsingular).

	Info [UMFPACK_IR_TAKEN]:  The number of iterative refinement steps
	    effectively taken.  The number of steps attempted may be one more
	    than this; the refinement algorithm backtracks if the last
	    refinement step worsens the solution.

	Info [UMFPACK_IR_ATTEMPTED]:   The number of iterative refinement steps
	    attempted.  The number of times a linear system was solved is one
	    more than this (once for the initial Ax=b, and once for each Ay=r
	    solved for each iterative refinement step attempted).

	Info [UMFPACK_OMEGA1]:  sparse backward error estimate, omega1, if
	    iterative refinement was performed, or -1 if iterative refinement
	    not performed.

	Info [UMFPACK_OMEGA2]:  sparse backward error estimate, omega2, if
	    iterative refinement was performed, or -1 if iterative refinement
	    not performed.

	Info [UMFPACK_SOLVE_FLOPS]:  the number of floating point operations
	    performed to solve the linear system.  This includes the work
	    taken for all iterative refinement steps, including the backtrack
	    (if any).

	Info [UMFPACK_SOLVE_TIME]:  The time taken by umfpack_*_solve, in
	    seconds.  In the ANSI C version, this may be invalid if the time
	    taken is more than about 36 minutes, because of wrap-around in the
	    ANSI C clock function.  Compile UMFPACK with -DGETRUSAGE if you have
	    the more accurate getrusage function.

	Only the above listed Info [...] entries are accessed.  The remaining
	entries of Info are not accessed or modified by umfpack_*_solve.
	Future versions might modify different parts of Info.
*/

```

sparse-matrix/umfpack/umf_config.h:
```
/* ========================================================================== */
/* === umf_config.h ========================================================= */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    This file controls the compile-time configuration of UMFPACK.  Modify the
    Makefile, the architecture-dependent Make.* file, and this file if
    necessary, to control these options.  The following flags may be given
    as options to your C compiler (as in "cc -DNBLAS", for example).  These
    flags are normally placed in your CONFIG string, defined in your Make.*.

	-DNBLAS

	    BLAS mode.  If -DNBLAS is set, then no BLAS will be used.  Vanilla
	    C code will be used instead.  This is portable, and easier to
	    install, but you won't get the best performance.

	    If -DNBLAS is not set, then externally-available BLAS routines
	    (dgemm, dger, and dgemv or the equivalent C-BLAS routines) will be
	    used.  This will give you the best performance, but perhaps at the
	    expense of portability.

	    The default is to use the BLAS, for both the C-callable umfpack.a
	    library and the MATLAB mexFunction.  If you have trouble installing
	    UMFPACK, set -DNBLAS.

	-DNCBLAS

	    If -DNCBLAS is set, then the C-BLAS will not be called.  This is the
	    default when compiling the MATLAB mexFunction, or when compiling
	    umfpack.a on Sun Solaris or SGI IRIX.

	    If -DNCBLAS is not set, then the C-BLAS interface to the BLAS is
	    used.  If your vendor-supplied BLAS library does not have a C-BLAS
	    interface, you can obtain the ATLAS BLAS, available at
	    http://www.netlib.org/atlas.

	    Using the C-BLAS is the default when compiling umfpack.a on all
	    architectures except Sun Solaris (the Sun Performance Library is
	    somewhat faster).  The ANSI C interface to the BLAS is fully
	    portable.

	    This flag is ignored if -DNBLAS is set.

	-DLP64

	    This should be defined if you are compiling in the LP64 model
	    (32 bit int's, 64 bit long's, and 64 bit pointers).  In Solaris,
	    this is obtained with the flags -xtarget=ultra -xarch=v9 for
	    the cc compiler (for example).

	-DLONGBLAS

	    If not defined, then the BLAS are not called in the long integer
	    version of UMFPACK (the umfpack_*l_* routines).  The most common
	    definitions of the BLAS, unfortunately, use int arguments, and
	    are thus not suitable for use in the LP64 model.  Only the Sun
	    Performance Library, as far as I can tell, has a version of the
	    BLAS that allows long integer (64-bit) input arguments.  This
	    flag is set automatically in Sun Solaris if you are using the
	    Sun Performance BLAS.  You can set it yourself, too, if your BLAS
	    routines can take long integer input arguments.

	-DNSUNPERF

	    Applies only to Sun Solaris.  If -DNSUNPERF is set, then the Sun
	    Performance Library BLAS will not be used.

	    The Sun Performance Library BLAS is used by default when compiling
	    the C-callable umfpack.a library on Sun Solaris.

	    This flag is ignored if -DNBLAS is set.

	-DNSCSL

	    Applies only to SGI IRIX.  If -DSCSL is set, then the SGI SCSL
	    Scientific Library BLAS will not be used.

	    The SGI SCSL Scientific Library BLAS is used by default when
	    compiling the C-callable umfpack.a library on SGI IRIX.

	    This flag is ignored if -DNBLAS is set.

	-DGETRUSAGE

	    If -DGETRUSAGE is set, then your system's getrusage routine will be
	    used for getting the process CPU time.  Otherwise the ANSI C clock
	    routine will be used.  The default is to use getrusage on Sun
	    Solaris, SGI Irix, Linux, and AIX (IBM RS 6000) and to use clock on
	    all other architectures.

    You should normally not set these flags yourself:

	-DBLAS_BY_VALUE		if scalars are passed by value, not reference
	-DBLAS_NO_UNDERSCORE	if no underscore should be appended
	-DBLAS_CHAR_ARG		if BLAS options are single char's, not strings

	    The BLAS options are normally set automatically.  If your
	    architecture cannot be determined (see UMFPACK_ARCHITECTURE, below)
	    then you may need to set these flags yourself.

	-DMATLAB_MEX_FILE

	    This flag is turned on when compiling the umfpack mexFunction for
	    use in MATLAB.  When compiling the MATLAB mexFunction, the MATLAB
	    BLAS are used (unless -DNBLAS is set).  The -DNCBLAS, -DNSCSL, and
	    -DNSUNPERF flags are all ignored.

	-DMATHWORKS

	    This flag is turned on when compiling umfpack as a built-in routine
	    in MATLAB.  The MATLAB BLAS are used for all architectures (-DNBLAS,
	    -DNCBLAS, -DNSCSL, and -DNSUNPERF flags are all ignored).  Internal
	    routines utMalloc, utFree, utRealloc, utPrintf, utDivideComplex,
	    and utFdlibm_hypot are used, and the "util.h" file is included.  
	    This avoids the problem discussed in the User Guide regarding memory
	    allocation in MATLAB.  utMalloc returns NULL on failure, instead of
	    terminating the mexFunction (which is what mxMalloc does).  However,
	    the ut* routines are not documented by The MathWorks, Inc., so I
	    cannot guarantee that you will always be able to use them.

	-DNDEBUG

	    Debugging mode (if NDEBUG is not defined).  The default, of course,
	    is no debugging.  Turning on debugging takes some work (see below).
	    If you do not edit this file, then debugging is turned off anyway,
	    regardless of whether or not -DNDEBUG is specified in your compiler
	    options.

    You can change the memory allocator routines by editting the definitions
    of ALLOCATE, FREE, and REALLOCATE, below.
*/



/* ========================================================================== */
/* === NDEBUG =============================================================== */
/* ========================================================================== */

/*
    UMFPACK will be exceedingly slow when running in debug mode.  The next three
    lines ensure that debugging is turned off.  If you want to compile UMFPACK
    in debugging mode, you must comment out the three lines below:
*/
#ifndef NDEBUG
#define NDEBUG
#endif

/*
    Next, you must either remove the -DNDEBUG option in the Makefile, or simply
    add the following line:
#undef NDEBUG
*/

/* ========================================================================== */
/* === Memory allocator ===================================================== */
/* ========================================================================== */

/* The MATLAB mexFunction uses MATLAB's memory manager, while the C-callable */
/* umfpack.a library uses the ANSI C malloc, free, and realloc routines. */

#ifdef MATLAB_MEX_FILE
#define ALLOCATE mxMalloc
#define FREE mxFree
#define REALLOCATE mxRealloc
#else
#ifdef MATHWORKS
/* Compiling UMFPACK as a built-in routine. */
/* Since UMFPACK carefully checks for out-of-memory after every allocation, */
/* we can use ut* routines here. */
#define ALLOCATE utMalloc
#define FREE utFree
#define REALLOCATE utRealloc
#else
#define ALLOCATE malloc
#define FREE free
#define REALLOCATE realloc
#endif
#endif


/* ========================================================================== */
/* === PRINTF macro ========================================================= */
/* ========================================================================== */

/* All output goes through the PRINTF macro.  Printing occurs only from the */
/* UMFPACK_report_* routines. */

#ifdef MATLAB_MEX_FILE
#define PRINTF(params) { (void) mexPrintf params ; }
#else
#ifdef MATHWORKS
/* Already #included "util.h" in umf_version.h */
#define PRINTF(params) { (void) utPrintf params ; }
#else
#define PRINTF(params) { (void) printf params ; }
#endif
#endif


/* ========================================================================== */
/* === 0-based or 1-based printing ========================================== */
/* ========================================================================== */

#if defined (MATLAB_MEX_FILE) && defined (NDEBUG)
/* In MATLAB, matrices are 1-based to the user, but 0-based internally. */
/* One is added to all row and column indices when printing matrices */
/* in UMFPACK_report_*.  */
#define INDEX(i) ((i)+1)
#else
/* In ANSI C, matrices are 0-based and indices are reported as such. */
/* This mode is also used for debug mode, and if MATHWORKS is defined rather */
/* than MATLAB_MEX_FILE. */
#define INDEX(i) (i)
#endif


/* ========================================================================== */
/* === Timer ================================================================ */
/* ========================================================================== */

/*
    If you have the getrusage routine (all Unix systems I've test do), then use
    that.  Otherwise, use the ANSI C clock function.   Note that on many
    systems, the ANSI clock function wraps around after only 2147 seconds, or
    about 36 minutes.  BE CAREFUL:  if you compare the run time of UMFPACK with
    other sparse matrix packages, be sure to use the same timer.  See
    umfpack_timer.c for the timer used by UMFPACK.
*/

/* Sun Solaris, SGI Irix, Linux, Compaq Alpha, and IBM RS 6000 all have */
/* getrusage.  It's in BSD unix, so perhaps all unix systems have it. */
#if defined (UMF_SOL2) || defined (UMF_SGI) || defined (UMF_LINUX) \
|| defined (UMF_ALPHA) || defined (UMF_AIX)
#define GETRUSAGE
#endif


/* ========================================================================== */
/* === BLAS ================================================================= */
/* ========================================================================== */

/*
    The adventure begins.  Figure out how to call the BLAS ...

    This works, but it is incredibly ugly.  The C-BLAS was supposed to solve
    this problem, and make it easier to interface a C program to the BLAS.
    Unfortunately, the C-BLAS does not have a "long" integer (64 bit) version.
    Various vendors have done their own 64-bit BLAS.  Sun has dgemm_64 routines
    with long integers, SGI has a 64-bit dgemm in their scsl_blas_i8 library
    with long long integers, and so on.

    Different vendors also have different ways of defining a complex number,
    some using struct's.  That's a bad idea.  See umf_version.h for the better
    way to do it (the method that was also chosen for the complex C-BLAS).

    To make matters worse, SGI's SCSL BLAS has a C-BLAS interface which
    differs from the ATLAS C-BLAS interface (see immediately below);
    although a more recent version of SGI's C-BLAS interface is correct
    if SCSL_VOID_ARGS is defined.
*/


/* -------------------------------------------------------------------------- */
/* Determine which BLAS to use. */
/* -------------------------------------------------------------------------- */

#if defined (MATHWORKS)
#define USE_MATLAB_BLAS

#elif defined (NBLAS)
#define USE_NO_BLAS

#elif defined (MATLAB_MEX_FILE)
#define USE_MATLAB_BLAS

#elif defined (UMF_SOL2) && !defined (NSUNPERF)
#define USE_SUNPERF_BLAS

#elif defined (UMF_SGI) && !defined (NSCSL)
#define USE_SCSL_BLAS

#elif !defined (NCBLAS)
#define USE_C_BLAS

#else
#define USE_FORTRAN_BLAS
#endif

/* -------------------------------------------------------------------------- */
/* int vs. long integer arguments */
/* -------------------------------------------------------------------------- */

/*
    Determine if the BLAS exists for the long integer version.  It exists if
    LONGBLAS is defined in the Makefile, or if using the BLAS from the
    Sun Performance Library, or SGI's SCSL Scientific Library.
*/

#if defined (USE_SUNPERF_BLAS) || defined (USE_SCSL_BLAS)
#ifndef LONGBLAS
#define LONGBLAS
#endif
#endif

/* do not use the BLAS if Int's are long and LONGBLAS is not defined */
#if defined (LONG_INTEGER) && !defined (LONGBLAS) && !defined (USE_NO_BLAS)
#define USE_NO_BLAS
#endif


/* -------------------------------------------------------------------------- */
/* Use (void *) arguments for the SGI */
/* -------------------------------------------------------------------------- */

#if defined (UMF_SGI)
/*
    Use (void *) pointers for complex types in SCSL.
    The ATLAS C-BLAS, and the SGI C-BLAS differ.  The former uses (void *)
    arguments, the latter uses SCSL_ZOMPLEX_T, which are either scsl_zomplex
    or (void *).  Using (void *) is simpler, and is selected by defining
    SCSL_VOID_ARGS, below.  The cc compiler doesn't complain, but gcc is
    more picky, and generates a warning without this next statement.
    With gcc and the 07/09/98 version of SGI's cblas.h, spurious warnings
    about complex BLAS arguments will be reported anyway.  This is because this
    older version of SGI's cblas.h does not make use of the SCSL_VOID_ARGS
    parameter, which is present in the 12/6/01 version of SGI's cblas.h.  You
    can safely ignore these warnings.
*/
#define SCSL_VOID_ARGS
#endif


/* -------------------------------------------------------------------------- */
/* The BLAS exists, construct appropriate macros */
/* -------------------------------------------------------------------------- */

#if !defined (USE_NO_BLAS)		/* { */

/*
    If the compile-time flag -DNBLAS is defined, then the BLAS are not used,
    portable vanilla C code is used instead, and the remainder of this file
    is ignored.

    Using the BLAS is much faster, but how C calls the Fortran BLAS is
    machine-dependent and thus can cause portability problems.  Thus, use
    -DNBLAS to ensure portability (at the expense of speed).

    Preferences:

	*** The best interface to use, regardless of the option you select
	    below, is the standard C-BLAS interface.  Not all vendor-supplied
	    BLAS libraries use this interface (as of April 2001).  The only
	    problem with this interface is that it does not extend to the LP64
	    model.  The C-BLAS does not provide for a 64-bit integer.  In
	    addition, SGI's older cblas.h can cause spurious warnings when
	    using the C-BLAS interface.

	1) most preferred:  use the optimized vendor-supplied library (such as
	    the Sun Performance Library, or IBM's ESSL).  This is often the
	    fastest, but might not be portable and might not always be
	    available.  When compiling a MATLAB mexFunction it might be
	    difficult get the mex compiler script to recognize the vendor-
	    supplied BLAS.

	2) When compiling the UMFPACK mexFunction to use UMFPACK in MATLAB, use
	    the BLAS provided by The Mathworks, Inc.  This assumes you are using
	    MATLAB V6 or higher, since the BLAS are not incorporated in V5 or
	    earlier versions.  On my Sun workstation, the MATLAB BLAS gave
	    slightly worse performance than the Sun Perf. BLAS.  The advantage
	    of using the MATLAB BLAS is that it's available on any computer that
	    has MATLAB V6 or higher.  I have not tried using MATLAB BLAS outside
	    of a mexFunction in a stand-alone C code, but MATLAB (V6) allows for
	    this.  This is well worth trying if you have MATLAB and don't want
	    to bother installing the ATLAS BLAS (option 3a, below).  The only
	    glitch to this is that MATLAB does not provide a portable interface
	    to the BLAS (an underscore is required for some but not all
	    architectures).  These variations are taken into account in the
	    mexopts.sh file provided with UMFPACK.  For Windows and MATLAB 6.0
	    or 6.1, you also need to copy the libmwlapack.dll file; see the
	    User Guide for details.

	    In the current distribution, the only BLAS that the UMFPACK 
	    mexFunction will use is the internal MATLAB BLAS.  It's possible to
	    use other BLAS, but handling the porting of using the mex compiler
	    with different BLAS libraries is not trivial.

	3) Use a portable high-performance BLAS library:

	    (a) The ATLAS BLAS, available at http://www.netlib.org/atlas,
		by R. Clint Whaley, Antoine Petitet, and Jack Dongarra.
		This has a standard C interface, and thus the interface to it is
		fully portable.  Its performance rivals, and sometimes exceeds,
		the vendor-supplied BLAS on many computers.

	    (b) The Fortran RISC BLAS by Michel Dayde', Iain Duff, Antoine
		Petitet, and Abderrahim Qrichi Aniba, available via anonymous
		ftp to ftp.enseeiht.fr in the pub/numerique/BLAS/RISC directory,
		See M. J. Dayde' and I. S. Duff, "The RISC BLAS:  A blocked
		implementation of level 3 BLAS for RISC processors, ACM Trans.
		Math. Software, vol. 25, no. 3., Sept. 1999.  This will give
		you good performance, but with the same C-to-Fortran portability
		problems as option (1).

	4) Use UMFPACK's built-in vanilla C code by setting -DNBLAS at compile
	    time.  The key advantage is portability, which is guaranteed if you
	    have an ANSI C compliant compiler.  You also don't need to download
	    any other package - UMFPACK is stand-alone.  No Fortran is used
	    anywhere in UMFPACK.  UMFPACK will be much slower than when using
	    options (1) through (3), however.

	5) least preferred:  use the standard Fortran implementation of the
	    BLAS, also available at Netlib (http://www.netlib.org/blas).  This
	    will be no faster than option (4), and not portable because of
	    C-to-Fortran calling conventions.  Don't bother trying option (5).

    The mechanics of how C calls the BLAS on various computers are as follows:

	* C-BLAS (from the ATLAS library, for example):
	    The same interface is used on all computers.

	* Defaults for calling the Fortran BLAS:
	    add underscore, pass scalars by reference, use string arguments.

	* The Fortran BLAS on Sun Solaris (when compiling the MATLAB mexFunction
	    or when using the Fortran RISC BLAS), SGI IRIX, Linux, and Compaq
	    Alpha: use defaults.

	* Sun Solaris (when using the C-callable Sun Performance library):
	    no underscore, pass scalars by value, use character arguments.

	* The Fortran BLAS (ESSL Library) on the IBM RS 6000, and HP Unix: 
	    no underscore, pass scalars by reference, use string arguments.

	* The Fortran BLAS on Windows:
	    no underscore, pass scalars by reference, use string arguments.
	    If you compile the umfpack mexFunction using umfpack_make, and are
	    using the lcc compiler bundled with MATLAB, then you must first
	    copy the umfpack\lcc_lib\libmwlapack.lib file into the
	    <matlab>\extern\lib\win32\lcc\ directory, where <matlab> is the
	    directory in which MATLAB is installed.  Next, type mex -setup
	    at the MATLAB prompt, and ask MATLAB to select the lcc compiler.
	    MATLAB has built-in BLAS, but it cannot be accessed by a program
	    compiled by lcc without first copying this file.
*/



/* -------------------------------------------------------------------------- */
#ifdef USE_C_BLAS	/* { */
/* -------------------------------------------------------------------------- */


/* -------------------------------------------------------------------------- */
/* use the C-BLAS (any computer) */
/* -------------------------------------------------------------------------- */

/*
    C-BLAS is the default interface, with the following exceptions.  Solaris
    uses the Sun Performance BLAS for umfpack.a (the C-callable library).  SGI
    IRIX uses the SCSL BLAS for umfpack.a.  All architectures use MATLAB's
    internal BLAS for the mexFunction on any architecture.  These options are
    set in the Make.* files.  The Make.generic file uses no BLAS at all.

    If you use the ATLAS C-BLAS, then be sure to set the -I flag to
    -I/path/ATLAS/include, where /path/ATLAS is the ATLAS installation
    directory.  See Make.solaris for an example.  You do not need to do this
    for the SGI, which has a /usr/include/cblas.h.
*/

#include "cblas.h"

#ifdef COMPLEX
#define BLAS_GEMM_ROUTINE cblas_zgemm
#define BLAS_GEMV_ROUTINE cblas_zgemv
#define BLAS_GER_ROUTINE  cblas_zgeru
#define BLAS_DECLARE_SCALAR(x) double x [2]
#define BLAS_ASSIGN(x,xr,xi) { x [0] = xr ; x [1] = xi ; }
#else
#define BLAS_GEMM_ROUTINE cblas_dgemm
#define BLAS_GEMV_ROUTINE cblas_dgemv
#define BLAS_GER_ROUTINE  cblas_dger
#define BLAS_DECLARE_SCALAR(x) double x
#define BLAS_ASSIGN(x,xr,xi) { x = xr ; }
#endif

#define BLAS_NO_TRANSPOSE CblasNoTrans
#define BLAS_TRANSPOSE CblasTrans
#define BLAS_COLUMN_MAJOR_ORDER CblasColMajor,
#define BLAS_SCALAR(x) x
#define BLAS_INT_SCALAR(n) n
#define BLAS_ARRAY(a) a



/* -------------------------------------------------------------------------- */
#else	/* } USE_C_BLAS { */
/* -------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------- */
/* use Fortran (or other architecture-specific) BLAS */
/* -------------------------------------------------------------------------- */

/* No such argument when not using the C-BLAS */
#define BLAS_COLUMN_MAJOR_ORDER

/* Determine which architecture we're on and set options accordingly. */
/* The default, if nothing is defined is to add an underscore, */
/* pass scalars by reference, and use string arguments. */

/* ---------------------------------- */
/* Sun Performance BLAS */
/* ---------------------------------- */

#ifdef USE_SUNPERF_BLAS
#ifdef _SUNPERF_H
/* <sunperf.h> has been included somehow anyway, outside of umf_config.h */
#error "sunperf.h must NOT be #include'd.  See umf_config.h for details."
#endif
#define BLAS_BY_VALUE
#define BLAS_NO_UNDERSCORE
#define BLAS_CHAR_ARG
#endif	/* USE_SUNPERF_BLAS */

/* ---------------------------------- */
/* SGI SCSL BLAS */
/* ---------------------------------- */

#ifdef USE_SCSL_BLAS
#if defined (LP64)
#include <scsl_blas_i8.h>
#else
#include <scsl_blas.h>
#endif
#define BLAS_BY_VALUE
#define BLAS_NO_UNDERSCORE
#endif	/* USE_SCSL_BLAS */

/* ---------------------------------- */
/* IBM AIX, Windows, and HP Fortran BLAS */
/* ---------------------------------- */

#if defined (UMF_AIX) || defined (UMF_WINDOWS) || defined (UMF_HP)
#define BLAS_NO_UNDERSCORE
#endif


/* -------------------------------------------------------------------------- */
/* BLAS names */
/* -------------------------------------------------------------------------- */

#if defined (LP64) && defined (USE_SUNPERF_BLAS) && defined (LONG_INTEGER)

/* 64-bit sunperf BLAS, for Sun Solaris only */
#ifdef COMPLEX
#define BLAS_GEMM_ROUTINE zgemm_64
#define BLAS_GEMV_ROUTINE zgemv_64
#define BLAS_GER_ROUTINE  zgeru_64
#else
#define BLAS_GEMM_ROUTINE dgemm_64
#define BLAS_GEMV_ROUTINE dgemv_64
#define BLAS_GER_ROUTINE  dger_64
#endif	/* COMPLEX */

#else

#ifdef COMPLEX

/* naming convention (use underscore, or not) */
#ifdef BLAS_NO_UNDERSCORE
#define BLAS_GEMM_ROUTINE zgemm
#define BLAS_GEMV_ROUTINE zgemv
#define BLAS_GER_ROUTINE  zgeru
#else
/* default:  add underscore */
#define BLAS_GEMM_ROUTINE zgemm_
#define BLAS_GEMV_ROUTINE zgemv_
#define BLAS_GER_ROUTINE  zgeru_
#endif

#else

/* naming convention (use underscore, or not) */
#ifdef BLAS_NO_UNDERSCORE
#define BLAS_GEMM_ROUTINE dgemm
#define BLAS_GEMV_ROUTINE dgemv
#define BLAS_GER_ROUTINE  dger
#else
/* default:  add underscore */
#define BLAS_GEMM_ROUTINE dgemm_
#define BLAS_GEMV_ROUTINE dgemv_
#define BLAS_GER_ROUTINE  dger_
#endif

#endif	/* COMPLEX */

#endif /* LP64 && USE_SUNPERF_BLAS */


/* -------------------------------------------------------------------------- */
/* BLAS real or complex floating-point scalars */
/* -------------------------------------------------------------------------- */

#ifdef COMPLEX

/*
    The SunPerf BLAS expects to see a doublecomplex scalar, but it
    also will accept an array of size 2.  See the manual, normally at
    file:///opt/SUNWspro/WS6U1/lib/locale/C/html/manuals/perflib/user_guide
    /plug_using_perflib.html .  This manual is inconsistent with the man pages
    for zgemm, zgemv, and zgeru and also inconsistent with the <sunperf.h>
    include file.  Use this instead, for SunPerf (only works if you do NOT
    include sunperf.h).  Fortunately, this file (umf_config.h) is not included
    in any user code that calls UMFPACK.  Thus, the caller may include
    sunperf.h in his or her own code, and that is safely ignored here.
    SGI's SCSL BLAS has yet a different kind of struct, but we can use a
    double array of size 2 instead (since SCSL_VOID_ARGS is defined).
    Most BLAS expect complex scalars as pointers to double arrays of size 2.
*/

#define BLAS_DECLARE_SCALAR(x) double x [2]
#define BLAS_ASSIGN(x,xr,xi) { x [0] = xr ; x [1] = xi ; }
#define BLAS_SCALAR(x) x

#else

#define BLAS_DECLARE_SCALAR(x) double x
#define BLAS_ASSIGN(x,xr,xi) { x = xr ; }
#ifdef BLAS_BY_VALUE
#define BLAS_SCALAR(x) x
#else
#define BLAS_SCALAR(x) &(x)
#endif

#endif /* COMPLEX */


/* -------------------------------------------------------------------------- */
/* BLAS integer scalars */
/* -------------------------------------------------------------------------- */

/*
    Fortran requires integers to be passed by reference.
    The SCSL BLAS requires long long arguments in LP64 mode.
*/

#if defined (USE_SCSL_BLAS) && defined (LP64)
#define BLAS_INT_SCALAR(n) ((long long) n)
#else
#ifdef BLAS_BY_VALUE
#define BLAS_INT_SCALAR(n) n
#else
#define BLAS_INT_SCALAR(n) &(n)
#endif
#endif


/* -------------------------------------------------------------------------- */
/* BLAS strings */
/* -------------------------------------------------------------------------- */

/*
    The Sun Performance BLA wants a character instead of a string.
*/

#ifdef BLAS_CHAR_ARG
#define BLAS_NO_TRANSPOSE 'N'
#define BLAS_TRANSPOSE 'T'
#else
#define BLAS_NO_TRANSPOSE "N"
#define BLAS_TRANSPOSE "T"
#endif


/* -------------------------------------------------------------------------- */
/* BLAS arrays */
/* -------------------------------------------------------------------------- */

/*
    The complex SunPerf BLAS expects to see a doublecomplex array of size s.
    This is broken (see above, regarding complex scalars in sunperf.h).
    For SunPerf BLAS, just pass a pointer to the array, and ignore sunperf.h.
    With sunperf.h, you would need:

	#define BLAS_ARRAY(a) ((doublecomplex *)(a))

    SGI's SCSL BLAS has yet a different kind of struct, but we can use a
    double array of size 2 instead (since SCSL_VOID_ARGS is defined).

    The real versions all use just a (double *) pointer.

    In all cases, no typecast is required.  This will break if <sunperf.h> is
    included.

    If you have read this far, I hope you see now why (void *) a much better
    choice for complex BLAS prototypes, and why double x [2] is better than
    an architecture dependent struct { double real ; double imag ; }
    type definition.

*/

#define BLAS_ARRAY(a) (a)


/* -------------------------------------------------------------------------- */
#endif /* USE_C_BLAS } */
/* -------------------------------------------------------------------------- */





/* -------------------------------------------------------------------------- */
/* BLAS macros, for all interfaces */
/* -------------------------------------------------------------------------- */

/*
   All architecture dependent issues have now been taken into consideration,
   and folded into the macros BLAS_DECLARE_SCALAR, BLAS_ASSIGN, BLAS_*_ROUTINE,
   BLAS_COLUMN_MAJOR_ORDER, BLAS_NO_TRANSPOSE, BLAS_TRANSPOSE, BLAS_SCALAR,
   BLAS_INT_SCALAR, BLAS_ARRAY, and Int.

   You will note that there is not a *** single *** name, declaration, or
   argument to the BLAS which is not somehow different in one or more versions
   of the BLAS!
*/




/* C = C - A*B, where A is m-by-k, B is k-by-n, and leading dimension is d */
#define BLAS_GEMM(m,n,k,A,B,C,d) \
{ \
    BLAS_DECLARE_SCALAR (alpha) ; \
    BLAS_DECLARE_SCALAR (beta) ; \
    BLAS_ASSIGN (alpha, -1.0, 0.0) ; \
    BLAS_ASSIGN (beta, 1.0, 0.0) ; \
    (void) BLAS_GEMM_ROUTINE (BLAS_COLUMN_MAJOR_ORDER \
	BLAS_NO_TRANSPOSE, BLAS_NO_TRANSPOSE, \
	BLAS_INT_SCALAR (m), BLAS_INT_SCALAR (n), BLAS_INT_SCALAR (k), \
	BLAS_SCALAR (alpha), \
	BLAS_ARRAY (A), BLAS_INT_SCALAR (d), \
	BLAS_ARRAY (B), BLAS_INT_SCALAR (d), BLAS_SCALAR (beta), \
	BLAS_ARRAY (C), BLAS_INT_SCALAR (d)) ; \
}


/* A = A - x*y', where A is m-by-n with leading dimension d */
#define BLAS_GER(m,n,x,y,A,d) \
{ \
    Int incx = 1 ; \
    BLAS_DECLARE_SCALAR (alpha) ; \
    BLAS_ASSIGN (alpha, -1.0, 0.0) ; \
    (void) BLAS_GER_ROUTINE (BLAS_COLUMN_MAJOR_ORDER \
	BLAS_INT_SCALAR (m), BLAS_INT_SCALAR (n), \
	BLAS_SCALAR (alpha), \
	BLAS_ARRAY (x), BLAS_INT_SCALAR (incx), \
	BLAS_ARRAY (y), BLAS_INT_SCALAR (d), \
	BLAS_ARRAY (A), BLAS_INT_SCALAR (d)) ; \
}


/* y = y - A'*x, where A is m-by-n with leading dimension d, */
/* and x and y are row vectors with stride d */
#define BLAS_GEMV_ROW(m,n,A,x,y,d) \
{ \
    BLAS_DECLARE_SCALAR (alpha) ; \
    BLAS_DECLARE_SCALAR (beta) ; \
    BLAS_ASSIGN (alpha, -1.0, 0.0) ; \
    BLAS_ASSIGN (beta, 1.0, 0.0) ; \
    (void) BLAS_GEMV_ROUTINE (BLAS_COLUMN_MAJOR_ORDER \
	BLAS_TRANSPOSE, \
	BLAS_INT_SCALAR (m), BLAS_INT_SCALAR (n), \
	BLAS_SCALAR (alpha), \
	BLAS_ARRAY (A), BLAS_INT_SCALAR (d), \
	BLAS_ARRAY (x), BLAS_INT_SCALAR (d), BLAS_SCALAR (beta), \
	BLAS_ARRAY (y), BLAS_INT_SCALAR (d)) ; \
}


/* y = y - A*x, where A is m-by-n with leading dimension d, */
/* and x and y are column vectors with stride 1 */
#define BLAS_GEMV_COL(m,n,A,x,y,d) \
{ \
    Int incx = 1 ; \
    Int incy = 1 ; \
    BLAS_DECLARE_SCALAR (alpha) ; \
    BLAS_DECLARE_SCALAR (beta) ; \
    BLAS_ASSIGN (alpha, -1.0, 0.0) ; \
    BLAS_ASSIGN (beta, 1.0, 0.0) ; \
    (void) BLAS_GEMV_ROUTINE (BLAS_COLUMN_MAJOR_ORDER \
	BLAS_NO_TRANSPOSE, \
	BLAS_INT_SCALAR (m), BLAS_INT_SCALAR (n), \
	BLAS_SCALAR (alpha), \
	BLAS_ARRAY (A), BLAS_INT_SCALAR (d), \
	BLAS_ARRAY (x), BLAS_INT_SCALAR (incx), BLAS_SCALAR (beta), \
	BLAS_ARRAY (y), BLAS_INT_SCALAR (incy)) ; \
}


#endif	/* !defined (USE_NO_BLAS) } */




```

sparse-matrix/umfpack/umfpack_report_status.h:
```
/* ========================================================================== */
/* === umfpack_report_status ================================================ */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

void umfpack_di_report_status
(
    const double Control [UMFPACK_CONTROL],
    int status
) ;

void umfpack_dl_report_status
(
    const double Control [UMFPACK_CONTROL],
    long status
) ;

void umfpack_zi_report_status
(
    const double Control [UMFPACK_CONTROL],
    int status
) ;

void umfpack_zl_report_status
(
    const double Control [UMFPACK_CONTROL],
    long status
) ;

/*
double int Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    int status ;
    umfpack_di_report_status (Control, status) ;

double long Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    long status ;
    umfpack_dl_report_status (Control, status) ;

complex int Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    int status ;
    umfpack_zi_report_status (Control, status) ;

complex long Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    long status ;
    umfpack_zl_report_status (Control, status) ;

Purpose:

    Prints the status (return value) of other umfpack_* routines.

Arguments:

    double Control [UMFPACK_CONTROL] ;   Input argument, not modified.

	If a (double *) NULL pointer is passed, then the default control
	settings are used.  Otherwise, the settings are determined from the
	Control array.  See umfpack_*_defaults on how to fill the Control
	array with the default settings.  If Control contains NaN's, the
	defaults are used.  The following Control parameters are used:

	Control [UMFPACK_PRL]:  printing level.

	    0 or less: no output, even when an error occurs
	    1: error messages only
	    2 or more: print status, whether or not an error occured
	    4 or more: also print the UMFPACK Copyright
	    6 or more: also print the UMFPACK License
	    Default: 1

    Int status ;			Input argument, not modified.

	The return value from another umfpack_* routine.
*/

```

sparse-matrix/umfpack/umf_build_tuples.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL Int UMF_build_tuples
(
    NumericType *Numeric,
    WorkType *Work
) ;


```

sparse-matrix/umfpack/umf_mem_alloc_head_block.c:
```
/* ========================================================================== */
/* === UMF_mem_alloc_head_block ============================================= */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/* The UMF_mem_* routines manage the Numeric->Memory memory space. */

/* allocate nunits from head of Numeric->Memory.  No header allocated. */
/* Returns the index into Numeric->Memory if successful, or 0 on failure. */

#include "umf_internal.h"

GLOBAL Int UMF_mem_alloc_head_block
(
    NumericType *Numeric,
    Int nunits
)
{
    Int p, usage ;
    DEBUG2 (("GET  BLOCK: from head, size "ID" ", nunits)) ;

    ASSERT (Numeric) ;
    ASSERT (Numeric->Memory) ;

    if (nunits > (Numeric->itail - Numeric->ihead))
    {
	DEBUG2 ((" failed\n")) ;
	return (0) ;
    }

    /* return p as an offset from Numeric->Memory */
    p = Numeric->ihead ;
    Numeric->ihead += nunits ;

    DEBUG2 ((ID"\n", p)) ;
    usage = Numeric->ihead + Numeric->tail_usage ;
    Numeric->max_usage = MAX (Numeric->max_usage, usage) ;
    return (p) ;
}


```

sparse-matrix/umfpack/umfpack_report_numeric.h:
```
/* ========================================================================== */
/* === umfpack_report_numeric =============================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

int umfpack_di_report_numeric
(
    void *Numeric,
    const double Control [UMFPACK_CONTROL]
) ;

long umfpack_dl_report_numeric
(
    void *Numeric,
    const double Control [UMFPACK_CONTROL]
) ;

int umfpack_zi_report_numeric
(
    void *Numeric,
    const double Control [UMFPACK_CONTROL]
) ;

long umfpack_zl_report_numeric
(
    void *Numeric,
    const double Control [UMFPACK_CONTROL]
) ;

/*
double int Syntax:

    #include "umfpack.h"
    void *Numeric ;
    double Control [UMFPACK_CONTROL] ;
    int status ;
    status = umfpack_di_report_numeric (Numeric, Control) ;

double long Syntax:

    #include "umfpack.h"
    void *Numeric ;
    double Control [UMFPACK_CONTROL] ;
    long status ;
    status = umfpack_dl_report_numeric (Numeric, Control) ;

complex int Syntax:

    #include "umfpack.h"
    void *Numeric ;
    double Control [UMFPACK_CONTROL] ;
    int status ;
    status = umfpack_zi_report_numeric (Numeric, Control) ;

complex long Syntax:

    #include "umfpack.h"
    void *Numeric ;
    double Control [UMFPACK_CONTROL] ;
    long status ;
    status = umfpack_zl_report_numeric (Numeric, Control) ;

Purpose:

    Verifies and prints a Numeric object (the LU factorization, both its pattern
    numerical values, and permutation vectors P and Q).  This routine checks the
    object more carefully than the computational routines.  Normally, this check
    is not required, since umfpack_*_numeric either returns (void *) NULL, or a
    valid Numeric object.  However, if you suspect that your own code has
    corrupted the Numeric object (by overruning memory bounds, for example),
    then this routine might be able to detect a corrupted Numeric object.  Since
    this is a complex object, not all such user-generated errors are guaranteed
    to be caught by this routine.

Returns:

    UMFPACK_OK if Control [UMFPACK_PRL] <= 2 (the input is not checked).

    Otherwise:

    UMFPACK_OK if the Numeric object is valid.
    UMFPACK_ERROR_invalid_Numeric_object if the Numeric object is invalid.
    UMFPACK_ERROR_out_of_memory if out of memory.

Arguments:

    void *Numeric ;			Input argument, not modified.

	The Numeric object, which holds the numeric factorization computed by
	umfpack_*_numeric.

    double Control [UMFPACK_CONTROL] ;	Input argument, not modified.

	If a (double *) NULL pointer is passed, then the default control
	settings are used.  Otherwise, the settings are determined from the
	Control array.  See umfpack_*_defaults on how to fill the Control
	array with the default settings.  If Control contains NaN's, the
	defaults are used.  The following Control parameters are used:

	Control [UMFPACK_PRL]:  printing level.

	    2 or less: no output.  returns silently without checking anything.
	    3: fully check input, and print a short summary of its status
	    4: as 3, but print first few entries of the input
	    5: as 3, but print all of the input
	    Default: 1
*/

```

sparse-matrix/umfpack/umf_create_element.c:
```
/* ========================================================================== */
/* === UMF_create_element =================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    Factorization of a frontal matrix is complete.  Create a new element for
    later assembly into a subsequent frontal matrix.  Returns TRUE if
    successful, FALSE if out of memory.
*/

#include "umf_internal.h"
#include "umf_mem_alloc_element.h"
#include "umf_mem_alloc_tail_block.h"
#include "umf_mem_free_tail_block.h"
#include "umf_get_memory.h"
#include "umf_blas3_update.h"

GLOBAL Int UMF_create_element
(
    NumericType *Numeric,
    WorkType *Work
)
{
    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    Int j, col, row, *Fcols, *Frows, fnrows, fncols, *Cols, len, needunits, t1,
	t2, size, e, i, *E, *Fcpos, *Frpos, *Rows, eloc, fnrows_max, f,
	got_memory, *Row_tuples, *Row_degree, *Row_tlen, *Col_tuples, max_mark,
	*Col_degree, *Col_tlen, nn, n_row, n_col ;
    Entry *C, *Fcol ;
    Element *ep ;
    Unit *p, *Memory ;
    Tuple *tp, *tp1, *tp2, tuple, *tpend ;
#ifndef NDEBUG
    double gprob_save ;
#endif

    /* ---------------------------------------------------------------------- */
    /* apply pending updates, if any */
    /* ---------------------------------------------------------------------- */

    if (Work->fnpiv > 0)
    {
	UMF_blas3_update (Work) ;
    }

    /* ---------------------------------------------------------------------- */
    /* get parameters */
    /* ---------------------------------------------------------------------- */

    DEBUG2 (("FRONTAL WRAPUP\n")) ;
    Col_degree = Numeric->Cperm ;
    Row_degree = Numeric->Rperm ;
    Row_tuples = Numeric->Uip ;
    Row_tlen   = Numeric->Uilen ;
    Col_tuples = Numeric->Lip ;
    Col_tlen   = Numeric->Lilen ;
    n_row = Work->n_row ;
    n_col = Work->n_col ;
    nn = MAX (n_row, n_col) ;
    Fcols = Work->Fcols ;
    Frows = Work->Frows ;
    Fcpos = Work->Fcpos ;
    Frpos = Work->Frpos ;
    Memory = Numeric->Memory ;
    fncols = Work->fncols ;
    fnrows = Work->fnrows ;
    fnrows_max = Work->fnrows_max ;

    tp = (Tuple *) NULL ;
    tp1 = (Tuple *) NULL ;
    tp2 = (Tuple *) NULL ;

    /* ---------------------------------------------------------------------- */
    /* add the current frontal matrix to the degrees of each column, */
    /* and place the modified columns back in the degree lists */
    /* ---------------------------------------------------------------------- */

    for (j = 0 ; j < fncols ; j++)
    {
	/* add the current frontal matrix to the degree */
	ASSERT (Fcols [j] >= 0 && Fcols [j] < n_col) ;
	Col_degree [Fcols [j]] += fnrows ;
    }

    /* ---------------------------------------------------------------------- */
    /* add the current frontal matrix to the degrees of each row */
    /* ---------------------------------------------------------------------- */

    for (i = 0 ; i < fnrows ; i++)
    {
	/* add the current frontal matrix to the degree */
	ASSERT (Frows [i] >= 0 && Frows [i] < n_row) ;
	Row_degree [Frows [i]] += fncols ;
    }

    /* ---------------------------------------------------------------------- */
    /* Reset the external degree counters */
    /* ---------------------------------------------------------------------- */

    E = Work->E ;
    max_mark = MAX_MARK (nn) ;

    if (!Work->pivcol_in_front)
    {
	/* clear the external column degrees. no more Usons of current front */
	Work->cdeg0 += (nn + 1) ;
	if (Work->cdeg0 >= max_mark)
	{
	    /* guard against integer overflow.  This is very rare */
	    Work->cdeg0 = 1 ;
	    for (e = 0 ; e <= Work->nel ; e++)
	    {
		if (E [e])
		{
		    ep = (Element *) (Memory + E [e]) ;
		    ep->cdeg = 0 ;
		}
	    }
	}
    }

    if (!Work->pivrow_in_front)
    {
	/* clear the external row degrees.  no more Lsons of current front */
	Work->rdeg0 += (nn + 1) ;
	if (Work->rdeg0 >= max_mark)
	{
	    /* guard against integer overflow.  This is very rare */
	    Work->rdeg0 = 1 ;
	    for (e = 0 ; e <= Work->nel ; e++)
	    {
		if (E [e])
		{
		    ep = (Element *) (Memory + E [e]) ;
		    ep->rdeg = 0 ;
		}
	    }
	}
    }

    /* ---------------------------------------------------------------------- */
    /* clear row/col offsets */
    /* ---------------------------------------------------------------------- */

    if (!Work->pivrow_in_front)
    {
	for (j = 0 ; j < fncols ; j++)
	{
	    Fcpos [Fcols [j]] = EMPTY ;
	}
    }

    if (!Work->pivcol_in_front)
    {
	for (i = 0 ; i < fnrows ; i++)
	{
	    Frpos [Frows [i]] = EMPTY ;
	}
    }

    if (fncols <= 0 || fnrows <= 0)
    {
	/* no element to create */
	DEBUG2 (("Element evaporation\n")) ;
	Work->prior_element = EMPTY ;
	return (TRUE) ;
    }

    /* ---------------------------------------------------------------------- */
    /* create element for later assembly */
    /* ---------------------------------------------------------------------- */

    needunits = 0 ;
    got_memory = FALSE ;
    eloc = UMF_mem_alloc_element (Numeric, fnrows, fncols, &Rows, &Cols, &C,
	&needunits, &ep) ;

    if (!eloc)
    {
	/* do garbage collection, realloc, and try again */
	if (!UMF_get_memory (Numeric, Work, needunits))
	{
	    return (FALSE) ;	/* out of memory */
	}
	got_memory = TRUE ;
	Memory = Numeric->Memory ;
#ifndef NDEBUG
	gprob_save = UMF_gprob ;
	UMF_gprob = 0 ;
#endif
	eloc = UMF_mem_alloc_element (Numeric, fnrows, fncols, &Rows, &Cols, &C,
	    &needunits, &ep) ;
	ASSERT (eloc >= 0) ;
	if (!eloc)
	{
	    return (FALSE) ;	/* out of memory */
	}
#ifndef NDEBUG
	UMF_gprob = gprob_save ;
#endif
    }

    e = ++(Work->nel) ;	/* get the name of this new frontal matrix */
    Work->prior_element = e ;
    DEBUG8 (("wrapup e "ID" nel "ID"\n", e, Work->nel)) ;

    ASSERT (e > 0 && e <= n_col + MIN (n_row,n_col)) ;
    ASSERT (E [e] == 0) ;
    E [e] = eloc ;

    if (Work->pivcol_in_front)
    {
	/* the new element is a Uson of the next frontal matrix */
	ep->cdeg = Work->cdeg0 ;
    }

    if (Work->pivrow_in_front)
    {
	/* the new element is an Lson of the next frontal matrix */
	ep->rdeg = Work->rdeg0 ;
    }

    /* ---------------------------------------------------------------------- */
    /* copy frontal matrix into the new element */
    /* ---------------------------------------------------------------------- */

    for (i = 0 ; i < fnrows ; i++)
    {
	Rows [i] = Frows [i] ;
    }
    for (i = 0 ; i < fncols ; i++)
    {
	Cols [i] = Fcols [i] ;
    }
    Fcol = Work->Fx ;
    for (j = 0 ; j < fncols ; j++)
    {
	for (i = 0 ; i < fnrows ; i++)
	{
	    C [i] = Fcol [i] ;
	}
	Fcol += fnrows_max ;
	C += fnrows ;
    }

    /* ---------------------------------------------------------------------- */
    /* add tuples for the new element */
    /* ---------------------------------------------------------------------- */

    tuple.e = e ;

    if (got_memory)
    {

	/* ------------------------------------------------------------------ */
	/* UMF_get_memory ensures enough space exists for each new tuple */
	/* ------------------------------------------------------------------ */

	/* place (e,f) in the element list of each column */
	for (tuple.f = 0 ; tuple.f < fncols ; tuple.f++)
	{
	    col = Fcols [tuple.f] ;
	    ASSERT (col >= 0 && col < n_col) ;
	    ASSERT (NON_PIVOTAL_COL (col)) ;
	    ASSERT (Col_tuples [col]) ;
	    tp = ((Tuple *) (Memory + Col_tuples [col])) + Col_tlen [col]++ ;
	    *tp = tuple ;
	}

	/* ------------------------------------------------------------------ */

	/* place (e,f) in the element list of each row */
	for (tuple.f = 0 ; tuple.f < fnrows ; tuple.f++)
	{
	    row = Frows [tuple.f] ;
	    ASSERT (row >= 0 && row < n_row) ;
	    ASSERT (NON_PIVOTAL_ROW (row)) ;
	    ASSERT (Row_tuples [row]) ;
	    tp = ((Tuple *) (Memory + Row_tuples [row])) + Row_tlen [row]++ ;
	    *tp = tuple ;
	}

    }
    else
    {

	/* ------------------------------------------------------------------ */
	/* might not have enough space for each tuple */
	/* ------------------------------------------------------------------ */

	/* ------------------------------------------------------------------ */
	/* place (e,f) in the element list of each column */
	/* ------------------------------------------------------------------ */

	for (tuple.f = 0 ; tuple.f < fncols ; tuple.f++)
	{
	    col = Fcols [tuple.f] ;
	    ASSERT (col >= 0 && col < n_col) ;
	    ASSERT (NON_PIVOTAL_COL (col)) ;
	    t1 = Col_tuples [col] ;
	    DEBUG1 (("Placing on col:"ID" , tuples at "ID"\n",
		col, Col_tuples [col])) ;

	    size = 0 ;
	    len = 0 ;

	    if (t1)
	    {
		p = Memory + t1 ;
		tp = (Tuple *) p ;
		size = GET_BLOCK_SIZE (p) ;
		len = Col_tlen [col] ;
		tp2 = tp + len ;
	    }

	    needunits = UNITS (Tuple, len + 1) ;
	    DEBUG1 (("len: "ID" size: "ID" needunits: "ID"\n",
		len, size, needunits));

	    if (needunits > size && t1)
	    {
		/* prune the tuples */
		tp1 = tp ;
		tp2 = tp ;
		tpend = tp + len ;
		for ( ; tp < tpend ; tp++)
		{
		    e = tp->e ;
		    ASSERT (e > 0 && e <= Work->nel) ;
		    if (!E [e]) continue ;   /* element already deallocated */
		    f = tp->f ;
		    p = Memory + E [e] ;
		    ep = (Element *) p ;
		    p += UNITS (Element, 1) ;
		    Cols = (Int *) p ;
		    ;
		    if (Cols [f] == EMPTY) continue ;	/* already assembled */
		    ASSERT (col == Cols [f]) ;
		    *tp2++ = *tp ;	/* leave the tuple in the list */
		}
		len = tp2 - tp1 ;
		Col_tlen [col] = len ;
		needunits = UNITS (Tuple, len + 1) ;
	    }

	    if (needunits > size)
	    {
		/* no room exists - reallocate elsewhere */
		DEBUG1 (("REALLOCATE Col: "ID", size "ID" to "ID"\n",
		    col, size, 2*needunits)) ;
#ifndef NDEBUG
		UMF_allocfail = FALSE ;
		if (UMF_gprob > 0)  /* a double relop, but ignore NaN case */
		{
		    double rrr = ((double) (rand ( ))) /
		    	(((double) RAND_MAX) + 1) ;
		    DEBUG1 (("Check random %e %e\n", rrr, UMF_gprob)) ;
		    UMF_allocfail = rrr < UMF_gprob ;
		    if (UMF_allocfail) DEBUG1 (("Random gar. (col tuple)\n")) ;
		}
#endif
		needunits = MIN (2*needunits, (Int) UNITS (Tuple, nn)) ;
		t2 = UMF_mem_alloc_tail_block (Numeric, needunits) ;
		if (!t2)
		{
		    /* get memory, reconstruct all tuple lists, and return */
		    return (UMF_get_memory (Numeric, Work, 0)) ;
		}
		Col_tuples [col] = t2 ;
		tp2 = (Tuple *) (Memory + t2) ;
		if (t1)
		{
		    for (i = 0 ; i < len ; i++)
		    {
			*tp2++ = *tp1++ ;
		    }
		    UMF_mem_free_tail_block (Numeric, t1) ;
		}
	    }

	    /* place the new (e,f) tuple in the element list of the column */
	    Col_tlen [col]++ ;
	    *tp2 = tuple ;
	}

	/* ------------------------------------------------------------------ */
	/* place (e,f) in the element list of each row */
	/* ------------------------------------------------------------------ */

	for (tuple.f = 0 ; tuple.f < fnrows ; tuple.f++)
	{
	    row = Frows [tuple.f] ;
	    ASSERT (row >= 0 && row < n_row) ;
	    ASSERT (NON_PIVOTAL_ROW (row)) ;
	    t1 = Row_tuples [row] ;
	    DEBUG1 (("Placing on row:"ID" , tuples at "ID"\n",
		row, Row_tuples [row])) ;

	    size = 0 ;
	    len = 0 ;
	    if (t1)
	    {
		p = Memory + t1 ;
		tp = (Tuple *) p ;
		size = GET_BLOCK_SIZE (p) ;
		len = Row_tlen [row] ;
		tp2 = tp + len ;
	    }

	    needunits = UNITS (Tuple, len + 1) ;
	    DEBUG1 (("len: "ID" size: "ID" needunits: "ID"\n",
		len, size, needunits)) ;

	    if (needunits > size && t1)
	    {
		/* prune the tuples */
		tp1 = tp ;
		tp2 = tp ;
		tpend = tp + len ;
		for ( ; tp < tpend ; tp++)
		{
		    e = tp->e ;
		    ASSERT (e > 0 && e <= Work->nel) ;
		    if (!E [e])
		    {
			continue ;	/* element already deallocated */
		    }
		    f = tp->f ;
		    p = Memory + E [e] ;
		    ep = (Element *) p ;
		    p += UNITS (Element, 1) ;
		    Cols = (Int *) p ;
		    Rows = Cols + (ep->ncols) ;
		    if (Rows [f] == EMPTY) continue ;	/* already assembled */
		    ASSERT (row == Rows [f]) ;
		    *tp2++ = *tp ;	/* leave the tuple in the list */
		}
		len = tp2 - tp1 ;
		Row_tlen [row] = len ;
		needunits = UNITS (Tuple, len + 1) ;
	    }

	    if (needunits > size)
	    {
		/* no room exists - reallocate elsewhere */
		DEBUG1 (("REALLOCATE Row: "ID", size "ID" to "ID"\n",
		    row, size, 2*needunits)) ;
#ifndef NDEBUG
		UMF_allocfail = FALSE ;
		if (UMF_gprob > 0)  /* a double relop, but ignore NaN case */
		{
		    double rrr = ((double) (rand ( ))) /
		    	(((double) RAND_MAX) + 1) ;
		    DEBUG1 (("Check random %e %e\n", rrr, UMF_gprob)) ;
		    UMF_allocfail = rrr < UMF_gprob ;
		    if (UMF_allocfail) DEBUG1 (("Random gar. (row tuple)\n")) ;
		}
#endif
		needunits = MIN (2*needunits, (Int) UNITS (Tuple, nn)) ;
		t2 = UMF_mem_alloc_tail_block (Numeric, needunits) ;
		if (!t2)
		{
		    /* get memory, reconstruct all tuple lists, and return */
		    return (UMF_get_memory (Numeric, Work, 0)) ;
		}
		Row_tuples [row] = t2 ;
		tp2 = (Tuple *) (Memory + t2) ;
		if (t1)
		{
		    for (i = 0 ; i < len ; i++)
		    {
			*tp2++ = *tp1++ ;
		    }
		    UMF_mem_free_tail_block (Numeric, t1) ;
		}
	    }

	    /* place the new (e,f) tuple in the element list of the row */
	    Row_tlen [row]++ ;
	    *tp2 = tuple ;
	}

    }

    /* ---------------------------------------------------------------------- */

#ifndef NDEBUG
    DEBUG1 (("Done extending\nFINAL: element row pattern: len="ID"\n", fncols));
    for (j = 0 ; j < fncols ; j++) DEBUG1 ((""ID"\n", Fcols [j])) ;
    DEBUG1 (("FINAL: element col pattern:  len="ID"\n", fnrows)) ;
    for (j = 0 ; j < fnrows ; j++) DEBUG1 ((""ID"\n", Frows [j])) ;
    for (j = 0 ; j < fncols ; j++)
    {
	col = Fcols [j] ;
	ASSERT (col >= 0 && col < n_col) ;
	UMF_dump_rowcol (1, Numeric, Work, col, TRUE) ;
    }
    for (j = 0 ; j < fnrows ; j++)
    {
	row = Frows [j] ;
	ASSERT (row >= 0 && row < n_row) ;
	UMF_dump_rowcol (0, Numeric, Work, row, TRUE) ;
    }

    if (n_row < 1000 && n_col < 1000)
    {
	UMF_dump_memory (Numeric) ;
    }

    DEBUG1 (("New element, after filling with stuff: "ID"\n", e)) ;
    UMF_dump_element (Numeric, Work, e, TRUE) ;

    if (nn < 1000)
    {
	DEBUG4 (("Matrix dump, after New element: "ID"\n", e)) ;
	UMF_dump_matrix (Numeric, Work, TRUE) ;
    }

    DEBUG3 (("FRONTAL WRAPUP DONE\n")) ;
#endif

    return (TRUE) ;
}


```

sparse-matrix/umfpack/umf_symbolic_usage.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL double UMF_symbolic_usage
(
    Int n_row,
    Int n_col,
    Int nchains,
    Int nfr
) ;


```

sparse-matrix/umfpack/umf_garbage_collection.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL void UMF_garbage_collection
(
    NumericType *Numeric,
    WorkType *Work
) ;


```

sparse-matrix/umfpack/umf_utsolve.c:
```
/* ========================================================================== */
/* === UMF_utsolve ========================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*  solves U'x = b or U.'x=b, where U is the upper triangular factor of a */
/*  matrix.  B is overwritten with the solution X. */
/*  Returns the floating point operation count */

#include "umf_internal.h"

GLOBAL double
#ifdef CONJUGATE_SOLVE
UMF_uhsolve			/* solve U'x=b  (complex conjugate transpose) */
#else
UMF_utsolve			/* solve U.'x=b (array transpose) */
#endif
(
    NumericType *Numeric,
    Entry X [ ],		/* b on input, solution x on output */
    Int Pattern [ ]		/* a work array of size n */
)
{
    /* ---------------------------------------------------------------------- */
    /* local variables */
    /* ---------------------------------------------------------------------- */

    Int k, deg, j, *ip, col, *Upos, *Uilen, kstart, kend, up,
	*Uip, n, uhead, ulen, pos, npiv ;
    Entry *xp, xk, *D ;

    /* ---------------------------------------------------------------------- */
    /* get parameters */
    /* ---------------------------------------------------------------------- */

    if (Numeric->n_row != Numeric->n_col) return (0.) ;
    n = Numeric->n_row ;
    npiv = Numeric->npiv ;
    Upos = Numeric->Upos ;
    Uilen = Numeric->Uilen ;
    Uip = Numeric->Uip ;
    D = Numeric->D ;
    kend = 0 ;

#ifndef NDEBUG
    DEBUG4 (("Utsolve start: npiv "ID" n "ID"\n", npiv, n)) ;
    for (j = 0 ; j < n ; j++)
    {
	DEBUG4 (("Utsolve start "ID": ", j)) ;
	EDEBUG4 (X [j]) ;
	DEBUG4 (("\n")) ;
    }
#endif

    for (kstart = 0 ; kstart < npiv ; kstart = kend + 1)
    {

	/* ------------------------------------------------------------------ */
	/* find the end of this Uchain */
	/* ------------------------------------------------------------------ */

	DEBUG4 (("kstart "ID" kend "ID"\n", kstart, kend)) ;
	/* for (kend = kstart ; kend < npiv && Uip [kend+1] > 0 ; kend++) ; */
	kend = kstart ;
	while (kend < npiv && Uip [kend+1] > 0)
	{
	    kend++ ;
	}

	/* ------------------------------------------------------------------ */
	/* scan the whole Uchain to find the pattern of the first row of U */
	/* ------------------------------------------------------------------ */

	k = kend+1 ;
	DEBUG4 (("\nKend "ID" K "ID"\n", kend, k)) ;

	/* ------------------------------------------------------------------ */
	/* start with last row in Uchain of U in Pattern [0..deg-1] */
	/* ------------------------------------------------------------------ */

	if (k == npiv)
	{
	    deg = Numeric->ulen ;
	    if (deg > 0)
	    {
		/* make last pivot row of U (singular matrices only) */
		for (j = 0 ; j < deg ; j++)
		{
		    Pattern [j] = Numeric->Upattern [j] ;
		}
	    }
	}
	else
	{
	    ASSERT (k >= 0 && k < npiv) ;
	    up = -Uip [k] ;
	    ASSERT (up > 0) ;
	    deg = Uilen [k] ;
	    DEBUG4 (("end of chain for row of U "ID" deg "ID"\n", k-1, deg)) ;
	    ip = (Int *) (Numeric->Memory + up) ;
	    for (j = 0 ; j < deg ; j++)
	    {
		col = *ip++ ;
		DEBUG4 (("  k "ID" col "ID"\n", k-1, col)) ;
		ASSERT (k <= col) ;
		Pattern [j] = col ;
	    }
	}

	/* empty the stack at the bottom of Pattern */
	uhead = n ;

	for (k = kend ; k > kstart ; k--)
	{
	    /* Pattern [0..deg-1] is the pattern of row k of U */

	    /* -------------------------------------------------------------- */
	    /* make row k-1 of U in Pattern [0..deg-1] */
	    /* -------------------------------------------------------------- */

	    ASSERT (k >= 0 && k < npiv) ;
	    ulen = Uilen [k] ;
	    /* delete, and push on the stack */
	    for (j = 0 ; j < ulen ; j++)
	    {
		ASSERT (uhead >= deg) ;
		Pattern [--uhead] = Pattern [--deg] ;
	    }
	    DEBUG4 (("middle of chain for row of U "ID" deg "ID"\n", k, deg)) ;
	    ASSERT (deg >= 0) ;

	    pos = Upos [k] ;
	    if (pos != EMPTY)
	    {
		/* add the pivot column */
		DEBUG4 (("k "ID" add pivot entry at position "ID"\n", k, pos)) ;
		ASSERT (pos >= 0 && pos <= deg) ;
		Pattern [deg++] = Pattern [pos] ;
		Pattern [pos] = k ;
	    }
	}

	/* Pattern [0..deg-1] is now the pattern of the first row in Uchain */

	/* ------------------------------------------------------------------ */
	/* solve using this Uchain, in reverse order */
	/* ------------------------------------------------------------------ */

	DEBUG4 (("Unwinding Uchain\n")) ;
	for (k = kstart ; k <= kend ; k++)
	{

	    /* -------------------------------------------------------------- */
	    /* construct row k */
	    /* -------------------------------------------------------------- */

	    ASSERT (k >= 0 && k < npiv) ;
	    pos = Upos [k] ;
	    if (pos != EMPTY)
	    {
		/* remove the pivot column */
		DEBUG4 (("k "ID" add pivot entry at position "ID"\n", k, pos)) ;
		ASSERT (k > kstart) ;
		ASSERT (pos >= 0 && pos < deg) ;
		ASSERT (Pattern [pos] == k) ;
		Pattern [pos] = Pattern [--deg] ;
	    }

	    up = Uip [k] ;
	    ulen = Uilen [k] ;
	    if (k > kstart)
	    {
		/* concatenate the deleted pattern; pop from the stack */
		for (j = 0 ; j < ulen ; j++)
		{
		    ASSERT (deg <= uhead && uhead < n) ;
		    Pattern [deg++] = Pattern [uhead++] ;
		}
		DEBUG4 (("middle of chain, row of U "ID" deg "ID"\n", k, deg)) ;
		ASSERT (deg >= 0) ;
	    }

	    /* -------------------------------------------------------------- */
	    /* use row k of U */
	    /* -------------------------------------------------------------- */

	    /* Go ahead and divide by zero if D [k] is zero. */
#ifdef CONJUGATE_SOLVE
	    /* xk = X [k] / conjugate (D [k]) ; */
	    DIV_CONJ (xk, X [k], D [k]) ;
#else
	    /* xk = X [k] / D [k] ; */
	    DIV (xk, X [k], D [k]) ;
#endif
	    X [k] = xk ;

	    if (IS_NONZERO (xk))
	    {
		if (k == kstart)
		{
		    up = -up ;
		    xp = (Entry *) (Numeric->Memory + up + UNITS (Int, ulen)) ;
		}
		else
		{
		    xp = (Entry *) (Numeric->Memory + up) ;
		}
		for (j = 0 ; j < deg ; j++)
		{
		    DEBUG4 (("  k "ID" col "ID" value", k, Pattern [j])) ;
		    EDEBUG4 (*xp) ;
		    DEBUG4 (("\n")) ;
#ifdef CONJUGATE_SOLVE
		    /* X [Pattern [j]] -= xk * conjugate (*xp) ; */
		    MULT_SUB_CONJ (X [Pattern [j]], xk, *xp) ;
#else
		    /* X [Pattern [j]] -= xk * (*xp) ; */
		    MULT_SUB (X [Pattern [j]], xk, *xp) ;
#endif
		    xp++ ;
		}
	    }
	}
	ASSERT (uhead == n) ;
    }

    for (k = npiv ; k < n ; k++)
    {
	/* Note that D [k] is identically zero. */
	ASSERT (IS_ZERO (D [k])) ;
	/* For conjugate solve, D [k] == conjugate (D [k]), in this case */
	/* xk = X [k] / D [k] ; */
	DIV (xk, X [k], D [k]) ;
	X [k] = xk ;
    }

#ifndef NDEBUG
    for (j = 0 ; j < n ; j++)
    {
	DEBUG4 (("Utsolve done "ID": ", j)) ;
	EDEBUG4 (X [j]) ;
	DEBUG4 (("\n")) ;
    }
    DEBUG4 (("Utsolve done.\n")) ;
#endif

    return (DIV_FLOPS * ((double) n) + MULTSUB_FLOPS * ((double) Numeric->unz));
}


```

sparse-matrix/umfpack/umf_tuple_lengths.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL Int UMF_tuple_lengths
(
    NumericType *Numeric,
    WorkType *Work,
    double *dusage
) ;


```

sparse-matrix/umfpack/umf_blas3_update.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL void UMF_blas3_update
(
    WorkType *Work
) ;


```

sparse-matrix/umfpack/umf_build_tuples_usage.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL Int UMF_build_tuples_usage
(
    const Int Col_tlen [ ],
    const Int Col_degree [ ],
    const Int Row_tlen [ ],
    const Int Row_degree [ ],
    Int n_row,
    Int n_col,
    double *dusage
) ;


```

sparse-matrix/umfpack/umfpack_col_to_triplet.h:
```
/* ========================================================================== */
/* === umfpack_col_to_triplet =============================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

int umfpack_di_col_to_triplet
(
    int n_col,
    const int Ap [ ],
    int Tj [ ]
) ;

long umfpack_dl_col_to_triplet
(
    long n_col,
    const long Ap [ ],
    long Tj [ ]
) ;

int umfpack_zi_col_to_triplet
(
    int n_col,
    const int Ap [ ],
    int Tj [ ]
) ;

long umfpack_zl_col_to_triplet
(
    long n_col,
    const long Ap [ ],
    long Tj [ ]
) ;

/*
double int Syntax:

    #include "umfpack.h"
    int n_col, *Tj, *Ap, status ;
    status = umfpack_di_col_to_triplet (n_col, Ap, Tj) ;

double long Syntax:

    #include "umfpack.h"
    long n_col, *Tj, *Ap, status ;
    status = umfpack_dl_col_to_triplet (n_col, Ap, Tj) ;

complex int Syntax:

    #include "umfpack.h"
    int n_col, *Tj, *Ap, status ;
    status = umfpack_zi_col_to_triplet (n_col, Ap, Tj) ;

complex long Syntax:

    #include "umfpack.h"
    long n_col, *Tj, *Ap, status ;
    status = umfpack_zl_col_to_triplet (n_col, Ap, Tj) ;

Purpose:

    Converts a column-oriented matrix to a triplet form.  Only the column
    pointers, Ap, are required, and only the column indices of the triplet form
    are constructed.   This routine is the opposite of umfpack_*_triplet_to_col.
    The matrix may be singular and/or rectangular.  Analogous to [i, Tj, x] =
    find (A) in MATLAB, except that zero entries present in the column-form of
    A are present in the output, and i and x are not created (those are just Ai
    and Ax+Az*1i, respectively, for a column-form matrix A).

Returns:

    UMFPACK_OK if successful
    UMFPACK_ERROR_argument_missing if Ap or Tj is missing
    UMFPACK_ERROR_n_nonpositive if n_col <= 0
    UMFPACK_ERROR_Ap0_nonzero if Ap [0] != 0
    UMFPACK_ERROR_nz_negative if Ap [n_col] < 0
    UMFPACK_ERROR_col_length_negative if Ap [j] > Ap [j+1] for any j in the
	range 0 to n-1.
    Unsorted columns and duplicate entries do not cause an error (these would
    only be evident by examining Ai).  Empty rows and columns are OK.

Arguments:

    Int n_col ;		Input argument, not modified.

	A is an n_row-by-n_col matrix.  Restriction: n_col > 0.
	(n_row is not required)

    Int Ap [n_col+1] ;	Input argument, not modified.

	The column pointers of the column-oriented form of the matrix.  See
	umfpack_*_*symbolic for a description.  The number of entries in
	the matrix is nz = Ap [n_col].  Restrictions on Ap are the same as those
	for umfpack_*_transpose.  Ap [0] must be zero, nz must be >= 0, and
	Ap [j] <= Ap [j+1] and Ap [j] <= Ap [n_col] must be true for all j in
	the range 0 to n_col-1.  Empty columns are OK (that is, Ap [j] may equal
	Ap [j+1] for any j in the range 0 to n_col-1).

    Int Tj [nz] ;	Output argument.

	Tj is an integer array of size nz on input, where nz = Ap [n_col].
	Suppose the column-form of the matrix is held in Ap, Ai, Ax, and Az
	(see umfpack_*_*symbolic for a description).  Then on output, the
	triplet form of the same matrix is held in Ai (row indices), Tj (column
	indices), and Ax (numerical values).  Note, however, that this routine
	does not require Ai and Ax (or Az for the complex version) in order to
	do the conversion.
*/

```

sparse-matrix/umfpack/umfpack_defaults.c:
```
/* ========================================================================== */
/* === UMFPACK_defaults ===================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    User-callable.  Sets default control parameters.  See umfpack_defaults.h
    for details.
*/

#include "umf_internal.h"

GLOBAL void UMFPACK_defaults
(
    double Control [UMFPACK_CONTROL]
)
{
    Int i ;

    if (!Control)
    {
	/* silently return if no Control array */
	return ;
    }

    for (i = 0 ; i < UMFPACK_CONTROL ; i++)
    {
	Control [i] = 0 ;
    }

    /* ---------------------------------------------------------------------- */
    /* default control settings: can be modified at run-time */
    /* ---------------------------------------------------------------------- */

    /* used in UMFPACK_report_* routines: */
    Control [UMFPACK_PRL] = UMFPACK_DEFAULT_PRL ;

    /* used in UMFPACK_*symbolic: */
    Control [UMFPACK_DENSE_ROW] = UMFPACK_DEFAULT_DENSE_ROW ;
    Control [UMFPACK_DENSE_COL] = UMFPACK_DEFAULT_DENSE_COL ;

    /* used in UMFPACK_numeric: */
    Control [UMFPACK_PIVOT_TOLERANCE] = UMFPACK_DEFAULT_PIVOT_TOLERANCE ;
    Control [UMFPACK_BLOCK_SIZE] = UMFPACK_DEFAULT_BLOCK_SIZE ;
    Control [UMFPACK_RELAXED_AMALGAMATION] =
	UMFPACK_DEFAULT_RELAXED_AMALGAMATION ;
    Control [UMFPACK_RELAXED2_AMALGAMATION] =
	UMFPACK_DEFAULT_RELAXED2_AMALGAMATION ;
    Control [UMFPACK_RELAXED3_AMALGAMATION] =
	UMFPACK_DEFAULT_RELAXED3_AMALGAMATION ;
    Control [UMFPACK_ALLOC_INIT] = UMFPACK_DEFAULT_ALLOC_INIT ;

    /* used in UMFPACK_*solve: */
    Control [UMFPACK_IRSTEP] = UMFPACK_DEFAULT_IRSTEP ;

    /* ---------------------------------------------------------------------- */
    /* compile-time settings: cannot be modified at run-time */
    /* ---------------------------------------------------------------------- */

#ifdef USE_NO_BLAS
    /* use externally-provided BLAS (dgemm, dger, dgemv, zgemm, zgeru, zgemv) */
    Control [UMFPACK_COMPILED_WITH_BLAS] = 1 ;
#else
    /* do not use the BLAS - use in-line C code instead */
    Control [UMFPACK_COMPILED_WITH_BLAS] = 0 ;
#endif

#ifdef MATLAB_MEX_FILE
    /* use mxMalloc, mxFree, mxRealloc, and mexPrintf */
    /* use mxAssert if debugging is enabled */
    Control [UMFPACK_COMPILED_FOR_MATLAB] = 1 ;
#else
#ifdef MATHWORKS
    /* use internal utMalloc, utFree, utRealloc, and utPrintf routines. */
    /* use utDivideComplex and utFdlibm_hypot for complex version. */
    /* use utAssert if debugging is enabled. */
    Control [UMFPACK_COMPILED_FOR_MATLAB] = 2 ;
#else
    /* use ANSI C malloc, free, realloc, and print */
    /* use ANSI C assert if debugging is enabled */
    Control [UMFPACK_COMPILED_FOR_MATLAB] = 0 ;
#endif
#endif

#ifdef GETRUSAGE
    /* uses the non-standard getrusage to get CPU time (Solaris) */
    Control [UMFPACK_COMPILED_WITH_GETRUSAGE] = 1 ;
#else
    /* uses the ANSI standard clock routine to get CPU time */
    /* this may wrap around */
    Control [UMFPACK_COMPILED_WITH_GETRUSAGE] = 0 ;
#endif

#ifndef NDEBUG
    /* UMFPACK is compiled in debug mode. */
    /* This is exceedingly slow. */
    PRINTF (("UMFPACK is running in debug mode.  This is very slow!\n")) ;
    Control [UMFPACK_COMPILED_IN_DEBUG_MODE] = 1 ;
#else
    /* UMFPACK is compiled in normal (non-debug) mode */
    Control [UMFPACK_COMPILED_IN_DEBUG_MODE] = 0 ;
#endif

}


```

sparse-matrix/umfpack/umf_valid_numeric.c:
```
/* ========================================================================== */
/* === UMF_valid_numeric ==================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/* Returns TRUE if the Numeric object is valid, FALSE otherwise. */
/* Does not check everything.  UMFPACK_report_numeric checks more. */

#include "umf_internal.h"

GLOBAL Int UMF_valid_numeric
(
    NumericType *Numeric
)
{
    /* This routine does not check the contents of the individual arrays, so */
    /* it can miss some errors.  All it checks for is the presence of the */
    /* arrays, and the Numeric "valid" entry. */

    if (!Numeric)
    {
	return (FALSE) ;
    }

    if (Numeric->valid != NUMERIC_VALID)
    {
	/* Numeric does not point to a NumericType object */
	return (FALSE) ;
    }

    if (Numeric->n_row <= 0 || Numeric->n_col <= 0 || !Numeric->D ||
	!Numeric->Rperm || !Numeric->Cperm ||
	!Numeric->Lpos || !Numeric->Upos ||
	!Numeric->Lilen || !Numeric->Uilen || !Numeric->Lip || !Numeric->Uip ||
	!Numeric->Memory || (Numeric->ulen > 0 && !Numeric->Upattern))
    {
	return (FALSE) ;
    }

    return (TRUE) ;
}


```

sparse-matrix/umfpack/umfpack_timer.c:
```
/* ========================================================================== */
/* === umfpack_timer ======================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
    User-callable.  Returns the time in seconds used by the process.  BE
    CAREFUL:  if you compare the run time of UMFPACK with other sparse matrix
    packages, be sure to use the same timer.  See umfpack_timer.h for details.
*/

#ifdef GETRUSAGE

/* -------------------------------------------------------------------------- */
/* use getrusage for accurate process times (and no overflow) */
/* -------------------------------------------------------------------------- */

/*
    This works under Solaris, SGI Irix, Linux, IBM RS 6000 (AIX), and Compaq
    Alpha.  It might work on other Unix systems, too.  Includes both the "user
    time" and the "system time".  The system time is the time spent by the
    operating system on behalf of the process, and thus should be charged to
    the process.
*/

#include <sys/time.h>
#include <sys/resource.h>

double umfpack_timer ( void )
{
    struct rusage ru ;
    double user_time, sys_time ;

    (void) getrusage (RUSAGE_SELF, &ru) ;

    user_time =
    ru.ru_utime.tv_sec			/* user time (seconds) */
    + 1e-6 * ru.ru_utime.tv_usec ;	/* user time (microseconds) */

    sys_time =
    ru.ru_stime.tv_sec			/* system time (seconds) */
    + 1e-6 * ru.ru_stime.tv_usec ;	/* system time (microseconds) */

    return (user_time + sys_time) ;
}

#else

/* -------------------------------------------------------------------------- */
/* Generic ANSI C: use the ANSI clock function */
/* -------------------------------------------------------------------------- */

/* This is portable, but may overflow.  On Sun Solaris, when compiling in */
/* 32-bit mode, the overflow occurs in only 2147 seconds (about 36 minutes). */

#include <time.h>

double umfpack_timer ( void )
{
    return (((double) (clock ( ))) / ((double) (CLOCKS_PER_SEC))) ;
}

#endif


```

sparse-matrix/umfpack/umf_realloc.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL void *UMF_realloc
(
    void *p,
    Int n_objects,
    size_t size_of_object
) ;


```

sparse-matrix/umfpack/umf_report_perm.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL Int UMF_report_perm
(
    Int n,
    const Int P [ ],
    Int W [ ],
    Int prl,
    Int user
) ;


```

sparse-matrix/umfpack/License:
```
UMFPACK Version 4.0 (Apr 11, 2002).  Copyright (c) 2002 by Timothy A.
Davis.  All Rights Reserved.

UMFPACK License:

    Your use or distribution of UMFPACK or any modified version of
    UMFPACK implies that you agree to this License.

    THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
    EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.

    Permission is hereby granted to use or copy this program, provided
    that the Copyright, this License, and the Availability of the original
    version is retained on all copies.  User documentation of any code that
    uses UMFPACK or any modified version of UMFPACK code must cite the
    Copyright, this License, the Availability note, and "Used by permission."
    Permission to modify the code and to distribute modified code is granted,
    provided the Copyright, this License, and the Availability note are
    retained, and a notice that the code was modified is included.  This
    software was developed with support from the National Science Foundation,
    and is provided to you free of charge.

Availability:

    http://www.cise.ufl.edu/research/sparse/umfpack


```

sparse-matrix/umfpack/umf_colamd.c:
```
/* ========================================================================== */
/* === UMF_colamd =========================================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

/*
UMF_colamd:  an approximate minimum degree column ordering algorithm,
	used as a preordering for UMFPACK.

Purpose:

    Colamd computes a permutation Q such that the Cholesky factorization of
    (AQ)'(AQ) has less fill-in and requires fewer floating point operations
    than A'A.  This also provides a good ordering for sparse partial
    pivoting methods, P(AQ) = LU, where Q is computed prior to numerical
    factorization, and P is computed during numerical factorization via
    conventional partial pivoting with row interchanges.  Colamd is the
    column ordering method used in SuperLU, part of the ScaLAPACK library.
    It is also available as built-in function in MATLAB Version 6,
    available from MathWorks, Inc. (http://www.mathworks.com).  This
    routine can be used in place of colmmd in MATLAB.  By default, the \
    and / operators in MATLAB perform a column ordering (using colmmd
    or colamd) prior to LU factorization using sparse partial pivoting,
    in the built-in MATLAB lu(A) routine.

    This code is derived from Colamd Version 2.0.

Authors:

    The authors of the COLAMD code itself are Stefan I. Larimore and Timothy A.
    Davis, University of Florida.  The algorithm was developed in collaboration
    with John Gilbert, Xerox PARC, and Esmond Ng, Oak Ridge National Laboratory.
    The AMD metric on which this is based is by Patrick Amestoy, T. Davis,
    and Iain Duff.

Date:

    UMFPACK Version: see above.
    COLAMD Version 2.0 was released on January 31, 2000.

Acknowledgements:

    This work was supported by the National Science Foundation, under
    grants DMS-9504974 and DMS-9803599.

UMFPACK:  Copyright (c) 2002 by Timothy A. Davis.  All Rights Reserved.

See the UMFPACK README file for the License for your use of this code.

Availability:

    Both UMFPACK and the original unmodified colamd/symamd library are
    available at http://www.cise.ufl.edu/research/sparse.

Changes for inclusion in UMFPACK:

    * symamd, symamd_report, and colamd_report removed

    * additional terms added to RowInfo, ColInfo, and stats

    * Frontal matrix information computed for UMFPACK

    * routines renamed

    * column elimination tree post-ordering incorporated.  In the original
	version 2.0, this was performed in colamd.m.

For more information, see:

	Amestoy, P. R. and Davis, T. A. and Duff, I. S.,
        An approximate minimum degree ordering algorithm,
	SIAM J. Matrix Analysis and Applic, vol 17, no 4., pp 886-905, 1996.

	Davis, T. A. and Gilbert, J. R. and Larimore, S. I. and Ng, E. G.,
	A column approximate minimum degree ordering algorithm,
	Univ. of Florida, CISE Dept., TR-00-005, Gainesville, FL
	Oct. 2000.  Submitted to ACM Trans. Math. Softw.

*/

/* ========================================================================== */
/* === Description of user-callable routines ================================ */
/* ========================================================================== */

/*
    ----------------------------------------------------------------------------
    colamd_recommended: removed for UMFPACK
    ----------------------------------------------------------------------------

    ----------------------------------------------------------------------------
    colamd_set_defaults:
    ----------------------------------------------------------------------------

	C syntax:

	    #include "colamd.h"
	    colamd_set_defaults (Int knobs [COLAMD_KNOBS]) ;

	Purpose:

	    Sets the default parameters.  The use of this routine is optional.

	Arguments:

	    double knobs [COLAMD_KNOBS] ;	Output only.

		Let c = knobs [COLAMD_DENSE_COL], r = knobs [COLAMD_DENSE_ROW].
		Colamd: rows with more than max (16, r*16*sqrt(n_col))
		entries are removed prior to ordering.  Columns with more than
		max (16, c*16*sqrt(n_row)) entries are removed prior to
		ordering, and placed last in the output column ordering.

		Symamd: removed for UMFPACK.

		COLAMD_DENSE_ROW and COLAMD_DENSE_COL are defined as 0 and 1,
		respectively, in colamd.h.  Default values of these two knobs
		are both 0.5.  Currently, only knobs [0] and knobs [1] are
		used, but future versions may use more knobs.  If so, they will
		be properly set to their defaults by the future version of
		colamd_set_defaults, so that the code that calls colamd will
		not need to change, assuming that you either use
		colamd_set_defaults, or pass a (double *) NULL pointer as the
		knobs array to colamd or symamd.

    ----------------------------------------------------------------------------
    colamd:
    ----------------------------------------------------------------------------

	C syntax:

	    #include "colamd.h"
	    Int UMF_colamd (Int n_row, Int n_col, Int Alen, Int *A, Int *p,
		double knobs [COLAMD_KNOBS], Int stats [COLAMD_STATS]) ;

	Purpose:

	    Computes a column ordering (Q) of A such that P(AQ)=LU or
	    (AQ)'AQ=LL' have less fill-in and require fewer floating point
	    operations than factorizing the unpermuted matrix A or A'A,
	    respectively.

	Returns:

	    TRUE (1) if successful, FALSE (0) otherwise.

	Arguments:

	    Int n_row ;		Input argument.

		Number of rows in the matrix A.
		Restriction:  n_row >= 0.
		Colamd returns FALSE if n_row is negative.

	    Int n_col ;		Input argument.

		Number of columns in the matrix A.
		Restriction:  n_col >= 0.
		Colamd returns FALSE if n_col is negative.

	    Int Alen ;		Input argument.

		Restriction (see note):
		Alen >= 2*nnz + 8*(n_col+1) + 6*(n_row+1) + n_col
		Colamd returns FALSE if these conditions are not met.

		Note:  this restriction makes an modest assumption regarding
		the size of the two typedef's structures in colamd.h.
		We do, however, guarantee that

			Alen >= UMF_COLAMD_RECOMMENDED (nnz, n_row, n_col)

		will be sufficient.

	    Int A [Alen] ;	Input and output argument.

		A is an integer array of size Alen.  Alen must be at least as
		large as the bare minimum value given above, but this is very
		low, and can result in excessive run time.  For best
		performance, we recommend that Alen be greater than or equal to
		UMF_COLAMD_RECOMMENDED (nnz, n_row, n_col), which adds
		nnz/5 to the bare minimum value given above.

		On input, the row indices of the entries in column c of the
		matrix are held in A [(p [c]) ... (p [c+1]-1)].  The row indices
		in a given column c need not be in ascending order, and
		duplicate row indices may be be present.  However, colamd will
		work a little faster if both of these conditions are met
		(Colamd puts the matrix into this format, if it finds that the
		the conditions are not met).

		The matrix is 0-based.  That is, rows are in the range 0 to
		n_row-1, and columns are in the range 0 to n_col-1.  Colamd
		returns FALSE if any row index is out of range.

		A holds the inverse permutation on output.

	    Int p [n_col+1] ;	Both input and output argument.

		p is an integer array of size n_col+1.  On input, it holds the
		"pointers" for the column form of the matrix A.  Column c of
		the matrix A is held in A [(p [c]) ... (p [c+1]-1)].  The first
		entry, p [0], must be zero, and p [c] <= p [c+1] must hold
		for all c in the range 0 to n_col-1.  The value p [n_col] is
		thus the total number of entries in the pattern of the matrix A.
		Colamd returns FALSE if these conditions are not met.

		On output, if colamd returns TRUE, the array p holds the column
		permutation (Q, for P(AQ)=LU or (AQ)'(AQ)=LL'), where p [0] is
		the first column index in the new ordering, and p [n_col-1] is
		the last.  That is, p [k] = j means that column j of A is the
		kth pivot column, in AQ, where k is in the range 0 to n_col-1
		(p [0] = j means that column j of A is the first column in AQ).

		If colamd returns FALSE, then no permutation is returned, and
		p is undefined on output.

	    double knobs [COLAMD_KNOBS] ;	Input argument.

		See colamd_set_defaults for a description.
		The behavior is undefined if knobs contains NaN's.
		(UMFPACK does not call umf_colamd with NaN-valued knobs).

	    Int stats [COLAMD_STATS] ;		Output argument.

		Statistics on the ordering, and error status.
		See colamd.h for related definitions.
		Colamd returns FALSE if stats is not present.

		stats [0]:  number of dense or empty rows ignored.

		stats [1]:  number of dense or empty columns ignored (and
				ordered last in the output permutation p)
				Note that a row can become "empty" if it
				contains only "dense" and/or "empty" columns,
				and similarly a column can become "empty" if it
				only contains "dense" and/or "empty" rows.

		stats [2]:  number of garbage collections performed.
				This can be excessively high if Alen is close
				to the minimum required value.

		stats [3]:  status code.  < 0 is an error code.
			    > 1 is a warning or notice.

			0	OK.  Each column of the input matrix contained
				row indices in increasing order, with no
				duplicates.

			-11	Columns of input matrix jumbled
				(unsorted columns or duplicate entries).

					stats [4]: the bad column index
					stats [5]: the bad row index

			-1	A is a null pointer

			-2	p is a null pointer

			-3	n_row is negative

					stats [4]: n_row

			-4	n_col is negative

					stats [4]: n_col

			-5	number of nonzeros in matrix is negative

					stats [4]: number of nonzeros, p [n_col]

			-6	p [0] is nonzero

					stats [4]: p [0]

			-7	A is too small

					stats [4]: required size
					stats [5]: actual size (Alen)

			-8	a column has a zero or negative number of
				entries (changed for UMFPACK)

					stats [4]: column with <= 0 entries
					stats [5]: number of entries in col

			-9	a row index is out of bounds

					stats [4]: column with bad row index
					stats [5]: bad row index
					stats [6]: n_row, # of rows of matrx

			-10	unused

			-999	(unused; see symamd.c)

		Future versions may return more statistics in the stats array.

	Example:

	    See http://www.cise.ufl.edu/~davis/colamd/example.c
	    for a complete example.

	    To order the columns of a 5-by-4 matrix with 11 nonzero entries in
	    the following nonzero pattern

		x 0 x 0
		x 0 x x
		0 x x 0
		0 0 x x
		x x 0 0

	    with default knobs and no output statistics, do the following:

		#include "colamd.h"
		#define ALEN UMF_COLAMD_RECOMMENDED (11, 5, 4)
		Int A [ALEN] = {1, 2, 5, 3, 5, 1, 2, 3, 4, 2, 4} ;
		Int p [ ] = {0, 3, 5, 9, 11} ;
		Int stats [COLAMD_STATS] ;
		UMF_colamd (5, 4, ALEN, A, p, (double *) NULL, stats) ;

	    The permutation is returned in the array p, and A is destroyed.


    ----------------------------------------------------------------------------
    symamd:  does not appear in this version for UMFPACK
    ----------------------------------------------------------------------------

    ----------------------------------------------------------------------------
    colamd_report: does not appear in this version for UMFPACK
    ----------------------------------------------------------------------------

    ----------------------------------------------------------------------------
    symamd_report: does not appear in this version for UMFPACK
    ----------------------------------------------------------------------------

*/

/* ========================================================================== */
/* === Scaffolding code definitions  ======================================== */
/* ========================================================================== */

/* ------------------ */
/* UMFPACK debugging control is in umf_config.h */
/* ------------------ */

/*
   Our "scaffolding code" philosophy:  In our opinion, well-written library
   code should keep its "debugging" code, and just normally have it turned off
   by the compiler so as not to interfere with performance.  This serves
   several purposes:

   (1) assertions act as comments to the reader, telling you what the code
	expects at that point.  All assertions will always be true (unless
	there really is a bug, of course).

   (2) leaving in the scaffolding code assists anyone who would like to modify
	the code, or understand the algorithm (by reading the debugging output,
	one can get a glimpse into what the code is doing).

   (3) (gasp!) for actually finding bugs.  This code has been heavily tested
	and "should" be fully functional and bug-free ... but you never know...

    To enable debugging, comment out the "#define NDEBUG" above.  For a MATLAB
    mexFunction, you will also need to modify mexopts.sh to remove the -DNDEBUG
    definition.  The code will become outrageously slow when debugging is
    enabled.  To control the level of debugging output, set an environment
    variable D to 0 (little), 1 (some), 2, 3, or 4 (lots).  When debugging,
    you should see the following message on the standard output:

	colamd: debug version, D = 1 (THIS WILL BE SLOW!)

    or a similar message for symamd.  If you don't, then debugging has not
    been enabled.

*/

/* ========================================================================== */
/* === Include files ======================================================== */
/* ========================================================================== */

/* ------------------ */
/* modified for UMFPACK: */
#include "umf_internal.h"
#include "umf_colamd.h"
#include "umf_order_front_tree.h"
#include "umf_apply_order.h"
/* ------------------ */

/* ========================================================================== */
/* === Definitions ========================================================== */
/* ========================================================================== */

/* ------------------ */
/* UMFPACK: duplicate definitions moved to umf_internal.h */
/* ------------------ */

/* Row and column status */
#define ALIVE	(0)
#define DEAD	(-1)

/* Column status */
#define DEAD_PRINCIPAL		(-1)
#define DEAD_NON_PRINCIPAL	(-2)

/* Macros for row and column status update and checking. */
#define ROW_IS_DEAD(r)			ROW_IS_MARKED_DEAD (Row[r].shared2.mark)
#define ROW_IS_MARKED_DEAD(row_mark)	(row_mark < ALIVE)
#define ROW_IS_ALIVE(r)			(Row [r].shared2.mark >= ALIVE)
#define COL_IS_DEAD(c)			(Col [c].start < ALIVE)
#define COL_IS_ALIVE(c)			(Col [c].start >= ALIVE)
#define COL_IS_DEAD_PRINCIPAL(c)	(Col [c].start == DEAD_PRINCIPAL)
#define KILL_ROW(r)			{ Row [r].shared2.mark = DEAD ; }
#define KILL_PRINCIPAL_COL(c)		{ Col [c].start = DEAD_PRINCIPAL ; }
#define KILL_NON_PRINCIPAL_COL(c)	{ Col [c].start = DEAD_NON_PRINCIPAL ; }

/* ------------------ */
/* UMFPACK: Colamd reporting mechanism moved to umf_internal.h */
/* ------------------ */

/* ========================================================================== */
/* === Prototypes of PRIVATE routines ======================================= */
/* ========================================================================== */

PRIVATE Int init_rows_cols
(
    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A [],
    Int p [],
    Int stats [COLAMD_STATS]
) ;

PRIVATE void init_scoring
(
    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A [],
    Int head [],
    double knobs [COLAMD_KNOBS],
    Int *p_n_row2,
    Int *p_n_col2,
    Int *p_max_deg
    /* ------------------ */
    /* added for UMFPACK */
    , Int *p_ndense_row		/* number of dense rows */
    , Int *p_nempty_row		/* number of original empty rows */
    , Int *p_nnewlyempty_row	/* number of newly empty rows */
    , Int *p_ndense_col		/* number of dense cols (excl "empty" cols) */
    , Int *p_nempty_col		/* number of original empty cols */
    , Int *p_nnewlyempty_col	/* number of newly empty cols */
) ;

PRIVATE Int find_ordering
(
    Int n_row,
    Int n_col,
    Int Alen,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A [],
    Int head [],
    Int n_col2,
    Int max_deg,
    Int pfree
    /* ------------------ */
    /* added for UMFPACK: */
    , Int Front_npivcol [ ]
    , Int Front_nrows [ ]
    , Int Front_ncols [ ]
    , Int Front_parent [ ]
    , Int Front_cols [ ]
    , Int *p_nfr
    /* ------------------ */
) ;

/* ------------------ */
/* order_children deleted for UMFPACK: */
/* ------------------ */

PRIVATE void detect_super_cols
(

#ifndef NDEBUG
    Int n_col,
    Colamd_Row Row [],
#endif /* NDEBUG */

    Colamd_Col Col [],
    Int A [],
    Int head [],
    Int row_start,
    Int row_length
) ;

PRIVATE Int garbage_collection
(
    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A [],
    Int *pfree
) ;

PRIVATE Int clear_mark
(
    Int n_row,
    Colamd_Row Row []
) ;

/* ------------------ */
/* print_report deleted for UMFPACK */
/* ------------------ */

/* ========================================================================== */
/* === Debugging prototypes and definitions ================================= */
/* ========================================================================== */

#ifndef NDEBUG

/* ------------------ */
/* debugging macros moved for UMFPACK */
/* ------------------ */

PRIVATE void debug_deg_lists
(
    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int head [],
    Int min_score,
    Int should,
    Int max_deg
) ;

PRIVATE void debug_mark
(
    Int n_row,
    Colamd_Row Row [],
    Int tag_mark,
    Int max_mark
) ;

PRIVATE void debug_matrix
(
    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A []
) ;

PRIVATE void debug_structures
(
    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A [],
    Int n_col2
) ;

/* ------------------ */
/* dump_super added for UMFPACK: */
PRIVATE void dump_super
(
    Int super_c,
    Colamd_Col Col [],
    Int n_col
) ;
/* ------------------ */

#endif /* NDEBUG */

/* ========================================================================== */



/* ========================================================================== */
/* === USER-CALLABLE ROUTINES: ============================================== */
/* ========================================================================== */


/* ========================================================================== */
/* === colamd_set_defaults ================================================== */
/* ========================================================================== */

/*
    The colamd_set_defaults routine sets the default values of the user-
    controllable parameters for colamd:

	knobs [0]	rows with knobs[0]*n_col entries or more are removed
			prior to ordering in colamd.  Rows and columns with
			knobs[0]*n_col entries or more are removed prior to
			ordering in symamd and placed last in the output
			ordering.

	knobs [1]	columns with knobs[1]*n_row entries or more are removed
			prior to ordering in colamd, and placed last in the
			column permutation.  Symamd ignores this knob.

	knobs [2..19]	unused, but future versions might use this
*/

GLOBAL void UMF_colamd_set_defaults
(
    /* === Parameters ======================================================= */

    double knobs [COLAMD_KNOBS]		/* knob array */
)
{
    /* === Local variables ================================================== */

    Int i ;

    if (!knobs)
    {
	return ;			/* UMFPACK always passes knobs array */
    }
    for (i = 0 ; i < COLAMD_KNOBS ; i++)
    {
	knobs [i] = 0 ;
    }
    knobs [COLAMD_DENSE_ROW] = 0.2 ;	/* default changed for UMFPACK */
    knobs [COLAMD_DENSE_COL] = 0.2 ;	/* default changed for UMFPACK */
}


/* ========================================================================== */
/* === symamd removed for UMFPACK =========================================== */
/* ========================================================================== */



/* ========================================================================== */
/* === colamd =============================================================== */
/* ========================================================================== */

/*
    The colamd routine computes a column ordering Q of a sparse matrix
    A such that the LU factorization P(AQ) = LU remains sparse, where P is
    selected via partial pivoting.   The routine can also be viewed as
    providing a permutation Q such that the Cholesky factorization
    (AQ)'(AQ) = LL' remains sparse.
*/

GLOBAL Int UMF_colamd		/* returns TRUE if successful, FALSE otherwise*/
(
    /* === Parameters ======================================================= */

    Int n_row,			/* number of rows in A */
    Int n_col,			/* number of columns in A */
    Int Alen,			/* length of A */
    Int A [],			/* row indices of A */
    Int p [],			/* pointers to columns in A */
    double knobs [COLAMD_KNOBS],/* parameters (uses defaults if NULL) */
    Int stats [COLAMD_STATS]	/* output statistics and error codes */

    /* ------------------ */
    /* added for UMFPACK: each Front_ array is of size n_col */
    , Int Front_npivcol [ ]	/* # pivot cols in each front */
    , Int Front_nrows [ ]	/* # of rows in each front (incl. pivot rows) */
    , Int Front_ncols [ ]	/* # of cols in each front (incl. pivot cols) */
    , Int Front_parent [ ]	/* parent of each front */
    , Int Front_cols [ ]	/* link list of pivot columns for each front */
    , Int *p_nfr		/* total number of frontal matrices */
    /* ------------------ */
)
{
    /* === Local variables ================================================== */

    Int i ;			/* loop index */
    Int nnz ;			/* nonzeros in A */
    Int Row_size ;		/* size of Row [], in integers */
    Int Col_size ;		/* size of Col [], in integers */
    Int need ;			/* minimum required length of A */
    Colamd_Row *Row ;		/* pointer into A of Row [0..n_row] array */
    Colamd_Col *Col ;		/* pointer into A of Col [0..n_col] array */
    Int n_col2 ;		/* number of non-dense, non-empty columns */
    Int n_row2 ;		/* number of non-dense, non-empty rows */
    Int ngarbage ;		/* number of garbage collections performed */
    Int max_deg ;		/* maximum row degree */
    double default_knobs [COLAMD_KNOBS] ;	/* default knobs array */

    /* ------------------ */
    /* debugging initializations moved for UMFPACK */
    /* ------------------ */

    /* ------------------ */
    /* added for UMFPACK: */
    Int f, j, nchild, ndense_row, nempty_row, parent,
	nrows, ncols, frsize, ndense_col, nempty_col, k, col, nfr,
	*Front_child, *Front_sibling, *Front_maxfr, *Front_order,
	fprev, maxfrsize, bigf, fnext, bigfprev, *Stack ;
    Int nnewlyempty_col, nnewlyempty_row ;
    /* ------------------ */

    /* === Check the input arguments ======================================== */

    if (!stats)
    {
	DEBUG0 (("colamd: stats not present\n")) ;
	return (FALSE) ;	/* UMFPACK:  always passes stats [ ] */
    }
    for (i = 0 ; i < COLAMD_STATS ; i++)
    {
	stats [i] = 0 ;
    }
    stats [COLAMD_STATUS] = COLAMD_OK ;
    stats [COLAMD_INFO1] = -1 ;
    stats [COLAMD_INFO2] = -1 ;

    if (!A)		/* A is not present */
    {
	/* UMFPACK:  always passes A [ ] */
	DEBUG0 (("colamd: A not present\n")) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_A_not_present ;
	return (FALSE) ;
    }

    if (!p)		/* p is not present */
    {
	/* UMFPACK:  always passes p [ ] */
	DEBUG0 (("colamd: p not present\n")) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_p_not_present ;
	return (FALSE) ;
    }

    if (n_row < 0)	/* n_row must be >= 0 */
    {
	/* UMFPACK:  does not call UMF_colamd if n <= 0 */
	DEBUG0 (("colamd: nrow negative "ID"\n", n_row)) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_nrow_negative ;
	stats [COLAMD_INFO1] = n_row ;
	return (FALSE) ;
    }

    if (n_col < 0)	/* n_col must be >= 0 */
    {
	/* UMFPACK:  does not call UMF_colamd if n <= 0 */
	DEBUG0 (("colamd: ncol negative "ID"\n", n_col)) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_ncol_negative ;
	stats [COLAMD_INFO1] = n_col ;
	return (FALSE) ;
    }

    nnz = p [n_col] ;
    if (nnz < 0)	/* nnz must be >= 0 */
    {
	/* UMFPACK:  does not call UMF_colamd if nnz < 0 */
	DEBUG0 (("colamd: number of entries negative "ID"\n", nnz)) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_nnz_negative ;
	stats [COLAMD_INFO1] = nnz ;
	return (FALSE) ;
    }

    if (p [0] != 0)	/* p [0] must be exactly zero */
    {
	DEBUG0 (("colamd: p[0] not zero "ID"\n", p [0])) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_p0_nonzero	;
	stats [COLAMD_INFO1] = p [0] ;
	return (FALSE) ;
    }

    /* === If no knobs, set default knobs =================================== */

    if (!knobs)
    {
	/* UMFPACK:  always passes the knobs */
	UMF_colamd_set_defaults (default_knobs) ;
	knobs = default_knobs ;
    }

    /* === Allocate the Row and Col arrays from array A ===================== */

    Col_size = UMF_COLAMD_C (n_col) ;
    Row_size = UMF_COLAMD_R (n_row) ;
    need = 2*nnz + n_col + Col_size + Row_size ;

    if (need > Alen)
    {
	/* UMFPACK: always passes enough space */
	/* not enough space in array A to perform the ordering */
	DEBUG0 (("colamd: Need Alen >= "ID", given only Alen = "ID"\n",
	    need, Alen)) ;
	stats [COLAMD_STATUS] = COLAMD_ERROR_A_too_small ;
	stats [COLAMD_INFO1] = need ;
	stats [COLAMD_INFO2] = Alen ;
	return (FALSE) ;
    }

    Alen -= Col_size + Row_size ;
    Col = (Colamd_Col *) &A [Alen] ;
    Row = (Colamd_Row *) &A [Alen + Col_size] ;

    /* === Construct the row and column data structures ===================== */

    if (!init_rows_cols (n_row, n_col, Row, Col, A, p, stats))
    {
	/* input matrix is invalid */
	DEBUG0 (("colamd: Matrix invalid\n")) ;
	return (FALSE) ;
    }

    /* === UMFPACK: Initialize front info =================================== */

    for (col = 0 ; col < n_col ; col++)
    {
	Front_npivcol [col] = 0 ;
	Front_nrows [col] = 0 ;
	Front_ncols [col] = 0 ;
	Front_parent [col] = EMPTY ;
	Front_cols [col] = EMPTY ;
    }

    /* === Initialize scores, kill dense rows/columns ======================= */

    init_scoring (n_row, n_col, Row, Col, A, p, knobs,
	&n_row2, &n_col2, &max_deg
	/* ------------------ */
	/* added for UMFPACK: */
	, &ndense_row, &nempty_row, &nnewlyempty_row
	, &ndense_col, &nempty_col, &nnewlyempty_col
	/* ------------------ */
	) ;
    ASSERT (n_row2 == n_row - nempty_row - nnewlyempty_row - ndense_row) ;
    ASSERT (n_col2 == n_col - nempty_col - nnewlyempty_col - ndense_col) ;

    /* === Order the supercolumns =========================================== */

    ngarbage = find_ordering (n_row, n_col, Alen, Row, Col, A, p,
	n_col2, max_deg, 2*nnz
	/* ------------------ */
	/* added for UMFPACK: */
	, Front_npivcol, Front_nrows, Front_ncols, Front_parent, Front_cols
	, &nfr
	/* ------------------ */
	) ;

    /* ------------------ */
    /* changed for UMFPACK: */

    /* use A [0..4*nfr-1] as workspace [ [ [ */
    Front_child   = A ;
    Front_sibling = Front_child + nfr ;
    Front_maxfr   = Front_sibling + nfr ;
    Front_order   = Front_maxfr + nfr ;

    for (j = 0 ; j < nfr ; j++)
    {
	Front_child [j] = EMPTY ;
	Front_sibling [j] = EMPTY ;
	Front_maxfr [j] = EMPTY ;
    }

    /* === find maxfr for each front ======================================== */

    DEBUG1 (("\n\n========================================FRONTS:\n")) ;
    for (j = 0 ; j < nfr ; j++)
    {
	parent = Front_parent [j] ;
	nrows = Front_nrows [j] ;
	ncols = Front_ncols [j] ;
	frsize = nrows * ncols ;
	DEBUG1 ((""ID" :  npiv "ID" nrows "ID" ncols "ID" parent "ID" ",
		j, Front_npivcol [j], nrows, ncols, parent)) ;
	Front_maxfr [j] = MAX (Front_maxfr [j], frsize) ;
	DEBUG1 (("Front_maxfr [j = "ID"] = "ID"\n", j, Front_maxfr[j]));
	if (parent != EMPTY)
	{
	    /* find the maximum frontsize of self and children */
	    Front_maxfr [parent] = MAX (Front_maxfr [parent], Front_maxfr [j]) ;
	    DEBUG1 (("Front_maxfr [parent = "ID"] = "ID"\n",
		parent, Front_maxfr [parent])) ;
	}
    }

    /* === put the fronts in the child lists ================================ */

    /* try to put them in order of smaller to larger */
    for (j = nfr-1 ; j >= 0 ; j--)
    {
	parent = Front_parent [j] ;
	if (parent != EMPTY)
	{
	    /* place the front in the link list of the children its parent */
	    /* bigger fronts will tend to be at the end of the list */
	    Front_sibling [j] = Front_child [parent] ;
	    Front_child [parent] = j ;
	}
    }

    /* === Order the front tree via depth-first-search ====================== */

    for (i = 0 ; i < nfr ; i++)
    {
	if (Front_child [i] != EMPTY)
	{
	    DEBUG1 (("Before partial sort, front "ID"\n", i)) ;
	    nchild = 0 ;
	    for (f = Front_child [i] ; f != EMPTY ; f = Front_sibling [f])
	    {
		DEBUG1 (("        "ID"  "ID"\n", f, Front_maxfr [f])) ;
		nchild++ ;
	    }

	    /* find the biggest front in the child list */
	    fprev = EMPTY ;
	    maxfrsize = EMPTY ;
	    bigfprev = EMPTY ;
	    bigf = EMPTY ;
	    for (f = Front_child [i] ; f != EMPTY ; f = Front_sibling [f])
	    {
		frsize = Front_maxfr [f] ;
		if (frsize >= maxfrsize)
		{
		    /* this is the biggest seen so far */
		    maxfrsize = frsize ;
		    bigfprev = fprev ;
		    bigf = f ;
		}
		fprev = f ;
	    }
	    ASSERT (bigf != EMPTY) ;

	    fnext = Front_sibling [bigf] ;

	    DEBUG1 (("bigf "ID" maxfrsize "ID" bigfprev "ID" fnext "ID" fprev"
		ID"\n", bigf, maxfrsize, bigfprev, fnext, fprev)) ;

	    if (fnext != EMPTY)
	    {
		/* if fnext is EMPTY, then bigf is already at the end of list */

		if (bigfprev == EMPTY)
		{
		    /* delete bigf from the front of the list */
		    Front_child [i] = fnext ;
		}
		else
		{
		    /* delete bigf from the middle of the list */
		    Front_sibling [bigfprev] = fnext ;
		}

		/* put bigf at the end of the list */
		Front_sibling [bigf] = EMPTY ;
		ASSERT (Front_child [i] != EMPTY) ;
		ASSERT (fprev != bigf) ;
		ASSERT (fprev != EMPTY) ;
		Front_sibling [fprev] = bigf ;
	    }

	    DEBUG1 (("After partial sort, front "ID"\n", i)) ;
	    for (f = Front_child [i] ; f != EMPTY ; f = Front_sibling [f])
	    {
		DEBUG1 (("        "ID"  "ID"\n", f, Front_maxfr [f])) ;
		ASSERT (Front_npivcol [f] > 0) ;
		nchild-- ;
	    }
	    ASSERT (nchild == 0) ;
	}
    }

    /* Front_maxfr no longer needed ] */

#ifndef NDEBUG
    UMF_nbug = nfr ;	/* frontal id's are in the range 0..nfr-1 */
    UMF_fbug = nfr ;	/* total number of frontal matrices */
#endif

    /* use Front_maxfr as workspace for stack */
    Stack = Front_maxfr ;
    k = 0 ;
    for (i = 0 ; i < nfr ; i++)
    {
	if (Front_parent [i] == EMPTY)
	{
	    DEBUG1 (("Root of front tree "ID"\n", i)) ;
	    k = UMF_order_front_tree (i, k, Front_child, Front_sibling,
		Front_order, Stack) ;
	}
    }

    /* Front_child, Front_sibling no longer needed ] */

    /* use A [0..nfr-1] as workspace */
    UMF_apply_order (Front_npivcol, Front_order, A, nfr, nfr) ;
    UMF_apply_order (Front_nrows,   Front_order, A, nfr, nfr) ;
    UMF_apply_order (Front_ncols,   Front_order, A, nfr, nfr) ;
    UMF_apply_order (Front_parent,  Front_order, A, nfr, nfr) ;
    UMF_apply_order (Front_cols,    Front_order, A, nfr, nfr) ;

    /* fix the parent to refer to the new numbering */
    for (i = 0 ; i < nfr ; i++)
    {
	parent = Front_parent [i] ;
	if (parent != EMPTY)
	{
	    Front_parent [i] = Front_order [parent] ;
	}
    }

    /* Front_order longer needed ] */

    /* === Order the columns in the fronts ================================== */

    /* use A [0..n_col-1] as inverse permutation */
    for (i = 0 ; i < n_col ; i++)
    {
	A [i] = -1 ;
    }
    k = 0 ;
    for (i = 0 ; i < nfr ; i++)
    {
	ASSERT (Front_npivcol [i] > 0) ;
	for (col = Front_cols [i] ; col != EMPTY ; col = Col [col].nextcol)
	{
	    ASSERT (col >= 0 && col < n_col) ;
	    p [k++] = col ;
	    ASSERT (A [col] == -1) ;
	    A [col] = k ;
	}
    }

    /* === Order the "dense" and null columns =============================== */

    ASSERT (k == n_col2) ;
    if (n_col2 < n_col)
    {
	for (col = 0 ; col < n_col ; col++)
	{
	    if (A [col] == -1)
	    {
		DEBUG1 (("colamd dense or null col "ID" k: "ID"\n", col, k)) ;
		k = Col [col].shared2.order ;
		ASSERT (k >= n_col2 && k < n_col) ;
		p [k] = col ;
		A [col] = k ;
	    }
	}
    }

    /* ------------------ */

    /* === Return statistics in stats ======================================= */

    /* ------------------ */
    /* modified for UMFPACK */
    stats [COLAMD_DENSE_ROW] = ndense_row ;
    stats [COLAMD_EMPTY_ROW] = nempty_row ;
    stats [COLAMD_NEWLY_EMPTY_ROW] = nnewlyempty_row ;
    stats [COLAMD_DENSE_COL] = ndense_col ;
    stats [COLAMD_EMPTY_COL] = nempty_col ;
    stats [COLAMD_NEWLY_EMPTY_COL] = nnewlyempty_col ;
    ASSERT (ndense_col + nempty_col + nnewlyempty_col == n_col - n_col2) ;
    /* ------------------ */
    stats [COLAMD_DEFRAG_COUNT] = ngarbage ;
    *p_nfr = nfr ;
    DEBUG1 (("colamd: done.\n")) ;
    return (TRUE) ;
}




/* ========================================================================== */
/* === colamd_report removed for UMFPACK ==================================== */
/* ========================================================================== */

/* ========================================================================== */
/* === symamd_report removed for UMFPACK ==================================== */
/* ========================================================================== */



/* ========================================================================== */
/* === NON-USER-CALLABLE ROUTINES: ========================================== */
/* ========================================================================== */

/* There are no user-callable routines beyond this point in the file */


/* ========================================================================== */
/* === init_rows_cols ======================================================= */
/* ========================================================================== */

/*
    Takes the column form of the matrix in A and creates the row form of the
    matrix.  Also, row and column attributes are stored in the Col and Row
    structs.  If the columns are un-sorted or contain duplicate row indices,
    this routine will also sort and remove duplicate row indices from the
    column form of the matrix.  Returns FALSE if the matrix is invalid,
    TRUE otherwise.  Not user-callable.
*/

PRIVATE Int init_rows_cols	/* returns TRUE if OK, or FALSE otherwise */
(
    /* === Parameters ======================================================= */

    Int n_row,			/* number of rows of A */
    Int n_col,			/* number of columns of A */
    Colamd_Row Row [],		/* of size n_row+1 */
    Colamd_Col Col [],		/* of size n_col+1 */
    Int A [],			/* row indices of A, of size Alen */
    Int p [],			/* pointers to columns in A, of size n_col+1 */
    Int stats [COLAMD_STATS]	/* colamd statistics */
)
{
    /* === Local variables ================================================== */

    Int col ;			/* a column index */
    Int row ;			/* a row index */
    Int *cp ;			/* a column pointer */
    Int *cp_end ;		/* a pointer to the end of a column */
    /* rp and rp_end not used for UMFPACK */
    Int last_row ;		/* previous row */

    /* === Initialize columns, and check column pointers ==================== */

    for (col = 0 ; col < n_col ; col++)
    {
	Col [col].start = p [col] ;
	Col [col].length = p [col+1] - p [col] ;

	if (Col [col].length < 0)
	{
	    /* column pointers must be non-decreasing */
	    stats [COLAMD_STATUS] = COLAMD_ERROR_col_length_negative ;
	    stats [COLAMD_INFO1] = col ;
	    stats [COLAMD_INFO2] = Col [col].length ;
	    DEBUG0 (("colamd: col "ID" length "ID" <= 0\n",
		col, Col [col].length));
	    return (FALSE) ;
	}

	Col [col].shared1.thickness = 1 ;
	Col [col].shared2.score = 0 ;
	Col [col].shared3.prev = EMPTY ;
	Col [col].shared4.degree_next = EMPTY ;

	/* ------------------ */
	/* added for UMFPACK: */
	Col [col].nextcol = EMPTY ;
	Col [col].lastcol = col ;
	/* ------------------ */
    }

    /* p [0..n_col] no longer needed, used as "head" in subsequent routines */

    /* === Scan columns, compute row degrees, and check row indices ========= */

    /* ------------------ */
    /* stats [COLAMD_INFO3] = 0 ; */
    /* number of duplicate or unsorted row indices - not computed in UMFPACK */
    /* ------------------ */

    for (row = 0 ; row < n_row ; row++)
    {
	Row [row].length = 0 ;
	/* ------------------ */
	/* removed for UMFPACK */
	/* Row [row].shared2.mark = -1 ; */
	/* ------------------ */
	/* ------------------ */
	/* added for UMFPACK: */
	Row [row].thickness = 1 ;
	Row [row].front = EMPTY ;
	/* ------------------ */
    }

    for (col = 0 ; col < n_col ; col++)
    {
	last_row = -1 ;

	cp = &A [p [col]] ;
	cp_end = &A [p [col+1]] ;

	while (cp < cp_end)
	{
	    row = *cp++ ;

	    /* make sure row indices within range */
	    if (row < 0 || row >= n_row)
	    {
		stats [COLAMD_STATUS] = COLAMD_ERROR_row_index_out_of_bounds ;
		stats [COLAMD_INFO1] = col ;
		stats [COLAMD_INFO2] = row ;
		/* ------------------ */
		/* not needed in UMFPACK: */
		/* stats [COLAMD_INFO3] = n_row ; */
		/* ------------------ */
		DEBUG0 (("colamd: row "ID" col "ID" out of bounds\n", row,col));
		return (FALSE) ;
	    }

	    /* ------------------ */
	    /* changed for UMFPACK */
	    if (row <= last_row)
	    {
		/* row index are unsorted or repeated (or both), thus col */
		/* is jumbled.  This is an error condition for UMFPACK */
		stats [COLAMD_STATUS] = COLAMD_ERROR_jumbled_matrix ;
		stats [COLAMD_INFO1] = col ;
		stats [COLAMD_INFO2] = row ;
		DEBUG1 (("colamd: row "ID" col "ID" unsorted/duplicate\n",
		    row, col)) ;
		return (FALSE) ;
	    }
	    /* ------------------ */

	    /* ------------------ */
	    /* changed for UMFPACK - jumbled columns not tolerated */
	    Row [row].length++ ;
	    /* ------------------ */

	    last_row = row ;
	}
    }

    /* === Compute row pointers ============================================= */

    /* row form of the matrix starts directly after the column */
    /* form of matrix in A */
    Row [0].start = p [n_col] ;
    Row [0].shared1.p = Row [0].start ;
    /* ------------------ */
    /* removed for UMFPACK */
    /* Row [0].shared2.mark = -1 ; */
    /* ------------------ */
    for (row = 1 ; row < n_row ; row++)
    {
	Row [row].start = Row [row-1].start + Row [row-1].length ;
	Row [row].shared1.p = Row [row].start ;
	/* ------------------ */
	/* removed for UMFPACK */
	/* Row [row].shared2.mark = -1 ; */
	/* ------------------ */
    }

    /* === Create row form ================================================== */

    /* ------------------ */
    /* jumbled matrix case removed for UMFPACK */
    /* ------------------ */

    ASSERT (stats [COLAMD_STATUS] == COLAMD_OK) ;

	for (col = 0 ; col < n_col ; col++)
	{
	    cp = &A [p [col]] ;
	    cp_end = &A [p [col+1]] ;
	    while (cp < cp_end)
	    {
		A [(Row [*cp++].shared1.p)++] = col ;
	    }
	}

    /* === Clear the row marks and set row degrees ========================== */

    for (row = 0 ; row < n_row ; row++)
    {
	Row [row].shared2.mark = 0 ;
	Row [row].shared1.degree = Row [row].length ;
    }

    /* ------------------ */
    /* recreate columns for jumbled matrix case removed for UMFPACK */
    /* ------------------ */

    return (TRUE) ;
}


/* ========================================================================== */
/* === init_scoring ========================================================= */
/* ========================================================================== */

/*
    Kills dense or empty columns and rows, calculates an initial score for
    each column, and places all columns in the degree lists.  Not user-callable.
*/

PRIVATE void init_scoring
(
    /* === Parameters ======================================================= */

    Int n_row,			/* number of rows of A */
    Int n_col,			/* number of columns of A */
    Colamd_Row Row [],		/* of size n_row+1 */
    Colamd_Col Col [],		/* of size n_col+1 */
    Int A [],			/* column form and row form of A */
    Int head [],		/* of size n_col+1 */
    double knobs [COLAMD_KNOBS],/* parameters */
    Int *p_n_row2,		/* number of non-dense, non-empty rows */
    Int *p_n_col2,		/* number of non-dense, non-empty columns */
    Int *p_max_deg		/* maximum row degree */
    /* ------------------ */
    /* added for UMFPACK */
    , Int *p_ndense_row		/* number of dense rows */
    , Int *p_nempty_row		/* number of original empty rows */
    , Int *p_nnewlyempty_row	/* number of newly empty rows */
    , Int *p_ndense_col		/* number of dense cols (excl "empty" cols) */
    , Int *p_nempty_col		/* number of original empty cols */
    , Int *p_nnewlyempty_col	/* number of newly empty cols */
    /* ------------------ */
)
{
    /* === Local variables ================================================== */

    Int c ;			/* a column index */
    Int r, row ;		/* a row index */
    Int *cp ;			/* a column pointer */
    Int deg ;			/* degree of a row or column */
    Int *cp_end ;		/* a pointer to the end of a column */
    Int *new_cp ;		/* new column pointer */
    Int col_length ;		/* length of pruned column */
    Int score ;			/* current column score */
    Int n_col2 ;		/* number of non-dense, non-empty columns */
    Int n_row2 ;		/* number of non-dense, non-empty rows */
    Int dense_row_count ;	/* remove rows with more entries than this */
    Int dense_col_count ;	/* remove cols with more entries than this */
    Int min_score ;		/* smallest column score */
    Int max_deg ;		/* maximum row degree */
    Int next_col ;		/* Used to add to degree list.*/

    /* ------------------ */
    /* added for UMFPACK */
    Int ndense_row ;		/* number of dense rows */
    Int nempty_row ;		/* number of empty rows */
    Int nnewlyempty_row ;	/* number of newly empty rows */
    Int ndense_col ;		/* number of dense cols (excl "empty" cols) */
    Int nempty_col ;		/* number of original empty cols */
    Int nnewlyempty_col ;	/* number of newly empty cols */
    Int ne ;
    /* ------------------ */

#ifndef NDEBUG
    Int debug_count ;		/* debug only. */
#endif /* NDEBUG */

    /* === Extract knobs ==================================================== */

    /* --------------------- */
    /* old dense row/column knobs:
    dense_row_count = MAX (0, MIN (knobs [COLAMD_DENSE_ROW] * n_col, n_col)) ;
    dense_col_count = MAX (0, MIN (knobs [COLAMD_DENSE_COL] * n_row, n_row)) ;
    */
    /* new, for UMFPACK: */
    /* Note: if knobs contains a NaN, this is undefined: */
    dense_row_count =
	UMFPACK_DENSE_DEGREE_THRESHOLD (knobs [COLAMD_DENSE_ROW], n_col) ;
    dense_col_count =
	UMFPACK_DENSE_DEGREE_THRESHOLD (knobs [COLAMD_DENSE_COL], n_row) ;
    /* Make sure dense_*_count is between 0 and n: */
    dense_row_count = MAX (0, MIN (dense_row_count, n_col)) ;
    dense_col_count = MAX (0, MIN (dense_col_count, n_row)) ;
    /* --------------------- */

    DEBUG1 (("colamd: densecount: "ID" "ID"\n",
	dense_row_count, dense_col_count)) ;
    max_deg = 0 ;
    n_col2 = n_col ;
    n_row2 = n_row ;

    /* --------------------- */
    /* added for UMFPACK */
    ndense_col = 0 ;
    nempty_col = 0 ;
    nnewlyempty_col = 0 ;
    ndense_row = 0 ;
    nempty_row = 0 ;
    nnewlyempty_row = 0 ;
    /* --------------------- */

    /* === Kill empty columns =============================================== */

    /* Put the empty columns at the end in their natural order, so that LU */
    /* factorization can proceed as far as possible. */
    for (c = n_col-1 ; c >= 0 ; c--)
    {
	deg = Col [c].length ;
	if (deg == 0)
	{
	    /* this is a empty column, kill and order it last */
	    Col [c].shared2.order = --n_col2 ;
	    KILL_PRINCIPAL_COL (c) ;
	    /* --------------------- */
	    /* added for UMFPACK */
	    nempty_col++ ;
	    /* --------------------- */
	}
    }
    DEBUG1 (("colamd: null columns killed: "ID"\n", n_col - n_col2)) ;

    /* === Count null rows ================================================== */

    for (r = 0 ; r < n_row ; r++)
    {
	deg = Row [r].shared1.degree ;
	if (deg == 0)
	{
	    /* this is an original empty row */
	    nempty_row++ ;
	}
    }

    /* === Kill dense columns =============================================== */

    /* Put the dense columns at the end, in their natural order */
    for (c = n_col-1 ; c >= 0 ; c--)
    {
	/* skip any dead columns */
	if (COL_IS_DEAD (c))
	{
	    continue ;
	}
	deg = Col [c].length ;
	if (deg > dense_col_count)
	{
	    /* this is a dense column, kill and order it last */
	    Col [c].shared2.order = --n_col2 ;
	    /* --------------------- */
	    /* added for UMFPACK */
	    ndense_col++ ;
	    /* --------------------- */
	    /* decrement the row degrees */
	    cp = &A [Col [c].start] ;
	    cp_end = cp + Col [c].length ;
	    while (cp < cp_end)
	    {
		Row [*cp++].shared1.degree-- ;
	    }
	    KILL_PRINCIPAL_COL (c) ;
	}
    }
    DEBUG1 (("colamd: Dense and null columns killed: "ID"\n", n_col - n_col2)) ;

    /* === Kill dense and empty rows ======================================== */

    ne = 0 ;
    for (r = 0 ; r < n_row ; r++)
    {
	deg = Row [r].shared1.degree ;
	ASSERT (deg >= 0 && deg <= n_col) ;
	/* --------------------- */
	/* added for UMFPACK */
	if (deg > dense_row_count)
	{
	    /* There is at least one dense row.  Continue ordering, but */
	    /* symbolic factorization will be redone after UMF_colamd is done.*/
	    ndense_row++ ;
	}
	if (deg == 0)
	{
	    /* this is a newly empty row, or original empty row */
	    ne++ ;
	}
	/* --------------------- */
	if (deg > dense_row_count || deg == 0)
	{
	    /* kill a dense or empty row */
	    KILL_ROW (r) ;
	    /* --------------------- */
	    /* added for UMFPACK */
	    Row [r].thickness = 0 ;
	    /* --------------------- */
	    --n_row2 ;
	}
	else
	{
	    /* keep track of max degree of remaining rows */
	    max_deg = MAX (max_deg, deg) ;
	}
    }
    nnewlyempty_row = ne - nempty_row ;
    DEBUG1 (("colamd: Dense rows killed: "ID"\n", ndense_row)) ;
    DEBUG1 (("colamd: Dense and null rows killed: "ID"\n", n_row - n_row2)) ;

    /* === Compute initial column scores ==================================== */

    /* At this point the row degrees are accurate.  They reflect the number */
    /* of "live" (non-dense) columns in each row.  No empty rows exist. */
    /* Some "live" columns may contain only dead rows, however.  These are */
    /* pruned in the code below. */

    /* now find the initial matlab score for each column */
    for (c = n_col-1 ; c >= 0 ; c--)
    {
	/* skip dead column */
	if (COL_IS_DEAD (c))
	{
	    continue ;
	}
	score = 0 ;
	cp = &A [Col [c].start] ;
	new_cp = cp ;
	cp_end = cp + Col [c].length ;
	while (cp < cp_end)
	{
	    /* get a row */
	    row = *cp++ ;
	    /* skip if dead */
	    if (ROW_IS_DEAD (row))
	    {
		continue ;
	    }
	    /* compact the column */
	    *new_cp++ = row ;
	    /* add row's external degree */
	    score += Row [row].shared1.degree - 1 ;
	    /* guard against integer overflow */
	    score = MIN (score, n_col) ;
	}
	/* determine pruned column length */
	col_length = (Int) (new_cp - &A [Col [c].start]) ;
	if (col_length == 0)
	{
	    /* a newly-made null column (all rows in this col are "dense" */
	    /* and have already been killed) */
	    DEBUG2 (("Newly null killed: "ID"\n", c)) ;
	    Col [c].shared2.order = --n_col2 ;
	    KILL_PRINCIPAL_COL (c) ;
	    /* --------------------- */
	    /* added for UMFPACK */
	    nnewlyempty_col++ ;
	    /* --------------------- */
	}
	else
	{
	    /* set column length and set score */
	    ASSERT (score >= 0) ;
	    ASSERT (score <= n_col) ;
	    Col [c].length = col_length ;
	    Col [c].shared2.score = score ;
	}
    }
    DEBUG1 (("colamd: Dense, null, and newly-null columns killed: "ID"\n",
	n_col-n_col2)) ;

    /* At this point, all empty rows and columns are dead.  All live columns */
    /* are "clean" (containing no dead rows) and simplicial (no supercolumns */
    /* yet).  Rows may contain dead columns, but all live rows contain at */
    /* least one live column. */

#ifndef NDEBUG
    debug_structures (n_row, n_col, Row, Col, A, n_col2) ;
#endif /* NDEBUG */

    /* === Initialize degree lists ========================================== */

#ifndef NDEBUG
    debug_count = 0 ;
#endif /* NDEBUG */

    /* clear the hash buckets */
    for (c = 0 ; c <= n_col ; c++)
    {
	head [c] = EMPTY ;
    }
    min_score = n_col ;
    /* place in reverse order, so low column indices are at the front */
    /* of the lists.  This is to encourage natural tie-breaking */
    for (c = n_col-1 ; c >= 0 ; c--)
    {
	/* only add principal columns to degree lists */
	if (COL_IS_ALIVE (c))
	{
	    DEBUG4 (("place "ID" score "ID" minscore "ID" ncol "ID"\n",
		c, Col [c].shared2.score, min_score, n_col)) ;

	    /* === Add columns score to DList =============================== */

	    score = Col [c].shared2.score ;

	    ASSERT (min_score >= 0) ;
	    ASSERT (min_score <= n_col) ;
	    ASSERT (score >= 0) ;
	    ASSERT (score <= n_col) ;
	    ASSERT (head [score] >= EMPTY) ;

	    /* now add this column to dList at proper score location */
	    next_col = head [score] ;
	    Col [c].shared3.prev = EMPTY ;
	    Col [c].shared4.degree_next = next_col ;

	    /* if there already was a column with the same score, set its */
	    /* previous pointer to this new column */
	    if (next_col != EMPTY)
	    {
		Col [next_col].shared3.prev = c ;
	    }
	    head [score] = c ;

	    /* see if this score is less than current min */
	    min_score = MIN (min_score, score) ;

#ifndef NDEBUG
	    debug_count++ ;
#endif /* NDEBUG */

	}
    }

#ifndef NDEBUG
    DEBUG1 (("colamd: Live cols "ID" out of "ID", non-princ: "ID"\n",
	debug_count, n_col, n_col-debug_count)) ;
    ASSERT (debug_count == n_col2) ;
    debug_deg_lists (n_row, n_col, Row, Col, head, min_score, n_col2, max_deg) ;
#endif /* NDEBUG */

    /* === Return number of remaining columns, and max row degree =========== */

    *p_n_col2 = n_col2 ;
    *p_n_row2 = n_row2 ;
    *p_max_deg = max_deg ;

    /* --------------------- */
    /* added for UMFPACK */
    *p_ndense_row = ndense_row ;
    *p_nempty_row = nempty_row ;	/* original empty rows */
    *p_nnewlyempty_row = nnewlyempty_row ;
    *p_ndense_col = ndense_col ;
    *p_nempty_col = nempty_col ;	/* original empty cols */
    *p_nnewlyempty_col = nnewlyempty_col ;
    /* --------------------- */
}


/* ========================================================================== */
/* === find_ordering ======================================================== */
/* ========================================================================== */

/*
    Order the principal columns of the supercolumn form of the matrix
    (no supercolumns on input).  Uses a minimum approximate column minimum
    degree ordering method.  Not user-callable.
*/

PRIVATE Int find_ordering	/* return the number of garbage collections */
(
    /* === Parameters ======================================================= */

    Int n_row,			/* number of rows of A */
    Int n_col,			/* number of columns of A */
    Int Alen,			/* size of A, 2*nnz + n_col or larger */
    Colamd_Row Row [],		/* of size n_row+1 */
    Colamd_Col Col [],		/* of size n_col+1 */
    Int A [],			/* column form and row form of A */
    Int head [],		/* of size n_col+1 */
    Int n_col2,			/* Remaining columns to order */
    Int max_deg,		/* Maximum row degree */
    Int pfree			/* index of first free slot (2*nnz on entry) */
    /* ------------------ */
    /* added for UMFPACK: */
    , Int Front_npivcol [ ]
    , Int Front_nrows [ ]
    , Int Front_ncols [ ]
    , Int Front_parent [ ]
    , Int Front_cols [ ]
    , Int *p_nfr		/* number of fronts */
    /* ------------------ */
)
{
    /* === Local variables ================================================== */

    Int k ;			/* current pivot ordering step */
    Int pivot_col ;		/* current pivot column */
    Int *cp ;			/* a column pointer */
    Int *rp ;			/* a row pointer */
    Int pivot_row ;		/* current pivot row */
    Int *new_cp ;		/* modified column pointer */
    Int *new_rp ;		/* modified row pointer */
    Int pivot_row_start ;	/* pointer to start of pivot row */
    Int pivot_row_degree ;	/* number of columns in pivot row */
    Int pivot_row_length ;	/* number of supercolumns in pivot row */
    Int pivot_col_score ;	/* score of pivot column */
    Int needed_memory ;		/* free space needed for pivot row */
    Int *cp_end ;		/* pointer to the end of a column */
    Int *rp_end ;		/* pointer to the end of a row */
    Int row ;			/* a row index */
    Int col ;			/* a column index */
    Int max_score ;		/* maximum possible score */
    Int cur_score ;		/* score of current column */
    unsigned Int hash ;		/* hash value for supernode detection */
    Int head_column ;		/* head of hash bucket */
    Int first_col ;		/* first column in hash bucket */
    Int tag_mark ;		/* marker value for mark array */
    Int row_mark ;		/* Row [row].shared2.mark */
    Int set_difference ;	/* set difference size of row with pivot row */
    Int min_score ;		/* smallest column score */
    Int col_thickness ;		/* "thickness" (no. of columns in a supercol) */
    Int max_mark ;		/* maximum value of tag_mark */
    Int pivot_col_thickness ;	/* number of columns represented by pivot col */
    Int prev_col ;		/* Used by Dlist operations. */
    Int next_col ;		/* Used by Dlist operations. */
    Int ngarbage ;		/* number of garbage collections performed */

#ifndef NDEBUG
    Int debug_d ;		/* debug loop counter */
    Int debug_step = 0 ;	/* debug loop counter */
#endif /* NDEBUG */

    /* ------------------ */
    /* added for UMFPACK: */
    Int pivot_row_thickness ;	/* number of rows represented by pivot row */
    Int nfr = 0 ;		/* number of fronts */
    Int child ;
    /* ------------------ */

    /* === Initialization and clear mark ==================================== */

    max_mark = MAX_MARK (n_col) ;	/* defined in umfpack.h */
    tag_mark = clear_mark (n_row, Row) ;
    min_score = 0 ;
    ngarbage = 0 ;
    DEBUG1 (("colamd: Ordering, n_col2="ID"\n", n_col2)) ;

    /* === Order the columns ================================================ */

    for (k = 0 ; k < n_col2 ; /* 'k' is incremented below */)
    {

#ifndef NDEBUG
	if (debug_step % 100 == 0)
	{
	    DEBUG2 (("\n...  Step k: "ID" out of n_col2: "ID"\n", k, n_col2)) ;
	}
	else
	{
	    DEBUG3 (("\n-----Step k: "ID" out of n_col2: "ID"\n", k, n_col2)) ;
	}
	debug_step++ ;
	debug_deg_lists (n_row, n_col, Row, Col, head,
		min_score, n_col2-k, max_deg) ;
	debug_matrix (n_row, n_col, Row, Col, A) ;
#endif /* NDEBUG */

	/* === Select pivot column, and order it ============================ */

	/* make sure degree list isn't empty */
	ASSERT (min_score >= 0) ;
	ASSERT (min_score <= n_col) ;
	ASSERT (head [min_score] >= EMPTY) ;

#ifndef NDEBUG
	for (debug_d = 0 ; debug_d < min_score ; debug_d++)
	{
	    ASSERT (head [debug_d] == EMPTY) ;
	}
#endif /* NDEBUG */

	/* get pivot column from head of minimum degree list */
	while (head [min_score] == EMPTY && min_score < n_col)
	{
	    min_score++ ;
	}
	pivot_col = head [min_score] ;
	ASSERT (pivot_col >= 0 && pivot_col <= n_col) ;
	next_col = Col [pivot_col].shared4.degree_next ;
	head [min_score] = next_col ;
	if (next_col != EMPTY)
	{
	    Col [next_col].shared3.prev = EMPTY ;
	}

	ASSERT (COL_IS_ALIVE (pivot_col)) ;
	DEBUG3 (("Pivot col: "ID"\n", pivot_col)) ;

	/* remember score for defrag check */
	pivot_col_score = Col [pivot_col].shared2.score ;

	/* the pivot column is the kth column in the pivot order */
	Col [pivot_col].shared2.order = k ;

	/* increment order count by column thickness */
	pivot_col_thickness = Col [pivot_col].shared1.thickness ;
	/* ------------------ */
	/* changed for UMFPACK: */
	k += pivot_col_thickness ;
	/* ------------------ */
	ASSERT (pivot_col_thickness > 0) ;

	/* === Garbage_collection, if necessary ============================= */

	needed_memory = MIN (pivot_col_score, n_col - k) ;
	if (pfree + needed_memory >= Alen)
	{
	    pfree = garbage_collection (n_row, n_col, Row, Col, A, &A [pfree]) ;
	    ngarbage++ ;
	    /* after garbage collection we will have enough */
	    ASSERT (pfree + needed_memory < Alen) ;
	    /* garbage collection has wiped out the Row[].shared2.mark array */
	    tag_mark = clear_mark (n_row, Row) ;

#ifndef NDEBUG
	    debug_matrix (n_row, n_col, Row, Col, A) ;
#endif /* NDEBUG */
	}

	/* === Compute pivot row pattern ==================================== */

	/* get starting location for this new merged row */
	pivot_row_start = pfree ;

	/* initialize new row counts to zero */
	pivot_row_degree = 0 ;

	/* ------------------ */
	/* added for UMFPACK: */
	pivot_row_thickness = 0 ;
	/* ------------------ */

	/* [ tag pivot column as having been visited so it isn't included */
	/* in merged pivot row */
	Col [pivot_col].shared1.thickness = -pivot_col_thickness ;

	/* pivot row is the union of all rows in the pivot column pattern */
	cp = &A [Col [pivot_col].start] ;
	cp_end = cp + Col [pivot_col].length ;
	while (cp < cp_end)
	{
	    /* get a row */
	    row = *cp++ ;
	    DEBUG4 (("Pivot col pattern "ID" "ID"\n", ROW_IS_ALIVE(row), row)) ;
	    /* skip if row is dead */
	    if (ROW_IS_DEAD (row))
	    {
		continue ;
	    }

	    /* ------------------ */
	    /* added for UMFPACK: */
	    /* sum the thicknesses of all the rows */
	    ASSERT (Row [row].thickness > 0) ;
	    pivot_row_thickness += Row [row].thickness ;
	    /* ------------------ */

	    rp = &A [Row [row].start] ;
	    rp_end = rp + Row [row].length ;
	    while (rp < rp_end)
	    {
		/* get a column */
		col = *rp++ ;
		/* add the column, if alive and untagged */
		col_thickness = Col [col].shared1.thickness ;
		if (col_thickness > 0 && COL_IS_ALIVE (col))
		{
		    /* tag column in pivot row */
		    Col [col].shared1.thickness = -col_thickness ;
		    ASSERT (pfree < Alen) ;
		    /* place column in pivot row */
		    A [pfree++] = col ;
		    pivot_row_degree += col_thickness ;
		    /* ------------------ */
		    /* added for UMFPACK: */
		    DEBUG4 (("\t\t\tNew live column in pivot row: "ID"\n",col));
		    /* ------------------ */
		}
/* ------------------ */
/* added for UMFPACK */
#ifndef NDEBUG
		if (col_thickness < 0 && COL_IS_ALIVE (col))
		{
		    DEBUG4 (("\t\t\tOld live column in pivot row: "ID"\n",col));
		}
#endif
/* ------------------ */
	    }
	}

	/* ------------------ */
	/* added for UMFPACK: */
	/* pivot_row_thickness is the number of rows in frontal matrix */
	/* both pivotal rows and nonpivotal rows */
	/* ------------------ */

	/* clear tag on pivot column */
	Col [pivot_col].shared1.thickness = pivot_col_thickness ;	/* ] */
	max_deg = MAX (max_deg, pivot_row_degree) ;

#ifndef NDEBUG
	DEBUG3 (("check2\n")) ;
	debug_mark (n_row, Row, tag_mark, max_mark) ;
#endif /* NDEBUG */

	/* === Kill all rows used to construct pivot row ==================== */

	/* also kill pivot row, temporarily */
	cp = &A [Col [pivot_col].start] ;
	cp_end = cp + Col [pivot_col].length ;
	while (cp < cp_end)
	{
	    /* may be killing an already dead row */
	    row = *cp++ ;

	    /* ------------------ */
	    /* added for UMFPACK: */
	    DEBUG2 (("Kill row in pivot col: "ID" alive? "ID", front "ID"\n",
		row, ROW_IS_ALIVE (row), Row [row].front)) ;

	    if (ROW_IS_ALIVE (row) && Row [row].front != EMPTY)
	    {
		/* Row [row].front is a child of current front */
		child = Row [row].front ;
		Front_parent [child] = nfr ;
	    }
	    /* ------------------ */

	    KILL_ROW (row) ;

	    /* ------------------ */
	    /* added for UMFPACK: */
	    Row [row].thickness = 0 ;
	    /* ------------------ */
	}

	/* === Select a row index to use as the new pivot row =============== */

	pivot_row_length = pfree - pivot_row_start ;
	if (pivot_row_length > 0)
	{
	    /* pick the "pivot" row arbitrarily (first row in col) */
	    pivot_row = A [Col [pivot_col].start] ;
	    DEBUG3 (("Pivotal row is "ID"\n", pivot_row)) ;
	}
	else
	{
	    /* there is no pivot row, since it is of zero length */
	    pivot_row = EMPTY ;
	    ASSERT (pivot_row_length == 0) ;
	}
	ASSERT (Col [pivot_col].length > 0 || pivot_row_length == 0) ;

	/* === Approximate degree computation =============================== */

	/* Here begins the computation of the approximate degree.  The column */
	/* score is the sum of the pivot row "length", plus the size of the */
	/* set differences of each row in the column minus the pattern of the */
	/* pivot row itself.  The column ("thickness") itself is also */
	/* excluded from the column score (we thus use an approximate */
	/* external degree). */

	/* The time taken by the following code (compute set differences, and */
	/* add them up) is proportional to the size of the data structure */
	/* being scanned - that is, the sum of the sizes of each column in */
	/* the pivot row.  Thus, the amortized time to compute a column score */
	/* is proportional to the size of that column (where size, in this */
	/* context, is the column "length", or the number of row indices */
	/* in that column).  The number of row indices in a column is */
	/* monotonically non-decreasing, from the length of the original */
	/* column on input to colamd. */

	/* === Compute set differences ====================================== */

	DEBUG3 (("** Computing set differences phase. **\n")) ;

	/* pivot row is currently dead - it will be revived later. */

	DEBUG3 (("Pivot row: \n")) ;
	/* for each column in pivot row */
	rp = &A [pivot_row_start] ;
	rp_end = rp + pivot_row_length ;
	while (rp < rp_end)
	{
	    col = *rp++ ;
	    ASSERT (COL_IS_ALIVE (col) && col != pivot_col) ;
	    DEBUG3 (("    Col: "ID"\n", col)) ;

	    /* clear tags used to construct pivot row pattern */
	    col_thickness = -Col [col].shared1.thickness ;
	    ASSERT (col_thickness > 0) ;
	    Col [col].shared1.thickness = col_thickness ;

	    /* === Remove column from degree list =========================== */

	    cur_score = Col [col].shared2.score ;
	    prev_col = Col [col].shared3.prev ;
	    next_col = Col [col].shared4.degree_next ;
	    ASSERT (cur_score >= 0) ;
	    ASSERT (cur_score <= n_col) ;
	    ASSERT (cur_score >= EMPTY) ;
	    if (prev_col == EMPTY)
	    {
		head [cur_score] = next_col ;
	    }
	    else
	    {
		Col [prev_col].shared4.degree_next = next_col ;
	    }
	    if (next_col != EMPTY)
	    {
		Col [next_col].shared3.prev = prev_col ;
	    }

	    /* === Scan the column ========================================== */

	    cp = &A [Col [col].start] ;
	    cp_end = cp + Col [col].length ;
	    while (cp < cp_end)
	    {
		/* get a row */
		row = *cp++ ;
		row_mark = Row [row].shared2.mark ;
		/* skip if dead */
		if (ROW_IS_MARKED_DEAD (row_mark))
		{
		    continue ;
		}
		ASSERT (row != pivot_row) ;
		set_difference = row_mark - tag_mark ;
		/* check if the row has been seen yet */
		if (set_difference < 0)
		{
		    ASSERT (Row [row].shared1.degree <= max_deg) ;
		    set_difference = Row [row].shared1.degree ;
		}
		/* subtract column thickness from this row's set difference */
		set_difference -= col_thickness ;
		ASSERT (set_difference >= 0) ;

		/* ------------------ */
		/* aggressive row absorption removed for UMFPACK */
		/* ------------------ */

		    /* save the new mark */
		    Row [row].shared2.mark = set_difference + tag_mark ;

	    }
	}

#ifndef NDEBUG
	debug_deg_lists (n_row, n_col, Row, Col, head,
		min_score, n_col2-k-pivot_row_degree, max_deg) ;
#endif /* NDEBUG */

	/* === Add up set differences for each column ======================= */

	DEBUG3 (("** Adding set differences phase. **\n")) ;

	/* for each column in pivot row */
	rp = &A [pivot_row_start] ;
	rp_end = rp + pivot_row_length ;
	while (rp < rp_end)
	{
	    /* get a column */
	    col = *rp++ ;
	    ASSERT (COL_IS_ALIVE (col) && col != pivot_col) ;
	    hash = 0 ;
	    cur_score = 0 ;
	    cp = &A [Col [col].start] ;
	    /* compact the column */
	    new_cp = cp ;
	    cp_end = cp + Col [col].length ;

	    DEBUG4 (("Adding set diffs for Col: "ID".\n", col)) ;

	    while (cp < cp_end)
	    {
		/* get a row */
		row = *cp++ ;
		ASSERT(row >= 0 && row < n_row) ;
		row_mark = Row [row].shared2.mark ;
		/* skip if dead */
		if (ROW_IS_MARKED_DEAD (row_mark))
		{
		    /* ------------------ */
		    /* changed for UMFPACK: */
		    DEBUG4 ((" Row "ID", dead\n", row)) ;
		    /* ------------------ */
		    continue ;
		}
		/* ------------------ */
		/* changed for UMFPACK: */
		/* ASSERT (row_mark > tag_mark) ; */
		DEBUG4 ((" Row "ID", set diff "ID"\n", row, row_mark-tag_mark));
		ASSERT (row_mark >= tag_mark) ;
		/* ------------------ */
		/* compact the column */
		*new_cp++ = row ;
		/* compute hash function */
		hash += row ;
		/* add set difference */
		cur_score += row_mark - tag_mark ;
		/* integer overflow... */
		cur_score = MIN (cur_score, n_col) ;
	    }

	    /* recompute the column's length */
	    Col [col].length = (Int) (new_cp - &A [Col [col].start]) ;

	    /* === Further mass elimination ================================= */

	    if (Col [col].length == 0)
	    {
		DEBUG4 (("further mass elimination. Col: "ID"\n", col)) ;
		/* nothing left but the pivot row in this column */
		KILL_PRINCIPAL_COL (col) ;
		pivot_row_degree -= Col [col].shared1.thickness ;
		ASSERT (pivot_row_degree >= 0) ;
		/* order it */
		Col [col].shared2.order = k ;
		/* increment order count by column thickness */
		k += Col [col].shared1.thickness ;

		/* ------------------ */
		/* added for UMFPACK: */
		pivot_col_thickness += Col [col].shared1.thickness ;

		/* add to column list of front ... */
#ifndef NDEBUG
		DEBUG1 (("Mass")) ;
		dump_super (col, Col, n_col) ;
#endif
		Col [Col [col].lastcol].nextcol = Front_cols [nfr] ;
		Front_cols [nfr] = col ;
		/* ------------------ */

	    }
	    else
	    {
		/* === Prepare for supercolumn detection ==================== */

		DEBUG4 (("Preparing supercol detection for Col: "ID".\n", col));

		/* save score so far */
		Col [col].shared2.score = cur_score ;

		/* add column to hash table, for supercolumn detection */
		hash %= n_col + 1 ;

		DEBUG4 ((" Hash = "ID", n_col = "ID".\n", hash, n_col)) ;
		ASSERT (hash <= n_col) ;

		head_column = head [hash] ;
		if (head_column > EMPTY)
		{
		    /* degree list "hash" is non-empty, use prev (shared3) of */
		    /* first column in degree list as head of hash bucket */
		    first_col = Col [head_column].shared3.headhash ;
		    Col [head_column].shared3.headhash = col ;
		}
		else
		{
		    /* degree list "hash" is empty, use head as hash bucket */
		    first_col = - (head_column + 2) ;
		    head [hash] = - (col + 2) ;
		}
		Col [col].shared4.hash_next = first_col ;

		/* save hash function in Col [col].shared3.hash */
		Col [col].shared3.hash = (Int) hash ;
		ASSERT (COL_IS_ALIVE (col)) ;
	    }
	}

	/* The approximate external column degree is now computed.  */

	/* === Supercolumn detection ======================================== */

	DEBUG3 (("** Supercolumn detection phase. **\n")) ;

	detect_super_cols (

#ifndef NDEBUG
		n_col, Row,
#endif /* NDEBUG */

		Col, A, head, pivot_row_start, pivot_row_length) ;

	/* === Kill the pivotal column ====================================== */

	KILL_PRINCIPAL_COL (pivot_col) ;

	/* ------------------ */
	/* added for UMFPACK: */
	/* add columns to column list of front */
#ifndef NDEBUG
	DEBUG1 (("Pivot")) ;
	dump_super (pivot_col, Col, n_col) ;
#endif
	Col [Col [pivot_col].lastcol].nextcol = Front_cols [nfr] ;
	Front_cols [nfr] = pivot_col ;
	/* ------------------ */

	/* === Clear mark =================================================== */

	tag_mark += (max_deg + 1) ;
	if (tag_mark >= max_mark)
	{
	    DEBUG2 (("clearing tag_mark\n")) ;
	    tag_mark = clear_mark (n_row, Row) ;
	}

#ifndef NDEBUG
	DEBUG3 (("check3\n")) ;
	debug_mark (n_row, Row, tag_mark, max_mark) ;
#endif /* NDEBUG */

	/* === Finalize the new pivot row, and column scores ================ */

	DEBUG3 (("** Finalize scores phase. **\n")) ;
	DEBUG3 (("pivot_row_degree "ID"\n", pivot_row_degree)) ;

	/* for each column in pivot row */
	rp = &A [pivot_row_start] ;
	/* compact the pivot row */
	new_rp = rp ;
	rp_end = rp + pivot_row_length ;
	while (rp < rp_end)
	{
	    col = *rp++ ;
	    DEBUG3 (("Col "ID" \n", col)) ;
	    /* skip dead columns */
	    if (COL_IS_DEAD (col))
	    {
		DEBUG3 (("dead\n")) ;
		continue ;
	    }
	    *new_rp++ = col ;
	    /* add new pivot row to column */
	    A [Col [col].start + (Col [col].length++)] = pivot_row ;

	    /* retrieve score so far and add on pivot row's degree. */
	    /* (we wait until here for this in case the pivot */
	    /* row's degree was reduced due to mass elimination). */
	    DEBUG3 ((" cur_score "ID" ", cur_score)) ;
	    cur_score = Col [col].shared2.score + pivot_row_degree ;
	    DEBUG3 (("ID", cur_score)) ;

	    /* calculate the max possible score as the number of */
	    /* external columns minus the 'k' value minus the */
	    /* columns thickness */
	    max_score = n_col - k - Col [col].shared1.thickness ;
	    DEBUG3 ((" max_score "ID" ", max_score)) ;

	    /* make the score the external degree of the union-of-rows */
	    cur_score -= Col [col].shared1.thickness ;
	    DEBUG3 ((" cur_score "ID" ", cur_score)) ;

	    /* make sure score is less or equal than the max score */
	    cur_score = MIN (cur_score, max_score) ;
	    ASSERT (cur_score >= 0) ;

	    /* store updated score */
	    Col [col].shared2.score = cur_score ;
	    DEBUG3 ((" "ID"\n", cur_score)) ;

	    /* === Place column back in degree list ========================= */

	    ASSERT (min_score >= 0) ;
	    ASSERT (min_score <= n_col) ;
	    ASSERT (cur_score >= 0) ;
	    ASSERT (cur_score <= n_col) ;
	    ASSERT (head [cur_score] >= EMPTY) ;
	    next_col = head [cur_score] ;
	    Col [col].shared4.degree_next = next_col ;
	    Col [col].shared3.prev = EMPTY ;
	    if (next_col != EMPTY)
	    {
		Col [next_col].shared3.prev = col ;
	    }
	    head [cur_score] = col ;

	    /* see if this score is less than current min */
	    min_score = MIN (min_score, cur_score) ;

	}

#ifndef NDEBUG
	debug_deg_lists (n_row, n_col, Row, Col, head,
		min_score, n_col2-k, max_deg) ;
#endif /* NDEBUG */

	/* ------------------ */
	/* added for UMFPACK: */
	/* frontal matrix can have more pivot cols than pivot rows for */
	/* singular matrices. */

	/* number of candidate pivot columns */
	Front_npivcol [nfr] = pivot_col_thickness ;

	/* all rows (not just size of contrib. block) */
	Front_nrows [nfr] = pivot_row_thickness ;

	/* all cols */
	Front_ncols [nfr] = pivot_col_thickness + pivot_row_degree ;

	Front_parent [nfr] = EMPTY ;

	pivot_row_thickness -= pivot_col_thickness ;
	DEBUG1 (("Front "ID" Pivot_row_thickness after pivot cols elim: "ID"\n",
	    nfr, pivot_row_thickness)) ;
	pivot_row_thickness = MAX (0, pivot_row_thickness) ;
	/* ------------------ */

	/* === Resurrect the new pivot row ================================== */

	if (pivot_row_degree > 0
	/* ------------------ */
	/* added for UMFPACK: */
	&& pivot_row_thickness > 0
	/* ------------------ */
	)
	{
	    /* update pivot row length to reflect any cols that were killed */
	    /* during super-col detection and mass elimination */
	    Row [pivot_row].start  = pivot_row_start ;
	    Row [pivot_row].length = (Int) (new_rp - &A[pivot_row_start]) ;
	    Row [pivot_row].shared1.degree = pivot_row_degree ;
	    Row [pivot_row].shared2.mark = 0 ;
	    /* ------------------ */
	    /* added for UMFPACK: */
	    Row [pivot_row].thickness = pivot_row_thickness ;
	    Row [pivot_row].front = nfr ;
	    /* ------------------ */
	    /* pivot row is no longer dead */
	}

	/* ------------------ */
	/* added for UMFPACK: */

#ifndef NDEBUG
	DEBUG1 (("Front "ID" : "ID" "ID" "ID" ", nfr,
		Front_npivcol [nfr], Front_nrows [nfr], Front_ncols [nfr])) ;
	DEBUG1 ((" cols:[ ")) ;
	debug_d = 0 ;
	for (col = Front_cols [nfr] ; col != EMPTY ; col = Col [col].nextcol)
	{
	    DEBUG1 ((" "ID, col)) ;
	    ASSERT (col >= 0 && col < n_col) ;
	    ASSERT (COL_IS_DEAD (col)) ;
	    debug_d++ ;
	    ASSERT (debug_d <= pivot_col_thickness) ;
	}
	ASSERT (debug_d == pivot_col_thickness) ;
	DEBUG1 ((" ]\n ")) ;
#endif
	nfr++ ; /* one more front */
	/* ------------------ */

    }

    /* === All principal columns have now been ordered ====================== */

    /* ------------------ */
    /* added for UMFPACK: */
    *p_nfr = nfr ;
    /* ------------------ */

    return (ngarbage) ;
}


/* ========================================================================== */
/* === order_children deleted for UMFPACK =================================== */
/* ========================================================================== */

/* ========================================================================== */
/* === detect_super_cols ==================================================== */
/* ========================================================================== */

/*
    Detects supercolumns by finding matches between columns in the hash buckets.
    Check amongst columns in the set A [row_start ... row_start + row_length-1].
    The columns under consideration are currently *not* in the degree lists,
    and have already been placed in the hash buckets.

    The hash bucket for columns whose hash function is equal to h is stored
    as follows:

	if head [h] is >= 0, then head [h] contains a degree list, so:

		head [h] is the first column in degree bucket h.
		Col [head [h]].headhash gives the first column in hash bucket h.

	otherwise, the degree list is empty, and:

		-(head [h] + 2) is the first column in hash bucket h.

    For a column c in a hash bucket, Col [c].shared3.prev is NOT a "previous
    column" pointer.  Col [c].shared3.hash is used instead as the hash number
    for that column.  The value of Col [c].shared4.hash_next is the next column
    in the same hash bucket.

    Assuming no, or "few" hash collisions, the time taken by this routine is
    linear in the sum of the sizes (lengths) of each column whose score has
    just been computed in the approximate degree computation.
    Not user-callable.
*/

PRIVATE void detect_super_cols
(
    /* === Parameters ======================================================= */

#ifndef NDEBUG
    /* these two parameters are only needed when debugging is enabled: */
    Int n_col,			/* number of columns of A */
    Colamd_Row Row [],		/* of size n_row+1 */
#endif /* NDEBUG */

    Colamd_Col Col [],		/* of size n_col+1 */
    Int A [],			/* row indices of A */
    Int head [],		/* head of degree lists and hash buckets */
    Int row_start,		/* pointer to set of columns to check */
    Int row_length		/* number of columns to check */
)
{
    /* === Local variables ================================================== */

    Int hash ;			/* hash value for a column */
    Int *rp ;			/* pointer to a row */
    Int c ;			/* a column index */
    Int super_c ;		/* column index of the column to absorb into */
    Int *cp1 ;			/* column pointer for column super_c */
    Int *cp2 ;			/* column pointer for column c */
    Int length ;		/* length of column super_c */
    Int prev_c ;		/* column preceding c in hash bucket */
    Int i ;			/* loop counter */
    Int *rp_end ;		/* pointer to the end of the row */
    Int col ;			/* a column index in the row to check */
    Int head_column ;		/* first column in hash bucket or degree list */
    Int first_col ;		/* first column in hash bucket */

    /* === Consider each column in the row ================================== */

    rp = &A [row_start] ;
    rp_end = rp + row_length ;
    while (rp < rp_end)
    {
	col = *rp++ ;
	if (COL_IS_DEAD (col))
	{
	    continue ;
	}

	/* get hash number for this column */
	hash = Col [col].shared3.hash ;
	ASSERT (hash <= n_col) ;

	/* === Get the first column in this hash bucket ===================== */

	head_column = head [hash] ;
	if (head_column > EMPTY)
	{
	    first_col = Col [head_column].shared3.headhash ;
	}
	else
	{
	    first_col = - (head_column + 2) ;
	}

	/* === Consider each column in the hash bucket ====================== */

	for (super_c = first_col ; super_c != EMPTY ;
	    super_c = Col [super_c].shared4.hash_next)
	{
	    ASSERT (COL_IS_ALIVE (super_c)) ;
	    ASSERT (Col [super_c].shared3.hash == hash) ;
	    length = Col [super_c].length ;

	    /* prev_c is the column preceding column c in the hash bucket */
	    prev_c = super_c ;

	    /* === Compare super_c with all columns after it ================ */

	    for (c = Col [super_c].shared4.hash_next ;
		c != EMPTY ; c = Col [c].shared4.hash_next)
	    {
		ASSERT (c != super_c) ;
		ASSERT (COL_IS_ALIVE (c)) ;
		ASSERT (Col [c].shared3.hash == hash) ;

		/* not identical if lengths or scores are different */
		if (Col [c].length != length ||
		    Col [c].shared2.score != Col [super_c].shared2.score)
		{
		    prev_c = c ;
		    continue ;
		}

		/* compare the two columns */
		cp1 = &A [Col [super_c].start] ;
		cp2 = &A [Col [c].start] ;

		for (i = 0 ; i < length ; i++)
		{
		    /* the columns are "clean" (no dead rows) */
		    ASSERT (ROW_IS_ALIVE (*cp1))  ;
		    ASSERT (ROW_IS_ALIVE (*cp2))  ;
		    /* row indices will same order for both supercols, */
		    /* no gather scatter nessasary */
		    if (*cp1++ != *cp2++)
		    {
			break ;
		    }
		}

		/* the two columns are different if the for-loop "broke" */
		if (i != length)
		{
		    prev_c = c ;
		    continue ;
		}

		/* === Got it!  two columns are identical =================== */

		ASSERT (Col [c].shared2.score == Col [super_c].shared2.score) ;

		Col [super_c].shared1.thickness += Col [c].shared1.thickness ;
		Col [c].shared1.parent = super_c ;
		KILL_NON_PRINCIPAL_COL (c) ;

		Col [c].shared2.order = EMPTY ;
		/* remove c from hash bucket */
		Col [prev_c].shared4.hash_next = Col [c].shared4.hash_next ;

		/* ------------------ */
		/* added for UMFPACK: */
		/* add c to end of list of super_c */
		ASSERT (Col [super_c].lastcol >= 0) ;
		ASSERT (Col [super_c].lastcol < n_col) ;
		Col [Col [super_c].lastcol].nextcol = c ;
		Col [super_c].lastcol = Col [c].lastcol ;
#ifndef NDEBUG
		/* dump the supercolumn */
		DEBUG1 (("Super")) ;
		dump_super (super_c, Col, n_col) ;
#endif
		/* ------------------ */

	    }
	}

	/* === Empty this hash bucket ======================================= */

	if (head_column > EMPTY)
	{
	    /* corresponding degree list "hash" is not empty */
	    Col [head_column].shared3.headhash = EMPTY ;
	}
	else
	{
	    /* corresponding degree list "hash" is empty */
	    head [hash] = EMPTY ;
	}
    }
}


/* ========================================================================== */
/* === garbage_collection =================================================== */
/* ========================================================================== */

/*
    Defragments and compacts columns and rows in the workspace A.  Used when
    all avaliable memory has been used while performing row merging.  Returns
    the index of the first free position in A, after garbage collection.  The
    time taken by this routine is linear is the size of the array A, which is
    itself linear in the number of nonzeros in the input matrix.
    Not user-callable.
*/

PRIVATE Int garbage_collection  /* returns the new value of pfree */
(
    /* === Parameters ======================================================= */

    Int n_row,			/* number of rows */
    Int n_col,			/* number of columns */
    Colamd_Row Row [],		/* row info */
    Colamd_Col Col [],		/* column info */
    Int A [],			/* A [0 ... Alen-1] holds the matrix */
    Int *pfree			/* &A [0] ... pfree is in use */
)
{
    /* === Local variables ================================================== */

    Int *psrc ;			/* source pointer */
    Int *pdest ;		/* destination pointer */
    Int j ;			/* counter */
    Int r ;			/* a row index */
    Int c ;			/* a column index */
    Int length ;		/* length of a row or column */

#ifndef NDEBUG
    Int debug_rows ;
    DEBUG2 (("Defrag..\n")) ;
    for (psrc = &A[0] ; psrc < pfree ; psrc++) ASSERT (*psrc >= 0) ;
    debug_rows = 0 ;
#endif /* NDEBUG */

    /* === Defragment the columns =========================================== */

    pdest = &A[0] ;
    for (c = 0 ; c < n_col ; c++)
    {
	if (COL_IS_ALIVE (c))
	{
	    psrc = &A [Col [c].start] ;

	    /* move and compact the column */
	    ASSERT (pdest <= psrc) ;
	    Col [c].start = (Int) (pdest - &A [0]) ;
	    length = Col [c].length ;
	    for (j = 0 ; j < length ; j++)
	    {
		r = *psrc++ ;
		if (ROW_IS_ALIVE (r))
		{
		    *pdest++ = r ;
		}
	    }
	    Col [c].length = (Int) (pdest - &A [Col [c].start]) ;
	}
    }

    /* === Prepare to defragment the rows =================================== */

    for (r = 0 ; r < n_row ; r++)
    {
	if (ROW_IS_ALIVE (r))
	{
	    if (Row [r].length == 0)
	    {
		/* this row is of zero length.  cannot compact it, so kill it */
		DEBUG3 (("Defrag row kill\n")) ;
		KILL_ROW (r) ;
	    }
	    else
	    {
		/* save first column index in Row [r].shared2.first_column */
		psrc = &A [Row [r].start] ;
		Row [r].shared2.first_column = *psrc ;
		ASSERT (ROW_IS_ALIVE (r)) ;
		/* flag the start of the row with the one's complement of row */
		*psrc = ONES_COMPLEMENT (r) ;

#ifndef NDEBUG
		debug_rows++ ;
#endif /* NDEBUG */

	    }
	}
    }

    /* === Defragment the rows ============================================== */

    psrc = pdest ;
    while (psrc < pfree)
    {
	/* find a negative number ... the start of a row */
	if (*psrc++ < 0)
	{
	    psrc-- ;
	    /* get the row index */
	    r = ONES_COMPLEMENT (*psrc) ;
	    ASSERT (r >= 0 && r < n_row) ;
	    /* restore first column index */
	    *psrc = Row [r].shared2.first_column ;
	    ASSERT (ROW_IS_ALIVE (r)) ;

	    /* move and compact the row */
	    ASSERT (pdest <= psrc) ;
	    Row [r].start = (Int) (pdest - &A [0]) ;
	    length = Row [r].length ;
	    for (j = 0 ; j < length ; j++)
	    {
		c = *psrc++ ;
		if (COL_IS_ALIVE (c))
		{
		    *pdest++ = c ;
		}
	    }
	    Row [r].length = (Int) (pdest - &A [Row [r].start]) ;

#ifndef NDEBUG
	    debug_rows-- ;
#endif /* NDEBUG */

	}
    }
    /* ensure we found all the rows */
    ASSERT (debug_rows == 0) ;

    /* === Return the new value of pfree ==================================== */

    return ((Int) (pdest - &A [0])) ;
}


/* ========================================================================== */
/* === clear_mark =========================================================== */
/* ========================================================================== */

/*
    Clears the Row [].shared2.mark array, and returns the new tag_mark.
    Return value is the new tag_mark.  Not user-callable.
*/

PRIVATE Int clear_mark	/* return the new value for tag_mark */
(
    /* === Parameters ======================================================= */

    Int n_row,		/* number of rows in A */
    Colamd_Row Row []	/* Row [0 ... n-1].shared2.mark is set to zero */
)
{
    /* === Local variables ================================================== */

    Int r ;

    for (r = 0 ; r < n_row ; r++)
    {
	if (ROW_IS_ALIVE (r))
	{
	    Row [r].shared2.mark = 0 ;
	}
    }

    /* ------------------ */
    return (1) ;
    /* ------------------ */

}


/* ========================================================================== */
/* === print_report removed for UMFPACK ===================================== */
/* ========================================================================== */



/* ========================================================================== */
/* === colamd debugging routines ============================================ */
/* ========================================================================== */

/* When debugging is disabled, the remainder of this file is ignored. */

#ifndef NDEBUG


/* ========================================================================== */
/* === debug_structures ===================================================== */
/* ========================================================================== */

/*
    At this point, all empty rows and columns are dead.  All live columns
    are "clean" (containing no dead rows) and simplicial (no supercolumns
    yet).  Rows may contain dead columns, but all live rows contain at
    least one live column.
*/

PRIVATE void debug_structures
(
    /* === Parameters ======================================================= */

    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A [],
    Int n_col2
)
{
    /* === Local variables ================================================== */

    Int i ;
    Int c ;
    Int *cp ;
    Int *cp_end ;
    Int len ;
    Int score ;
    Int r ;
    Int *rp ;
    Int *rp_end ;
    Int deg ;

    /* === Check A, Row, and Col ============================================ */

    for (c = 0 ; c < n_col ; c++)
    {
	if (COL_IS_ALIVE (c))
	{
	    len = Col [c].length ;
	    score = Col [c].shared2.score ;
	    DEBUG4 (("initial live col "ID" "ID" "ID"\n", c, len, score)) ;
	    ASSERT (len > 0) ;
	    ASSERT (score >= 0) ;
	    ASSERT (Col [c].shared1.thickness == 1) ;
	    cp = &A [Col [c].start] ;
	    cp_end = cp + len ;
	    while (cp < cp_end)
	    {
		r = *cp++ ;
		ASSERT (ROW_IS_ALIVE (r)) ;
	    }
	}
	else
	{
	    i = Col [c].shared2.order ;
	    ASSERT (i >= n_col2 && i < n_col) ;
	}
    }

    for (r = 0 ; r < n_row ; r++)
    {
	if (ROW_IS_ALIVE (r))
	{
	    i = 0 ;
	    len = Row [r].length ;
	    deg = Row [r].shared1.degree ;
	    ASSERT (len > 0) ;
	    ASSERT (deg > 0) ;
	    rp = &A [Row [r].start] ;
	    rp_end = rp + len ;
	    while (rp < rp_end)
	    {
		c = *rp++ ;
		if (COL_IS_ALIVE (c))
		{
		    i++ ;
		}
	    }
	    ASSERT (i > 0) ;
	}
    }
}


/* ========================================================================== */
/* === debug_deg_lists ====================================================== */
/* ========================================================================== */

/*
    Prints the contents of the degree lists.  Counts the number of columns
    in the degree list and compares it to the total it should have.  Also
    checks the row degrees.
*/

PRIVATE void debug_deg_lists
(
    /* === Parameters ======================================================= */

    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int head [],
    Int min_score,
    Int should,
    Int max_deg
)
{
    /* === Local variables ================================================== */

    Int deg ;
    Int col ;
    Int have ;
    Int row ;

    /* === Check the degree lists =========================================== */

    if (n_col > 10000 && UMF_debug <= 0)
    {
	return ;
    }
    have = 0 ;
    DEBUG4 (("Degree lists: "ID"\n", min_score)) ;
    for (deg = 0 ; deg <= n_col ; deg++)
    {
	col = head [deg] ;
	if (col == EMPTY)
	{
	    continue ;
	}
	DEBUG4 ((ID":", deg)) ;
	while (col != EMPTY)
	{
	    DEBUG4 ((" "ID, col)) ;
	    have += Col [col].shared1.thickness ;
	    ASSERT (COL_IS_ALIVE (col)) ;
	    col = Col [col].shared4.degree_next ;
	}
	DEBUG4 (("\n")) ;
    }
    DEBUG4 (("should "ID" have "ID"\n", should, have)) ;
    ASSERT (should == have) ;

    /* === Check the row degrees ============================================ */

    if (n_row > 10000 && UMF_debug <= 0)
    {
	return ;
    }
    for (row = 0 ; row < n_row ; row++)
    {
	if (ROW_IS_ALIVE (row))
	{
	    ASSERT (Row [row].shared1.degree <= max_deg) ;
	}
    }
}


/* ========================================================================== */
/* === debug_mark =========================================================== */
/* ========================================================================== */

/*
    Ensures that the tag_mark is less that the maximum and also ensures that
    each entry in the mark array is less than the tag mark.
*/

PRIVATE void debug_mark
(
    /* === Parameters ======================================================= */

    Int n_row,
    Colamd_Row Row [],
    Int tag_mark,
    Int max_mark
)
{
    /* === Local variables ================================================== */

    Int r ;

    /* === Check the Row marks ============================================== */

    ASSERT (tag_mark > 0 && tag_mark <= max_mark) ;
    if (n_row > 10000 && UMF_debug <= 0)
    {
	return ;
    }
    for (r = 0 ; r < n_row ; r++)
    {
	ASSERT (Row [r].shared2.mark < tag_mark) ;
    }
}


/* ========================================================================== */
/* === debug_matrix ========================================================= */
/* ========================================================================== */

/*
    Prints out the contents of the columns and the rows.
*/

PRIVATE void debug_matrix
(
    /* === Parameters ======================================================= */

    Int n_row,
    Int n_col,
    Colamd_Row Row [],
    Colamd_Col Col [],
    Int A []
)
{
    /* === Local variables ================================================== */

    Int r ;
    Int c ;
    Int *rp ;
    Int *rp_end ;
    Int *cp ;
    Int *cp_end ;

    /* === Dump the rows and columns of the matrix ========================== */

    if (UMF_debug < 3)
    {
	return ;
    }
    DEBUG3 (("DUMP MATRIX:\n")) ;
    for (r = 0 ; r < n_row ; r++)
    {
	DEBUG3 (("Row "ID" alive? "ID"\n", r, ROW_IS_ALIVE (r))) ;
	if (ROW_IS_DEAD (r))
	{
	    continue ;
	}

	/* ------------------ */
	/* changed for UMFPACK: */
	DEBUG3 (("start "ID" length "ID" degree "ID" thickness "ID"\n",
		Row [r].start, Row [r].length, Row [r].shared1.degree,
		Row [r].thickness)) ;
	/* ------------------ */

	rp = &A [Row [r].start] ;
	rp_end = rp + Row [r].length ;
	while (rp < rp_end)
	{
	    c = *rp++ ;
	    DEBUG4 (("	"ID" col "ID"\n", COL_IS_ALIVE (c), c)) ;
	}
    }

    for (c = 0 ; c < n_col ; c++)
    {
	DEBUG3 (("Col "ID" alive? "ID"\n", c, COL_IS_ALIVE (c))) ;
	if (COL_IS_DEAD (c))
	{
	    continue ;
	}
	/* ------------------ */
	/* changed for UMFPACK: */
	DEBUG3 (("start "ID" length "ID" shared1[thickness,parent] "ID
		" shared2 [order,score] "ID"\n", Col [c].start, Col [c].length,
		Col [c].shared1.thickness, Col [c].shared2.score));
	/* ------------------ */
	cp = &A [Col [c].start] ;
	cp_end = cp + Col [c].length ;
	while (cp < cp_end)
	{
	    r = *cp++ ;
	    DEBUG4 (("	"ID" row "ID"\n", ROW_IS_ALIVE (r), r)) ;
	}

	/* ------------------ */
	/* added for UMFPACK: */
	DEBUG1 (("Col")) ;
	dump_super (c, Col, n_col) ;
	/* ------------------ */

    }
}

/* ------------------ */
/* dump_super added for UMFPACK: */
PRIVATE void dump_super
(
    Int super_c,
    Colamd_Col Col [],
    Int n_col
)
{
    Int col, ncols ;

    DEBUG1 ((" =[ ")) ;
    ncols = 0 ;
    for (col = super_c ; col != EMPTY ; col = Col [col].nextcol)
    {
	DEBUG1 ((" "ID, col)) ;
	ASSERT (col >= 0 && col < n_col) ;
	if (col != super_c)
	{
	    ASSERT (COL_IS_DEAD (col)) ;
	}
	if (Col [col].nextcol == EMPTY)
	{
	    ASSERT (col == Col [super_c].lastcol) ;
	}
	ncols++ ;
	ASSERT (ncols <= Col [super_c].shared1.thickness) ;
    }
    ASSERT (ncols == Col [super_c].shared1.thickness) ;
    DEBUG1 (("]\n")) ;
}
/* ------------------ */


#endif /* NDEBUG */


```

sparse-matrix/umfpack/umfpack_wsolve.h:
```
/* ========================================================================== */
/* === umfpack_wsolve ======================================================= */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

int umfpack_di_wsolve
(
    int sys,
    const int Ap [ ],
    const int Ai [ ],
    const double Ax [ ],
    double X [ ],
    const double B [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO],
    int Wi [ ],
    double W [ ]
) ;

long umfpack_dl_wsolve
(
    long sys,
    const long Ap [ ],
    const long Ai [ ],
    const double Ax [ ],
    double X [ ],
    const double B [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO],
    long Wi [ ],
    double W [ ]
) ;

int umfpack_zi_wsolve
(
    int sys,
    const int Ap [ ],
    const int Ai [ ],
    const double Ax [ ], const double Az [ ],
    double Xx [ ],	 double Xz [ ],
    const double Bx [ ], const double Bz [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO],
    int Wi [ ],
    double W [ ]
) ;

long umfpack_zl_wsolve
(
    long sys,
    const long Ap [ ],
    const long Ai [ ],
    const double Ax [ ], const double Az [ ],
    double Xx [ ],	 double Xz [ ],
    const double Bx [ ], const double Bz [ ],
    void *Numeric,
    const double Control [UMFPACK_CONTROL],
    double Info [UMFPACK_INFO],
    long Wi [ ],
    double W [ ]
) ;

/*
double int Syntax:

    #include "umfpack.h"
    void *Numeric ;
    int status, *Ap, *Ai, *Wi, sys ;
    double *B, *X, *Ax, *W, Info [UMFPACK_INFO], Control [UMFPACK_CONTROL] ;
    status = umfpack_di_wsolve (sys, Ap, Ai, Ax, X, B, Numeric,
	Control, Info, Wi, W) ;

double long Syntax:

    #include "umfpack.h"
    void *Numeric ;
    long status, *Ap, *Ai, *Wi, sys ;
    double *B, *X, *Ax, *W, Info [UMFPACK_INFO], Control [UMFPACK_CONTROL] ;
    status = umfpack_dl_wsolve (sys, Ap, Ai, Ax, X, B, Numeric,
	Control, Info, Wi, W) ;

complex int Syntax:

    #include "umfpack.h"
    void *Numeric ;
    int status, *Ap, *Ai, *Wi, sys ;
    double *Bx, *Bz, *Xx, *Xz, *Ax, *Az, *W,
	Info [UMFPACK_INFO], Control [UMFPACK_CONTROL] ;
    status = umfpack_zi_wsolve (sys, Ap, Ai, Ax, Az, Xx, Xz, Bx, Bz, Numeric,
	Control, Info, Wi, W) ;

complex long Syntax:

    #include "umfpack.h"
    void *Numeric ;
    long status, *Ap, *Ai, *Wi, sys ;
    double *Bx, *Bz, *Xx, *Xz, *Ax, *Az, *W,
	Info [UMFPACK_INFO], Control [UMFPACK_CONTROL] ;
    status = umfpack_zl_wsolve (sys, Ap, Ai, Ax, Az, Xx, Xz, Bx, Bz, Numeric,
	Control, Info, Wi, W) ;

Purpose:

    Given LU factors computed by umfpack_*_numeric (PAQ=LU) and the
    right-hand-side, B, solve a linear system for the solution X.  Iterative
    refinement is optionally performed.  This routine is identical to
    umfpack_*_solve, except that it does not dynamically allocate any workspace.
    When you have many linear systems to solve, this routine is faster than
    umfpack_*_solve, since the workspace (Wi, W) needs to be allocated only
    once, prior to calling umfpack_*_wsolve.

Returns:

    The status code is returned.  See Info [UMFPACK_STATUS], below.

Arguments:

    Int sys ;		Input argument, not modified.
    Int Ap [n+1] ;	Input argument, not modified.
    Int Ai [nz] ;	Input argument, not modified.
    double Ax [nz] ;	Input argument, not modified.
    double X [n] ;	Output argument.
    double B [n] ;	Input argument, not modified.
    void *Numeric ;	Input argument, not modified.
    double Control [UMFPACK_CONTROL] ;	Input argument, not modified.
    double Info [UMFPACK_INFO] ;	Output argument.

    for complex versions:
    double Az [nz] ;	Input argument, not modified, imaginary part
    double Xx [n] ;	Output argument, real part.
    double Xz [n] ;	Output argument, imaginary part
    double Bx [n] ;	Input argument, not modified, real part
    double Bz [n] ;	Input argument, not modified, imaginary part

	The above arguments are identical to umfpack_*_solve, except that the
	error code UMFPACK_ERROR_out_of_memory will not be returned in
	Info [UMFPACK_STATUS], since umfpack_*_wsolve does not allocate any
	memory.

    Int Wi [n] ;		Workspace.
    double W [c*n] ;		Workspace, where c is defined below.

	The Wi and W arguments are workspace used by umfpack_*_wsolve.  They
	need not be initialized on input, and their contents are undefined on
	output.  The size of W depends on whether or not iterative refinement is
	used, and which version (real or complex) is called.  Iterative
	refinement is performed if Ax=b, A'x=b, or A.'x=b is being solved,
	Control [UMFPACK_IRSTEP] > 0, and A is nonsingular.  The size of W is
	given below:

				no iter.	with iter.
				refinement	refinement
	umfpack_di_wsolve	n		5*n
	umfpack_dl_wsolve	n		5*n
	umfpack_zi_wsolve	4*n		10*n
	umfpack_zl_wsolve	4*n		10*n
*/

```

sparse-matrix/umfpack/umfpack_report_control.h:
```
/* ========================================================================== */
/* === umfpack_report_control =============================================== */
/* ========================================================================== */

/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

void umfpack_di_report_control
(
    const double Control [UMFPACK_CONTROL]
) ;

void umfpack_dl_report_control
(
    const double Control [UMFPACK_CONTROL]
) ;

void umfpack_zi_report_control
(
    const double Control [UMFPACK_CONTROL]
) ;

void umfpack_zl_report_control
(
    const double Control [UMFPACK_CONTROL]
) ;

/*
double int Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    umfpack_di_report_control (Control) ;

double long Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    umfpack_dl_report_control (Control) ;

complex int Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    umfpack_zi_report_control (Control) ;

double long Syntax:

    #include "umfpack.h"
    double Control [UMFPACK_CONTROL] ;
    umfpack_zl_report_control (Control) ;

Purpose:

    Prints the current control settings.  Note that with the default print
    level, nothing is printed.  Does nothing if Control is (double *) NULL.

Arguments:

    double Control [UMFPACK_CONTROL] ;   Input argument, not modified.

	If a (double *) NULL pointer is passed, then the default control
	settings are used.  Otherwise, the settings are determined from the
	Control array.  See umfpack_*_defaults on how to fill the Control
	array with the default settings.  If Control contains NaN's, the
	defaults are used.  The following Control parameters are used:

	Control [UMFPACK_PRL]:  printing level.

	    1 or less: no output
	    2 or more: print all of Control
	    Default: 1
*/

```

sparse-matrix/umfpack/umf_extend_front.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL void UMF_extend_front
(
    WorkType *Work
) ;


```

sparse-matrix/umfpack/ChangeLog:
```
Apr 11, 2002:

    * bug fix:  the Microsoft compiler doesn't handle NaN's properly.
	utIsNaN, and other ut* routines, added for MathWorks version
	to handle this properly.

Apr 1, 2002:

    * bug fix:  if a column was all NaN's, then UMFPACK would fail
	to find a pivot row.  umf_row_search.c and umf_internal.h
	modified to fix this problem.

Mar 9, 2002:  V4.0beta released

    * Map argument added to umfpack_*_triplet_to_col.  New files
	(umf_triplet.[ch]) added.
    * minor changes made so that UMFPACK can be compiled with g++ 
    * additional error checking added to umfpack_*_numeric, for
	detecting more changes in pattern (Ap, Ai) since last
	call to umfpack_*_symbolic

Feb 21, 2002:

    * User Guide explains the Makefile vs. GNUmakefile

    * umf_config.h modified, so that the complex SCSL C-BLAS uses
	(void *) arguments instead of (scsl_zomplex *).  gcc generates
	some spurious warnings (cc doesn't complain).  Affects the SGI
	IRIX only.

    * ported to Compaq Alpha

Feb 20, 2002: V4.0 (alpha) released.

    * V4.0 not yet ported to the Compaq Alpha (V3.2 was ported).

Feb 6 to Feb 19, 2002:

    * Relaxed restrictions on sizes of arrays for umfpack_*_transpose and
	umfpack_*_triplet_to_col.  Size of "max(n,nz)" now just size nz.

    * workspace for umfpack_*_wsolve increased in size.

    * two user arrays for umfpack_*_get_symbolic increased in size,
	by 1 (Chain_maxrows, Chain_maxcols).

    * lu_normest.m added.

Jan 18 to Feb 5, 2002:

    * The matrix A can be complex, singular, and/or rectangular.
	The solve step that uses the LU factors can only handle
	matrices that are complex or real, singuluar or non-singular, 
	and *** square ***, however.

    * Estimate of the condition number computed:
	(min (abs (diag (U))) / (max (abs (diag (U)))))

    * Forward/backsolves can solve with A.' as well as A'.

    * char * arguments removed from user-callable routines to make it
	easier for Fortran to call UMFPACK.  No Fortran interface is (yet)
	provided, however.

	The solve codes for umfpack_*_*solve changed to #define'd
	integers:

	    UMFPACK_A       Ax=b
	    UMFPACK_At      A'x=b
	    UMFPACK_Aat     A.'x=b
	    UMFPACK_Pt_L    P'Lx=b
	    UMFPACK_L       Lx=b
	    UMFPACK_Lt_P    L'Px=b
	    UMFPACK_Lat_P   L.'Px=b
	    UMFPACK_Lt      L'x=b
	    UMFPACK_U_Qt    UQ'x=b
	    UMFPACK_U       Ux=b
	    UMFPACK_Q_Ut    QU'x=b
	    UMFPACK_Q_Uat   QU.'x=b
	    UMFPACK_Ut      U'x=b
	    UMFPACK_Uat     U.'x=b

	All arguments are now either int, long scalars (pass by value),
	or int, long, double arrays (pass by reference), or void * pointers
	(pass by value or reference).  A void * pointer is of size 32 or 64
	bits on most machines.  There is no need for the caller (C or Fortran)
	to dereference the void * pointers, so these can be treated as
	integer*4 or integer*8 in Fortran.  A Fortran interface would have to
	have all arguments passed by reference.

    * All user-callable routine names changed.  The four sets are now:
	umfpack_di_*	real (double precision), int's as integers
	umfpack_dl_*	real (double precision), longs's as integers
	umfpack_zi_*	real (double precision), int's as integers
	umfpack_zl_*	real (double precision), longs's as integers

    * Ptree (row preordering) and info on pivotal rows for each front
	added to Symbolic object (extracted by umfpack_*_get_symbolic).
	Ptree added as output argument to "umfpack (A, 'symbolic')"
	mexFunction.

    * umfpack_*_transpose can do A' or A.'

    * umfpack_wsolve.c file removed (now generated from umfpack_solve.c).

    * Can now extract just the diagonal of U with umfpack_*_get_numeric,
	without having to extract the entire matrix U.

    * UMFPACK_ERROR_singular_matrix (-2) removed.

    * UMFPACK_WARNING_singular_matrix (1) added.

    * Control [UMFPACK_PIVOT_OPTION] removed.  No longer any symmetric
	pivot option (conflicts with the handling of singular and
	rectangular matrices).

    * Iterative refinement can do Ax=b, A'x=b, or A.'x=b.

    * Most floating-point operations done in macros, to support the complex
	versions.

    * Info [UMFPACK_N] is now Info [UMFPACK_NROW]

    * Info [UMFPACK_NCOL], Info [UMFPACK_UDIAG_NZ], Info [UMFPACK_UDIAG_NZ]
	added.

    * umfpack_* routines with "n" as input now use two arguments,
	n_row and n_col.

    * umfpack mexFunction now explicitly transposes A for b/A.  It computes
	it using the array transpose as (A.'\b.').'

January 1, 2002:  UMFPACK Version 3.2 released.  Submitted to ACM Trans.
	on Mathematical Software.

    * The umfpack mexFunction now returns the Info array when the matrix
	is singular.  Returned an empty array prior to this change.

    * Renamed variable that conflicted with system library routines
    	(system and j1).

    * Added a #ifdef MATHWORKS definition, so the built-in UMFPACK routine
	(in a future release of MATLAB) can use the internal ut* memory
	allocation routines, ut* assertion routine, and utPrintf.

    * MAX and MIN are not defined if they are already defined.

    * A bug fix in umf_kernel_init (a variable was not properly initialized).

    * Removed unused variables.

October 8, 2001:  UMFPACK Version 3.1 released.

August-October, 2001:

    * added umfpack_btf M-file.

    * modified the BLAS update in the frontal matrix.  If there are only
      a few pivots in remaining in the current front, then the BLAS3 update
      is delayed to include pivots in the next front.

    * Removed the special-case handling of dense columns from the numerical
      factorization (kept it in the colamd preordering).  This improves the
      performance of UMFPACK on dense matrices by a factor of 5 or so, and
      simplifies the code.

    * Added a symmetric-preference pivoting option.  The option slightly
      (but uniformly) improves the ordering when factorizing matrices with
      symmetric nonzero pattern.  That class of matrix is better handled by
      the symmetric-pattern multifrontal method (MA41 in the Harwell
      Subroutine Library), however.

    * Fixed the detection of integer overflow.  The 32-bit version cannot
      make use of more than 2GB of main memory (use the 64-bit version
      in that case, instead).  The 32-bit version did not correctly detect
      when it was trying to factorize too large of a matrix.

May 4, 2001:

    * SGI port extended.  It can now call the SCSL Scientific Library, with
	64-bit BLAS.  Make.sgi and umf_config.h modified.

April 30, 2001:  UMFPACK Version 3.0 released.  Changes since 3.0Beta release:

    * Long integer version added (umfpack_l_* user-callable routines).

    * Peak memory usage in the numerical factorization reduced by a total of
	12n integers (8n temporary workspace used during numerical
	factorization, and 4n for the permanent LU factors which was allocated
	at the beginning of factorization).

    * Ported to the IBM RS 6000 and Compaq Alpha, with help from Anshul Gupta
	and Friedrich Grund, respectively.

    * 64-bit version added.  Uses dgemm_64, dgemv_64, and dger_64 in the Sun
	Performance Library.  64-bit versions with the BLAS might not work on
	any other platform, because they take int's as their integer input
	arguments instead of long's.  Unfortunately, the proposed ANSI
	definition of the C-BLAS also uses int's as input integer arguments.
	It ought to use long's, or include a version that uses long's, just
	like the Sun Performance Library BLAS.

    * Additional statistics returned in Info:
	Info [UMFPACK_SIZE_OF_INT]	sizeof (int)
	Info [UMFPACK_SIZE_OF_LONG]	sizeof (long)
	Info [UMFPACK_SIZE_OF_POINTER]	sizeof (void *)
	Info [UMFPACK_SIZE_OF_ENTRY]	(was Info [UMFPACK_WORD])
	Info [UMFPACK_MAX_FRONT_SIZE_ESTIMATE]	est. front matrix size
	Info [UMFPACK_MAX_FRONT_SIZE]	actual max frontal matrix size.
	Contents of Info rearranged.

    * UMFPACK_ERROR_bad_configurution error code replaced with
	UMFPACK_ERROR_problem_too_large error code.  The "bad configuration"
	error occured when sizeof (int) < sizeof (size_t).  Now, the int
	version of UMFPACK can use 32-bit int's and 64-bit pointers, and the
	long version can use 64-bit long's and 64-bit pointers.  Both versions
	check to see if the array sizes allocated are larger than what can be
	accessed by an integer index variable (int or long, depending on the
	version), and returns UMFPACK_ERROR_problem_too_large if they become
	too large.

March 15, 2001:  UMFPACK Version 3.0Beta released.


```

sparse-matrix/umfpack/umf_row_search.h:
```
/* -------------------------------------------------------------------------- */
/* UMFPACK Version 4.0 (Apr 11, 2002), Copyright (c) 2002 by Timothy A.       */
/* Davis.  All Rights Reserved.  See README for License.                      */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.            */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                       */
/* -------------------------------------------------------------------------- */

GLOBAL Int UMF_row_search
(
    NumericType *Numeric,
    WorkType *Work,
    SymbolicType *Symbolic,
    Int cdeg,
    const Int Pattern [ ],
    Int pivrow [2],
    Int rdeg [2],
    Int W_i [ ],
    Int W_o [ ],
    Int prior_pivrow [2],
    const Entry Fcol [ ],
    Int pivcol,
    Int freebie [2]
) ;


```


combined_doc_context: ## README (README):
```
Cactus Code Thorn AHFinderDirect
Authors    : Jonathan Thornburg <jthorn@aei.mpg.de>
Maintainer : Jonathan Thornburg <jthorn@aei.mpg.de>
CVS info   : $Header$
--------------------------------------------------------------------------

Purpose of This Thorn
=====================

This thorn finds an apparent horizon (or more generally, a closed
2-surface with S^2 topology having any desired constant expansion)
given the 3D xyz-grid metric and extrinsic curvature (and optionally
the StaticConformal conformal factor).  It uses a direct method,
writing the apparent horizon equation as an elliptic PDE on
angular-coordinate space.  This is very fast and accurate, but
it does require an intitial guess for the apparent horizon position.


Documentation
=============

The doc/ directory contains detailed documentation for this thorn,
including a discussion of those parameters which you'll likely want
to set (or at least know about) in practice.  For full information
on all the parameters, see the comments in the  param.ccl  file.

The par/ directory contains some sample par files.

src/CODESTYLE documents the general programming conventions used in
this thorn.

src/PORTING.OUTSIDE.CACTUS has some general remarks on what it would
take to port this thorn to a non-Cactus numerical relativity code.

See below in this file for notes on compiling this thorn.


Publications
============
The algorithms used in this thorn are described in the papers
@article{
	Thornburg2003:AH-finding,
	author = "Jonathan Thornburg",
	title  = "A Fast Apparent-Horizon Finder
		  for 3-Dimensional {C}artesian Grids in Numerical Relativity",
	journal = "Classical and Quantum Gravity",
	volume = 21, number = 2,
	year   = "2004", month = "January 21",
	pages  = "743--766",
	doi    = "10.1088/0264-9381/21/2/026",
	url    = "http://stacks.iop.org/0264-9381/21/743",
	eprint = "gr-qc/0306056",
	note   = "gr-qc/0306056",
	}
and
@article{
	Thornburg-1996-horizon-finding,
	author = "Jonathan Thornburg",
	title = "Finding Apparent Horizons in Numerical Relativity",
	journal = "Physical Review D",
	pages = "4899--4918",
	volume = 54, number = 8,
	year = 1996, month = "October 15",
	eprint = "gr-qc/9508014",
	}
As a courtesy, I ask that these papers be cited in any published research
which uses this thorn, or which uses code from this thorn.


Copyright
=========

This thorn is copyright (C) 2001-2004
by Jonathan Thornburg <jthorn@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


Other Software Used (Already Contained within AHFinderDirect)
=============================================================

AHFinderDirect's  src/sparse-matrix/  directory contains various
sparse-matrix libraries, which have their own copyrights and licensing terms:

The src/sparse-matrix/umfpack/ directory contains a subset of the files
in UMFPACK version 4.0 (11.Apr.2002).  This code is copyright (C) 2002 by
Timothy A. Davis, and is subect to the UMFPACK License:

    Your use or distribution of UMFPACK or any modified version of
    UMFPACK implies that you agree to this License.

    THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
    EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.

    Permission is hereby granted to use or copy this program, provided
    that the Copyright, this License, and the Availability of the original
    version is retained on all copies.  User documentation of any code that
    uses UMFPACK or any modified version of UMFPACK code must cite the
    Copyright, this License, the Availability note, and "Used by permission."
    Permission to modify the code and to distribute modified code is granted,
    provided the Copyright, this License, and the Availability note are
    retained, and a notice that the code was modified is included.  This
    software was developed with support from the National Science Foundation,
    and is provided to you free of charge.


Other Software Required
=======================

This thorn inherits from Grid, ADMBase, StaticConformal, SpaceMask, IO
(all in the CactusBase or CactusEinstein arrangements), and
SphericalSurface (in the AEIThorns arrangement; you can get AEIThorns
by anonymous CVS checkout from cvs_anon@cvs.aei.mpg.de:/numrelcvs, CVS
path AEIThorns/SphericalSurface).

By default this thorn uses various Cactus APIs which are supplied by
other thorns:
* This thorn uses CCTK_InterpGridArrays() for interpolating grid arrays.
  This is supplied by a driver-specific thorn, such as PUGHInterp or
  CarpetInterp.
* This thorn uses CCTK_ReduceLocArrayToArray1D() for interprocessor
  communication in the multiprocessor Newton solver.
	[see src/driver/README.parallel for details]
  This is supplied by a driver-specific thorn; at present PUGHReduce
  it this for the PUGH driver.
* This thorn uses CCTK_InterpLocalUniform() for interpatch and surface
  interpolation, and it needs various interpolation options which at
  present are (only) supported by the AEIThorns/AEILocalInterp local
  interpolator.

This thorn is written in C++, so you'll need a C++ compiler -- in fact
a fairly modern one -- to compile this thorn.  See the "Compilation Notes"
section below for details.

Most of this thorn's relativity code is machine-generated using Maple
(version 7), but you don't need Maple unless you want to modify the
relativity code.

By default, this thorn doesn't use any external libraries.  However,
if HAVE_DENSE_JACOBIAN__LAPACK is defined in src/include/config.h,
then this thorn uses the LAPACK library (which in turn uses the BLAS
library), so you will need to configure your Cactus to use LAPACK.
Instructions on doing this are in the file README.library in this
directory.


Code Notes
==========

This thorn is written in C++, and requires a fairly modern C++ compiler.
In particular:
* Templates are used, but only rather simple ones for containers or other
  low-level stuff templated on the floating-point or integer datatype.
  These templates are always instantiated explicitly.
* bool, mutable, typename are used.
* The new-style casts static_cast<...> and const_cast<...> are used.
  reinterpret_cast<...> and dynamic_cast<...> are *not* used.
* The code will compile ok under either the archaic or the modern
  for-loop declaration scope rules.
* C header files are used in the pre-namespaces form (eg <stdio.h>).
  (In ISO-standard C++ this is deprecated (but still supported), but
  alas none of the more modern forms (<cstdio> and namespace std::)
  seem to be supported on as wide a range of systems as the pre-namespaces
  form.)
* <assert.h> is used fairly heavily for sanity checks.
* To avoid various portability problems, none of the C++ standard
  template library (STL) is used.


Compiler Notes
==============

This thorn should be fully portable across architectures and operating
systems, but C++ compilers may be a problem:

This thorn has been compiled and run successfully using

x86
===
* gcc version 2.95.3 20010125 (prerelease)
* g++ (GCC) 3.2 for x86
* g++ (GCC) 3.2.2 for x86
* g++ (GCC) 3.3 for x86
* Intel(R) C++ Compiler for 32-bit applications, Version 5.0.1   Build 010730D0
* Intel C++ Compiler for Linux, version 6.0
* Intel(R) C++ Compiler for 32-bit applications, Version 7.0   Build 20021021Z
  On platinum.ncsa.uiuc.edu, Denis Pollney reports success configuring Cactus
  with
    LIBS="crypt F90 CEPCF90 intrins mkl mkl_lapack guide"
    LIBDIRS=/usr/local/intel/mkl/lib/32
* Portland Group pgCC 3.3-2
  On platinum.ncsa.uiuc.edu, Denis Pollney reports success configuring Cactus
  with
    LAPACK=yes
    LAPACK_DIR=/usr/local/pgi/linux86

ia64, a.k.a. Itanium
====================
* Intel(R) C++ Itanium(R) Compiler for Itanium(R)-based applications
  Version 7.0 Beta, Build 20020911
  On titan.ncsa.uiuc.edu, Denis Pollney reports success configuring Cactus
  with
    LIBS="crypt F90 CEPCF90 intrins mkl_lapack mkl_itp guide"
    LIBDIRS=/usr/local/intel/mkl/lib/64

Alpha
=====
* g++ (GCC) 3.0
  On lemieux.psc.edu, Denis Pollney reports success configuring Cactus
  with
    LIBS="m c_r dxml for"
* Using the DEC/Compaq/HP/whatever-their-corporate-name-is-this-week
  compiler, you need to specify the  -nopt  compiler option to disable
  automatic template instantiation.

SGI
===
* On modi4.ncsa.uiuc.edu (an old SGI Origin), using
  MIPSpro Compilers: Version 7.3.1.3m
  you need to specify the  -no_auto_include  compiler option to disable
  automatic template instantiation.  In fact, you should probably specify
  this option on any SGI system...

IBM Power4 (AIX)
================
* C for AIX Compiler, Version 6
  On psi19.rzg.mpg.de, Denis Pollney reports success configuring Cactus
  with
    LIBS="f m lapack-essl essl"
    LIBDIRS="/afs/rzg/@sys/lib"

Please let me (Jonathan) know of any other compilers which are ok.

This thorn compiles, but does *NOT* work, using the folloing
systems/compilers; the cure is to switch to a newer compiler version:
x86
===
* gcc version 2.96 20000731 (Red Hat Linux 7.1 2.96-98)
  (This is the system gcc on Red Hat GNU/Linux 7.2 systems)

The code will almost certainly *not* compile using
* any "egcs" version of gcc, or more generally, any version of gcc
  earlier than 2.95
* any version of Microsoft Visual C++ up to and including mid-2002

```

## 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
%  relevent 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
%       % BEGIN CACTUS THORNGUIDE",
%     except for filling in the title, author, date etc. fields.
%   - You can define your own macros are OK, but they must appear after
%     the BEGIN CACTUS THORNGUIDE line, and do 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 graphix package.
%     More specifically, with the "includegraphics" command. Do
%     not specify any graphic file extensions in your .tex file. This
%     will allow us (later) to create a PDF version of the ThornGuide
%     via pdflatex. |
%   - References should be included with the latex "bibitem" command.
%   - 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}
\bibliographystyle{alpha}

% 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{Jonathan Thornburg \quad {\tt <jthorn@aei.mpg.de>}}

% The title of the document (not necessarily the name of the Thorn)
\title{{\bf AHFinderDirect} -- A Fast Apparent Horizon Finder}

% the date your document was last changed, if your document is in CVS,
% please us:
%    \date{$ $Date$ $}
\date{$ $Date$ $}

\maketitle

% Do not delete next line
% START CACTUS THORNGUIDE

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

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% misc text stuff
\def\code#1{{\tt #1}}			% variable names, parameter values, etc
\def\defn#1{``#1''}			% definition of a term
\def\arrangement#1{{\bf #1}}		% name of an arrangement
\def\thorn#1{{\bf #1}}			% name of a thorn
\def\program#1{{\bf #1}}		% name of computer program
\def\cvsplace#1{{\bf #1}}		% name of a CVS repository/directory/tag
\def\cf{\hbox{cf.\hbox{}}}
\def\eg{\hbox{eg.\hbox{}}}
\def\ie{\hbox{i.e.\hbox{}}}
\def\eqref#1{$(\ref{#1})$}

% get size/spacing of "++" right, cf online C++ FAQ question 35.1
\def\Cplusplus{\hbox{C\raise.25ex\hbox{\footnotesize ++}}}

% misc math mode stuff
\def\assign{\leftarrow}			% for pseudocode algorithms
\def\ltsim{\lesssim}
\def\gtsim{\gtrsim}
\def\tfrac#1#2{{\textstyle    \frac{#1}{#2}}}
\def\dfrac#1#2{{\displaystyle \frac{#1}{#2}}}
\def\thalf{\tfrac{1}{2}}
\def\const{{\rm const}}
\def\ij{{ij}}
\def\uv{{uv}}
\def\del{\nabla}

\def\I{{\text{\scriptsize I}}}                  % grid-point index
\def\J{{\text{\scriptsize J}}}                  % grid-point index
\def\K{{\text{\scriptsize K}}}                  % grid-point index
\def\M{{\text{\scriptsize M}}}                  % molecule index

\def\Jac[#1]{{\bf J} \bigl[ #1 \bigr]}          % discrete Jacobian for fn #1

% Add an abstract for this thorn's documentation
\begin{abstract}
Thorn \thorn{AHFinderDirect} locates apparent horizons (or more
generally, closed 2-surfaces with $S^2$ topology having any desired
constant expansion) in a numerically computed slice using a direct
method, posing the apparent horizon equation as an elliptic PDE on
angular-coordinate space.  This is very fast and accurate, but
requires a ``reasonable'' initial guess.  This thorn guide describes
how to use the thorn.
\end{abstract}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Introduction}

A \defn{marginally trapped surface} is a closed 2-surface in a slice whose
congruence of future-pointing outgoing null geodesics has zero expansion.
There may be several such surfaces, some nested inside others; an
\defn{apparent horizon} is an outermost marginally trapped surface.
In terms of the usual $3+1$ variables, an apparent horizon satisfies
the equation
\begin{equation}
\Theta \equiv \del_i n^i + K_\ij n^i n^j - K = 0
					\label{AHFinderDirect/eqn-horizon}
\end{equation}
where $n^i$ is the outward-pointing unit normal to the apparent horizon,
and $\del_i$ is the covariant derivative operator associated with the
3-metric $g_\ij$ in the slice.  (See \cite{AHFinderDirect/York89}
for a derivation of equation~\eqref{AHFinderDirect/eqn-horizon}.)
(Optionally, you can replace the right hand side
of~\eqref{AHFinderDirect/eqn-horizon} by any specified nonzero
constant, \ie{} you can find a surface of constant
(in general nonzero) expansion.; this is dicsussed in
section~\ref{AHFinderDirect/sect-parameters/other-parameters}.)

Thorn~\thorn{AHFinderDirect} finds an apparent horizon by numerically
solving equation~\eqref{AHFinderDirect/eqn-horizon}.  It requires
as input the usual Cactus 3-metric $g_\ij$ and extrinsic curvature
$K_\ij$, (and optionally the conformal factor $\psi$ if the
\thorn{StaticConformal} metric semantics are used),
and produces as output the Cactus $(x,y,z)$ coordinates of a large
number of points on the apparent horizon, together with some auxiliary
information like the apparent horizon area and centroid position, and
the irreducable mass associated with the area.

Besides this thorn guide, the other main sources of information on
\thorn{AHFinderDirect} are the comments in the \verb|param.ccl| file,
the paper \cite{AHFinderDirect/Thornburg2003:AH-finding},
and to a lesser extent the paper \cite{AHFinderDirect/Thornburg95}.
As a courtesy, I ask that both these papers be cited in any published
research which uses this thorn, or which uses code from this thorn.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{What \thorn{AHFinderDirect} Needs}
\label{AHFinderDirect/sect-what-AHFinderDirect-needs}

There are some restrictions on the spacetime, or more precisely
on each slice where you want to find apparent horizons, which are
necessary in order for \thorn{AHFinderDirect} to work:
\begin{itemize}
\item	\thorn{AHFinderDirect} requires that the Cactus geometry
	($g_{ij}$, $K_{ij}$, and optionally $\psi$) be nonsingular
	in a neighborhood of the apparent horizon.  In particular,
	this means that it quite certainly will {\em not\/} work for
	spacetimes/slicings which have a singular geometry on the
	horizon, such as Schwarzschild/Schwarzschild and
	Kerr/Boyer-Lindquist.%%%
\footnote{%%%
	 Nonsingular slicings of those same spacetimes,
	 such as Schwarzschild/Eddington-Finkelstein,
	 Kerr/Kerr, and Kerr/Kerr-Schild, are fine, and
	 are in fact nice test cases.
	 }%%%
\item	Less obviously, this also means that if there is a
	singularity in the geometry somewhere near the apparent
	horizon, then you need to have a high enough Cactus 3-D grid
	resolution that the geometry interpolation doesn't ``see''
	the singularity.  (If \thorn{AHFinderDirect} ``sees'' the
	singularity, it may ``just'' fail to find the horizon,
	and/or it may report that the interpolated $g_{ij}$ fails
	to be positive definite or even contains NaNs.)
\item	At the moment \thorn{AHFinderDirect} and the Cactus interpolators
	don't know how to avoid an excised region, so if the apparent horizon
	(or any trial horizon surface as the algorithm is iterating
	towards the apparent horizon) gets too close to an excised
	region, you'll get garbage results as the interpolator tries
	to interpolate data from the excised region.  I plan to fix
	this sometime soon.
\item	\thorn{AHFinderDirect} requires that any apparent horizon
	it's going to (try to) find must be a \defn{Strahlk\"{o}rper}
	(literally ``ray body'', or more commonly ``star-shaped region'')
	relative to some local coordinate origin (which you must specify).
	A Strahlk\"{o}rper is defined by Minkowski
	(\cite[p.~108]{AHFinderDirect/Schroeder86})
	as
	   \begin{quote}
	   a region in $n$-dimensional Euclidean space containing the
	   origin and whose surface, as seen from the origin, exhibits
	   only one point in any direction.
	   \end{quote}
	In other words, using polar spherical coordinates relative
	to the local coordinate origin, the apparent horizon's shape
	must be parameterizable as $r = h(\text{angle})$ for some
	single-valued function $h: S^2 \to \Re^+$.  (\thorn{AHFinderDirect}
	uses precisely this parameterization.)
\end{itemize}

There are also some restrictions on your Cactus configuration and run;
here's what works and what doesn't:
\begin{itemize}
\item	I {\em strongly\/} recommend using a current-CVS checkout of
	the Cactus flesh and of all relevant thorns.  I haven't tested
	\thorn{AHFinderDirect} at all with older versions of the flesh
	or other thorns.
\item	\thorn{AHFinderDirect} works fine with the \thorn{PUGH} unigrid
	driver and with the \thorn{Carpet} mesh-refinement driver.  So
	far as I know it's never been tested with any other driver.
\item	\thorn{AHFinderDirect} works fine in single- or multi-processor
	Cactus runs.
\item	Obviously, your Cactus configuration must include
	\thorn{AHFinderDirect}, and your \code{ActiveThorns} parameter(s)
	must activate it.
\item	\thorn{AHFinderDirect} inherits from the other thorns
	(strictly speaking, implementations) listed in
	table~\ref{AEIThorns/AHFinderDirect/tab-inherits-from},
	so you'll need them (or more precisely some thorns providing them)
	in your configuration and activated, too.


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{table}[tbp]
\begin{center}
\begin{tabular}{l@{\qquad}l}
\thorn{Implementation}		& Typically provided by \thorn{Thorn}	\\
\hline %---------------------------------------------------------------
\thorn{Grid}			& \thorn{CactusBase/CartGrid3d}		\\
\thorn{IO}			& \thorn{CactusBase/IOUtil}		\\
\thorn{ADMBase}			& \thorn{CactusEinstein/ADMBase}	\\
\thorn{StaticConformal}		& \thorn{CactusEinstein/StaticConformal}\\
\thorn{SpaceMask}		& \thorn{CactusEinstein/SpaceMask}	\\
\thorn{SphericalSurface}	& \thorn{AEIThorns/SphericalSurface}	%%%\\
\end{tabular}
\end{center}
\caption[Other Thorns from which \thorn{AHFinderDirect} Inherits]
	{
	This table lists all the other implementations from which
	\thorn{AHFinderDirect} inherits, and the thorns which typically
	provide these implementations.
	}
\label{AEIThorns/AHFinderDirect/tab-inherits-from}
\end{table}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\item	\verb|Grid::domain = "full"|, \verb|"bitant"|,
	\verb|"quadrant"|, and \verb|"octant"| are supported.
	Alas, at present rotating (or more precisely nonlocal)
	symmetry boundary conditions aren't supported.
\item	The \verb|ADMBase::metric_type| values \verb|"physical"| and
	\verb|"static conformal"| are supported; for the latter you must
	have storage turned on for at least the conformal factor
	\verb|StaticConformal::psi|.  (The Cactus 3-D grid functions
	for 1st and 2nd derivatives of \verb|psi| aren't used.)
\item	\thorn{AHFinderDirect} uses the \verb|CCTK_InterpGridArrays()|
	Cactus global (multi-processor grid array) interpolator API;
	this is provided by \thorn{PUGHInterp} or \thorn{CarpetInterp}
	(so you must have the appropriate one of these thorns compiled
	in and activated).  \verb|CCTK_InterpGridArrays()| in turn uses
	the new \verb|CCTK_InterpLocalUniform()| processor-local
	interpolator API; \thorn{AHFinderDirect} uses various options
	in this API which at present are only supported by thorn
	\thorn{AEILocalInterp} (so you must have this thorn compiled in
	and activated)
\item	\thorn{AHFinderDirect} uses various Cactus reduction APIs to
	coordinate multi-processor horizon finding, so (even if you're
	only going to run on a single processor) you must have a reduction
	thorn like \thorn{PUGHReduce} or \thorn{CarpetReduce} compiled in
	and activated.
\item	At present only a few of \thorn{AHFinderDirect}'s parameters
	are steerable.  (This is actually quite easy to fix; I just
	haven't gotten around to it yet.)
\item	I think \thorn{AHFinderDirect} will ``work'' with checkpoint/restart,
	but I haven't tested this yet.  Here ``work'' means the restart
	will be like starting a new run, in that \thorn{AHFinderDirect}
	will set the initial guess for each horizon in a start-of-a-new-run
	manner.  Alas, it will also write a new \verb|BH_diagnostics| file
	for each horizon found, overwriting any existing \verb|BH_diagnostics|
	files.  This is a bug, which I plan to fix soon (the right behavior
	is/will be to {\em append\/} to the existing \verb|BH_diagnostics|
	file).
\end{itemize}

\thorn{AHFinderDirect} can pass information to the rest of Cactus in
several ways; these are described in detail in
section~\ref{AHFinderDirect/sect-parameters/communicating-with-other-thorns}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Obtaining and Compiling \thorn{AHFinderDirect}}

You should be able to obtain the source code for this thorn via the
usual procedures for anonymous git checkout; at present it lives in
the \arrangement{EinsteinAnalysis} arrangement.

This thorn is written primarily in \Cplusplus{}, calling C and
Fortran~77 numerical libraries.%%%
\footnote{%%%
	 The (C) code for the relativity computations
	 (\ie{}~the apparent horizon equation itself)
	 is mainly machine-generated from Maple code,
	 which in turn uses a Perl preprocessor.
	 But the machine-generated C code is already
	 in CVS along with the rest of this thorn's
	 source code, so you don't need to have Maple
	 installed unless you want to modify the
	 relativity computations.%%%
	 }%%%
{}  In theory the code should be quite portable to modern \Cplusplus{}
compilers, but in practice I've had a number of portability problems
with various compilers.  See the ``Code Notes'' and ``Compiler Notes''
sections in the top-level \code{README} file for details and lists of
compilers currently known to be ok or not.

By default \thorn{AHFinderDirect} doesn't use any external libraries.
However, if \code{HAVE\_DENSE\_JACOBIAN\_\_LAPACK} is defined in
\code{src/include/config.h}, then \thorn{AHFinderDirect} uses the
LAPACK library for solving linear equations.  In this case you need
to configure Cactus with \code{LAPACK=yes}.  See the top-level
\code{README} and \code{README.library} files for details on this.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{\thorn{AHFinderDirect} Parameters}

This thorn has lots of parameters, but most of them have reasonable
default values which you probably won't need to change.  Here I describe
the parameters which you are likely to want to at least look at, and
possibly set explicitly.

Note that all of the ``\code{[}$n$\code{]}'' parameters are Cactus
array parameters, which you need to specify separately in your parameter
file for each apparent horizon.  {\bf IMPORTANT: Apparent horizons are
numbered starting at 1, not 0!}  The example in
section~\ref{AHFinderDirect/sect-examples} should make this clear.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Overall Parameters}
\label{AHFinderDirect/sect-parameters/overall-parameters}

\begin{description}
\item[\code{find\_every}]
\mbox{}\\
	This is an integer parameter specifying how often
	\thorn{AHFinderDirect} should try to find apparent horizons:
	If $\verb|find_every| = 0$, \thorn{AHFinderDirect} is a no-op.
	If $\verb|find_every| \ne 0$, \thorn{AHFinderDirect} tries to
	find apparent horizons each \verb|find_every| time steps.%%%
\footnote{%%%
	 More precisely, if ${\tt find\_every} \ne 0$,
	 \thorn{AHFinderDirect} tries to find apparent
	 horizons at each time step where {\tt cctk\_iteration}
	 is evenly divisible by {\tt find\_every}.
	 }%%%
{}	The default value is~1, \ie{} \thorn{AHFinderDirect} tries
	to find apparent horizons at every time step.
\item[\code{N\_horizons}]
\mbox{}\\
	How many apparent horizons do you want to find in each slice?
	Typical values are 1 (the default), 2, or 3.%%%
\footnote{%%%
	 Larger values are also possible.  The present upper
	 limit is 100, but it would be trivial to raise this
	 further if desired -- see the comments in \code{param.ccl}
	 for details.
	 }%%%
{}	{\bf This thorn numbers the apparent horizons from 1 to
	\code{N\_horizons} inclusive.}
	There are a number of other parameters (described below)
	which you need to set for of these each apparent horizons.

	Note that \code{N\_horizons} sets the number of apparent horizons
	you want to find {\em in the Cactus 3-D numerical grid\/}, not in
	the whole spacetime.  For example, if you are simulating (say)
	Misner data with \verb|Grid::domain = "bitant"|, with the two
	throats at (say) roughly $z = \pm 1$, then you should set
	\verb|N_horizons = 1| to find those two apparent horizons,
	since you're only finding one apparent horizon within the
	numerical grid.  If you also want to search for a common
	apparent horizon surrounding both black holes, then you should
	set \verb|N_horizons = 2|, since you're finding at most 2
	apparent horizons within the numerical grid.

\item[\code{verbose\_level}]
\mbox{}\\
	This controls how verbose this thorn is in printing
	informational (non-error) messages describing what it's
	doing.  In order from tersest to most verbose, the allowable
	values are
	\begin{description}
	\item[\code{"no output"}]
	\mbox{}\\
		Don't print anything.
	\item[\code{"physics highlights"}]
	\mbox{}\\
		Print only a single line each time
		\thorn{AHFinderDirect} runs, giving which
		horizons were found.
	\item[\code{"physics details"}]
	\mbox{}\\
		Print two lines for each horizon found, giving
		the horizon area, centroid position, and irreducible mass.
		This is the default.
	\item[\code{"algorithm highlights"}]
	\mbox{}\\
		Also print a single line for each Newton iteration giving
		the 2-norm and $\infty$-norm of the $\Theta(h)$ function
		defined by equation~\eqref{AHFinderDirect/eqn-horizon}.
	\item[\code{"algorithm details"}]
	\mbox{}\\
		Print lots of detailed messages tracing what the code
		is doing.
	\item[\code{"algorithm debug"}]
	\mbox{}\\
		Print even more detailed messages tracing what the code
		is doing, mainly useful for debugging purposes.
	\end{description}
\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Choosing the Local Coordinate Origin for each Apparent Horizon}
\label{AHFinderDirect/sect-parameters/local-coordinate-origin}

For each apparent horizon you want to find, you need to specify
the Cactus $(x,y,z)$ coordinates of a local coordinate system origin.
As described in
section~\ref{AHFinderDirect/sect-what-AHFinderDirect-needs},
each apparent horizon must be a Strahlk\"{o}rper with respect to its
local coordinate system origin.

You specify the local coordinate system origin for each horizon with
the (Cactus array) parameters
\begin{description}
\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{origin\_x[}$n$\code{]}	\\
     \code{origin\_y[}$n$\code{]}	\\
     \code{origin\_z[}$n$\code{]}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	These all default to 0.0.
	In practice, you should set these parameters to be somewhere
	reasonably close to your best guess for the center of each apparent
	horizon.  These aren't {\em too\/} critical: being off by 1/4
	of the horizon radius is no problem, and -- assuming the algorithm
	still converges -- even 1/2 of the horizon radius only slows
	the convergence by an extra iteration or two.  But poor values
	of these parameters do make the algorithm more likely to fail
	to converge.
\end{description}

At present the local coordinate origin is fixed once you set it;
there's no provision for it to move to track a moving black hole.
I hope to add such a provision soon.%%%
\footnote{%%%
	 This would be along the lines of the
	 \thorn{AHFinder} apparent horizon finder's
	 \code{AHFinder::ahf\_wander = true}
	 option.
	 }%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Specifying the Initial Guess}

\thorn{AHFinderDirect} requires an initial guess
for the apparent horizon's coordinate position and shape
(that is, for the $h(\text{angle})$ function defined in
section~\ref{AHFinderDirect/sect-what-AHFinderDirect-needs}),
for each apparent horizon you want to find.  Unlike some other apparent
horizon finders (\eg{}~the curvature flow method in \thorn{AHFinder}),
for \thorn{AHFinderDirect} there's no restriction on whether the initial
guess is inside, outside, or crossing the actual apparent horizon: the
only important thing is that it should be ``close''.  Just how close
the initial guess needs to be for \thorn{AHFinderDirect} to find the
(a) apparent horizon depends on the slice and the coordinates, but as
a general rule of thumb any initial guess that's within 1/3 to 1/2 of
the mean horizon radius will probably work.

The ``initial guess'' specification is used the first time we try to
find any given apparent horizon, and also any succeeding time when the
most recent attempt to find this apparent horizon failed.  If we succeed
in finding a given apparent horizon, than that apparent horizon position
is automatically reused as the initial guess the next time we try to
find the same apparent horizon; in this case the explicit ``initial guess''
specification is ignored.%%%
\footnote{%%%
	 This is similar to the \thorn{AHFinder} apparent
	 horizon finder's behavior with the
	 \code{AHFinder::ahf\_guessold = true}
	 option.
	 }%%%

There are a number of parameters for specifying the initial guess:
\begin{description}
\item[\code{initial\_guess\_method[}$n$\code{]}]
\mbox{}\\
	This sets what type of the initial guess is used for each
	apparent horizon position.
	There are several possibilities, most with their own sets
	of subparameters:%%%
\footnote{%%%
	 The naming scheme for these is similar to
	 that used in the \thorn{Exact} thorn.
	 }%%%
$^,$%%%
\footnote{%%%
	 For the Kerr parameters, note that this thorn
	 always uses the convention that the spin parameter
	 $a \equiv J/m^2$ is dimensionless.
	 }%%%
	\begin{description}
	\item[\code{"read from file"}]
	\mbox{}\\
		This reads the initial-guess $h(\text{angle})$ function
		from a data file.  The file format is currently hard-wired
		to be that written with \verb|file_format = "ASCII (gnuplot)"|
		(see below).  The subparameter
		\verb|initial_guess__read_from_named_file__file_name|
		specifies the file name.
	\item[\code{"Kerr/Kerr"}]
	\mbox{}\\
		This sets the initial guess to the analytically-known
		apparent horizon position in a Kerr spacetime
		in Kerr coordinates.  This is a coordinate sphere
		of radius $(1 + \sqrt{1 - a^2}) m$.
		There are subparameters
		\begin{description}
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_Kerr\_Kerr\_\_x\_posn[}$n$\code{]} \\
		     \code{initial\_guess\_\_Kerr\_Kerr\_\_y\_posn[}$n$\code{]} \\
		     \code{initial\_guess\_\_Kerr\_Kerr\_\_z\_posn[}$n$\code{]} %%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			to set the position of the Kerr black hole
			(note this position is in {\em global\/} Cactus
			coordinates, not relative to the local coordinate
			origin), and
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_Kerr\_Kerr\_\_mass[}$n$\code{]} \\
		     \code{initial\_guess\_\_Kerr\_Kerr\_\_spin[}$n$\code{]} %%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			to set its mass and spin.
		\end{description}
	\item[\code{"Kerr/Kerr-Schild"}]
	\mbox{}\\
		This sets the initial guess to the analytically-known
		apparent horizon position in a Kerr spacetime in
		Kerr-Schild coordinates.  This is a coordinate ellipsoid
		with radia (semi-major axes)
		\begin{equation}
		\renewcommand{\arraystretch}{2.0}
		\begin{array}{lclcl}
		r_z	& = &	(1 + \sqrt{1 - a^2}) m			\\
		r_x = r_y
			& = &	r_z \sqrt{1 + \left(\dfrac{am}{r_z}\right)^2}
			& = &	\sqrt{\dfrac{2 r_z}{m}} \, m
									%%%\\
		\end{array}
		\end{equation}
		\begin{description}
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_Kerr\_KerrSchild\_\_x\_posn[}$n$\code{]} \\
		     \code{initial\_guess\_\_Kerr\_KerrSchild\_\_y\_posn[}$n$\code{]} \\
		     \code{initial\_guess\_\_Kerr\_KerrSchild\_\_z\_posn[}$n$\code{]} %%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			(note this position is in {\em global\/} Cactus
			coordinates, not relative to the local coordinate
			origin), and
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_Kerr\_KerrSchild\_\_mass[}$n$\code{]} \\
		     \code{initial\_guess\_\_Kerr\_KerrSchild\_\_spin[}$n$\code{]} %%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			to set its mass and spin.
		\end{description}
	\item[\code{"coordinate sphere"}]
	\mbox{}\\
		This sets the initial guess to a coordinate sphere;
		there are subparameters
		\begin{description}
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_coord\_sphere\_\_x\_center[}$n$\code{]} \\
		     \code{initial\_guess\_\_coord\_sphere\_\_y\_center[}$n$\code{]} \\
		     \code{initial\_guess\_\_coord\_sphere\_\_z\_center[}$n$\code{]} %%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			(note this position is in {\em global\/} Cactus
			coordinates, not relative to the local coordinate
			origin), and
		\item[\code{initial\_guess\_\_coord\_sphere\_\_radius[}$n$\code{]}]
		\mbox{}\\
			to set the radius.
		\end{description}
	\item[\code{"coordinate ellipsoid"}]
	\mbox{}\\
		This sets the initial guess to a coordinate ellipsoid;
		there are subparameters
		\begin{description}
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_coord\_ellipsoid\_\_x\_center[}$n$\code{]} \\
		     \code{initial\_guess\_\_coord\_ellipsoid\_\_y\_center[}$n$\code{]} \\
		     \code{initial\_guess\_\_coord\_ellipsoid\_\_z\_center[}$n$\code{]} %%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			(note this position is in {\em global\/} Cactus
			coordinates, not relative to the local coordinate
			origin), and
		\item[%%%
		     \begin{tabular}{@{}l@{}}
		     \code{initial\_guess\_\_coord\_ellipsoid\_\_x\_radius[}$n$\code{]}	\\
		     \code{initial\_guess\_\_coord\_ellipsoid\_\_y\_radius[}$n$\code{]}	\\
		     \code{initial\_guess\_\_coord\_ellipsoid\_\_z\_radius[}$n$\code{]}	%%%\\
		     \end{tabular}%%%
		     ]
		\mbox{}\\
			to set the radia (semimajor axes).
		\end{description}
	\end{description}
\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{I/O Parameters for the Apparent Horizon Shape(s)}

The main output of this thorn is the computed horizon shape function
$h({\rm angle})$, and correspondingly the $(x,y,z)$ coordinate positions
of the apparent-horizon-surface (angular) grid points.  There are several
parameters controlling if, how often, and how these should be written
to data files:

\begin{description}
\item[\code{output\_h\_every}]
\mbox{}\\
	As described in
	section~\ref{AHFinderDirect/sect-parameters/overall-parameters},
	\thorn{AHFinderDirect} will try to find the apparent horizon(s)
	every \verb|find_every| time steps.  However, you can control
	how often (if at all) the apparent horizon shape(s) are written
	to data files: this is only done if \verb|output_h_every|
	is nonzero, and the Cactus time step number \verb|cctk_iteration|
	is an integral multiple of this parameter (\verb|output_h_every|).

\item[\code{file\_format}]
\mbox{}\\
	This specifies the file format for horizon-shape (and other
	angular-grid-function) data files.  Unfortunately, at the
	moment only a single format is implemented,
	\begin{description}
	\item[\code{"ASCII (gnuplot)"}]
	\mbox{}\\
		This is a simple ASCII format designed for easy
		plotting with gnuplot:
		\begin{itemize}
		\item	\thorn{AHFinderDirect} writes a separate data file
			for each Cactus time step and for each apparent
			horizon found.  By default these are all written
			in to the \verb|IOUtil::out_dir| directory;
			see \verb|h_directory| (below) to change this.
		\item	The time step number and the apparent horizon number
			are both encoded in the file name; the actual
			file name is given by a \verb|printf()| format
			\verb|"%s/%s.t%d.ah%d.%s"|, where
			\begin{itemize}
			\item	the first \verb|%s| is the directory
				set by the \verb|IO::out_dir| and/or
				\verb|h_directory| parameters (see below)
			\item	the second \verb|%s| is the base file name
				set by the \verb|h_base_file_name|
				parameter (see below)
			\item	the first \verb|%d| is the
				Cactus time step number
			\item	the second \verb|%d| is the
				apparent horizon number
			\item	the third \verb|%s| is the
				file name extension, set by the
				\verb|ASCII_gnuplot_file_name_extension|
				parameter; this defaults to \verb|"gp"|
			\end{itemize}
		\item	Comment lines begin with \verb|#|.
		\item	Patches are separated by 2 blank lines;
			rows of apparent-horizon points within a patch
			are separated by single blank lines.
		\item	Each apparent-horizon-surface point
			is described by a single line, containing
			the whitespace-separated fields
			\begin{itemize}
			\item	Two ``unwrapped'' angular coordinates
				in degrees, representing a point on $S^2$.
			\item	The $h$ value, giving the radius
				of the apparent horizon surface
				at that angle.
			\item	The corresponding Cactus $(x,y,z)$
				coordinates.
			\end{itemize}
		\end{itemize}
		See section~\ref{AHFinderDirect/sect-visualization}
		for a discussion of visualization for these and other
		\thorn{AHFinderDirect} output files.
	\end{description}

\item[\code{h\_directory}]
\mbox{}\\
	This specifies the directory in which the $h$ data files
	are to be written.  If it doesn't already exist, this directory
	is created before writing the data files.  This parameter
	defaults to the value of the \verb|IO::out_dir| parameter.

\item[\code{h\_base\_file\_name}]
\mbox{}\\
	This specifies the base file name for $h$ data files, as
	described above.  This defaults to "h".

\item[\code{ASCII\_gnuplot\_file\_name\_extension}]
\mbox{}\\
	This specifies the file name extension for $h$ data files, as
	described above.  This defaults to "gp".
\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Parameters for the ``BH\_diagnostics'' Files}
\label{AHFinderDirect/sect-parameters/BH-diagnostics-parameters}

As well as the apparent horizon shape files, this thorn can also
write files giving time series of various diagnostics.  These are
controlled by the following parameters:

\begin{description}
\item[\code{output\_BH\_diagnostics}]
\mbox{}\\
	If this Boolean parameter is set to true,
	\thorn{AHFinderDirect} will write a ``black hole diagnostics''
	file for each distinct apparent horizon found
	(up to \verb|N_horizons| files in all).  Each such file
	contains a time series of various diagnostics for all time
	steps when the corresponding apparent horizon was found.
	The file format is again a simple ASCII format designed
	for easy plotting with gnuplot:
	\begin{itemize}
	\item	The apparent horizon number is encoded in the file name;
		the actual file name is given by a \verb|printf()| format
		\verb|"%s/%s.ah%d.%s"|, where
		\begin{itemize}
		\item	the first \verb|%s| is the directory
			set by the \verb|IO::out_dir| and/or
			\verb|BH_diagnostics_directory| parameters (see below)
		\item	the second \verb|%s| is the base file name
			set by the \verb|BH_diagnostics_base_file_name|
			parameter (see below); this defaults to
			\verb|"BH_diagnostics"|
		\item	the \verb|%d| is the apparent horizon number
		\item	the third \verb|%s| is the file name extension,
			set by the \verb|BH_diagnostics_file_name_extension|
			parameter; this defaults to \verb|"gp"|
		\end{itemize}
	\item	The file begins with a block of header comments
		(lines begining with \verb|#|) describing the data
		fields.
	\item	Each time this apparent horizon is found, a single
		line is appended to the data file, containing various
		whitespace-separated fields.  The the precise list of
		fields, see the header comments, or see the function
		\verb|output()| in \verb|src/driver/BH_diagnostics.cc|.
		As of this writing the fields are:
		\begin{itemize}
		\item	the Cactus iteration number \verb|cctk_iteration|
		\item	the Cactus time coordinate \verb|cctk_time|
		\item	the Cactus $(x,y,z)$ coordinates of the apparent
			horizon centroid
		\item	the minimum, maximum, and mean coordinate radia
			of the apparent horizon about the local
			coordinate origin
		\item	the minimum and maximum Cactus $x$, $y$, and $z$
			coordinates of the apparent horizon surface
		\item	the proper circumferences of the apparent horizon
			in the $xy$, $xz$, and $yz$ local-coordinate planes%%%
\footnote{%%%
	 Note that if the apparent horizon is not
	 symmetric across one of these coordinate planes,
	 then the circumference in this plane is not
	 very meaningful.  In particular, in this case
	 this circumference is probably different from
	 the apparent horizon's maximum ``hoop'' circumference.
	 }%%%
		\item	the $xz/xy$ and $yz/xy$ ratios of the proper
			circumferences
		\item	the proper area of the apparent horizon, $A$
		\item	the irreducible mass of the apparent horizon,
			$\sqrt{A/16\pi}$
		\item	the areal radius of the apparent horizon,
			$\sqrt{A/4\pi}$
		\end{itemize}
	\end{itemize}

\item[\code{BH\_diagnostics\_directory}]
\mbox{}\\
	This specifies the directory in which the black hole diagnostics
	data files are to be written.  If it doesn't already exist, this
	directory is created before writing the data files.  This parameter
	defaults to the value of the \verb|IO::out_dir| parameter.

\item[\code{BH\_diagnostics\_base\_file\_name}]
\mbox{}\\
	This specifies the base file name for black hole diagnostics
	data files, as described above.  This defaults to
	\verb|"BH_diagnostics"|.

\item[\code{BH\_diagnostics\_file\_name\_extension}]
\mbox{}\\
	This specifies the file name extension for black hole diagnostics
	data files, as described above.  This defaults to \verb|"gp"|.
\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{(Excision) Mask Parameters}
\label{AHFinderDirect/sect-parameters/mask-parameters}

This thorn can optionally set a mask grid function (or functions)
at each point of the Cactus grid, to indicate where that point is
with respect to the apparent horizon(s).  This is usually used for
excision.

\begin{description}
\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{set\_mask\_for\_all\_horizons}			\\
     \code{set\_mask\_for\_individual\_horizon[}$n$\code{]}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	These Boolean parameters control whether \thorn{AHFinderDirect}
	should set a mask grid function(s): If the (C) expression
	\verb:set_mask_for_all_horizons || set_mask_for_individual_horizon[i]:
	is true, then \thorn{AHFinderDirect} will set a mask grid
	function(s) for apparent horizon $i$.
	All these parameters default to false (don't set a mask).

	If any of these parameters is set to true, you almost certainly
	also need to set the Boolean parameter \verb|SpaceMask::use_mask|
	to true to to turn on storage for the mask grid function(s)!
\end{description}

If it's setting a mask(s), \thorn{AHFinderDirect} partitions the
Cactus grid into 3~regions: an \defn{inside}, a \defn{buffer},
and an \defn{outside}.  Typically the inner region is excised, but
\thorn{AHFinderDirect} doesn't itself do this:  It just sets the mask(s);
you need to use some other thorn(s) to do the actual excision.

The 3~regions are defined as follows:  For a grid point a distance $r[i]$
from horizon~$i$'s local coordinate origin, with horizon~$i$'s radius
in this same direction (again, measured from its local coordinate origin)
being $r_{\text horizon}[i]$,%%%
\footnote{%%%
	 Note that these are flat-space distances
	 $[(x-x_0)^2 + (y - y_0)^2 + (z-z_0)^2]^{1/2}$
	 -- the spacetime metric is {\em not\/} used here.
	 }%%%
{} the regions are defined by
\begin{equation}
\renewcommand{\arraystretch}{1.333}
\begin{tabular}{l@{~}lll@{~}lll}
$r[i] \le r{	ext{r}}[i]$	& \text{for some $i$}
	&
	&				&
	& $\Rightarrow$	& \text{inner}					\\
$r[i] >   r_{\text{r}}[i]$	& \text{for all $i$}
	& \text{and}	&
	$r[i] \le r_{\text{r}}[i]$	& \text{for some $i$}
	& $\Rightarrow$	& \text{buffer}					\\
$r[i] >   r_{\text{r}}[i]$	& \text{for all $i$}
	&
	&				&
	& $\Rightarrow$	& \text{outer}				%%%\\
\end{tabular}
\end{equation}
where
\begin{equation}
\renewcommand{\arraystretch}{1.333}
\begin{array}{lcl}
r_{\text{r}}
	& = &	\verb|mask_radius_multiplier| \times r_{\text{n}}
		\,\,+\,\,
		\verb|mask_radius_offset| \times {\Delta x}_{\text{e}}
									\\
r_{\text{r}}
	& = &	r_{\text{r}}
		\,\,+\,\,
		\verb|mask_buffer_thickness| \times {\Delta x}_{\text{e}}
									%%%\\
\end{array}
				\label{AHFinderDirect/eqn-r-inside-outside}
\end{equation}
and where ${\Delta x}_{\text{e}}$ is the base-grid Cactus grid spacing
(more precisely, the geometric mean of the base grid's $x$, $y$,
and $z$ Cactus grid spacings)%%%
\footnote{%%%
	 The ``base grid'' part here only matters if
	 you're doing mesh refinement, in which case
	 it's necessary to keep excision consistent
	 between the different refinement levels.
	 }%%%
.

\begin{description}
\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{mask\_radius\_multiplier}	\\
     \code{mask\_radius\_offset}	\\
     \code{mask\_buffer\_thickness}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	These parameters are used to define the radia $r_{\text{r}}$
	and $r_{\text{r}}$ in
	equation~\eqref{AHFinderDirect/eqn-r-inside-outside} above.

	Note that the sign convention here is that
	\verb|mask_radius_multiplier| is {\em multiplied\/}
	by the horizon radius, then \verb|mask_radius_offset|
	(scaled by the Cactus grid spacing) is {\em added\/}.
	Thus for use with excision (where the inner region --
	which will be excised -- must be somewhat {\em inside\/}
	the horizon), \verb|mask_radius_multiplier| should be a
	positive real number slightly less than~1.0, and/or
	\verb|mask_radius_offset| a negative real number.

	The default values for these parameters are
	\begin{verbatim}
	mask_radius_multiplier =  0.8
	mask_radius_offset     = -5.0
	mask_buffer_thickness  =  5.0
	\end{verbatim}

\item[\code{mask\_is\_noshrink}]
\mbox{}\\
	This Boolean parameter specifies whether the inside and
	buffer regions should be prevented from ever shrinking
	during a time evolution (this is the default), or whether
	they should be set independently from one time step to the
	next (and thus allowed to either grow or shrink).  More
	precisely, once a given grid point has been classified as
	inside, buffer, or outside, \thorn{AHFinderDirect} executes
	the following algorithm:
	\begin{description}
	\item[inside]\mbox{}\\[-\baselineskip]
		\begin{tabbing}
		$\verb|mask| \assign \verb|inside_value|$		  %%%\\
		\end{tabbing}
	\item[buffer]\mbox{}\\[-\baselineskip]
		\begin{tabbing}
		if \=(\verb|mask_is_noshrink|
		      \verb|&&|
		      ($\verb|mask| = \verb|inside_value|$))		\+\\
		   then \=\{						\+\\
			\verb|#|
			this point was previously {\bf inside}
			$\Rightarrow$ no-op here			  \\
			\}						\-\\
		   else \>$\verb|mask| \assign \verb|buffer_value|$	\-%%%\\
		\end{tabbing}
	\item[outside]\mbox{}\\[-\baselineskip]
		\begin{tabbing}
		if \=(\verb|mask_is_noshrink|
		      \verb|&&|
		      (($\verb|mask| = \verb|inside_value|$)
		       \verb.||. ($\verb|mask| = \verb|buffer_value|$))	\+\\
		   then \=\{						\+\\
			\verb|#|
			this point was previously {\bf inside}
			or {\bf buffer} $\Rightarrow$ no-op here	  \\
			\}						\-\\
		   else \>$\verb|mask| \assign \verb|outside_value|$	\-%%%\\
		\end{tabbing}
	\end{description}

\item[\code{min\_horizon\_radius\_points\_for\_mask}]
\mbox{}\\
	By default, \thorn{AHFinderDirect} sets the mask for each
	apparent horizon found.  If we're using mesh refinement, it's
	possible for an apparent horizon to be found on a coarse grid,
	and the masked region to be only a few grid points across on
	a fine grid.  This causes some other Cactus thorns
	(\eg{} \thorn{LegoExcision}) to crash. :(

	This parameter can be used to avoid this problem:
	For each apparent horizon that it finds, \thorn{AHFinderDirect}
	only sets the mask on a given grid if
	\begin{equation}
	r_{\text{inner},\min} \ge \verb|min_horizon_radius_points_for_mask|
		     * {\Delta x}_{\text{current},\max}
	\end{equation}
	where $r_{\text{inner},\min}$ is the minimum over all angles
	of $r_{\text{r}}$ as defined by
	equation~\eqref{AHFinderDirect/eqn-r-inside-outside},
	and ${\Delta x}_{\text{current},\max}$ is the maximum of the
	Cactus $x$, $y$, and~$z$ grid spacings on the current Cactus
	grid.%%%
\footnote{%%%
	 Note that this is the {\bf current\/} Cactus
	 grid spacing, not the {\bf base\/} Cactus grid
	 spacing that's used when calculating
	 $r_{\text{r}}$ and $r_{\text{r}}$ by
	 equation~\eqref{AHFinderDirect/eqn-r-inside-outside}.
	 }%%%
{}	If this condition isn't satisfied, then \thorn{AHFinderDirect}
	skips setting the mask for this apparent horizon, just as if
	this apparent horizon wasn't found.  (Note that all other
	processing for an apparent horizon being found is still done,
	including writing output files, using the apparent horizon shape
	as the initial guess for the next time step's apparent-horizon
	finding, etc.; it's only the mask processing for this horizon
	(at this time step) that's skipped.)
\end{description}

\thorn{AHFinderDirect} supports two types of mask grid functions;
the following two Boolean parameters choose which of them you want
to set; you can set either or even both of these:
\begin{description}
\item[\code{set\_old\_style\_mask}]
\mbox{}\\
	This parameter (default \verb|true|) specifies an
	old-style excision mask, one stored in a \verb|CCTK_REAL|
	Cactus grid function.  (The \thorn{AHFinder} apparent horizon
	finder uses this type of mask.)

\item[\code{set\_new\_style\_mask}]
\mbox{}\\
	This parameter (default \verb|false|) specifies a
	new-style excision mask, one stored in a specified bit field
	of a \verb|CCTK_INT| Cactus grid function.
	The bit field is specified by its name,
	as registered with the \thorn{SpaceMask} thorn.
	We plan to eventually convert all Cactus excision
	(and other uses of mask grid functions) to this scheme,
	but at the moment not much code supports it.
	Note that \thorn{AHFinderDirect} doesn't itself create/register
	any bit fields or state names with \thorn{SpaceMask} --
	you must arrange for some other thorn(s) to do this.
\end{description}

For an old-style mask, the following parameters specify the mask grid
function and how it should be set:
\begin{description}
\item[\code{old\_style\_mask\_gridfn\_name}]
\mbox{}\\
	This parameter specifies the mask grid function's name.

\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{old\_style\_mask\_inside\_value}	\\
     \code{old\_style\_mask\_buffer\_value}	\\
     \code{old\_style\_mask\_outside\_value}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	If an old-style mask is to be set in the corresponding
	regions, these parameters specify the values to which
	it should be set.  These are all \verb|CCTK_REAL| values.
\end{description}

For an new-style mask, the following parameters specify the mask grid
function and how it should be set:
\begin{description}
\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{new\_style\_mask\_gridfn\_name}	\\
     \code{new\_style\_mask\_bitfield\_name}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	These parameters specify the mask grid function's name
	and the bitfield name within it.

\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{new\_style\_mask\_inside\_value}	\\
     \code{new\_style\_mask\_buffer\_value}	\\
     \code{new\_style\_mask\_outside\_value}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	If an new-style mask is to be set in the corresponding
	regions, these parameters specify the values to which
	it should be set.  These are all character-string state
	names, as registered with the \thorn{SpaceMask} thorn.
\end{description}
Note that \thorn{AHFinderDirect} doesn't itself register any bitfields
or states with \thorn{SpaceMask} -- you must arrange for some other thorn(s)
to do this before \thorn{AHFinderDirect} tries to find the horizon(s).

If \thorn{AHFinderDirect} sets a mask or masks, this happens in the
same schedule bin(s) as the horizon finding.  More precisely,
\thorn{AHFinderDirect} creates two schedule groups for this purpose:
\begin{itemize}
\item	The schedule group \verb|group_for_mask_stuff| is
	scheduled to run just after the horizon is found.
\item	The schedule group \verb|group_where_mask_is_set|
	is scheduled inside the schedule group \verb|group_for_mask_stuff|.
\item	The actual setting of the mask is scheduled inside
	the schedule group \verb|group_where_mask_is_set|.
\end{itemize}
Thorn \thorn{PreviousMask} uses these schedule groups to keep a
``previous'' as well as a ``current'' mask.  See that thorn's thorn
guide for further details.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Communicating with Other Thorns}
\label{AHFinderDirect/sect-parameters/communicating-with-other-thorns}

Besides the data files it writes, \thorn{AHFinderDirect} currently
has three ways to communicate with other Cactus thorns:
\begin{itemize}
\item	\thorn{AHFinderDirect} can set a mask grid function(s) based
	on the (a) horizon's shape; other thorns may then use this for
	excision or other purposes.  \thorn{AHFinderDirect} supports
	both the old-style (\verb|CCTK_REAL|) mask
	(compatible with \thorn{AHFinder}) or
	the new-style (\verb|CCTK_INT|) mask bit-fields defined by
	\thorn{SpaceMask}; you can even use both styles simultaneously.
\item	\thorn{AHFinderDirect} can announce a selected horizon's centroid
	position to another thorn (typically \thorn{DriftCorrect}, which
	uses this to adjust its corotating shift vector).
	This uses the new function-aliasing version of \thorn{DriftCorrect},
	not the old version which worked with an auxiliary thorn
	\thorn{AHFSetDCCentroid}.
\item	\thorn{AHFinderDirect} provides a set of aliased functions
	which any other thorn(s) can call to find out the shape of
	a specified horizon.
\item	\thorn{AHFinderDirect} can store information about the
	horizon(s) it finds in the \thorn{SphericalSurface} variables
	for other thorns to use.
\end{itemize}

\thorn{AHFinderDirect}'s mask features are described in
section~\ref{AHFinderDirect/sect-parameters/mask-parameters};
the other communication mechanisms are described in the following
subsections.

%%%%%%%%%%%%%%%%%%%%

\subsubsection{Parameters for Announcing a Horizon Centroid to Other Thorns}

This thorn can optionally announce the centroid of a specified
apparent horizon to another thorn (typically \thorn{DriftCorrect})
each time that apparent horizon is found.  This is controlled by the
following parameter:

\begin{description}
\item[\code{which\_horizon\_to\_announce\_centroid}]
\mbox{}\\
	This is an integer parameter which defaults to
	0 (which means not to announce any centroid).
	If it's set to a nonzero integer, that specifies the
	horizon number to have its centroid announced.
\end{description}

%%%%%%%%%%%%%%%%%%%%

\subsubsection{Aliased Functions to Provide Horizon-Shape Information}

\thorn{AHFinderDirect} provides the following aliased functions
to allow other thorns to find out about the horizons.  Each function
returns a status code which is $\ge 0$~for ok, or negative for an error.
\begin{verbatim}
#
# This function returns the local coordinate origin for a given horizon.
#
CCTK_INT FUNCTION HorizonLocalCoordinateOrigin                          \
   (CCTK_INT IN horizon_number,                                         \
    CCTK_REAL OUT origin_x, CCTK_REAL OUT origin_y, CCTK_REAL OUT origin_z)

#
# The following function queries whether or not the specified horizon
# was found the most recent time AHFinderDirect searched for it.
# The return value is:
#  1 if the horizon was found
#  0 if the horizon was not found
#  negative for an error
#
CCTK_INT FUNCTION HorizonWasFound(CCTK_INT IN horizon_number)

#
# The following function computes the horizon radius in the direction
# of each (x,y,z) point, or -1.0 if this horizon wasn't found the most
# recent time AHFinderDirect searched for it.  More precisely, for each
# (x,y,z), consider the ray from the local coordinate origin through
# (x,y,z).  This function computes the Euclidean distance between the
# local coordinate origin and this ray's intersection with the horizon,
# or -1.0 if this horizon wasn't found the most recent time AHFinderDirect
# searched for it.  
#
# If this function is to be used in a multiprocessor run on a horizon
# which was found on some other processor, the parameter
#   AHFinderDirect::always_broadcast_horizon_shape
# must be set to true to get the correct answer.
#
CCTK_INT FUNCTION HorizonRadiusInDirection                              \
   (CCTK_INT IN horizon_number,                                         \
    CCTK_INT IN N_points,                                               \
    CCTK_REAL IN ARRAY x, CCTK_REAL IN ARRAY y, CCTK_REAL IN ARRAY z,   \
    CCTK_REAL OUT ARRAY radius)
\end{verbatim}

%%%%%%%%%%%%%%%%%%%%

\subsubsection{Storing Horizon-Shape Information in the \thorn{SphericalSurface} Variables}

\thorn{SphericalSurface} (in the \thorn{AEIThorns} arrangement)
defines a set of generic grid arrays which describe ``spherical surfaces''.
\thorn{AHFinderDirect} can optionally store information about the
horizons it finds in the \thorn{SphericalSurface} variables.
This is controlled by the following parameters:
\begin{description}
\item[\code{which\_surface\_to\_store\_info[}$n$\code{]}]
\mbox{}\\
	This parameter should be set to the \thorn{SphericalSurface}
	surface number into which information on a given \thorn{AHFinderDirect}
	horizon should be stored, or to -1 to skip storing the information.
	It defaults to -1 for each horizon.

	{\bf Note that \thorn{SphericalSurface} numbers surfaces
	starting from 0, whereas \thorn{AHFinderDirect} numbers
	horizons starting from 1!}

	At present, if multiple \thorn{AHFinderDirect} horizons
	specify the same \thorn{SphericalSurface} surface, the
	highest-numbered horizon will ``win'', \ie{} it will overwrite
	the data from any lower-numbered horizons.
\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Other Parameters}
\label{AHFinderDirect/sect-parameters/other-parameters}

\begin{description}
\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{run\_at\_CCTK\_ANALYSIS}			\\
     \code{run\_at\_CCTK\_POSTSTEP}			\\
     \code{run\_at\_CCTK\_POSTINITIAL}			\\
     \code{run\_at\_CCTK\_POST\_RECOVER\_VARIABLES}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	These parameters (which default to \verb|true|, \verb|false|,
	\verb|false|, and \verb|true| respectively) control which
	schedule bins \thorn{AHFinderDirect} runs in.  Historically,
	\thorn{AHFinderDirect} ran in CCTK\_ANALYSIS, and that's
	still the default, but these parameters allow you to change this
	so it runs in CCTK\_POSTSTEP and/or CCTK\_POSTINITIAL
	instead.  (You can even run in all three bins if you want!)

	In general we need to run at CCTK\_POST\_RECOVER\_VARIABLES, since
	\begin{itemize}
	\item	parameters may have been steered at recovery, so we may need
		to find a new horizon or horizons, and
	\item	we need to set the mask again to make sure it's correct
		right away (since our next regular horizon-finding may not
		be until some time steps later)
	\end{itemize}
	Therefore the \verb|run_at_CCTK_POST_RECOVER_VARIABLES| parameter
	should probably be left at its default setting of \verb|true|.

\item[%%%
     \begin{tabular}{@{}l@{}}
     \code{geometry\_interpolator\_name}	\\
     \code{geometry\_interpolator\_pars}	%%%\\
     \end{tabular}
     ]
\mbox{}\\
	These parameters control the (3-D) \defn{geometry interpolation}
	of the spacetime geometry ($g_{ij}$ and $K_{ij}$, or their
	\thorn{StaticConformal} equivalents) to the apparent horizon
	position.  The defaults are set to use a quadratic Hermite
	interpolator.  This works fairly well, but because of the interpolator
	molecule size you must use $\verb|driver::ghost_size| \ge 2$.

	If you want to get very high accuracy from \thorn{AHFinderDirect},
	then you should use a cubic Hermite geometry interpolator, by
	setting
\begin{verbatim}
AHFinderDirect::geometry_interpolator_pars = "
  order=3
  boundary_off_centering_tolerance={1.0e-10 1.0e-10 1.0e-10 1.0e-10 1.0e-10 1.0e-10}
  boundary_extrapolation_tolerance={0.0 0.0 0.0 0.0 0.0 0.0}
                                             "
\end{verbatim}
	Assuming perfectly accurate geometry variables in the 3-D Cactus grid,
	this will make \thorn{AHFinderDirect} (very) roughly an order
	of magnitude more accurate.  However, the larger molecule size
	will make it about a factor of 2--3~slower, and will also require
	that you set $\verb|driver::ghost_size| \ge 3$.
	The sample parameter file \verb|par/Kerr-order3.par|
	shows an example of this.

\item[\code{N\_zones\_per\_right\_angle[}$n$\code{]}]
\mbox{}\\
	This parameter sets the angular resolution used to compute
	each patch.  The units are the number of angular grid zones
	per right angle.  The default is 18, \ie{} a 5~degree angular
	resolution.  There's no problem with this parameter varying
	from one horizon to another, but for simplicity it should be
	even.%%%
\footnote{%%%
	 More precisely, it {\em must\/} be even for
	 any horizon whose patch system type is anything
	 other than {\tt "full sphere"}.  See the comments
	 in {\tt param.ccl} for further information.%%%
	 }%%%

{}	For any horizon which is close to spherical about its
	local coordinate origin, you can lower this parameter to make
	\thorn{AHFinderDirect} run faster (typical run-times scale
	roughly as the cube of this parameter); 6~is about the
	minimum reasonable value.

	For any horizon which is highly non-spherical about its
	local coordinate origin, you can raise this parameter to
	get better resolution; 30~should be enough for even a
	highly non-spherical horizon.

\item[\code{max\_allowable\_horizon\_radius[}$n$\code{]}]
\mbox{}\\
	This parameter gives the maximum mean-coordinate-radius
	which any given trial surface may have in the course of
	trying to solve the apparent horizon equation.%%%
\footnote{%%%
	 Note that this is an unweighted arithmetic
	 mean over the horizon-surface grid points,
	 the same value which is printed as \code{r\_grid}
	 during the horizon-finding iterations.
	 }%%%
{}	In particular, if any trial surface has a mean coordinate radius
	which exceeds this parameter, \thorn{AHFinderDirect} gives up
	and deems this apparent horizon to be ``not found''.

	This parameter defaults to $10^{10}$ (effectively $+\infty$)
	for each apparent horizon.  You can set it to a smaller value
	to make \thorn{AHFinderDirect} a bit more efficient, or
	(probably more important in practice) to stop
	\thorn{AHFinderDirect} from iterating off the edge of the grid
	if this causes problems with interpolation or boundary conditions.

\item[\code{max\_allowable\_Theta}]
\mbox{}\\
	This parameter gives the maximum $\|\Theta\|_\infty$
	which any given trial surface may have in the course of
	trying to solve the apparent horizon equation.
	In particular, if any trial surface has $\|\Theta\|_\infty$
	exceeding this parameter, \thorn{AHFinderDirect} gives up
	and deems this apparent horizon to be ``not found''.
	This parameter defaults to $10^{10}$ (effectively $+\infty$)
	for each apparent horizon.

\item[\code{surface\_expansion[}$n$\code{]}]
\mbox{}\\
	This parameter (which defaults to 0.0) sets the expansion
	of each surface.  With the default setting this thorn
	solves~\eqref{AHFinderDirect/eqn-horizon} to find apparent
	horizons.  With other settings of this parameter this thorn
	can be used to find \defn{surfaces of constant expansion};
	these may be useful for excision, wave extraction, or
	other purposes (\cite{AHFinderDirect/Schnetter03a}).

	To help in choosing the value(s) of the
	\verb|surface_expansion[|$n$\verb|]| parameter,
	figure~\ref{AHFinderDirect/fig-Schwarzschild-EF-Theta(r)}
	(from \cite{AHFinderDirect/Thornburg95}),
	shows the expansion of $r = \text{constant}$ surfaces
	in an Eddington-Finkelsteon slice of the unit-mass
	Schwarzschild spacetime.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\begin{figure}[htbp]
\begin{center}
\includegraphics{AEIThorns_AHFinderDirect_Schw_EF_Theta_of_r}
\end{center}
\caption[Expansion $\Theta$ for $r = \text{constant}$ Surfaces
	 in an Eddington-Finkelstein slice
	 of the Unit-Mass Schwarzschild Spacetime]
	{
	This figure shows the expansion $\Theta$ for $r = \text{constant}$
	surfaces in an Eddington-Finkelstein slice of the unit-Mass
	Schwarzschild Spacetime.
	}
\label{AHFinderDirect/fig-Schwarzschild-EF-Theta(r)}
\end{figure}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Monitoring \thorn{AHFinderDirect}'s Status}

There are two primary ways of monitoring what \thorn{AHFinderDirect}
is doing during a Cactus run: the \verb|BH_diagnostics| files and the
\verb|CCTK_INFO| messages written to the Cactus standard output:

The \verb|BH_diagnostics| files are described in detail in
section~\ref{AHFinderDirect/sect-parameters/BH-diagnostics-parameters}.
These files are written and ``flushed'' at each time step, so they're
always up-to-date.

During the apparent-horizon--finding process, \thorn{AHFinderDirect}
writes various \verb|CCTK_INFO| messages describing the convergence
of the iterative solution of the apparent horizon
equation~\ref{AHFinderDirect/eqn-horizon} on each processor.
In particular, if \verb|verbose_level| is set to
\verb|"algorithm highlights"| or a more verbose setting
(\cf{}~section~\ref{AHFinderDirect/sect-parameters/overall-parameters}),
then \thorn{AHFinderDirect} writes \verb|CCTK_INFO| messages like
these:
\begin{verbatim}
INFO (AHFinderDirect):    proc 0/horizon 1:it 1 r_grid=0.595 ||Theta||=1.1e-01
INFO (AHFinderDirect):    proc 0/horizon 1:it 2 r_grid=0.614 ||Theta||=7.2e-02
INFO (AHFinderDirect):    proc 0/horizon 1:it 3 r_grid=0.632 ||Theta||=2.9e-02
INFO (AHFinderDirect):    proc 0/horizon 1:it 4 r_grid=0.642 ||Theta||=9.9e-04
INFO (AHFinderDirect):    proc 0/horizon 1:it 5 r_grid=0.642 ||Theta||=7.9e-07
INFO (AHFinderDirect):    proc 0/horizon 1:it 6 r_grid=0.642 ||Theta||=7.2e-13
INFO (AHFinderDirect): AH 1/2: r=0.660716 at (0.000000,0.000000,1.127434)
INFO (AHFinderDirect): AH 1/2: area=338.0473838 m_irreducible=2.59330658
INFO (AHFinderDirect): writing h to "misner.h.t0.ah1.gp"
\end{verbatim}
Here \verb|r_grid| is a rough estimate of the mean radius of
the trial surface at each iteration, and \verb:||Theta||: is the
infinity-norm of $\Theta$, the left hand side of the apparent
horizon equation~\ref{AHFinderDirect/eqn-horizon} over the surface.
Once the apparent horizon has been found
(\verb:||Theta||: is sufficiently small), then \thorn{AHFinderDirect}
prints its mean radius,%%%
\footnote{%%%
	 Note that this may differ significantly from
	 the last iterations' estimated radius.  This
	 is because the mean radius printed during the
	 iterations is only a rough estimate (it's the
	 unweighted arithmetic mean of the radius at
	 all the horizon-surface grid points), while
	 the radius printed after the horizon is found
	 is a more accurate value (it's computed via
	 numerical integrals over the surface, taking
	 into account the induced metric).
	 }%%%
{} centroid position, area, and irreducible mass.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Visualization}
\label{AHFinderDirect/sect-visualization}

There are several ways to visualize \thorn{AHFinderDirect}'s output:

The simplest is to plot various quantities from the
\verb|BH_diagnostics| files (described in detail in
section~\ref{AHFinderDirect/sect-parameters/BH-diagnostics-parameters}).
For example, using gnuplot (\verb|http://www.gnuplot.info|), you can
plot a graph of the surface area of horizon \#4 as a function of
coordinate time, with the command
\begin{verbatim}
plot 'BH_diagnostics.ah4.gp' using 2:26 with points
\end{verbatim}
ygraph (\verb|http://www.aei.mpg.de/~pollney/ygraph/|) may also be
able to directly plot the \verb|BH_diagnostics| files.

Given a horizon-shape data file \verb|h.t105.h4.gp|, the gnuplot
command
\begin{verbatim}
splot 'h.t105.h4.gp' with lines
\end{verbatim}
will plot the $h(\text{angle})$ function, with the $x$ and $y$~axes
of the plot being the two ``unwrapped'' angular coordinates on $S^2$,
in degrees, and the $z$~axis being $h(\text{angle})$.  However,
in practice this usually isn't very informative.  Instead, you
probably want the gnuplot command
\begin{verbatim}
splot 'h.t105.h4.gp' using 4:5:6 with lines
\end{verbatim}
which will plot the 3-D shape of the apparent horizon surface.

The \verb|src/misc| directory of the \thorn{AHFinderDirect} source
code contains several perl scripts which are useful in visualizing
\thorn{AHFinderDirect} output.  In particular, the script \verb|select.plane|
selects a particular 2-D plane.  If you put this in your Unix path,
it can be used with a gnuplot command like
\begin{verbatim}
splot '<select.plane xy <h.t105.h4.gp' using 4:5 with lines
\end{verbatim}
to show the shape of an apparent horizon in the xy plane.%%%
\footnote{%%%
	 Note that this script isn't very smart: it doesn't
	 know about the \thorn{AHFinderDirect} local coordinate
	 origin.  That is, for example, if you select the $xy$
	 plane, you're selecting the \emph{global} $xy$ plane
	 ($z=0$).
	 }%%%

Another Visualization option is OpenDX (\verb|http://www.opendx.org/|).
Thomas Radke's has written some OpenDX macros
\program{ImportAHFinderDirectGnuplot.net}
and \program{ImportAHFinderDirectGnuplotPatch.net}
to import \thorn{AHFinderDirect} horizon-shape data files.  
These macros use a set of ``control files'' named \verb|*.dx|,
one per horizon, which \thorn{AHFinderDirect} (by default) writes
into the same directory as the main horizon--shape output files.
You can get these macros by anonymous CVS with the command
\begin{verbatim}
cvs -d :pserver:cvs_anon@cvs.aei.mpg.de:/numrelcvs \
     checkout AEIPhysics/Visualization/OpenDX
\end{verbatim}

Another Visualization option is to produce files in the standard
Xdmf~\cite{AHFinderDirect/Xdmf:web} file format which can be visualized for
example using VisIt~\cite{AHFinderDirect/VisIt:web}. Frank L\"offler has
written a python script \program{AH2xdmf.py}, which is included in this
thorn's code repository, which generates such files from
\thorn{AHFinderDirect}'s output from horizon shape files
\verb|h.t%d.ah%d.gp\|. Please see its help text for details.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Accuracy}

The apparent horizon positions are typically computed very accurately;
tests on Kerr spacetimes give typical errors of $10^{-4}m$ to $10^{-5}m$.

The various diagnostics printed to standard output and written to the
black hole diagnostics file(s), are typically computed to accuracies
on the order of a part per million or so.

Note, however, that the irreducible mass $m_{\text{e}}$
may differ considerably from the black hole's local mass or its
contribution to the slice's ADM mass.  For example, for Kerr spacetime
in Kerr-Schild coordinates,
$m_{\text{e}}/m_{\text{M}} = 0.949$, $0.894$, and $0.723$
for spin parameters $a \equiv J/m^2 = 0.6$, $0.8$, and $0.999$, respectively.
It would be better to (also) use the ``isolated horizons'' formalism of
\cite{AHFinderDirect/Dreyer-etal-2002-isolated-horizons};
at some point this thorn may be enhanced to do this.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Examples}
\label{AHFinderDirect/sect-examples}

There are a few example parameter files in the \code{par/} directory,
including Kerr initial data, Misner initial data, and Misner time-evolution
tests.  The \verb|Kerr-tiny.par| parameter file is close to a minimal
\thorn{AHFinderDirect} example:

\begin{verbatim}
# This parameter file sets up Kerr/Kerr-Schild initial data, then
# finds the apparent horizon in it.  The local coordinate system origin
# and the initial guess are both deliberately de-centered with respect
# to the black hole, to make this a non-trivial test for the apparent
# horizon finder.
#
# This parameter file is "tiny" in the sense that it sets only a
# small number of AHFinderDirect parameters.

# flesh
cactus::cctk_itlast = 0

ActiveThorns = "PUGH"
driver::ghost_size = 2
driver::global_nx = 31
driver::global_ny = 31
driver::global_nz = 19

ActiveThorns = "CoordBase CartGrid3D"
grid::domain = "bitant"
grid::avoid_origin = false
grid::type = "byspacing"
grid::dxyz = 0.2

ActiveThorns = "ADMBase ADMCoupling StaticConformal Spacemask CoordGauge Exact"
ADMBase::initial_lapse = "exact"
ADMBase::initial_shift = "exact"
ADMBase::initial_data = "exact"
ADMBase::lapse_evolution_method = "static"
ADMBase::shift_evolution_method = "static"
ADMBase::metric_type = "physical"
Exact::exact_model = "Kerr/Kerr-Schild"
Exact::Kerr_KerrSchild__mass = 1.0
Exact::Kerr_KerrSchild__spin = 0.6

########################################

ActiveThorns = "IOUtil"
IOUtil::parfile_write = "no"

########################################

ActiveThorns = "SphericalSurface"
ActiveThorns = "AEILocalInterp PUGHInterp PUGHReduce AHFinderDirect"
AHFinderDirect::h_base_file_name     = "Kerr-tiny.h"

AHFinderDirect::N_horizons = 1
AHFinderDirect::origin_x[1] = 0.5
AHFinderDirect::origin_y[1] = 0.7
AHFinderDirect::origin_z[1] = 0.0

AHFinderDirect::initial_guess_method[1] = "coordinate sphere"
AHFinderDirect::initial_guess__coord_sphere__x_center[1] = -0.2
AHFinderDirect::initial_guess__coord_sphere__y_center[1] =  0.3
AHFinderDirect::initial_guess__coord_sphere__z_center[1] =  0.0
AHFinderDirect::initial_guess__coord_sphere__radius[1] = 2.0
\end{verbatim}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Surfaces of Constant Expansion}

Surfaces of Constant Expansion (CE surfaces) are introduced in
\cite{AHFinderDirect/Schnetter03a} as a generalisation of
apparent horizons (AH).  On an AH surface, the expansion is zero
everywhere.  On a CE surfaces, the expansion is still everywhere the
same, but it need not be zero.  CE surfaces are also a generalisation
of Constant Mean Curvature surfaces (CMC surfaces); both are identical
when the extrinsic curvature vanishes.  As described in
\cite{AHFinderDirect/Schnetter03a}, it is likely that CE
surfaces foliate the spacelike hypersurface outside of some interior
region.  This interior region is inside the common apparent horizon,
if it exists.

CE surfaces can give some insight into the spacetime, because they can
be used to analyse the part of the spacelike hypersurface ``between
the horizons and infinity''.  Most notably, they can be used to look
at the region where a common horizon is about to (or believed to)
form.  Similarly, one can use them for collapsing stars where an
apparent horizon has not yet formed.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Pretracking}

Apparent horizon pretracking is introduced in
\cite{AHFinderDirect/Schnetter03a}.  This is an application
of CE surfaces.  Even when there is no common horizon, there are still
common CE surfaces surrounding multiple black holes.  Pretracking
consists of tracking in time the smallest common CE surface that can
be found.  It is reasonable to believe that this surface will evolve
into the common horizon at the time where this common horizon begins
to exist.  The expansion of this smallest CE surface is also an
indication of how close the spacelike hypersurface is to having a
common apparent horizon.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{How \thorn{AHFinderDirect} Works}
\label{AHFinderDirect/sect-how-ahfinderdirect-works}

\thorn{AHFinderDirect} uses the apparent horizon
(henceforth \defn{horizon}) finding algorithm of
\cite{AHFinderDirect/Thornburg95},
modified slightly to work with $g_\ij$ and $K_\ij$ on a Cartesian ($xyz$)
grid.  The algorithm is described in detail in
\cite{AHFinderDirect/Thornburg2003:AH-finding}.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{General Description of the Algorithm}

As described above, I parameterizes the horizon shape by
$r = h(\text{angle})$ for some single-value function $h: S^2 \to \Re^+$.
The apparent horizon equation~\eqref{AHFinderDirect/eqn-horizon}
then becomes a 2-D elliptic PDE on $S^2$ for the function $h$.  I finite
difference this in angle to obtain a system of simultaneous nonlinear
algebraic equations for $h$ at the angular grid points, and solve this
system of equations by a global Newton's method (or a variant with
improved convergence).

Computationally, this algorithm has 3 main parts:
\begin{itemize}
\item	Computation of the ``horizon function'' $\Theta(h)$ given a trial
	surface defined by a trial horizon shape function $h$.  This is
	done by interpolating the Cactus geometry fields $g_\ij$ and
	$K_\ij$ (and optionally $\psi$) from the 3-D $xyz$ grid to the
	(2-D set of) trial-horizon-surface grid points (also computing
	$\partial_k g_\ij$ in the interpolation process), then doing
	all further computations with angular grid functions defined
	solely on $S^2$ (\ie{} at the horizon-surface grid points).
\item	Computation of the Jacobian matrix $\Jac[\Theta(h)]$ of $\Theta(h)$.
	This thorn incorporates the \defn{symbolic differentiation} technique
	described in \cite{AHFinderDirect/Thornburg95},
	so this computation is quite fast.  The Jacobian is a highly
	sparse matrix; \thorn{AHFinderDirect} has code to store it
	as either a dense matrix (for debugging purposes), or a sparse
	matrix (the default).  Which option is used is determined by
	a compile-time configuration in \verb|src/include/config.h|.
\item	Solving the nonlinear equations $\Theta(h) = 0$ by a global
	Newton's method or a variant.  How this is done depends on how
	the Jacobian is stored.  At present,
	\begin{itemize}
	\item	If \thorn{AHFinderDirect} is configured to store the
		Jacobian as a dense matrix, then LAPACK is used to solve
		the linear equations.
	\item	If \thorn{AHFinderDirect} is configured to store the
		Jacobian as a sparse matrix, then an
		incomplete-$\sf LU$-decomposition--conjugate-gradient
		solver is used.
	\end{itemize}
	By default only the sparse-matrix code is configured, so LAPACK
	isn't used and there's no need to link with the LAPACK library.
\end{itemize}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{The Multipatch System}
\label{AHFinderDirect/sect-multipatch-system}

Perhaps the most unusual feature of \thorn{AHFinderDirect} is the
``multipatch'' system used to cover $S^2$ without coordinate singularities.
In general there are 6~patches, one each covering a neighborhood of
the $\pm z$, $\pm x$, and $\pm y$ axes, but this may be reduced in
the presence of suitable symmetries.  For example,
figure~\ref{AHFinderDirect/fig-3patch}
on page~\pageref{AHFinderDirect/fig-3patch} shows a system
of 3~patches covering the $+xyz$~octant of $S^2$.  This would be
suitable for finding an apparent horizon with mirror symmetry about
the (local) $z=0$~plane, and either 90~degree periodic rotation symmetry
about the (local) $z$~axis, or mirror symmetry about each of the (local)
$x$~and $y$~axes.

\begin{figure}[htbp]
\begin{center}
\includegraphics{AEIThorns_AHFinderDirect_3patch}
\end{center}
\caption[Illustration of the Multipatch System]
	{
	This figure shows a multipatch system covering the
	$(+,+,+)$~octant of the unit sphere~$S^2$ with 3~patches.
	The angular resolution is 5~degrees.  Notice that the
	patches overlap by several ``ghost zone'' grid points.
	}
\label{AHFinderDirect/fig-3patch}
\end{figure}

To allow easy angular finite differencing within the patch system,
each patch is extended beyond its nominal extent by a ``ghost zone''%%%
\footnote{%%%
	 Note that this terminology differs somewhat
	 from that used by Cactus in general; Cactus
	 would call these ``patch zones'' or ``symmetry
	 zones''.
	 }%%%
{} (2~grid points wide in figure~\ref{AHFinderDirect/fig-3patch}).
Angular grid function values in the ghost zone can be obtained by
interpatch interpolation%%%
\footnote{%%%
	 Due to the way the patch coordinates are defined,
	 adjacent patches always share a common ``perpendicular''
	 angular coordinate, so only 1-D interpolation
	 is needed here.
	 }%%%
{} or by applying symmetry operations.  Once this is done, then angular
finite differencing within the nominal extent of each patch can proceed
normally, ignoring the patch boundaries.  \thorn{AHFinderDirect} can
be configured at compile time to use either 2nd~order or 4th~order
angular finite differencing (3~point or 5~point angular molecules);
the default is 4th~order (5~point).  This is configured at compile time
in \verb|src/include/config.h|.

By default \thorn{AHFinderDirect} will automagically choose a patch
system type for each apparent horizon searched for, based on the local
coordinate origin and the symmetries implicit in the Cactus grid type.
This generally works well, but if desired you can instead manually
specify the patch system type, the angular resolution, the width of
the ghost zones, etc.  See the \verb|param.ccl| file for details.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\subsection{Other Software Used}

\thorn{AHFinderDirect}'s \verb|src/sparse-matrix/| directory contains
various sparse-matrix libraries, which have their own copyrights and
licensing terms:

The \verb|src/sparse-matrix/umfpack/| directory contains a subset of the
files in UMFPACK version 4.0 (11.Apr.2002).  This code is
copyright~(\copyright)~2002 by Timothy A. Davis, and is subect to the
UMFPACK License:

\begin{verbatim}
Your use or distribution of UMFPACK or any modified version of
UMFPACK implies that you agree to this License.

THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.

Permission is hereby granted to use or copy this program, provided
that the Copyright, this License, and the Availability of the original
version is retained on all copies.  User documentation of any code that
uses UMFPACK or any modified version of UMFPACK code must cite the
Copyright, this License, the Availability note, and "Used by permission."
Permission to modify the code and to distribute modified code is granted,
provided the Copyright, this License, and the Availability note are
retained, and a notice that the code was modified is included.  This
software was developed with support from the National Science Foundation,
and is provided to you free of charge.
\end{verbatim}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Other Related Thorns}

If you're interested in \thorn{AHFinderDirect}, you might also be
interested in some other related thorns:

\begin{description}
\item[\thorn{EHFinder}] (in the \arrangement{AEIDevelopment} arrangement)
	was written by Peter Diener, and finds the {\em event\/} horizon(s)
	in a numerically computed spacetime.  It's described in detail in
	the paper~\cite{AHFinderDirect/Diener03a}.
\item[\thorn{AHFinder}] (in the \arrangement{CactusEinstein} arrangement)
	was written by Miguel Alcubierre, and includes two different
	algorithms for finding apparent horizons, a minimization method
	and a ``fast flow'' method based on \cite{AHFinderDirect/Gundlach97a}.
	Unfortunately, both methods are very slow in practice.
\item[\thorn{TGRapparentHorizon2D}] (in the \arrangement{TAT} arrangement)
	was written by Erik Schnetter, and is another apparent horizon
	finder.  It uses methods very similar to this thorn, and (like
	this thorn) is very fast and accurate.  However, it's no longer
	under active development.  It's described in detail in the
	papers~\cite{AHFinderDirect/Schnetter02a}
	and~\cite{AHFinderDirect/Schnetter03a}.
\item[\thorn{AHFinderDirect} (\cvsplace{Erik} branch)]
      (in the \arrangement{AEIThorns} arrangement)\\
	Erik Schnetter has added a number of new features to
	\thorn{AHFinderDirect} on a CVS branch with the tag \cvsplace{Erik},
	including horizon pretracking (to locate places where horizons
	are about to form), and the ability to find constant-expansion
	and constant-mean-curvature surfaces specified by their areal radius.
	We hope to integrate these into the main \thorn{AHFinderDirect}
	branch during the summer of 2004.
\end{description}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{Acknowledgments}

I thank Peter Diener, Ian Hawke, and Erik Schnetter for many valuable
conversations.  I think Thomas Radke for his work on the new interpolators.
I thank the whole Cactus crew for a great infrastructure!

Erik Schnetter originally implemented a number of improvements to
this thorn, notably the \thorn{SphericalSurface} interface and the
new features in the \cvsplace{Erik} branch.

I thank the Alexander von Humboldt foundation and the AEI visitors'
and postdoctoral fellowships programs for financial support.

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% make LaTeX read in AHFinderDirect.bbl produced by bibtex
\bibliography{AHFinderDirect}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

% Do not delete next line
% END CACTUS THORNGUIDE

\end{document}

```



Reference solution:
src_code:       SUBROUTINE SILUCG(N,
     $                  IA,JA,A,
     $                  B,X,
     $                  ITEMP,RTEMP,
     $                  EPS,ITER,
     $                  ISTATUS,IERROR)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(*),X(*),ITEMP(*),RTEMP(*)
C
C     INCOMPLETE LU DECOMPOSITION-CONJUGATE GRADIENT
C     -          --               -         -
C WHERE:
C     |N| IS THE NUMBER OF EQUATIONS.  IF N < 0, ITEMP AND
C       RTEMP CONTAIN THE ILU FROM A PREVIOUS CALL AND
C       B AND X ARE THE NEW RHS AND INITIAL GUESS.
C     IA IS AN INTEGER ARRAY DIMENSIONED |N|+1.  IA(I) IS THE
C       INDEX INTO ARRAYS JA AND A OF THE FIRST NON-ZERO
C       ELEMENT IN ROW I.  LET MAX=IA(|N|+1)-IA(1).
C     JA IS AN INTEGER ARRAY DIMENSIONED MAX.  JA(K) GIVES
C       THE COLUMN NUMBER OF A(K).
C     A IS A REAL ARRAY DIMENSIONED MAX.  IT CONTAINS THE
C       NONZERO ELEMENTS OF THE MATRIX STORED BY ROW.
C     B CONTAINS THE RHS VECTOR.
C     X IS A REAL ARRAY DIMENSIONED |N|.  ON ENTRY, IT CONTAINS
C       AN INITIAL ESTIMATE; ON EXIT, THE SOLUTION.
C     ITEMP IS AN INTEGER SCRATCH ARRAY DIMENSIONED 3*(|N|+MAX)+2.
C     RTEMP IS AN REAL SCRATCH ARRAY DIMENSIONED 4*|N|+MAX.
C     EPS IS THE CONVERGENCE CRITERIA.  IT SPECIFIES THE RELATIVE
C       ERROR ALLOWED IN THE SOLUTION.  TO BE PRECISE, CONVERGENCE
C       IS DEEMED TO HAVE OCCURED WHEN THE INFINITY-NORM OF THE
C       CHANGE IN THE SOLUTION IN ONE ITERATION IS .LE. EPS * THE
C       INFINITY-NORM OF THE CURRENT SOLUTION.  HOWEVER, IF EPS
C       .LT. 0.0, IT IS INTERNALLY SCALED BY THE MACHINE PRECISION,
C       SO THAT, FOR EXAMPLE, EPS = -256.0 WILL ALLOW THE LAST 8 BITS
C       OF THE SOLUTION TO BE IN ERROR.       
C     MAXITER GIVES THE REQUESTED NUMBER OF ITERATIONS,
C       OR IS 0 FOR "NO LIMIT".
C     ISTATUS IS AN INTEGER VARIABLE, WHICH IS SET TO:
C       -I IF THERE IS AN ERROR IN THE MATRIX STRUCTURE IN ROW I
C          (SUCH AS A ZERO ELEMENT ON THE DIAGONAL).
C        0 IF THE ITERATION FAILED TO REACH THE CONVERGENCE CRITERION
C          IN ITER ITERATIONS.
C       +I IF THE ITERATION CONVERGED IN I ITERATIONS.
C
C MODIFICATIONS
C * RETURN LOGICAL VALUE, J. THORNBURG, 13/MAY/85.
C * ADD CONVERGENCE CRITERIA, J. THORNBURG, 17/MAY/85.
C * CHANGED CONVERGENCE CRITERIA, J. THORNBURG, 4/AUG/89.
C * AVOID SKIP RETURNS, J. THORNBURG, 10/SEP/89.
C * CHANGE FUNCTION BACK TO SUBROUTINE, J. THORNBURG, 3.JUN.2003
C * CHANGE CONVERGENCE CRITERIA TO AVOID SPURIOUSLY STOPPING TOO SOON
C   ON SMALL RHS VECTORS -- J. THORNBURG, 11.JUN.2003
C
C REFERENCE:
C     D.S.KERSHAW,"THE INCOMPLETE CHOLESKY-CONJUGATE GRADIENT
C       METHOD FOR INTERATIVE SOLUTION OF LINEAR EQUATIONS",
C       J.COMPUT.PHYS. JAN 1978 PP 43-65
C
      LOGICAL SLU0
      NP=IABS(N)
      ISTATUS=0
      IF (NP.EQ.0) GO TO 20
C CALCULATE INDICES FOR BREAKING UP TEMPORARY ARRAYS.
      N1=NP+1
      MAX=IA(N1)-IA(1)
      ILU=1
      JLU=ILU+N1
      ID=JLU+MAX
      IC=ID+NP
      JC=IC+N1
      JCI=JC+MAX
      IR=1
      IP=IR+NP
      IS1=IP+NP
      IS2=IS1+NP
      IALU=IS2+NP
      IF (N.LT.0) GO TO 10
C DO INCOMPLETE LU DECOMPOSITION
      IF (SLU0(NP,IA,JA,A,ITEMP(IC),ITEMP(JC),ITEMP(JCI),RTEMP(IALU),
     *    ITEMP(ILU),ITEMP(JLU),ITEMP(ID),RTEMP(IR),IERROR)) GOTO 20
C AND DO CONJUGATE GRADIENT ITERATIONS
C CALL MODIFIED TO ADD ADJUSTABLE CONVERGENCE CRITERIA EPS
C - J. THORNBURG, 17/MAY/85.
10    CALL SNCG0(NP,IA,JA,A,B,X,ITEMP(ILU),ITEMP(JLU),ITEMP(ID),
     *  RTEMP(IALU),RTEMP(IR),RTEMP(IP),RTEMP(IS1),RTEMP(IS2),
     *  EPS,MAXITER,ITER)
C ITER IS ACTUAL NUMBER OF ITERATIONS (NEGATIVE IF NO CONVERGENCE)
      ISTATUS = ITER
      IF (ITER .LT. 0) ISTATUS = 0
      RETURN
C ERROR RETURN FROM INCOMPLETE LU DECOMPOSITION
20    ISTATUS = -IERROR
      RETURN
      END

      LOGICAL FUNCTION SLU0(N,IA,JA,A,IC,JC,JCI,ALU,ILU,JLU,ID,V,IE)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),IC(*),JC(*),JCI(*),
     *  ALU(*),ILU(*),JLU(*),ID(N),V(N)
      LOGICAL NODIAG
      COMMON /ICBD00/ ICBAD
C     INCOMPLETE LU DECOMPOSITION
C WHERE:
C     N,IA,JA, AND A ARE DESCRIBED IN FUNCTION SILUCG
C     IC IS AN INTEGER ARRAY DIMENSIONED N+1, IC(J) GIVES THE
C       INDEX OF THE FIRST NONZERO ELEMENT IN COLMN J IN
C       ARRAY JC.
C     JC IS AN INTEGER ARRAY WITH THE SAME DIMENSION AS A.
C       JC(K) GIVES THE ROW NUMBER OF THE K'TH ELEMENT IN
C       THE COLUMN STRUCTURE.
C     JCI IS AN INTEGER ARRAY WITH THE SAME DIMENSION AS A.
C       JCI(K) GIVES THE INDEX INTO ARRAY A OF THE K'TH ELEMENT
C       OF THE COLUMN STRUCTURE.
C     ALU HAS THE SAME DIMENSION AS A.  ON EXIT, IT WILL
C       CONTAIN THE INCOMPLETE LU DECOMPOSITION OF A WITH THE
C       RECIPROCALS OF THE DIAGONAL ELEMENTS OF U.
C     ILU AND JLU CORRESPONDS TO IA AND JA BUT FOR ALU.
C     ID IS AN INTEGER ARRAY DIMENSIONED N.  IT CONTAINS
C       INDICES TO THE DIAGONAL ELEMENTS OF U.
C     V IS A REAL SCRATCH VECTOR OF LENGTH N.
C     IE GIVES THE ROW NUMBER IN ERROR IF AN ERROR OCCURED
C       (RETURN VALUE .TRUE.), OR IS UNUSED IF ALL IS WELL
C       (RETURN VALUE .FALSE.).
C
C     RETURN VALUE = .FALSE. IF ALL IS WELL, .TRUE. IF ERROR.
C
C NOTE: SLU0 SETS ARGUMENTS IC THROUGH V.
C
      ICBAD=0
C ZERO COUNT OF ZERO DIAGONAL ELEMENTS IN U.
C
C FIRST CHECK STRUCTURE OF A AND BUILD COLUMN STRUCTURE
      DO 10 I=1,N
        IC(I)=0
10    CONTINUE
      DO 30 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        NODIAG=.TRUE.
        DO 20 K=KS,KE
          J=JA(K)
          IF (J.LT.1.OR.J.GT.N) GO TO 210
          IC(J)=IC(J)+1
          IF (J.EQ.I) NODIAG=.FALSE.
20      CONTINUE
        IF (NODIAG) GO TO 210
30    CONTINUE
C MAKE IC INTO INDICES
      KOLD=IC(1)
      IC(1)=1
      DO 40 I=1,N
        KNEW=IC(I+1)
        IF (KOLD.EQ.0) GO TO 210
        IC(I+1)=IC(I)+KOLD
        KOLD=KNEW
40    CONTINUE
C SET JC AND JCI FOR COLUMN STRUCTURE
      DO 60 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        DO 50 K=KS,KE
          J=JA(K)
          L=IC(J)
          IC(J)=L+1
          JC(L)=I
          JCI(L)=K
50      CONTINUE
60    CONTINUE
C FIX UP IC
      KOLD=IC(1)
      IC(1)=1
      DO 70 I=1,N
        KNEW=IC(I+1)
        IC(I+1)=KOLD
        KOLD=KNEW
70    CONTINUE
C FIND SORTED ROW STRUCTURE FROM SORTED COLUMN STRUCTURE
      NP=N+1
      DO 80 I=1,NP
        ILU(I)=IA(I)
80    CONTINUE
C MOVE ELEMENTS, SET JLU AND ID
      DO 100 J=1,N
        KS=IC(J)
        KE=IC(J+1)-1
        DO 90 K=KS,KE
          I=JC(K)
          L=ILU(I)
          ILU(I)=L+1
          JLU(L)=J
          KK=JCI(K)
          ALU(L)=A(KK)
          IF (I.EQ.J) ID(J)=L
90      CONTINUE
100   CONTINUE
C RESET ILU (COULD JUST USE IA)
      DO 110 I=1,NP
        ILU(I)=IA(I)
110   CONTINUE
C FINISHED WITH SORTED COLUMN AND ROW STRUCTURE
C
C DO LU DECOMPOSITION USING GAUSSIAN ELIMINATION
      DO 120 I=1,N
        V(I)=0.0
120   CONTINUE
      DO 200 IROW=1,N
        I=ID(IROW)
        PIVOT=ALU(I)
        IF (PIVOT.NE.0.0) GO TO 140
C THIS CASE MAKES THE ILU LESS ACCURATE
        ICBAD=ICBAD+1
        KS=ILU(IROW)
        KE=ILU(IROW+1)-1
        DO 130 K=KS,KE
          PIVOT=PIVOT+ABS(ALU(K))
130     CONTINUE
        IF (PIVOT.EQ.0.0) GO TO 220
140     PIVOT=1.0/PIVOT
        ALU(I)=PIVOT
        KKS=I+1
        KKE=ILU(IROW+1)-1
        IF (KKS.GT.KKE) GO TO 160
        DO 150 K=KKS,KKE
          J=JLU(K)
          V(J)=ALU(K)
150     CONTINUE
C FIX L IN COLUMN IROW AND DO PARTIAL LU IN SUBMATRIX
160     KS=IC(IROW)
        KE=IC(IROW+1)-1
        DO 190 K=KS,KE
          I=JC(K)
          IF (I.LE.IROW) GO TO 190
          LS=ILU(I)
          LE=ILU(I+1)-1
          DO 180 L=LS,LE
            J=JLU(L)
            IF (J.LT.IROW) GO TO 180
            IF (J.GT.IROW) GO TO 170
            AMULT=ALU(L)*PIVOT
            ALU(L)=AMULT
            IF (AMULT.EQ.0.0) GO TO 190
            GO TO 180
170         IF (V(J).EQ.0.0) GO TO 180
            ALU(L)=ALU(L)-AMULT*V(J)
180       CONTINUE
190     CONTINUE
C RESET V
        IF (KKS.GT.KKE) GO TO 200
        DO 195 K=KKS,KKE
          J=JLU(K)
          V(J)=0.0
195     CONTINUE
200   CONTINUE
C NORMAL RETURN
      SLU0 = .FALSE.
      RETURN
C ERROR RETURNS
210   IE=I
      SLU0 = .TRUE.
      RETURN
C NORMAL RETURN
220   IE=IROW
      SLU0 = .TRUE.
      RETURN
      END

      SUBROUTINE SNCG0(N,IA,JA,A,B,X,ILU,JLU,ID,ALU,R,P,S1,S2,
     *  EPS,ITER,IE)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(N),X(N),ILU(*),JLU(*),ALU(*),ID(N),
     *  R(N),P(N),S1(N),S2(N)
C     NONSYMMETRIC CONJUGATE GRADIENT
C WHERE:
C     N,IA,JA,A,B, AND X ARE DESCRIBED IN SUBROUTINE SILUCG.
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW OF LU.
C     JLU GIVES COLUMN NUMBER.
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U.
C     ALU HAS NONZERO ELEMENTS OF LU MATRIX STORED BY ROW
C       WITH RECIPROCALS OF DIAGONAL ELEMENTS OF U.
C     R,P,S1, AND S2 ARE VECTORS OF LENGTH N USED IN THE
C       ITERATIONS.
C FOLLOWING PARAMETER ADDED BY J. THORNBURG, 17/MAY/85.
C     EPS IS CONVERGENCE CRITERIA.  (DESCRIBED IN SUBROUTINE
C       SILUCG).
C     ITER IS MAX NUMBER OF ITERATIONS, OR 0 FOR "NO LIMIT".
C     IE GIVES ACTUAL NUMBER OF ITERATIONS, NEGATIVE IF
C       NO CONVERGENCE.
C
C R0=B-A*X0
      CALL SMUL10(N,IA,JA,A,X,R)
      DO 10 I=1,N
        R(I)=B(I)-R(I)
10    CONTINUE
C P0=(UT*U)(-1)*AT*(L*LT)(-1)*R0
C FIRST SOLVE L*LT*S1=R0
      CALL SSUBL0(N,ILU,JLU,ID,ALU,R,S1)
C TIMES TRANSPOSE OF A
      CALL SMUL20(N,IA,JA,A,S1,S2)
C THEN SOLVE UT*U*P=S2
      CALL SSUBU0(N,ILU,JLU,ID,ALU,S2,P)
      IE=0
      RDOT = SGVV(R,S1,N)
C LOOP BEGINS HERE
20    CALL SMUL30(N,ILU,JLU,ID,ALU,P,S2)
      PDOT = SGVV(P,S2,N)
C
      IF (PDOT.EQ.0.0) RETURN
C
      ALPHA=RDOT/PDOT
C EQUATION 9PA                      ALPHA=(R,LINV*R)/(P,UT*U*P)
C 1 + ||X CHANGE|| CHANGED TO ||X CHANGE|| -- J. THORNBURG, 11.JUN.2003
      XMAX=0.0
      XDIF=0.0
      DO 30 I=1,N
        AP=ALPHA*P(I)
        X(I)=X(I)+AP
C EQUATION 9PB                      X=X+ALPHA*P
        AP=ABS(AP)
        XX=ABS(X(I))
        IF (AP.GT.XDIF) XDIF=AP
        IF (XX.GT.XMAX) XMAX=XX
30    CONTINUE
      IE=IE+1
C
C CONVERGENCE TEST (CHANGED BY J. THORNBURG, 17/MAY/85, 4/AUG/89)
C
      IF ((EPS .GT. 0.0) .AND. (XDIF .LE. EPS * XMAX)) RETURN
      IF ((EPS .LT. 0.0) .AND. (XMAX + XDIF/ABS(EPS) .EQ. XMAX))
     *   RETURN
C
C EXCEEDED ITERATION LIMIT?
C
      IF ((ITER .NE. 0) .AND. (IE .GE. ITER)) GO TO 60
      CALL SMUL10(N,IA,JA,A,P,S2)
      DO 40 I=1,N
        R(I)=R(I)-ALPHA*S2(I)
C EQUATION 9PC                      R=R-ALPHA*A*P
40    CONTINUE
      CALL SSUBL0(N,ILU,JLU,ID,ALU,R,S1)
C     CALL INPROD(R,S1,EDOT,RRDOT,N)
      RRDOT = SGVV(R,S1,N)
      BETA=RRDOT/RDOT
C EQUATION 9PD                      BETA=(R+,LINV*R+)/(R,LINV*R)
      RDOT=RRDOT
      CALL SMUL20(N,IA,JA,A,S1,S2)
      CALL SSUBU0(N,ILU,JLU,ID,ALU,S2,S1)
      DO 50 I=1,N
        P(I)=S1(I)+BETA*P(I)
C EQUATION 9PE                      P=(UT*U)(-1)*AT*(L*LT)(-1)*R+BETA*P
50    CONTINUE
      GO TO 20
60    IE=-IE
      RETURN
      END

      SUBROUTINE SMUL10(N,IA,JA,A,B,X)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(N),X(N)
C MULTIPLY A TIMES B TO GET X
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     IA GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW
C     JA GIVES COLUMN NUMBER
C     A CONTAINS THE NONZERO ELEMENTS OF THE NONSYMMETRIC
C       MATRIX STORED BY ROW
C     B IS THE VECTOR
C     X IS THE PRODUCT (MUST BE DIFFERENT FROM B)
C
      DO 20 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        SUM=0.0
        DO 10 K=KS,KE
          J=JA(K)
          SUM=SUM+A(K)*B(J)
10      CONTINUE
        X(I)=SUM
20    CONTINUE
      RETURN
      END

      SUBROUTINE SMUL20(N,IA,JA,A,B,X)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION IA(*),JA(*),A(*),B(N),X(N)
C MULTIPLY TRANSPOSE OF A TIMES B TO GET X
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     IA GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW
C     JA GIVES COLUMN NUMBER
C     A CONTAINS THE NONZERO ELEMENTS OF THE NONSYMMETRIC
C       MATRIX STORED BY ROW
C     B IS THE VECTOR
C     X IS THE PRODUCT (MUST BE DIFFERENT FROM B)
C
      DO 10 I=1,N
        X(I)=0.0
10    CONTINUE
      DO 30 I=1,N
        KS=IA(I)
        KE=IA(I+1)-1
        BB=B(I)
        DO 20 K=KS,KE
          J=JA(K)
          X(J)=X(J)+A(K)*BB
20      CONTINUE
30    CONTINUE
      RETURN
      END

      SUBROUTINE SMUL30(N,ILU,JLU,ID,ALU,B,X)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION ILU(*),JLU(*),ID(*),ALU(*),B(N),X(N)
C MULTIPLY TRANSPOSE OF U TIMES U TIMES B TO GET X
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW OF LU
C     JLU GIVES COLUMN NUMBER
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U
C     ALU HAS NONZERO ELEMENTS OF LU MATRIX STORED BY ROW
C       WITH RECIPROCALS OF DIAGONAL ELEMENTS
C     B IS THE VECTOR
C     X IS THE PRODUCT UT*U*B (X MUST BE DIFFERENT FROM B)
C
      DO 10 I=1,N
        X(I)=0.0
10    CONTINUE
      DO 50 I=1,N
        KS=ID(I)+1
        KE=ILU(I+1)-1
        DIAG=1.0/ALU(KS-1)
        XX=DIAG*B(I)
        IF (KS.GT.KE) GO TO 30
        DO 20 K=KS,KE
          J=JLU(K)
          XX=XX+ALU(K)*B(J)
20      CONTINUE
30      X(I)=X(I)+DIAG*XX
        IF (KS.GT.KE) GO TO 50
        DO 40 K=KS,KE
          J=JLU(K)
          X(J)=X(J)+ALU(K)*XX
40      CONTINUE
50    CONTINUE
      RETURN
      END

      SUBROUTINE SSUBU0(N,ILU,JLU,ID,ALU,B,X)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION ILU(*),JLU(*),ID(*),ALU(*),B(N),X(N)
C DO FORWARD AND BACK SUBSTITUTION TO SOLVE UT*U*X=B
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW OF LU
C     JLU GIVES COLUMN NUMBER
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U
C     ALU HAS NONZERO ELMENTS OF LU MATRIX STORED BY ROW
C       WITH RECIPROCALS OF DIAGONAL ELEMENTS OF U
C     B IS THE RHS VECTOR
C     X IS THE SOLUTION VECTOR
C
      NP=N+1
      DO 10 I=1,N
        X(I)=B(I)
10    CONTINUE
C FORWARD SUBSTITUTION
      DO 30 I=1,N
        KS=ID(I)+1
        KE=ILU(I+1)-1
        XX=X(I)*ALU(KS-1)
        X(I)=XX
        IF (KS.GT.KE) GO TO 30
        DO 20 K=KS,KE
          J=JLU(K)
          X(J)=X(J)-ALU(K)*XX
20      CONTINUE
30    CONTINUE
C BACK SUBSTITUTION
      DO 60 II=1,N
        I=NP-II
        KS=ID(I)+1
        KE=ILU(I+1)-1
        SUM=0.0
        IF (KS.GT.KE) GO TO 50
        DO 40 K=KS,KE
          J=JLU(K)
          SUM=SUM+ALU(K)*X(J)
40      CONTINUE
50      X(I)=(X(I)-SUM)*ALU(KS-1)
60    CONTINUE
      RETURN
      END

      SUBROUTINE SSUBL0(N,ILU,JLU,ID,ALU,B,X)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION ILU(*),JLU(*),ID(*),ALU(*),B(N),X(N)
C DO FORWARD AND BACK SUBSTITUTION TO SOLVE L*LT*X=B
C WHERE:
C     N IS THE ORDER OF THE MATRIX
C     ILU GIVES INDEX OF FIRST NONZERO ELEMENT IN ROW LU
C     JLU GIVES THE COLUMN NUMBER
C     ID GIVES INDEX OF DIAGONAL ELEMENT OF U
C     ALU HAS NONZERO ELEMENTS OF LU MATRIX STORED BY ROW
C       DIAGONAL ELEMENTS OF L ARE 1.0 AND NOT STORED
C     B IS THE RHS VECTOR
C     X IS THE SOLUTION VECTOR
C
      NP=N+1
      DO 10 I=1,N
        X(I)=B(I)
10    CONTINUE
C FORWARD SUBSTITUTION
      DO 30 I=1,N
        KS=ILU(I)
        KE=ID(I)-1
        IF (KS.GT.KE) GO TO 30
        SUM=0.0
        DO 20 K=KS,KE
          J=JLU(K)
          SUM=SUM+ALU(K)*X(J)
20      CONTINUE
        X(I)=X(I)-SUM
30    CONTINUE
C BACK SUBSTITUTION
      DO 50 II=1,N
        I=NP-II
        KS=ILU(I)
        KE=ID(I)-1
        IF (KS.GT.KE) GO TO 50
        XX=X(I)
        IF (XX.EQ.0.0) GO TO 50
        DO 40 K=KS,KE
          J=JLU(K)
          X(J)=X(J)-ALU(K)*XX
40      CONTINUE
50    CONTINUE
      RETURN
      END

      REAL FUNCTION SGVV(V,W,N)
      IMPLICIT REAL (A-H,O-Z)
      DIMENSION V(N),W(N)
C     SUBROUTINE TO COMPUTE REAL VECTOR DOT PRODUCT.
C
      SUM = 0.0
            DO 10 I = 1,N
            SUM = SUM + V(I)*W(I)
10          CONTINUE
      SGVV = SUM
      RETURN
      END