#ifndef _OPTIONSH_
#define _OPTIONSH_

#include <mpi.h>
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string>
#include <boost/program_options.hpp>

using namespace std;
namespace po = boost::program_options;

class Options{

    public:

	//Moving all enums here so Option.hpp can be non-dependent on anything else...
        enum TimeSteppingType {CONST_DT, CONST_CFL};
        enum BCType {PERIODIC_SOLVE, DIRICHLET_SOLVE};
        enum BCKind {PERIODIC,
                     INTERNALLY_PERIODIC,
                     SPONGE,
                     ADIABATIC_WALL,
                     CONST_T_WALL,
                     MOVING_ADIABATIC_WALL,
                     INLET};
        enum SpongeKind {RECTILINEAR, CYLINDRICAL};

	enum FDType {CD2, PADE6, PENTA10};
	enum FilterType {COMPACT8, COMPACT10};

	enum LESModel {NOMODEL, VREMAN, DSM};

	enum StatsAvgType {NONE, XI1_AVG, XI2_AVG, XI3_AVG, LOCAL, GLOBAL};

	enum RKType {TVDRK3, RK4, KENRK4, LSLDDRK4};

	//Variable map...
	po::variables_map vm;

	//Root rank
	int root;
	int mpiRank;

	//Time Stepping Information
	TimeSteppingType TSType;
	string TSType_str;
	double CFL, dt, maxTime;
	int maxTimeStep, filterStep, checkStep, dumpStep;
	bool subStepFiltering;

	//Boundary Condition Stuff
	string bcXType_str, bcYType_str, bcZType_str;
	BCType bcXType, bcYType, bcZType;

	string bcX0_str, bcX1_str, bcY0_str, bcY1_str, bcZ0_str, bcZ1_str;
	BCKind bcX0, bcX1, bcY0, bcY1, bcZ0, bcZ1;
	double periodicDisp[3][3];

	//SPONGE STUFF
	string spongeKind_str;
	SpongeKind spongeKind;
	double spongeP, spongeAvgT, spongeStrength;
	double spongeRectX0Perc, spongeRectY0Perc, spongeRectZ0Perc;
	double spongeRectX1Perc, spongeRectY1Perc, spongeRectZ1Perc;
	int spongeCylAxisOrient;
	double spongeCylAxisX, spongeCylAxisY, spongeCylAxisZ, rMin;

	//DOMAIN Information
	int Nx, Ny, Nz;

	//Pencil Decomposition Stuff
	int pRow, pCol;

	//SOLVER Information
	double alphaF, mu_ref;
	bool useTiming;
	FDType xFDType, yFDType, zFDType;
	string xFDType_str, yFDType_str, zFDType_str;
	FilterType filterType;
	string filterType_str;
	RKType rkType;
	string rkType_str;

	//RESTART stuff
	bool fromRestart;
	bool onlyGridFromRestart;
	string filename;
	bool spongeFromRestart;
	string sponge_filename;
	int  timeStep;
	double time;

	//STATS stuff
	StatsAvgType statsAvgType;
	string statsAvgType_str;

	bool velocityStats;
	bool velocityStatsFromRestart;
	string velocityStatsFilename;

	bool thermoStats;
	bool thermoStatsFromRestart;
	string thermoStatsFilename;

	int stats_interval;

	//LES stuff
	StatsAvgType musgsAvgType;
	string musgsAvgType_str;
	LESModel lesModel;
	string lesModel_str;

	//DSM LES Model Stuff
	bool dumpCoeffRange;
	bool useTaukk;
	int testFilterType;

