/*    Copyright (c) 2010-2019, Delft University of Technology
 *    All rigths reserved
 *
 *    This file is part of the Tudat. Redistribution and use in source and
 *    binary forms, with or without modification, are permitted exclusively
 *    under the terms of the Modified BSD license. You should have received
 *    a copy of the license with this file. If not, please or visit:
 *    http://tudat.tudelft.nl/LICENSE.
 *
 */

#define BOOST_TEST_MAIN

#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/unit_test.hpp>

#include <Eigen/Core>

#include "Tudat/Astrodynamics/BasicAstrodynamics/orbitalElementConversions.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/unitConversions.h"
#include "Tudat/Basics/testMacros.h"
#include "Tudat/Mathematics/BasicMathematics/mathematicalConstants.h"
#include "Tudat/Mathematics/BasicMathematics/basicMathematicsFunctions.h"

#include "Tudat/Astrodynamics/BasicAstrodynamics/unifiedStateModelQuaternionElementConversions.h"
#include "Tudat/Basics/basicTypedefs.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/stateVectorIndices.h"

namespace tudat
{
namespace unit_tests
{

//! Test the functionality of the time conversion functions.
BOOST_AUTO_TEST_SUITE( test_USM7_Element_Conversions )

//! Unit test for conversion Keplerian orbital elements to unified state model elements.
BOOST_AUTO_TEST_CASE( testconvertKeplerianToUnifiedStateModelQuaternionsElements )
{
    using namespace orbital_element_conversions;
    using namespace unit_conversions;
    using mathematical_constants::PI;

    // Setting fraction tolerance for correctness evaluation
    double tolerance = 1.0E-14;

    // Declare gravitational parameter of central body
    const double centralBodyGravitationalParameter = 1.32712440018e20; // [m^3/s^2]

    // Initializing default Keplerian orbit
    Eigen::Vector6d keplerianElements = Eigen::Vector6d::Zero( 6 );
    keplerianElements( semiMajorAxisIndex ) = 1.5e11;
    keplerianElements( eccentricityIndex ) = 0.1;
    keplerianElements( inclinationIndex ) = convertDegreesToRadians( 50.0 );
    keplerianElements( argumentOfPeriapsisIndex ) = convertDegreesToRadians( 350.0 );
    keplerianElements( longitudeOfAscendingNodeIndex ) = convertDegreesToRadians( 15.0 );
    keplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 10.0 );

    // Unified state model element vector declaration
    Eigen::VectorXd expectedUnifiedStateModelElements
            = Eigen::VectorXd::Zero( 7 );
    Eigen::VectorXd computedUnifiedStateModelElements
            = Eigen::VectorXd::Zero( 7 );

    // Case 1: Elliptical prograde orbit (default case).
    {
        // Default case, so no modification necessary.

        // Expected unified state model elements [m/s,m/s,m/s,-,-,-,-].
        // (Results obtained using MATLAB code).
        expectedUnifiedStateModelElements( CHodographUSM7Index ) = 29894.5892222602;
        expectedUnifiedStateModelElements( Rf1HodographUSM7Index ) = -260.548512780222;
        expectedUnifiedStateModelElements( Rf2HodographUSM7Index ) = 2978.08312848463;
        expectedUnifiedStateModelElements( epsilon1USM7Index ) = -0.419002703925548;
        expectedUnifiedStateModelElements( epsilon2USM7Index ) = -0.0551627524676706;
        expectedUnifiedStateModelElements( epsilon3USM7Index ) = -0.118296904421275;
        expectedUnifiedStateModelElements( etaUSM7Index ) = -0.898554198280556;

        // Compute unified state model elements.
        computedUnifiedStateModelElements =
                convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                               centralBodyGravitationalParameter );

        // Check if computed unified state model elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedUnifiedStateModelElements,
                                           computedUnifiedStateModelElements, tolerance );

    }

    // Case 2: Hyperbolic retrograde orbit.
    {
        // Modify Keplerian elements [m,-,rad,rad,rad,rad], i.e. overwrite them.
        keplerianElements( semiMajorAxisIndex ) = -1.5e11;
        keplerianElements( eccentricityIndex ) = 2.0;
        keplerianElements( inclinationIndex ) = convertDegreesToRadians( 170.0 );
        keplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 10.0 );

        // Set expected unified state model elements [m/s,m/s,m/s,-,-,-,-].
        // (Results obtained using MATLAB code).
        expectedUnifiedStateModelElements( CHodographUSM7Index ) = 17173.1340579794;
        expectedUnifiedStateModelElements( Rf1HodographUSM7Index ) = -2993.47450825659;
        expectedUnifiedStateModelElements( Rf2HodographUSM7Index ) = 34215.5701963558;
        expectedUnifiedStateModelElements( epsilon1USM7Index ) = -0.987672114350896;
        expectedUnifiedStateModelElements( epsilon2USM7Index ) = -0.130029500651719;
        expectedUnifiedStateModelElements( epsilon3USM7Index ) = -0.0113761072309622;
        expectedUnifiedStateModelElements( etaUSM7Index )= -0.0864101132863834;

        // Compute unified state model elements.
        computedUnifiedStateModelElements =
                convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                               centralBodyGravitationalParameter );

        // Check if computed unified state model elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedUnifiedStateModelElements,
                                           computedUnifiedStateModelElements, tolerance );

    }

    // Case 3: Parabolic retrograde orbit.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        keplerianElements( semiLatusRectumIndex ) = 1.5e11;
        keplerianElements( eccentricityIndex ) = 1.0;
        keplerianElements( inclinationIndex ) = convertDegreesToRadians( 170.0 );
        keplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 170.0 );

        // Set expected unified state model elements [m/s,m/s,m/s,-,-,-,-].
        // (Results obtained using MATLAB code).
        expectedUnifiedStateModelElements( CHodographUSM7Index ) = 29744.7407136119;
        expectedUnifiedStateModelElements( Rf1HodographUSM7Index ) = -2592.42496973134;
        expectedUnifiedStateModelElements( Rf2HodographUSM7Index ) = 29631.5529950138;
        expectedUnifiedStateModelElements( epsilon1USM7Index ) = -0.299561523151596;
        expectedUnifiedStateModelElements( epsilon2USM7Index ) = 0.95008776981561;
        expectedUnifiedStateModelElements( epsilon3USM7Index ) = -0.0870727897926938;
        expectedUnifiedStateModelElements( etaUSM7Index ) = -0.00380168010402369;

        // Compute unified state model elements.
        computedUnifiedStateModelElements =
                convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                               centralBodyGravitationalParameter );

        // Check if computed unified state model elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedUnifiedStateModelElements,
                                           computedUnifiedStateModelElements, tolerance );
    }

    // Case 4: Circular prograde orbit with non-zero argument of pericenter, test for error.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        keplerianElements( eccentricityIndex ) = 0.0;
            // Eccentricity is zero, while argument of pericenter is non-zero -> should give error
        keplerianElements( inclinationIndex ) = convertDegreesToRadians( 50.0 );

        // Declare variable indicating whether an exception has been thrown.
        bool isExceptionFound = false;

        // Try computing the unified state model elements and catch the expected runtime error.
        try
        {
            computedUnifiedStateModelElements =
                    convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                                 centralBodyGravitationalParameter );
        }
        catch( std::runtime_error )
        {
            isExceptionFound = true;
        }

        // Check if runtime error has occured
        BOOST_CHECK( isExceptionFound );
    }

    // Case 5: 0 inclination orbit, test for error because longitude of ascending node is non-zero
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        keplerianElements( eccentricityIndex ) = 0.1;
        keplerianElements( inclinationIndex ) = 0.0;

        // Declare variable indicating whether an exception has been thrown.
        bool isExceptionFound = false;

        // Try computing the unified state model elements and catch the expected runtime error.
        try
        {
            computedUnifiedStateModelElements =
                    convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                                 centralBodyGravitationalParameter );
        }
        catch( std::runtime_error )
        {
            isExceptionFound = true;
        }

        // Check if runtime error has occured
        BOOST_CHECK( isExceptionFound );
    }

    // Case 6: 180 inclination orbit.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        keplerianElements( inclinationIndex ) = PI; // = 180 deg

        // Set expected unified state model elements [m/s,m/s,m/s,-,-,-,-]. (Results were calculated by
        // hand).
        expectedUnifiedStateModelElements( CHodographUSM7Index ) = 29894.5892222602;
        expectedUnifiedStateModelElements( Rf1HodographUSM7Index ) = -260.548512780222;
        expectedUnifiedStateModelElements( Rf2HodographUSM7Index ) = 2978.08312848463;
        expectedUnifiedStateModelElements( epsilon1USM7Index ) = -0.300705799504273;
        expectedUnifiedStateModelElements( epsilon2USM7Index ) = 0.953716950748227;
        expectedUnifiedStateModelElements( epsilon3USM7Index ) = -6.11740603377039e-17;
        expectedUnifiedStateModelElements( etaUSM7Index ) = -2.67091715588637e-18;

        // Compute unified state model elements.
        computedUnifiedStateModelElements =
                convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                               centralBodyGravitationalParameter );

        // Because two elements are near-zero, a close fraction/percentage check will fail.
        // Therefore, 1.0 is added to the elements to avoid this
        expectedUnifiedStateModelElements( epsilon3USM7Index ) =
                expectedUnifiedStateModelElements( epsilon3USM7Index ) + 1.0;
        expectedUnifiedStateModelElements( etaUSM7Index ) =
                expectedUnifiedStateModelElements( etaUSM7Index ) + 1.0;
        computedUnifiedStateModelElements( epsilon3USM7Index ) =
                computedUnifiedStateModelElements( epsilon3USM7Index ) + 1.0;
        computedUnifiedStateModelElements( etaUSM7Index ) =
                computedUnifiedStateModelElements( etaUSM7Index ) + 1.0;

        // Check if computed elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedUnifiedStateModelElements,
                                           computedUnifiedStateModelElements, tolerance );
    }

    // Case 7: 0 eccentricity and inclination orbit.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        keplerianElements( eccentricityIndex ) = 0.0;
        keplerianElements( inclinationIndex ) = 0.0;
        keplerianElements( longitudeOfAscendingNodeIndex ) = 0.0; // Default value because of zero inclination
        keplerianElements( argumentOfPeriapsisIndex ) = 0.0; // Default value because of zero eccentricity

        // Expected unified state model elements [m/s,m/s,m/s,-,-,-,-].
        // (Results obtained using code archive B. Romgens (2011)).
        expectedUnifiedStateModelElements( CHodographUSM7Index ) = 29744.7407136119;
        expectedUnifiedStateModelElements( Rf1HodographUSM7Index ) = 0;
        expectedUnifiedStateModelElements( Rf2HodographUSM7Index ) = 0;
        expectedUnifiedStateModelElements( epsilon1USM7Index ) = 0;
        expectedUnifiedStateModelElements( epsilon2USM7Index ) = 0;
        expectedUnifiedStateModelElements( epsilon3USM7Index ) = 0.996194698091746;
        expectedUnifiedStateModelElements( etaUSM7Index ) = 0.0871557427476581;

        // Compute unified state model elements.
        computedUnifiedStateModelElements =
                convertKeplerianToUnifiedStateModelQuaternionsElements( keplerianElements,
                                                               centralBodyGravitationalParameter );

        // Check if computed elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedUnifiedStateModelElements,
                                           computedUnifiedStateModelElements, tolerance );
    }

    // Case 8: 200 degree inclination orbit, test for error.
    {
        keplerianElements( inclinationIndex ) = convertDegreesToRadians( 200.0 );
        bool isExceptionFound = false;

        // Try to convert Kepler to unified state model Elements
        try
        {
            computedUnifiedStateModelElements = convertKeplerianToUnifiedStateModelQuaternionsElements
                    ( keplerianElements, centralBodyGravitationalParameter );
        }
        // Catch the expected runtime error, and set the boolean flag to true.
        catch ( std::runtime_error )
        {
            isExceptionFound = true;
        }

        // Check value of flag.
        BOOST_CHECK( isExceptionFound );
    }
}

