#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
#include <Structs/Graphs/dynamicGraph.h>
#include <Structs/Graphs/adjacencyListImpl.h>
#include <Heuristics/blind.h>
#include <Heuristics/ideal.h>
#include <Heuristics/boundedIdeal.h>
#include <Algorithms/multicriteriaDijkstra.h>
#include <Algorithms/multicriteriaGraph.h>
#include <Algorithms/namoaStar2.h>
#include <Algorithms/namoaStar.h>
#include <Algorithms/multicriteriaArc.h>
#include <Utilities/timer.h>
#include <boost/program_options.hpp>

#define NUM_CRITERIA 2

namespace po = boost::program_options;

class Results
{
public:
    Results( const std::vector< std::pair<unsigned int, unsigned int> >& queries):m_queries(queries)
    {
    }

    void add( const std::string& header, const std::vector< double >& results)
    {
        m_headers.push_back(header);
        m_data.push_back(results);
    }

    void print(std::ostream& out)
    {
        out << "source\tdestination";
        for( unsigned int i = 0; i < m_headers.size(); ++i)
        {
            out << "\t" << m_headers[i];
        }
        out << "\n";
        for( unsigned int i = 0; i < m_queries.size(); ++i)
        {
            out << m_queries[i].first << "\t" << m_queries[i].second;
            for( unsigned int j = 0; j < m_data.size(); ++j)
            {
                out << "\t" << m_data[j][i];
            }
            out << "\n";
        }
    }

private:
    std::vector< std::string> m_headers;
    std::vector< std::vector<double> > m_data;
    const std::vector< std::pair<unsigned int, unsigned int> >& m_queries;
};

template< class DijkstraVariant, typename GraphType>
void runQueries( GraphType& G, std::vector< std::pair<unsigned int,unsigned int> >& queries, std::vector<typename GraphType::NodeDescriptor>& ids, Results& results, const std::string& graphname, const std::string& algoname)
{
    std::vector<double> times;
    std::vector<double> paths;
    std::vector<double> generatedLabels;
    typedef typename GraphType::NodeIterator NodeIterator;
    typename GraphType::NodeIterator s,t;
    unsigned int sourceId, targetId;
    unsigned int timestamp = 0;
    //create dijkstra
    DijkstraVariant dijkstra(G, NUM_CRITERIA, &timestamp);
    //create output message
    std::string message("Experiments at ");
    message.append( graphname + " " + algoname);
    ProgressBar show_progress( queries.size(), message);
    std::cout << message << std::endl;
    //run queries
    Timer timer;
    unsigned int query_n = 1;
    for( std::vector< std::pair<unsigned int,unsigned int> >::iterator it = queries.begin(); it != queries.end(); ++it)
    {
        //clear nodes
        NodeIterator u, lastnode;
        for( u = G.beginNodes(), lastnode = G.endNodes(); u != lastnode; ++u)
        {
            u->g_op.clear();
            u->g_cl.clear();
            u->labels.clear();
        }
        sourceId = it->first;
        targetId = it->second;
        std::cout << query_n << ") " << sourceId << "->" << targetId << std::endl;
        ++query_n;
        s = G.getNodeIterator( ids[sourceId]);
        t = G.getNodeIterator( ids[targetId]);
		std::cout << "Initializing...\n";
		timer.start();
        dijkstra.init( s, t, NUM_CRITERIA);
		std::cout << "Running query:\n";
        dijkstra.runQuery( s, t);
        times.push_back(timer.getElapsedTime());
        paths.push_back(t->g_cl.size());
        //paths.push_back(t->labels.size());
        generatedLabels.push_back(dijkstra.getGeneratedLabels());
        std::cout << "\tTime:\t" << times[times.size()-1] << "sec\n";
        std::cout << "\tNon-dominated solutions:\t" << t->g_cl.size() << "\n";
		std::cout << "\tGenerated labels: " << dijkstra.getGeneratedLabels() << "\n\n";
    }
    results.add(graphname + " " + algoname,times);
    results.add(graphname + " " + algoname,generatedLabels);
    results.add(graphname + " " + algoname + " |C|",paths);
}

template< typename GraphType>
void runExperimentsAt( GraphType& G, std::vector< std::pair<unsigned int,unsigned int> >& queries, std::vector<typename GraphType::NodeDescriptor>& ids, Results& results, const std::string& name, const unsigned int& algorithmVariant)
{
    switch( algorithmVariant)
    {
    case 1:
        // output message for all queries
        std::cout << "with NAMOA* (blind) ...\n\n";
        runQueries<NamoaStar2<GraphType,BlindHeuristic> >( G, queries, ids, results, name, "NAMOA*2_Blind");
        break;
    case 2:
        std::cout << "with NAMOA*_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,TCHeuristic> >( G, queries, ids, results, name, "NAMOA*2_TC");
        break;
    case 3:
        std::cout << "with NAMOA*_bound_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,BoundedTCHeuristic> >( G, queries, ids, results, name, "NAMOA*2_BTC");
        //std::cout << "with NAMOA* Arc Flags (" << name << ") ...\n\n";
        //runQueries<NamoaStarArc<GraphType,GreatCircleHeuristic> >( G, queries, ids, gChecker, showOnScreen);
        break;
    case 0: // default
        std::cout << "with NAMOA* (blind) ...\n\n";
        runQueries<NamoaStar2<GraphType,BlindHeuristic> >( G, queries, ids, results, name, "NAMOA*2_Blind");
        std::cout << "with NAMOA*_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,TCHeuristic> >( G, queries, ids, results, name, "NAMOA*2_TC");
        std::cout << "with NAMOA*_bound_tc ...\n\n";
        runQueries<NamoaStar2<GraphType,BoundedTCHeuristic> >( G, queries, ids, results, name, "NAMOA*2_BTC");
        break;
    }
}

void readQueries( std::vector< std::pair<unsigned int,unsigned int> >& queries, const std::string& filename, const unsigned int& numQueries)
{
	std::string token;
	unsigned int uID,vID;
	std::ifstream in;
    in.exceptions ( std::ifstream::failbit | std::ifstream::badbit );
    std::cout << "Reading queries from " << filename << std::endl;
	unsigned int readQueries = 0;

	try {
    	in.open( filename.c_str());
        assert( in.good());
        while ( ((readQueries < numQueries) || numQueries == 0) && getline(in,token))
        {
			std::stringstream graphinfo;
			graphinfo.str(token);
        	graphinfo >> uID >> vID;
			queries.push_back( std::pair<unsigned int, unsigned int>( uID, vID));
			++readQueries;
        }
        in.close();
    }
    catch (std::ifstream::failure e) {
        std::cerr << "Exception opening/reading file '" << filename << "'\n";
        throw e;
    }
}

typedef DynamicGraph< AdjacencyListImpl, Node, Edge>       Graph;
typedef Graph::NodeIterator                                NodeIterator;
typedef Graph::EdgeIterator                                EdgeIterator;
typedef Graph::NodeDescriptor                              NodeDescriptor;

int main( int argc, char* argv[])
{
    std::string basePath = std::string(getenv("HOME")) + "/Projects/Graphs/DIMACS9/";
    // Set default parameters
    unsigned int numQueries = 50;
    unsigned int algorithmVariant = 0;
    std::string map ="NY";
    // Declare the supported options.
    po::options_description desc("Allowed options");
    desc.add_options()
        ("size,s", po::value< unsigned int>(), "number of queries. Default:50")
        ("algorithm,a", po::value< unsigned int>(), "NAMOA* algorithm. All[0], NAMOA* blind[1], NAMOA* TC[2], NAMOA* Bounded TC[3]. Default:0")
        ("map,m", po::value< std::string>(), "Input map. The name of the map to read. Maps must be in '$HOME/Projects/Graphs/DIMACS9/");
    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);
    if (vm.empty()) {
        std::cout << desc << "\n";
        return 0;
    }
    if (vm.count("size"))       numQueries = vm["size"].as<unsigned int>();
    if (vm.count("algorithm"))  algorithmVariant = vm["algorithm"].as<unsigned int>();
    if (vm.count("map"))        map = vm["map"].as<std::string>();
    Graph G;
    DIMACS9DoubleReader<Graph> reader( basePath + map + "/" + map + "_dist.gr",
                                basePath + map + "/" + map + "_travel.gr", basePath + map + "/" + map + ".co");
    filetrace = map + "_trace.dat";
    o_debug.open(filetrace.c_str(), std::fstream::app);
    Timer timer;
    // read the input graph and store it in memory
    timer.start();
    G.read(&reader);
    std::cout << "Graph has " << (double)G.memUsage()/1048576 << " Mbytes. Time spent to read:\t" << timer.getElapsedTime() << "sec" << std::endl;
	std::vector< std::pair<unsigned int,unsigned int> > queries;
    readQueries( queries, basePath + map + "/" + map+ "_queries", numQueries);
    Results results(queries);
    runExperimentsAt( G, queries, reader.getIds(), results, "ADJ", algorithmVariant);
    map += "_results";
    // print results
    std::ofstream out(map.c_str(), std::fstream::app);
    results.print(out);
    out.close();
    o_debug.close();
    return 0;
}


#ifndef TEST_UNIT_MATH_PRIM_MAT_VECTORIZE_EXPECT_PRIM_ROW_VECTOR_VALUE_HPP
#define TEST_UNIT_MATH_PRIM_MAT_VECTORIZE_EXPECT_PRIM_ROW_VECTOR_VALUE_HPP

#include <test/unit/math/prim/mat/vectorize/expect_val_eq.hpp>
#include <Eigen/Dense>
#include <vector>

template <typename F>
void expect_prim_row_vector_value() {
  using Eigen::RowVectorXd;
  using std::vector;

  std::vector<double> valid_inputs = F::valid_inputs();

  RowVectorXd c = RowVectorXd::Map(valid_inputs.data(), valid_inputs.size());

  RowVectorXd fc = F::template apply<RowVectorXd>(c);
  EXPECT_EQ(c.size(), fc.size());
  for (int i = 0; i < fc.size(); ++i) {
    expect_val_eq(F::apply_base(c(i)), fc(i));
  }

  vector<RowVectorXd> d;
  d.push_back(c);
  d.push_back(c);

  vector<RowVectorXd> fd = F::template apply<vector<RowVectorXd> >(d);
  EXPECT_EQ(d.size(), fd.size());
  for (size_t i = 0; i < fd.size(); ++i) {
    EXPECT_EQ(d[i].size(), fd[i].size());
    for (int j = 0; j < fd[i].size(); ++j) {
      expect_val_eq(F::apply_base(d[i](j)), fd[i](j));
    }
  }
}

#endif


//////////////////////////////////////////////////////////////////////////////////////////
// kernel::bandwidth_selection::detail::cross_validate.hpp                              //
//                                                                                //
//  (C) Copyright 2009 Erwann Rogard                                                    //
//  Use, modification and distribution are subject to the                               //
//  Boost Software License, Version 1.0. (See accompanying file                         //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)                    //
//////////////////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_KERNEL_BANDWIDTH_SELECTION_DETAIL_K_FOLD_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_KERNEL_BANDWIDTH_SELECTION_DETAIL_K_FOLD_HPP_ER_2009
#include <boost/statistics/detail/kernel/estimation/estimator.hpp>
#include <boost/statistics/detail/kernel/estimation/detail/mean_accumulator.hpp>
#include <boost/statistics/detail/cross_validation/k_fold/partition.hpp>
#include <boost/statistics/detail/cross_validation/k_fold/cross_validate.hpp>
#include <boost/statistics/detail/cross_validation/k_fold/train_predict.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace kernel{
namespace bandwidth_selection{
namespace detail{