    Options(int mpiRank){

      root = 0;
      this->mpiRank = mpiRank;

      if(mpiRank == root){
	ifstream input_file("MPI_3DCompact.in");

	po::options_description input("Input Parameters");

	input.add_options()
	    ("TIMESTEPPING.TSTYPE",	 po::value<string>(&TSType_str), "Time Stepping Type")
	    ("TIMESTEPPING.CFL", 	 po::value<double>(&CFL), "CFL Number")
	    ("TIMESTEPPING.DT", 	 po::value<double>(&dt), "Time Step Size")
	    ("TIMESTEPPING.MAXTIME",     po::value<double>(&maxTime), "Max Time")
	    ("TIMESTEPPING.MAXTIMESTEP", po::value<int>(&maxTimeStep), "Max Time Step")
	    ("TIMESTEPPING.FILTERSTEP",  po::value<int>(&filterStep), "Filter Step")
	    ("TIMESTEPPING.SUBSTEPFILTERING",  po::value<bool>(&subStepFiltering), "Filtering every substep")
	    ("TIMESTEPPING.CHECKSTEP",   po::value<int>(&checkStep), "Check Step")
	    ("TIMESTEPPING.DUMPSTEP",    po::value<int>(&dumpStep), "Dump Step")
	    ("BC.BCXTYPE", 		 po::value<string>(&bcXType_str), "BC Type in X")
	    ("BC.BCYTYPE", 		 po::value<string>(&bcYType_str), "BC Type in Y")
	    ("BC.BCZTYPE", 		 po::value<string>(&bcZType_str), "BC Type in Z")
	    ("BC.BCX0",			 po::value<string>(&bcX0_str), "BC Kind for X0")
	    ("BC.BCX1",			 po::value<string>(&bcX1_str), "BC Kind for X1")
	    ("BC.BCY0"	,		 po::value<string>(&bcY0_str), "BC Kind for Y0")
	    ("BC.BCY1",			 po::value<string>(&bcY1_str), "BC Kind for Y1")
	    ("BC.BCZ0",			 po::value<string>(&bcZ0_str), "BC Kind for Z0")
	    ("BC.BCZ1",			 po::value<string>(&bcZ1_str), "BC Kind for Z1")
	    ("BC.PERIODICDISPX_X", 	 po::value<double>(&periodicDisp[0][0]), "Periodic Displacement for X0 face to X1 face in the x-direction")
	    ("BC.PERIODICDISPX_Y", 	 po::value<double>(&periodicDisp[0][1]), "Periodic Displacement for X0 face to X1 face in the y-direction")
	    ("BC.PERIODICDISPX_Z", 	 po::value<double>(&periodicDisp[0][2]), "Periodic Displacement for X0 face to X1 face in the z-direction")
	    ("BC.PERIODICDISPY_X", 	 po::value<double>(&periodicDisp[1][0]), "Periodic Displacement for Y0 face to Y1 face in the x-direction")
	    ("BC.PERIODICDISPY_Y", 	 po::value<double>(&periodicDisp[1][1]), "Periodic Displacement for Y0 face to Y1 face in the y-direction")
	    ("BC.PERIODICDISPY_Z", 	 po::value<double>(&periodicDisp[1][2]), "Periodic Displacement for Y0 face to Y1 face in the z-direction")
	    ("BC.PERIODICDISPZ_X", 	 po::value<double>(&periodicDisp[2][0]), "Periodic Displacement for Z0 face to Z1 face in the x-direction")
	    ("BC.PERIODICDISPZ_Y", 	 po::value<double>(&periodicDisp[2][1]), "Periodic Displacement for Z0 face to Z1 face in the y-direction")
	    ("BC.PERIODICDISPZ_Z", 	 po::value<double>(&periodicDisp[2][2]), "Periodic Displacement for Z0 face to Z1 face in the z-direction")
	    ("SPONGE.KIND",		 po::value<string>(&spongeKind_str), "Type of sponge boundary")
	    ("SPONGE.AVGTIME",		 po::value<double>(&spongeAvgT), "Sponge averaging time")
	    ("SPONGE.PINF",		 po::value<double>(&spongeP), "Sponge back pressure")
	    ("SPONGE.STRENGTH",		 po::value<double>(&spongeStrength), "Sponge strength")
	    ("SPONGE.RECTX0PERC",	 po::value<double>(&spongeRectX0Perc), "Sponge Percent of Domain in X Direction - X0 Face")
	    ("SPONGE.RECTY0PERC",	 po::value<double>(&spongeRectY0Perc), "Sponge Percent of Domain in Y Direction - Y0 Face")
	    ("SPONGE.RECTZ0PERC",	 po::value<double>(&spongeRectZ0Perc), "Sponge Percent of Domain in Z Direction - Z0 Face")
	    ("SPONGE.RECTX1PERC",	 po::value<double>(&spongeRectX1Perc), "Sponge Percent of Domain in X Direction - X1 Face")
	    ("SPONGE.RECTY1PERC",	 po::value<double>(&spongeRectY1Perc), "Sponge Percent of Domain in Y Direction - Y1 Face")
	    ("SPONGE.RECTZ1PERC",	 po::value<double>(&spongeRectZ1Perc), "Sponge Percent of Domain in Z Direction - Z1 Face")
    	    ("SPONGE.CYLAXIS_ORIENT",	 po::value<int>(&spongeCylAxisOrient), "Axis that the cylindrical sponge shape faces")
	    ("SPONGE.CYLAXIS_X",	 po::value<double>(&spongeCylAxisX), "X-location of cylindrical sponge center")
	    ("SPONGE.CYLAXIS_Y", 	 po::value<double>(&spongeCylAxisY), "Y-location of cylindrical sponge center")
	    ("SPONGE.CYLAXIS_Z",	 po::value<double>(&spongeCylAxisZ), "Z-location of cylindrical sponge center")
	    ("SPONGE.RMIN",		 po::value<double>(&rMin), "Minimum radius of cylinder sponge")
	    ("DOMAIN.NX",                po::value<int>(&Nx), "Number of points in X-Direction")
	    ("DOMAIN.NY",                po::value<int>(&Ny), "Number of points in Y-Direction")
	    ("DOMAIN.NZ",                po::value<int>(&Nz), "Number of points in Z-Direction")
	    ("PENCILDECOMP.PROW",        po::value<int>(&pRow), "Number of Rows in the decomposition")
	    ("PENCILDECOMP.PCOL",        po::value<int>(&pCol), "Number of Cols in the decomposition")
	    ("SOLVER.ALPHAF", 		 po::value<double>(&alphaF), "Filter alpha coefficient value")
	    ("SOLVER.MU_REF", 		 po::value<double>(&mu_ref), "Reference Viscosity")
	    ("SOLVER.USETIMING",         po::value<bool>(&useTiming), "Report timing for different code sections")
	    ("SOLVER.XFDTYPE",		 po::value<string>(&xFDType_str), "Finite Differences Type in the X-Direction")
	    ("SOLVER.YFDTYPE",		 po::value<string>(&yFDType_str), "Finite Differences Type in the Y-Direction")
	    ("SOLVER.ZFDTYPE",		 po::value<string>(&zFDType_str), "Finite Differences Type in the Z-Direction")
	    ("SOLVER.FILTERTYPE",	 po::value<string>(&filterType_str), "Filter type for all three directions")
	    ("SOLVER.RKTYPE",		 po::value<string>(&rkType_str), "Type of Runge-Kutta time stepping")
	    ("RESTART.FROMRESTART", 	 po::value<bool>(&fromRestart), "Do we start the simulation from a restart")
	    ("RESTART.ONLYGRIDFROMRESTART", 	 po::value<bool>(&onlyGridFromRestart), "Only pull the grid from the restart file")
	    ("RESTART.FILENAME",	 po::value<string>(&filename), "Filename of the restart file")
	    ("RESTART.SPONGEAVGFROMRESTART", 	 po::value<bool>(&spongeFromRestart), "Pull the sponge average from file")
	    ("RESTART.SPONGEAVGFILENAME",	 po::value<string>(&sponge_filename), "Filename of the sponge-avg file")
	    ("STATS.VELOCITYSTATS",	 po::value<bool>(&velocityStats), "Flag for recording velocity statistics")
	    ("STATS.THERMOSTATS",	 po::value<bool>(&thermoStats), "Flag for recording thermodynamic quantity statistics")
	    ("STATS.VELOCITYSTATSFROMRESTART",	 po::value<bool>(&velocityStatsFromRestart), "Flag for using restart in velocity stats")
	    ("STATS.THERMOSTATSFROMRESTART",	 po::value<bool>(&thermoStatsFromRestart), "Flag for using restart in thermo statistics")
	    ("STATS.VELOCITYSTATSFILENAME",	 po::value<string>(&velocityStatsFilename), "Velocity statistics restart filename")
	    ("STATS.THERMOSTATSFILENAME",	 po::value<string>(&thermoStatsFilename), "Thermo statistics restart filename")
	    ("STATS.STATSAVGTYPE",	 po::value<string>(&statsAvgType_str), "Name of spacial averaging type for statistics")
	    ("STATS.INTERVAL",	 po::value<int>(&stats_interval), "How often (in terms of timesteps) do we update the statistics data")
	    ("LES.LESMODEL",	 po::value<string>(&lesModel_str), "Name of the LES model")
	    ("LES.LESAVERAGING", po::value<string>(&musgsAvgType_str), "Averaging of mu_sgs type")
	    ("LES.DSMTESTFILTER", po::value<int>(&testFilterType), "Type of test filter used in dynamic coefficient calculation")
	    ("LES.DSMUSETAUKK", po::value<bool>(&useTaukk), "Whether or not to use taukk component in pressure calculation")
	    ("LES.DSMCOEFFRANGE", po::value<bool>(&dumpCoeffRange), "Whether or not to dump the Smag. Coeff, CI, and Prt.");

	    //Potentially include the style of computation options from CurvilinearCsolver? OCC vs. VANILLA etc.

	po::store(po::parse_config_file(input_file, input), vm);
	po::notify(vm);

	//Running through all of the input parameters

	cout << " ============== " << endl;
	cout << " =INPUT PARAMS= " << endl;
	cout << " ============== " << endl;

	//Should also probably check that these are positive

	//MISSING A THING FOR WHAT KIND OF TIMESTEPPING IT IS (CONST CFL OR DT)
	parseTSTypeFromString("TIMESTEPPING.TSTYPE", TSType_str, TSType);
	if(TSType == CONST_DT){
	    forceValue<double>("TIMESTEPPING.DT", "DT", dt);
	    CFL = -1.0;
	}else if(TSType == CONST_CFL){
	    checkValue<double>("TIMESTEPPING.CFL", "CFL", CFL, 0.5);
	    dt = -1.0;
	}

	checkValue<double>("TIMESTEPPING.MAXTIME", "maxTime", maxTime, 1000);
	checkValue<int>(   "TIMESTEPPING.MAXTIMESTEP", "maxTimeStep", maxTimeStep, 10000);
	checkValue<int>(   "TIMESTEPPING.FILTERSTEP", "filterStep", filterStep, 1);
	checkValue<int>(   "TIMESTEPPING.CHECKSTEP", "checkStep", checkStep, 1);
	checkValue<int>(   "TIMESTEPPING.DUMPSTEP", "dumpStep", dumpStep, 1000);
	checkValue<bool>(   "TIMESTEPPING.SUBSTEPFILTERING", "subStepFiltering", subStepFiltering, false);

	//As of right now we're restricted to Nx|Ny|Nz > 10...
	if(Nx <= 10 || Ny <= 10 || Nz <= 10){
	    cout << " > Number of grid points in any direction must be greater than 10! " << endl;
	    MPI_Abort(MPI_COMM_WORLD, -10);
	}

	checkValue<int>("PENCILDECOMP.PROW", "pRow", pRow, 0);
	checkValue<int>("PENCILDECOMP.PCOL", "pCol", pCol, 0);

	checkValue<double>("SOLVER.ALPHAF", "alphaF", alphaF, 0.45);
	forceValue<double>("SOLVER.MU_REF", "mu_ref", mu_ref);
	checkValue<bool>("SOLVER.USETIMING", "useTiming", useTiming, false);
	parseFDTypeFromString("SOLVER.XFDTYPE", xFDType_str, xFDType);
	parseFDTypeFromString("SOLVER.YFDTYPE", yFDType_str, yFDType);
	parseFDTypeFromString("SOLVER.ZFDTYPE", zFDType_str, zFDType);
	parseFilterTypeFromString("SOLVER.FILTERTYPE", filterType_str, filterType);
	parseRKTypeFromString("SOLVER.RKTYPE", rkType_str, rkType);

	//Parse from string...
	parseBCTypeFromString("BC.BCXTYPE", bcXType_str, bcXType);
	parseBCTypeFromString("BC.BCYTYPE", bcYType_str, bcYType);
	parseBCTypeFromString("BC.BCZTYPE", bcZType_str, bcZType);
	parseBCKindFromString("BC.BCX0", bcX0_str, bcX0);
	parseBCKindFromString("BC.BCX1", bcX1_str, bcX1);
	parseBCKindFromString("BC.BCY0", bcY0_str, bcY0);
	parseBCKindFromString("BC.BCY1", bcY1_str, bcY1);
	parseBCKindFromString("BC.BCZ0", bcZ0_str, bcZ0);
	parseBCKindFromString("BC.BCZ1", bcZ1_str, bcZ1);

	//PERIODIC BC DISPLACEMENTS...
	if(bcX0 == PERIODIC && bcX1 == PERIODIC){
	    forceValue<double>("BC.PERIODICDISPX_X", "x0 to x1 periodic displacement in x", periodicDisp[0][0]);
	    forceValue<double>("BC.PERIODICDISPX_Y", "x0 to x1 periodic displacement in y", periodicDisp[0][1]);
	    forceValue<double>("BC.PERIODICDISPX_Z", "x0 to x1 periodic displacement in z", periodicDisp[0][2]);
	}else if((bcX0 == PERIODIC && bcX1 != PERIODIC) || (bcX0 != PERIODIC && bcX1 == PERIODIC)){
	    cout << " > " << " BC Mismatch for X0-X1 Faces! " << endl;
	    MPI_Abort(MPI_COMM_WORLD, -10);
	}else{
	    periodicDisp[0][0] = 0.0;
	    periodicDisp[0][1] = 0.0;
	    periodicDisp[0][2] = 0.0;
	}

	if(bcY0 == PERIODIC && bcY1 == PERIODIC){
	    forceValue<double>("BC.PERIODICDISPY_X", "y0 to y1 periodic displacement in x", periodicDisp[1][0]);
	    forceValue<double>("BC.PERIODICDISPY_Y", "y0 to y1 periodic displacement in y", periodicDisp[1][1]);
	    forceValue<double>("BC.PERIODICDISPY_Z", "y0 to y1 periodic displacement in z", periodicDisp[1][2]);
	}else if((bcY0 == PERIODIC && bcY1 != PERIODIC) || (bcY0 != PERIODIC && bcY1 == PERIODIC)){
	    cout << " > " << " BC Mismatch for Y0-Y1 Faces! " << endl;
	    MPI_Abort(MPI_COMM_WORLD, -10);
	}else{
	    periodicDisp[1][0] = 0.0;
	    periodicDisp[1][1] = 0.0;
	    periodicDisp[1][2] = 0.0;
	}

	if(bcZ0 == PERIODIC && bcZ1 == PERIODIC){
	    forceValue<double>("BC.PERIODICDISPZ_X", "z0 to z1 periodic displacement in x", periodicDisp[2][0]);
	    forceValue<double>("BC.PERIODICDISPZ_Y", "z0 to z1 periodic displacement in y", periodicDisp[2][1]);
	    forceValue<double>("BC.PERIODICDISPZ_Z", "z0 to z1 periodic displacement in z", periodicDisp[2][2]);
	}else if((bcZ0 == PERIODIC && bcZ1 != PERIODIC) || (bcZ0 != PERIODIC && bcZ1 == PERIODIC)){
	    cout << " > " << " BC Mismatch for Z0-Z1 Faces! " << endl;
	    MPI_Abort(MPI_COMM_WORLD, -10);
	}else{
	    periodicDisp[2][0] = 0.0;
	    periodicDisp[2][1] = 0.0;
	    periodicDisp[2][2] = 0.0;
	}

	//Do boundary condition validation to make sure things are peachy...
	bcValidation();

	//Lets collect and parse all of the sponge stuff...
	if(bcX0 == SPONGE || \
	   bcX1 == SPONGE || \
	   bcY0 == SPONGE || \
	   bcY1 == SPONGE || \
	   bcZ0 == SPONGE || \
	   bcZ1 == SPONGE){

	    parseSpongeFromString("SPONGE.KIND", spongeKind_str, spongeKind);
	    checkValue<double>("SPONGE.AVGTIME",  "spongeAvgT", spongeAvgT, 1.0);
	    checkValue<double>("SPONGE.PINF",     "spongeP"   , spongeP, 1.0/1.4);
	    checkValue<double>("SPONGE.STRENGTH", "spongeStrength", spongeStrength, 1.0);

	    if(spongeKind == RECTILINEAR){

		if(bcX0 == SPONGE){
	    	    forceValue<double>("SPONGE.RECTX0PERC", "spongeRectX0Perc", spongeRectX0Perc);
		}

		if(bcX1 == SPONGE){
	    	    forceValue<double>("SPONGE.RECTX1PERC", "spongeRectX1Perc", spongeRectX1Perc);
		}

		if(bcY0 == SPONGE){
	    	    forceValue<double>("SPONGE.RECTY0PERC", "spongeRectY0Perc", spongeRectY0Perc);
		}

		if(bcY1 == SPONGE){
	    	    forceValue<double>("SPONGE.RECTY1PERC", "spongeRectY1Perc", spongeRectY1Perc);
		}

		if(bcZ0 == SPONGE){
	    	    forceValue<double>("SPONGE.RECTZ0PERC", "spongeRectZ0Perc", spongeRectZ0Perc);
		}

		if(bcZ1 == SPONGE){
	    	    forceValue<double>("SPONGE.RECTZ1PERC", "spongeRectZ1Perc", spongeRectZ1Perc);
		}

	    }else if(spongeKind == CYLINDRICAL){
		forceValue<int>("SPONGE.CYLAXIS_ORIENT", "spongeCylAxisOrient", spongeCylAxisOrient);
                if(spongeCylAxisOrient < 0 || spongeCylAxisOrient > 2){
                    cout << " > Sponge cylinder orientation invalid number, needs to be 0, 1, 2. value = " << spongeCylAxisOrient << endl;
                    MPI_Abort(MPI_COMM_WORLD, -10);
                }

		forceValue<double>("SPONGE.CYLAXIS_X", "spongeCylAxisX", spongeCylAxisX);
		forceValue<double>("SPONGE.CYLAXIS_Y", "spongeCylAxisY", spongeCylAxisY);
		forceValue<double>("SPONGE.CYLAXIS_Z", "spongeCylAxisZ", spongeCylAxisZ);
		forceValue<double>("SPONGE.RMIN", "rMin", rMin);
	    }

	}

	//Restart file stuff
	forceValue<bool>("RESTART.FROMRESTART", "fromRestart", fromRestart);
	forceValue<bool>("RESTART.ONLYGRIDFROMRESTART", "onlyGridFromRestart", onlyGridFromRestart);

	if(fromRestart || onlyGridFromRestart){
	    forceValue<string>("RESTART.FILENAME", "restart filename", filename);
	}

	if(fromRestart && onlyGridFromRestart){
	    cout << "Both 'only use grid from restart file' and 'from restart file' checked off, these are conflicting!" << endl;
	    MPI_Abort(MPI_COMM_WORLD, -10);
	}

	//Need the dimensions of the grid if not from restart frile
	if(!(fromRestart || onlyGridFromRestart)){
	    forceValue<int>("DOMAIN.NX", "Nx", Nx);
	    forceValue<int>("DOMAIN.NY", "Ny", Ny);
	    forceValue<int>("DOMAIN.NZ", "Nz", Nz);
	}

	forceValue<bool>("RESTART.SPONGEAVGFROMRESTART", "spongeFromRestart", spongeFromRestart);
	if(spongeFromRestart){
	    forceValue<string>("RESTART.SPONGEAVGFILENAME", "sponge_filename", sponge_filename);
	}

	//Now lets do all of the statistics stuff
	checkValue<bool>("STATS.VELOCITYSTATS", "velocityStats", velocityStats, false);

	if(velocityStats){
	    forceValue<bool>("STATS.VELOCITYSTATSFROMRESTART", "velocityStatsFromRestart", velocityStatsFromRestart);
	    if(velocityStatsFromRestart){
		forceValue<string>("STATS.VELOCITYSTATSFILENAME", "velocityStatsFilename", velocityStatsFilename);
	    }
	}

	checkValue<bool>("STATS.THERMOSTATS",   "thermoStats",   thermoStats,   false);
	if(thermoStats){
	    forceValue<bool>("STATS.THERMOSTATSFROMRESTART", "thermoStatsFromRestart", thermoStatsFromRestart);
	    if(thermoStatsFromRestart){
		forceValue<string>("STATS.THERMOSTATSFILENAME", "thermoStatsFilename", thermoStatsFilename);
	    }

	}

	//Do the averaging type check here
	//parseStatsAvgType()...
	parseStatsAvgTypeString("STATS.STATSAVGTYPE", statsAvgType_str, statsAvgType);

	//Is the restart redundant?
	checkValue<int>("STATS.INTERVAL", "stats_interval", stats_interval, 5);

	//LES model stuff
	parseLESModelString("LES.LESMODEL", lesModel_str, lesModel);
	parseStatsAvgTypeString("LES.LESAVERAGING", musgsAvgType_str, musgsAvgType);

	checkValue<int>("LES.DSMTESTFILTER", "testFilterType", testFilterType, 1);
	checkValue<bool>("LES.DSMUSETAUKK", "useTaukk", useTaukk, true);
	checkValue<bool>("LES.DSMCOEFFRANGE", "dumpCoeffRange", dumpCoeffRange, false);

      }

      //Now we'll have to broadcast all of that stuff out to the other ranks...

      //Probably not the most efficient way of doing this but definitely the laziest

      //[TIMESTEPPING]
      MPI_Bcast(&TSType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&CFL, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&dt,  1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&maxTime, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&maxTimeStep, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&filterStep, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&checkStep, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&dumpStep, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&subStepFiltering, 1, MPI_INT, root, MPI_COMM_WORLD);

      //[BC]
      MPI_Bcast(&bcXType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcYType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcZType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcX0, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcX1, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcY0, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcY1, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcZ0, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&bcZ1, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(periodicDisp, 9, MPI_DOUBLE, root, MPI_COMM_WORLD);

      //[SPONGE]
      MPI_Bcast(&spongeKind, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeP, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeAvgT, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeStrength, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeRectX0Perc, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeRectX1Perc, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeRectY0Perc, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeRectY1Perc, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeRectZ0Perc, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeRectZ1Perc, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeCylAxisOrient, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeCylAxisX, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeCylAxisY, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeCylAxisZ, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&rMin, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);

      //[DOMAIN]
      MPI_Bcast(&Nx, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&Ny, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&Nz, 1, MPI_INT, root, MPI_COMM_WORLD);

      //[PENCILDECOMP]
      MPI_Bcast(&pRow, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&pCol, 1, MPI_INT, root, MPI_COMM_WORLD);

      //[SOLVER]
      MPI_Bcast(&alphaF, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&mu_ref, 1, MPI_DOUBLE, root, MPI_COMM_WORLD);
      MPI_Bcast(&useTiming, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);
      MPI_Bcast(&xFDType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&yFDType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&zFDType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&filterType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&rkType, 1, MPI_INT, root, MPI_COMM_WORLD);

      //[RESTART]
      MPI_Bcast(&fromRestart, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);
      MPI_Bcast(&onlyGridFromRestart, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);
      MPI_Bcast(&spongeFromRestart, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);

      //Restart file name
      int stringSize;
      if(mpiRank == root){
          stringSize = filename.size()+1;
      }
      MPI_Bcast(&stringSize, 1, MPI_INT, root, MPI_COMM_WORLD);

      char filename_c[stringSize];
      if(mpiRank == root){
          strcpy(filename_c,filename.c_str());
      }

      MPI_Bcast(filename_c, stringSize, MPI_CHAR, root, MPI_COMM_WORLD);
      filename.assign(filename_c, stringSize);

      //Sponge Average Restart File name
      if(mpiRank == root){
          stringSize = sponge_filename.size()+1;
      }
      MPI_Bcast(&stringSize, 1, MPI_INT, root, MPI_COMM_WORLD);

      char sfilename_c[stringSize];
      if(mpiRank == root){
          strcpy(sfilename_c,sponge_filename.c_str());
      }

      MPI_Bcast(sfilename_c, stringSize, MPI_CHAR, root, MPI_COMM_WORLD);
      sponge_filename.assign(sfilename_c, stringSize);

      //STATS Stuff

      MPI_Bcast(&velocityStats, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);
      if(velocityStats){

	  //if we're doing velocity stats, see if we need to pull from restartt
	  MPI_Bcast(&velocityStatsFromRestart, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);

	  //If we're pulling from restart, get the filename
	  if(velocityStatsFromRestart){

	      if(mpiRank == root){
          	  stringSize = velocityStatsFilename.size()+1;
      	      }
     	      MPI_Bcast(&stringSize, 1, MPI_INT, root, MPI_COMM_WORLD);

      	      char sfilename_c[stringSize];
      	      if(mpiRank == root){
          	  strcpy(sfilename_c,velocityStatsFilename.c_str());
      	      }

      	      MPI_Bcast(sfilename_c, stringSize, MPI_CHAR, root, MPI_COMM_WORLD);
      	      velocityStatsFilename.assign(sfilename_c, stringSize);
	  }
      }

      //Same stuff but for thermodynamic stats
      MPI_Bcast(&thermoStats, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);
      if(thermoStats){

	  //if we're doing thermo stats, see if we need to pull from restartt
	  MPI_Bcast(&thermoStatsFromRestart, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);

	  //If we're pulling from restart, get the filename
	  if(thermoStatsFromRestart){

	      if(mpiRank == root){
          	  stringSize = thermoStatsFilename.size()+1;
      	      }
     	      MPI_Bcast(&stringSize, 1, MPI_INT, root, MPI_COMM_WORLD);

      	      char sfilename_c[stringSize];
      	      if(mpiRank == root){
          	  strcpy(sfilename_c, thermoStatsFilename.c_str());
      	      }

      	      MPI_Bcast(sfilename_c, stringSize, MPI_CHAR, root, MPI_COMM_WORLD);
      	      thermoStatsFilename.assign(sfilename_c, stringSize);
	  }
      }

      //Type of statistical avering type
      MPI_Bcast(&statsAvgType, 1, MPI_INT, root, MPI_COMM_WORLD);

      //Stats interval
      MPI_Bcast(&stats_interval, 1, MPI_INT, root, MPI_COMM_WORLD);

      //LES Model type
      MPI_Bcast(&lesModel, 1, MPI_INT, root, MPI_COMM_WORLD);
      //LES Model Averaging Type
      MPI_Bcast(&musgsAvgType, 1, MPI_INT, root, MPI_COMM_WORLD);

      //LES DSM Options
      MPI_Bcast(&testFilterType, 1, MPI_INT, root, MPI_COMM_WORLD);
      MPI_Bcast(&useTaukk, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);
      MPI_Bcast(&dumpCoeffRange, 1, MPI_C_BOOL, root, MPI_COMM_WORLD);

   }

    template<typename T>
    void checkValue(string vmKey, string informalName, T &inputContainer, T defaultVal){

	if(vm.count(vmKey)){
	    cout << " > " << informalName << " = " << inputContainer << endl;
	}else{
	    cout << " > " << informalName << " not specified, using default value of " << defaultVal << endl;
	    inputContainer = defaultVal;
	}
    }

    template<typename T>
    void forceValue(string vmKey, string informalName, T inputContainer){

	if(vm.count(vmKey)){
	    cout << " > " << informalName << " = " << inputContainer << endl;
	}else{
	    cout << " > " << informalName << " not specified, NEEDS TO BE SPECIFED " << endl;
	    MPI_Abort(MPI_COMM_WORLD, -10);
	}
    }

    void parseLESModelString(string vmKey, string inString, LESModel &currentType);
    void parseStatsAvgTypeString(string vmKey, string inString, StatsAvgType &currentType);
    void parseFDTypeFromString(string vmKey, string inString, FDType &currentType);
    void parseFilterTypeFromString(string vmKey, string inString, FilterType &currentType);
    void parseRKTypeFromString(string vmKey, string inString, RKType &currentType);
    void parseTSTypeFromString(string vmKey, string inString, TimeSteppingType &currentType);
    void parseBCTypeFromString(string vmKey, string inString, BCType &currentType);
    void parseBCKindFromString(string vmKey, string inString, BCKind &currentType);
    void bcValidation();

    void parseSpongeFromString(string vmKey, string inString, SpongeKind &spongeKind);

};

#endif


/* Copyright (c) 2017, United States Government, as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 *
 * All rights reserved.
 *
 * The Astrobee platform is licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

#include <sparse_mapping/reprojection.h>
#include <camera/camera_model.h>

#include <Eigen/Geometry>
#include <gtest/gtest.h>

#include <vector>

TEST(reprojection, pose_estimation) {
  // create camera model
  Eigen::Vector3d true_camera_pos(0, 0, 0);
  Eigen::Matrix3d true_camera_rotation;
  true_camera_rotation = Eigen::AngleAxisd(0 * M_2_PI, Eigen::Vector3d::UnitX())
                       * Eigen::AngleAxisd(0 * M_2_PI, Eigen::Vector3d::UnitY())
                       * Eigen::AngleAxisd(0 * M_2_PI, Eigen::Vector3d::UnitZ());
  camera::CameraModel camera(true_camera_pos, true_camera_rotation,
                             90 * M_PI / 180.0, 640, 480);

  // create correct landmarks and observations
  std::vector<Eigen::Vector3d> landmarks;
  std::vector<Eigen::Vector2d> observations;
  landmarks.push_back(Eigen::Vector3d(8, 0, 8));
  landmarks.push_back(Eigen::Vector3d(2, 1, 5));
  landmarks.push_back(Eigen::Vector3d(-2, 1, 4));
  landmarks.push_back(Eigen::Vector3d(2, 3, 6));
  landmarks.push_back(Eigen::Vector3d(-2, -3, 7));
  landmarks.push_back(Eigen::Vector3d(1, 0, 4));
  landmarks.push_back(Eigen::Vector3d(0.5, -0.5, 6));
  for (size_t i = 0; i < landmarks.size(); i++) {
    // Purposely introducing rounding error here.
    Eigen::Vector2i obs = (camera.ImageCoordinates(landmarks[i]).array() + 0.5).cast<int>();
    observations.push_back(Eigen::Vector2d(obs[0], obs[1]));
  }

  // start with a nearby guess
  Eigen::Vector3d guess_pos(0.2, -0.3, -1.0);
  Eigen::Matrix3d guess_rot;
  guess_rot = Eigen::AngleAxisd(0.4   * M_2_PI, Eigen::Vector3d::UnitX())
            * Eigen::AngleAxisd(-0.15 * M_2_PI, Eigen::Vector3d::UnitY())
            * Eigen::AngleAxisd(0.2   * M_2_PI, Eigen::Vector3d::UnitZ());
  camera::CameraModel estimate(guess_pos, guess_rot, camera.GetParameters());

  // check that ceres solver converges
  ceres::Solver::Options options;
  options.linear_solver_type = ceres::ITERATIVE_SCHUR;
  options.num_threads = 1;
  options.max_num_iterations = 100;
  options.minimizer_progress_to_stdout = false;
  ceres::Solver::Summary summary;
  // run ceres solver
  sparse_mapping::EstimateCamera(&estimate, &landmarks, observations, options, &summary);

  // confirm solution
  Eigen::Vector3d orig_angle = camera.GetRotation() * Eigen::Vector3d::UnitX();
  Eigen::Vector3d observed_angle = estimate.GetRotation() * Eigen::Vector3d::UnitX();
  EXPECT_NEAR((true_camera_pos - estimate.GetPosition()).norm(), 0, 0.1);
  EXPECT_NEAR(acos(observed_angle.dot(orig_angle)), 0, 0.05);

  // add incorrect observations
  landmarks.push_back(Eigen::Vector3d(-3, 5, -8));
  observations.push_back(Eigen::Vector2d(100, 300));
  landmarks.push_back(Eigen::Vector3d(1, -2, -1));
  observations.push_back(Eigen::Vector2d(400, 100));
  // run ransac
  sparse_mapping::RansacEstimateCamera(landmarks, observations, 50, 4, &estimate);

  // check success
  orig_angle = camera.GetRotation() * Eigen::Vector3d::UnitX();
  observed_angle = estimate.GetRotation() * Eigen::Vector3d::UnitX();
  EXPECT_NEAR((true_camera_pos - estimate.GetPosition()).norm(), 0, 0.1)
    << "Estimated position: " << estimate.GetPosition().transpose() << " Expected: "
    << true_camera_pos.transpose();
  EXPECT_NEAR(acos(observed_angle.dot(orig_angle)), 0, 0.05);
}

TEST(reprojection, affine_estimation) {
  // Test solving for affine transform between two datatsets

  // Create datasets with known transform
  Eigen::Matrix3Xd in(3, 100), out(3, 100);
  for (int row = 0; row < in.rows(); row++) {
    for (int col = 0; col < in.cols(); col++) {
      in(row, col) = log(2*row + 10.0)/sqrt(1.0*col + 4.0) + sqrt(col*1.0)/(row + 1.0);
    }
  }

  Eigen::Quaternion<double> Q(1, 3, 5, 2);
  Q.normalize();
  Eigen::Matrix3d R = Q.toRotationMatrix();
  double scale = 2.0;
  Eigen::Vector3d S;
  S << -5, 6, -27;
  for (int col = 0; col < in.cols(); col++)
    out.col(col) = scale*R*in.col(col) + S;

  Eigen::Affine3d A;
  sparse_mapping::Find3DAffineTransform(in, out, &A);

  // See if we got the transform we expected
  EXPECT_LE((scale*R-A.linear()).cwiseAbs().maxCoeff(), 1e-12);
  EXPECT_LE((S-A.translation()).cwiseAbs().maxCoeff(), 1e-12);
  // Translation vector should be the same
  EXPECT_NEAR(A.translation()[0], S[0], 1e-3);
  EXPECT_NEAR(A.translation()[1], S[1], 1e-3);
  EXPECT_NEAR(A.translation()[2], S[2], 1e-3);
}

// Run all the tests that were declared with TEST()
int main(int argc, char** argv) {
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}


// Implementing the class that is defined in the header file: BarrierOption.hpp
//
// (c) Sudhansh Dua

#include "BarrierOption.hpp"
#include <string>
#include <boost/math/distributions.hpp>
#include <cmath>

using namespace std;
using namespace boost::math;

double BarrierOption::DownAndOutCallBarrier() const
{
	return ::DownAndOutCallBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::DownAndOutPutBarrier() const
{
	return ::DownAndOutPutBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::DownAndInCallBarrier() const
{
	return ::DownAndInCallBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::DownAndInPutBarrier() const
{
	return ::DownAndInPutBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::UpAndOutCallBarrier() const
{
	return ::UpAndOutCallBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::UpAndOutPutBarrier() const
{
	return ::UpAndOutPutBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::UpAndInCallBarrier() const
{
	return ::UpAndInCallBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

double BarrierOption::UpAndInPutBarrier() const
{
	return ::UpAndInPutBarrier(S, H, K, cr, T, r, sig, b, type, InOrOut);
}

void BarrierOption::init()		// Initialising all the default values
{
	//	Default values
	T = 1;
	H = 105;
	cr = 3;
	r = 0.08;
	sig = 0.25;
	K = 100;
	S = 100;			//	Default stock price
	b = r;				//	Black - Scholes(1973) stock option model: b = r (i.e. non-dividend paying stock)

	type = "C";			//	Call option as default
	InOrOut = "In";			//	In Barrier as default
}

void BarrierOption::copy(const BarrierOption& option)
{
	H = option.H;
	T = option.T;
	InOrOut = option.InOrOut;
	cr = option.cr;
	r = option.r;
	sig = option.sig;
	K = option.K;
	b = option.b;
	type = option.type;
	S = option.S;
}

//	Constructors and destructor
//	Default Constructor
BarrierOption::BarrierOption() : Option()
{
	init();
}

//	Copy constructor
BarrierOption::BarrierOption(const BarrierOption& option) : Option(option)
{
	copy(option);
}

//	Constructor that accepts values
BarrierOption::BarrierOption(const double& S1, const double& H1, const double& K1, const double& cr1, const double& T1, const double& r1, const double& sig1,
	const double& b1, const string type1, const string InOrOut1) : Option(), S(S1), H(H1), K(K1), cr(cr1), T(T1), r(r1), sig(sig1), b(b1), type(type1), InOrOut(InOrOut1) {}

//	Destructor
BarrierOption::~BarrierOption() {}

//	Assignment Operator
BarrierOption& BarrierOption::operator = (const BarrierOption& option)
{
	if (this == &option)
	{
		return *this;		//	Self-assignment check!
	}
	Option::operator = (option);
	copy(option);
	return *this;
}

// Functions that calculate the option price
double BarrierOption::Price() const
{
	if (S >= H)
	{ 							// Down Barrier

		if (InOrOut == "In")						// Down and in barrier
		{

			if (type == "C")
			{												// Down and in call barrier
				return DownAndInCallBarrier();
			}
			else
			{												// Down and in put barrier
				return DownAndInPutBarrier();
			}
		}
		else
		{								// Down and out barrier
			if (type == "C")
			{												// Down and out call barrier
				return DownAndOutCallBarrier();
			}
			else
			{												// Down and out put barrier
				return DownAndOutPutBarrier();
			}
		}
	}

	else
	{							// Up Barrier

		if (InOrOut == "In")
		{								// Up and in barrier

			if (type == "C")
			{												// Up and in call barrier
				return UpAndInCallBarrier();
			}
			else
			{												// Up and in put barrier
				return UpAndInPutBarrier();
			}
		}
		else
		{								// Up and out barrier
			if (type == "C")
			{												// Up and out call barrier
				return UpAndOutCallBarrier();
			}
			else
			{												// Up and out put barrier
				return UpAndOutPutBarrier();
			}
		}
	}
}

// Modifier functions
void BarrierOption::toggle()			//	Change the option type
{
	type = ((type == "C") ? "P" : "C");
}

// Global Functions

double DownAndOutCallBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = 1;
	double phi = 1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = (phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1)) - (phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T)))));
	double B = (phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2)) - (phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T)))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return A - C + F;
	}
	else
		return B - D + F;
}

double DownAndOutPutBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = 1;
	double phi = -1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return A - B + C - D + F;
	}
	else
		return F;
}

double DownAndInCallBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = 1;
	double phi = 1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return C + E;
	}
	else
		return A - B + D + E;

}

double DownAndInPutBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = 1;
	double phi = -1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return B - C + D + E;
	}
	else
		return A + E;
}

double UpAndOutCallBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = -1;
	double phi = 1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return F;
	}
	else
		return A - B + C - D + F;

}

double UpAndOutPutBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = -1;
	double phi = -1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return B - D + F;
	}
	else
		return A - C + F;
}

double UpAndInCallBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = -1;
	double phi = 1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return A + E;
	}
	else
		return B - C + D + E;

}

double UpAndInPutBarrier(const double S, const double H, const double K, const double cr, const double T, const double r, const double sig, const double b, const string type, const string InOrOut)
{
	double ita = -1;
	double phi = -1;

	double mu = (b - (sig * sig * 0.5)) / (sig * sig);
	double psi = sqrt((mu * mu) + (2 * r / (sig * sig)));
	double x1 = (log(S / K) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double x2 = (log(S / H) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y1 = (log(H * H / (S * K)) / (sig * sqrt(T))) + ((1 + mu) * sig * sqrt(T));
	double y2 = (log(H / S)) / (sig * sqrt(T)) + ((1 + mu) * sig * sqrt(T));
	double z = (log(H / S) / (sig * sqrt(T))) + (psi * sig * sqrt(T));

	normal_distribution<> standard_normal(0.0, 1.0);
	double A = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x1) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x1 - (sig * sqrt(T))));
	double B = phi * S * exp((b - r) * T) * cdf(standard_normal, phi * x2) - phi * K * exp(-r * T) * cdf(standard_normal, phi * (x2 - (sig * sqrt(T))));
	double C = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y1)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y1 - (sig * sqrt(T))))));
	double D = (phi * S * pow(H / S, 2 * (mu + 1)) * exp((b - r) * T) * cdf(standard_normal, ita * y2)) - (phi * K * exp(-r * T) * (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T))))));
	double E = (cr * exp(-r * T) * ((cdf(standard_normal, ita * (x2 - (sig * sqrt(T))))) - (pow(H / S, 2 * mu) * cdf(standard_normal, ita * (y2 - (sig * sqrt(T)))))));
	double F = (cr * ((pow(H / S, mu + psi) * cdf(standard_normal, ita * z)) + (pow(H / S, mu - psi) * cdf(standard_normal, ita * (z - (2 * psi * sig * sqrt(T)))))));

	if (K > H)
	{
		return A - B + D + E;
	}
	else
		return C + E;
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// Unit Test

// Copyright (c) 2010-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2021.
// Modifications copyright (c) 2021, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is 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)

#define BOOST_GEOMETRY_DEFINE_STREAM_OPERATOR_SEGMENT_RATIO

#include <fstream>
#include <iostream>
#include <iomanip>

#include <geometry_test_common.hpp>

#include <boost/geometry/algorithms/correct.hpp>
#include <boost/geometry/algorithms/detail/overlay/debug_turn_info.hpp>
#include <boost/geometry/algorithms/detail/overlay/overlay.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/io/wkt/wkt.hpp>

#if defined(TEST_WITH_SVG)
#  include <boost/geometry/io/svg/svg_mapper.hpp>
#endif

#include <boost/geometry/policies/robustness/get_rescale_policy.hpp>

#include <algorithms/overlay/overlay_cases.hpp>

template <typename Geometry>
using rev = bg::util::bool_constant<bg::point_order<Geometry>::value == bg::counterclockwise>;

template <typename Geometry1, typename Geometry2>
inline typename bg::coordinate_type<Geometry1>::type
intersect(Geometry1 const& g1, Geometry2 const& g2, std::string const& name,
          bg::detail::overlay::operation_type op)
{
    boost::ignore_unused(name);

    typedef typename bg::strategy::side::services::default_strategy
    <
        typename bg::cs_tag<Geometry1>::type
    >::type side_strategy_type;

    typedef typename bg::point_type<Geometry1>::type point_type;

    typedef typename bg::rescale_policy_type<point_type>::type
        rescale_policy_type;

    rescale_policy_type rescale_policy
            = bg::get_rescale_policy<rescale_policy_type>(g1, g2);

    typedef bg::detail::overlay::traversal_turn_info
    <
        point_type,
        typename bg::detail::segment_ratio_type<point_type, rescale_policy_type>::type
    > turn_info;
    std::vector<turn_info> turns;

    bg::detail::get_turns::no_interrupt_policy policy;
    bg::get_turns
        <
            rev<Geometry1>::value,
            rev<Geometry2>::value,
            bg::detail::overlay::assign_null_policy
        >(g1, g2, rescale_policy, turns, policy);

    bg::enrich_intersection_points
            <
                rev<Geometry1>::value, rev<Geometry2>::value,
                bg::overlay_intersection
            >(turns, bg::detail::overlay::operation_intersection,
        g1, g2, rescale_policy, side_strategy_type());

    typedef bg::model::ring<typename bg::point_type<Geometry1>::type> ring_type;
    typedef std::deque<ring_type> out_vector;
    out_vector v;

    bg::detail::overlay::traverse
        <
            rev<Geometry1>::value, rev<Geometry2>::value,
            Geometry1, Geometry2
        >::apply(g1, g2, op, rescale_policy, turns, v);

    typename bg::coordinate_type<Geometry1>::type result = 0.0;
    for (ring_type& ring : v)
    {
        result += bg::area(ring);
    }

#if defined(TEST_WITH_SVG)
    {
        std::ostringstream filename;
        filename
            << name << "_"
            << (op == bg::detail::overlay::operation_intersection ? "i" : "u")
            << "_" << (rev<Geometry1>::value ? "ccw" : "cw")
            << "_" << (rev<Geometry2>::value ? "ccw" : "cw")
            << ".svg";

        std::ofstream svg(filename.str().c_str());

        bg::svg_mapper<typename bg::point_type<Geometry1>::type> mapper(svg, 500, 500);
        mapper.add(g1);
        mapper.add(g2);

        // Input shapes in green/blue
        mapper.map(g1, "fill-opacity:0.5;fill:rgb(153,204,0);"
                "stroke:rgb(153,204,0);stroke-width:3");
        mapper.map(g2, "fill-opacity:0.3;fill:rgb(51,51,153);"
                "stroke:rgb(51,51,153);stroke-width:3");

        // Traversal rings in magenta/light yellow fill
        for  (ring_type const& ring : v)
        {
            mapper.map(ring, "fill-opacity:0.3;stroke-opacity:0.4;fill:rgb(255,255,0);"
                    "stroke:rgb(255,0,255);stroke-width:8");
        }

        // turn points in orange, + enrichment/traversal info

        // Simple map to avoid two texts at same place (note that can still overlap!)
        std::map<std::pair<int, int>, int> offsets;
        int index = 0;
        int const lineheight = 10;
        int const margin = 5;

        for (turn_info const& turn : turns)
        {
            mapper.map(turn.point, "fill:rgb(255,128,0);"
                    "stroke:rgb(0,0,0);stroke-width:1", 3);

            {
                // Map characteristics
                // Create a rounded off point
                std::pair<int, int> p
                    = std::make_pair(
                        boost::numeric_cast<int>(0.5 + 10 * bg::get<0>(turn.point)),
                        boost::numeric_cast<int>(0.5 + 10 * bg::get<1>(turn.point))
                        );
                std::string style =  "fill:rgb(0,0,0);font-family:Arial;font-size:10px";

                std::ostringstream out;
                out << index
                    << ": " << bg::method_char(turn.method)
                    << std::endl
                    << "op: " << bg::operation_char(turn.operations[0].operation)
                    << " / " << bg::operation_char(turn.operations[1].operation)
                    << (turn.is_discarded() ? " (discarded) " : turn.blocked() ? " (blocked)" : "")
                    << std::endl;

                if (turn.operations[0].enriched.next_ip_index != -1)
                {
                    out << "ip: " << turn.operations[0].enriched.next_ip_index;
                }
                else
                {
                    out << "vx: " << turn.operations[0].enriched.travels_to_vertex_index;
                }
                out << " ";
                if (turn.operations[1].enriched.next_ip_index != -1)
                {
                    out << "ip: " << turn.operations[1].enriched.next_ip_index;
                }
                else
                {
                    out << "vx: " << turn.operations[1].enriched.travels_to_vertex_index;
                }

                out << std::endl;

                out

                    << std::setprecision(3)
                    << "dist: " << turn.operations[0].fraction
                    << " / "  << turn.operations[1].fraction
                    << std::endl;

                offsets[p] += lineheight;
                int offset = offsets[p];
                offsets[p] += lineheight * 5;
                mapper.text(turn.point, out.str(), style, margin, offset, lineheight);
            }
            index++;
        }
    }
#endif

    return result;
}

template <typename Geometry1, typename Geometry2>
inline typename bg::coordinate_type<Geometry1>::type intersect(std::string const& wkt1, std::string const& wkt2, std::string const& name,
            bg::detail::overlay::operation_type op)
{
    Geometry1 geometry1;
    Geometry2 geometry2;
    bg::read_wkt(wkt1, geometry1);
    bg::read_wkt(wkt2, geometry2);

    // Reverse if necessary: adapt to cw/ccw
    bg::correct(geometry1);
    bg::correct(geometry2);

    return intersect(geometry1, geometry2, name, op);
}

template <typename T>
inline void test_polygon(std::string const& wkt1, std::string const& wkt2, std::string const& name)
{
    typedef bg::model::d2::point_xy<T> point;
    typedef bg::model::polygon<point> clock;
    typedef bg::model::polygon<point, false> counter;

    namespace ov = bg::detail::overlay;
    T area1 = intersect<clock, clock>(wkt1, wkt2, name, ov::operation_intersection);
    T area2 = intersect<counter, counter>(wkt1, wkt2, name, ov::operation_intersection);
    T area3 = intersect<clock, counter>(wkt1, wkt2, name, ov::operation_intersection);
    T area4 = intersect<counter, clock>(wkt1, wkt2, name, ov::operation_intersection);
    BOOST_CHECK_CLOSE(area1, area2, 0.001);
    BOOST_CHECK_CLOSE(area3, area4, 0.001);
    BOOST_CHECK_CLOSE(area1, area3, 0.001);
    BOOST_CHECK_CLOSE(area2, area4, 0.001);

    area1 = intersect<clock, clock>(wkt1, wkt2, name, ov::operation_union);
    area2 = intersect<counter, counter>(wkt1, wkt2, name, ov::operation_union);
    area3 = intersect<clock, counter>(wkt1, wkt2, name, ov::operation_union);
    area4 = intersect<counter, clock>(wkt1, wkt2, name, ov::operation_union);
    BOOST_CHECK_CLOSE(area1, area2, 0.001);
    BOOST_CHECK_CLOSE(area3, area4, 0.001);
    BOOST_CHECK_CLOSE(area1, area3, 0.001);
    BOOST_CHECK_CLOSE(area2, area4, 0.001);
}

template <typename T>
inline void test_box_polygon(std::string const& wkt1, std::string const& wkt2, std::string const& name)
{
    typedef bg::model::d2::point_xy<T> point;
    typedef bg::model::box<point> box;
    typedef bg::model::polygon<point> clock;
    typedef bg::model::polygon<point, false> counter;

    namespace ov = bg::detail::overlay;
    T area1 = intersect<box, clock>(wkt1, wkt2, name + "_bp", ov::operation_intersection);
    T area2 = intersect<box, counter>(wkt1, wkt2, name + "_bp", ov::operation_intersection);
    T area3 = intersect<clock, box>(wkt2, wkt1, name + "_pb", ov::operation_intersection);
    T area4 = intersect<counter, box>(wkt2, wkt1, name + "_pb", ov::operation_intersection);
    BOOST_CHECK_CLOSE(area1, area2, 0.001);
    BOOST_CHECK_CLOSE(area3, area4, 0.001);
    BOOST_CHECK_CLOSE(area1, area3, 0.001);
    BOOST_CHECK_CLOSE(area2, area4, 0.001);

    area1 = intersect<box, clock>(wkt1, wkt2, name + "_bp", ov::operation_union);
    area2 = intersect<box, counter>(wkt1, wkt2, name + "_bp", ov::operation_union);
    area3 = intersect<clock, box>(wkt2, wkt1, name + "_pb", ov::operation_union);
    area4 = intersect<counter, box>(wkt2, wkt1, name + "_pb", ov::operation_union);
    BOOST_CHECK_CLOSE(area1, area2, 0.001);
    BOOST_CHECK_CLOSE(area3, area4, 0.001);
    BOOST_CHECK_CLOSE(area1, area3, 0.001);
    BOOST_CHECK_CLOSE(area2, area4, 0.001);
}

int test_main(int, char* [])
{
    //bool const ig = true;
    test_polygon<double>(case_1[0], case_1[1], "c1");
    test_polygon<double>(case_2[0], case_2[1], "c2");
    test_polygon<double>(case_3[0], case_3[1], "c3");
    test_polygon<double>(case_4[0], case_4[1], "c4");
    test_polygon<double>(case_5[0], case_5[1], "c5");
    test_polygon<double>(case_6[0], case_6[1], "c6");
    test_polygon<double>(case_7[0], case_7[1], "c7");
    test_polygon<double>(case_8[0], case_8[1], "c8");
    test_polygon<double>(case_9[0], case_9[1], "c9" /*, ig */);

    test_polygon<double>(case_10[0], case_10[1], "c10");
    test_polygon<double>(case_11[0], case_11[1], "c11");
    test_polygon<double>(case_12[0], case_12[1], "c12");
    test_polygon<double>(case_13[0], case_13[1], "c13");
    test_polygon<double>(case_14[0], case_14[1], "c14");
    test_polygon<double>(case_15[0], case_15[1], "c15");
    test_polygon<double>(case_16[0], case_16[1], "c16");
    test_polygon<double>(case_17[0], case_17[1], "c17");
    test_polygon<double>(case_18[0], case_18[1], "c18");
    test_polygon<double>(case_19[0], case_19[1], "c19");
    test_polygon<double>(case_20[0], case_20[1], "c20");
    test_polygon<double>(case_21[0], case_21[1], "c21");
    test_polygon<double>(case_22[0], case_22[1], "c22" /*, ig */);
    test_polygon<double>(case_23[0], case_23[1], "c23");
    test_polygon<double>(case_24[0], case_24[1], "c24");
    test_polygon<double>(case_25[0], case_25[1], "c25" /*, ig */);
    test_polygon<double>(case_26[0], case_26[1], "c26" /*, ig */);
    test_polygon<double>(case_27[0], case_27[1], "c27");
    test_polygon<double>(case_28[0], case_28[1], "c28");
    test_polygon<double>(case_29[0], case_29[1], "c29");
    test_polygon<double>(case_30[0], case_30[1], "c30");
    test_polygon<double>(case_31[0], case_31[1], "c31" /*, ig */);
    test_polygon<double>(case_32[0], case_32[1], "c32" /*, ig */);
    test_polygon<double>(case_33[0], case_33[1], "c33" /*, ig */);
    test_polygon<double>(case_34[0], case_34[1], "c34");
    test_polygon<double>(case_35[0], case_35[1], "c35");
    test_polygon<double>(case_36[0], case_36[1], "c36" /*, ig */);
    test_polygon<double>(case_37[0], case_37[1], "c37" /*, ig */);
    test_polygon<double>(case_38[0], case_38[1], "c38" /*, ig */);
    test_polygon<double>(case_39[0], case_39[1], "c39");
    test_polygon<double>(case_40[0], case_40[1], "c40" /*, ig */);
    test_polygon<double>(case_41[0], case_41[1], "c41");
    test_polygon<double>(case_42[0], case_42[1], "c42");
    //test_polygon<double>(case_43[0], case_43[1], "c43", inv);
    test_polygon<double>(case_44[0], case_44[1], "c44");
    test_polygon<double>(case_45[0], case_45[1], "c45");
    //test_polygon<double>(case_46[0], case_46[1], "c46", inv);
    //test_polygon<double>(case_47[0], case_47[1], "c47" /*, ig */);
    //test_polygon<double>(case_48[0], case_48[1], "c48");
    test_polygon<double>(case_49[0], case_49[1], "c49");
    test_polygon<double>(case_50[0], case_50[1], "c50");
    test_polygon<double>(case_51[0], case_51[1], "c51");
    test_polygon<double>(case_52[0], case_52[1], "c52" /*, ig */);
    test_polygon<double>(case_53[0], case_53[1], "c53");
    // Invalid ones / overlaying intersection points / self tangencies
    //test_polygon<double>(case_54[0], case_54[1], "c54");
    //test_polygon<double>(case_55[0], case_55[1], "c55");
    //test_polygon<double>(case_56[0], case_56[1], "c56");
    //test_polygon<double>(case_57[0], case_57[1], "c57" /*, ig */);
    //test_polygon<double>(case_58[0], case_58[1], "c58");
    //test_polygon<double>(case_59[0], case_59[1], "c59");

    test_polygon<double>(pie_16_4_12[0], pie_16_4_12[1], "pie_16_4_12");
    test_polygon<double>(pie_23_21_12_500[0], pie_23_21_12_500[1], "pie_23_21_12_500");
    test_polygon<double>(pie_23_23_3_2000[0], pie_23_23_3_2000[1], "pie_23_23_3_2000");
    test_polygon<double>(pie_23_16_16[0], pie_23_16_16[1], "pie_23_16_16");
    test_polygon<double>(pie_16_2_15_0[0], pie_16_2_15_0[1], "pie_16_2_15_0");
    test_polygon<double>(pie_4_13_15[0], pie_4_13_15[1], "pie_4_13_15");
    test_polygon<double>(pie_20_20_7_100[0], pie_20_20_7_100[1], "pie_20_20_7_100");

    test_polygon<double>(hv_1[0], hv_1[1], "hv1");
    test_polygon<double>(hv_2[0], hv_2[1], "hv2");
    test_polygon<double>(hv_3[0], hv_3[1], "hv3");
    test_polygon<double>(hv_4[0], hv_4[1], "hv4");
    test_polygon<double>(hv_5[0], hv_5[1], "hv5");
    test_polygon<double>(hv_6[0], hv_6[1], "hv6");
    test_polygon<double>(hv_7[0], hv_7[1], "hv7");
    test_polygon<double>(dz_1[0], dz_1[1], "dz_1");
    test_polygon<double>(dz_2[0], dz_2[1], "dz_2");
    test_polygon<double>(dz_3[0], dz_3[1], "dz_3");

    test_box_polygon<double>("POLYGON((1 1,4 4))", case_1[0], "bp1");

    {
        static std::string example_box = "POLYGON((1.5 1.5, 4.5 2.5))";
        static std::string example_ring =
            "POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3))";
        test_box_polygon<double>(example_box, example_ring, "bp2");
    }

    return 0;
}