//! Unit test for the conversion of unified state model elements to Keplerian elements
BOOST_AUTO_TEST_CASE( testconvertUnifiedStateModelQuaternionsToKeplerianElements )
{
    /* Used procedure:
      Because the Kepler to unified state model elements are verified, a subsequent conversion back
      to Keplerian elements should yield the same outcome as the input Keplerian state. This
      principle is used for verification.
     */

    using namespace orbital_element_conversions;
    using namespace unit_conversions;
    using mathematical_constants::PI;

    // Setting fraction tolerance for correctness evaluation
    double tolerance = 1.0E-14;

    // Declare gravitational parameter of central body
    const double centralBodyGravitationalParameter = 1.32712440018e20; // [m^3/s^2]

    // Initializing default Keplerian orbit
    Eigen::Vector6d expectedKeplerianElements = Eigen::VectorXd::Zero( 6 );
    expectedKeplerianElements( semiMajorAxisIndex ) = 1.5e11;
    expectedKeplerianElements( eccentricityIndex ) = 0.1;
    expectedKeplerianElements( inclinationIndex ) = convertDegreesToRadians( 50.0 );
    expectedKeplerianElements( argumentOfPeriapsisIndex ) = convertDegreesToRadians( 350.0 );
    expectedKeplerianElements( longitudeOfAscendingNodeIndex ) = convertDegreesToRadians( 15.0 );
    expectedKeplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 170.0 );

    // Declaring computed output vector.
    Eigen::Vector6d computedKeplerianElements = Eigen::VectorXd::Zero( 6 );

    // Case 1: Elliptical prograde orbit (default case).
    {
        // Default case, so no modification necessary.

        // Convert to unified state model elements and back.
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                            centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
    }

    // Case 2: Hyperbolic retrograde orbit.
    {
        // Modify Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( semiMajorAxisIndex ) = -1.5e11;
        expectedKeplerianElements( eccentricityIndex ) = 2.0;
        expectedKeplerianElements( inclinationIndex ) = convertDegreesToRadians( 160.0 );
        expectedKeplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 10.0 ); // 170 is above limit

        // Convert to unified state model elements and back.
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                            centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
    }

    // Case 3: Parabolic retrograde orbit.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( semiLatusRectumIndex ) = 3.5e11;
        expectedKeplerianElements( eccentricityIndex ) = 1.0;
        expectedKeplerianElements( inclinationIndex ) = convertDegreesToRadians( 90.0 );

        // Convert to unified state model elements and back.
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                   centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
       }

    // Case 4: Circular prograde orbit.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( semiMajorAxisIndex ) = 3.5e11;
        expectedKeplerianElements( eccentricityIndex ) = 0.0;
        expectedKeplerianElements( inclinationIndex ) = convertDegreesToRadians( 70.0 );
        expectedKeplerianElements( argumentOfPeriapsisIndex ) = 0.0; // For e = 0, undefined.

        // Convert to unified state model elements and back.
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                   centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
    }

    // Case 5: 0 inclination orbit,
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( eccentricityIndex ) = 0.3;
        expectedKeplerianElements( inclinationIndex ) = 0.0;
        expectedKeplerianElements( longitudeOfAscendingNodeIndex ) = 0.0; // Set to zero as for
        // non-inclined orbit planes, this parameter is undefined

        // Convert to unified state model elements and back.
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                   centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
    }

    // Case 6: 180 inclination orbit, test for error.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( semiMajorAxisIndex ) = 1.5e15;
        expectedKeplerianElements( inclinationIndex ) = PI;
        expectedKeplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 240.0 );

        // Declare variable indicating whether an exception has been thrown.
        bool isExceptionFound = false;

        // Try convert to unified state model elements and back and catch the expected runtime error.
        try
        {
            computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                 centralBodyGravitationalParameter ),
                        centralBodyGravitationalParameter );
        }
        catch( std::runtime_error )
        {
            isExceptionFound = true;
        }

        // Check if runtime error has occured
        BOOST_CHECK( isExceptionFound );
    }

    // Case 7: 0 eccentricity and inclination orbit.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( eccentricityIndex ) = 0.0;
            // argument of pericenter was set to 0 in case 4, so no error.
        expectedKeplerianElements( inclinationIndex ) = 0.0;
            // longitude of ascending node was set to 0 in case 5, so no error.

        // Convert to unified state model elements and back
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                   centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
    }

    // Case 8: true anomaly exceeding 180 degrees.
    {
        // Set Keplerian elements [m,-,rad,rad,rad,rad].
        expectedKeplerianElements( semiMajorAxisIndex ) = 1.5e11;
        expectedKeplerianElements( eccentricityIndex ) = 0.1;
        expectedKeplerianElements( inclinationIndex ) = convertDegreesToRadians( 50.0 );
        expectedKeplerianElements( argumentOfPeriapsisIndex ) = convertDegreesToRadians( 350.0 );
        expectedKeplerianElements( longitudeOfAscendingNodeIndex ) = convertDegreesToRadians( 15.0 );
        expectedKeplerianElements( trueAnomalyIndex ) = convertDegreesToRadians( 240.0 );

        // Convert to unified state model elements and back
        computedKeplerianElements = convertUnifiedStateModelQuaternionsToKeplerianElements(
                    convertKeplerianToUnifiedStateModelQuaternionsElements( expectedKeplerianElements,
                                                                   centralBodyGravitationalParameter ),
                    centralBodyGravitationalParameter );

        // Check if computed Keplerian elements match the expected values.
        TUDAT_CHECK_MATRIX_CLOSE_FRACTION( expectedKeplerianElements,
                                           computedKeplerianElements, tolerance );
    }
}
BOOST_AUTO_TEST_SUITE_END( )

} // end namespace unit_tests
} // end namespace tudat


