#ifndef TGRID_H
#define TGRID_H

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
#endif /* TGRID_H  */