#include <fstream>
#include <iostream>
#include <memory>

#include <Eigen/Dense>

#include "datatypes.hpp"
#include "utility.hpp"

std::default_random_engine generator;

template<typename T>
void
writeCsvMatrix (std::ostream &fd,
		const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> &data,
		const Eigen::VectorXi &alloc)
{
  fd << "cluster";
  for (int i = 0; i < data.cols(); i++) {
    fd << ",f" << i+1;
  }
  fd << "\n";
  for (int i = 0; i < data.rows(); i++) {
    fd << "c" << alloc(i)+1;
    for (int j = 0; j < data.cols(); j++) {
      fd << "," << data(i,j);
    }
    fd << "\n";
  }
}

template<typename T> void
checkEqualTemplate (T a, T b, const std::string &msg)
{
  if (a == b)
    return;
  std::cerr
    << "Error: " << msg
    << " (expecting " << a << " == " << b << ")"
    << std::endl;
}

void checkEqual(int a, int b, const std::string &msg) {
  return checkEqualTemplate(a, b, msg); }

template<typename T> void
checkApproxEqual(const T &a, const T &b,
		 const std::string &msg,
		 typename T::Scalar epsilon = 1e-6)
{
  // Eigen::Array<bool, T::RowsAtCompileTime, T::ColsAtCompileTime>
  // em = (a - b).array() <= a.array().abs().min(b.array().abs()) * epsilon;
  if (a.isApprox(b, epsilon))
    return;
  std::cerr
    << "Error: " << msg
    << " (matrix equality check failed at epsilon=" << epsilon << ")"
    << " differences observed: " << std::endl
    << (a-b).array().abs() << std::endl
    << std::endl;
}