#include "Player.h"
#include <Eigen/Dense>
#include <Eigen/Core>
#include <iostream>
#include <random>
#include <NNWrapper.h>
#include <math.h>       /* isnan, sqrt */

int pickRandomElement(std::vector<int> v){
	std::random_device random_device;
	std::mt19937 engine{random_device()};
	std::uniform_int_distribution<int> dist(0, v.size() - 1);

	return  v[dist(engine)];
}

int pickStochasticElement(ArrayXf p){
	std::random_device random_device;
	std::mt19937 engine{random_device()};

    std::discrete_distribution<> dist(p.data(),p.data() +  p.size());

	return  dist(engine);
}

int HumanPlayer::getAction(std::shared_ptr<Game> game){
	int action;
	ArrayXf poss = game->getPossibleActions();
	std::cout << "Enter an action";
	std::cin >> action;
	action--;

	while (poss[action] != 1){
		std::cout << "Invalid, Enter an action";
		std::cin >> action;
		action--;
	}

	return action;
}

std::string HumanPlayer::name(){
	return "Human Player";
}

int PerfectPlayer::getAction(std::shared_ptr<Game> game){
	std::vector<float> scores = this->getBestScores(game);
	float max_score = - std::numeric_limits<float>::max();
	std::vector<int> max_index;

	for (unsigned int i = 0; i < scores.size(); i++){
		if (scores[i] > max_score){
			max_score = scores[i];
			max_index = std::vector<int>();
			max_index.push_back(i);
		} else if(scores[i] == max_score){
			max_index.push_back(i);
		}
	}

	return pickRandomElement(max_index);
}

ConnectSolver::ConnectSolver(std::string opening_book) {
  this->solver.loadBook(opening_book);
}

std::vector<float> ConnectSolver::getBestScores(std::shared_ptr<Game> game){
	std::shared_ptr<ConnectFour> c_game = std::dynamic_pointer_cast<ConnectFour>(game);
    ArrayXf poss = c_game->getPossibleActions();

	float score;
	std::vector<float> scores;
	float max_possible_score = (c_game->getBoardSize()[0]*c_game->getBoardSize()[1])/2;

	for (int i = 0; i < poss.size(); i++){
		if (poss[i] != 0){
			Position pos;
			std::string to_play = c_game->getPlayedMoves()+ std::to_string(i + 1);

    		if (pos.play(to_play) != to_play.size()){
				score = max_possible_score;
    		} else{
	    		score = this->solver.solve(pos, false)*-1;
    		}

    		scores.push_back(score);
    	} else {
    		scores.push_back(-std::numeric_limits<float>::max());
    	}
    }

    return scores;
}

std::string ConnectSolver::name(){
	return "Perfect C4 Player";
}

ProbabilisticPlayer::ProbabilisticPlayer(int deterministicAfter) : deterministicAfter(deterministicAfter) {}

int ProbabilisticPlayer::getAction(std::shared_ptr<Game> game){
	ArrayXf p = this->getProbabilities(game);
	int action;
	//std::cout<< p<<"\n fds" << std::endl;
	if (this->howManyMovesPlayed > this->deterministicAfter){
		p.maxCoeff(&action);
	} else {
		action = pickStochasticElement(p);
	}

	this->howManyMovesPlayed++;

	return action;
}

AlphaZeroPlayer::AlphaZeroPlayer(NNWrapper& nn, MCTS::Config mcts, int deterministicAfter):
								ProbabilisticPlayer(deterministicAfter),
								nn(nn), mcts(mcts){}

ArrayXf AlphaZeroPlayer::getProbabilities(std::shared_ptr<Game> game){
	return MCTS::simulate(game, this->nn, this->mcts);
}

std::string AlphaZeroPlayer::name(){
	return "AZ Player: " + this->nn.getFilename();
}

MCTSPlayer::MCTSPlayer(MCTS::Config mcts, int deterministicAfter):
								ProbabilisticPlayer(deterministicAfter),
								mcts(mcts){}

ArrayXf MCTSPlayer::getProbabilities(std::shared_ptr<Game> game){
	ArrayXf p = MCTS::simulate_random(game, this->mcts);
	return p;
}

std::string MCTSPlayer::name(){
	return "MCTS Player";
}

NNPlayer::NNPlayer(NNWrapper& nn, int deterministicAfter) : ProbabilisticPlayer(deterministicAfter), nn(nn) {}

ArrayXf NNPlayer::getProbabilities(std::shared_ptr<Game> game){
	NN::Input i = NN::Input({game->getBoard()});

	NN::Output res = this->nn.predict(i)[0];

	ArrayXf poss = game->getPossibleActions();
	ArrayXf valid_actions = poss*res.policy;

	std::cout << game->getBoard() << std::endl;;
	std::cout << valid_actions << std::endl;;
	std::cout << "value " <<res.value << std::endl;;
	return valid_actions;
}

std::string NNPlayer::name(){
	return "NN Player: " + this->nn.getFilename();
}

RandomPlayer::RandomPlayer(): seed(-1){ this->gen =  std::mt19937(this->rd()); }
RandomPlayer::RandomPlayer(int seed): seed(seed){  this->gen =  std::mt19937(this->seed); }

int RandomPlayer::getAction(std::shared_ptr<Game> game){
    //std::cout<< "random" << std::endl;
    ArrayXf poss = game->getPossibleActions();
    poss = poss/poss.sum();
    std::discrete_distribution<> dist(poss.data(),poss.data() +  poss.size());

    return dist(this->gen);
}

std::string RandomPlayer::name(){
	return "Randomy Player";
}


#ifndef MATH_AFFINE_HPP
#define MATH_AFFINE_HPP

#include <boost/operators.hpp>

namespace Math {

	template<typename R>
	struct point: boost::operators<point<R>> {
		R x, y, z;
		operator dual<R>(void) const {
			return {{1},{0,x,y,z}};
		}
		point& operator+=(const point<R> &rhs) {
			x += rhs.x;
			y += rhs.y;
			z += rhs.z;
			return *this;
		}
		point& operator-=(const point<R> &rhs) {
			x -= rhs.x;
			y -= rhs.y;
			z -= rhs.z;
			return *this;
		}
		point& operator*=(const R &rhs) {
			return *this = {x*rhs, y*rhs, z*rhs};
		}
		point& operator*=(R && rhs) {
			return *this = {x*rhs, y*rhs, z*rhs};
		}
		point operator*(R rhs) const {
			return Point(x*rhs, y*rhs, z*rhs);
		}
		point(R x = 0, R y = 0, R z = 0):
			x(x), y(y), z(z) {}
		point(const point &p):
			x(p.x), y(p.y), z(p.z) {}
	};

	template<typename S, typename R>
	point<R> operator*(const S &lhs, const point<R> &rhs) {
		point<R> out(rhs);
		return out *= static_cast<R>(lhs);
	}
	template<typename S, typename R>
	point<R> operator*(S && lhs, const point<R> &rhs) {
		return rhs * static_cast<R>(lhs);
	}

	template<typename R>
	struct unit {
		R x, y, z;
		operator dual<R>(void) const {
			return {1,0,0,0,0, x/2, y/2, z/2};
		}
		unit(R x, R y, R z): x(x), y(y), z(z) {}
		unit(unit<R> const& u): x(u.x), y(u.y), z(u.z) {}
	};

	template<typename R>
	struct ray: virtual dual<R> {
		R r;
		unit<R> n;
		ray(R r, R x, R y, R z):
			r(r), n(x, y, z),
			dual<R>(1, 0, 0, 0, 0,
				r*x/2, r*y/2, r*z/2) {}
		ray(R r, const unit<R> &dir):
			r(r), n(dir),
			dual<R>(1, 0, 0, 0, 0,
				r*n.x/2, r*n.y/2, r*n.z/2) {}
		ray(unit<R> const& dir):
			ray(1, dir) {}
		ray(ray<R> const& rhs):
			ray(rhs.r, rhs.n) {}
	};

	template<typename R>
	struct rotor: quat<R> {
		R theta;
		unit<R> n;
		operator dual<R>(void) const {
			return {*this, 0};
		}
		rotor(R theta, R nx, R ny, R nz):
			rotor(theta, unit<R>(nx, ny, nz)) {}
		rotor(R theta, const unit<R>& n):
			theta(theta), n(n),
			quat<R>(cos(theta/2), sin(theta/2)*n.x,
				sin(theta/2)*n.y, sin(theta/2)*n.z) {}
	};