    template<
        typename U, // data-unit
        template<typename,typename,typename> class V,
        typename K,
        typename Ft,
        typename Fi,
        typename Fo,
        typename A = typename
            kernel::detail::mean_accumulator<typename K::result_type>::type
    >
    class k_fold :
        public cross_validation::k_fold::partition<U,Ft,Fi,Fo>
    {

        typedef cross_validation::k_fold::partition<U,Ft,Fi,Fo>     kfp_;
        typedef typename kfp_::meta_training_range::type            tr_;
        typedef estimator<tr_,V,K,A>                                est_t;
        typedef kfp_& rkfp_;

        public:
        typedef kfp_ super_;
        typedef est_t estimator_type;

        // Constructor
        k_fold(){}
        template<typename It>
        k_fold(
            unsigned num_folds,
            It b,   //dataset
            It e
        ):kfp_(
            num_folds,
            b,e
        ){
            // Default constructs estimator
        }

        // TODO create a constructor with argument pack (overloading constr
        // uctor would be cumbersome)

        // Access

        // Cross validation

        template<typename T,typename It>
        It train_predict(
            K k,
            It  it_p // predicted values
        )
        {
            rkfp_ rkfp = static_cast<rkfp_>(*this);

            return cross_validation::k_fold::train_predict(
                rkfp,
                est_t(k),
                it_p
            );
        }

        template<typename It,typename It1>
        It cross_validate(
            K k,
            It i_p,     // predicted values
            It1 i_o    // true values
        ){
            rkfp_ rkfp = static_cast<rkfp_>(*this);

            return cross_validation::k_fold::cross_validate(
                rkfp,
                est_t(k),
                i_p,
                i_o
            );
        }

    };

}// detail
}// bandwidth_selection
}// kernel
}// detail
}// statistics
}// boost

#endif


#include <algorithm>
#include <GsTLAppli/geostat/parameters_handler.h>
#include <GsTLAppli/geostat/utilities.h>
#include <GsTLAppli/utils/gstl_messages.h>
#include <GsTLAppli/utils/error_messages_handler.h>
#include <GsTLAppli/utils/string_manipulation.h>
#include <GsTLAppli/grid/grid_model/geostat_grid.h>
#include <GsTLAppli/grid/grid_model/combined_neighborhood.h>
#include <GsTLAppli/grid/grid_model/gval_iterator.h>
#include <GsTLAppli/grid/grid_model/cartesian_grid.h>
#include <GsTLAppli/grid/grid_model/point_set.h>
#include <GsTLAppli/appli/manager_repository.h>
#include <GsTLAppli/math/random_numbers.h>
#include <GsTLAppli/appli/utilities.h>

#include <GsTL/sampler/monte_carlo_sampler.h>
#include <GsTL/simulation/sequential_simulation.h>

//TESTING BEGIN
#include <GsTL/cdf/gaussian_cdf.h>
#include <GsTL/sampler/monte_carlo_sampler.h>
#include <GsTL/cdf_estimator/gaussian_cdf_Kestimator.h>
#include <GsTL/simulation/sequential_simulation.h>
#include <GsTL/univariate_stats/cdf_transform.h>
#include <GsTL/univariate_stats/build_cdf.h>
//TESTING END

#include <GsTLAppli/grid/grid_model/point_set_neighborhood.h>

#include <iterator>
#include <vector>
#include <algorithm>
#include <fstream>

#include <GsTLAppli/grid/grid_model/reduced_grid.h>

#include <boost/math/tools/roots.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/math/special_functions/legendre.hpp>
#include <boost/algorithm/minmax_element.hpp>
#include <boost/math/distributions/normal.hpp>

#include <qstring.h>
#include <qfile.h>

#include <qfiledialog.h>
#include <GsTLAppli/appli/project.h>
#include <GsTLAppli/utils/gstl_plugins.h>

#include "kernelsim.h"

kernelsim::kernelsim()
{
	simul_grid_ = 0;
	harddata_grid_ = 0;
	training_image_ = 0;
	training_property_ = 0;
	harddata_property_ = 0;
	assign_harddata_ = false;
	neighborhood_ = 0;
	multireal_property_ = 0;
	hard_data_ti_ = 0;

	temporary_harddata_property_ = 0;
	temporary_training_property_ = 0;

}

kernelsim::~kernelsim()
{
	clean();
}