void
checkApproxEqual(const double a, const double b,
		 const std::string &msg, double epsilon = 1e-6)
{
  using std::abs;
  using std::min;
  epsilon *= min(abs(a),abs(b));
  if(abs(a-b) <= epsilon)
    return;
  std::cerr
    << "Error: " << msg
    << " (expecting ||" << a << " - " << b << "|| <= " << epsilon << ")"
    << std::endl;
}

template<typename T> void
checkProbItemMassGt(const Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> &prob,
		    int item, T mass, const std::string &msg)
{
  // normalise, nans and infinities will propagate!
  auto probnorm = prob.array() / prob.sum();

  // check all positive
  if ((probnorm < 0).any()) {
    std::cerr
      << "Error: " << msg
      << " (some probabilities are not positive)\n";
  }
  if (probnorm(item) < mass) {
    std::cerr
      << "Error: " << msg
      << " (mass of item " << item
      << " is " << probnorm(item)
      << ", i.e. < " << mass << ")\n";
  }
}

double
nuConditionalAlphaOracle(const Eigen::MatrixXi &alloc)
{
  return alloc.rows();
}

double
phiConditionalAlphaOracle(const Eigen::MatrixXi &alloc,
			  int m, int p)
{
  int sum = 0;
  for (int i = 0; i < alloc.rows(); i++) {
    sum += alloc(i,m) == alloc(i,p);
  }
  return 1 + sum;
}