	template<typename R>
	struct pivot: dual<R> {
		ray<R> translation;
		rotor<R> rotation;
		pivot(const ray<R> &offset, const rotor<R> &rotor):
			translation(offset), rotation(rotor),
			dual<R>((~dual<R>(offset))(rotor)) {}
		pivot(ray<R> && offset, rotor<R> && rotor):
			translation(offset), rotation(rotor),
			dual<R> ((~dual<R>(offset))(rotor)) {}
	};
}

#endif


#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE Suites
#include <boost/test/unit_test.hpp>
#define EIGEN_USE_MKL_ALL
#include<iostream>
#include <Eigen/Eigenvalues>
#include"numerics.hpp"
#include"reddm.hpp"
#include"tpoperators.hpp"
#include "files.hpp"
using namespace boost::unit_test;
using boost::unit_test_framework::test_suite;
using namespace Many_Body;
BOOST_AUTO_TEST_SUITE(timeevesting)
BOOST_AUTO_TEST_CASE(timeev)
{
    using Mat= Operators::Mat;
  int L=4;
  double omega=1;
  double gamma=1;
  double t0=1;
  double T=1;
   using HolsteinBasis= TensorProduct<ElectronBasis, PhononBasis>;
        PhononBasis g2{ 2, 1};
  ElectronBasis e( L, 1);
  //  std::cout<< e<<std::endl;

  PhononBasis ph(L, 6);
  //std::cout<< ph<<std::endl;
  HolsteinBasis TP(e, ph);
  std::cout<< TP.dim << std::endl;
        Mat E1=Operators::EKinOperatorL(TP, e, t0, true);
      Mat Ebdag=Operators::BosonCOperator(TP, ph, gamma, true);
      Mat Eb=Operators::BosonDOperator(TP, ph, gamma, true);
      Mat Eph=Operators::NumberOperator(TP, ph, omega,  true);

      //Mat E=Operators::NumberOperatore(TP, e, 1, false);
      //    std::cout<< HH << std::endl;
      Eigen::VectorXd eigenVals(TP.dim);
            Mat H=E1+Eph  +Ebdag + Eb;
          Eigen::MatrixXd HH=Eigen::MatrixXd(H);
  	  Eigen::VectorXd ev(TP.dim);
  	  auto optModes=makeThermalRDMTP(HH, ev, TP, T, false, 0);
  	  double sum=0;
  	  for(auto& l : optModes)
  	    {
  	      std::cout<< l<<std::endl;
  	      sum+=l.sum();
  	    }
	  std::cout << "and the sum of all was "<< sum<<std::endl;
  // //   std::cout<< TP<< std::endl;
  // Eigen::VectorXd v1=Eigen::VectorXd::Zero(TP.dim);
  // v1[1]=1/std::sqrt(2);
  // Eigen::VectorXd v2=Eigen::VectorXd::Zero(TP.dim);
  //   v2[5]=1/std::sqrt(2);
  // 	Eigen::VectorXd V1=v1 +v2;
  // 	Eigen::MatrixXd V2=V1.transpose();
  // 	// std::cout<< V1<<std::endl;
  // 	// std::cout<< V2<<std::endl;
  // 	MatrixXd M=V1*(V2);
  // 	// std::cout<< M<<std::endl;
  // 	// 	std::cout<< g2<<std::endl;
  // 	//	makeRedDM(g2, 0, M);
  // 	makeRedDMTP(TP, ph,  0, M);

 }
BOOST_AUTO_TEST_CASE(vecred)
{

  // int L=4;
  // double omega=1;
  // double gamma=1;
  // double t0=1;
  // double T=2;
  //  using HolsteinBasis= TensorProduct<ElectronBasis, PhononBasis>;
  //       PhononBasis g2{ 2, 1};
  // ElectronBasis e( L, 1);
  // //  std::cout<< e<<std::endl;

  // PhononBasis ph(L, 8);
  // HolsteinBasis TP(e, ph);
  //    std::cout<<TP.dim<<std::endl;
  // Eigen::VectorXcd psi=Eigen::VectorXcd::Random(TP.dim);
  // psi/=psi.norm();
  // auto l=  makeRedDMTP(TP, ph, 0, psi);
  // 	   double sum{0};
  // 	   for(auto&  k : l)
  // 	     {
  // 	       //  std::cout<< k << std::endl;
  // 	       std::cout<< std::endl;
  // 	       std::cout<< "with trace "<< k.trace()<< std::endl;
  // 	       std::cout<< std::endl;
  // 	       //sum+=(k.trace());
  // 	     }
  // 	   std::cout<< sum << std::endl;
  // MatrixXcd rho=psi*psi.adjoint();
  // makeRedDM(ph, 0,  rho);
  // std::cout<< std::endl;
  // std::cout<< std::endl;
  // makeRedDM(ph, 0,  psi);

 }

BOOST_AUTO_TEST_SUITE_END()


#include <Eigen/Dense>

#include "AprilTags/FloatImage.h"
#include "AprilTags/MathUtil.h"
#include "AprilTags/GLine2D.h"
#include "AprilTags/Quad.h"
#include "AprilTags/Segment.h"

namespace AprilTags {

const float Quad::maxQuadAspectRatio = 32;

Quad::Quad(const std::vector<std::pair<float, float> > &p,
           const std::pair<float, float> &opticalCenter)
    : quadPoints(p),
      segments(),
      observedPerimeter(),
      homography(opticalCenter) {
#ifdef STABLE_H
  std::vector<std::pair<float, float> > srcPts;
  srcPts.push_back(std::make_pair(-1, -1));
  srcPts.push_back(std::make_pair(1, -1));
  srcPts.push_back(std::make_pair(1, 1));
  srcPts.push_back(std::make_pair(-1, 1));
  homography.setCorrespondences(srcPts, p);
#else
  homography.addCorrespondence(-1, -1, quadPoints[0].first,
                               quadPoints[0].second);
  homography.addCorrespondence(1, -1, quadPoints[1].first,
                               quadPoints[1].second);
  homography.addCorrespondence(1, 1, quadPoints[2].first, quadPoints[2].second);
  homography.addCorrespondence(-1, 1, quadPoints[3].first,
                               quadPoints[3].second);
#endif

#ifdef INTERPOLATE
  p0 = Eigen::Vector2f(p[0].first, p[0].second);
  p3 = Eigen::Vector2f(p[3].first, p[3].second);
  p01 = (Eigen::Vector2f(p[1].first, p[1].second) - p0);
  p32 = (Eigen::Vector2f(p[2].first, p[2].second) - p3);
#endif
}

std::pair<float, float> Quad::interpolate(float x, float y) {
#ifdef INTERPOLATE
  Eigen::Vector2f r1 = p0 + p01 * (x + 1.) / 2.;
  Eigen::Vector2f r2 = p3 + p32 * (x + 1.) / 2.;
  Eigen::Vector2f r = r1 + (r2 - r1) * (y + 1) / 2;
  return std::pair<float, float>(r(0), r(1));
#else
  return homography.project(x, y);
#endif
}

std::pair<float, float> Quad::interpolate01(float x, float y) {
  return interpolate(2 * x - 1, 2 * y - 1);
}

void Quad::search(const FloatImage &fImage, std::vector<Segment *> &path,
                  Segment &parent, int depth, std::vector<Quad> &quads,
                  const std::pair<float, float> &opticalCenter) {
  // cout << "Searching segment " << parent.getId() << ", depth=" << depth << ",
  // #children=" << parent.children.size() << endl;
  // terminal depth occurs when we've found four segments.
  if (depth == 4) {
    // cout << "Entered terminal depth" << endl; // debug code

    // Is the first segment the same as the last segment (i.e., a loop?)
    if (path[4] == path[0]) {
      // the 4 corners of the quad as computed by the intersection of segments.
      std::vector<std::pair<float, float> > p(4);
      float calculatedPerimeter = 0;
      bool bad = false;
      for (int i = 0; i < 4; i++) {
        // compute intersections between all the lines. This will give us
        // sub-pixel accuracy for the corners of the quad.
        GLine2D linea(std::make_pair(path[i]->getX0(), path[i]->getY0()),
                      std::make_pair(path[i]->getX1(), path[i]->getY1()));
        GLine2D lineb(
            std::make_pair(path[i + 1]->getX0(), path[i + 1]->getY0()),
            std::make_pair(path[i + 1]->getX1(), path[i + 1]->getY1()));

        p[i] = linea.intersectionWith(lineb);
        calculatedPerimeter += path[i]->getLength();

        // no intersection? Occurs when the lines are almost parallel.
        if (p[i].first == -1) bad = true;
      }
      // cout << "bad = " << bad << endl;
      // eliminate quads that don't form a simply connected loop, i.e., those
      // that form an hour glass, or wind the wrong way.
      if (!bad) {
        float t0 =
            std::atan2(p[1].second - p[0].second, p[1].first - p[0].first);
        float t1 =
            std::atan2(p[2].second - p[1].second, p[2].first - p[1].first);
        float t2 =
            std::atan2(p[3].second - p[2].second, p[3].first - p[2].first);
        float t3 =
            std::atan2(p[0].second - p[3].second, p[0].first - p[3].first);

        //  double ttheta = fmod(t1-t0, 2*M_PI) + fmod(t2-t1, 2*M_PI) +
        //    fmod(t3-t2, 2*M_PI) + fmod(t0-t3, 2*M_PI);
        float ttheta = MathUtil::mod2pi(t1 - t0) + MathUtil::mod2pi(t2 - t1) +
                       MathUtil::mod2pi(t3 - t2) + MathUtil::mod2pi(t0 - t3);
        // cout << "ttheta=" << ttheta << endl;
        // the magic value is -2*PI. It should be exact,
        // but we allow for (lots of) numeric imprecision.
        if (ttheta < -7 || ttheta > -5) bad = true;
      }

      if (!bad) {
        float d0 = MathUtil::distance2D(p[0], p[1]);
        float d1 = MathUtil::distance2D(p[1], p[2]);
        float d2 = MathUtil::distance2D(p[2], p[3]);
        float d3 = MathUtil::distance2D(p[3], p[0]);
        float d4 = MathUtil::distance2D(p[0], p[2]);
        float d5 = MathUtil::distance2D(p[1], p[3]);

        // check sizes
        if (d0 < Quad::minimumEdgeLength || d1 < Quad::minimumEdgeLength ||
            d2 < Quad::minimumEdgeLength || d3 < Quad::minimumEdgeLength ||
            d4 < Quad::minimumEdgeLength || d5 < Quad::minimumEdgeLength) {
          bad = true;
          // cout << "tagsize too small" << endl;
        }

        // check aspect ratio
        float dmax = max(max(d0, d1), max(d2, d3));
        float dmin = min(min(d0, d1), min(d2, d3));

        if (dmax > dmin * Quad::maxQuadAspectRatio) {
          bad = true;
          // cout << "aspect ratio too extreme" << endl;
        }
      }

      if (!bad) {
        Quad q(p, opticalCenter);
        q.segments = path;
        q.observedPerimeter = calculatedPerimeter;
        quads.push_back(q);
      }
    }
    return;
  }

  //  if (depth >= 1) // debug code
  // cout << "depth: " << depth << endl;

  // Not terminal depth. Recurse on any children that obey the correct
  // handedness.
  for (unsigned int i = 0; i < parent.children.size(); i++) {
    Segment &child = *parent.children[i];
    //    cout << "  Child " << child.getId() << ":  ";
    // (handedness was checked when we created the children)

    // we could rediscover each quad 4 times (starting from
    // each corner). If we had an arbitrary ordering over
    // points, we can eliminate the redundant detections by
    // requiring that the first corner have the lowest
    // value. We're arbitrarily going to use theta...
    if (child.getTheta() > path[0]->getTheta()) {
      // cout << "theta failed: " << child.getTheta() << " > " <<
      // path[0]->getTheta() << endl;
      continue;
    }
    path[depth + 1] = &child;
    search(fImage, path, child, depth + 1, quads, opticalCenter);
  }
}

}  // namespace


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_MODF_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_MODF_HPP_INCLUDED
#include <boost/simd/function/std.hpp>