//intialize the parameters related to the algorithm
bool kernelsim::initialize( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{
	//-------------
	// Prepare the simulation grid and the property to be simulated.

	if (!get_simul_grid(parameters, errors))
	{
		return false;
	}

	//Set up the hard data

	if (!get_hard_data(parameters, errors))
	{
		return false;
	}

	//-------------
	// Set up the training image

	if (!get_training_image(parameters, errors))
	{
		return false;
	}

	//-------------
	// Set up the search neighborhood

	if (!set_up_neighborhood(parameters, errors))
	{
		return false;
	}

	//-------------
	// Set up the regions

	if (!set_up_regions(parameters, errors))
	{
		return false;
	}

	//-------------
	//// Set up the covariance
	//if(!set_up_covariance(parameters, errors))
	//{
	//	return false;
	//}

	//set up the bound values
	if(!set_bound_values(parameters, errors))
	{
		return false;
	}
	//test 0.5 2020-05-05
	build_frequency_table();

	//double c = 0;
	//std::vector<double> cutoffs;
	//while (c < 0.8) {
	//	c += 0.01;
	//	cutoffs.push_back(c);
	//}
	//cutoffs.push_back(1.0);
	//build_frequency_table(cutoffs);

	//You will have to tranform the properties to the interval [-1, 1] before you compute the moments.!!! 2018-02-13.

	//Use a transformation class to change the properties' values on hard grid and training image into [-1,1]
	if(temporary_harddata_property_)
		std::for_each(temporary_harddata_property_->begin(),temporary_harddata_property_->end(), transform_to_legendre_domain(zmin_, zmax_));
		//std::for_each(temporary_harddata_property_->begin(),temporary_harddata_property_->end(), proportional_transform_legendre_domain<PropertyValueProxy>(freq_table_, val_table_, 2*bin_width_));
	if(temporary_training_property_)
		std::for_each(temporary_training_property_->begin(),temporary_training_property_->end(), transform_to_legendre_domain(zmin_, zmax_));
		//std::for_each(temporary_training_property_->begin(),temporary_training_property_->end(), proportional_transform_legendre_domain<PropertyValueProxy>(freq_table_, val_table_, 2*bin_width_));

	///++++++++++++++++Here we store the hard data in a grid with the same size as the simulation grid+++++++++++++++
	//++++++++++++++++In addition, the data are preprocessed to computer the legendre polymials evaluated at these data values
	//++++++++++++++++For testing the hard-data-driven. 05/19/2019

	SmartPtr<Property_copier> tmp_property_copier_ =
		Property_copier_factory::get_copier( harddata_grid_, hard_data_ti_);
	if( !tmp_property_copier_ ) {
		std::ostringstream message;
		message << "It is currently not possible to copy a property from a "
			<< harddata_grid_->classname() << " to a "
			<< harddata_grid_->classname() ;
		errors->report( !tmp_property_copier_, "Transform_Hard_Data", message.str() );
		return false;
	}

	//clear the previous data stored in the hard data TI
	for(int i = 0; i < hard_data_ti_property_->size(); ++ i)
	{
		hard_data_ti_property_->set_not_informed(i);
	}

	tmp_property_copier_->copy(harddata_grid_, temporary_harddata_property_, hard_data_ti_, hard_data_ti_property_);
	//tmp_property_copier_->copy(harddata_grid_, harddata_property_, hard_data_ti_, hard_data_ti_property_);
	hard_data_ti_->select_property(hard_data_ti_property_->name());
	build_legendre_hard_ti(parameters, errors);

	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	//currently just fixed for testing.
	//double sigma = 0.1;
	if(!build_kernel_moments(parameters, errors))
	{
		return false;
	}

	//-------------
	// Number of realizations and random number seed

	nb_of_realizations_ =
		String_Op::to_number<int>( parameters->value( "Nb_Realizations.value" ) );

	seed_ = String_Op::to_number<int>( parameters->value( "Seed.value" ) );

	//-------------
	//Set the simple kriging type
	geostat_utils::KrigTagMap tags_map;
	geostat_utils::KrigDefaultsMap defaults;
	defaults[ geostat_utils::SK ] = "0.0";

	geostat_utils::Kriging_type ktype = geostat_utils::SK;
	geostat_utils::initialize( ktype, combiner_, Kconstraints_,
								tags_map,
								parameters, errors,
								simul_grid_, defaults );

	return true;
}

//main steps of the algorithm
int kernelsim::execute( GsTL_project* proj)
{

	// Set up a progress notifier
	int total_steps = simul_grid_->size() * (nb_of_realizations_);
	int frequency = std::max( total_steps / 20, 1 );
	SmartPtr<Progress_notifier> progress_notifier =
		utils::create_notifier( "Running kernelsim",
		total_steps, frequency );

	int nb_multigrids = 3;
	int finest_grid_nb = 1;

	// work on the fine grid
	if( dynamic_cast<Strati_grid*>( simul_grid_ ) ) {
		Strati_grid* sgrid = dynamic_cast<Strati_grid*>( simul_grid_ );
		sgrid->set_level( finest_grid_nb );
	}

	//Set the training image to the finest resolution
	training_image_->set_level(finest_grid_nb);

	//To be changed later:
	//null_dummy_cdf marginal;
	Avg_dummy_cdf marginal;

	Gaussian_kernel_cdf ccdf;
	ccdf.order(max_order_);
	ccdf.learning_rate(learning_rate_);

	ccdf.standard_deviation(sigma_);
	//June 01, 2018 should'nt fxied the number of the prototypes now!
	//ccdf.num_prototypes(num_prototypes_);

  // set up the cdf-estimator
  typedef Gaussian_cdf_Kestimator< Covariance<Location>,
                                   Neighborhood,
                                   geostat_utils::KrigingConstraints
                                  >    Kriging_cdf_estimator;
  SLM_kde_estimator<> cdf_estimator( *(dynamic_cast<Geostat_grid*>(training_image_)),
							//*harddata_grid_,
							*hard_data_ti_,
							Legendre_moments_,
							Legendre_values_,
							Legendre_values_hard_ti_,
							marginal,
							zmin_,
							zmax_,
							angle_tol_,
							lag_tol_,
							band_tol_,
							num_ti_replicate_,
							num_hd_replicate_
						);

   cdf_estimator.init_prototypes(ccdf, num_prototypes_, num_sel_protos_);

	// Initialize the global random number generator
	Global_random_number_generator::instance()->seed( seed_ );

  // set up the sampler
  Random_number_generator gen;
  Monte_carlo_sampler_t< Random_number_generator > sampler( gen );

  bool from_scratch = true;
  // loop on all realizations
  for( int nreal = 0; nreal < nb_of_realizations_ ; nreal ++ ) {

    // compute the random path
    simul_grid_->init_random_path(from_scratch);
    from_scratch = false;

    // update the progress notifier
    progress_notifier->message() << "working on realization "
                                 << nreal+1 << gstlIO::end;
    if( !progress_notifier->notify() ) return 1;

    // Create a new property to hold the realization and tell the simulation
    // grid to use it as the current property
    appli_message( "Creating new realization" );
    GsTLGridProperty* prop = multireal_property_->new_realization();
    simul_grid_->select_property( prop->name() );
    neighborhood_->select_property( prop->name() );

    // initialize the new realization with the hard data, if that was requested
    if( property_copier_ ) {
      //Copy the property after transformation 2018-02-13

		property_copier_->copy( harddata_grid_, temporary_harddata_property_,
			simul_grid_, prop );
    }

    appli_message( "Doing simulation" );

    // do the simulation

	int status =
		sequential_simulation( simul_grid_->random_path_begin(),
			     simul_grid_->random_path_end(),
			     *(neighborhood_.raw_ptr()),
			     ccdf,
				 //cdf,
			     cdf_estimator,
			     marginal,
			     sampler, progress_notifier.raw_ptr()
			     );
    if( status == -1 ) {
      clean( prop );
      return 1;
    }

	std::cout<< "The number of bad points is: "<< status << std::endl;

	//At last, we should change the simulated values back to the original scale, because the previous results were drawn from [-1, 1].
	std::for_each(simul_grid_->selected_property()->begin(), simul_grid_->selected_property()->end(), back_from_legendre_domain(zmin_, zmax_));//(lowerbound_, upperbound_));
	//std::for_each(simul_grid_->selected_property()->begin(), simul_grid_->selected_property()->end(), proportional_backfrom_legendre_domain<PropertyValueProxy>(freq_table_, val_table_, 2*bin_width_));//(lowerbound_, upperbound_));

  }
  cdf_estimator.close();
  clean();

  return 0;
}

//create an instance of the algorithm
Named_interface* kernelsim::create_new_interface(std::string&)
{
	return new kernelsim;
}

//Get the simulation grid and specify the name of property to be simulated
bool kernelsim::get_simul_grid( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{
	std::string simul_grid_name = parameters->value( "Grid_Name.value" );
	errors->report( simul_grid_name.empty(),
		"Grid_Name", "No grid selected" );
	std::string property_name = parameters->value( "Property_Name.value" );
	errors->report( property_name.empty(),
		"Property_Name", "No property name specified" );

	// Get the simulation grid from the grid manager
	if( simul_grid_name.empty() ) return false;

	bool ok = geostat_utils::create( simul_grid_, simul_grid_name,
		"Grid_Name", errors );

	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	//Create a duplicate grid to hold the hard data in a Cartesian grid. 15/05/2019
	//ok = geostat_utils::create(hard_data_ti_ , simul_grid_name + ".hard",
	//	"Grid_Name", errors );

	//Need to create a new Cartesian grid to do this!!
	//if( !ok)
	{
		hard_ti_name_ = simul_grid_name + ".hard";
		std::string full_name( "/GridObject/Model/" + simul_grid_name + ".hard" );
		SmartPtr<Named_interface> ni =
			Root::instance()->new_interface("cgrid://"+ simul_grid_name + ".hard", full_name);

		if( ni.raw_ptr() == 0 ) {
		errors->report( "Object " + full_name + " already exists. Use a different name." );
		//    appli_warning( "object " << full_name << "already exists" );
		return false;
		}

		Cartesian_grid* grid = dynamic_cast<Cartesian_grid*>( ni.raw_ptr() );
		Cartesian_grid* sgrid = dynamic_cast<Cartesian_grid*>( simul_grid_);
		grid->set_dimensions(
			sgrid->geometry()->dim(0),
			sgrid->geometry()->dim(1),
			sgrid->geometry()->dim(2),
			sgrid->cell_dimensions()[0],
			sgrid->cell_dimensions()[1],
			sgrid->cell_dimensions()[2]
		);

		grid->origin(sgrid->origin());
		//grid->set_rotation_z(sgrid->rotation_z());

		hard_data_ti_ = grid;
		ok = true;
	}

	hard_data_ti_property_ = hard_data_ti_->property(property_name);
	if(!hard_data_ti_property_)
		hard_data_ti_property_ = hard_data_ti_->add_property(property_name);

	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++//

	if( !ok ) return false;

	// create  a multi-realization property
	multireal_property_ =
		simul_grid_->add_multi_realization_property( property_name );

	return true;
}

//retrieve the hard data grid
bool kernelsim::get_hard_data( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{
	std::string harddata_grid_name = parameters->value( "Hard_Data.grid" );

	if( !harddata_grid_name.empty() ) {
		std::string hdata_prop_name = parameters->value( "Hard_Data.property" );
		errors->report( hdata_prop_name.empty(),
			"Hard_Data", "No property name specified" );

		// Get the hard data grid from the grid manager
		bool ok = geostat_utils::create( harddata_grid_, harddata_grid_name,
			"Hard_Data", errors );
		if( !ok ) return false;

		harddata_property_ = harddata_grid_->property( hdata_prop_name );
		if( !harddata_property_ ) {
			std::ostringstream error_stream;
			error_stream <<  harddata_grid_name
				<<  " does not have a property called "
				<< hdata_prop_name;
			errors->report( "Hard_Data", error_stream.str() );
			return false;
		}

		//Generate a temporary property that stores what is transformed to interval [-1,1] based on
		//the linear transformation of the original property, because the Legendre polynomials were
		//defined on D=[-1,1].
		std::string tmp_hdata_prop_name = hdata_prop_name + "temporary_";

		temporary_harddata_property_ = harddata_grid_->add_property(tmp_hdata_prop_name);

		if( !temporary_harddata_property_ ) {
			std::ostringstream error_stream;
			error_stream <<  harddata_grid_name
				<<  " can not create a temporary property called "
				<< tmp_hdata_prop_name;
			errors->report( "Hard_Data", error_stream.str() );
			return false;
		}
		SmartPtr<Property_copier> tmp_property_copier_ =
			Property_copier_factory::get_copier( harddata_grid_, harddata_grid_ );

		if( !tmp_property_copier_ ) {
			std::ostringstream message;
			message << "It is currently not possible to copy a property from a "
				<< harddata_grid_->classname() << " to a "
				<< harddata_grid_->classname() ;
			errors->report( !tmp_property_copier_, "Transform_Hard_Data", message.str() );
			return false;
		}

		tmp_property_copier_->copy(harddata_grid_, harddata_property_, harddata_grid_, temporary_harddata_property_);

	}

	// hard data assignment and transform is only needed if we have a valid
	// hard data grid and property.  We always assign the data if it belongs
	// the same grid

	assign_harddata_ =
		String_Op::to_number<bool>( parameters->value( "Assign_Hard_Data.value" ) );
	if( harddata_grid_ == NULL ) assign_harddata_=false;
	else if( harddata_grid_ == simul_grid_ ) assign_harddata_=true;

	if( assign_harddata_ ) {
		property_copier_ =
			Property_copier_factory::get_copier( harddata_grid_, simul_grid_ );
		if( !property_copier_ ) {
			std::ostringstream message;
			message << "It is currently not possible to copy a property from a "
				<< harddata_grid_->classname() << " to a "
				<< simul_grid_->classname() ;
			errors->report( !property_copier_, "Assign_Hard_Data", message.str() );
			return false;
		}
	}

	return true;
}

//Get the instance of training image
bool kernelsim::get_training_image( const Parameters_handler* parameters,
		Error_messages_handler* error_mesgs )
{
	training_image_name_ = parameters->value( "PropertySelector_Training.grid" );
	error_mesgs->report( training_image_name_.empty(),
		"PropertySelector_Training", "No training image selected" );

	training_property_name_ = parameters->value( "PropertySelector_Training.property" );
	error_mesgs->report( training_property_name_.empty(),
		"PropertySelector_Training", "No training property selected" );

	// Get the training image from the grid manager
	// and select the training property
	if( !training_image_name_.empty() )
	{
		training_image_ = dynamic_cast<RGrid*>(
			Root::instance()->interface(
			gridModels_manager + "/" + training_image_name_).raw_ptr() );

		if( !training_image_ )
		{
			std::ostringstream error_stream;
			error_stream <<  training_image_name_ <<  " is not a valid training image";
			error_mesgs->report( "PropertySelector_Training", error_stream.str() );
			return false;
		}

		training_property_ = training_image_->property( training_property_name_ );
		if( !training_property_ ) {
			std::ostringstream error_stream;
			error_stream <<  training_image_name_
				<<  " does not have a property called "
				<< training_property_name_;
			error_mesgs->report( "Training_Data", error_stream.str() );
			return false;
		}

		//Generate a temporary property that stores what is transformed to interval [-1,1] based on
		//the linear transformation of the original property, because the Legendre polynomials were
		//defined on D=[-1,1].
		std::string tmp_training_prop_name = training_property_name_ + "temporary_";

		temporary_training_property_ = training_image_->add_property(tmp_training_prop_name);

		if( !temporary_training_property_ ) {
			std::ostringstream error_stream;
			error_stream <<  training_image_name_
				<<  " can not create a temporary property called "
				<< tmp_training_prop_name;
			error_mesgs->report( "Hard_Data", error_stream.str() );
			return false;
		}
		SmartPtr<Property_copier> tmp_property_copier_ =
			Property_copier_factory::get_copier( training_image_, training_image_ );

		if( !tmp_property_copier_ ) {
			std::ostringstream message;
			message << "It is currently not possible to copy a property from a "
				<< training_image_->classname() << " to a "
				<< training_image_->classname() ;
			error_mesgs->report( !tmp_property_copier_, "Transform_Training_Data", message.str() );
			return false;
		}

		tmp_property_copier_->copy(training_image_, training_property_, training_image_, temporary_training_property_);

		training_image_->select_property( tmp_training_prop_name );

		return true;
	}
	else
		return false;
}

//Set the covariance model
bool kernelsim::set_up_covariance( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{
  //-------------
  // Variogram (covariance) initialization

  bool init_cov_ok =
    geostat_utils::initialize_covariance( &covar_, "Variogram",
                                          parameters, errors );

  return init_cov_ok;
}

//initialize the parameters for searching
bool kernelsim::set_up_neighborhood(const Parameters_handler* parameters,
	Error_messages_handler* errors)
{
	int max_neigh =
		String_Op::to_number<int>(parameters->value("Max_Conditioning_Data.value"));

	num_ti_replicate_ = String_Op::to_number<int>(parameters->value("num_ti_replicate.value"));
	num_hd_replicate_ = String_Op::to_number<int>(parameters->value("num_hd_replicate.value"));

	//Get the values for the tolerances to find a replicate in the hard data
	angle_tol_ = String_Op::to_number<double>(parameters->value("Angle_tol.value"));
	//Change to radian
	const static double pi = 3.14159265359;
	angle_tol_ = angle_tol_ * pi / 180.0;

	lag_tol_ = String_Op::to_number<double>(parameters->value("Lag_tol.value"));
	band_tol_ = String_Op::to_number<double>(parameters->value("Band_tol.value"));

	GsTLTriplet ranges;
	GsTLTriplet angles;
	bool extract_ok =
		geostat_utils::extract_ellipsoid_definition(ranges, angles,
			"Search_Ellipsoid.value",
			parameters, errors);
	if (!extract_ok) return false;

	// If the hard data are not "relocated" on the simulation grid,
	// use a "combined neighborhood", otherwise use a single
	// neighborhood
	if (!harddata_grid_ || assign_harddata_) {

		neighborhood_ = SmartPtr<Neighborhood>(
			simul_grid_->neighborhood(ranges, angles));

	}
	else {
		Neighborhood* simul_neigh = simul_grid_->neighborhood(ranges, angles);

		simul_neigh->max_size(max_neigh);
		harddata_grid_->select_property(harddata_property_->name());

		Neighborhood* harddata_neigh;
		if (dynamic_cast<Point_set*>(harddata_grid_)) {
			harddata_neigh =
				harddata_grid_->neighborhood(ranges, angles, 0, true);
		}
		else {
			harddata_neigh =
				harddata_grid_->neighborhood(ranges, angles, 0);
		}

		harddata_neigh->max_size(max_neigh);
		//  harddata_neigh->select_property( harddata_property_->name() );

		neighborhood_ =
			SmartPtr<Neighborhood>(new Combined_neighborhood(harddata_neigh,
				simul_neigh));
		//     SmartPtr<Neighborhood>( new Combined_neighborhood_dedup( harddata_neigh,
	   //							                                           simul_neigh, &covar_, false) );
	}

	neighborhood_->max_size(max_neigh);

	// octant_2D_filter * filter = new octant_2D_filter(20, 6, 1, 5);

	double r = ranges[0];
	if (r < ranges[1])
		r = ranges[1];
	if (r < ranges[2])
		r = ranges[2];

	if (num_hd_replicate_  == 0 || num_hd_replicate_ == 1)
	{
		search_filter * filter = new search_filter(lag_tol_, band_tol_, angle_tol_, r + lag_tol_,  0.5*lag_tol_);
		neighborhood_->search_neighborhood_filter(filter);
	}

  //Do not include the center
  neighborhood_->includes_center(false);
  //For advanced parameters such as octant searching
  geostat_utils::set_advanced_search(neighborhood_,
                      "AdvancedSearch", parameters, errors);

  return true;
}

//set the regions for simulation (not used here now)
bool kernelsim::set_up_regions( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{
	std::string region_name = parameters->value( "Grid_Name.region" );
	if (!region_name.empty() && simul_grid_->region( region_name ) == NULL ) {
		errors->report("Grid_Name","Region "+region_name+" does not exist");
	}
	else grid_region_.set_temporary_region( region_name, simul_grid_);

	if(harddata_grid_ && !assign_harddata_ && harddata_grid_ != simul_grid_) {
		region_name = parameters->value( "Hard_Data.region" );
		if (!region_name.empty() && harddata_grid_->region( region_name ) == NULL ) {
			errors->report("Hard_Data","Region "+region_name+" does not exist");
		}
		else  hd_grid_region_.set_temporary_region( region_name,harddata_grid_ );
	}

	if(training_image_ != simul_grid_) {
		std::string region_name = parameters->value( "PropertySelector_Training.region" );
		if (!region_name.empty() && training_image_->region( region_name ) == NULL ) {
			errors->report("PropertySelector_Training","Region "+region_name+" does not exist");
		}
		else ti_grid_region_.set_temporary_region( region_name, training_image_);
		return true;
	}

	return true;
}

bool kernelsim::set_bound_values( const Parameters_handler* parameters, Error_messages_handler* errors )
{

	std::string orderstr = parameters->value("Maximum_order.value");
	if(orderstr.empty()){
		errors->report("Maximum_order", "Define an maximal order of Legenddre polynomials to approximate the cdf");
		return false;
	}

	max_order_ = String_Op::to_number<int>(orderstr);

	//I have to rewritten the minmax finding program, because the MS std::minmax_element or boost
	//is very, very slow to do this, probabily due to the safety bound check on the iterator with every visiting.
	//Although this can be avoided by define the _SECURE_SCL to 0, however this definition must be
	//consistent on every library (file) that use the STL, otherwise it will produce strange runtime errors.
	property_type zmax, zmin;

	if(harddata_property_){
		zmax = *(harddata_property_->begin());
		zmin = zmax;
		for(GsTLGridProperty::iterator iter = harddata_property_->begin(); iter != harddata_property_->end(); ++ iter){
			if(zmax < *iter)
				zmax = *iter;
			if(zmin > *iter)
				zmin = *iter;
		}
	}
	else{
		zmax = *(training_property_->begin());
		zmin = zmax;
	}

	for(GsTLGridProperty::iterator iter = training_property_->begin(); iter != training_property_->end(); ++ iter){
		if(zmax < *iter)
			zmax = *iter;
		if(zmin > *iter)
			zmin = *iter;
	}

	std::string strzmin = parameters->value("Min_value.value");
	std::string strzmax = parameters->value("Max_value.value");
	if(strzmin.empty()){
		errors->report("Min_value", "The input lower bound is required");
		return false;
	}

	if(strzmax.empty()){
		errors->report("Max_value", "The input upper bound is required");
		return false;
	}

	zmax_ = zmax;
	zmin_ = zmin;

	upperbound_ = zmax;
	lowerbound_ = zmin;

	zmax = String_Op::to_number<property_type>(strzmax);
	zmin = String_Op::to_number<property_type>(strzmin);

	if(zmax < zmax_){
		errors->report("Max_value", "The input upper bound is less than the existing data");
		return false;
	}
	else{
		zmax_ = zmax;
	}

	if(zmin > zmin_){
		errors->report("Min_value", "The input lower bound is greater than the existing data");
		return false;
	}
	else{
		zmin_ = zmin;
	}

	num_prototypes_ = String_Op::to_number<int>( parameters->value( "num_prototypes.value" ) );
	num_sel_protos_ = String_Op::to_number<int>( parameters->value( "num_sel_prototypes.value" ) );

	std::string str = parameters->value("optimize_check.value");
	if(str == "1")
	{
		b_optmize_width_ = true;
	}
	else
		b_optmize_width_ = false;

	str = parameters->value("max_num_iteration.value");

	if(b_optmize_width_){
		if(str.empty()){
			errors->report("max_num_iteration", "Define the maximum iterations");
			return false;
		}

		num_max_iterations_ = String_Op::to_number<int>(str);
	}

	str = parameters->value("sigma_lower_bound.value");
	sigma_lb_ = String_Op::to_number<double>(str);

	str = parameters->value("sigma_upper_bound.value");
	sigma_ub_ = String_Op::to_number<double>(str);

	str = parameters->value("learning_rate.value");
	learning_rate_ = String_Op::to_number<double>(str);

	return true;

}

//free the memory usage
void kernelsim::clean( GsTLGridProperty* prop )
{
	if(prop)
		simul_grid_->remove_property( prop->name() );

	if(temporary_harddata_property_){
		harddata_grid_->remove_property(temporary_harddata_property_->name());
		temporary_harddata_property_ = 0;
	}
	if(temporary_training_property_){
		training_image_->remove_property(temporary_training_property_->name());
		temporary_training_property_ = 0;
	}
	for(int i = 0; i < Legendre_moments_.size(); ++ i)
	{
		delete Legendre_moments_[i];
	}
	for(int i = 0; i < Legendre_values_.size(); ++ i)
	{
		delete Legendre_values_[i];
	}
	Legendre_moments_.clear();
	Legendre_values_.clear();

	if (hard_data_ti_)
	{
		//std::string str = hard_data_ti_->name();
		Root::instance()->delete_interface("/GridObject/Model/" + hard_ti_name_);// hard_data_ti_->name());
		hard_data_ti_ = 0;
	}

}

//Build the kernel moments and Legendre polynomial values for the training image
//These values will be used in the later steps to build the matrix for the Quadratic
//programming, since we are assuming now that all the replicates come from the training
//image.
bool kernelsim::build_kernel_moments( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{

	std::string widthstr = parameters->value("kernel_width.value");
	if(widthstr.empty()){
		errors->report("kernel_width", "Define the kernel width");
		return false;
	}

	sigma_ = String_Op::to_number<double>(widthstr);

	//Initialize the moments up to W the same size as the training image.
	//We are working on the properties that are transformed to the interval [-1, 1].
	for(int w = 0; w <= max_order_; ++ w)
	{
		Legendre_moments_.push_back(new GsTLGridProperty(temporary_training_property_->size(), "moments"));
	}
	//We don't store the first two order of Legendre polynomials since they are trivial.
	for(int w = 0; w <= max_order_ - 2; ++ w)
	{
		Legendre_values_.push_back(new GsTLGridProperty(temporary_training_property_->size(), "values"));
	}

	//kermel moments
	std::vector<double> I(max_order_ + 1, 0.0);
	//intermediate variable to compute the kernel moments
	std::vector<double> T(max_order_ + 1, 0.0);
	//Legendre polynomial values
	std::vector<double> P(max_order_ + 1, 0.0);

	//Now let's go through the property array of the training image.
	for(int i = 0; i < temporary_training_property_->size(); ++ i)
	{
		//Added to skip non-data-value in the TI, May 22, 2018
		if(!temporary_training_property_->is_informed(i))
			continue;

		//we need to define the normal distribution with the mean as the node value and standard deviation as sigma at first.
		double mu = temporary_training_property_->get_value(i);
		boost::math::normal df(mu, sigma_);
		//I[0] = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		//double c1 = sigma_*sigma_*(boost::math::pdf(df, -1) - boost::math::pdf(df, 1));
		//double c2 = sigma_*sigma_*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1));
		double scaling = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		I[0] = 1;
		//double c1 = sigma_*sigma_*(boost::math::pdf(df, -1) - boost::math::pdf(df, 1))/scaling;
		double c1 = sigma_*sigma_*(boost::math::pdf(df, 1) - boost::math::pdf(df, -1))/scaling;
		double c2 = sigma_*sigma_*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1))/scaling;

		I[1] = mu * I[0] - c1;
		T[0] = I[1];
		P[0] = 1;
		P[1] = mu;
		//The above are the initialized values for the recursive computations

		Legendre_moments_[0]->set_value(I[0], i);
		Legendre_moments_[1]->set_value(I[1], i);

		for(int w = 1; w < max_order_; ++ w)
		{
			T[w] = mu * I[w] - (w%2?c2:c1);
			int k = w - 1;
			while (k >= 0)
			{
				T[w] += sigma_*sigma_* (2*k+1)*I[k];
				k -= 2;
			}

			I[w+1] = ((2*w+1)*T[w] - w*I[w-1])/double (w+1);

			P[w+1] = (mu*P[w]*(2*w+1) - w*P[w-1])/double(w+1);

			Legendre_moments_[w+1]->set_value(I[w+1], i);
			Legendre_values_[w-1]->set_value(P[w+1], i);
		}

	}

	return true;

}

//+++++++++++++precompute the legendre values for the hard data +++++++++++++++++++++++++++++
bool kernelsim::build_legendre_hard_ti( const Parameters_handler* parameters,
		Error_messages_handler* errors )
{

	//We don't store the first two order of Legendre polynomials since they are trivial.
	for(int w = 0; w <= max_order_ - 2; ++ w)
	{
		Legendre_values_hard_ti_.push_back(new GsTLGridProperty(hard_data_ti_property_->size(), "values"));
	}

	//kermel moments
	std::vector<double> I(max_order_ + 1, 0.0);
	//intermediate variable to compute the kernel moments
	std::vector<double> T(max_order_ + 1, 0.0);
	//Legendre polynomial values
	std::vector<double> P(max_order_ + 1, 0.0);

	//Now let's go through the property array of the training image.
	for(int i = 0; i < hard_data_ti_property_->size(); ++ i)
	{
		//Added to skip non-data-value in the TI, May 22, 2018
		if(!hard_data_ti_property_->is_informed(i))
			continue;

		//we need to define the normal distribution with the mean as the node value and standard deviation as sigma at first.
		double mu = hard_data_ti_property_->get_value(i);
		P[0] = 1;
		P[1] = mu;
		//The above are the initialized values for the recursive computations

		for(int w = 1; w < max_order_; ++ w)
		{
			P[w+1] = (mu*P[w]*(2*w+1) - w*P[w-1])/double(w+1);
			Legendre_values_hard_ti_[w-1]->set_value(P[w+1], i);
		}

	}

	return true;

}

//An copy and paster from the ::build_kernel_moments
void kernelsim::update_kernel_moments()
{
	//kermel moments
	std::vector<double> I(max_order_ + 1, 0.0);
	//intermediate variable to compute the kernel moments
	std::vector<double> T(max_order_ + 1, 0.0);
	//Legendre polynomial values
	std::vector<double> P(max_order_ + 1, 0.0);

	//Now let's go through the property array of the training image.
	for(int i = 0; i < temporary_training_property_->size(); ++ i)
	{
		//Added to skip non-data-value in the TI, May 22, 2018
		if(!temporary_training_property_->is_informed(i))
			continue;

		//we need to define the normal distribution with the mean as the node value and standard deviation as sigma at first.
		double mu = temporary_training_property_->get_value(i);
		boost::math::normal df(mu, sigma_);
		//I[0] = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		//double c1 = sigma_*sigma_*(boost::math::pdf(df, -1) - boost::math::pdf(df, 1));
		//double c2 = sigma_*sigma_*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1));
		double scaling = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		I[0] = 1;
		//double c1 = sigma_*sigma_*(boost::math::pdf(df, -1) - boost::math::pdf(df, 1))/scaling;
		double c1 = sigma_*sigma_*(boost::math::pdf(df, 1) - boost::math::pdf(df, -1))/scaling;
		double c2 = sigma_*sigma_*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1))/scaling;

		I[1] = mu * I[0] - c1;
		T[0] = I[1];
		P[0] = 1;
		P[1] = mu;

		//The above are the initialized values for the recursive computations

		Legendre_moments_[0]->set_value(I[0], i);
		Legendre_moments_[1]->set_value(I[1], i);

		for(int w = 1; w < max_order_; ++ w)
		{

			T[w] = mu * I[w] - (w%2?c2:c1);
			int k = w - 1;
			while (k >= 0)
			{
				T[w] += sigma_*sigma_* (2*k+1)*I[k];
				k -= 2;
			}

			I[w+1] = ((2*w+1)*T[w] - w*I[w-1])/double (w+1);

			P[w+1] = (mu*P[w]*(2*w+1) - w*P[w-1])/double(w+1);

			Legendre_moments_[w+1]->set_value(I[w+1], i);
			Legendre_values_[w-1]->set_value(P[w+1], i);
		}

	}
}

void kernelsim::compute_derivative()
{

}

//Optimze the kernel width by stochastic gradient descent. June 15 2018
int kernelsim::optimize_kernel_width()
{

	return 0;
}

void kernelsim::build_frequency_table(double bin_width)
{
	bin_width_ = bin_width;
	if(!temporary_harddata_property_)
		return;
	//Copy the data vaules to a new vector
	std::vector<double> values(temporary_harddata_property_->size());

	for(int i = 0; i < values.size(); ++ i)
	{
		values[i] = temporary_harddata_property_->get_value(i);
	}

	//sort the values
	//CAN'T UNDERSTAND WHY SORT FUNCTION DOES NOT WORK ON THIS MACHINE!
	//std::sort(values.begin(), values.end());

	//
	int num_bins = int (1.0/bin_width);
	freq_table_.resize(num_bins+1);
	val_table_.resize(num_bins+1);

	freq_table_[0] = -1;
	val_table_[0] = zmin_;

	for(int i = 1; i < num_bins; ++ i)
	{
		freq_table_[i] = freq_table_[i-1] + bin_width*2;
		int n = int (bin_width * i * values.size());

		std::nth_element(values.begin(), values.begin() + n - 1, values.end());
		val_table_[i] = values[n];
	}

	freq_table_[num_bins] = 1;
	//val_table_[num_bins] = 12;
	val_table_[num_bins] = zmax_;

}

void kernelsim::build_frequency_table(std::vector<double>& cutoffs)
{
	if (!temporary_harddata_property_)
		return;
	//Copy the data vaules to a new vector
	std::vector<double> values(temporary_harddata_property_->size());

	for (int i = 0; i < values.size(); ++i)
	{
		values[i] = temporary_harddata_property_->get_value(i);
	}

	//
	int num_bins = cutoffs.size();
	freq_table_.resize(num_bins);
	val_table_.resize(num_bins);

	freq_table_[0] = -1;
	val_table_[0] = zmin_;

	for (int i = 1; i < num_bins-1; ++i)
	{
		freq_table_[i] = freq_table_[i - 1] + (cutoffs[i]-cutoffs[i-1])* 2;
		int n = int(cutoffs[i] * values.size());

		std::nth_element(values.begin(), values.begin() + n - 1, values.end());
		val_table_[i] = values[n];
	}

	freq_table_[num_bins-1] = 1;
	val_table_[num_bins - 1] = zmax_;
	//val_table_[num_bins - 1] = 12;

}

//inversion of the probability
double Base_kernel_cdf::inverse(double p) const
{
	double result = 0.0;

	//boost::uintmax_t max_iter = 1000;

	static const double lim=1.0e-12;
	static const double INFINITY=GsTL::INFINITY;

	std::pair<double, double> root = boost::math::tools::bisect(
		//boost::BOOST_BIND(&Truncated_Legendre_cdf::prob,this,_1),
		Base_kernel_cdf::root_find_helper (const_cast<Base_kernel_cdf*> (this), p),
		-1.0,
		1.0,
		//-INFINITY,
		//INFINITY,
		basic_toleration()//,
		//max_iter
		);

	result = (root.first + root.second)/2;

	return result;
}

#include <boost/math/distributions/normal.hpp>

//Compute the value of probability Prob(Z <= z)
double Gaussian_kernel_cdf::prob( double z ) const
{
	double p = 0.0;

	//double s = std::accumulate(coefs_.begin(), coefs_.end(), 0.0);
	//double s = std::sqrt(variance_);

	for (int i = 0; i < means_.size(); ++ i)
	{
		//boost::math::normal c(means_[0], s);
		//double x = boost::math::cdf(c, z);
		//p += coefs_[i] * x;

		Gaussian_cdf cdf(means_[i], variance_);//0.2);
		//x = cdf.prob(z);

		//if(coefs_[i]==0.0)
		//	continue;
		//p += coefs_[i] * cdf.prob(z);
		//To shift accoring to the definition of a truncated normal distribution.
		p += coefs_[i] * (cdf.prob(z) - shift_[i])/scaling_[i];
		//p += coefs_[i] * cdf.prob(z);

	}

	//p -= overall_shift_;
	//p /= overall_scaling_;

	//if(p<0)
	//	p = 0;
	//else if(p>1)
	//	p = 1.0;

	//assert( p>=0 && p<=1);

	return p;
}

void Gaussian_kernel_cdf::balance()
{
	if(balanced_)
		return;

	shift_.resize(means_.size());
	scaling_.resize(means_.size());

	overall_shift_ = 0;
	overall_scaling_ = 0;

	for (int i = 0; i < means_.size(); ++ i)
	{
		//boost::math::normal c(means_[0], s);
		//double x = boost::math::cdf(c, z);
		//p += coefs_[i] * x;
		Gaussian_cdf cdf(means_[i], variance_);

		//To shift accoring to the definition of a truncated normal distribution.
		shift_[i] =  cdf.prob(-1.0);
		double s = cdf.prob(1.0) - shift_[i];
		scaling_[i] = s;

		overall_shift_ += coefs_[i] * shift_[i];
		overall_scaling_ += coefs_[i] * s;

	}

	balanced_ = true;
}

//Some helpful functions related to the algorithm of reproducing kernel generated by Legendre polynomials.

//This function compute the moment of a univariate Legendre polynomial with normal distribution on the interval [-1,1].
//Store the moments up to w in the vector
int Legendre_Gauss_Mom(int max_order, double mu, double sigma, std::vector<double>& moments, std::vector<double>& P, std::vector<double>& prototype_derivatives)
{
	//We only consider the moments of positive orders
	assert (max_order >= 1);

	//Moments from order 0 to order w
	//moments.resize(max_order+1, 0.0);
	moments.assign(max_order+1, 0.0);

	//Derivatives corresponding to the moments on sigma 2018-10-12
	//prototype_derivatives.resize(max_order+1, 0.0);
	prototype_derivatives.assign(max_order+1, 0.0);

	//intermediate variable to compute the kernel moments
	std::vector<double> T(max_order + 1, 0.0);
	//Legendre polynomial values
	//P.resize(max_order + 1, 0.0);
	P.assign(max_order + 1, 0.0);

	//The vector to store intermediate values to compute derivatives 12/10/2018
	std::vector<double> DI(max_order + 1, 0.0);
	std::vector<double> DT(max_order + 1, 0.0);

		boost::math::normal df(mu, sigma);
		//June 04, 2018. IMPORTANT, we actually need a truncated normal distribution on [-1, 1] here to be the prototypes.
		//OTHERWISE, the integral of the pdf is not 1 and will intend to generate extreme high values in the simulation.
		//moments[0] = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		//double c1 = sigma*sigma*(boost::math::pdf(df, -1) - boost::math::pdf(df, 1));
		//double c2 = sigma*sigma*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1));

		double scaling = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		moments[0] = 1.0;
		double c1 = sigma*sigma*(boost::math::pdf(df, 1) - boost::math::pdf(df, -1))/scaling;
		double c2 = sigma*sigma*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1))/scaling;
		moments[1] = mu * moments[0] - c1;
		T[0] = moments[1];
		P[0] = 1;
		P[1] = mu;
		//The above are the initialized values for the recursive computations

		//The derivatives of initialized values --Added on 12/10/2018
		double C1 = ((1-mu)*(1-mu)/sigma + sigma)/scaling * boost::math::pdf(df, 1) - ((1+mu)*(1+mu)/sigma + sigma)/scaling  * boost::math::pdf(df, -1)
			+ ((1-mu)*boost::math::pdf(df, 1) + (1+mu)*boost::math::pdf(df, -1))*c1/sigma/scaling;

		double C2 = ((1-mu)*(1-mu)/sigma+ sigma)/scaling  * boost::math::pdf(df, 1) + ((1+mu)*(1+mu)/sigma + sigma)/scaling * boost::math::pdf(df, -1)
			+ ((1-mu)*boost::math::pdf(df, 1) + (1+mu)*boost::math::pdf(df, -1))*c2/sigma/scaling;

		//
		DI[0] = 0;
		DI[1] = -C1;
		DT[0] = DI[1];
		//The above are intialized to compute the derivatives recursively.

		//It's a waste of memory to store the constant value DI[0], but just for the temporary convenience.
		prototype_derivatives[0] = DI[0];
		prototype_derivatives[1] = DI[1];

		for(int w = 1; w < max_order; ++ w)
		{
			DT[w] = mu * DI[w] - (w%2?C2:C1);

			T[w] = mu * moments[w] - (w%2?c2:c1);
			int k = w - 1;
			while (k >= 0)
			{
				DT[w] += (sigma*sigma*DI[k] + 2*sigma*moments[k])*(2*k+1);

				T[w] += sigma*sigma* (2*k+1)*moments[k];
				k -= 2;
			}

			DI[w+1] = ((2*w+1)*DT[w] - w*DI[w-1])/double (w+1);
			P[w+1] = (mu*P[w]*(2*w+1) - w*P[w-1])/double(w+1);

			prototype_derivatives[w+1] = DI[w+1];
			moments[w+1] = ((2*w+1)*T[w] - w*moments[w-1])/double (w+1);
		}

	////Let's calculate E[P_0] at first, not that P_0(x) = 1.
	//boost::math::normal c(m, 1.0);

	//double EP0 =  boost::math::cdf(c, 1-m) - boost::math::cdf(c, -1-m);
	////Now let's compute E[P_1] and note thata P_1(x) = x.

	////Check the derivation on the blue notebook.(YLQ)
	//double EP1 = m * EP0 + boost::math::pdf(c, -1-m) - boost::math::pdf(c, 1-m);

	//moments[0] = EP0;
	//if (w == 0){
	//	return 0;
	//}
	//moments[1] = EP1;
	//if (w==1){
	//
	//	return 0;
	//}

	//double c1 = boost::math::pdf(c, 1);
	//double c2 = boost::math::pdf(c, -1);

	////Now calculate the moments based on the recursive relations.
	////Check the derivation from the blue notebook.
	//double dP0 = EP0;
	//double dP1 = EP1;
	//for(int n = 2; n <= w; ++ n){

	//	double realn = (double) n;
	//	//note the property of Pn(-1) = (-1)^n and Pn(1) = 1
	//	double cn = ((n%2)? c2:-c2) - c1;

	//	//Compute integral of Gau(x)dP_(n-1)
	//	//for( int i = n - 1; i > 0; i -= 2){
	//		//dP += (2*i + 1) * moments[i];
	//	//}
	//
	//	moments[n] =  ( (2 * realn - 1) *(cn + m * moments[n-1] + ((n%2)?dP0:dP1)) -
	//					(realn - 1) * moments[n-2] ) / realn;
	//	if(n%2){
	//		dP0 += moments[n];
	//	}
	//	else{
	//		dP1 += moments[n];
	//	}
	//}

	return 0;
}