double
gammaConditionalAlphaOracle(const Eigen::MatrixXi &alloc,
			  int m, int jm)
{
  int sum = 0;
  for (int i = 0; i < alloc.rows(); i++) {
    sum += alloc(i,m) == jm;
  }
  return 1 + sum;
}

/* the following set of functions work by having an @outer function
 * that works "down" through the files, setting $j_i$ to the
 * appropriate value and then calling the next @outer function.  Once
 * $j$ has been set for all $K$ files (i.e. @{i == K}), @outer defers
 * to @inner and the product for the given $j$ is evaluated.
 */

// See Equation 2 in section "A.2 Normalising Constant" of Kirk et.al 2012
double
nuConditionalBetaOracle(const mdisampler &mdi)
{
  const int N = mdi.nclus(), K = mdi.nfiles();

  std::vector<int> j(mdi.nfiles());

  auto inner = [K,&mdi,&j]() {
    long double prod = 1;
    for (int k = 0; k < K; k++) {
      prod *= mdi.weight(j[k], k);
    }
    for (int k = 0; k < K-1; k++) {
      for (int l = k+1; l < K; l++) {
	prod *= 1 + mdi.phi(k, l) * (j[k] == j[l]);
      }
    }
    return prod;
  };
  std::function<long double(int)> outer = [&outer, &inner, &j, N, K](int i) {
    if (i == K)
      return inner();

    long double sum = 0;
    for (j[i] = 0; j[i] < N; j[i]++)
      sum += outer(i+1);
    return sum;
  };
  return outer(0);
}