#include <boost/simd/function/trunc.hpp>
#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <boost/config.hpp>
#include <boost/fusion/include/std_pair.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( modf_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          )
  {
    BOOST_FORCEINLINE void operator() ( A0 a0, A0 & frac,A0 & ent) const BOOST_NOEXCEPT
    {
      ent = simd::trunc(a0);
      frac = a0 - ent;
    }
  };
  BOOST_DISPATCH_OVERLOAD ( modf_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() ( A0 a0,A0 & ent) const BOOST_NOEXCEPT
    {
      A0 frac;
      simd::modf(a0,frac,ent);
      return frac;
    }
  };
  BOOST_DISPATCH_OVERLOAD ( modf_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          )
  {
    using result_t = std::pair<A0,A0>;
    BOOST_FORCEINLINE result_t operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      A0 first, second;
      boost::simd::modf(a0, first, second);
      return {first, second};
    }
  };
  BOOST_DISPATCH_OVERLOAD ( modf_
                          , (typename A0)
                          , bd::cpu_
                          , bs::std_tag
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          , bd::scalar_ < bd::arithmetic_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() (const std_tag &,  A0 a0, A0 & ent) const BOOST_NOEXCEPT
    {
      A0 frac;
      frac = std::modf(a0,&ent);
      return frac;
    }
  };

} } }

#endif


/*
 * ScalarCurveConfig.hpp
 *
 *  Created on: Mar 5, 2015
 *      Author: Paul Furgale, Renaud Dube, Péter Fankhauser
 *   Institute: ETH Zurich, Autonomous Systems Lab
 */

#pragma once

#include <Eigen/Core>

namespace curves {

template <int N>
struct VectorSpaceConfig {
  typedef Eigen::Matrix<double,N,1> ValueType;
  typedef Eigen::Matrix<double,N,1> DerivativeType;
};

} // namespace


/* boost random_test.cpp various tests
 *
 * Copyright Jens Maurer 2000
 * Distributed under 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)
 *
 * $Id: random_test.cpp 60755 2010-03-22 00:45:06Z steven_watanabe $
 */

#if defined(BOOST_MSVC) && BOOST_MSVC <= 1300
#pragma warning( disable : 4786 )
#endif

#include <iostream>
#include <sstream>
#include <string>
#include <cmath>
#include <iterator>
#include <vector>
#include <boost/random.hpp>
#include <boost/config.hpp>

#include <boost/test/test_tools.hpp>
#include <boost/test/included/test_exec_monitor.hpp>

#ifdef BOOST_NO_STDC_NAMESPACE
  namespace std { using ::abs; using ::fabs; using ::pow; }
#endif

/*
 * General portability note:
 * MSVC mis-compiles explicit function template instantiations.
 * For example, f<A>() and f<B>() are both compiled to call f<A>().
 * BCC is unable to implicitly convert a "const char *" to a std::string
 * when using explicit function template instantiations.
 *
 * Therefore, avoid explicit function template instantiations.
 */

/*
 * A few equidistribution tests
 */

// yet to come...

template<class Generator>
void check_uniform_int(Generator & gen, int iter)
{
  std::cout << "testing uniform_int(" << (gen.min)() << "," << (gen.max)()
            << ")" << std::endl;
  int range = (gen.max)()-(gen.min)()+1;
  std::vector<int> bucket(range);
  for(int j = 0; j < iter; j++) {
    int result = gen();
    if(result < (gen.min)() || result > (gen.max)())
      std::cerr << "   ... delivers " << result << std::endl;
    else
      bucket[result-(gen.min)()]++;
  }
  int sum = 0;
  // use a different variable name "k", because MSVC has broken "for" scoping
  for(int k = 0; k < range; k++)
    sum += bucket[k];
  double avg = static_cast<double>(sum)/range;
  double p = 1 / static_cast<double>(range);
  double threshold = 2*std::sqrt(static_cast<double>(iter)*p*(1-p));
  for(int i = 0; i < range; i++) {
    if(std::fabs(bucket[i] - avg) > threshold) {
      // 95% confidence interval
      std::cout << "   ... has bucket[" << i << "] = " << bucket[i]
                << "  (distance " << (bucket[i] - avg) << ")"
                << std::endl;
    }
  }
}

template<class Generator>
void test_uniform_int(Generator & gen)
{
  typedef boost::uniform_int<int> int_gen;

  // large range => small range (modulo case)
  typedef boost::variate_generator<Generator&, int_gen> level_one;

  level_one uint12(gen, int_gen(1,2));
  BOOST_CHECK((uint12.distribution().min)() == 1);
  BOOST_CHECK((uint12.distribution().max)() == 2);
  check_uniform_int(uint12, 100000);
  level_one uint16(gen, int_gen(1,6));
  check_uniform_int(uint16, 100000);

  // test chaining to get all cases in operator()

  // identity map
  typedef boost::variate_generator<level_one&, int_gen> level_two;
  level_two uint01(uint12, int_gen(0, 1));
  check_uniform_int(uint01, 100000);

  // small range => larger range
  level_two uint05(uint12, int_gen(-3, 2));
  check_uniform_int(uint05, 100000);

  // small range => larger range
  level_two uint099(uint12, int_gen(0, 99));
  check_uniform_int(uint099, 100000);

  // larger => small range, rejection case
  typedef boost::variate_generator<level_two&, int_gen> level_three;
  level_three uint1_4(uint05, int_gen(1, 4));
  check_uniform_int(uint1_4, 100000);

  typedef boost::uniform_int<boost::uint8_t> int8_gen;
  typedef boost::variate_generator<Generator&, int8_gen> gen8_t;

  gen8_t gen8_03(gen, int8_gen(0, 3));

  // use the full range of the type, where the destination
  // range is a power of the source range
  typedef boost::variate_generator<gen8_t, int8_gen> uniform_uint8;
  uniform_uint8 uint8_0255(gen8_03, int8_gen(0, 255));
  check_uniform_int(uint8_0255, 100000);

  // use the full range, but a generator whose range is not
  // a root of the destination range.
  gen8_t gen8_02(gen, int8_gen(0, 2));
  uniform_uint8 uint8_0255_2(gen8_02, int8_gen(0, 255));
  check_uniform_int(uint8_0255_2, 100000);

  // expand the range to a larger type.
  typedef boost::variate_generator<gen8_t, int_gen> uniform_uint_from8;
  uniform_uint_from8 uint0300(gen8_03, int_gen(0, 300));
  check_uniform_int(uint0300, 100000);
}