//This function compute the moment of a univariate Legendre polynomial with normal distribution on the interval [-1,1].
//Store the moments up to w in the vector
int Legendre_Gauss_Mom(int max_order, double mu, double sigma, std::vector<double>& moments, std::vector<double>& P)
{
	//We only consider the moments of positive orders
	assert (max_order >= 1);

	//Moments from order 0 to order w
	//moments.resize(max_order+1, 0.0);
	moments.assign(max_order+1, 0.0);

	//intermediate variable to compute the kernel moments
	std::vector<double> T(max_order + 1, 0.0);
	//Legendre polynomial values
	//P.resize(max_order + 1, 0.0);
	P.assign(max_order + 1, 0.0);

		boost::math::normal df(mu, sigma);
		//June 04, 2018. IMPORTANT, we actually need a truncated normal distribution on [-1, 1] here to be the prototypes.
		//OTHERWISE, the integral of the pdf is not 1 and will intend to generate extreme high values in the simulation.
		//moments[0] = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		//double c1 = sigma*sigma*(boost::math::pdf(df, -1) - boost::math::pdf(df, 1));
		//double c2 = sigma*sigma*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1));

		double scaling = boost::math::cdf(df, 1) - boost::math::cdf(df, -1);
		moments[0] = 1.0;
		double c1 = sigma*sigma*(boost::math::pdf(df, 1) - boost::math::pdf(df, -1))/scaling;
		double c2 = sigma*sigma*(boost::math::pdf(df, 1) + boost::math::pdf(df, -1))/scaling;
		moments[1] = mu * moments[0] - c1;
		T[0] = moments[1];
		P[0] = 1;
		P[1] = mu;
		//The above are the initialized values for the recursive computations

		for(int w = 1; w < max_order; ++ w)
		{

			T[w] = mu * moments[w] - (w%2?c2:c1);
			int k = w - 1;
			while (k >= 0)
			{
				T[w] += sigma*sigma* (2*k+1)*moments[k];
				k -= 2;
			}

			P[w+1] = (mu*P[w]*(2*w+1) - w*P[w-1])/double(w+1);

			moments[w+1] = ((2*w+1)*T[w] - w*moments[w-1])/double (w+1);
		}

	return 0;
}

