#include "MaterialProperties.h"
#include <cc_CommonUtilities.h>
#include <string>
#include <stdlib.h>
#include <locale>
#include <iostream>
#include <ostream>
#include <sstream>

#include <boost\filesystem.hpp>
#include <boost\property_tree\ptree.hpp>
#include <boost\property_tree\json_parser.hpp>
#include <boost\foreach.hpp>
#include <boost\algorithm\string.hpp>
#include <sstream>

#include <fstream>
#include <iterator>
#include <boost/regex.hpp>
#include <stdlib.h>
#include "cc_LoggerBoost.h"

namespace isis
{

	const double PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR = 1.0 / 1000000.0;
	const double CONVERSION_FACTOR_ONE = 1.0;
	const double M_CUBED_TO_MM_CUBED = 1000.0 * 1000.0 * 1000.0;
	const double M_CUBED_TO_MM_CUBED_RECIPROCAL = 1.0 / M_CUBED_TO_MM_CUBED;
	const double M_TO_MM_RECIPROCAL = 1.0  / 1000.0;
/*
	std::string ConvertToUpperCase(const std::string &in_String)
	{
		std::string temp_string(in_String);
		std::locale loc;
		for (std::string::iterator p = temp_string.begin(); temp_string.end() != p; ++p)    *p = toupper(*p, loc);
		return temp_string;
	}

*/

void stream_AnalysisMaterialProperties( const AnalysisMaterialProperties &in_CADAnalyses, std::ostream &out_Stream )
{
	out_Stream << std::endl << "Material Name: " << in_CADAnalyses.materialName;
	out_Stream << std::endl << "Material Type: " << isis::MaterialType_string(in_CADAnalyses.materialType);

	out_Stream << std::endl << "   ModulusOfElasticity";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.modulusOfElasticityDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.modulusOfElasticity;

	out_Stream << std::endl << "   PoissonsRatio";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.poissonsRatioDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.poissonsRatio;

	out_Stream << std::endl << "   TensileYieldStrength";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.tensileYieldStrengthDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.tensileYieldStrength;

	out_Stream << std::endl << "   TensileUltimateStrength";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.tensileUltimateStrengthDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.tensileUltimateStrength;

	out_Stream << std::endl << "   ShearStrength";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.shearStrengthDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.shearStrength;

	out_Stream << std::endl << "   BearingYieldStrength";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.bearingYieldStrengthDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.bearingYieldStrength;

	out_Stream << std::endl << "   BearingUltimateStrength";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.bearingUltimateStrengthDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.bearingUltimateStrength;

	out_Stream << std::endl << "   FatigueStrength";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.fatigueStrengthDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.fatigueStrength;

	out_Stream << std::endl << "   fatigueNumberOfCycles";
	out_Stream << std::endl << "      Defined: " << in_CADAnalyses.fatigueNumberOfCyclesDefined;
	out_Stream << std::endl << "      Value: "   << in_CADAnalyses.fatigueNumberOfCycles;

};

void stream_AnalysisMaterialProperties_Allowables( const AnalysisMaterialProperties_Allowables	&in_AnalysisMaterialProperties, std::ostream &out_Stream )
{
	out_Stream << std::endl << "Material Name: " << in_AnalysisMaterialProperties.materialName;
	out_Stream << std::endl << "Material Type: " << isis::MaterialType_string(in_AnalysisMaterialProperties.materialType);

	out_Stream << std::endl << "   TensileStrength";
	out_Stream << std::endl << "      Value: "   << in_AnalysisMaterialProperties.tensileStrength;

	out_Stream << std::endl << "   ShearStrength";
	out_Stream << std::endl << "      Value: "   << in_AnalysisMaterialProperties.shearStrength;

	out_Stream << std::endl << "   BearingStrength";
	out_Stream << std::endl << "      Value: "   << in_AnalysisMaterialProperties.bearingStrength;

	out_Stream << std::endl << "   NumberOfCycles";
	out_Stream << std::endl << "      Value: "   << in_AnalysisMaterialProperties.numberOfCycles;

};

///////////////////////////////////////////////////////////////////////////////////////////////
bool 	StressValueComputed(	bool	in_Value_1_Defined,
								double	in_Value_1,
								bool	in_Value_2_Defined,
								double	in_Value_2,
								double	in_ReferenceValue,
								double  &out_ComputedValue )
{

		if ( in_Value_1_Defined && in_Value_2_Defined  )
		{
			out_ComputedValue =  (in_ReferenceValue / in_Value_1 ) * in_Value_2;
			//std::cout << std::endl << "BEGIN StressValueComputed";
			//std::cout << std::endl << "  in_Value_1_Defined: "	<< in_Value_1_Defined;
			//std::cout << std::endl << "  in_Value_1: "			<< in_Value_1;
			//std::cout << std::endl << "  in_Value_2_Defined: "	<< in_Value_1_Defined;
			//std::cout << std::endl << "  in_Value_2: "			<< in_Value_2;
			//std::cout << std::endl << "  in_ReferenceValue:  "	<< in_ReferenceValue;
			//std::cout << std::endl << "  out_ComputedValue:  "	<< out_ComputedValue;
			//std::cout << std::endl << "END StressValueComputed";
			return true;
		}
		else
		{
			return false;
		}
}
/////////////////////////////////////////////////////////////////////////////////////////////////

// General Rules:
//	1)	If in_NumberOfCycles == 0 assume infinite number of cycles
//	2)	Consider 500,000,000 (5M) cycles to be infinite cycles
//	3)  Consider less than 1,000 cycles to be equivalent to 1 cycle for determining the allowable stress levels.
//	3)	Aluminum must have in_AnalysisMaterialProperties.fatigueStrength and fatigueNumberOfCycles set.
//	4)	For Steel, if in_AnalysisMaterialProperties.fatigueStrength is not set, assume that it is
//		50% of in_AnalysisMaterialProperties.tensileUltimateStrength
//	5)	TBD For cycle values between 1,000 and 500,000,000, we need a graph of X-Number Cycles, and Y-Stress.  These
//		are usually log base 10 graphs.
//
//	Material  		Fatigue		Number 		Tensile							Bearing							Shear
//					Given		Cycles		Strength						Strength						Strength
//	-----------		--------	-------		--------------					-----------						---------------
//	Steel/Aluminum	Yes			N < 1000	tensileYieldStrength			bearingYieldStrength			shearStrength
//	Stee/Aluminum	Yes			1000 < N 	fatigueStrength					SF * bearingUltimateStrength	SF * shearStrength
//	Steel			No			1000 < N 	.5 * tensileUltimateStrength	SF  * bearingYieldStrength		SF  * shearStrength
//	Aluminum		No			(Not Allowed, throw exception)
//
// SF - Scale Factor = fatigueStrength / tensileUltimateStrength;

void ComputeAllowableStressLevels(	int										in_NumberOfCycles,
									const AnalysisMaterialProperties		&in_AnalysisMaterialProperties,
								    AnalysisMaterialProperties_Allowables	&out_AnalysisMaterialProperties )
																				throw (isis::application_exception)
{
	///////////////////////////////////////////////
	// zero out values
	///////////////////////////////////////////////
	out_AnalysisMaterialProperties.materialName = "";
	out_AnalysisMaterialProperties.tensileStrength	= 0;
	out_AnalysisMaterialProperties.bearingStrength	= 0;
	out_AnalysisMaterialProperties.shearStrength	= 0;
	out_AnalysisMaterialProperties.numberOfCycles	= 0;

	///////////////////////////////////////////////
	// Only Aluminum and Steel Currently Supported
	///////////////////////////////////////////////
	if ( ( in_AnalysisMaterialProperties.materialType != MATERIAL_ALUMINUM ) &&
		 ( in_AnalysisMaterialProperties.materialType != MATERIAL_STEEL ) )

	{
		// zzz fix this to use the common functions
		std::string tempMaterialType = "Unknown";
		if (in_AnalysisMaterialProperties.materialType == MATERIAL_CERAMIC )tempMaterialType = "Ceramic";
		if (in_AnalysisMaterialProperties.materialType == MATERIAL_PLASTIC )tempMaterialType = "Plastic";

		std::string TempError = "Function ComputeAllowableStressLevels(...) for material " + in_AnalysisMaterialProperties.materialName +
		" was passed: " + tempMaterialType +
		".  Currently only Steel and Aluminum are supported. Plastic and Ceramic will be supported in future releases.";
		throw isis::application_exception(TempError.c_str());
	}

	///////////////////////////////////////////////////////
	// Fatigue strength must be given defined for Aluminum
	///////////////////////////////////////////////////////
	if ( ( in_AnalysisMaterialProperties.materialType == MATERIAL_ALUMINUM ) &&
		 ( !in_AnalysisMaterialProperties.fatigueStrengthDefined ) )
	{
		std::string TempError = "Function ComputeAllowableStressLevels(...) for material " + in_AnalysisMaterialProperties.materialName +
		" does not have a fatigue strength defined.  Aluminum must have fatigue strength defined.";
		throw isis::application_exception(TempError.c_str());
	}

	////////////////////////////////////////
	// Tensile yield strength must be given
	////////////////////////////////////////
	if ( !in_AnalysisMaterialProperties.tensileYieldStrengthDefined )
	{
		std::string TempError = "Function ComputeAllowableStressLevels(...) for material " + in_AnalysisMaterialProperties.materialName +
		" does not have a tensile yield strength defined.  All material must have tensile yield strength defined.";
		throw isis::application_exception(TempError.c_str());
	}

	////////////////////////////////////////
	// Tensile Ultimate strength must be given
	////////////////////////////////////////
	if ( !in_AnalysisMaterialProperties.tensileUltimateStrengthDefined )
	{
		std::string TempError = "Function ComputeAllowableStressLevels(...) for material " + in_AnalysisMaterialProperties.materialName +
		" does not have a tensile ultimate strength defined.  All material must have tensile ultimate strength defined.";
		throw isis::application_exception(TempError.c_str());
	}

	//////////////////////////////////////////////
	// Shear Strength must be given for Aluminum
	/////////////////////////////////////////////
	if ( !in_AnalysisMaterialProperties.shearStrengthDefined &&  in_AnalysisMaterialProperties.materialType == MATERIAL_ALUMINUM  )
	{
		std::string TempError = "Function ComputeAllowableStressLevels(...) for material " + in_AnalysisMaterialProperties.materialName +
		" does not have a shear strength defined.  Aluminum must have shear strength defined.";
		throw isis::application_exception(TempError.c_str());
	}

	////////////////////////////////////////
	// Set Material Name and Type
	////////////////////////////////////////
	out_AnalysisMaterialProperties.materialName = in_AnalysisMaterialProperties.materialName;
	out_AnalysisMaterialProperties.materialType = in_AnalysisMaterialProperties.materialType;

	////////////////////////////////////////
	// Set Number of cycles
	////////////////////////////////////////
	out_AnalysisMaterialProperties.numberOfCycles = in_NumberOfCycles;

	////////////////////////////////////////
	// Compute tempShearStrength
	////////////////////////////////////////
	double tempShearStrength;
	if ( in_AnalysisMaterialProperties.shearStrengthDefined )
	{
		tempShearStrength = in_AnalysisMaterialProperties.shearStrength;
	}
	else
	{
		// Case for steel when shear strength is not defined, use rule of thumb
		tempShearStrength = .58 * in_AnalysisMaterialProperties.tensileYieldStrength;
	}

	////////////////////////////////////////
	// No Fatigue Case
	////////////////////////////////////////
	if ( ( in_NumberOfCycles != 0 ) && ( in_NumberOfCycles < 1000 ) )
	{
		out_AnalysisMaterialProperties.tensileStrength = in_AnalysisMaterialProperties.tensileYieldStrength;
		out_AnalysisMaterialProperties.shearStrength   = tempShearStrength;

		if (in_AnalysisMaterialProperties.bearingYieldStrengthDefined )
			out_AnalysisMaterialProperties.bearingStrength = in_AnalysisMaterialProperties.bearingYieldStrength;
		else
			out_AnalysisMaterialProperties.bearingStrength = in_AnalysisMaterialProperties.tensileYieldStrength;

	}
	else
	{
		/////////////////////////////////////////
		// Fatigue Case - Steel and Aluminum
		////////////////////////////////////////

		// Note fatigue strength would be defined for aluminum as checked above, maybe not for steel.
		double tempFatigueStrength;

		if ( in_AnalysisMaterialProperties.fatigueStrengthDefined )
			tempFatigueStrength = in_AnalysisMaterialProperties.fatigueStrength;
		else
			tempFatigueStrength = .5 * in_AnalysisMaterialProperties.tensileUltimateStrength;

		// Tensile
		out_AnalysisMaterialProperties.tensileStrength = tempFatigueStrength;

		// Bearing
		bool stressComputed = false;
		stressComputed = StressValueComputed(
							in_AnalysisMaterialProperties.tensileUltimateStrengthDefined,
							in_AnalysisMaterialProperties.tensileUltimateStrength,
							in_AnalysisMaterialProperties.bearingUltimateStrengthDefined,
							in_AnalysisMaterialProperties.bearingUltimateStrength,
							tempFatigueStrength,
							out_AnalysisMaterialProperties.bearingStrength );

		if ( !stressComputed )
		   stressComputed = StressValueComputed(
							in_AnalysisMaterialProperties.tensileYieldStrengthDefined,
							in_AnalysisMaterialProperties.tensileYieldStrength,
							in_AnalysisMaterialProperties.bearingYieldStrengthDefined,
							in_AnalysisMaterialProperties.bearingYieldStrength,
							tempFatigueStrength,
							out_AnalysisMaterialProperties.bearingStrength );

		if ( !stressComputed )
			out_AnalysisMaterialProperties.bearingStrength = tempFatigueStrength;

		// Shear
		stressComputed = StressValueComputed(
							in_AnalysisMaterialProperties.tensileUltimateStrengthDefined,
							in_AnalysisMaterialProperties.tensileUltimateStrength,
							true,
							tempShearStrength,
							tempFatigueStrength,
							out_AnalysisMaterialProperties.shearStrength );

		if ( !stressComputed )
			stressComputed = StressValueComputed(
							in_AnalysisMaterialProperties.tensileYieldStrengthDefined,
							in_AnalysisMaterialProperties.tensileYieldStrength,
							true,
							tempShearStrength,
							tempFatigueStrength,
							out_AnalysisMaterialProperties.shearStrength );
		if ( !stressComputed )
			out_AnalysisMaterialProperties.shearStrength =  .5 * in_AnalysisMaterialProperties.shearStrength;

	}

	///////////////////////////////////////////////
	//	At this point, the strength properties
	//	should be set.  It would be a programming
	//	error if this was not the case. Throw
	//	and exception if the strength properties
	//	are not set.
	///////////////////////////////////////////////

	if ( out_AnalysisMaterialProperties.tensileStrength	== 0 ||
		 out_AnalysisMaterialProperties.bearingStrength	== 0 ||
	     out_AnalysisMaterialProperties.shearStrength	== 0 )
	{
		std::ostringstream tensileStrength_str;
		std::ostringstream bearingStrength_str;
		std::ostringstream shearStrength_str;

		tensileStrength_str << out_AnalysisMaterialProperties.tensileStrength;
		bearingStrength_str << out_AnalysisMaterialProperties.bearingStrength;
		shearStrength_str   << out_AnalysisMaterialProperties.shearStrength;

		std::string TempError = "Function ComputeAllowableStressLevels(...) for material " + in_AnalysisMaterialProperties.materialName +
		" failed to compute the all of the strength material properties.  Computed properties are: Tensile Strength: " +
		tensileStrength_str.str() + ", Bearing Strength: " + bearingStrength_str.str() + ", Shear Strength: " + shearStrength_str.str() + ".";
		throw isis::application_exception(TempError.c_str());
	}
}

enum e_UnitType
{
	UNIT_MPA,
	UNIT_GPA,
	UNIT_NA
};

e_UnitType RetrieveUnitType(	const std::string &in_MaterialName,
								const std::string &in_Units )
																	throw (isis::application_exception)
{
	std::string units = isis::ConvertToUpperCase(in_Units);

	if ( strcmp(units.c_str(), "MPA") == 0  )
		return UNIT_MPA;
	else
		if ( strcmp(units.c_str(), "GPA")  == 0  )
			return UNIT_GPA;
		else
			if ( strcmp(units.c_str(), "N/A")  == 0  )
				return UNIT_NA;

	// No known type found.
	std::string TempError = "Function RetrieveUnitType(...) for material " + in_MaterialName +
		" was passed: " + in_Units +
		".  Acceptable types (case insensitive) are: MPA, GPA, and N/A.";
	throw isis::application_exception(TempError.c_str());
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// Description:
//		This function converts string representations of material properties
//		(i.e. in_MaterialMetrics_map[in_MaterialPropertyName] ) to doubles and stores
//		the results in out_Property.
//
//		Note -	All units stored in out_Property will be in MPa except for unit-less values
//				such as PoissonsRatio.
//
// Pre-Conditions:
//		if in_MaterialPropertyName[in_MaterialPropertyName] exists
//			in_MaterialPropertyName[in_MaterialPropertyName].units must be MPa, GPa, or N/A
//
// Post-Conditions:
//		if in_MaterialPropertyName[in_MaterialPropertyName] not exists
//			return (no action taken)
//		else
//			if in_MaterialPropertyName[in_MaterialPropertyName].units != MPa, GPa, or N/A
//				throw isis::application_exception
//			else
//				out_Property =  in_MaterialPropertyName[in_MaterialPropertyName].value
//				out_PropertyDefnied = true
//				if in_MaterialPropertyName[in_MaterialPropertyName].units == GPa
//					scale out_Property to be in MPA
//
void SetSingleAnalysisMaterialProperty(
					const std::string						&in_MaterialPropertyName,
					const std::map<std::string, MaterialMetrics> &in_MaterialMetrics_map,
					double									&out_Property,
					bool									&out_PropertyDefnied)
																throw (isis::application_exception)
{
	std::map<std::string, MaterialMetrics>::const_iterator itr = in_MaterialMetrics_map.find(in_MaterialPropertyName);

	if (itr != in_MaterialMetrics_map.end() )
	{
		// Note Poissoin's ration could be zero.  Since we will analyze metals, this should not be a problem.
		if ( itr->second.value == 0 )
		{
			out_PropertyDefnied = false;
		}
		else
		{
			out_Property = itr->second.value;
			out_PropertyDefnied = true;
			if( RetrieveUnitType(in_MaterialPropertyName,itr->second.units ) == UNIT_GPA ) out_Property *= 1000;
		}
	}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
// This funcdtion will convert integer values.  The only integer value is FatigueNumberOfCycles
// which has no units.
void SetSingleAnalysisMaterialProperty(
					const std::string						&in_MaterialPropertyName,
					const std::map<std::string, MaterialMetrics> &in_MaterialMetrics_map,
					long									&out_Property,
					bool									&out_PropertyDefnied)
{
	std::map<std::string, MaterialMetrics>::const_iterator itr = in_MaterialMetrics_map.find(in_MaterialPropertyName);

	if (itr != in_MaterialMetrics_map.end() )
	{
		//out_Property = strtol(itr->second.value.c_str(), NULL,10 );
		out_Property = itr->second.value;

		out_PropertyDefnied = true;
	}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
void PopulateAnalysisMaterialStruct( const Material				 &in_Material,
									 AnalysisMaterialProperties  &out_AnalysisMaterialProperties_struct )
																		throw (isis::application_exception)
{
	out_AnalysisMaterialProperties_struct.materialType = in_Material.materialType;
	out_AnalysisMaterialProperties_struct.materialName = in_Material.materialName;

	//std::map<std::string, MaterialMetrics> i;

	SetSingleAnalysisMaterialProperty(	"ModulusOfElasticity",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.modulusOfElasticity,
										out_AnalysisMaterialProperties_struct.modulusOfElasticityDefined);

	SetSingleAnalysisMaterialProperty(	"PoissonsRatio",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.poissonsRatio,
										out_AnalysisMaterialProperties_struct.poissonsRatioDefined);

	SetSingleAnalysisMaterialProperty(	"TensileYieldStrength",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.tensileYieldStrength,
										out_AnalysisMaterialProperties_struct.tensileYieldStrengthDefined);

	SetSingleAnalysisMaterialProperty(	"TensileUltimateStrength",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.tensileUltimateStrength,
										out_AnalysisMaterialProperties_struct.tensileUltimateStrengthDefined);

	SetSingleAnalysisMaterialProperty(	"ShearStrength",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.shearStrength,
										out_AnalysisMaterialProperties_struct.shearStrengthDefined);

	SetSingleAnalysisMaterialProperty(	"BearingYieldStrength",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.bearingYieldStrength,
										out_AnalysisMaterialProperties_struct.bearingYieldStrengthDefined);

	SetSingleAnalysisMaterialProperty(	"BearingUltimateStrength",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.bearingUltimateStrength,
										out_AnalysisMaterialProperties_struct.bearingUltimateStrengthDefined);

	SetSingleAnalysisMaterialProperty(	"FatigueStrength",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.fatigueStrength,
										out_AnalysisMaterialProperties_struct.fatigueStrengthDefined);

	SetSingleAnalysisMaterialProperty(	"FatigueNumberOfCycles",
										in_Material.materialMetrics_map,
										out_AnalysisMaterialProperties_struct.fatigueNumberOfCycles,
										out_AnalysisMaterialProperties_struct.fatigueNumberOfCyclesDefined);

}

	////////////////////////////////////////////////////////////////////////////////////////////////////

	// in_UnitsConversionFactor should equal to 1.0 if no conversion.
	bool MaterialPropertyFound_RetrieveValue(
						const boost::property_tree::ptree::value_type	&in_Material_pt,
						const std::string	    &in_MaterialName,
						const std::string		&in_MaterialLibPropertyName, // e.g. strength_tensile_ultimate
						const					std::string in_MaterialLibRequiredUnit,  // e.g. Pa
						double					in_UnitsConversionFactor,  // e.g. PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR
						const std::string		&in_ConvertToUnit,
						double					&out_Value)
	{

		//std::clog << std::endl << "   " << in_Material_pt.first.data();

		bool materialFound = false;

		// Case insensitive check.
		if ( isis::ConvertToUpperCase(std::string(in_Material_pt.first.data())) == isis::ConvertToUpperCase(in_MaterialLibPropertyName) )
		{
			std::string unit;
			if ( in_MaterialLibRequiredUnit.size() == 0 )
			{
				unit = "None";
			}
			else
			{
				try
				{
					unit = in_Material_pt.second.get<std::string>("unit");
				}
				catch (...)  // Could not get the unit, but units are required.  Assume the property is not defined
				{

					isis_LOG(lg, isis_CONSOLE_FILE, isis_ERROR) << "Material units not defined for: " << isis_EOL <<
											  "   Material Name:     " << in_MaterialName << isis_EOL <<
											  "   Material Property: " << in_MaterialLibPropertyName << isis_EOL <<
											  "   Note:              Without units, this material property cannot be used.";
					return false;
				}
			}

			materialFound = true;
			if ( in_MaterialLibRequiredUnit.size() > 0 )
			{
				// Must verify units.
				if ( isis::ConvertToUpperCase(in_MaterialLibRequiredUnit) != isis::ConvertToUpperCase(unit) )
				{
					std::stringstream errorString;
								errorString <<
										"Material:          " <<  in_MaterialName << std::endl <<
										"Material Property: " <<  in_MaterialLibPropertyName << std::endl <<
										"Material Unit:	    " <<  unit  << std::endl <<
										"Required Unit:     " << in_MaterialLibRequiredUnit << std::endl <<
										"The units in the material library are incompatible with the required units.";
					throw isis::application_exception(errorString.str());
				}
			}

			std::string value_string =  in_Material_pt.second.get<std::string>("value");

			if ( isis::ConvertToUpperCase(value_string) == "NONE") return false;

			out_Value =  atof(value_string.c_str()) * in_UnitsConversionFactor;
			// Alternate conversion approach, should consider.
			// boost::optional<float> v = pt.get_optional<float>("a.path.to.float.value");
			// http://www.boost.org/doc/libs/1_43_0/doc/html/boost_propertytree/accessing.html

			if ( out_Value == 0 )
			{
				std::stringstream errorString;
				errorString <<
					"Material:          " <<  in_MaterialName << std::endl <<
					"Material Property: " <<  in_MaterialLibPropertyName << std::endl <<
					"Material Unit:	    " <<  unit  << std::endl <<
					"Value:             " << value_string << std::endl <<
					"A zero value is not allowed.";
				throw isis::application_exception(errorString.str());
			}

			isis_LOG(lg, isis_FILE, isis_INFO) <<
					"Material:                " <<  in_MaterialName << isis_EOL <<
					"Material Property:       " <<  in_MaterialLibPropertyName << isis_EOL <<
					"Material Unit:	          " <<  unit  << isis_EOL <<
					"Material Library Value:  " <<  value_string << isis_EOL <<
					"Unit Conversion Factor:  " <<  in_UnitsConversionFactor << isis_EOL <<
					"Convert to Unit:         " << in_ConvertToUnit << isis_EOL <<
					"Converted Value		  " << out_Value;
		}

		return materialFound;
	}
	////////////////////////////////////////////////////////////////////////////////////////////////////

	// Throw an exception if:
	// a) All the meterials in in_MaterialNames are not found in n_MaterialLibrary_PathAndFileName
	// b) Material properties are not in Pa or kg/m3.
	//
	//  Note - this function converts :
	//				Pa to MPa
	//				kg/m3 to kg/mm3
	//				W/(m*K) to W/(mm*K)
	//				and stores the converted values
	//         in out_Materials
	//

	void ReadMaterialsLibrary(  	const std::string &in_MaterialLibrary_PathAndFileName,
									const std::set<std::string> &in_MaterialNames,
									std::map<std::string, Material> &out_Materials )
													 throw (isis::application_exception)
	{
		using boost::property_tree::ptree;

		std::string currentMaterialName;
		std::string currentMaterialProperty;

		try
		{
			// Create a set of material names.  When a material is found remove it from the set.
			// If the set is not empty at the end of the program, throw and exception.
			std::set<std::string>  notFound_MaterialNames_set;
			for each ( std::string i in in_MaterialNames) notFound_MaterialNames_set.insert(i);

			boost::filesystem::path p(in_MaterialLibrary_PathAndFileName);
			if (boost::filesystem::exists(p))
			{
				if (boost::filesystem::is_regular_file(p))
				{
					ptree pt;
					boost::property_tree::read_json(p.make_preferred().string(), pt);
					BOOST_FOREACH(ptree::value_type& materialLib_pt, pt.get_child("Material library"))
					{
						//if (materialLib_pt.size() != 1 )
						//{
						//	std::stringstream errorString;
						//		errorString <<
						//				"Function:         ReadMaterialsLibrary" << endl <<
						//				"Material Library: "  << in_MaterialLibrary_PathAndFileName << endl <<
						//				"Number of \"Material library\" tags: " << pt.size() << endl <<
						//				"The material library must contain one and only one \"Material library\" tag.";
						//		throw isis::application_exception(errorString.str());
						//}

						//std::clog << std::endl << materialLib_pt.first.data();
						for each ( const std::string materialName in in_MaterialNames )
						{
							currentMaterialName = materialName;
							if ( isis::ConvertToUpperCase(std::string(materialLib_pt.first.data())) == isis::ConvertToUpperCase(materialName) )
							{
								notFound_MaterialNames_set.erase(materialName);
								out_Materials[materialName].materialName = materialName;
								out_Materials[materialName].analysisMaterialProperties.materialName = materialName;

								//std::cout << std::endl << materialLib_pt.first.data();
								BOOST_FOREACH(const ptree::value_type& material_pt, materialLib_pt.second)
								{
									double materialPropertyValue;

									if ( isis::ConvertToUpperCase(material_pt.first) ==  "MATERIAL_CATEGORY" )
									{
										std::string materialType =  material_pt.second.get<std::string>("value");
										out_Materials[materialName].materialType = MaterialType_enum(materialType);
										out_Materials[materialName].analysisMaterialProperties.materialType = MaterialType_enum(materialType);
									}

									currentMaterialProperty = "mechanical__modulus_elastic";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.modulusOfElasticityDefined = true;
										out_Materials[materialName].analysisMaterialProperties.modulusOfElasticity = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.modulusOfElasticityUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "mechanical__ratio_poissons";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"",
																	1.0,
																	"",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.poissonsRatioDefined = true;
										out_Materials[materialName].analysisMaterialProperties.poissonsRatio = materialPropertyValue;
									}

									currentMaterialProperty = "mechanical__strength_tensile_yield";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.tensileYieldStrengthDefined = true;
										out_Materials[materialName].analysisMaterialProperties.tensileYieldStrength = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.tensileYieldStrengthUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "mechanical__strength_tensile_ultimate";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.tensileUltimateStrengthDefined = true;
										out_Materials[materialName].analysisMaterialProperties.tensileUltimateStrength = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.tensileUltimateStrengthUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "mechanical__strength_shear";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.shearStrengthDefined = true;
										out_Materials[materialName].analysisMaterialProperties.shearStrength = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.shearStrengthUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "mechanical__strength_fatigue";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.fatigueStrengthDefined = true;
										out_Materials[materialName].analysisMaterialProperties.fatigueStrength = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.fatigueStrengthUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "mechanical__strength_bearing_yield";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.bearingYieldStrengthDefined = true;
										out_Materials[materialName].analysisMaterialProperties.bearingYieldStrength = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.bearingYieldStrengthUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "mechanical__strength_bearing_ultimate";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"Pa",
																	PASCAL_TO_MEGA_PASCAL_CONVERSION_FACTOR,
																	"MPa",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.bearingUltimateStrengthDefined = true;
										out_Materials[materialName].analysisMaterialProperties.bearingUltimateStrength = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.bearingUltimateStrengthUnit = CADUnitsPressure_enum("MPa");
									}

									currentMaterialProperty = "density";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	//"kg/m3",
																	"kg/m^3",
																	M_CUBED_TO_MM_CUBED_RECIPROCAL,
																	"kg/mm3",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.denstiyDefined = true;
										out_Materials[materialName].analysisMaterialProperties.density= materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.densityUnit = CADUnitsDensity_enum("kg/mm3");
									}

									currentMaterialProperty = "thermal__conductivity";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"W/(m*K)",
																	M_TO_MM_RECIPROCAL,
																	"w/mmk",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.thermalConductivityDefined = true;
										out_Materials[materialName].analysisMaterialProperties.thermalConductivity = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.thermalConductivityUnit = CADUnitsThermalConductivity_enum("w/mmk");
									}

									currentMaterialProperty = "thermal__capacity_specific_heat";

									if ( MaterialPropertyFound_RetrieveValue( material_pt,
																	materialName,
																	currentMaterialProperty,
																	"J/(kg-K)",
																	1.0,
																	"j/kgk",
																	materialPropertyValue ) )
									{
										out_Materials[materialName].analysisMaterialProperties.heatCapacityDefined = true;
										out_Materials[materialName].analysisMaterialProperties.heatCapacity = materialPropertyValue;
										out_Materials[materialName].analysisMaterialProperties.heatCapacityUnit = CADUnitsHeatCapacity_enum("j/kgk");
									}

								}  // END BOOST_FOREACH(const ptree::value_type& material_pt, materialLib_pt.second)
							}  // END if ( isis::ConvertToUpperCase(std::string(materialLib_pt.first.data())) == isis::ConvertToUpperCase(materialName) )
						}  // END for each ( const std::string i in in_MaterialNames )
					} // END BOOST_FOREACH(ptree::value_type& materialLib_pt, pt.get_child("Material library"))
				} // END if (boost::filesystem::is_regular_file(p))
			} // END if (boost::filesystem::exists(p))
			else
			{
				std::stringstream errorString;
				errorString << "Material library input file not found: " << in_MaterialLibrary_PathAndFileName;
				throw isis::application_exception(errorString.str());
			}

			if ( notFound_MaterialNames_set.size() != 0 )
			{
				std::stringstream errorString;
				errorString << "Material(s) not found in the material library: " << in_MaterialLibrary_PathAndFileName << std::endl <<
								"Material(s):" << std::endl;
				for each (std::string i in notFound_MaterialNames_set) errorString << i << std::endl;
				throw isis::application_exception(errorString.str());

			}

		}
		catch(const boost::property_tree::ptree_error &exc)
		{
			std::stringstream errorString;
			errorString << "Input file processing error: " << in_MaterialLibrary_PathAndFileName <<  std::endl <<
							"   Material Name:     " << currentMaterialName <<  std::endl <<
							"   Material Property: " << currentMaterialProperty <<  std::endl <<
							"   Error: " << exc.what();
			throw isis::application_exception(errorString.str());
		}