// See Kirk et.al 2012 $b_\phi$ from "Conditional for $\phi_{mp}$"
double
phiConditionalBetaOracle(const mdisampler &mdi,
			int m, int p)
{
  const int N = mdi.nclus(), K = mdi.nfiles();

  std::vector<int> j(mdi.nfiles());

  auto inner = [K,&mdi,&j,m,p]() {
    long double prod = 1;
    for (int k = 0; k < K; k++) {
      prod *= mdi.weight(j[k], k);
    }
    for (int k = 0; k < K-1; k++) {
      for (int l = k+1; l < K; l++) {
	if (l != p)
	  prod *= 1 + mdi.phi(k, l) * (j[k] == j[l]);
      }
    }
    for (int k = 0; k < p; k++) {
      if (k != m)
	prod *= 1 + mdi.phi(k, p) * (j[k] == j[p]);
    }
    return prod;
  };
  std::function<long double(int)> outer = [&outer,&inner,&j,N,K,m,p](int i) {
    if (i == K)
      return inner();

    // $j_m$ and $j_p$ are set outside this iteration
    if (i == m || i == p)
      return outer(i+1);

    long double sum = 0;
    for (j[i] = 0; j[i] < N; j[i]++)
      sum += outer(i+1);
    return sum;
  };
  // iterate over $\sum_{j_m = j_p = 1}^N$
  long double sum = 0;
  for (int i = 0; i < N; i++) {
    j[m] = j[p] = i;
    sum += outer(0);
  }
  return mdi.nu() * sum;
}

// See Kirk et.al 2012 $b_\gamma$ from "Conditional for $\gamma_{j_m m}$"
double
gammaConditionalBetaOracle (const mdisampler &mdi,
			   const int m, const int jm)
{
  const int N = mdi.nclus(), K = mdi.nfiles();

  std::vector<int> j(mdi.nfiles());
  // within data @m we are interested in cluster @jm
  j[m] = jm;

  auto inner = [K,m,&mdi,&j]() {
    long double prod = 1;
    for (int k = 0; k < K; k++) {
      if(k != m)
	prod *= mdi.weight(j[k], k);
    }
    for (int k = 0; k < K-1; k++) {
      for (int l = k+1; l < K; l++) {
	prod *= 1 + mdi.phi(k, l) * (j[k] == j[l]);
      }
    }
    return prod;
  };
  std::function<long double(int)> outer = [&outer, &inner, &j, N, K, m](int i) {
    if (i == K)
      return inner();

    if (i == m)
      return outer(i+1);

    long double sum = 0;
    for (j[i] = 0; j[i] < N; j[i]++)
      sum += outer(i+1);
    return sum;
  };
  return mdi.nu() * outer(0);
}

int
main()
{
  /* datatypes
   *
   * * loading data (CPU)
   *
   * * summarising data given cluster allocations (CPU GPU)
   *
   * * sampling cluster parameters given above summary (CPU GPU)
   *
   * * sampling cluster allocations given cluster parameters (CPU GPU)
   *
   * MDI prior
   *
   * * Sampling Nu given [weights and allocations]?
   *
   * * Sampling	weights given [lots!]
   *
   * * Sampling	DPs given weights and nu
   */

  generator.seed(1);

  // given a single "file" for each datatype, load it in define
  // allocations (same for all four files), weights, nu, DP
  // concentration

  const int
    nfiles = 4,
    nitems = 5,
    nclus = 10,
    ngaussfeatures = 3;

  // 5 items, cluster allocations [0 0 1 1 2]. one file for each
  // datatype
  Eigen::MatrixXi alloc(nitems, nfiles);
  for (int i = 0; i < nfiles; i++)
    alloc.col(i) << 0, 0, 1, 1, 2;
  Eigen::MatrixXf
    weights = alloc.cast<float>();
  Eigen::VectorXf
    dpmass = Eigen::VectorXf::Ones(nfiles);

  Eigen::MatrixXf data_gaussian(nitems, ngaussfeatures);
  data_gaussian <<
    -2.1, -2.1, -2.1,
    -1.9, -1.9, -1.9,
     1.9,  1.9,  1.9,
     2.1,  2.1,  2.1,
     5.0,  5.0,  5.0;

  // write dummy data out, load it back in and make sure it's the same
  // as the original data
  {
    std::ofstream out("data_gauss.txt");
    writeCsvMatrix (out, data_gaussian, alloc.col(0));
  }

  gaussianDatatype dt_gauss("data_gauss.txt");

  checkEqual(nitems, dt_gauss.items().size(),
	     "number of items read from gaussian dataset");
  checkEqual(ngaussfeatures, dt_gauss.features().size(),
	     "number of features read from gaussian dataset");

  checkApproxEqual<Eigen::MatrixXf>(data_gaussian.transpose(), dt_gauss.rawdata(),
				    "gaussian data from file");

  // given weights, allocations, nu:
  shared shared(nfiles, nclus, nitems);
  interdataset inter(nfiles);
  mdisampler mdi(inter, nclus);

  shared.sampleFromPrior();
  mdi.sampleFromPrior();

  shared.setAlloc(alloc);

  cuda::sampler	cuda(nfiles, nitems, nclus,
		     inter.getPhiOrd(), inter.getWeightOrd());
  cuda.setNu(mdi.nu());
  cuda.setDpMass(eigenMatrixToStdVector(mdi.dpmass()));
  cuda.setAlloc(eigenMatrixToStdVector(alloc));
  cuda.setPhis(eigenMatrixToStdVector(mdi.phis()));
  cuda.setWeights(eigenMatrixToStdVector(mdi.weights()));

  gaussianSampler * gauss_sampler = dt_gauss.newSampler(nclus, &cuda);

  // given known allocations, calculate Gaussian summaries
  {
    gauss_sampler->cudaSampleParameters(alloc.col(0));
    gauss_sampler->cudaAccumAllocProbs();

    const std::vector<runningstats<> > state(gauss_sampler->accumState(alloc.col(0)));
    checkEqual(nclus * ngaussfeatures, state.size(),
	       "number of accumulated gaussian stats");

    // check Gaussian summaries are OK
    const runningstats<> rs[nclus] = {{2,-2,0.02},{2,2,0.02},{1,5,0}};
    bool ok = true;
    for (int j = 0; j < nclus; j++) {
      for (int i = 0; i < ngaussfeatures; i++) {
	const runningstats<>
	  &a = state[j * ngaussfeatures + i],
	  &b = rs[j];
	ok &= a.isApprox(b);
      }
    }
    if (!ok) {
      std::cout << "Error: some of the accumulated gaussian stats are incorrect\n";
    }
  }

  // given known cluster parameters, ...
  {
    Eigen::MatrixXf mu(ngaussfeatures, nclus), tau(ngaussfeatures,nclus);
    mu.fill(0); tau.fill(20);
    mu.col(0).fill(-2);
    mu.col(1).fill( 2);
    mu.col(2).fill( 5);
    gauss_sampler->debug_setMuTau(mu, tau);
  }
  // ... sample Gaussian cluster association probabilities
  {
    std::unique_ptr<sampler::item> is(gauss_sampler->newItemSampler());
    for (int i = 0; i < nitems; i++) {
      Eigen::VectorXf prob((*is)(i));
      prob = (prob.array() - prob.maxCoeff()).exp();
      checkProbItemMassGt<float>(prob, alloc(i,0), 0.5,
				 "gaussian cluster allocations");
    }
  }

  {
    double
      oracle = nuConditionalBetaOracle(mdi);
    checkApproxEqual(oracle, mdi.nuConditionalBeta(),
		     "MDI nu beta conditional, CPU code");
    checkApproxEqual(oracle, cuda.collectNuConditionalBeta(),
		     "MDI nu beta conditional, GPU code");

    oracle = nuConditionalAlphaOracle(alloc);
    checkApproxEqual(oracle, shared.nuConditionalAlpha(),
		     "MDI nu alpha conditional, CPU code");
  }

  {
    Eigen::MatrixXf oracle, cpu, gpu;
    oracle = cpu = gpu = Eigen::MatrixXf::Zero(nfiles, nfiles);
    std::vector<float> gpuv = cuda.collectPhiConditionalsBeta();

    for (int k = 0, i = 0; k < nfiles; k++) {
      for (int l = k+1; l < nfiles; l++) {
	oracle(k,l) = phiConditionalBetaOracle(mdi, k, l);
	cpu(k,l)    = mdi.phiConditionalBeta(k, l);
	gpu(k,l)    = gpuv[i++];
      }
    }
    checkApproxEqual(oracle, cpu, "MDI phi beta conditionals, CPU code");
    checkApproxEqual(oracle, gpu, "MDI phi beta conditionals, GPU code");

    gpuv = cuda.collectPhiConditionalsAlpha();

    for (int k = 0, i = 0; k < nfiles; k++) {
      for (int l = k+1; l < nfiles; l++) {
	oracle(k,l) = phiConditionalAlphaOracle(alloc, k, l);
	cpu(k,l)    = shared.phiConditionalAlpha(k, l) + 1; // what to do about prior?
	gpu(k,l)    = gpuv[i++];
      }
    }
    checkApproxEqual(oracle, cpu, "MDI phi alpha conditionals, CPU code");
    checkApproxEqual(oracle, gpu, "MDI phi alpha conditionals, GPU code");
  }

  {
    Eigen::MatrixXf oracle, cpu, gpu;
    oracle = cpu = Eigen::MatrixXf::Zero(nclus, nfiles);

    gpu = stdVectorToEigenMatrix(cuda.collectGammaConditionalsBeta(),
				 nclus, nfiles);

    // take out the prior, not sure what to do about this.  I think I
    // should be checking priors as well, but conflating it in the
    // test unnecessarily seems to make things more difficult to debug
    // than it could...  hum!
    gpu.array() -= 1;

    for (int k = 0; k < nfiles; k++) {
      for (int j = 0; j < nclus; j++) {
	oracle(j,k) = gammaConditionalBetaOracle(mdi, k, j);
	cpu(j,k)    = mdi.gammaConditionalBeta(k, j);
      }
    }

    checkApproxEqual(oracle, cpu, "MDI gamma beta conditionals, CPU code");
    checkApproxEqual(oracle, gpu, "MDI gamma beta conditionals, GPU code");
  }

  return 0;
}