GEOSTAT_PLUGIN(kernelsim)


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#include <cstdio>
#include <iostream>
#include <boost/numeric/mtl/recursion/bit_masking.hpp>
#include <boost/numeric/mtl/utility/exception.hpp>

using namespace std;

template <unsigned long Mask>
void test()
{
    printf("Mask %lx, 32x32 base is row-major %i, is column-major %i, shark 2 row-major %i"
	   ", 4x4 row-major %i, column-major %i\n",
	   Mask, mtl::is_32_base_case_row_major<Mask>::value,
	   mtl::is_k_power_base_case_col_major<5, Mask>::value,
	   mtl::is_k_power_base_case_row_major_t_shark<5, 1, Mask>::value,
	   mtl::is_k_power_base_case_row_major<2, Mask>::value,
	   mtl::is_k_power_base_case_col_major<2, Mask>::value);
}

template <unsigned long Mask1, unsigned long Mask2>
void check_same_mask()
{
    printf("Mask1 %lx, Mask2 %lx\n", Mask1, Mask2);
    MTL_THROW_IF(Mask1 != Mask2, mtl::runtime_error("Different masks\n"));
}

#if 0 // Do I use this???
template <bool is_4, unsigned long long s, unsigned long long l>
struct mm
{
    static const unsigned long value= (const unsigned long) l;
};