#if defined(BOOST_MSVC) && _MSC_VER < 1300

// These explicit instantiations are necessary, otherwise MSVC does
// not find the <boost/operators.hpp> inline friends.
// We ease the typing with a suitable preprocessor macro.
#define INSTANT(x) \
template class boost::uniform_smallint<x>; \
template class boost::uniform_int<x>; \
template class boost::uniform_real<x>; \
template class boost::bernoulli_distribution<x>; \
template class boost::geometric_distribution<x>; \
template class boost::triangle_distribution<x>; \
template class boost::exponential_distribution<x>; \
template class boost::normal_distribution<x>; \
template class boost::uniform_on_sphere<x>; \
template class boost::lognormal_distribution<x>;

INSTANT(boost::minstd_rand0)
INSTANT(boost::minstd_rand)
INSTANT(boost::ecuyer1988)
INSTANT(boost::kreutzer1986)
INSTANT(boost::hellekalek1995)
INSTANT(boost::mt19937)
INSTANT(boost::mt11213b)

#undef INSTANT
#endif

#if !defined(BOOST_NO_INT64_T) && !defined(BOOST_NO_INTEGRAL_INT64_T)
// testcase by Mario Rutti
class ruetti_gen
{
public:
  ruetti_gen() : state((max)() - 1) {}
  typedef boost::uint64_t result_type;
  result_type min BOOST_PREVENT_MACRO_SUBSTITUTION () const { return 0; }
  result_type max BOOST_PREVENT_MACRO_SUBSTITUTION () const { return std::numeric_limits<result_type>::max BOOST_PREVENT_MACRO_SUBSTITUTION (); }
  result_type operator()() { return state--; }
private:
  result_type state;
};

void test_overflow_range()
{
  ruetti_gen gen;
  boost::variate_generator<ruetti_gen, boost::uniform_int<> >
    rng(gen, boost::uniform_int<>(0, 10));
  for (int i=0;i<10;i++)
    (void) rng();
}
#else
void test_overflow_range()
{ }
#endif

template <typename EngineT>
struct rand_for_random_shuffle
{
  explicit rand_for_random_shuffle(EngineT &engine)
    : m_engine(engine)
  { }

  template <typename IntT>
  IntT operator()(IntT upperBound)
  {
    assert(upperBound > 0);

    if (upperBound == 1)
    {
      return 0;
    }

    typedef boost::uniform_int<IntT> distribution_type;
    typedef boost::variate_generator<EngineT &, distribution_type> generator_type;

    return generator_type(m_engine, distribution_type(0, upperBound - 1))();
  }

  EngineT &m_engine;

};

// Test that uniform_int<> can be used with std::random_shuffle
// Author: Jos Hickson
void test_random_shuffle()
{
    typedef boost::uniform_int<> distribution_type;
    typedef boost::variate_generator<boost::mt19937 &, distribution_type> generator_type;

    boost::mt19937 engine1(1234);
    boost::mt19937 engine2(1234);

    rand_for_random_shuffle<boost::mt19937> referenceRand(engine1);

    distribution_type dist(0,10);
    generator_type testRand(engine2, dist);

    std::vector<int> referenceVec;

    for (int i = 0; i < 200; ++i)
    {
      referenceVec.push_back(i);
    }

    std::vector<int> testVec(referenceVec);

    std::random_shuffle(referenceVec.begin(), referenceVec.end(), referenceRand);
    std::random_shuffle(testVec.begin(), testVec.end(), testRand);

    typedef std::vector<int>::iterator iter_type;
    iter_type theEnd(referenceVec.end());

    for (iter_type referenceIter(referenceVec.begin()), testIter(testVec.begin());
         referenceIter != theEnd;
         ++referenceIter, ++testIter)
    {
      BOOST_CHECK_EQUAL(*referenceIter, *testIter);
    }
}

int test_main(int, char*[])
{

#if !defined(__INTEL_COMPILER) || !defined(_MSC_VER) || __INTEL_COMPILER > 700
  boost::mt19937 mt;
  test_uniform_int(mt);

  // bug report from Ken Mahler:  This used to lead to an endless loop.
  typedef boost::uniform_int<unsigned int> uint_dist;
  boost::minstd_rand mr;
  boost::variate_generator<boost::minstd_rand, uint_dist> r2(mr,
                                                            uint_dist(0, 0xffffffff));
  r2();
  r2();

  // bug report from Fernando Cacciola:  This used to lead to an endless loop.
  // also from Douglas Gregor
  boost::variate_generator<boost::minstd_rand, boost::uniform_int<> > x(mr, boost::uniform_int<>(0, 8361));
  (void) x();

  // bug report from Alan Stokes and others: this throws an assertion
  boost::variate_generator<boost::minstd_rand, boost::uniform_int<> > y(mr, boost::uniform_int<>(1,1));
  std::cout << "uniform_int(1,1) " << y() << ", " << y() << ", " << y()
            << std::endl;

  test_overflow_range();
  test_random_shuffle();

  return 0;
#else
  std::cout << "Intel 7.00 on Win32 loops, so the test is disabled\n";
  return 1;
#endif
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_EXPRECNEG_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_EXPRECNEG_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-exponential
    Function object implementing exprecneg capabilities

    Computes the  function: \f$e^{-\frac1x}\f$

    @par Semantic:

    For every parameter of floating type T0

    @code
    T r = exprecneg(x);
    @endcode

    is equivalent to
    @code
    T r = exp(-rec((x)));
    @endcode

    @see exp, exprecnegc

  **/
  const boost::dispatch::functor<tag::exprecneg_> exprecneg = {};
} }
#endif

#include <boost/simd/function/scalar/exprecneg.hpp>
#include <boost/simd/function/simd/exprecneg.hpp>

#endif


// Copyright John Maddock 2006.
// Copyright Paul A. Bristow 2007, 2009
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#define BOOST_MATH_OVERFLOW_ERROR_POLICY ignore_error

#include <boost/math/concepts/real_concept.hpp>
#include <boost/math/special_functions/math_fwd.hpp>
#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>
#include <boost/test/results_collector.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/math/tools/stats.hpp>
#include <boost/math/tools/test.hpp>
#include <boost/math/constants/constants.hpp>
#include <boost/type_traits/is_floating_point.hpp>
#include <boost/array.hpp>
#include "functor.hpp"
#include "table_type.hpp"
#include "handle_test_result.hpp"

#ifndef SC_
#define SC_(x) static_cast<typename table_type<T>::type>(BOOST_JOIN(x, L))
#endif

#define BOOST_CHECK_CLOSE_EX(a, b, prec, i) \
   {\
      unsigned int failures = boost::unit_test::results_collector.results( boost::unit_test::framework::current_test_case().p_id ).p_assertions_failed;\
      BOOST_CHECK_CLOSE(a, b, prec); \
      if(failures != boost::unit_test::results_collector.results( boost::unit_test::framework::current_test_case().p_id ).p_assertions_failed)\
      {\
         std::cerr << "Failure was at row " << i << std::endl;\
         std::cerr << std::setprecision(35); \
         std::cerr << "{ " << data[i][0] << " , " << data[i][1] << " , " << data[i][2];\
         std::cerr << " , " << data[i][3] << " , " << data[i][4] << " , " << data[i][5] << " } " << std::endl;\
      }\
   }