/*
 * Copyright (c) 2016-2017 Nicholas Corgan (n.corgan@gmail.com)
 *
 * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt
 * or copy at http://opensource.org/licenses/MIT)
 */

#include "../exception_internal.hpp"
#include "../utils/misc.hpp"

#include <pkmn/calculations/shininess.hpp>

#include <boost/config.hpp>

#include <algorithm>

namespace pkmn { namespace calculations {

    bool gen2_shiny(
        int IV_attack,
        int IV_defense,
        int IV_speed,
        int IV_special
    ) {
        pkmn::enforce_IV_bounds("Attack",  IV_attack,  false);
        pkmn::enforce_IV_bounds("Defense", IV_defense, false);
        pkmn::enforce_IV_bounds("Speed",   IV_speed,   false);
        pkmn::enforce_IV_bounds("Special", IV_special, false);

        BOOST_STATIC_CONSTEXPR int valid_IV_attack[] = {2,3,6,7,10,11,14,15};

        bool ret = true;

        if((IV_defense != 10) or (IV_speed != 10) or (IV_special) != 10) {
            ret = false;
        } else if(std::find(valid_IV_attack, valid_IV_attack+8, IV_attack) == (valid_IV_attack+8)) {
            ret = false;
        }

        return ret;
    }

    bool modern_shiny(
        uint32_t personality,
        uint32_t trainer_id
    ) {
        const uint16_t* p = reinterpret_cast<const uint16_t*>(&personality);
        const uint16_t* t = reinterpret_cast<const uint16_t*>(&trainer_id);

        bool ret = true;

        for(size_t i = 3; i < 16; ++i) {
            size_t num_ones = 0;
            if(p[0] & (1 << i)) ++num_ones;
            if(p[1] & (1 << i)) ++num_ones;
            if(t[0] & (1 << i)) ++num_ones;
            if(t[1] & (1 << i)) ++num_ones;
            if(num_ones % 2) {
                ret = false;
                break;
            }
        }

        return ret;
    }

}}


//
// Copyright (c) 2018-2020 CNRS INRIA
//

#ifndef __pinocchio_autodiff_cppad_hpp__
#define __pinocchio_autodiff_cppad_hpp__

#include "pinocchio/math/fwd.hpp"
#define PINOCCHIO_WITH_CPPAD_SUPPORT

// Do not include this file directly.
// Copy and use directly the intructions from <cppad/example/cppad_eigen.hpp>
// to avoid redifinition of EIGEN_MATRIXBASE_PLUGIN for Eigen 3.3.0 and later
//#include <cppad/example/cppad_eigen.hpp>

#define EIGEN_MATRIXBASE_PLUGIN <pinocchio/autodiff/cppad/math/eigen_plugin.hpp>

#include <cppad/cppad.hpp>
#include <Eigen/Dense>

namespace boost
{
  namespace math
  {
    namespace constants
    {
      namespace detail
      {
        template<typename Scalar>
        struct constant_pi< CppAD::AD<Scalar> > : constant_pi<Scalar>
        {
          typedef CppAD::AD<Scalar> ADScalar;

          template <int N>
          static inline ADScalar get(const mpl::int_<N>& n)
          {
            return ADScalar(constant_pi<Scalar>::get(n));
          }

        };
      }
    }
  }
}

namespace Eigen
{
  namespace internal
  {
    // Specialization of Eigen::internal::cast_impl for CppAD input types
    template<typename Scalar>
    struct cast_impl<CppAD::AD<Scalar>,Scalar>
    {
#if EIGEN_VERSION_AT_LEAST(3,2,90)
      EIGEN_DEVICE_FUNC
#endif
      static inline Scalar run(const CppAD::AD<Scalar> & x)
      {
        return CppAD::Value(x);
      }
    };
  }
} //namespace Eigen

// Source from #include <cppad/example/cppad_eigen.hpp>
namespace Eigen
{
  template <class Base> struct NumTraits< CppAD::AD<Base> >
  {  // type that corresponds to the real part of an AD<Base> value
    typedef CppAD::AD<Base>   Real;
    // type for AD<Base> operations that result in non-integer values
    typedef CppAD::AD<Base>   NonInteger;
    //  type to use for numeric literals such as "2" or "0.5".
    typedef CppAD::AD<Base>   Literal;
    // type for nested value inside an AD<Base> expression tree
    typedef CppAD::AD<Base>   Nested;

    enum {
      // does not support complex Base types
      IsComplex             = 0 ,
      // does not support integer Base types
      IsInteger             = 0 ,
      // only support signed Base types
      IsSigned              = 1 ,
      // must initialize an AD<Base> object
      RequireInitialization = 1 ,
      // computational cost of the corresponding operations
      ReadCost              = 1 ,
      AddCost               = 2 ,
      MulCost               = 2
    };

    // machine epsilon with type of real part of x
    // (use assumption that Base is not complex)
    static CppAD::AD<Base> epsilon(void)
    {  return CppAD::numeric_limits< CppAD::AD<Base> >::epsilon(); }

    // relaxed version of machine epsilon for comparison of different
    // operations that should result in the same value
    static CppAD::AD<Base> dummy_precision(void)
    {  return 100. *
      CppAD::numeric_limits< CppAD::AD<Base> >::epsilon();
    }

    // minimum normalized positive value
    static CppAD::AD<Base> lowest(void)
    {  return CppAD::numeric_limits< CppAD::AD<Base> >::min(); }

    // maximum finite value
    static CppAD::AD<Base> highest(void)
    {  return CppAD::numeric_limits< CppAD::AD<Base> >::max(); }

    // number of decimal digits that can be represented without change.
    static int digits10(void)
    {  return CppAD::numeric_limits< CppAD::AD<Base> >::digits10; }
  };
} // namespace Eigen

// Source from #include <cppad/example/cppad_eigen.hpp>
#include "pinocchio/utils/static-if.hpp"

namespace CppAD
{
  // functions that return references
  template <class Base> const AD<Base>& conj(const AD<Base>& x)
  {  return x; }
  template <class Base> const AD<Base>& real(const AD<Base>& x)
  {  return x; }

  // functions that return values (note abs is defined by cppad.hpp)
  template <class Base> AD<Base> imag(const AD<Base>& /*x*/)
  {  return CppAD::AD<Base>(0.); }
  template <class Base> AD<Base> abs2(const AD<Base>& x)
  {  return x * x; }

  template<typename Scalar>
  AD<Scalar> min(const AD<Scalar>& x, const AD<Scalar>& y)
  {
    using ::pinocchio::internal::if_then_else;
    using ::pinocchio::internal::LT;
    return if_then_else(LT, y, x, y, x);
  }

  template<typename Scalar>
  AD<Scalar> max(const AD<Scalar>& x, const AD<Scalar>& y)
  {
    using ::pinocchio::internal::if_then_else;
    using ::pinocchio::internal::LT;
    return if_then_else(LT, x, y, y, x);
  }

} // namespace CppAD

#include "pinocchio/utils/static-if.hpp"

namespace pinocchio
{
  template<typename Scalar>
  struct TaylorSeriesExpansion< CppAD::AD<Scalar> > : TaylorSeriesExpansion<Scalar>
  {
    typedef TaylorSeriesExpansion<Scalar> Base;
    typedef CppAD::AD<Scalar> ADScalar;

    template<int degree>
    static ADScalar precision()
    {
      return ADScalar(Base::template precision<degree>());
    }

  };

} // namespace pinocchio

#include "pinocchio/autodiff/cppad/spatial/se3-tpl.hpp"
#include "pinocchio/autodiff/cppad/spatial/log.hxx"
#include "pinocchio/autodiff/cppad/utils/static-if.hpp"
#include "pinocchio/autodiff/cppad/math/quaternion.hpp"
#include "pinocchio/autodiff/cppad/algorithm/aba.hpp"

#endif // #ifndef __pinocchio_autodiff_cppad_hpp__


/*-----------------------------------------------------------------------------+
Copyright (c) 2008-2009: Joachim Faulhaber
+------------------------------------------------------------------------------+
   Distributed under the Boost Software License, Version 1.0.
      (See accompanying file LICENCE.txt or copy at
           http://www.boost.org/LICENSE_1_0.txt)
+-----------------------------------------------------------------------------*/
#define BOOST_TEST_MODULE icl::interval_map_mixed unit test
#include <libs/icl/test/disable_test_warnings.hpp>
#include <string>
#include <boost/mpl/list.hpp>
#include "../unit_test_unwarned.hpp"

// interval instance types
#include "../test_type_lists.hpp"
#include "../test_value_maker.hpp"

#include <boost/icl/interval_set.hpp>
#include <boost/icl/separate_interval_set.hpp>
#include <boost/icl/split_interval_set.hpp>
#include <boost/icl/interval_map.hpp>
#include <boost/icl/split_interval_map.hpp>

using namespace std;
using namespace boost;
using namespace unit_test;
using namespace boost::icl;

#include "../test_interval_map_mixed.hpp"

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_add_4_bicremental_types)
{            interval_map_mixed_add_4_bicremental_types<bicremental_type_1, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_add2_4_bicremental_types)
{            interval_map_mixed_add2_4_bicremental_types<bicremental_type_2, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_subtract_4_bicremental_types)
{            interval_map_mixed_subtract_4_bicremental_types<bicremental_type_3, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_erase_4_bicremental_types)
{            interval_map_mixed_erase_4_bicremental_types<bicremental_type_4, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_erase2_4_bicremental_types)
{            interval_map_mixed_erase2_4_bicremental_types<bicremental_type_5, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_insert_erase_4_bicremental_types)
{            interval_map_mixed_insert_erase_4_bicremental_types<bicremental_type_6, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_insert_erase2_4_bicremental_types)
{            interval_map_mixed_insert_erase2_4_bicremental_types<bicremental_type_7, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_basic_intersect_4_bicremental_types)
{            interval_map_mixed_basic_intersect_4_bicremental_types<bicremental_type_8, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_basic_intersect2_4_bicremental_types)
{            interval_map_mixed_basic_intersect2_4_bicremental_types<bicremental_type_1, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_intersect_4_bicremental_types)
{            interval_map_mixed_intersect_4_bicremental_types<bicremental_type_2, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_intersect2_4_bicremental_types)
{            interval_map_mixed_intersect2_4_bicremental_types<bicremental_type_3, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_disjoint_4_bicremental_types)
{            interval_map_mixed_disjoint_4_bicremental_types<bicremental_type_4, int>(); }