template <unsigned long long s, unsigned long long l>
struct mm<true, s, l>
{
    static const unsigned long value= (const unsigned long) s;
};

template <unsigned long long s, unsigned long long l>
struct mask
{
    static const unsigned long value= mm<sizeof(unsigned long) == 4, s, l>::value;
};
#endif

int main(int, char**)
{
    using mtl::row_major; using mtl::col_major; using mtl::generate_mask;

    const unsigned long z= 0, morton= (z-1) / 3, morton_z= ~morton, doppled_4_row= morton + 7,
                        doppled_4_col= morton - 2, doppled_32_row= morton + 651,
	                doppled_32_col= morton - 310, doppled_32_row_shark_2= morton + 620;

    test<morton>();
    test<morton_z>();
    test<doppled_4_row>();
    test<doppled_4_col>();
    test<doppled_32_row>();
    test<doppled_32_col>();
    test<doppled_32_row_shark_2>();

    const unsigned long morton_gen= generate_mask<true, 0, row_major, 0>::value,
	morton_z_gen= generate_mask<false, 0, row_major, 0>::value,
	doppled_4_row_gen= generate_mask<true, 2, row_major, 0>::value,
	doppled_4_col_gen= generate_mask<true, 2, col_major, 0>::value,
	doppled_32_row_gen= generate_mask<true, 5, row_major, 0>::value,
	doppled_32_col_gen= generate_mask<true, 5, col_major, 0>::value,
	doppled_32_row_shark_2_gen= generate_mask<true, 5, row_major, 1>::value;

    check_same_mask<morton, morton_gen>();
    check_same_mask<morton_z, morton_z_gen>();
    check_same_mask<doppled_4_row, doppled_4_row_gen>();
    check_same_mask<doppled_4_col, doppled_4_col_gen>();
    check_same_mask<doppled_32_row, doppled_32_row_gen>();
    check_same_mask<doppled_32_col, doppled_32_col_gen>();
    check_same_mask<doppled_32_row_shark_2, doppled_32_row_shark_2_gen>();

    return 0;
}


//
//  lsh_example.cpp
//  LSH
//
//  Created by Roberto Perdisci on 1/7/17.
//  Copyright © 2017 Roberto Perdisci. All rights reserved.
//

#include "lsh.hpp"
#include "minhash.hpp"
#include <iostream>
#include <fstream>
#include <string>
#include <boost/program_options.hpp>

int main(int argc, char* argv[]) {

    unsigned mh_sig_len;
    unsigned lsh_bands;
    unsigned ngram_len;
    std::string corpus_dir;

    namespace po = boost::program_options;
    po::options_description desc("Options");
    desc.add_options()
    ("help,h", "Help message")
    ("mh_sig_len,l", po::value<unsigned>(&mh_sig_len)->default_value(240), "Length of minhash signature")
    ("lsh_bands,b", po::value<unsigned>(&lsh_bands)->default_value(80), "Number of bands used for LSH")
    ("ngram_len,n", po::value<unsigned>(&ngram_len)->default_value(1), "ngram length (number of grams)")
    ("corpus_dir,d", po::value<std::string>(), "Directory containing the documents")
    ;

    po::variables_map vm;
    po::store(po::parse_command_line(argc, argv, desc), vm);
    po::notify(vm);

    if (vm.count("help")) {
        std::cout << desc << std::endl;
        return 1;
    }

    if (!vm.count("corpus_dir")) {
        std::cout << "Error: corpus_dir must be provided!" << std::endl;
        std::cout << desc << std::endl;
        return 1;
    }

    corpus_dir = vm["corpus_dir"].as<std::string>();

    rp::LSH lsh(mh_sig_len,lsh_bands,ngram_len);
    std::cerr << "parameters:" << mh_sig_len << " | " << lsh_bands << " | " << ngram_len << std::endl;

    std::cerr << "Computing minhash for files in : " << corpus_dir << std::endl;
    lsh.minhash_corpus(corpus_dir,true,true);
    std::ofstream of;
    of.open("minhash_sigs.csv");
    rp::MinHashMap mh_map = lsh.get_minhash_signatures();
    for (rp::MinHashMap::value_type itm : mh_map) {
        of << itm.first;
        for(uint32_t v : itm.second)
            of << ", " << v;
        of << std::endl;
    }
    of.close();

    std::cerr << "Computing bands  " << lsh_bands << std::endl;
    lsh.compute_bands();
    std::cerr << "Computing candidates " << std::endl;
    lsh.compute_candidate_pairs();
    std::vector<rp::CandidatePair> cpv = lsh.get_candidate_pairs();

    std::cerr << "Computing minhash similarity: " << cpv.size() << std::endl;
    lsh.compute_minhash_similarities();
    of.open("minhash_sim.csv");
    std::vector<float> jsvmh = lsh.get_minhash_similarities();
    for(int i=0; i<cpv.size(); i++)
        of << cpv[i].first << "," << cpv[i].second << "," << jsvmh[i] << std::endl;
    of.close();

    // we can also compute the similarity based on the
    // original document ngrams
    // this is available if lsh.minhash_corpus() was called with keep_ngrams set to true
    of.open("ngram_sim.csv");
    std::cerr << "Computing ngram similarity: " << cpv.size() << std::endl;
    lsh.compute_ngram_similarities();
    std::vector<float> jsvng = lsh.get_ngram_similarities();
    for(int i=0; i<cpv.size(); i++)
        of << "ngram: " << cpv[i].first << "," << cpv[i].second << "," << jsvng[i] << std::endl;
    of.close();

}


/* ************************************************************************
 * Copyright 2016 Advanced Micro Devices, Inc.
 * ************************************************************************ */

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cctype>
#include <boost/program_options.hpp>

#include "rocblas.h"
#include "utility.hpp"
#include "rocblas.hpp"
#include "rocblas_data.hpp"
#include "rocblas_datatype2char.hpp"
#include "testing_iamax_iamin.hpp"
#include "testing_asum.hpp"
#include "testing_axpy.hpp"
#include "testing_copy.hpp"
#include "testing_dot.hpp"
#include "testing_swap.hpp"
#include "testing_gemv.hpp"
#include "testing_ger.hpp"
#include "testing_syr.hpp"
#include "testing_nrm2.hpp"
#include "testing_scal.hpp"
#include "testing_trtri.hpp"
#include "testing_trtri_batched.hpp"
#include "testing_geam.hpp"
#include "testing_set_get_vector.hpp"
#include "testing_set_get_matrix.hpp"
#include "type_dispatch.hpp"

#if BUILD_WITH_TENSILE
#include "testing_gemm.hpp"
#include "testing_gemm_strided_batched.hpp"
#include "testing_trsm.hpp"
#include "testing_trsv.hpp"
#include "testing_gemm_ex.hpp"
#include "testing_gemm_strided_batched_ex.hpp"

// Template to dispatch testing_gemm_ex for performance tests
// When Ti == void or complex, the test is marked invalid
template <typename Ti,
          typename To = Ti,
          typename Tc = To,
          typename    = typename std::conditional<!std::is_same<Ti, void>::value && !is_complex<Ti>,
                                               std::true_type,
                                               std::false_type>::type>
struct perf_gemm_ex
{
    explicit operator bool() const { return true; }
    void operator()(const Arguments& arg) { testing_gemm_ex<Ti, To, Tc>(arg); }
};

template <typename Ti, typename To, typename Tc>
struct perf_gemm_ex<Ti, To, Tc, std::false_type> : rocblas_test_invalid
{
};

// Template to dispatch testing_gemm_strided_batched_ex for performance tests
// When Ti == void or complex, the test is marked invalid
template <typename Ti,
          typename To = Ti,
          typename Tc = To,
          typename    = typename std::conditional<!std::is_same<Ti, void>::value && !is_complex<Ti>,
                                               std::true_type,
                                               std::false_type>::type>
struct perf_gemm_strided_batched_ex
{
    explicit operator bool() const { return true; }
    void operator()(const Arguments& arg) { testing_gemm_strided_batched_ex<Ti, To, Tc>(arg); }
};

template <typename Ti, typename To, typename Tc>
struct perf_gemm_strided_batched_ex<Ti, To, Tc, std::false_type> : rocblas_test_invalid
{
};

#endif