		catch(const isis::application_exception &exc)
		{
			std::stringstream errorString;
			errorString << "Input file processing error: " << in_MaterialLibrary_PathAndFileName <<  std::endl <<
							"   Material Name:     " << currentMaterialName <<  std::endl <<
							"   Material Property: " << currentMaterialProperty <<  std::endl <<
							"   Error: " << exc.what();
			throw isis::application_exception(errorString.str());
		}

	} // END ReadMaterialsLibrary

} // namespace isis


///////////////////////////////////////////////////////////////
//  Copyright 2012 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt
//
// Comparison operators for cpp_int_backend:
//
#ifndef BOOST_MP_CPP_INT_COMPARISON_HPP
#define BOOST_MP_CPP_INT_COMPARISON_HPP

#include <boost/multiprecision/detail/constexpr.hpp>
#include <boost/type_traits/make_unsigned.hpp>

namespace boost {
namespace multiprecision {
namespace backends {

#ifdef BOOST_MSVC
#pragma warning(push)
#pragma warning(disable : 4018 4389 4996)
#endif

//
// Start with non-trivial cpp_int's:
//
template <unsigned MinBits, unsigned MaxBits, cpp_integer_type SignType,
          cpp_int_check_type Checked, class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<
        cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>>::value,
    bool>::type
eval_eq(
    const cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator> &a,
    const cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator> &b)
    BOOST_NOEXCEPT {
  return (a.sign() == b.sign()) && (a.size() == b.size()) &&
         std_constexpr::equal(a.limbs(), a.limbs() + a.size(), b.limbs());
}
template <unsigned MinBits1, unsigned MaxBits1, cpp_integer_type SignType1,
          cpp_int_check_type Checked1, class Allocator1, unsigned MinBits2,
          unsigned MaxBits2, cpp_integer_type SignType2,
          cpp_int_check_type Checked2, class Allocator2>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1,
                                        Allocator1>>::value &&
        !is_trivial_cpp_int<cpp_int_backend<MinBits2, MaxBits2, SignType2,
                                            Checked2, Allocator2>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits1, MaxBits1, SignType1, Checked1,
                              Allocator1> &a,
        const cpp_int_backend<MinBits2, MaxBits2, SignType2, Checked2,
                              Allocator2> &b) BOOST_NOEXCEPT {
  return (a.sign() == b.sign()) && (a.size() == b.size()) &&
         std_constexpr::equal(a.limbs(), a.limbs() + a.size(), b.limbs());
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked,
                              Allocator> &a,
        limb_type b) BOOST_NOEXCEPT {
  return (a.sign() == false) && (a.size() == 1) && (*a.limbs() == b);
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked,
                              Allocator> &a,
        signed_limb_type b) BOOST_NOEXCEPT {
  return (a.sign() == (b < 0)) && (a.size() == 1) &&
         (*a.limbs() == boost::multiprecision::detail::unsigned_abs(b));
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              Allocator> &a,
        limb_type b) BOOST_NOEXCEPT {
  return (a.size() == 1) && (*a.limbs() == b);
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              Allocator> &a,
        signed_limb_type b) BOOST_NOEXCEPT {
  return (b < 0)
             ? eval_eq(a, cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                          Checked, Allocator>(b))
             : eval_eq(a, static_cast<limb_type>(
                              b)); // Use bit pattern of b for comparison
}

template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked,
                              Allocator> &a,
        limb_type b) BOOST_NOEXCEPT {
  if (a.sign())
    return true;
  if (a.size() > 1)
    return false;
  return *a.limbs() < b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
inline BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked,
                              Allocator> &a,
        signed_limb_type b) BOOST_NOEXCEPT {
  if ((b == 0) || (a.sign() != (b < 0)))
    return a.sign();
  if (a.sign()) {
    if (a.size() > 1)
      return true;
    return *a.limbs() > boost::multiprecision::detail::unsigned_abs(b);
  } else {
    if (a.size() > 1)
      return false;
    return *a.limbs() < boost::multiprecision::detail::unsigned_abs(b);
  }
}

template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              Allocator> &a,
        limb_type b) BOOST_NOEXCEPT {
  if (a.size() > 1)
    return false;
  return *a.limbs() < b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              Allocator> &a,
        signed_limb_type b) BOOST_NOEXCEPT {
  return (b < 0) ? a.compare(b) < 0
                 : eval_lt(a, static_cast<limb_type>(
                                  b)); // Use bit pattern of b for comparison
}