BOOST_AUTO_TEST_CASE
(fastest_icl_interval_map_mixed_erase_if_4_integral_types)
{            interval_map_mixed_erase_if_4_integral_types<int, int>(); }


#ifndef SKYLARK_INNER_HPP
#define SKYLARK_INNER_HPP

#include <boost/mpi.hpp>

namespace skylark { namespace base {

template<typename T>
inline El::Base<T> Nrm2(const El::Matrix<T>& x) {
    return El::Nrm2(x);
}

template<typename T>
inline El::Base<T> Nrm2(const El::DistMatrix<T>& x) {
    return El::Nrm2(x);
}

template<typename T>
inline El::Base<T> Nrm2(const El::DistMatrix<T, El::VC, El::STAR>& x) {
    boost::mpi::communicator comm(x.DistComm().comm, boost::mpi::comm_attach);
    T local = El::Nrm2(x.LockedMatrix());
    T snrm = boost::mpi::all_reduce(comm, local * local, std::plus<T>());
    return sqrt(snrm);
}

template<typename T>
inline El::Base<T> Nrm2(const El::DistMatrix<T, El::VR, El::STAR>& x) {
    boost::mpi::communicator comm(x.DistComm(), boost::mpi::comm_attach);
    T local = El::Nrm2(x.LockedMatrix());
    T snrm = boost::mpi::all_reduce(comm, local * local, std::plus<T>());
    return sqrt(snrm);
}

template<typename T>
inline El::Base<T> Nrm2(const El::DistMatrix<T, El::STAR, El::STAR>& x) {
    return El::Nrm2(x.LockedMatrix());
}

template<typename T>
inline void ColumnNrm2(const El::Matrix<T>& A,
    El::Matrix<El::Base<T> >& N) {

    N.Resize(A.Width(), 1);
    T *n = N.Buffer();
    const T *a = A.LockedBuffer();
    for(El::Int j = 0; j < A.Width(); j++) {
        n[j] = 0.0;
        for(El::Int i = 0; i < A.Height(); i++)
            n[j] += a[j * A.LDim() + i] * El::Conj(a[j * A.LDim() + i]);
        n[j] = sqrt(n[j]);
    }
}

template<typename T>
inline void ColumnNrm2(const El::DistMatrix<T, El::STAR, El::STAR>& A,
    El::DistMatrix<El::Base<T>, El::STAR, El::STAR>& N) {
    N.Resize(A.Width(), 1);
    T *n = N.Buffer();
    const T *a = A.LockedBuffer();
    for(El::Int j = 0; j < A.Width(); j++) {
        n[j] = 0.0;
        for(El::Int i = 0; i < A.LocalHeight(); i++)
            n[j] += a[j * A.LDim() + i] * El::Conj(a[j * A.LDim() + i]);
        n[j] = sqrt(n[j]);
    }
}

template<typename T, El::Distribution U, El::Distribution V>
inline void ColumnNrm2(const El::DistMatrix<T, U, V>& A,
    El::DistMatrix<El::Base<T>, El::STAR, El::STAR>& N) {

    std::vector<T> n(A.Width(), 1);
    std::fill(n.begin(), n.end(), 0.0);
    const El::Matrix<T> &Al = A.LockedMatrix();
    const T *a = Al.LockedBuffer();
    for(int j = 0; j < Al.Width(); j++)
        for(int i = 0; i < Al.Height(); i++)
            n[A.GlobalCol(j)] +=
                a[j * Al.LDim() + i] * El::Conj(a[j * Al.LDim() + i]);

    N.Resize(A.Width(), 1);
    El::Zero(N);
    boost::mpi::communicator comm(N.Grid().Comm().comm, boost::mpi::comm_attach);
    boost::mpi::all_reduce(comm, n.data(), A.Width(), N.Buffer(), std::plus<T>());
    for(int j = 0; j < A.Width(); j++)
        N.Set(j, 0, sqrt(N.Get(j, 0)));
}

template<typename T>
inline void ColumnNrm2(const El::AbstractDistMatrix<T>& A,
    El::DistMatrix<El::Base<T>, El::STAR, El::STAR>& N) {

    N.Resize(A.Width(), 1);

    if (A.Participating()) {
        std::vector<T> n(A.Width(), 1);
        std::fill(n.begin(), n.end(), 0.0);
        const El::Matrix<T> &Al = A.LockedMatrix();
        const T *a = Al.LockedBuffer();
        for(El::Int j = 0; j < Al.Width(); j++)
            for(El::Int i = 0; i < Al.Height(); i++)
                n[A.GlobalCol(j)] +=
                    a[j * Al.LDim() + i] * El::Conj(a[j * Al.LDim() + i]);

        El::Zero(N);
        El::mpi::AllReduce(n.data(), N.Buffer(), A.Width(), MPI_SUM,
            A.DistComm());
        for(El::Int j = 0; j < A.Width(); j++)
            N.Set(j, 0, sqrt(N.Get(j, 0)));
    }

    El::mpi::Broadcast(N.Buffer(), A.Width(), A.Root(), A.CrossComm());
}

template<typename T>
inline void ColumnDot(const El::Matrix<T>& A, const El::Matrix<T>& B,
    El::Matrix<T>& N) {

    // TODO just assuming sizes are OK for now.

    T *n = N.Buffer();
    const T *a = A.LockedBuffer();
    const T *b = B.LockedBuffer();
    for(El::Int j = 0; j < A.Width(); j++) {
        n[j] = 0.0;
        for(El::Int i = 0; i < A.Height(); i++)
            n[j] += a[j * A.LDim() + i] * El::Conj(b[j * B.LDim() + i]);
    }
}

template<typename T>
inline void ColumnDot(const El::DistMatrix<T, El::STAR, El::STAR>& A,
    const El::DistMatrix<T, El::STAR, El::STAR>& B,
    El::DistMatrix<T, El::STAR, El::STAR>& N) {

    // TODO just assuming sizes are OK for now.

    T *n = N.Buffer();
    const T *a = A.LockedBuffer();
    const T *b = B.LockedBuffer();
    for(El::Int j = 0; j < A.Width(); j++) {
        n[j] = 0.0;
        for(El::Int i = 0; i < A.LocalHeight(); i++)
            n[j] += a[j * A.LDim() + i] * El::Conj(b[j * B.LDim() + i]);
    }
}

template<typename T, El::Distribution U, El::Distribution V>
inline void ColumnDot(const El::DistMatrix<T, U, V>& A,
    const El::DistMatrix<T, U, V>& B,
    El::DistMatrix<T, El::STAR, El::STAR>& N) {

    // TODO just assuming sizes are OK for now, and grid aligned.
    std::vector<T> n(A.Width(), 1);
    std::fill(n.begin(), n.end(), 0);
    const El::Matrix<T> &Al = A.LockedMatrix();
    const T *a = Al.LockedBuffer();
    const El::Matrix<T> &Bl = B.LockedMatrix();
    const T *b = Bl.LockedBuffer();
    for(El::Int j = 0; j < Al.Width(); j++)
        for(El::Int i = 0; i < Al.Height(); i++)
            n[A.GlobalCol(j)] +=
                a[j * Al.LDim() + i] * El::Conj(b[j * Bl.LDim() + i]);

   N.Resize(A.Width(), 1);
   El::Zero(N);
   boost::mpi::communicator comm(N.Grid().Comm().comm, boost::mpi::comm_attach);
   boost::mpi::all_reduce(comm, n.data(), A.Width(), N.Buffer(), std::plus<T>());
}

template<typename T>
inline void RowDot(const El::Matrix<T>& A, const El::Matrix<T>& B,
    El::Matrix<T>& N) {

    // TODO just assuming sizes are OK for now.

    T *n = N.Buffer();
    const T *a = A.LockedBuffer();
    const T *b = B.LockedBuffer();
    for(El::Int i = 0; i < A.Height(); i++)
        n[i] = 0.0;

    for(El::Int j = 0; j < A.Width(); j++) {
        for(El::Int i = 0; i < A.Height(); i++)
            n[i] += a[j * A.LDim() + i] * El::Conj(b[j * B.LDim() + i]);
    }
}

} } // namespace skylark::base

#endif // SKYLARK_INNER_HPP


/**
 * @copyright   Copyright (c) 2021, Swift Engineering Inc.
 * @license     Licensed under the MIT license. See LICENSE for details.
 */
#include <gtest/gtest.h>

#include <string>
#include <sstream>
#include <boost/array.hpp>
#include <stdio.h>

#include "LookupTable.hpp"

class LookupTableTest : public ::testing::Test {
  protected:
    std::vector<std::string> colNames = {"Angle Of Attack", "Lift Coefficient"};
    std::vector<std::vector<double>> cols = {{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}};
    avionics_sim::LookupTable lookupTable_ =
        avionics_sim::LookupTable(colNames, cols);

    // You can define per-test set-up logic as usual.
    virtual void SetUp() {
    }
};

TEST_F(LookupTableTest, TestConstruction) {
    // Given a value within the bounds of the first data vector
    double refValue = 1.5;

    // When value is lookup in table
    double result = lookupTable_.lookup(1.5, "Angle Of Attack", "Lift Coefficient");

    // Then
    ASSERT_EQ(result, 4.5);
}


// Copyright (C) 2015 National ICT Australia (NICTA)
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// -------------------------------------------------------------------
//
// Written by Conrad Sanderson - http://conradsanderson.id.au

#include <armadillo>
#include "catch.hpp"

using namespace arma;

TEST_CASE("gen_ones_1")
  {
  mat A(5,6,fill::ones);

  REQUIRE( accu(A)  == Approx(double(5*6)) );
  REQUIRE( A.n_rows == 5 );
  REQUIRE( A.n_cols == 6 );

  mat B(5,6,fill::randu);

  B.ones();

  REQUIRE( accu(B)  == Approx(double(5*6)) );
  REQUIRE( B.n_rows == 5 );
  REQUIRE( B.n_cols == 6 );

  mat C = ones<mat>(5,6);

  REQUIRE( accu(C)  == Approx(double(5*6)) );
  REQUIRE( C.n_rows == 5 );
  REQUIRE( C.n_cols == 6 );

  mat D; D = ones<mat>(5,6);

  REQUIRE( accu(D)  == Approx(double(5*6)) );
  REQUIRE( D.n_rows == 5 );
  REQUIRE( D.n_cols == 6 );

  mat E; E = 2*ones<mat>(5,6);

  REQUIRE( accu(E)  == Approx(double(2*5*6)) );
  REQUIRE( E.n_rows == 5 );
  REQUIRE( E.n_cols == 6 );
  }

TEST_CASE("gen_ones_2")
  {
  mat A(5,6,fill::zeros);

  A.col(1).ones();

  REQUIRE( accu(A.col(0)) == Approx(0.0)              );
  REQUIRE( accu(A.col(1)) == Approx(double(A.n_rows)) );
  REQUIRE( accu(A.col(2)) == Approx(0.0)              );

  mat B(5,6,fill::zeros);

  B.row(1).ones();

  REQUIRE( accu(B.row(0)) == Approx(0.0)              );
  REQUIRE( accu(B.row(1)) == Approx(double(B.n_cols)) );
  REQUIRE( accu(B.row(2)) == Approx(0.0)              );

  mat C(5,6,fill::zeros);

  C(span(1,3),span(1,4)).ones();

  REQUIRE( accu(C.head_cols(1)) == Approx(0.0) );
  REQUIRE( accu(C.head_rows(1)) == Approx(0.0) );

  REQUIRE( accu(C.tail_cols(1)) == Approx(0.0) );
  REQUIRE( accu(C.tail_rows(1)) == Approx(0.0) );

  REQUIRE( accu(C(span(1,3),span(1,4))) == Approx(double(3*4)) );

  mat D(5,6,fill::zeros);

  D.diag().ones();

  REQUIRE( accu(D.diag()) == Approx(double(5)) );
  }

TEST_CASE("gen_ones_3")
  {
  mat A(5,6,fill::zeros);

  uvec indices = { 2, 4, 6 };

  A(indices).ones();

  REQUIRE( accu(A) == Approx(double(3)) );

  REQUIRE( A(0)          == Approx(0.0) );
  REQUIRE( A(A.n_elem-1) == Approx(0.0) );

  REQUIRE( A(indices(0)) == Approx(1.0) );
  REQUIRE( A(indices(1)) == Approx(1.0) );
  REQUIRE( A(indices(2)) == Approx(1.0) );
  }