template <class Real, class T>
void do_test_gamma_2(const T& data, const char* type_name, const char* test_name)
{
   //
   // test gamma_p_inva(T, T) against data:
   //
   using namespace std;
   typedef typename T::value_type row_type;
   typedef Real                   value_type;

   std::cout << test_name << " with type " << type_name << std::endl;

   //
   // These sanity checks test for a round trip accuracy of one half
   // of the bits in T, unless T is type float, in which case we check
   // for just one decimal digit.  The problem here is the sensitivity
   // of the functions, not their accuracy.  This test data was generated
   // for the forward functions, which means that when it is used as
   // the input to the inverses then it is necessarily inexact.  This rounding
   // of the input is what makes the data unsuitable for use as an accuracy check,
   // and also demonstrates that you can't in general round-trip these functions.
   // It is however a useful sanity check.
   //
   value_type precision = static_cast<value_type>(ldexp(1.0, 1-boost::math::policies::digits<value_type, boost::math::policies::policy<> >()/2)) * 100;
   if(boost::math::policies::digits<value_type, boost::math::policies::policy<> >() < 50)
      precision = 1;   // 1% or two decimal digits, all we can hope for when the input is truncated to float

   for(unsigned i = 0; i < data.size(); ++i)
   {
      //
      // These inverse tests are thrown off if the output of the
      // incomplete gamma is too close to 1: basically there is insuffient
      // information left in the value we're using as input to the inverse
      // to be able to get back to the original value.
      //
      if(Real(data[i][5]) == 0)
         BOOST_CHECK_EQUAL(boost::math::gamma_p_inva(Real(data[i][1]), Real(data[i][5])), boost::math::tools::max_value<value_type>());
      else if((1 - Real(data[i][5]) > 0.001) && (fabs(Real(data[i][5])) > 2 * boost::math::tools::min_value<value_type>()))
      {
         value_type inv = boost::math::gamma_p_inva(Real(data[i][1]), Real(data[i][5]));
         BOOST_CHECK_CLOSE_EX(Real(data[i][0]), inv, precision, i);
      }
      else if(1 == Real(data[i][5]))
         BOOST_CHECK_EQUAL(boost::math::gamma_p_inva(Real(data[i][1]), Real(data[i][5])), boost::math::tools::min_value<value_type>());
      else if(Real(data[i][5]) > 2 * boost::math::tools::min_value<value_type>())
      {
         // not enough bits in our input to get back to x, but we should be in
         // the same ball park:
         value_type inv = boost::math::gamma_p_inva(Real(data[i][1]), Real(data[i][5]));
         BOOST_CHECK_CLOSE_EX(Real(data[i][0]), inv, 100, i);
      }

      if(Real(data[i][3]) == 0)
         BOOST_CHECK_EQUAL(boost::math::gamma_q_inva(Real(data[i][1]), Real(data[i][3])), boost::math::tools::min_value<value_type>());
      else if((1 - Real(data[i][3]) > 0.001)
         && (fabs(Real(data[i][3])) > 2 * boost::math::tools::min_value<value_type>())
         && (fabs(Real(data[i][3])) > 2 * boost::math::tools::min_value<double>()))
      {
         value_type inv = boost::math::gamma_q_inva(Real(data[i][1]), Real(data[i][3]));
         BOOST_CHECK_CLOSE_EX(Real(data[i][0]), inv, precision, i);
      }
      else if(1 == Real(data[i][3]))
         BOOST_CHECK_EQUAL(boost::math::gamma_q_inva(Real(data[i][1]), Real(data[i][3])), boost::math::tools::max_value<value_type>());
      else if(Real(data[i][3]) > 2 * boost::math::tools::min_value<value_type>())
      {
         // not enough bits in our input to get back to x, but we should be in
         // the same ball park:
         value_type inv = boost::math::gamma_q_inva(Real(data[i][1]), Real(data[i][3]));
         BOOST_CHECK_CLOSE_EX(Real(data[i][0]), inv, 100, i);
      }
   }
   std::cout << std::endl;
}

template <class Real, class T>
void do_test_gamma_inva(const T& data, const char* type_name, const char* test_name)
{
   typedef typename T::value_type row_type;
   typedef Real                   value_type;

   typedef value_type (*pg)(value_type, value_type);
#if defined(BOOST_MATH_NO_DEDUCED_FUNCTION_POINTERS)
   pg funcp = boost::math::gamma_p_inva<value_type, value_type>;
#else
   pg funcp = boost::math::gamma_p_inva;
#endif

   boost::math::tools::test_result<value_type> result;

   std::cout << "Testing " << test_name << " with type " << type_name
      << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n";

   //
   // test gamma_p_inva(T, T) against data:
   //
   result = boost::math::tools::test_hetero<Real>(
      data,
      bind_func<Real>(funcp, 0, 1),
      extract_result<Real>(2));
   handle_test_result(result, data[result.worst()], result.worst(), type_name, "boost::math::gamma_p_inva", test_name);
   //
   // test gamma_q_inva(T, T) against data:
   //
#if defined(BOOST_MATH_NO_DEDUCED_FUNCTION_POINTERS)
   funcp = boost::math::gamma_q_inva<value_type, value_type>;
#else
   funcp = boost::math::gamma_q_inva;
#endif
   result = boost::math::tools::test_hetero<Real>(
      data,
      bind_func<Real>(funcp, 0, 1),
      extract_result<Real>(3));
   handle_test_result(result, data[result.worst()], result.worst(), type_name, "boost::math::gamma_q_inva", test_name);
}

template <class T>
void test_gamma(T, const char* name)
{
#ifndef TEST_UDT
   //
   // The actual test data is rather verbose, so it's in a separate file
   //
   // First the data for the incomplete gamma function, each
   // row has the following 6 entries:
   // Parameter a, parameter z,
   // Expected tgamma(a, z), Expected gamma_q(a, z)
   // Expected tgamma_lower(a, z), Expected gamma_p(a, z)
   //
#  include "igamma_med_data.ipp"

   do_test_gamma_2<T>(igamma_med_data, name, "Running round trip sanity checks on incomplete gamma medium sized values");

#  include "igamma_small_data.ipp"

   do_test_gamma_2<T>(igamma_small_data, name, "Running round trip sanity checks on incomplete gamma small values");

#  include "igamma_big_data.ipp"

   do_test_gamma_2<T>(igamma_big_data, name, "Running round trip sanity checks on incomplete gamma large values");

#endif

#  include "igamma_inva_data.ipp"

   do_test_gamma_inva<T>(igamma_inva_data, name, "Incomplete gamma inverses.");
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_REM_PIO2_CEPHES_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_REM_PIO2_CEPHES_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-trigonometric
    Function object implementing rem_pio2_cephes capabilities

    Computes the remainder modulo \f$\pi/2\f$ with cephes algorithm,
    and the angle quadrant between 0 and 3.
    This is a quick version accurate if the input is in \f$[-20\pi,20\pi]\f$.

    @par Semantic:

    For every parameters of floating type T:

    @code
    T r;
    as_integer<T> n;
    rem_pio2_cephes_(x, n, r);
    @endcode

    is similar to:

    @code
    as_integer<T> n = idivround2even(x, Pio_2<T>());
    T r =  remainder(x, Pio_2<T>());
    @endcode

    @see rem_pio2, rem_pio2_straight,rem_2pi, rem_pio2_medium,

  **/
  const boost::dispatch::functor<tag::rem_pio2_cephes_> rem_pio2_cephes = {};
} }
#endif

#include <boost/simd/function/scalar/rem_pio2_cephes.hpp>
#include <boost/simd/function/simd/rem_pio2_cephes.hpp>

#endif


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_ILOG2_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_ILOG2_HPP_INCLUDED

#include <boost/simd/detail/nsm.hpp>
#include <boost/simd/function/clz.hpp>
#include <boost/simd/function/exponent.hpp>
#include <boost/simd/detail/dispatch/function/overload.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;

  BOOST_DISPATCH_OVERLOAD(ilog2_, (typename A0), bd::cpu_, bd::scalar_<bd::floating_<A0>>)
  {
    BOOST_FORCEINLINE bd::as_integer_t<A0> operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      BOOST_ASSERT_MSG( a0 > 0
                      , "Logarithm is not defined for zero or negative values." );
      return exponent(a0);
    }
  };

  BOOST_DISPATCH_OVERLOAD(ilog2_, (typename A0), bd::cpu_, bd::scalar_<bd::arithmetic_<A0>>)
  {
    BOOST_FORCEINLINE A0 operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      BOOST_ASSERT_MSG( a0 > 0
                      , "Logarithm is not defined for zero or negative values." );
      return A0(sizeof(A0)*8-boost::simd::clz(a0)-1);
    }
  };

#if defined(BOOST_MSVC)
  BOOST_DISPATCH_OVERLOAD(ilog2_, (typename A0), bd::cpu_, bd::scalar_<bd::integer_<A0>>)
  {
    BOOST_FORCEINLINE A0 operator()(A0 a0) const BOOST_NOEXCEPT
    {
      BOOST_ASSERT_MSG( a0 > 0, "Logarithm is not defined for zero or negative values." );
      return impl(a0, typename nsm::bool_<sizeof(A0) <= 4>::type());
    }

    static BOOST_FORCEINLINE A0 impl( A0  a0,  tt::true_type const &) BOOST_NOEXCEPT
    {
      unsigned long index;
      BOOST_VERIFY(::_BitScanReverse(&index, a0));
      return static_cast<A0>(index);
    }

    #if defined(_WIN64)
    static BOOST_FORCEINLINE A0 impl(A0 a0, tt::false_type const &) BOOST_NOEXCEPT
    {
      unsigned long index;
      BOOST_VERIFY(::_BitScanReverse64(&index, a0));
      return static_cast<A0>(index);
    }
    #else
    static BOOST_FORCEINLINE A0 impl(A0 a0, tt::false_type const &) BOOST_NOEXCEPT
    {
      return static_cast<A0>(sizeof(A0)*8-boost::simd::clz(a0)-1);
    }
    #endif
  };
  #endif
} } }