template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked,
                              Allocator> &a,
        limb_type b) BOOST_NOEXCEPT {
  if (a.sign())
    return false;
  if (a.size() > 1)
    return true;
  return *a.limbs() > b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
inline BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked,
                              Allocator> &a,
        signed_limb_type b) BOOST_NOEXCEPT {
  if (b == 0)
    return !a.sign() && ((a.size() > 1) || *a.limbs());
  if (a.sign() != (b < 0))
    return !a.sign();
  if (a.sign()) {
    if (a.size() > 1)
      return false;
    return *a.limbs() < boost::multiprecision::detail::unsigned_abs(b);
  } else {
    if (a.size() > 1)
      return true;
    return *a.limbs() > boost::multiprecision::detail::unsigned_abs(b);
  }
}

template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              Allocator> &a,
        limb_type b) BOOST_NOEXCEPT {
  if (a.size() > 1)
    return true;
  return *a.limbs() > b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class Allocator>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    !is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                        Checked, Allocator>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              Allocator> &a,
        signed_limb_type b) BOOST_NOEXCEPT {
  return (b < 0) ? a.compare(b) > 0
                 : eval_gt(a, static_cast<limb_type>(
                                  b)); // Use bit pattern of b for comparison.
}
//
// And again for trivial cpp_ints:
//
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                       Checked, void>>::value,
    bool>::value
