#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)


/*
 *  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;
}


#ifndef BOOST_GEOMETRY_PROJECTIONS_AITOFF_HPP
#define BOOST_GEOMETRY_PROJECTIONS_AITOFF_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

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

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

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// Purpose:  Implementation of the aitoff (Aitoff) and wintri (Winkel Tripel)
// projections.
// Author:   Gerald Evenden
// Copyright (c) 1995, Gerald Evenden

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include <boost/core/ignore_unused.hpp>
#include <boost/geometry/util/math.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct aitoff {};
    struct wintri {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace aitoff
    {
            template <typename T>
            struct par_aitoff
            {
                T    cosphi1;
                int  mode;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_aitoff_spheroid : public base_t_fi<base_aitoff_spheroid<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_aitoff<CalculationType> m_proj_parm;

                inline base_aitoff_spheroid(const Parameters& par)
                    : base_t_fi<base_aitoff_spheroid<CalculationType, Parameters>,
                     CalculationType, Parameters>(*this, par) {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    CalculationType c, d;

                    if((d = acos(cos(lp_lat) * cos(c = 0.5 * lp_lon)))) {/* basic Aitoff */
                        xy_x = 2. * d * cos(lp_lat) * sin(c) * (xy_y = 1. / sin(d));
                        xy_y *= d * sin(lp_lat);
                    } else
                        xy_x = xy_y = 0.;
                    if (this->m_proj_parm.mode) { /* Winkel Tripel */
                        xy_x = (xy_x + lp_lon * this->m_proj_parm.cosphi1) * 0.5;
                        xy_y = (xy_y + lp_lat) * 0.5;
                    }
                }
                /***********************************************************************************
                *
                * Inverse functions added by Drazen Tutic and Lovro Gradiser based on paper:
                *
                * I.Özbug Biklirici and Cengizhan Ipbüker. A General Algorithm for the Inverse
                * Transformation of Map Projections Using Jacobian Matrices. In Proceedings of the
                * Third International Symposium Mathematical & Computational Applications,
                * pages 175{182, Turkey, September 2002.
                *
                * Expected accuracy is defined by EPSILON = 1e-12. Should be appropriate for
                * most applications of Aitoff and Winkel Tripel projections.
                *
                * Longitudes of 180W and 180E can be mixed in solution obtained.
                *
                * Inverse for Aitoff projection in poles is undefined, longitude value of 0 is assumed.
                *
                * Contact : dtutic@geof.hr
                * Date: 2015-02-16
                *
                ************************************************************************************/

                // INVERSE(s_inverse)  sphere
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    static const CalculationType ONEPI = detail::ONEPI<CalculationType>();
                    static const CalculationType TWOPI = detail::TWOPI<CalculationType>();
                    static const CalculationType EPSILON = 1e-12;

                    int iter, MAXITER = 10, round = 0, MAXROUND = 20;
                    CalculationType D, C, f1, f2, f1p, f1l, f2p, f2l, dp, dl, sl, sp, cp, cl, x, y;

                    if ((fabs(xy_x) < EPSILON) && (fabs(xy_y) < EPSILON )) { lp_lat = 0.; lp_lon = 0.; return; }

                    /* intial values for Newton-Raphson method */
                    lp_lat = xy_y; lp_lon = xy_x;
                    do {
                        iter = 0;
                        do {
                            sl = sin(lp_lon * 0.5); cl = cos(lp_lon * 0.5);
                            sp = sin(lp_lat); cp = cos(lp_lat);
                            D = cp * cl;
                                   C = 1. - D * D;
                            D = acos(D) / pow(C, 1.5);
                                   f1 = 2. * D * C * cp * sl;
                                   f2 = D * C * sp;
                                   f1p = 2.* (sl * cl * sp * cp / C - D * sp * sl);
                                   f1l = cp * cp * sl * sl / C + D * cp * cl * sp * sp;
                                f2p = sp * sp * cl / C + D * sl * sl * cp;
                                  f2l = 0.5 * (sp * cp * sl / C - D * sp * cp * cp * sl * cl);
                                  if (this->m_proj_parm.mode) { /* Winkel Tripel */
                                f1 = 0.5 * (f1 + lp_lon * this->m_proj_parm.cosphi1);
                                f2 = 0.5 * (f2 + lp_lat);
                                f1p *= 0.5;
                                f1l = 0.5 * (f1l + this->m_proj_parm.cosphi1);
                                f2p = 0.5 * (f2p + 1.);
                                f2l *= 0.5;
                            }
                            f1 -= xy_x; f2 -= xy_y;
                            dl = (f2 * f1p - f1 * f2p) / (dp = f1p * f2l - f2p * f1l);
                            dp = (f1 * f2l - f2 * f1l) / dp;
                            while (dl > ONEPI) dl -= ONEPI; /* set to interval [-ONEPI, ONEPI]  */
                            while (dl < -ONEPI) dl += ONEPI; /* set to interval [-ONEPI, ONEPI]  */
                            lp_lat -= dp;    lp_lon -= dl;
                        } while ((fabs(dp) > EPSILON || fabs(dl) > EPSILON) && (iter++ < MAXITER));
                        if (lp_lat > TWOPI) lp_lat -= 2.*(lp_lat-TWOPI); /* correct if symmetrical solution for Aitoff */
                        if (lp_lat < -TWOPI) lp_lat -= 2.*(lp_lat+TWOPI); /* correct if symmetrical solution for Aitoff */
                        if ((fabs(fabs(lp_lat) - TWOPI) < EPSILON) && (!this->m_proj_parm.mode)) lp_lon = 0.; /* if pole in Aitoff, return longitude of 0 */

                        /* calculate x,y coordinates with solution obtained */
                        if((D = acos(cos(lp_lat) * cos(C = 0.5 * lp_lon)))) {/* Aitoff */
                            x = 2. * D * cos(lp_lat) * sin(C) * (y = 1. / sin(D));
                            y *= D * sin(lp_lat);
                        } else
                            x = y = 0.;
                        if (this->m_proj_parm.mode) { /* Winkel Tripel */
                            x = (x + lp_lon * this->m_proj_parm.cosphi1) * 0.5;
                            y = (y + lp_lat) * 0.5;
                        }
                    /* if too far from given values of x,y, repeat with better approximation of phi,lam */
                    } while (((fabs(xy_x-x) > EPSILON) || (fabs(xy_y-y) > EPSILON)) && (round++ < MAXROUND));

                    //if (iter == MAXITER && round == MAXROUND) fprintf(stderr, "Warning: Accuracy of 1e-12 not reached. Last increments: dlat=%e and dlon=%e\n", dp, dl);
                }

                static inline std::string get_name()
                {
                    return "aitoff_spheroid";
                }

            };

            template <typename Parameters, typename T>
            inline void setup(Parameters& par, par_aitoff<T>& proj_parm)
            {
                boost::ignore_unused(proj_parm);
                par.es = 0.;
            }

            // Aitoff
            template <typename Parameters, typename T>
            inline void setup_aitoff(Parameters& par, par_aitoff<T>& proj_parm)
            {
                proj_parm.mode = 0;
                setup(par, proj_parm);
            }

            // Winkel Tripel
            template <typename Parameters, typename T>
            inline void setup_wintri(Parameters& par, par_aitoff<T>& proj_parm)
            {
                static const T TWO_D_PI = detail::TWO_D_PI<T>();

                proj_parm.mode = 1;
                if (pj_param(par.params, "tlat_1").i) {
                    if ((proj_parm.cosphi1 = cos(pj_param(par.params, "rlat_1").f)) == 0.)
                        BOOST_THROW_EXCEPTION( projection_exception(-22) );
                } else /* 50d28' or phi1=acos(2/pi) */
                    proj_parm.cosphi1 = TWO_D_PI;
                setup(par, proj_parm);
            }

    }} // namespace detail::aitoff
    #endif // doxygen

    /*!
        \brief Aitoff projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
        \par Example
        \image html ex_aitoff.gif
    */
    template <typename CalculationType, typename Parameters>
    struct aitoff_spheroid : public detail::aitoff::base_aitoff_spheroid<CalculationType, Parameters>
    {
        inline aitoff_spheroid(const Parameters& par) : detail::aitoff::base_aitoff_spheroid<CalculationType, Parameters>(par)
        {
            detail::aitoff::setup_aitoff(this->m_par, this->m_proj_parm);
        }
    };

    /*!
        \brief Winkel Tripel projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Miscellaneous
         - Spheroid
        \par Projection parameters
         - lat_1: Latitude of first standard parallel (degrees)
        \par Example
        \image html ex_wintri.gif
    */
    template <typename CalculationType, typename Parameters>
    struct wintri_spheroid : public detail::aitoff::base_aitoff_spheroid<CalculationType, Parameters>
    {
        inline wintri_spheroid(const Parameters& par) : detail::aitoff::base_aitoff_spheroid<CalculationType, Parameters>(par)
        {
            detail::aitoff::setup_wintri(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::aitoff, aitoff_spheroid, aitoff_spheroid)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::wintri, wintri_spheroid, wintri_spheroid)

        // Factory entry(s)
        template <typename CalculationType, typename Parameters>
        class aitoff_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<aitoff_spheroid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        class wintri_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<wintri_spheroid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void aitoff_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("aitoff", new aitoff_entry<CalculationType, Parameters>);
            factory.add_to_factory("wintri", new wintri_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_AITOFF_HPP


#if COMPILATION_INSTRUCTIONS
(echo "#include<" $0 ">" > $0x.cpp) && time clang++ - O3 - std = c++ 1z - Wall `# - Wfatal
        - errors` - I..- D_TEST_SPARSE_COO_MATRIX $0x.cpp - lstdc++ fs - lboost_system - lboost_timer - o $0x.x
    && time $0x.x $ @ && rm - f $0x.cpp;
exit
#endif

////////////////////////////////////////////////////////////////////////////////
// File developed by:
// Alfredo Correa, correaa@llnl.gov
//    Lawrence Livermore National Laboratory
//
// File created by:
// Alfredo Correa, correaa@llnl.gov
//    Lawrence Livermore National Laboratory
////////////////////////////////////////////////////////////////////////////////

#ifndef SPARSE_COO_MATRIX_HPP
#define SPARSE_COO_MATRIX_HPP

#include <array>
#include <cassert>
#include <cstddef> // ptrdiff_t
#include <vector>
#include <tuple>

    namespace ma
{
  namespace sparse
  {
  using size_type = std::size_t;

  template<class T, class index, class Alloc = std::allocator<T>>
  class coo_matrix
  {
  protected:
    using alloc_ts        = std::allocator_traits<Alloc>;
    using index_allocator = typename alloc_ts::template rebind_alloc<index>;
    std::vector<index, index_allocator> is_;
    std::vector<index, index_allocator> js_;
    std::vector<T, Alloc> vs_;
    size_type cols_;
    size_type rows_;

  public:
    using element                            = T;
    coo_matrix(coo_matrix const&)            = default;
    coo_matrix(coo_matrix&&)                 = default;
    bool operator==(coo_matrix const&) const = delete;
    coo_matrix(std::tuple<index, index> const& arr                                    = std::tuple<index, index>{0, 0},
               std::initializer_list<std::pair<std::tuple<index, index>, element>> il = {})
        : cols_(std::get<0>(arr)), rows_(std::get<1>(arr))
    {
      for (auto e : il)
        emplace(e.first, e.second);
    }
    void reserve(size_type s)
    {
      is_.reserve(s);
      js_.reserve(s);
      vs_.reserve(s);
    }
    auto size() const { return rows_; }
    auto num_elements() const { return size() * cols_; }
    std::array<index, 2> shape() const { return {{size(), cols_}}; }
    auto num_non_zero_elements() const { return vs_.size(); }
    T* non_zero_values_data() { return vs_.data(); }
    index* non_zero_indices1_data() { return (index*)is_.data(); }
    index* non_zero_indices2_data() const { return (index*)js_.data(); }
    decltype(auto) move_non_zero_values() && { return std::move(vs_); }
    decltype(auto) move_non_zero_indices1() && { return std::move(is_); }
    decltype(auto) move_non_zero_indices2() && { return std::move(js_); }
    void clear()
    {
      is_.clear();
      js_.clear();
      vs_.clear();
      cols_ = 0;
      rows_ = 0;
    }
    template<class Pair = std::array<index, 2>, class TT>
    void emplace(Pair&& indices, TT&& tt)
    {
      using std::get;
      is_.emplace_back(get<0>(std::forward<Pair>(indices)));
      js_.emplace_back(get<1>(std::forward<Pair>(indices)));
      vs_.emplace_back(std::forward<TT>(tt));
    }

  protected:
    struct row_reference
    {
      coo_matrix& self_;
      index i_;
      struct element_reference
      {
        row_reference& self_;
        index j_;
        template<class TT>
        element_reference&& operator=(TT&& tt) &&
        {
          self_.self_.emplace({{self_.i_, j_}}, std::forward<TT>(tt));
          return std::move(*this);
        }
      };
      using reference = element_reference;
      reference operator[](index i) && { return reference{*this, i}; }
    };

  public:
    using reference = row_reference;
    reference operator[](index i) { return reference{*this, i}; }
    friend decltype(auto) size(coo_matrix const& s) { return s.size(); }
    friend decltype(auto) shape(coo_matrix const& s) { return s.shape(); }
    friend decltype(auto) clear(coo_matrix& s) { s.clear(); }
  };
  /*
template<class... Ts>
std::array<index, 2> index_bases(coo_matrix<Ts...> const&){return {{0,0}};}
template<class... Ts>
auto num_non_zero_elements(coo_matrix<Ts...> const& s){
	return s.num_non_zero_elements();
}
template<class... Ts>
auto non_zero_values_data(coo_matrix<Ts...>& s){return s.non_zero_values_data();}
template<class... Ts>
auto non_zero_indices1_data(coo_matrix<Ts...>& s){
	return s.non_zero_indices1_data();
}
template<class... Ts>
auto non_zero_indices2_data(coo_matrix<Ts...>& s){
	return s.non_zero_indices2_data();
}
*/
  } // namespace sparse
}

#ifdef _TEST_SPARSE_COO_MATRIX

#include "iterator/zipper.hpp"
#include "timer/timed.hpp"

#include <boost/timer/timer.hpp>

#include <algorithm> // std::sort
#include <cassert>
#include <iostream>
#include <random>

using std::cerr;
using std::cout;
using std::get;

int main()
{
  using ma::sparse::coo_matrix;

  auto const M          = 40000;
  auto const N          = 40000;
  double const sparsity = 0.01;

  // generate tuples for reference
  auto const csource = [&]() {
    std::vector<std::tuple<int, int, double>> source;
    source.reserve(M * N * sparsity);
    std::default_random_engine gen;
    std::uniform_real_distribution<double> dist;
    std::uniform_real_distribution<double> dist2(0, 10);
    for (auto i = M; i != 0; --i)
      for (auto j = N; j != 0; --j)
        if (dist(gen) < sparsity)
          source.emplace_back(i - 1, j - 1, dist2(gen));
    return source;
  }();

  cerr << "value size " << csource.size() * sizeof(double) / 1000000 << " MB\n";
  {
    coo_matrix<double> coom({M, N});
    {
      boost::timer::auto_cpu_timer t("ugly syntax: %t seconds\n");
      for (auto& s : csource)
        coom.emplace({{get<0>(s), get<1>(s)}}, get<2>(s));
    }
  }
  {
    coo_matrix<double> coom({M, N});
    {
      boost::timer::auto_cpu_timer t("generic syntax: %t seconds\n");
      for (auto& s : csource)
        coom[get<0>(s)][get<1>(s)] = get<2>(s);
    }
  }

  coo_matrix<double> small({4, 4}, {{{3, 3}, 1}, {{2, 1}, 3}, {{0, 1}, 9}});
}

#endif
#endif


/*
 *            Copyright 2009-2018 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      Licensed under the Apache License, Version 2.0 (the "License")
 *
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <votca/xtp/sigma.h>
#include <cmath>
#include <boost/math/constants/constants.hpp>
#include <votca/tools/constants.h>
#include <votca/xtp/ppm.h>
#include <votca/xtp/threecenter.h>

namespace votca {
  namespace xtp {

    Eigen::MatrixXd Sigma::SetupFullQPHamiltonian() {

      // constructing full QP Hamiltonian
      Eigen::MatrixXd Hqp = _sigma_x + _sigma_c - (*_vxc);
      // diagonal elements are given by _qp_energies
      for (int m = 0; m < Hqp.rows(); m++) {
        Hqp(m, m) = _gwa_energies(m + _qpmin);
      }
      return Hqp;
    }

    void Sigma::X_diag(const TCMatrix_gwbse& Mmn){
      int gwsize = Mmn.getAuxDimension(); // size of the GW basis
      _sigma_x=Eigen::MatrixXd::Zero(_qptotal,_qptotal);
      #pragma omp parallel for
      for (int gw_level = 0; gw_level < _qptotal; gw_level++) {
        const MatrixXfd & Mmn1 = Mmn[ gw_level + _qpmin ];
        double sigma_x = 0;
        for (int i_gw = 0; i_gw < gwsize; i_gw++) {
          // loop over all occupied bands used in screening
          for (int i_occ = 0; i_occ <= _homo; i_occ++) {
            sigma_x -= Mmn1( i_occ,i_gw) * Mmn1( i_occ,i_gw);
          } // occupied bands
        } // gwbasis functions
        _sigma_x(gw_level, gw_level) = (1.0 - _ScaHFX) * sigma_x;
      }
    }

    void Sigma::C_diag(const TCMatrix_gwbse& Mmn, const PPM& ppm, const Eigen::VectorXd& qp_old){
      int levelsum = Mmn.get_ntot(); // total number of bands
      int gwsize = Mmn.getAuxDimension(); // size of the GW basis

      // loop over all GW levels
#pragma omp parallel for
      for (int gw_level = 0; gw_level < _qptotal; gw_level++) {
        const MatrixXfd & Mmn1 = Mmn[ gw_level + _qpmin ];
        const double qpmin = qp_old(gw_level + _qpmin);
        double sigma_c = 0.0;
        // loop over all functions in GW basis
        for (int i_gw = 0; i_gw < gwsize; i_gw++) {
          // the ppm_weights smaller 1.e-5 are set to zero in rpa.cc PPM_construct_parameters
          if (ppm.getPpm_weight()(i_gw) < 1.e-9) {
            continue;
          }
          const double ppm_freq = ppm.getPpm_freq()(i_gw);
          const double fac = 0.5*ppm.getPpm_weight()(i_gw) * ppm_freq;
          // loop over all bands
          double sigma_c_loc=0.0;
          for (int i = 0; i < _homo+1; i++) {
            const double factor=Stabilize(qpmin - qp_old(i) +ppm_freq);
            // sigma_c diagonal elements
            sigma_c_loc += factor * Mmn1(i, i_gw) * Mmn1( i,i_gw);
          }// bands
          for (int i = _homo+1; i < levelsum; i++) {
            const double factor=Stabilize(qpmin - qp_old(i) -ppm_freq);
            // sigma_c diagonal elements
            sigma_c_loc += factor * Mmn1(i,i_gw) * Mmn1(i,i_gw);
          }// bands
          sigma_c+=sigma_c_loc*fac;
        }// GW functions
        _sigma_c(gw_level, gw_level) = sigma_c;
        // update _qp_energies
        _gwa_energies(gw_level + _qpmin) = (*_dftenergies)(gw_level + _qpmin) + sigma_c + _sigma_x(gw_level, gw_level) - (*_vxc)(gw_level, gw_level);
      }// all bands
    }

    void Sigma::CalcdiagElements(const TCMatrix_gwbse& Mmn, const PPM & ppm) {
        X_diag(Mmn);
        if(_gwa_energies.size()<1){
            throw std::runtime_error("Sigma gwa_energies not set!");
        }
      _sigma_c=Eigen::MatrixXd::Zero(_qptotal,_qptotal);

      // initial _qp_energies are dft energies
      Eigen::VectorXd qp_old = _gwa_energies;
      // only diagonal elements except for in final iteration
      for (int g_iter = 0; g_iter < _g_sc_max_iterations; g_iter++) {

        C_diag(Mmn, ppm, qp_old);
        Eigen::VectorXd diff = qp_old - _gwa_energies;
        bool energies_converged = true;

        int state = 0;
        double diff_max = diff.cwiseAbs().maxCoeff(&state);
        if (diff_max > _g_sc_limit) {
          energies_converged = false;
        }

        if (tools::globals::verbose) {
          double _DFTgap = (*_dftenergies)(_homo + 1) - (*_dftenergies)(_homo);
          double _QPgap = _gwa_energies(_homo + 1) - _gwa_energies(_homo);
          CTP_LOG(ctp::logDEBUG, *_log) << ctp::TimeStamp() << " G_Iteration: " << g_iter + 1 << " shift=" << _QPgap - _DFTgap << " E_diff max=" << diff_max << " StateNo:" << state << std::flush;
        }
        double alpha = 0.0;
        _gwa_energies = (1 - alpha) * _gwa_energies + alpha*qp_old;

        if (energies_converged) {
          CTP_LOG(ctp::logDEBUG, *_log) << ctp::TimeStamp() << " Converged after " << g_iter + 1 << " G iterations." << std::flush;
          break;
        } else if (g_iter == _g_sc_max_iterations - 1) {
          CTP_LOG(ctp::logDEBUG, *_log) << ctp::TimeStamp() << " G-self-consistency cycle not converged after " << _g_sc_max_iterations << " iterations." << std::flush;
          break;

        } else {
          qp_old = _gwa_energies;
        }
      } // iterations
      return;
    }

    void Sigma::X_offdiag(const TCMatrix_gwbse& Mmn){
      int gwsize = Mmn.getAuxDimension();
      #pragma omp parallel for schedule(dynamic)
      for (int gw_level1 = 0; gw_level1 < _qptotal; gw_level1++) {
        const MatrixXfd& Mmn1 = Mmn[ gw_level1 + _qpmin ];
        for (int gw_level2 = gw_level1+1; gw_level2 < _qptotal; gw_level2++) {
          const MatrixXfd & Mmn2 = Mmn[ gw_level2 + _qpmin ];
          double sigma_x = 0;
          for (int i_gw = 0; i_gw < gwsize; i_gw++) {
            // loop over all occupied bands used in screening
            for (int i_occ = 0; i_occ <= _homo; i_occ++) {
              sigma_x -= Mmn1(i_occ,i_gw) * Mmn2(i_occ,i_gw);
            } // occupied bands
          } // gwbasis functions
          _sigma_x(gw_level1, gw_level2) = (1.0 - _ScaHFX) * sigma_x;
          _sigma_x(gw_level2, gw_level1) = (1.0 - _ScaHFX) * sigma_x;
        }
      }
      return;
    }

    double Sigma::Stabilize(double denom){
      const double fourpi = 4*boost::math::constants::pi<double>();
      double stab = 1.0;
      if (std::abs(denom) < 0.25) {
        stab = 0.5 * (1.0 - std::cos(fourpi * denom));
      }
      return stab / denom;
    }

    double Sigma::SumSymmetric(real_gwbse Mmn1xMmn2, double qpmin1, double qpmin2, double gwa_energy){
      double factor=Stabilize(qpmin1 - gwa_energy);
      factor+= Stabilize(qpmin2 - gwa_energy);
      return Mmn1xMmn2 * factor;
    }

    void Sigma::C_offdiag(const TCMatrix_gwbse& Mmn, const PPM& ppm){

      #pragma omp parallel
      {
        int lumo=_homo+1;
        const int levelsum = Mmn.get_ntot(); // total number of bands
        const int gwsize = Mmn.getAuxDimension(); // size of the GW basis
        const Eigen::VectorXd ppm_weight=ppm.getPpm_weight();
        const Eigen::VectorXd ppm_freqs=ppm.getPpm_freq();
        #pragma omp for schedule(dynamic)
        for (int gw_level1 = 0; gw_level1 < _qptotal; gw_level1++) {
        const MatrixXfd& Mmn1=Mmn[ gw_level1 + _qpmin ];
        for (int gw_level2 = gw_level1+1; gw_level2 < _qptotal; gw_level2++) {
          const MatrixXfd Mmn1xMmn2=Mmn[ gw_level2 + _qpmin ].cwiseProduct(Mmn1);
          const Eigen::VectorXd gwa_energies=_gwa_energies;
          const double qpmin1 = gwa_energies(gw_level1 + _qpmin);
          const double qpmin2 = gwa_energies(gw_level2 + _qpmin);

          double sigma_c=0;
          for (int i_gw = 0; i_gw < gwsize; i_gw++) {
            // the ppm_weights smaller 1.e-5 are set to zero in rpa.cc PPM_construct_parameters
            if (ppm_weight(i_gw) < 1.e-9) {
              continue;
            }
            const double ppm_freq= ppm_freqs(i_gw);
            const double fac = 0.25* ppm_weight(i_gw) * ppm_freq;
            double sigma_loc=0.0;
            // loop over occ screening levels
              for (int i = 0; i < lumo; i++) {
                const double gwa_energy = gwa_energies(i) - ppm_freq;
                sigma_loc+=SumSymmetric(Mmn1xMmn2(i,i_gw), qpmin1, qpmin2, gwa_energy);
              }
              // loop over unocc screening levels
              for (int i = lumo; i < levelsum; i++) {
                const double gwa_energy = gwa_energies(i) + ppm_freq;
                sigma_loc+=SumSymmetric(Mmn1xMmn2(i,i_gw), qpmin1, qpmin2, gwa_energy);
              }
              sigma_c += sigma_loc*fac;
            }
          _sigma_c(gw_level1, gw_level2) = sigma_c;
          _sigma_c(gw_level2, gw_level1) = sigma_c;
        }// GW row
      }//GW col
      }

      return;
    }

    void Sigma::CalcOffDiagElements(const TCMatrix_gwbse& Mmn, const PPM & ppm) {

      X_offdiag(Mmn);
      C_offdiag(Mmn, ppm);

      return;
    }

  }
};


#include <ripple/basics/contract.h>
#include <ripple/protocol/IOUAmount.h>
#include <boost/multiprecision/cpp_int.hpp>
#include <algorithm>
#include <numeric>
#include <iterator>
#include <stdexcept>

namespace ripple {

static std::int64_t const minMantissa = 1000000000000000ull;
static std::int64_t const maxMantissa = 9999999999999999ull;

static int const minExponent = -96;
static int const maxExponent = 80;

void
IOUAmount::normalize ()
{
    if (mantissa_ == 0)
    {
        *this = beast::zero;
        return;
    }

    bool const negative = (mantissa_ < 0);

    if (negative)
        mantissa_ = -mantissa_;

    while ((mantissa_ < minMantissa) && (exponent_ > minExponent))
    {
        mantissa_ *= 10;
        --exponent_;
    }

    while (mantissa_ > maxMantissa)
    {
        if (exponent_ >= maxExponent)
            Throw<std::overflow_error> ("IOUAmount::normalize");

        mantissa_ /= 10;
        ++exponent_;
    }

    if ((exponent_ < minExponent) || (mantissa_ < minMantissa))
    {
        *this = beast::zero;
        return;
    }

    if (exponent_ > maxExponent)
        Throw<std::overflow_error> ("value overflow");

    if (negative)
        mantissa_ = -mantissa_;
}

IOUAmount&
IOUAmount::operator+= (IOUAmount const& other)
{
    if (other == beast::zero)
        return *this;

    if (*this == beast::zero)
    {
        *this = other;
        return *this;
    }

    auto m = other.mantissa_;
    auto e = other.exponent_;

    while (exponent_ < e)
    {
        mantissa_ /= 10;
        ++exponent_;
    }

    while (e < exponent_)
    {
        m /= 10;
        ++e;
    }

    mantissa_ += m;

    if (mantissa_ >= -10 && mantissa_ <= 10)
    {
        *this = beast::zero;
        return *this;
    }

    normalize ();

    return *this;
}

bool
IOUAmount::operator<(IOUAmount const& other) const
{
    bool const lneg = mantissa_ < 0;
    bool const rneg = other.mantissa_ < 0;

    if (lneg != rneg)
        return lneg;

    if (mantissa_ == 0)
        return other.mantissa_ > 0;

    if (other.mantissa_ == 0)
        return false;

    if (exponent_ > other.exponent_)
        return lneg;
    if (exponent_ < other.exponent_)
        return !lneg;

    return mantissa_ < other.mantissa_;
}

std::string
to_string (IOUAmount const& amount)
{
    if (amount == beast::zero)
        return "0";

    int const exponent = amount.exponent ();
    auto mantissa = amount.mantissa ();

    if (((exponent != 0) && ((exponent < -25) || (exponent > -5))))
    {
        std::string ret = std::to_string (mantissa);
        ret.append (1, 'e');
        ret.append (std::to_string (exponent));
        return ret;
    }

    bool negative = false;

    if (mantissa < 0)
    {
        mantissa = -mantissa;
        negative = true;
    }

    assert (exponent + 43 > 0);

    size_t const pad_prefix = 27;
    size_t const pad_suffix = 23;

    std::string const raw_value (std::to_string (mantissa));
    std::string val;

    val.reserve (raw_value.length () + pad_prefix + pad_suffix);
    val.append (pad_prefix, '0');
    val.append (raw_value);
    val.append (pad_suffix, '0');

    size_t const offset (exponent + 43);

    auto pre_from (val.begin ());
    auto const pre_to (val.begin () + offset);

    auto const post_from (val.begin () + offset);
    auto post_to (val.end ());

    if (std::distance (pre_from, pre_to) > pad_prefix)
        pre_from += pad_prefix;

    assert (post_to >= post_from);

    pre_from = std::find_if (pre_from, pre_to,
        [](char c)
        {
            return c != '0';
        });

    if (std::distance (post_from, post_to) > pad_suffix)
        post_to -= pad_suffix;

    assert (post_to >= post_from);

    post_to = std::find_if(
        std::make_reverse_iterator (post_to),
        std::make_reverse_iterator (post_from),
        [](char c)
        {
            return c != '0';
        }).base();

    std::string ret;

    if (negative)
        ret.append (1, '-');

    if (pre_from == pre_to)
        ret.append (1, '0');
    else
        ret.append(pre_from, pre_to);

    if (post_to != post_from)
    {
        ret.append (1, '.');
        ret.append (post_from, post_to);
    }

    return ret;
}

IOUAmount
mulRatio (
    IOUAmount const& amt,
    std::uint32_t num,
    std::uint32_t den,
    bool roundUp)
{
    using namespace boost::multiprecision;

    if (!den)
        Throw<std::runtime_error> ("division by zero");

    static auto const powerTable = []
    {
        std::vector<uint128_t> result;
        result.reserve (30);
        uint128_t cur (1);
        for (int i = 0; i < 30; ++i)
        {
            result.push_back (cur);
            cur *= 10;
        };
        return result;
    }();

    static auto log10Floor = [](uint128_t const& v)
    {
        auto const l = std::lower_bound (powerTable.begin (), powerTable.end (), v);
        int index = std::distance (powerTable.begin (), l);
        if (*l != v)
            --index;
        return index;
    };

    static auto log10Ceil = [](uint128_t const& v)
    {
        auto const l = std::lower_bound (powerTable.begin (), powerTable.end (), v);
        return int(std::distance (powerTable.begin (), l));
    };

    static auto const fl64 =
        log10Floor (std::numeric_limits<std::int64_t>::max ());

    bool const neg = amt.mantissa () < 0;
    uint128_t const den128 (den);
    uint128_t const mul =
        uint128_t (neg ? -amt.mantissa () : amt.mantissa ()) * uint128_t (num);

    auto low = mul / den128;
    uint128_t rem (mul - low * den128);

    int exponent = amt.exponent ();

    if (rem)
    {
        auto const roomToGrow = fl64 - log10Ceil (low);
        if (roomToGrow > 0)
        {
            exponent -= roomToGrow;
            low *= powerTable[roomToGrow];
            rem *= powerTable[roomToGrow];
        }
        auto const addRem = rem / den128;
        low += addRem;
        rem = rem - addRem * den128;
    }

    bool hasRem = bool(rem);
    auto const mustShrink = log10Ceil (low) - fl64;
    if (mustShrink > 0)
    {
        uint128_t const sav (low);
        exponent += mustShrink;
        low /= powerTable[mustShrink];
        if (!hasRem)
            hasRem = bool(sav - low * powerTable[mustShrink]);
    }

    std::int64_t mantissa = low.convert_to<std::int64_t> ();

    if (neg)
        mantissa *= -1;

    IOUAmount result (mantissa, exponent);

    if (hasRem)
    {
        if (roundUp && !neg)
        {
            if (!result)
            {
                return IOUAmount (minMantissa, minExponent);
            }
            return IOUAmount (result.mantissa () + 1, result.exponent ());
        }

        if (!roundUp && neg)
        {
            if (!result)
            {
                return IOUAmount (-minMantissa, minExponent);
            }
            return IOUAmount (result.mantissa () - 1, result.exponent ());
        }
    }

    return result;
}

}


// Copyright 2020 Makani Technologies LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "sim/math/ode_solver_odeint.h"

#include <glog/logging.h>
#include <boost/numeric/odeint.hpp>
#include <boost/ref.hpp>

#include <algorithm>
#include <exception>
#include <vector>

#include "sim/math/ode_solver.h"

namespace odeint = boost::numeric::odeint;

namespace sim {

OdeSolverStatus OdeIntOdeSolver::Integrate(double t0, double tf,
                                           const std::vector<double> &x0,
                                           double *t_int,
                                           std::vector<double> *x) {
  std::copy(x0.begin(), x0.end(), x->begin());
  try {
    odeint::runge_kutta_cash_karp54<std::vector<double>> stepper;
    odeint::integrate_adaptive(
        odeint::make_controlled(params_.abs_tolerance, params_.rel_tolerance,
                                stepper),
        boost::ref(*this), *x, t0, tf, params_.initial_time_step);
    if (t_int != nullptr) {
      *t_int = tf;
    }
  } catch (std::exception &ex) {
    LOG(ERROR) << "odeint reported an error: " << ex.what();
    return OdeSolverStatus::kError;
  }
  return OdeSolverStatus::kSuccess;
}

void OdeIntOdeSolver::operator()(
    const std::vector<double> &x,
    std::vector<double> &dx,  // NOLINT(runtime/references)
    double t) {
  ode_system_.CalcDerivatives(t, x, &dx);
}

}  // namespace sim


#pragma once

#include <Eigen/Core>
#include <Eigen/Sparse>
#include <iostream>
#include <utility>
#include <vector>

#include "aux/filtered_range.hpp"
#include "enum/enum.hpp"
#include "spectral/basis/toolbox/spectral_basis.hpp"

namespace boltzmann {

// ----------------------------------------------------------------------
template <typename SPECTRAL_BASIS>
class RotateBasis
{
 private:
  typedef SPECTRAL_BASIS basis_t;
  typedef typename basis_t::index_t index_t;

 public:
  RotateBasis(const SPECTRAL_BASIS& basis)
      : basis_(basis)
  {
    this->init();
  }

  void init();

  /**
   * @brief rotate in counter clockwise direction
   *
   * @param out
   * @param in
   * @param phi angle
   * @param L number of repetitions
   */
  template <typename NUMERIC>
  void apply(NUMERIC* out, const NUMERIC* in, const double phi, const int L = 1) const;

 private:
  const basis_t& basis_;
  Eigen::SparseMatrix<double> R_;

  // internal variable
  mutable double theta_;

  typedef int a_freq;  // angular frequency
  std::unordered_map<a_freq, std::vector<std::pair<index_t, index_t> > > v_pairs_;

  // those elements who do not depend on the angular index `l` need just to be copied
  std::vector<index_t> v_copy_;

  int l_max;
};

// ----------------------------------------------------------------------
template <typename SPECTRAL_BASIS>
void
RotateBasis<SPECTRAL_BASIS>::init()
{
  int N = basis_.n_dofs();
  R_.resize(N, N);
  l_max = spectral::get_max_l(basis_);

  typedef typename basis_t::elem_t elem_t;
  typedef typename basis_t::index_t index_t;

  typedef typename boost::mpl::at_c<typename elem_t::types_t, 1>::type radial_elem_t;
  typedef typename boost::mpl::at_c<typename elem_t::types_t, 0>::type ang_elem_t;

  typename elem_t::Acc::template get<radial_elem_t> get_rad;
  typename elem_t::Acc::template get<ang_elem_t> get_ang;

  {
    // im cos(0*phi) elements do not change, append their
    // indices to the v_copy_ array.
    std::function<bool(const elem_t&)> pred = [&](const elem_t& e) {
      return (get_ang(e).get_id().l == 0);
    };
    auto range = filtered_range(basis_.begin(), basis_.end(), pred);

    auto begin = std::get<0>(range);
    auto end = std::get<1>(range);
    for (auto it = begin; it != end; ++it) {
      v_copy_.push_back(basis_.get_dof_index(it->get_id()));
    }
  }

  for (int l = 1; l <= l_max; ++l) {
    std::function<bool(const elem_t&)> pred = [&](const elem_t& e) {
      return (get_ang(e).get_id().l == l);
    };
    auto range = filtered_range(basis_.begin(), basis_.end(), pred);
    auto begin = std::get<0>(range);
    auto end = std::get<1>(range);

    for (auto it = begin; it != end; ++it) {
      auto ang_elem = get_ang(*it);
      auto rad_elem = get_rad(*it);

      typedef std::vector<std::pair<index_t, index_t> > vec_t;

      if (ang_elem.get_id().t == SIN) {
        index_t i_sin = basis_.get_dof_index(it->get_id());
        ang_elem_t cos_elem(COS, l);
        elem_t elem2(cos_elem, rad_elem);
        index_t i_cos = basis_.get_dof_index(elem2.get_id());

        auto search = v_pairs_.find(l);
        if (search == v_pairs_.end()) {
          v_pairs_[l] = vec_t({std::make_pair(i_cos, i_sin)});
        } else {
          v_pairs_[l].push_back(std::make_pair(i_cos, i_sin));
        }
      }
    }
  }
}

// ----------------------------------------------------------------------
template <typename SPECTRAL_BASIS>
template <typename NUMERIC>
void
RotateBasis<SPECTRAL_BASIS>::apply(NUMERIC* out,
                                   const NUMERIC* in,
                                   const double phi,
                                   const int L) const
{
  typedef NUMERIC numeric_t;
  const unsigned int N = basis_.n_dofs();

  Eigen::VectorXd vsin(l_max + 1);
  Eigen::VectorXd vcos(l_max + 1);

  for (int i = 0; i <= l_max; ++i) {
    vsin[i] = std::sin(-i * phi);
    vcos[i] = std::cos(i * phi);
  }

  //#pragma omp parallel for
  for (int ix = 0; ix < L; ++ix) {
    numeric_t* p_out = out + ix * N;
    const numeric_t* p_in = in + ix * N;

    // walk through v_copy_
    for (auto& i : v_copy_) {
      p_out[i] = p_in[i];
    }

    // apply the rotation
    for (auto it = v_pairs_.begin(); it != v_pairs_.end(); ++it) {
      int l = it->first;

      const double rcos = vcos[l];
      const double rsin = vsin[l];

      for (auto it_v = it->second.begin(); it_v != it->second.end(); ++it_v) {
        index_t i_cos = it_v->first;
        index_t i_sin = it_v->second;

        p_out[i_sin] = p_in[i_sin] * rcos - p_in[i_cos] * rsin;
        p_out[i_cos] = p_in[i_sin] * rsin + p_in[i_cos] * rcos;
      }
    }
  }
}

}  // end namespace