int run_bench_test(const char* function, char precision, Arguments arg)
{
    static constexpr char prefix[] = "testing_";
    if(!strncmp(function, prefix, sizeof(prefix) - 1))
    {
        function += sizeof(prefix) - 1;
    }

    if(!strcmp(function, "asum"))
    {
        if(precision == 's')
            testing_asum<float, float>(arg);
        else if(precision == 'd')
            testing_asum<double, double>(arg);
    }
    else if(!strcmp(function, "axpy"))
    {
        if(precision == 'h')
            testing_axpy<rocblas_half>(arg);
        else if(precision == 's')
            testing_axpy<float>(arg);
        else if(precision == 'd')
            testing_axpy<double>(arg);
    }
    else if(!strcmp(function, "copy"))
    {
        if(precision == 's')
            testing_copy<float>(arg);
        else if(precision == 'd')
            testing_copy<double>(arg);
    }
    else if(!strcmp(function, "dot"))
    {
        if(precision == 's')
            testing_dot<float>(arg);
        else if(precision == 'd')
            testing_dot<double>(arg);
    }
    else if(!strcmp(function, "swap"))
    {
        if(precision == 's')
            testing_swap<float>(arg);
        else if(precision == 'd')
            testing_swap<double>(arg);
    }
    else if(!strcmp(function, "iamax"))
    {
        if(precision == 's')
            testing_iamax<float>(arg);
        else if(precision == 'd')
            testing_iamax<double>(arg);
    }
    else if(!strcmp(function, "iamin"))
    {
        if(precision == 's')
            testing_iamin<float>(arg);
        else if(precision == 'd')
            testing_iamin<double>(arg);
    }
    else if(!strcmp(function, "nrm2"))
    {
        if(precision == 's')
            testing_nrm2<float, float>(arg);
        else if(precision == 'd')
            testing_nrm2<double, double>(arg);
    }
    else if(!strcmp(function, "scal"))
    {
        if(precision == 's')
            testing_scal<float>(arg);
        else if(precision == 'd')
            testing_scal<double>(arg);
    }
    else if(!strcmp(function, "gemv"))
    {
        if(precision == 's')
            testing_gemv<float>(arg);
        else if(precision == 'd')
            testing_gemv<double>(arg);
    }
    else if(!strcmp(function, "ger"))
    {
        if(precision == 's')
            testing_ger<float>(arg);
        else if(precision == 'd')
            testing_ger<double>(arg);
    }
    else if(!strcmp(function, "syr"))
    {
        if(precision == 's')
            testing_syr<float>(arg);
        else if(precision == 'd')
            testing_syr<double>(arg);
    }
    else if(!strcmp(function, "trtri"))
    {
        if(precision == 's')
            testing_trtri<float>(arg);
        else if(precision == 'd')
            testing_trtri<double>(arg);
    }
    else if(!strcmp(function, "trtri_batched"))
    {
        if(precision == 's')
            testing_trtri_batched<float>(arg);
        else if(precision == 'd')
            testing_trtri_batched<double>(arg);
    }
    else if(!strcmp(function, "geam"))
    {
        if(precision == 's')
            testing_geam<float>(arg);
        else if(precision == 'd')
            testing_geam<double>(arg);
    }
    else if(!strcmp(function, "set_get_vector"))
    {
        if(precision == 's')
            testing_set_get_vector<float>(arg);
        else if(precision == 'd')
            testing_set_get_vector<double>(arg);
    }
    else if(!strcmp(function, "set_get_matrix"))
    {
        if(precision == 's')
            testing_set_get_matrix<float>(arg);
        else if(precision == 'd')
            testing_set_get_matrix<double>(arg);
    }
#if BUILD_WITH_TENSILE
    else if(!strcmp(function, "gemm"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = arg.transA == 'N' ? arg.M : arg.K;
        rocblas_int min_ldb = arg.transB == 'N' ? arg.K : arg.N;
        rocblas_int min_ldc = arg.M;

        if(arg.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            arg.lda = min_lda;
        }
        if(arg.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            arg.ldb = min_ldb;
        }
        if(arg.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            arg.ldc = min_ldc;
        }

        if(precision == 'h')
            testing_gemm<rocblas_half>(arg);
        else if(precision == 's')
            testing_gemm<float>(arg);
        else if(precision == 'd')
            testing_gemm<double>(arg);
    }
    else if(!strcmp(function, "gemm_ex"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = arg.transA == 'N' ? arg.M : arg.K;
        rocblas_int min_ldb = arg.transB == 'N' ? arg.K : arg.N;
        rocblas_int min_ldc = arg.M;
        rocblas_int min_ldd = arg.M;

        if(arg.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            arg.lda = min_lda;
        }
        if(arg.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            arg.ldb = min_ldb;
        }
        if(arg.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            arg.ldc = min_ldc;
        }
        if(arg.ldd < min_ldd)
        {
            std::cout << "rocblas-bench INFO: ldd < min_ldd, set ldd = " << min_ldc << std::endl;
            arg.ldd = min_ldd;
        }
        rocblas_gemm_dispatch<perf_gemm_ex>(arg);
    }
    else if(!strcmp(function, "gemm_strided_batched"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = arg.transA == 'N' ? arg.M : arg.K;
        rocblas_int min_ldb = arg.transB == 'N' ? arg.K : arg.N;
        rocblas_int min_ldc = arg.M;
        if(arg.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            arg.lda = min_lda;
        }
        if(arg.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            arg.ldb = min_ldb;
        }
        if(arg.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            arg.ldc = min_ldc;
        }

        //      rocblas_int min_stride_a =
        //          arg.transA == 'N' ? arg.K * arg.lda : arg.M * arg.lda;
        //      rocblas_int min_stride_b =
        //          arg.transB == 'N' ? arg.N * arg.ldb : arg.K * arg.ldb;
        //      rocblas_int min_stride_a =
        //          arg.transA == 'N' ? arg.K * arg.lda : arg.M * arg.lda;
        //      rocblas_int min_stride_b =
        //          arg.transB == 'N' ? arg.N * arg.ldb : arg.K * arg.ldb;
        rocblas_int min_stride_c = arg.ldc * arg.N;
        //      if (arg.stride_a < min_stride_a)
        //      {
        //          std::cout << "rocblas-bench INFO: stride_a < min_stride_a, set stride_a = " <<
        //          min_stride_a << std::endl;
        //          arg.stride_a = min_stride_a;
        //      }
        //      if (arg.stride_b < min_stride_b)
        //      {
        //          std::cout << "rocblas-bench INFO: stride_b < min_stride_b, set stride_b = " <<
        //          min_stride_b << std::endl;
        //          arg.stride_b = min_stride_b;
        //      }
        if(arg.stride_c < min_stride_c)
        {
            std::cout << "rocblas-bench INFO: stride_c < min_stride_c, set stride_c = "
                      << min_stride_c << std::endl;
            arg.stride_c = min_stride_c;
        }

        if(precision == 'h')
            testing_gemm_strided_batched<rocblas_half>(arg);
        else if(precision == 's')
            testing_gemm_strided_batched<float>(arg);
        else if(precision == 'd')
            testing_gemm_strided_batched<double>(arg);
    }
    else if(!strcmp(function, "gemm_strided_batched_ex"))
    {
        // adjust dimension for GEMM routines
        rocblas_int min_lda = arg.transA == 'N' ? arg.M : arg.K;
        rocblas_int min_ldb = arg.transB == 'N' ? arg.K : arg.N;
        rocblas_int min_ldc = arg.M;
        rocblas_int min_ldd = arg.M;
        if(arg.lda < min_lda)
        {
            std::cout << "rocblas-bench INFO: lda < min_lda, set lda = " << min_lda << std::endl;
            arg.lda = min_lda;
        }
        if(arg.ldb < min_ldb)
        {
            std::cout << "rocblas-bench INFO: ldb < min_ldb, set ldb = " << min_ldb << std::endl;
            arg.ldb = min_ldb;
        }
        if(arg.ldc < min_ldc)
        {
            std::cout << "rocblas-bench INFO: ldc < min_ldc, set ldc = " << min_ldc << std::endl;
            arg.ldc = min_ldc;
        }
        if(arg.ldd < min_ldd)
        {
            std::cout << "rocblas-bench INFO: ldd < min_ldd, set ldd = " << min_ldc << std::endl;
            arg.ldd = min_ldd;
        }
        rocblas_int min_stride_c = arg.ldc * arg.N;
        if(arg.stride_c < min_stride_c)
        {
            std::cout << "rocblas-bench INFO: stride_c < min_stride_c, set stride_c = "
                      << min_stride_c << std::endl;
            arg.stride_c = min_stride_c;
        }

        rocblas_gemm_dispatch<perf_gemm_strided_batched_ex>(arg);
    }
    else if(!strcmp(function, "trsm"))
    {
        if(precision == 's')
            testing_trsm<float>(arg);
        else if(precision == 'd')
            testing_trsm<double>(arg);
    }
    else if(!strcmp(function, "trsv"))
    {
        if(precision == 's')
            testing_trsv<float>(arg);
        else if(precision == 'd')
            testing_trsv<double>(arg);
    }
#endif
    else
    {
        printf("Invalid value for --function \n");
        return -1;
    }

    return 0;
}

int rocblas_bench_datafile(const std::string& datafile)
{
    RocBLAS_TestData::set_filename(datafile);

    for(auto i = RocBLAS_TestData::begin(); i != RocBLAS_TestData::end(); ++i)
    {
        Arguments arg = *i;
        char precision;

        // disable unit_check in client benchmark, it is only used in gtest unit test
        arg.unit_check = 0;

        // enable timing check,otherwise no performance data collected
        arg.timing = 1;

        switch(arg.a_type)
        {
        case rocblas_datatype_f64_r: precision = 'd'; break;
        case rocblas_datatype_f32_r: precision = 's'; break;
        case rocblas_datatype_f16_r: precision = 'h'; break;
        case rocblas_datatype_f64_c: precision = 'z'; break;
        case rocblas_datatype_f32_c: precision = 'c'; break;
        case rocblas_datatype_f16_c: precision = 'k'; break;
        default: precision                     = 's'; break;
        }
        run_bench_test(arg.function, precision, arg);
    }

    test_cleanup::cleanup();
    return 0;
}

using namespace boost::program_options;

int main(int argc, char* argv[])
{
    Arguments arg;
    arg.unit_check =
        0;          // disable unit_check in client benchmark, it is only used in gtest unit test
    arg.timing = 1; // enable timing check,otherwise no performance data collected

    std::string function;
    char precision;
    char a_type;
    char b_type;
    char c_type;
    char d_type;
    char compute_type;

    rocblas_int device_id;
    std::string datafile;

    options_description desc("rocblas client command line options");
    desc.add_options()
        // clang-format off
        ("sizem,m",
         value<rocblas_int>(&arg.M)->default_value(128),
         "Specific matrix size: sizem is only applicable to BLAS-2 & BLAS-3: the number of "
         "rows or columns in matrix.")

        ("sizen,n",
         value<rocblas_int>(&arg.N)->default_value(128),
         "Specific matrix/vector size: BLAS-1: the length of the vector. BLAS-2 & "
         "BLAS-3: the number of rows or columns in matrix")

        ("sizek,k",
         value<rocblas_int>(&arg.K)->default_value(128),
         "Specific matrix size:sizek is only applicable to BLAS-3: the number of columns in "
         "A and rows in B.")

        ("lda",
         value<rocblas_int>(&arg.lda)->default_value(128),
         "Leading dimension of matrix A, is only applicable to BLAS-2 & BLAS-3.")

        ("ldb",
         value<rocblas_int>(&arg.ldb)->default_value(128),
         "Leading dimension of matrix B, is only applicable to BLAS-2 & BLAS-3.")

        ("ldc",
         value<rocblas_int>(&arg.ldc)->default_value(128),
         "Leading dimension of matrix C, is only applicable to BLAS-2 & BLAS-3.")

        ("ldd",
         value<rocblas_int>(&arg.ldd)->default_value(128),
         "Leading dimension of matrix D, is only applicable to BLAS-EX ")

        ("stride_a",
         value<rocblas_int>(&arg.stride_a)->default_value(128*128),
         "Specific stride of strided_batched matrix A, is only applicable to strided batched"
         "BLAS-2 and BLAS-3: second dimension * leading dimension.")

        ("stride_b",
         value<rocblas_int>(&arg.stride_b)->default_value(128*128),
         "Specific stride of strided_batched matrix B, is only applicable to strided batched"
         "BLAS-2 and BLAS-3: second dimension * leading dimension.")

        ("stride_c",
         value<rocblas_int>(&arg.stride_c)->default_value(128*128),
         "Specific stride of strided_batched matrix C, is only applicable to strided batched"
         "BLAS-2 and BLAS-3: second dimension * leading dimension.")

        ("stride_d",
         value<rocblas_int>(&arg.stride_d)->default_value(128*128),
         "Specific stride of strided_batched matrix D, is only applicable to strided batched"
         "BLAS_EX: second dimension * leading dimension.")

        ("incx",
         value<rocblas_int>(&arg.incx)->default_value(1),
         "increment between values in x vector")

        ("incy",
         value<rocblas_int>(&arg.incy)->default_value(1),
         "increment between values in y vector")

        ("alpha",
          value<double>(&arg.alpha)->default_value(1.0), "specifies the scalar alpha")

        ("beta",
         value<double>(&arg.beta)->default_value(0.0), "specifies the scalar beta")

        ("function,f",
         value<std::string>(&function)->default_value("gemv"),
         "BLAS function to test. Options: gemv, ger, syr, trsm, trsv, trmm, symv, syrk, syr2k")

        ("precision,r",
         value<char>(&precision)->default_value('s'), "Options: h,s,d,c,z")

        ("a_type",
         value<char>(&a_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix A, only applicable to BLAS_EX")

        ("b_type",
         value<char>(&b_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix B, only applicable to BLAS_EX")

        ("c_type",
         value<char>(&c_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix C, only applicable to BLAS_EX")

        ("d_type",
         value<char>(&d_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of matrix D, only applicable to BLAS_EX")

        ("compute_type",
         value<char>(&compute_type)->default_value('s'), "Options: h,s,d,c,z"
         "Precision of computation, only applicable to BLAS_EX")

        ("transposeA",
         value<char>(&arg.transA)->default_value('N'),
         "N = no transpose, T = transpose, C = conjugate transpose")

        ("transposeB",
         value<char>(&arg.transB)->default_value('N'),
         "N = no transpose, T = transpose, C = conjugate transpose")

        ("side",
         value<char>(&arg.side)->default_value('L'),
         "L = left, R = right. Only applicable to certain routines")

        ("uplo",
         value<char>(&arg.uplo)->default_value('U'),
         "U = upper, L = lower. Only applicable to certain routines") // xsymv xsyrk xsyr2k xtrsm
                                                                     // xtrmm xtrsv
        ("diag",
         value<char>(&arg.diag)->default_value('N'),
         "U = unit diagonal, N = non unit diagonal. Only applicable to certain routines") // xtrsm xtrsv
                                                                                // xtrmm
        ("batch",
         value<rocblas_int>(&arg.batch_count)->default_value(1),
         "Number of matrices. Only applicable to batched routines") // xtrsm xtrmm xgemm

        ("verify,v",
         value<rocblas_int>(&arg.norm_check)->default_value(0),
         "Validate GPU results with CPU? 0 = No, 1 = Yes (default: No)")

        ("iters,i",
         value<rocblas_int>(&arg.iters)->default_value(10),
         "Iterations to run inside timing loop")

        ("algo",
         value<uint32_t>(&arg.algo)->default_value(0),
         "extended precision gemm algorithm")

        ("solution_index",
         value<int32_t>(&arg.solution_index)->default_value(0),
         "extended precision gemm solution index")

        ("flags",
         value<uint32_t>(&arg.flags)->default_value(10),
         "extended precision gemm flags")

        ("workspace_size",
         value<size_t>(&arg.workspace_size)->default_value(10),
         "extended precision gemm workspace size")

        ("data",
         value<std::string>(&datafile),
         "Data file to use for test arguments (overrides all of the above)")

        ("device",
         value<rocblas_int>(&device_id)->default_value(0),
         "Set default device to be used for subsequent program runs")

        ("help,h", "produces this help message")

        ("version", "Prints the version number");
    // clang-format on

    variables_map vm;
    store(parse_command_line(argc, argv, desc), vm);
    notify(vm);

    if(vm.count("help"))
    {
        std::cout << desc << std::endl;
        return 0;
    }

    if(vm.find("version") != vm.end())
    {
        char blas_version[100];
        rocblas_get_version_string(blas_version, sizeof(blas_version));
        std::cout << "rocBLAS version: " << blas_version << std::endl;
        return 0;
    }
    // Device Query
    rocblas_int device_count = query_device_property();

    std::cout << std::endl;
    if(device_count <= device_id)
    {
        printf("Error: Invalid device ID. There may not be such device ID. Will exit \n");
        return -1;
    }
    else
    {
        set_device(device_id);
    }

    if(datafile != "")
    {
        return rocblas_bench_datafile(datafile);
    }

    if(!strchr("hsdcz", tolower(precision)))
    {
        std::cerr << "Invalid value for --precision" << std::endl;
        return -1;
    }

    arg.a_type = char2rocblas_datatype(a_type);
    if(arg.a_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --a_type" << std::endl;
        return -1;
    }

    arg.b_type = char2rocblas_datatype(b_type);
    if(arg.b_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --b_type" << std::endl;
        return -1;
    }

    arg.c_type = char2rocblas_datatype(c_type);
    if(arg.c_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --c_type" << std::endl;
        return -1;
    }

    arg.d_type = char2rocblas_datatype(d_type);
    if(arg.d_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --d_type" << std::endl;
        return -1;
    }

    arg.compute_type = char2rocblas_datatype(compute_type);
    if(arg.compute_type == static_cast<rocblas_datatype>(-1))
    {
        std::cerr << "Invalid value for --compute_type" << std::endl;
        return -1;
    }

    if(arg.M < 0 || arg.N < 0 || arg.K < 0)
    {
        printf("Invalid matrix dimension\n");
    }

    return run_bench_test(function.c_str(), precision, arg);
}


#include <iostream>
#include <vector>
#include <functional>
#include <Eigen/Dense>

using namespace Eigen;
using std::vector;

double function(vector<MatrixXd>);
vector<MatrixXd> function2(vector<MatrixXd>);

int main()
{
    using std::cout;
    using std::endl;
    using std::vector;

    MatrixXd x = MatrixXd::Ones(2,2);
    MatrixXd W = MatrixXd::Ones(2,2);
    vector<MatrixXd> X;
    X.push_back(x);
    auto loss = [X](MatrixXd W) -> vector<MatrixXd> {return (function2(X));};
    std::function<vector<MatrixXd>(MatrixXd)> loss_W = loss;

    // double out;
    vector<MatrixXd> out;
    out = loss_W(W);
    cout << out[0] << endl;

    return 0;
}

double function(vector<MatrixXd> inputs){
    return inputs[0].sum();
}

vector<MatrixXd> function2(vector<MatrixXd> inputs)
{
    return inputs;
}


/*
 *  Submitted as the final project for Dr. Adrian Del Maestro's PHYS 642
 *  Statistical Mechanics (Phase Transitions and Critical Phenomena) class,
 *  Fall 2021 semester at University of Tennessee, Knoxville.
 *
 *  I did it in C++ because I figured I can get good practice in this way. I
 *  also didn't want to use <iostream> because, other than operator overloading,
 *  it's pretty familiar (not that it really should make a huge difference in
 *  this program one way or the other) - at least, the std::cin/cout part is
 *  familiar, which is all that I'd really use.
 */

#include <boost/program_options.hpp>
#define BOOST_PROGRAM_OPTIONS_SET

#include "common_headers.hpp"
#include "MC_functions.hpp"
// #include "MC_functions.cpp"
// #include "GetOptions.cpp"

using std::string;

namespace br = boost::random;
namespace bu = boost::uuids;
namespace po = boost::program_options;

int main(int ac, char **av)
{
    // Command-line parameters
    size_t L            = 16;
    double temp         = 1/3;
//    double beta         = 1;
    size_t numSamp      = pow(2, 17);   // 2^17 > 10^5
    uint64_t numEqSteps = pow(2, 15);

//    uint64_t result = GetOptions(ac, av, &L, &beta, &numSamp, &numEqSteps);
    uint64_t result = GetOptions(ac, av, &L, &temp, &numSamp, &numEqSteps);

    if(result == 0) {}

    else if(result == 1) {return 0; }

    else if(result != 0 && result != 1)
    {
        printf("Please enter valid options and try again.\n");
        return 2;
    }

    // The rest of the parameters
    size_t N            = L*L;
//    double J            = 1;  // These aren't actually implemented, but could
//    double kB           = 1;  // be easily
    bu::uuid id = bu::random_generator()();
    string string_uuid = bu::to_string(id);
    string baseName = string_uuid + ".dat";

    // Accumulators, etc., for desired quantities
    //

    br::random_device rd;
    br::mt19937_64 rng(rd);
    br::uniform_real_distribution<double> dist01(0,1);
//    Params Pars(L, N, numSamp, numEqSteps, beta, baseName, id, rng, dist01);
    Params Pars(L, N, numSamp, numEqSteps, temp, baseName, id, rng, dist01);

    MonteCarlo(Pars);

    return 0;
}


//
// Created by philipp on 22.12.19.
//
#include <Eigen/Dense>

#include "funnels/lyapunov.hh"

namespace lyapunov{

  bool lyapunov_t::intersect(const lyap_zone_t &other) {
    throw std::runtime_error("Virtual");
    return false;
  }

  bool lyapunov_t::covers(const lyap_zone_t &other) {
    throw std::runtime_error("Virtual");
    return false;
  }

  bool lyapunov_t::is_covered(const lyap_zone_t &other){
    throw std::runtime_error("Virtual");
    return false;
  }

  fixed_ellipsoidal_zone_copied_t::fixed_ellipsoidal_zone_copied_t(
      const Eigen::VectorXd &x0, const Eigen::MatrixXd &C):
      _x0(x0), _C(C){}

  fixed_ellipsoidal_zone_ref_t::fixed_ellipsoidal_zone_ref_t(
      const Eigen::VectorXd &x0, const Eigen::MatrixXd &C):
      _x0(x0), _C(C){}

}


#ifndef POLIMIDL_LAYERS_INTERNAL_POINTWISE_CONVOLUTION_HPP
#define POLIMIDL_LAYERS_INTERNAL_POINTWISE_CONVOLUTION_HPP

#include <algorithm>
#include <chrono>
#include <iterator>
#include <iostream>

#include <Eigen/Dense>

#include "../alignment.hpp"
#include "../layer.hpp"

namespace polimidl {
namespace layers {
namespace internal {
template <typename type_t, typename components>
class pointwise_convolution : public layer<type_t, components> {
 public:
  pointwise_convolution(const type_t* coeff) :
      coeff_(coeff),
      batch_size_(1) {}

  template <typename input_t, typename temporary_t, typename output_t,
            typename scheduler_t>
  void operator()(input_t input, temporary_t temporary, output_t output,
                  std::size_t rows, std::size_t columns,
                  const scheduler_t& scheduler) const {
    const std::size_t cells = rows * columns;
    for (std::size_t start_cell = 0; start_cell < cells;
         start_cell += batch_size_) {
      const std::size_t end_cell = std::min(cells, start_cell + batch_size_);
      scheduler.schedule([=] (std::size_t worker) {
        const std::size_t cells_in_this_batch = end_cell - start_cell;
        auto i_input = &input[components::input * start_cell];
        auto i_output = &output[components::output * start_cell];
        matrix_input_t val(i_input, components::input, cells_in_this_batch);
        matrix_output_t out(i_output, components::output, cells_in_this_batch);
        out.noalias() = coeff_ * val;
      });
    }
    scheduler.wait();
  }

  template <typename input_t, typename temporary_t, typename output_t,
            typename scheduler_t>
  void optimize_for(input_t input, temporary_t temporary, output_t output,
                    std::size_t rows, std::size_t columns,
                    const scheduler_t& scheduler) {
    const std::size_t cells = rows * columns;
    if (scheduler.number_of_workers() == 1) {
      batch_size_ = cells;
      return;
    }

    auto best_duration = std::chrono::high_resolution_clock::duration::max();

    std::size_t best_batch_size = 1;
    auto test_and_maybe_set = [&]() {
      for (std::size_t iteration = 0; iteration < 3; ++iteration) {
        const auto start = std::chrono::high_resolution_clock::now();
        operator()(input, temporary, output, rows, columns, scheduler);
        const auto current_duration =
            std::chrono::high_resolution_clock::now() - start;
        if (current_duration < best_duration) {
          best_duration = current_duration;
          best_batch_size = batch_size_;
        }
      }
    };
    batch_size_ = 1;
    while (batch_size_ < cells) {
      // Ensure the alignment of all the Eigen Matrixes.
      if ((batch_size_ * components::input) % alignment_t::type_t_alignment ||
          (batch_size_ * components::output) % alignment_t::type_t_alignment) {
        batch_size_ *= 2;
        continue;
      }
      test_and_maybe_set();
      batch_size_ *= 2;
    }
    batch_size_ = cells;
    test_and_maybe_set();
    batch_size_ = best_batch_size;
  }
 private:
  using alignment_t = alignment<type_t>;
  using matrix_input_t = Eigen::Map<
      Eigen::Matrix<type_t, components::input, Eigen::Dynamic>,
      alignment_t::eigen_alignment>;
  using matrix_output_t = Eigen::Map<
      Eigen::Matrix<type_t, components::output, Eigen::Dynamic>,
      alignment_t::eigen_alignment>;
  using matrix_coefficients_t = Eigen::Map<
      const Eigen::Matrix<type_t, components::output, components::input,
                          Eigen::RowMajor>,
      alignment_t::eigen_alignment>;

  matrix_coefficients_t coeff_;
  std::size_t batch_size_;
};
}
}
}

#endif  // POLIMIDL_LAYERS_INTERNAL_POINTWISE_CONVOLUTION_HPP


// Copyright (c) 2021 Graphcore Ltd. All rights reserved.

#include <popsolver/Model.hpp>
#define BOOST_TEST_MODULE ReifiedLessOrEqualOrEqual
#include <boost/test/unit_test.hpp>

using namespace popsolver;

BOOST_AUTO_TEST_CASE(ReifiedLessOrEqualCase1) {
  Model m;

  auto a = m.addConstant(0);
  auto b = m.addConstant(0);
  auto c = m.reifiedLessOrEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{1});
}

BOOST_AUTO_TEST_CASE(ReifiedLessOrEqualCase2) {
  Model m;

  auto a = m.addConstant(1);
  auto b = m.addConstant(0);
  auto c = m.reifiedLessOrEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{0});
}

BOOST_AUTO_TEST_CASE(ReifiedLessOrEqualCase3) {
  Model m;

  auto a = m.addConstant(20);
  auto b = m.addConstant(19);
  auto c = m.reifiedLessOrEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{0});
}

BOOST_AUTO_TEST_CASE(ReifiedLessOrEqualCase4) {
  Model m;

  auto a = m.addConstant(DataType::max());
  auto b = m.addConstant(DataType::max());
  auto c = m.reifiedLessOrEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{1});
}

BOOST_AUTO_TEST_CASE(ReifiedLessOrEqualCase5) {
  Model m;

  auto a = m.addConstant(DataType::min());
  auto b = m.addConstant(DataType::max());
  auto c = m.reifiedLessOrEqual(a, b);
  auto s = m.minimize(c);

  BOOST_CHECK(s.validSolution());
  BOOST_CHECK_EQUAL(s[c], DataType{1});
}