eval_eq(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &b)
    BOOST_NOEXCEPT {
  return (a.sign() == b.sign()) && (*a.limbs() == *b.limbs());
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                       Checked, void>>::value,
    bool>::value
eval_eq(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &b) BOOST_NOEXCEPT {
  return *a.limbs() == *b.limbs();
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class U>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_unsigned<U>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_eq(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    U b) BOOST_NOEXCEPT {
  return !a.sign() && (*a.limbs() == b);
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class S>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_signed<S>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_eq(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    S b) BOOST_NOEXCEPT {
  return (a.sign() == (b < 0)) &&
         (*a.limbs() == boost::multiprecision::detail::unsigned_abs(b));
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class U>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_unsigned<U>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        U b) BOOST_NOEXCEPT {
  return *a.limbs() == b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class S>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_signed<S>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_eq(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        S b) BOOST_NOEXCEPT {
  typedef typename make_unsigned<S>::type ui_type;
  if (b < 0) {
    cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked, void> t(b);
    return *a.limbs() == *t.limbs();
  } else {
    return *a.limbs() == static_cast<ui_type>(b);
  }
}

template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                       Checked, void>>::value,
    bool>::type
eval_lt(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked, void>
        &b) BOOST_NOEXCEPT {
  if (a.sign() != b.sign())
    return a.sign();
  return a.sign() ? *a.limbs() > *b.limbs() : *a.limbs() < *b.limbs();
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                       Checked, void>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &b) BOOST_NOEXCEPT {
  return *a.limbs() < *b.limbs();
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class U>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_unsigned<U>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_lt(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    U b) BOOST_NOEXCEPT {
  if (a.sign())
    return true;
  return *a.limbs() < b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class S>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_signed<S>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_lt(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    S b) BOOST_NOEXCEPT {
  if (a.sign() != (b < 0))
    return a.sign();
  return a.sign()
             ? (*a.limbs() > boost::multiprecision::detail::unsigned_abs(b))
             : (*a.limbs() < boost::multiprecision::detail::unsigned_abs(b));
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class U>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_unsigned<U>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        U b) BOOST_NOEXCEPT {
  return *a.limbs() < b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class S>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_signed<S>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_lt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        S b) BOOST_NOEXCEPT {
  typedef typename make_unsigned<S>::type ui_type;
  if (b < 0) {
    cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked, void> t(b);
    return *a.limbs() < *t.limbs();
  } else {
    return *a.limbs() < static_cast<ui_type>(b);
  }
}

template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                       Checked, void>>::value,
    bool>::type
eval_gt(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &b)
    BOOST_NOEXCEPT {
  if (a.sign() != b.sign())
    return !a.sign();
  return a.sign() ? *a.limbs() < *b.limbs() : *a.limbs() > *b.limbs();
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                       Checked, void>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &b) BOOST_NOEXCEPT {
  return *a.limbs() > *b.limbs();
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class U>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_unsigned<U>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_gt(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    U b) BOOST_NOEXCEPT {
  if (a.sign())
    return false;
  return *a.limbs() > b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class S>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_signed<S>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, signed_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_gt(
    const cpp_int_backend<MinBits, MaxBits, signed_magnitude, Checked, void> &a,
    S b) BOOST_NOEXCEPT {
  if (a.sign() != (b < 0))
    return !a.sign();
  return a.sign()
             ? (*a.limbs() < boost::multiprecision::detail::unsigned_abs(b))
             : (*a.limbs() > boost::multiprecision::detail::unsigned_abs(b));
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class U>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_unsigned<U>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        U b) BOOST_NOEXCEPT {
  return *a.limbs() > b;
}
template <unsigned MinBits, unsigned MaxBits, cpp_int_check_type Checked,
          class S>
BOOST_MP_FORCEINLINE BOOST_MP_CXX14_CONSTEXPR typename enable_if_c<
    is_signed<S>::value &&
        is_trivial_cpp_int<cpp_int_backend<MinBits, MaxBits, unsigned_magnitude,
                                           Checked, void>>::value,
    bool>::type
eval_gt(const cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked,
                              void> &a,
        S b) BOOST_NOEXCEPT {
  typedef typename make_unsigned<S>::type ui_type;
  if (b < 0) {
    cpp_int_backend<MinBits, MaxBits, unsigned_magnitude, Checked, void> t(b);
    return *a.limbs() > *t.limbs();
  } else {
    return *a.limbs() > static_cast<ui_type>(b);
  }
}

#ifdef BOOST_MSVC
#pragma warning(pop)
#endif

} // namespace backends
} // namespace multiprecision
} // namespace boost

#endif


/// @file DeconvolverBase.tcc
/// @brief Base class for a deconvolver
/// @details This interface class defines a deconvolver used to estimate an
/// image from a dirty image, psf optionally using a mask and a weights image.
/// @ingroup Deconvolver
///
///
/// @copyright (c) 2007 CSIRO
/// Australia Telescope National Facility (ATNF)
/// Commonwealth Scientific and Industrial Research Organisation (CSIRO)
/// PO Box 76, Epping NSW 1710, Australia
/// atnf-enquiries@csiro.au
///
/// This file is part of the ASKAP software distribution.
///
/// The ASKAP software distribution 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 program 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 program; if not, write to the Free Software
/// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
///
/// @author Tim Cornwell <tim.cornwell@csiro.au>
///

#include <string>

#include <casacore/casa/aips.h>
#include <boost/shared_ptr.hpp>
#include <casacore/casa/Arrays/Array.h>
#include <casacore/casa/Arrays/ArrayMath.h>
#include <fft/FFTWrapper.h>
#include <askap/AskapLogging.h>
ASKAP_LOGGER(decbaselogger, ".deconvolution.base");

#include <deconvolution/DeconvolverBase.h>
#include <deconvolution/DeconvolverState.h>

namespace askap {

    namespace synthesis {

        template<class T, class FT>
        DeconvolverBase<T, FT>::~DeconvolverBase()
        {
            auditAllMemory();
        };

        template<class T, class FT>
        DeconvolverBase<T, FT>::DeconvolverBase(Vector<Array<T> >& dirty, Vector<Array<T> >& psf) :
                itsBMaj(0.0), itsBMin(0.0), itsBPa(0.0)
        {
            init(dirty, psf);
        }

        template<class T, class FT>
        DeconvolverBase<T, FT>::DeconvolverBase(Array<T>& dirty, Array<T>& psf) :
                itsBMaj(0.0), itsBMin(0.0), itsBPa(0.0)
        {
            Vector<Array<T> > dirtyVec(1);
            dirtyVec(0) = dirty.nonDegenerate();
            Vector<Array<T> > psfVec(1);
            psfVec(0) = psf.nonDegenerate();
            init(dirtyVec, psfVec);
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::init(Vector<Array<T> >& dirtyVec, Vector<Array<T> >& psfVec)
        {

            ASKAPCHECK(psfVec.nelements() == dirtyVec.nelements(),
                       "Vectors of dirty images and PSF's not same length");

            itsNumberTerms = dirtyVec.nelements();

            itsDirty.resize(itsNumberTerms);
            itsPsf.resize(itsNumberTerms);
            itsModel.resize(itsNumberTerms);
            itsWeight.resize(itsNumberTerms);

            ASKAPLOG_INFO_STR(decbaselogger, "There are " << itsNumberTerms << " dirty images");

            for (uInt term = 0; term < itsNumberTerms; term++) {

                ASKAPASSERT(dirtyVec(term).nonDegenerate().shape().nelements() == 2);
                ASKAPASSERT(psfVec(term).nonDegenerate().shape().nelements() == 2);

                this->itsDirty(term) = dirtyVec(term).nonDegenerate().copy();
                this->itsPsf(term) = psfVec(term).nonDegenerate().copy();

                ASKAPASSERT(this->itsPsf(term).shape().conform(this->itsDirty(term).shape()));

                ASKAPLOG_INFO_STR(decbaselogger, "Dirty image(" << term << ") has shape: "
                                      << this->dirty(term).shape());

                this->model(term).resize(this->dirty(term).shape());
                this->model(term).set(T(0.0));
            }

            casa::IPosition minPos;
            casa::IPosition maxPos;
            T minVal, maxVal;
            ASKAPLOG_INFO_STR(decbaselogger, "Validating PSF");
            casa::minMax(minVal, maxVal, minPos, maxPos, this->psf(0));

            const Int nx(this->psf(0).shape()(0));
            const Int ny(this->psf(0).shape()(1));

            ASKAPLOG_INFO_STR(decbaselogger, "Maximum of PSF(0) = " << maxVal << " at " << maxPos);
            ASKAPLOG_INFO_STR(decbaselogger, "Minimum of PSF(0) = " << minVal << " at " << minPos);

            if ((maxPos(0) != nx / 2) || (maxPos(1) != ny / 2)) {
                ASKAPTHROW(AskapError, "Peak of PSF(0) is at " << maxPos << ": not at centre pixel: [" << nx / 2 << "," << ny / 2 << "]");
            }

            this->itsPeakPSFVal = maxVal;
            this->itsPeakPSFPos = maxPos;

            itsDS = boost::shared_ptr<DeconvolverState<T> >(new DeconvolverState<T>());
            ASKAPASSERT(itsDS);
            itsDC = boost::shared_ptr<DeconvolverControl<T> >(new DeconvolverControl<T>());
            ASKAPASSERT(itsDC);
            itsDM = boost::shared_ptr<DeconvolverMonitor<T> >(new DeconvolverMonitor<T>());
            ASKAPASSERT(itsDM);

            this->validateShapes();

            auditAllMemory();

        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::configure(const LOFAR::ParameterSet& parset)
        {
            this->itsDC->configure(parset);
            this->itsDM->configure(parset);

            // Get the beam information
            const casa::Vector<float> beam = parset.getFloatVector("beam");
            ASKAPCHECK(beam.size() == 3, "Need three elements for beam. You have " << beam);
            ASKAPLOG_INFO_STR(logger, "Restore solver will convolve with the 2D gaussian: " << beam(0) <<
                              " x " << beam(1) << " pixels at position angle " << beam(2) << " degrees");
            itsBMaj = beam(0);
            itsBMin = beam(1);
            itsBPa = beam(2);
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::setModel(const Array<T> model, const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            this->itsModel(term) = model.nonDegenerate().copy();
            this->validateShapes();
        }

        template<class T, class FT>
        Array<T> & DeconvolverBase<T, FT>::model(const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            return itsModel(term);
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::updateDirty(Array<T>& dirty, const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            if (!dirty.shape().nonDegenerate().conform(this->dirty(term).shape())) {
                throw(AskapError("Updated dirty image has different shape"));
            }
            this->itsDirty(term) = dirty.nonDegenerate().copy();
            this->validateShapes();
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::updateDirty(Vector<Array<T> >& dirtyVec)
        {
            if (dirtyVec.nelements() != this->itsDirty.nelements()) {
                throw(AskapError("Updated dirty image has different shape"));
            }
            this->itsDirty.resize(dirtyVec.nelements());
            for (uInt term = 0; term < dirtyVec.nelements(); term++) {
                if (!dirtyVec(term).nonDegenerate().shape().conform(this->itsDirty(term).nonDegenerate().shape())) {
                    throw(AskapError("Updated dirty image has different shape from original"));
                }
                this->itsDirty(term) = dirtyVec(term).nonDegenerate().copy();
            }
            this->validateShapes();
        }

        template<class T, class FT>
        bool DeconvolverBase<T, FT>::deconvolve()
        {
            throw(AskapError("Called base class deconvolver"));
        }

        template<class T, class FT>
        Array<T> & DeconvolverBase<T, FT>::dirty(const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            return itsDirty(term);
        }

        template<class T, class FT>
        Array<T> & DeconvolverBase<T, FT>::psf(const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            return itsPsf(term);
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::setWeight(Array<T> weight, const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            this->itsWeight(term) = weight.nonDegenerate().copy();
        }

        template<class T, class FT>
        Array<T> & DeconvolverBase<T, FT>::weight(const uInt term)
        {
            ASKAPCHECK(term < itsNumberTerms, "Term " << term << " greater than allowed " << itsNumberTerms);
            return itsWeight(term);
        }

        template<class T, class FT>
        boost::shared_ptr<DeconvolverControl<T> > DeconvolverBase<T, FT>::control() const
        {
            ASKAPASSERT(itsDC);
            return itsDC;
        }

        template<class T, class FT>
        bool DeconvolverBase<T, FT>::setControl(boost::shared_ptr<DeconvolverControl<T> > DC)
        {
            itsDC = DC;
            ASKAPASSERT(itsDC);
            return True;
        }

        template<class T, class FT>
        boost::shared_ptr<DeconvolverMonitor<T> > DeconvolverBase<T, FT>::monitor() const
        {
            ASKAPASSERT(itsDM);
            return itsDM;
        }

        template<class T, class FT>
        bool DeconvolverBase<T, FT>::setMonitor(boost::shared_ptr<DeconvolverMonitor<T> > DM)
        {
            itsDM = DM;
            ASKAPASSERT(itsDM);
            return True;
        }

        template<class T, class FT>
        boost::shared_ptr<DeconvolverState<T> > DeconvolverBase<T, FT>::state() const
        {
            ASKAPASSERT(itsDS);
            return itsDS;
        }

        template<class T, class FT>
        bool DeconvolverBase<T, FT>::setState(boost::shared_ptr<DeconvolverState<T> > DS)
        {
            itsDS = DS;
            ASKAPASSERT(itsDS);
            return True;
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::validateShapes()
        {
            casa::IPosition minPos;
            casa::IPosition maxPos;
            T minVal, maxVal;
            casa::minMax(minVal, maxVal, minPos, maxPos, this->psf(0));
            const Int nx(this->psf(0).shape()(0));
            const Int ny(this->psf(0).shape()(1));
            if ((maxPos(0) != nx / 2) || (maxPos(1) != ny / 2)) {
                ASKAPTHROW(AskapError, "Peak of PSF(0) is at " << maxPos << ": not at centre pixel: [" << nx / 2 << "," << ny / 2 << "]");
            }

            for (uInt term = 0; term < itsNumberTerms; term++) {
                if (!(this->dirty(term).shape().size())) {
                    ASKAPTHROW(AskapError, "Dirty image has zero size");
                }
                if (!(this->psf(term).shape().size())) {
                    ASKAPTHROW(AskapError, "PSF image has zero size");
                }
                if (!(this->psf(term).shape()[0] == this->dirty().shape()[0]) || !(this->psf(term).shape()[1] == this->dirty().shape()[1])) {
                    ASKAPTHROW(AskapError, "PSF has different shape from dirty image");
                }

                // The model and dirty image shapes only need to agree on the
                // first two axes
                if (!(this->model(term).shape().size())) {
                    ASKAPTHROW(AskapError, "Model has zero size");
                }
                if (!(this->model(term).shape()[0] == this->dirty().shape()[0]) || !(this->model(term).shape()[1] == this->dirty().shape()[1])) {
                    ASKAPTHROW(AskapError, "Model has different shape from dirty image");
                }
            }
            if (!this->itsPeakPSFPos.size()) {
                ASKAPTHROW(AskapError, "Position of PSF peak not defined " << this->itsPeakPSFPos);
            }
            if (this->itsPeakPSFVal == 0.0) {
                ASKAPTHROW(AskapError, "PSF peak is zero");
            }
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::initialise()
        {
            ASKAPLOG_INFO_STR(decbaselogger, "Initialising weight images");

            // Always check shapes on initialise
            this->validateShapes();

        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::finalise()
        {
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::updateResiduals(Vector<Array<T> >& model)
        {
            ASKAPCHECK(model.shape() == itsNumberTerms, "Number of terms in model " << model.shape()
                           << " not same as number of terms specified "
                           << itsNumberTerms);

            for (uInt term = 0; term < itsNumberTerms; term++) {
                Array<FT> xfr;
                xfr.resize(psf(term).shape());
                casa::setReal(xfr, psf(term));
                scimath::fft2d(xfr, true);
                Array<FT> work;
                // Find residuals for current model model
                work.resize(model(term).shape());
                work.set(FT(0.0));
                casa::setReal(work, model(term));
                scimath::fft2d(work, true);
                work = xfr * work;
                scimath::fft2d(work, false);
                this->dirty(term) = this->dirty(term) - real(work);
            }
        }

        template<class T, class FT>
        bool DeconvolverBase<T, FT>::restore(Vector<Array<T> >& restored)
        {
            return restore(restored, itsModel);
        }

        template<class T, class FT>
        bool DeconvolverBase<T, FT>::restore(Vector<Array<T> >& restored, Vector<Array<T> >& model)
        {
            if (itsBMaj <= 0.0) {
                ASKAPLOG_INFO_STR(decbaselogger, "Beam not specified - no restoration");
                return false;
            }

            ASKAPCHECK(model.shape() == itsNumberTerms, "Number of terms in model " << model.shape()
                           << " not same as number of terms specified "
                           << itsNumberTerms);

            ASKAPCHECK(restored.shape() == itsNumberTerms, "Number of terms in restored image " << restored.shape()
                           << " not same as number of terms specified "
                           << itsNumberTerms);

            const int nx(model(0).shape()(0));
            const int ny(model(0).shape()(1));
            const IPosition centre(2, nx / 2, ny / 2);
            Matrix<FT> gaussian(nx, ny);
            gaussian.set(0.0);

            const float scalex(sqrt(4.0*log(2.0)) / itsBMaj);
            const float scaley(sqrt(4.0*log(2.0)) / itsBMin);
            const float theta(itsBPa*C::pi / 180.0);
            const float cs(cos(theta));
            const float sn(sin(theta));
            for (int y = 0; y < nx; y++) {
                const float dy = (y - ny / 2) * scaley;
                for (int x = 0; x < nx; x++) {
                    const float dx = (x - nx / 2) * scalex;
                    const float rsq = pow((dx * cs + dy * sn), 2) + pow((-dx * sn + dy * cs), 2);
                    if (rsq < 20.0) {
                        gaussian(x, y) = exp(-rsq);
                    }
                }
            }
            const float volume(sum(real(gaussian)));

            scimath::fft2d(gaussian, true);

            ASKAPLOG_INFO_STR(logger, "Volume of PSF = " << volume << " pixels");

            for (uInt term = 0; term < itsNumberTerms; term++) {
                Array<FT> vis(model(term).shape());
                vis.set(FT(0.0));
                casa::setReal(vis, model(term));
                scimath::fft2d(vis, true);
                vis = vis * gaussian;
                scimath::fft2d(vis, false);
                restored(term).resize(model(term).shape());
                restored(term) = this->dirty(term) + real(vis);
            }
            return true;
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::updateResiduals(Array<T> & model)
        {
            Vector<Array<T> > modelVec(1);
            modelVec(0) = model;
            updateResiduals(modelVec);
        }

        template<class T, class FT>
        uInt DeconvolverBase<T, FT>::auditMemory(Vector<casa::Array<T> >& vecArray)
        {
            uInt memory = 0;
            for (uInt term = 0; term < vecArray.nelements(); term++) {
                memory += sizeof(T) * vecArray(term).nelements();
            }
            return memory;
        }

        template<class T, class FT>
        uInt DeconvolverBase<T, FT>::auditMemory(Vector<casa::Array<FT> >& vecArray)
        {
            uInt memory = 0;
            for (uInt term = 0; term < vecArray.nelements(); term++) {
                memory += sizeof(FT) * vecArray(term).nelements();
            }
            return memory;
        }

        template<class T, class FT>
        void DeconvolverBase<T, FT>::auditAllMemory()
        {
            ASKAPLOG_DEBUG_STR(decbaselogger, "Dirty images  " << auditMemory(itsDirty));
            ASKAPLOG_DEBUG_STR(decbaselogger, "PSFs          " << auditMemory(itsPsf));
            ASKAPLOG_DEBUG_STR(decbaselogger, "Models        " << auditMemory(itsModel));
            ASKAPLOG_DEBUG_STR(decbaselogger, "Weight images " << auditMemory(itsWeight));
        }

        template<class T, class FT>
        IPosition DeconvolverBase<T, FT>::findSubPsfShape()
        {
            IPosition subPsfShape(2, this->model().shape()(0), this->model().shape()(1));
            // Only use the specified psfWidth if it makes sense
            // Also make sure it is even, otherwise the fft of the PSF is incorrect and the clean diverges
            if (this->control()->psfWidth() > 1) {
                uInt psfWidth = (this->control()->psfWidth()/2)*2;
                if ((psfWidth < uInt(this->model().shape()(0))) && (psfWidth < uInt(this->model().shape()(1)))) {
                    subPsfShape(0) = psfWidth;
                    subPsfShape(1) = psfWidth;
                }
            }
            return subPsfShape;
        }
    } // namespace synthesis

} // namespace askap


/*
  Copyright 2008 Intel Corporation

  Use, modification and distribution are subject to the Boost Software License,
  Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  http://www.boost.org/LICENSE_1_0.txt).
*/

#ifndef BOOST_POLYGON_ISOTROPY_HPP
#define BOOST_POLYGON_ISOTROPY_HPP

//external
#include <cmath>
#include <cstddef>
#include <cstdlib>
#include <vector>
#include <deque>
#include <map>
#include <set>
#include <list>
//#include <iostream>
#include <algorithm>
#include <limits>
#include <iterator>
#include <string>

#ifndef BOOST_POLYGON_NO_DEPS

#include <boost/config.hpp>
#ifdef BOOST_MSVC
#define BOOST_POLYGON_MSVC
#endif
#ifdef BOOST_INTEL
#define BOOST_POLYGON_ICC
#endif
#ifdef BOOST_HAS_LONG_LONG
#define BOOST_POLYGON_USE_LONG_LONG
typedef boost::long_long_type polygon_long_long_type;
typedef boost::ulong_long_type polygon_ulong_long_type;
//typedef long long polygon_long_long_type;
//typedef unsigned long long polygon_ulong_long_type;
#endif
#else

#ifdef _WIN32
#define BOOST_POLYGON_MSVC
#endif
#ifdef __ICC
#define BOOST_POLYGON_ICC
#endif
#define BOOST_POLYGON_USE_LONG_LONG
typedef long long polygon_long_long_type;
typedef unsigned long long polygon_ulong_long_type;
#endif

namespace boost { namespace polygon{

  enum GEOMETRY_CONCEPT_ID {
    COORDINATE_CONCEPT,
    INTERVAL_CONCEPT,
    POINT_CONCEPT,
    POINT_3D_CONCEPT,
    RECTANGLE_CONCEPT,
    POLYGON_90_CONCEPT,
    POLYGON_90_WITH_HOLES_CONCEPT,
    POLYGON_45_CONCEPT,
    POLYGON_45_WITH_HOLES_CONCEPT,
    POLYGON_CONCEPT,
    POLYGON_WITH_HOLES_CONCEPT,
    POLYGON_90_SET_CONCEPT,
    POLYGON_45_SET_CONCEPT,
    POLYGON_SET_CONCEPT
  };

  struct undefined_concept {};

  template <typename T>
  struct geometry_concept { typedef undefined_concept type; };

  template <typename GCT, typename T>
  struct view_of {};

  template <typename T1, typename T2>
  view_of<T1, T2> view_as(const T2& obj) { return view_of<T1, T2>(obj); }

  template <typename T, bool /*enable*/ = true>
  struct coordinate_traits {};

  //used to override long double with an infinite precision datatype
  template <typename T>
  struct high_precision_type {
    typedef long double type;
  };

  template <typename T>
  T convert_high_precision_type(const typename high_precision_type<T>::type& v) {
    return T(v);
  }

  //used to override std::sort with an alternative (parallel) algorithm
  template <typename iter_type>
  void polygon_sort(iter_type _b_, iter_type _e_);

  template <typename iter_type, typename pred_type>
  void polygon_sort(iter_type _b_, iter_type _e_, const pred_type& _pred_);

  template <>
  struct coordinate_traits<int> {
    typedef int coordinate_type;
    typedef long double area_type;
#ifdef BOOST_POLYGON_USE_LONG_LONG
    typedef polygon_long_long_type manhattan_area_type;
    typedef polygon_ulong_long_type unsigned_area_type;
    typedef polygon_long_long_type coordinate_difference;
#else
    typedef long manhattan_area_type;
    typedef unsigned long unsigned_area_type;
    typedef long coordinate_difference;
#endif
    typedef long double coordinate_distance;
  };

  template<>
  struct coordinate_traits<long, sizeof(long) == sizeof(int)> {
    typedef coordinate_traits<int> cT_;
    typedef cT_::coordinate_type coordinate_type;
    typedef cT_::area_type area_type;
    typedef cT_::manhattan_area_type manhattan_area_type;
    typedef cT_::unsigned_area_type unsigned_area_type;
    typedef cT_::coordinate_difference coordinate_difference;
    typedef cT_::coordinate_distance coordinate_distance;
  };

#ifdef BOOST_POLYGON_USE_LONG_LONG
  template <>
  struct coordinate_traits<polygon_long_long_type> {
    typedef polygon_long_long_type coordinate_type;
    typedef long double area_type;
    typedef polygon_long_long_type manhattan_area_type;
    typedef polygon_ulong_long_type unsigned_area_type;
    typedef polygon_long_long_type coordinate_difference;
    typedef long double coordinate_distance;
  };

  template<>
  struct coordinate_traits<long, sizeof(long) == sizeof(polygon_long_long_type)> {
    typedef coordinate_traits<polygon_long_long_type> cT_;
    typedef cT_::coordinate_type coordinate_type;
    typedef cT_::area_type area_type;
    typedef cT_::manhattan_area_type manhattan_area_type;
    typedef cT_::unsigned_area_type unsigned_area_type;
    typedef cT_::coordinate_difference coordinate_difference;
    typedef cT_::coordinate_distance coordinate_distance;
  };
#endif

  template <>
  struct coordinate_traits<float> {
    typedef float coordinate_type;
    typedef float area_type;
    typedef float manhattan_area_type;
    typedef float unsigned_area_type;
    typedef float coordinate_difference;
    typedef float coordinate_distance;
  };

  template <>
  struct coordinate_traits<double> {
    typedef double coordinate_type;
    typedef double area_type;
    typedef double manhattan_area_type;
    typedef double unsigned_area_type;
    typedef double coordinate_difference;
    typedef double coordinate_distance;
  };

  template <>
  struct coordinate_traits<long double> {
    typedef long double coordinate_type;
    typedef long double area_type;
    typedef long double manhattan_area_type;
    typedef long double unsigned_area_type;
    typedef long double coordinate_difference;
    typedef long double coordinate_distance;
  };

  template <typename T>
  struct scaling_policy {
    template <typename T2>
    static inline T round(T2 t2) {
      return (T)std::floor(t2+0.5);
    }

    static inline T round(T t2) {
      return t2;
    }
  };

  struct coordinate_concept {};

  template <>
  struct geometry_concept<int> { typedef coordinate_concept type; };
#ifdef BOOST_POLYGON_USE_LONG_LONG
  template <>
  struct geometry_concept<polygon_long_long_type> { typedef coordinate_concept type; };
#endif
  template <>
  struct geometry_concept<float> { typedef coordinate_concept type; };
  template <>
  struct geometry_concept<double> { typedef coordinate_concept type; };
  template <>
  struct geometry_concept<long double> { typedef coordinate_concept type; };

  struct gtl_no { static const bool value = false; };
  struct gtl_yes { typedef gtl_yes type;
    static const bool value = true; };

  template <bool T, bool T2>
  struct gtl_and_c { typedef gtl_no type; };
  template <>
  struct gtl_and_c<true, true> { typedef gtl_yes type; };

  template <typename T, typename T2>
  struct gtl_and : gtl_and_c<T::value, T2::value> {};
  template <typename T, typename T2, typename T3>
  struct gtl_and_3 { typedef typename gtl_and<
                       T, typename gtl_and<T2, T3>::type>::type type; };

  template <typename T, typename T2, typename T3, typename T4>
  struct gtl_and_4 { typedef typename gtl_and_3<
                       T, T2, typename gtl_and<T3, T4>::type>::type type; };
  template <typename T, typename T2>
  struct gtl_or { typedef gtl_yes type; };
  template <typename T>
  struct gtl_or<T, T> { typedef T type; };

  template <typename T, typename T2, typename T3>
  struct gtl_or_3 { typedef typename gtl_or<
                      T, typename gtl_or<T2, T3>::type>::type type; };

  template <typename T, typename T2, typename T3, typename T4>
  struct gtl_or_4 { typedef typename gtl_or<
                      T, typename gtl_or_3<T2, T3, T4>::type>::type type; };

  template <typename T>
  struct gtl_not { typedef gtl_no type; };
  template <>
  struct gtl_not<gtl_no> { typedef gtl_yes type; };

  template <typename T>
  struct gtl_if {
#ifdef BOOST_POLYGON_MSVC
    typedef gtl_no type;
#endif
  };
  template <>
  struct gtl_if<gtl_yes> { typedef gtl_yes type; };

  template <typename T, typename T2>
  struct gtl_same_type { typedef gtl_no type; };
  template <typename T>
  struct gtl_same_type<T, T> { typedef gtl_yes type; };
  template <typename T, typename T2>
  struct gtl_different_type { typedef typename gtl_not<typename gtl_same_type<T, T2>::type>::type type; };

  struct manhattan_domain {};
  struct forty_five_domain {};
  struct general_domain {};

  template <typename T>
  struct geometry_domain { typedef general_domain type; };

  template <typename domain_type, typename coordinate_type>
  struct area_type_by_domain { typedef typename coordinate_traits<coordinate_type>::area_type type; };
  template <typename coordinate_type>
  struct area_type_by_domain<manhattan_domain, coordinate_type> {
    typedef typename coordinate_traits<coordinate_type>::manhattan_area_type type; };

  template<bool E, class R = void>
  struct enable_if_ {
      typedef R type;
  };

  template<class R>
  struct enable_if_<false, R> { };

  template<class E, class R = void>
  struct enable_if
      : enable_if_<E::value, R> { };

  struct y_c_edist : gtl_yes {};

  template <typename coordinate_type_1, typename coordinate_type_2>
    typename enable_if<
    typename gtl_and_3<y_c_edist, typename gtl_same_type<typename geometry_concept<coordinate_type_1>::type, coordinate_concept>::type,
                       typename gtl_same_type<typename geometry_concept<coordinate_type_1>::type, coordinate_concept>::type>::type,
    typename coordinate_traits<coordinate_type_1>::coordinate_difference>::type
  euclidean_distance(const coordinate_type_1& lvalue, const coordinate_type_2& rvalue) {
    typedef typename coordinate_traits<coordinate_type_1>::coordinate_difference Unit;
    return (lvalue < rvalue) ? (Unit)rvalue - (Unit)lvalue : (Unit)lvalue - (Unit)rvalue;
  }

  // predicated_swap swaps a and b if pred is true

  // predicated_swap is guarenteed to behave the same as
  // if(pred){
  //   T tmp = a;
  //   a = b;
  //   b = tmp;
  // }
  // but will not generate a branch instruction.
  // predicated_swap always creates a temp copy of a, but does not
  // create more than one temp copy of an input.
  // predicated_swap can be used to optimize away branch instructions in C++
  template <class T>
  inline bool predicated_swap(const bool& pred,
                              T& a,
                              T& b) {
    const T tmp = a;
    const T* input[2] = {&b, &tmp};
    a = *input[!pred];
    b = *input[pred];
    return pred;
  }

  enum direction_1d_enum { LOW = 0, HIGH = 1,
                           LEFT = 0, RIGHT = 1,
                           CLOCKWISE = 0, COUNTERCLOCKWISE = 1,
                           REVERSE = 0, FORWARD = 1,
                           NEGATIVE = 0, POSITIVE = 1 };
  enum orientation_2d_enum { HORIZONTAL = 0, VERTICAL = 1 };
  enum direction_2d_enum { WEST = 0, EAST = 1, SOUTH = 2, NORTH = 3 };
  enum orientation_3d_enum { PROXIMAL = 2 };
  enum direction_3d_enum { DOWN = 4, UP = 5 };
  enum winding_direction {
    clockwise_winding = 0,
    counterclockwise_winding = 1,
    unknown_winding = 2
  };

  class direction_2d;
  class direction_3d;
  class orientation_2d;

  class direction_1d {
  private:
    unsigned int val_;
    explicit direction_1d(int d);
  public:
    inline direction_1d() : val_(LOW) {}
    inline direction_1d(const direction_1d& that) : val_(that.val_) {}
    inline direction_1d(const direction_1d_enum val) : val_(val) {}
    explicit inline direction_1d(const direction_2d& that);
    explicit inline direction_1d(const direction_3d& that);
    inline direction_1d& operator = (const direction_1d& d) {
      val_ = d.val_; return * this; }
    inline bool operator==(direction_1d d) const { return (val_ == d.val_); }
    inline bool operator!=(direction_1d d) const { return !((*this) == d); }
    inline unsigned int to_int(void) const { return val_; }
    inline direction_1d& backward() { val_ ^= 1; return *this; }
    inline int get_sign() const { return val_ * 2 - 1; }
  };

  class direction_2d;

  class orientation_2d {
  private:
    unsigned int val_;
    explicit inline orientation_2d(int o);
  public:
    inline orientation_2d() : val_(HORIZONTAL) {}
    inline orientation_2d(const orientation_2d& ori) : val_(ori.val_) {}
    inline orientation_2d(const orientation_2d_enum val) : val_(val) {}
    explicit inline orientation_2d(const direction_2d& that);
    inline orientation_2d& operator=(const orientation_2d& ori) {
      val_ = ori.val_; return * this; }
    inline bool operator==(orientation_2d that) const { return (val_ == that.val_); }
    inline bool operator!=(orientation_2d that) const { return (val_ != that.val_); }
    inline unsigned int to_int() const { return (val_); }
    inline void turn_90() { val_ = val_^ 1; }
    inline orientation_2d get_perpendicular() const {
      orientation_2d retval = *this;
      retval.turn_90();
      return retval;
    }
    inline direction_2d get_direction(direction_1d dir) const;
  };

  class direction_2d {
  private:
    int val_;

  public:

    inline direction_2d() : val_(WEST) {}

    inline direction_2d(const direction_2d& that) : val_(that.val_) {}

    inline direction_2d(const direction_2d_enum val) : val_(val) {}

    inline direction_2d& operator=(const direction_2d& d) {
      val_ = d.val_;
      return * this;
    }

    inline ~direction_2d() { }

    inline bool operator==(direction_2d d) const { return (val_ == d.val_); }
    inline bool operator!=(direction_2d d) const { return !((*this) == d); }
    inline bool operator< (direction_2d d) const { return (val_ < d.val_); }
    inline bool operator<=(direction_2d d) const { return (val_ <= d.val_); }
    inline bool operator> (direction_2d d) const { return (val_ > d.val_); }
    inline bool operator>=(direction_2d d) const { return (val_ >= d.val_); }

    // Casting to int
    inline unsigned int to_int(void) const { return val_; }

    inline direction_2d backward() const {
      // flip the LSB, toggles 0 - 1   and 2 - 3
      return direction_2d(direction_2d_enum(val_ ^ 1));
    }

    // Returns a direction 90 degree left (LOW) or right(HIGH) to this one
    inline direction_2d turn(direction_1d t) const {
      return direction_2d(direction_2d_enum(val_ ^ 3 ^ (val_ >> 1) ^ t.to_int()));
    }

    // Returns a direction 90 degree left to this one
    inline direction_2d left() const {return turn(HIGH);}

    // Returns a direction 90 degree right to this one
    inline direction_2d right() const {return turn(LOW);}

    // N, E are positive, S, W are negative
    inline bool is_positive() const {return (val_ & 1);}
    inline bool is_negative() const {return !is_positive();}
    inline int get_sign() const {return ((is_positive()) << 1) -1;}

  };

  direction_1d::direction_1d(const direction_2d& that) : val_(that.to_int() & 1) {}

  orientation_2d::orientation_2d(const direction_2d& that) : val_(that.to_int() >> 1) {}

  direction_2d orientation_2d::get_direction(direction_1d dir) const {
    return direction_2d(direction_2d_enum((val_ << 1) + dir.to_int()));
  }

  class orientation_3d {
  private:
    unsigned int val_;
    explicit inline orientation_3d(int o);
  public:
    inline orientation_3d() : val_((int)HORIZONTAL) {}
    inline orientation_3d(const orientation_3d& ori) : val_(ori.val_) {}
    inline orientation_3d(orientation_2d ori) : val_(ori.to_int()) {}
    inline orientation_3d(const orientation_3d_enum val) : val_(val) {}
    explicit inline orientation_3d(const direction_2d& that);
    explicit inline orientation_3d(const direction_3d& that);
    inline ~orientation_3d() {  }
    inline orientation_3d& operator=(const orientation_3d& ori) {
      val_ = ori.val_; return * this; }
    inline bool operator==(orientation_3d that) const { return (val_ == that.val_); }
    inline bool operator!=(orientation_3d that) const { return (val_ != that.val_); }
    inline unsigned int to_int() const { return (val_); }
    inline direction_3d get_direction(direction_1d dir) const;
  };

  class direction_3d {
  private:
    int val_;

  public:

    inline direction_3d() : val_(WEST) {}

    inline direction_3d(direction_2d that) : val_(that.to_int()) {}
    inline direction_3d(const direction_3d& that) : val_(that.val_) {}

    inline direction_3d(const direction_2d_enum val) : val_(val) {}
    inline direction_3d(const direction_3d_enum val) : val_(val) {}

    inline direction_3d& operator=(direction_3d d) {
      val_ = d.val_;
      return * this;
    }

    inline ~direction_3d() { }

    inline bool operator==(direction_3d d) const { return (val_ == d.val_); }
    inline bool operator!=(direction_3d d) const { return !((*this) == d); }
    inline bool operator< (direction_3d d) const { return (val_ < d.val_); }
    inline bool operator<=(direction_3d d) const { return (val_ <= d.val_); }
    inline bool operator> (direction_3d d) const { return (val_ > d.val_); }
    inline bool operator>=(direction_3d d) const { return (val_ >= d.val_); }

    // Casting to int
    inline unsigned int to_int(void) const { return val_; }

    inline direction_3d backward() const {
      // flip the LSB, toggles 0 - 1   and 2 - 3 and 4 - 5
      return direction_2d(direction_2d_enum(val_ ^ 1));
    }

    // N, E, U are positive, S, W, D are negative
    inline bool is_positive() const {return (val_ & 1);}
    inline bool is_negative() const {return !is_positive();}
    inline int get_sign() const {return ((is_positive()) << 1) -1;}

  };

  direction_1d::direction_1d(const direction_3d& that) : val_(that.to_int() & 1) {}
  orientation_3d::orientation_3d(const direction_3d& that) : val_(that.to_int() >> 1) {}
  orientation_3d::orientation_3d(const direction_2d& that) : val_(that.to_int() >> 1) {}

  direction_3d orientation_3d::get_direction(direction_1d dir) const {
    return direction_3d(direction_3d_enum((val_ << 1) + dir.to_int()));
  }

}
}
#endif