#endif


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_SSE_FAST_RSQRT_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_SSE_FAST_RSQRT_HPP_INCLUDED
#if defined(BOOST_SIMD_HAS_SSE2_SUPPORT)

#include <boost/simd/arithmetic/functions/fast_rsqrt.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( fast_rsqrt_
                                    , boost::simd::tag::sse_
                                    , (A0)
                                    , (scalar_< single_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(float a0) const
    {
      float r;
      _mm_store_ss( &r, _mm_rsqrt_ss( _mm_load_ss( &a0 ) ) );
      return r * (( 3.f - (r * r) * a0) * 0.5f);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( fast_rsqrt_
                                    , boost::simd::tag::sse_
                                    , (A0)
                                    , (scalar_< double_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(double a0) const
    {
      float arg = a0;
      _mm_store_ss( & arg, _mm_rsqrt_ss( _mm_load_ss( & arg ) ) );

      double  r = arg;

      r *= ((3.0 - (r * r) * a0) * 0.5);
      r *= ((3.0 - (r * r) * a0) * 0.5);

      return r;
    }
  };
} } }

#endif

#endif


/* Copyright (C) 2021 Intel Corporation
 * 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 <helib/helib.h>
#include <helib/debugging.h>

#include <NTL/BasicThreadPool.h>

#include "gtest/gtest.h"
#include "test_common.h"

#include "../src/macro.h" // Private header
// only run if HEXL has been linked.
#ifdef USE_INTEL_HEXL
#include "../src/intelExt.h"       // Private header
#include "../src/PrimeGenerator.h" // Private header

namespace {

// FIXME Copied from GTestGeneral. Should really have common functionality.
::testing::AssertionResult ciphertextMatches(const helib::EncryptedArray& ea,
                                             const helib::SecKey& sk,
                                             const helib::PtxtArray& p,
                                             const helib::Ctxt& c)
{
  helib::PtxtArray pp(ea);
  pp.decrypt(c, sk);
  if (pp == p) {
    return ::testing::AssertionSuccess();
  } else {
    return ::testing::AssertionFailure()
           << "Ciphertext does not match plaintext:" << std::endl
           << "p = " << p << std::endl
           << "pp = " << pp << std::endl;
  }
}

struct Parameters
{
  Parameters(unsigned m,
             unsigned p,
             unsigned r,
             unsigned bits,
             const std::vector<long>& gens = {},
             const std::vector<long>& ords = {}) :
      m(m), p(p), r(r), bits(bits), gens(gens), ords(ords){};

  const unsigned m;
  const unsigned p;
  const unsigned r;
  const unsigned bits;
  const std::vector<long> gens;
  const std::vector<long> ords;

  friend std::ostream& operator<<(std::ostream& os, const Parameters& params)
  {
    return os << "{"
              << "m = " << params.m << ", "
              << "p = " << params.p << ", "
              << "r = " << params.r << ", "
              << "gens = " << helib::vecToStr(params.gens) << ", "
              << "ords = " << helib::vecToStr(params.ords) << ", "
              << "bits = " << params.bits << "}";
  }
};

class TestHEXL_BGV : public ::testing::TestWithParam<Parameters>
{
protected:
  const unsigned long m;
  const unsigned long p;
  const unsigned long r;
  const unsigned long bits;
  helib::Context context;
  helib::SecKey secretKey;
  const helib::PubKey publicKey;
  const helib::EncryptedArray& ea;

  TestHEXL_BGV() :
      m(GetParam().m),
      p(GetParam().p),
      r(GetParam().r),
      bits(GetParam().bits),
      context(helib::ContextBuilder<helib::BGV>()
                  .m(m)
                  .p(p)
                  .r(r)
                  .bits(bits)
                  .build()),
      secretKey(context),
      publicKey((secretKey.GenSecKey(),
                 helib::addSome1DMatrices(secretKey),
                 secretKey)),
      ea(context.getEA())
  {}

  virtual void SetUp() override
  {
    NTL::SetNumThreads(1);
    if (helib_test::verbose) {
      ea.getPAlgebra().printout();
      std::cout << "r = " << context.getAlMod().getR() << std::endl;
      std::cout << "ctxtPrimes=" << context.getCtxtPrimes()
                << ", specialPrimes=" << context.getSpecialPrimes() << "\n"
                << std::endl;
    }

    helib::setupDebugGlobals(&secretKey, context.shareEA());
  }

  virtual void TearDown() override { helib::cleanupDebugGlobals(); }
};

struct HEXL_params
{
  const long N; // phim
  const long modulus;
  HEXL_params(long _N, long _modulus) : N(_N), modulus(_modulus) {}
};

class TestHEXL : public ::testing::TestWithParam<HEXL_params>
{
protected:
  const long N; // phim
  const long modulus;

  TestHEXL() : N(GetParam().N), modulus(GetParam().modulus) {}
};

TEST(TestHEXL, hexlInUse)
{
  // This test does not use HEXL_params because algebra must be d == 1

  long N = 64;
  long modulus = 769;

  std::vector<long> args(N);
  for (size_t i = 0; i < args.size(); ++i) {
    args[i] = i + 1;
  }
  auto expected_outputs = args;

  intel::FFTFwd(args.data(), args.data(), N, modulus);
  intel::FFTRev1(args.data(), args.data(), N, modulus);

  EXPECT_TRUE(std::equal(args.begin(), args.end(), expected_outputs.begin()));
}

TEST_P(TestHEXL_BGV, multiplyTwoCtxts)
{
  helib::PtxtArray p0(ea), p1(ea);
  p0.random();
  p1.random();

  helib::Ctxt c0(publicKey), c1(publicKey);
  p0.encrypt(c0);
  p1.encrypt(c1);

  p0 *= p1;
  c0 *= c1;

  EXPECT_TRUE(ciphertextMatches(ea, secretKey, p0, c0));
}

TEST_P(TestHEXL_BGV, encryptDecrypt)
{
  NTL::SetNumThreads(1);
  helib::PtxtArray p0(ea);

  std::vector<long> v0 = {0, 5};

  p0.load(v0);

  helib::Ctxt c0(publicKey);
  p0.encrypt(c0);

  EXPECT_TRUE(ciphertextMatches(ea, secretKey, p0, c0));
}

TEST_P(TestHEXL, CModulusFFT)
{
  NTL::SetNumThreads(1);

  long m = 2 * N;
  helib::PAlgebra zms(m, modulus);

  std::cout << "***SP bits: " << HELIB_SP_NBITS << '\n';

  helib::PrimeGenerator prime_generator(HELIB_SP_NBITS, m);
  long q = prime_generator.next();
  helib::Cmodulus cmod(zms, q, 0);

  NTL::ZZX poly;
  poly.SetLength(2);
  poly[0] = 0;
  poly[1] = 5;
  //  std::cout << "TEST: input " << poly << std::endl;

  NTL::vec_long transformed;
  cmod.FFT(transformed, poly);
  //  std::cout << "TEST: transformed " << transformed << std::endl;

  NTL::zz_pX inverse;
  cmod.iFFT(inverse, transformed);
  //  std::cout << "TEST: inverse " << inverse << std::endl;

  NTL::ZZX inverse_conv = NTL::conv<NTL::ZZX>(inverse);
  EXPECT_EQ(inverse_conv, poly);
}

// clang-format off
INSTANTIATE_TEST_SUITE_P(typicalParameters, TestHEXL_BGV, ::testing::Values(
    //Parameters(16, 3, 1, 300) // m power of 2.
    Parameters(8, 769, 1, 50) // m power of 2.
    ));

INSTANTIATE_TEST_SUITE_P(typicalParameters, TestHEXL, ::testing::Values(
    //Parameters(16, 3, 1, 300) // m power of 2.
    HEXL_params(/*phim=*/8, /*p=*/769), // m power of 2, d == 1.
    HEXL_params(/*phim=*/64, /*p=*/769), // m power of 2, d == 1.
    HEXL_params(/*m=512, phim=*/256, /*p=*/769) // m power of 2, d == 2.
    ));
// clang-format on

} // namespace

#else

namespace {

TEST(TestHEXL, noTestRequired)
{
  std::cout << "***SP bits: " << HELIB_SP_NBITS << '\n';
}

} // namespace

#endif // USE_INTEL_HEXL