/* pcmsolver_copyright_start */
/*
 *     PCMSolver, an API for the Polarizable Continuum Model
 *     Copyright (C) 2013-2016 Roberto Di Remigio, Luca Frediani and contributors
 *
 *     This file is part of PCMSolver.
 *
 *     PCMSolver is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     PCMSolver is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
 *
 *     For information on the complete list of contributors to the
 *     PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
 */
/* pcmsolver_copyright_end */

#include "catch.hpp"

#include <cmath>
#include <iostream>

#include <Eigen/Core>

#include "bi_operators/CollocationIntegrator.hpp"
#include "AnalyticEvaluate.hpp"
#include "green/AnisotropicLiquid.hpp"
#include "green/DerivativeTypes.hpp"

SCENARIO("Evaluation of the anisotropic liquid Green's function and its derivatives", "[green][green_anisotropic_liquid]")
{
    GIVEN("A liquid with an anisotropic permittivity tensor")
    {
        Eigen::Vector3d epsilon = (Eigen::Vector3d() << 2.0, 80.0, 15.0).finished();
        Eigen::Vector3d euler   = (Eigen::Vector3d() << 6.0, 40.0, 15.0).finished();
        Eigen::Vector3d source = Eigen::Vector3d::Random();
        Eigen::Vector3d sourceNormal = source + Eigen::Vector3d::Random();
        sourceNormal.normalize();
        Eigen::Vector3d probe = Eigen::Vector3d::Random();
        Eigen::Vector3d probeNormal = probe + Eigen::Vector3d::Random();
        probeNormal.normalize();
        Eigen::Array4d result = analyticAnisotropicLiquid(epsilon, euler, sourceNormal, source, probeNormal, probe);

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidTest_numerical tests the numerical evaluation of the AnisotropicLiquid Green's function against analytical result
         */
        WHEN("the derivatives are evaluated numerically")
        {
            AnisotropicLiquid<Numerical, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
        }

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidTest_directional_AD tests the automatic evaluation (directional derivative only)
         *  of the AnisotropicLiquid Green's function against analytical result
         */
        WHEN("the derivatives are evaluated via AD")
        {
            AnisotropicLiquid<> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
        }

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidTest_gradient_AD tests the automatic evaluation (full gradient)
         *  of the AnisotropicLiquid Green's function against analytical result
         */
        WHEN("the derivatives are evaluated via AD using the full gradient")
        {
            AnisotropicLiquid<AD_gradient, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
        }

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidTest_hessian_AD tests the automatic evaluation (full hessian)
         *  of the AnisotropicLiquid Green's function against analytical result
         */
        WHEN("the derivatives are evaluated via AD using the full hessian")
        {
            AnisotropicLiquid<AD_hessian, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
            /*
               AND_THEN("the value of the Green's function hessian is")
               {
               double hessian = result(4);
               double gf_hessian = gf.hessian(sourceNormal, source, probeNormal, probe);
               REQUIRE(hessian == Approx(gf_hessian));
               }
               */
        }
    }

    // Define a uniform dielectric as an anisotropic dielectric and test the evaluation of the
    // Green's function and derivatives against the analytic result for the uniform dielectric
    GIVEN("A liquid with an isotropic permittivity tensor")
    {
        Eigen::Vector3d epsilon = (Eigen::Vector3d() << 80.0, 80.0, 80.0).finished();
        Eigen::Vector3d euler   = (Eigen::Vector3d() << 0.0, 0.0, 0.0).finished();
        Eigen::Vector3d source = Eigen::Vector3d::Random();
        Eigen::Vector3d sourceNormal = source + Eigen::Vector3d::Random();
        sourceNormal.normalize();
        Eigen::Vector3d probe = Eigen::Vector3d::Random();
        Eigen::Vector3d probeNormal = probe + Eigen::Vector3d::Random();
        probeNormal.normalize();
        Eigen::Array4d result = analyticUniformDielectric(epsilon(0), sourceNormal, source, probeNormal, probe);

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidUniformTest_numerical tests the numerical evaluation of the AnisotropicLiquid Green's function against analytical result for a uniform dielectric
         */
        WHEN("the derivatives are evaluated numerically")
        {
            AnisotropicLiquid<Numerical, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
        }

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidUniformTest_directional_AD tests the automatic evaluation (directional derivative only)
         *  of the AnisotropicLiquid Green's function against analytical result for a uniform dielectric
         */
        WHEN("the derivatives are evaluated via AD")
        {
            AnisotropicLiquid<AD_directional, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
        }

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidUniformTest_gradient_AD tests the automatic evaluation (full gradient)
         *  of the AnisotropicLiquid Green's function against analytical result for a uniform dielectric
         */
        WHEN("the derivatives are evaluated via AD using the full gradient")
        {
            AnisotropicLiquid<AD_gradient, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
        }

        /*! \class AnisotropicLiquid
         *  \test \b AnisotropicLiquidUniformTest_hessian_AD tests the automatic evaluation (full hessian)
         *  of the AnisotropicLiquid Green's function against analytical result for a uniform dielectric
         */
        WHEN("the derivatives are evaluated via AD using the full hessian")
        {
            AnisotropicLiquid<AD_hessian, CollocationIntegrator> gf(epsilon, euler);
            THEN("the value of the Green's function is")
            {
                double value = result(0);
                double gf_value = gf.kernelS(source, probe);
                REQUIRE(value == Approx(gf_value));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the probe point is")
            {
                double derProbe = result(1);
                double gf_derProbe = gf.derivativeProbe(probeNormal, source, probe);
                REQUIRE(derProbe == Approx(gf_derProbe));
            }
            AND_THEN("the value of the Green's function directional derivative wrt the source point is")
            {
                double derSource = result(2);
                double gf_derSource = gf.derivativeSource(sourceNormal, source, probe);
                REQUIRE(derSource == Approx(gf_derSource));
            }
            /*
               AND_THEN("the value of the Green's function hessian is")
               {
               double hessian = result(4);
               double gf_hessian = gf.hessian(sourceNormal, source, probeNormal, probe);
               REQUIRE(hessian == Approx(gf_hessian));
               }
               */
        }
    }
}


///////////////////////////////////////////////////////////////////////////////
// statistics::survival::model::meta::model_data.hpp                         //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_SURVIVAL_MODEL_META_MODEL_DATA_HPP_ER_2009
#define BOOST_STATISTICS_SURVIVAL_MODEL_META_MODEL_DATA_HPP_ER_2009
#include <boost/statistics/model/wrap/aggregate/model_data.hpp>
#include <boost/statistics/survival/data/data/event.hpp>

namespace boost{
namespace statistics{
namespace survival{
namespace model{
namespace meta{

    // See statistics/model/libs/doc/readme
    template<typename T,typename M,typename X>
    struct model_data{
        typedef boost::statistics::survival::data::event<T> y_;
        typedef boost::statistics::model::model_data_< M, X, y_ > type;
    };

}// meta
}// model
}// survival
}// statistics
}// boost

#endif


#include <cstdio>
#include <iomanip>
#include <iostream>
#include <limits>
#include <stdexcept>

#include <Eigen/Dense>

#include <spii/spii.h>
#include <spii/solver.h>

#include "sgdsolver.hpp"

namespace spii {

void SGDSolver::solve(const Function& function, SolverResults* results) const {
  // Get the dimensionality of the problem.
  size_t dimension = function.get_number_of_scalars();
  std::cout << "Dimension is " << dimension << std::endl;

  if (dimension == 0) {
    results->exit_condition = SolverResults::FUNCTION_TOLERANCE;
    return;
  }

  Eigen::VectorXd current_point, gradient;
  Eigen::VectorXd next_point, next_point_gradient;

  function.copy_user_to_global(&current_point);
  Eigen::VectorXd stepdirection(dimension);

  results->exit_condition = SolverResults::INTERNAL_ERROR;
  int iteration = 0;

  double function_value = std::numeric_limits<double>::quiet_NaN();
  double gradient_norm = std::numeric_limits<double>::quiet_NaN();

  double previous_function_value = 0;
  double gain = 1.0;

  double exponentially_weighed_average_gain = gain;

  while (iteration < 500) {
    function_value = function.evaluate(current_point, &gradient);
    gradient_norm = std::max(gradient.maxCoeff(), -gradient.minCoeff());
    stepdirection = -gradient;

    gain = previous_function_value - function_value;

    printf("[%d:] Loss is %+.17e, norm is %+.17e, gain is %+.17e\n",
      iteration, function_value, gradient_norm, gain);

    double stepsize = (1.0 / gradient_norm) * (1.0/(iteration+1));

    // Evaluate the function at the next point and see if we are making
    // progress.
    double next_function_value;
    while (stepsize > 1.0e-10) {
      next_point = current_point + (10*stepsize) * stepdirection;
      next_function_value = function.evaluate(next_point,
        &next_point_gradient);
      // In order to make progress, the next_function_value needs to be smaller
      // than the current_function_value, so the gain needs to be positive.
      gain = function_value - next_function_value;
      if ((gain <= 0) || std::isnan(gain)) {
        printf("[%d:] gain is %+.17e, reducing stepsize * norm from %+.17e\n",
          iteration, gain, stepsize * gradient_norm);
        stepsize = stepsize / 100.0;
      } else {
        printf("[%d:] gain is %+.17e reducing loss to %+.17e\n", iteration,
          gain, next_function_value);
        current_point = next_point;
        gradient = next_point_gradient;
        break;
      }
    }

    if (stepsize <= 1.0e-100) {
      exponentially_weighed_average_gain = gain + (0.001 *
        exponentially_weighed_average_gain);
    } else {
      exponentially_weighed_average_gain = gain +
        (0.5 * exponentially_weighed_average_gain);
    }

    printf("[%d:] Exponentially weighed avg gain: %+.17e\n", iteration,
      exponentially_weighed_average_gain);

    if (exponentially_weighed_average_gain < 1.0e-20) {
      printf("[%d:] Exponentially weighed avg gain too small, aborting\n", iteration);
      if (exponentially_weighed_average_gain < 0.0) {
        printf("[%d:] Can't be negative, exiting.\n", iteration);
        exit(-1);
      }
      break;
    }

    if (function_value < 1.0) {
      printf("[%d:] Loss less than 1, aborting\n", iteration);
      break;
    }
    ++iteration;
    previous_function_value = function_value;
  }

  function.copy_global_to_user(current_point);
}

} // namespace spii


#include <fstream>
#include <iostream>
#include <vector>
#include <Eigen/Dense>
#include <cassert>
#include <algorithm>
#include <cmath>
#include <iterator>
#include "sobol.h"
#include "Transformable.h"
#include "RunManagerAbstract.h"
#include "ParamTransformSeq.h"
#include "ModelRunPP.h"
#include "Stats.h"
#include "FileManager.h"

using namespace std;
using namespace Eigen;

Sobol::Sobol(Pest &_pest_scenario,
	FileManager &_file_manager, ObjectiveFunc *_obj_func_ptr,
	const ParamTransformSeq &_par_transform,
	int _n_sample, PARAM_DIST _par_dist, unsigned int _seed)
	: GsaAbstractBase(_pest_scenario, _file_manager, _obj_func_ptr, _par_transform,
		_par_dist, _seed), n_sample(_n_sample)
	{
	}

VectorXd Sobol::gen_rand_vec(long nsample, double min, double max)
{
	VectorXd v(nsample);
	long v_len = v.size();

	if (par_dist == PARAM_DIST::normal)
	{
		std::normal_distribution<> distribution((max + min) / 2.0, (max - min) / 4.0);
		for (long i = 0; i < v_len; ++i)
		{
			v[i] = distribution(rand_engine);
			while (v[i] < min || v[i] > max) v[i] = distribution(rand_engine);
		}
	}
	else
	{
		std::uniform_real_distribution<double> distribution(min, max);
		for (long i = 0; i < v_len; ++i)
		{
			v[i] = distribution(rand_engine);
		}
	}
	return v;
}

void Sobol::gen_m1_m2()
{
	long npar = adj_par_name_vec.size();
	//generate random matrices
	double par_min;
	double par_max;
	VectorXd v1;
	VectorXd v2;
	m1 = MatrixXd::Zero(n_sample, npar);
	m2 = MatrixXd::Zero(n_sample, npar);
	for (int i=0; i<npar; ++i)
	{
		string &p_name = adj_par_name_vec[i];
		par_min = min_numeric_pars[p_name];
		par_max = max_numeric_pars[p_name];
		v1 = gen_rand_vec(n_sample, par_min, par_max);
		v2 = gen_rand_vec(n_sample, par_min, par_max);
		m1.col(i) = v1;
		m2.col(i) = v2;
	}
}

MatrixXd Sobol::gen_N_matrix(const MatrixXd &m1, const MatrixXd &m2, const vector<int> &idx_vec)
{
  MatrixXd n = m2;
  for (int i : idx_vec)
  {
	n.col(i) = m1.col(i);
  }
  return n;
}

void Sobol::add_model_runs(RunManagerAbstract &run_manager, const MatrixXd &n)
{
	for (int i=0; i<n_sample; ++i)
	{
		VectorXd tmp_vec =  n.row(i);
		Parameters tmp_pars(adj_par_name_vec, tmp_vec);
		base_partran_seq_ptr->numeric2model_ip(tmp_pars);
		run_manager.add_run(tmp_pars);
	}
}

void Sobol::assemble_runs(RunManagerAbstract &run_manager)
{
	MatrixXd c;
	run_manager.reinitialize();
	gen_m1_m2();

	//calculate a0
	int n_adj_par = adj_par_name_vec.size();

	add_model_runs(run_manager, m1);
	add_model_runs(run_manager, m2);

	//cout << m1 << endl << endl;
	//cout << m2 << endl << endl;
	//calculate first order runs a1,....an
	vector<int> idx_vec;
	for (int ai=0; ai<n_adj_par; ++ai)
	{
		idx_vec.clear();

		idx_vec.push_back(ai);
		c = gen_N_matrix(m1, m2, idx_vec);
		//cout << c << endl << endl;
		add_model_runs(run_manager, c);
	}
}

vector<double> Sobol::get_obs_vec(RunManagerAbstract &run_manager, int run_set, ModelRun &model_run, const string &obs_name)
{
	ModelRun run0 = model_run;

	int run_b = run_set * n_sample;
	int run_e = run_b + n_sample;

	Parameters pars0;
	Observations obs0;
	int nrun = 0;
	vector<double> obs_vec = vector<double>(n_sample, MISSING_DATA);
	for (int run_id = run_b; run_id<run_e; ++run_id)
	{
		double obs = MISSING_DATA;
		bool success = run_manager.get_run(run_id, pars0, obs0);
		if (success)
		{
			run0.update_ctl(pars0, obs0);
			obs = obs0.get_rec(obs_name);
			if (obs == Observations::no_data) obs = MISSING_DATA;
		}
		obs_vec[nrun] = obs;
		nrun++;
	}
	return obs_vec;
}

vector<double> Sobol::get_phi_vec(RunManagerAbstract &run_manager, int run_set, ModelRun &model_run)
{
	ModelRun run0 = model_run;

	int run_b = run_set * n_sample;
	int run_e = run_b + n_sample;

	Parameters pars0;
	Observations obs0;
	int nrun = 0;
	vector<double> phi_vec = vector<double>(n_sample, MISSING_DATA);
	for(int run_id=run_b; run_id<run_e; ++run_id)
	{
		double phi = MISSING_DATA;
		bool success = run_manager.get_run(run_id, pars0, obs0);
		if (success)
		{
			run0.update_ctl(pars0, obs0);
			phi = run0.get_phi(0.0);
		}
		phi_vec[nrun] = phi;
		nrun++;
	}
	return phi_vec;
}

void Sobol::calc_sen(RunManagerAbstract &run_manager, ModelRun model_run)
{
	ofstream &fout_sbl = file_manager_ptr->open_ofile_ext("sbl");
	fout_sbl << "Sobol Sensitivity for PHI" << endl;
	calc_sen_single(run_manager, model_run, fout_sbl, string());

	vector<string> obs_names = run_manager.get_obs_name_vec();
	for (const string &iobs : obs_names)
	{
		fout_sbl << endl << endl;
		fout_sbl << "Sobol Sensitivity for observation \"" << iobs <<"\"" << endl;
		calc_sen_single(run_manager, model_run, fout_sbl, iobs);
	}

	file_manager_ptr->close_file("sbl");
}

void Sobol::calc_sen_single(RunManagerAbstract &run_manager, ModelRun model_run, ofstream &fout_sbl, const string &obs_name)
{
	vector<double> ya;
	vector<double> yb;
	if (obs_name.empty())
	{
		ya = get_phi_vec(run_manager, 0, model_run);
		yb = get_phi_vec(run_manager, 1, model_run);
	}
	else
	{
		ya = get_obs_vec(run_manager, 0, model_run, obs_name);
		yb = get_obs_vec(run_manager, 1, model_run, obs_name);
	}

	vector<double> ya_yb_prod = vec_array_prod(ya, yb, MISSING_DATA);
	vector<double> y_ab;
	y_ab.reserve(ya.size() + yb.size()); // preallocate memory
	y_ab.insert(y_ab.end(), ya.begin(), ya.end());
	y_ab.insert(y_ab.end(), yb.begin(), yb.end());

	//Compute Mean for the S_i's
	double mean_sq_si = vec_mean_missing_data(ya_yb_prod, MISSING_DATA);
	// Compute Var for S_i's
	pair<double, size_t> data = sum_of_prod_missing_data(y_ab, y_ab, MISSING_DATA);
	double var_si = data.first / (data.second - 2.0) - mean_sq_si;

	//Compute Mean for the S_ti's
	double mean_sq_sti = pow(vec_mean_missing_data(y_ab, MISSING_DATA), 2.0);
	// Compute Var for S_ti's
	double sti_u = sobol_u_missing_data(yb, yb, MISSING_DATA);
	double var_sti = sti_u - mean_sq_sti;
	fout_sbl << "E(Y) = " << sqrt(mean_sq_si) << ";  Var(Y) = " << var_si << " (for S_i calculations)" << endl;
	fout_sbl << "E(Y) = " << sqrt(mean_sq_sti) << ";  Var(Y) = " << var_sti << " (for S_ti calculations)" << endl;
	size_t npar = adj_par_name_vec.size();

	fout_sbl << "parameter_name, s_i, st_i, n_runs" << endl;
	for (size_t i=0; i<npar; ++i)
	{
		vector<double> yci;
		if (obs_name.empty())
		{
			yci = get_phi_vec(run_manager, i + 2, model_run);
		}
		else
		{
			yci = get_obs_vec(run_manager, i + 2, model_run, obs_name);
		}

		pair<double, int> sumprod_num = sum_of_prod_missing_data(ya, yci, MISSING_DATA);
		long int n_runs = sumprod_num.second;
		double sobol_uj = sumprod_num.first / (sumprod_num.second - 1.0);
		double si = (sobol_uj - mean_sq_si) / var_si;

		double sobol_umj = sobol_u_missing_data(yb, yci, MISSING_DATA);
		double sti = 1 - (sobol_umj - mean_sq_sti) / var_sti;

		fout_sbl << adj_par_name_vec[i] << ", " << si << ", " << sti << ", " << n_runs << endl;
	}
}


#include <iostream>
#include <fstream>
#include <vector>
#include <string>
#include <map>
#include <tuple>

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

#include "Day8.hpp"

void day8() {
	std::ifstream inputFile("Day8.txt");
	std::string line;
	std::getline(inputFile, line);

	std::vector<int> numbers;

	const int WIDTH = 25;
	const int HEIGHT = 6;
	const int LAYERS_COUNT = 100;

	int x = 0;
	int y = 0;
	int layer = 0;

	std::vector<std::vector<std::vector<int>>> layers(LAYERS_COUNT);
	layers[0] = std::vector<std::vector<int>>(HEIGHT);
	layers[0][0] = std::vector<int>(WIDTH);

	for (int i = 0; i < line.size(); ++i) {
		int currentNumber = std::stoi(line.substr(i, 1));
		layers[layer][y][x] = currentNumber;
		x++;
		if (i == 14999) {
			break;
		}
		if (x % WIDTH == 0) {
			x = 0;
			y++;
			if (y % HEIGHT == 0) {
				y = 0;
				layer++;
				layers[layer] = std::vector<std::vector<int>>(HEIGHT);
			}
			layers[layer][y] = std::vector<int>(WIDTH);
		}
	}

	std::map<int, int> count0;
	std::map<int, int> count1;
	std::map<int, int> count2;

	int layerWithFewer0 = -1;
	int minNumber0 = WIDTH * HEIGHT;

	int i = 0;
	int lol = 0;
	for (auto itLayer = layers.begin(); itLayer != layers.end(); ++itLayer) {
		count0[i] = 0;
		count1[i] = 0;
		count2[i] = 0;

		for (auto itHeight = itLayer->begin(); itHeight != itLayer->end(); ++itHeight) {
			for (auto itWidth = itHeight->begin(); itWidth != itHeight->end(); ++itWidth) {
				lol++;
				switch (*itWidth)
				{
				case 0:
					count0[i]++;
					break;
				case 1:
					count1[i]++;
					break;
				case 2:
					count2[i]++;
					break;
				}
			}
		}

		if (count0[i] < minNumber0) {
			minNumber0 = count0[i];
			layerWithFewer0 = i;
		}

		//std::cout << "Layer " << i << " 0: " << count0[i] << " 1: " << count1[i] << " 2: " << count2[i] << std::endl;

		i++;
	}

	//std::cout << "Layer with Min 0: " << layerWithFewer0 << "(" << minNumber0 << ") - Mult: " << (count1[layerWithFewer0] * count2[layerWithFewer0]) << std::endl;

	//for (auto i = numbers.begin(); i != numbers.end(); ++i)
	//	std::cout << *i << ' ';

	std::vector<std::vector<int>> finalMessage = std::vector<std::vector<int>>(HEIGHT);
	for (int y = 0; y < HEIGHT; ++y) {
		finalMessage[y] = std::vector<int>(WIDTH);
	}

	std::cout << std::endl << std::endl;
	for (int y = 0; y < HEIGHT; ++y) {
		for (int x = 0; x < WIDTH; ++x) {
			int layer = 0;
			while (layers[layer][y][x] == 2) {
				layer++;
			}
			int value = layers[layer][y][x];
			finalMessage[y][x] = value;
			std::cout << (value == 1 ? "x" : " ");
		}
		std::cout << std::endl;
	}
}


#include <vector>
#include <memory>
#include <Eigen/Core>
#include <sequential-line-search/sequential-line-search.h>

///////////////////////////////////////////////////////////////////////////////
// C API
///////////////////////////////////////////////////////////////////////////////

void init(unsigned);
void proceedOptimization(double);
std::vector<double> getParametersFromSlider(double);
std::vector<double> getXmax();

///////////////////////////////////////////////////////////////////////////////
// unnamed namespace for sealing
///////////////////////////////////////////////////////////////////////////////

using namespace sequential_line_search;
using Eigen::MatrixXd;
using Eigen::VectorXd;

namespace
{

    std::shared_ptr<sequential_line_search::PreferenceRegressor> regressor;
    std::shared_ptr<sequential_line_search::Slider> slider;

    sequential_line_search::Data data;

    unsigned        dimension;
    Eigen::VectorXd x_max;
    double          y_max;

    void clear()
    {
        regressor = nullptr;
        slider    = nullptr;

        data.X = MatrixXd::Zero(0, 0);
        data.D.clear();
        x_max  = VectorXd::Zero(0);
        y_max  = NAN;
    }

    std::vector<double> convertToSTL(const Eigen::VectorXd& vec_x_Eigen)
    {
        std::vector<double> vec_x_STL(vec_x_Eigen.rows());
        for (int i = 0; i < vec_x_Eigen.rows(); ++ i)
        {
            vec_x_STL[i] = vec_x_Eigen(i);
        }
        return vec_x_STL;
    }

    void computeRegression()
    {
        regressor = std::make_shared<PreferenceRegressor>(data.X, data.D);
    }

    void updateSliderEnds()
    {
        // If this is the first time...
        if (x_max.rows() == 0)
        {
            slider = std::make_shared<Slider>(utils::generateRandomVector(dimension), utils::generateRandomVector(dimension), true);
            return;
        }

        const VectorXd x_1 = regressor->find_arg_max();
        const VectorXd x_2 = acquisition_function::FindNextPoint(*regressor);

        slider = std::make_shared<Slider>(x_1, x_2, true);
    }

    const VectorXd computeParametersFromSlider(double value)
    {
        return slider->end_0 * (1.0 - value) + slider->end_1 *  value;
    }

}

///////////////////////////////////////////////////////////////////////////////
// C API
///////////////////////////////////////////////////////////////////////////////

void init(unsigned _dimension)
{
    dimension = _dimension;
    clear();
    computeRegression();
    updateSliderEnds();
}

void proceedOptimization(double slider_position)
{
    // Add new preference data
    const VectorXd x = computeParametersFromSlider(slider_position);
    data.AddNewPoints(x, { slider->orig_0, slider->orig_1 });

    // Compute regression
    computeRegression();

    // Check the current best
    unsigned index;
    y_max = regressor->y.maxCoeff(&index);
    x_max = regressor->X.col(index);

    // Update slider ends
    updateSliderEnds();
}

std::vector<double> getParametersFromSlider(double value)
{
    return convertToSTL(slider->end_0 * (1.0 - value) + slider->end_1 * value);
}

std::vector<double> getXmax()
{
    return convertToSTL(x_max);
}

///////////////////////////////////////////////////////////////////////////////
// pybind11
///////////////////////////////////////////////////////////////////////////////

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;

PYBIND11_MODULE(pySequentialLineSearch, m) {
    m.doc() = R"pbdoc(
    Sequential Line Search Plugin by Pybind11
    -----------------------------------------

    .. currentmodule:: SequentialLineSearch

    .. autosummary::
    :toctree: _generate
    init
    proceedOptimization
    getParametersFromSlider
    getXmax
    )pbdoc";

    m.def("init", &init, R"pbdoc(
          (Re)initializes this module.
          arg0: parameter dimension (int)
          )pbdoc");

    m.def("proceedOptimization", &proceedOptimization, R"pbdoc(
          Proceeds the optimization step by feeding the slider value.
          arg0: slider position (double)
          )pbdoc");

    m.def("getParametersFromSlider", &getParametersFromSlider, R"pbdoc(
          Gets the parameters as list, in the specific slider position.
          arg0: slider position (double)
          )pbdoc");

    m.def("getXmax", &getXmax, R"pbdoc(
          Gets the current best parameters as a list.
          )pbdoc");

#ifdef VERSION_INFO
    m.attr("__version__") = VERSION_INFO;
#else
    m.attr("__version__") = "dev";
#endif
}


/*

Copyright (c) 2005-2018, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef DELTANOTCHODESYSTEM_HPP_
#define DELTANOTCHODESYSTEM_HPP_

#include "ChasteSerialization.hpp"
#include <boost/serialization/base_object.hpp>

#include <cmath>
#include <iostream>

#include "AbstractOdeSystem.hpp"

/**
 * Represents the Delta-Notch ODE system described by Collier et al,
 * "Pattern formation by lateral inhibition with feedback: a mathematical
 * model of delta-notch intercellular signalling" (Journal of Theoretical
 * Biology 183:429-446, 1996).
 */
class DeltaNotchOdeSystem : public AbstractOdeSystem
{
private:

    friend class boost::serialization::access;
    /**
     * Serialize the object and its member variables.
     *
     * @param archive the archive
     * @param version the current version of this class
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & boost::serialization::base_object<AbstractOdeSystem>(*this);
    }

    ///\todo extract model parameters as member variables

public:

    /**
     * Default constructor.
     *
     * @param stateVariables optional initial conditions for state variables (only used in archiving)
     */
    DeltaNotchOdeSystem(std::vector<double> stateVariables=std::vector<double>());

    /**
     * Destructor.
     */
    ~DeltaNotchOdeSystem();

    /**
     * Compute the RHS of the  Collier et al. system of ODEs.
     *
     * Returns a vector representing the RHS of the ODEs at each time step, y' = [y1' ... yn'].
     * An ODE solver will call this function repeatedly to solve for y = [y1 ... yn].
     *
     * @param time used to evaluate the RHS.
     * @param rY value of the solution vector used to evaluate the RHS.
     * @param rDY filled in with the resulting derivatives (using  Collier et al. system of equations).
     */
    void EvaluateYDerivatives(double time, const std::vector<double>& rY, std::vector<double>& rDY);
};

// Declare identifier for the serializer
#include "SerializationExportWrapper.hpp"
CHASTE_CLASS_EXPORT(DeltaNotchOdeSystem)

namespace boost
{
namespace serialization
{
/**
 * Serialize information required to construct a DeltaNotchOdeSystem.
 */
template<class Archive>
inline void save_construct_data(
    Archive & ar, const DeltaNotchOdeSystem * t, const unsigned int file_version)
{
    const std::vector<double>& state_variables = t->rGetConstStateVariables();
    ar & state_variables;
}

/**
 * De-serialize constructor parameters and initialise a DeltaNotchOdeSystem.
 */
template<class Archive>
inline void load_construct_data(
    Archive & ar, DeltaNotchOdeSystem * t, const unsigned int file_version)
{
    std::vector<double> state_variables;
    ar & state_variables;

    // Invoke inplace constructor to initialise instance
    ::new(t)DeltaNotchOdeSystem(state_variables);
}
}
} // namespace ...

#endif /*DELTANOTCHODESYSTEM_HPP_*/


//
//  Copyright Toon Knapen, Karl Meerbergen
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//

#include "../../blas/test/random.hpp"

#include <boost/numeric/bindings/begin.hpp>
#include <boost/numeric/bindings/value_type.hpp>
#include <boost/numeric/bindings/remove_imaginary.hpp>
#include <boost/numeric/bindings/ublas/matrix.hpp>
#include <boost/numeric/bindings/ublas/vector.hpp>
#include <boost/numeric/bindings/lapack/driver/gees.hpp>
#include <boost/numeric/bindings/detail/array.hpp>
#include <boost/numeric/bindings/vector_view.hpp>
//#include <boost/numeric/bindings/detail/complex_utils.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/type_traits/is_complex.hpp>
#include <boost/mpl/if.hpp>

#include <iostream>
#include <limits>

namespace ublas = boost::numeric::ublas;
namespace lapack = boost::numeric::bindings::lapack;
namespace bindings = boost::numeric::bindings;

struct apply_real {
  template< typename MatrixA, typename VectorW, typename MatrixVS,
        typename Workspace >
  static inline std::ptrdiff_t gees( const char jobvs, const char sort,
        external_fp select, MatrixA& a, fortran_int_t& sdim, VectorW& w,
        MatrixVS& vs, Workspace work ) {
    return lapack::gees( jobvs, sort, select, a, sdim, w, vs, work );
    /*
    fortran_int_t info = lapack::gees( jobvs, sort, select, a, sdim,
      bindings::detail::real_part_view(w), bindings::detail::imag_part_view(w),
      vs, work );
    bindings::detail::interlace(w);
    return info;
    */
  }
};

struct apply_complex {
  template< typename MatrixA, typename VectorW, typename MatrixVS,
        typename Workspace >
  static inline std::ptrdiff_t gees( const char jobvs, const char sort,
        external_fp select, MatrixA& a, fortran_int_t& sdim, VectorW& w,
        MatrixVS& vs, Workspace work ) {
    return lapack::gees( jobvs, sort, select, a, sdim, w, vs, work );
  }
};

// Randomize a matrix
template <typename M>
void randomize(M& m) {
   typedef typename M::size_type  size_type ;
   typedef typename M::value_type value_type ;

   size_type size1 = m.size1() ;
   size_type size2 = m.size2() ;

   for (size_type i=0; i<size2; ++i) {
      for (size_type j=0; j<size1; ++j) {
         m(j,i) = random_value< value_type >() ;
      }
   }
} // randomize()

template< typename T >
struct dispatch_select
{
};
template<>
struct dispatch_select<float>
{
  static fortran_bool_t my_select(float* w_real, float* w_imag) {
    return *w_real > std::abs(*w_imag);
  }
};
template<>
struct dispatch_select<double>
{
  static fortran_bool_t my_select(double* w_real, double* w_imag) {
    return *w_real > std::abs(*w_imag);
  }
};
template<>
struct dispatch_select<std::complex<float> >
{
  static fortran_bool_t my_select(std::complex<float>* w) {
    return w->real() > std::abs(w->imag());
  }
};
template<>
struct dispatch_select<std::complex<double> >
{
  static fortran_bool_t my_select(std::complex<double>* w) {
    return w->real() > std::abs(w->imag());
  }
};

template <typename T, typename W>
int do_memory_type(int n, W workspace) {
   typedef typename boost::mpl::if_<boost::is_complex<T>, apply_complex, apply_real>::type apply_t;
   typedef typename bindings::remove_imaginary< T>::type real_type ;
   typedef std::complex< real_type >                                            complex_type ;

   typedef ublas::matrix<T, ublas::column_major> matrix_type ;
   typedef ublas::vector<complex_type>           vector_type ;
   double safety_factor (1.5);

   // Set matrix
   matrix_type a( n, n );
   matrix_type z( n, n );
   vector_type e1( n );
   vector_type e2( n );

   randomize( a );
   matrix_type a2( a );
   external_fp select = reinterpret_cast<external_fp>(dispatch_select<T>::my_select);
   fortran_int_t sdim_info(0);
   // Compute Schur decomposition.
   apply_t::gees( 'V', 'S', select, a, sdim_info, e1, z, workspace ) ;

   // Check Schur factorization
   if (norm_frobenius( prod( a2, z ) - prod( z, a ) )
           >= safety_factor*10.0* norm_frobenius( a2 ) * std::numeric_limits< real_type >::epsilon() ) return 255 ;

   matrix_type z_dummy( 1, 1 );
   apply_t::gees( 'N', 'S', select, a2, sdim_info, e2, z_dummy, workspace ) ;
   if (norm_2( e1 - e2 ) > safety_factor*norm_2( e1 ) * std::numeric_limits< real_type >::epsilon()) return 255 ;

   if (norm_frobenius( a2 - a )
           >= safety_factor*10.0* norm_frobenius( a2 ) * std::numeric_limits< real_type >::epsilon() ) return 255 ;

   return 0 ;
} // do_value_type()

template <typename T>
struct Workspace {
   typedef ublas::vector<T>                         array_type ;
   typedef ublas::vector< fortran_bool_t >               bool_array_type ;
   typedef lapack::detail::workspace2< array_type,bool_array_type > type ;

   Workspace(size_t n)
   : work_( 3*n )
   , bwork_( n )
   {}

   type operator() () {
      return lapack::workspace(work_, bwork_) ;
   }

   array_type work_ ;
   bool_array_type bwork_;
};

template <typename T>
struct Workspace< std::complex<T> > {
   typedef ublas::vector<T>                                                 real_array_type ;
   typedef ublas::vector< std::complex<T> >                                 complex_array_type ;
   typedef ublas::vector< fortran_bool_t >                                       bool_array_type ;
   typedef lapack::detail::workspace3< complex_array_type,real_array_type,bool_array_type > type ;

   Workspace(size_t n)
   : work_( 2*n )
   , rwork_( n )
   , bwork_( n )
   {}

   type operator() () {
      return lapack::workspace(work_, rwork_, bwork_) ;
   }

   complex_array_type work_ ;
   real_array_type    rwork_ ;
   bool_array_type    bwork_;
};

template <typename T>
int do_value_type() {
   const int n = 8 ;

   if (do_memory_type<T,lapack::optimal_workspace>( n, lapack::optimal_workspace() ) ) return 255 ;
   if (do_memory_type<T,lapack::minimal_workspace>( n, lapack::minimal_workspace() ) ) return 255 ;

   Workspace<T> work( n );
   if (do_memory_type<T,typename Workspace<T>::type >( n, work() ) ) return 255 ;
   return 0;
} // do_value_type()

int main() {
   // Run tests for different value_types
   std::cout << "float\n" ;
   if (do_value_type<float>()) return 255;

   std::cout << "double\n" ;
   if (do_value_type<double>()) return 255;

   std::cout << "complex<float>\n" ;
   if (do_value_type< std::complex<float> >()) return 255;

   std::cout << "complex<double>\n" ;
   if (do_value_type< std::complex<double> >()) return 255;

   std::cout << "Regression test succeeded\n" ;
   return 0;
}


#ifndef UTIL_HPP
#define UTIL_HPP

#include <fmt/color.h>
#include <fmt/format.h>

#include <algorithm>
#include <charconv>
#include <cmath>
#include <execution>
#include <fstream>
#include <iostream>
#include <numeric>
#include <optional>
#include <ostream>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include <Eigen/Eigen>

template <typename T>
static std::optional<T> parse_number(const std::string& s)
{
    static_assert(std::is_arithmetic_v<T>);
    T result;
    if (auto [p, ec] = std::from_chars(s.data(), s.data() + s.size(), result); ec == std::errc()) {
        return { result };
    }
    return {};
}

static std::vector<std::string> split(const std::string& s, char delimiter)
{
    std::vector<std::string> tokens;
    std::string token;
    std::istringstream tokenStream(s);
    while (std::getline(tokenStream, token, delimiter)) {
        tokens.push_back(token);
    }
    return tokens;
}

template <typename T>
static std::vector<T> to_vec(const std::string& s, char delimiter)
{
    auto tokens = split(s, delimiter);
    std::vector<T> vec;
    for (const auto& t : tokens) {
        if (auto res = parse_number<T>(t); !res.has_value()) {
            throw new std::runtime_error(fmt::format("Error: cannot parse '{}' as an {}\n", t, typeid(T).name()));
        } else {
            vec.push_back(res.value());
        }
    }
    return vec;
}

#endif


/*
 * MIT License
 *
 * Copyright (c) 2020 Robert Grupp
 *
 * 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 "xregOpenCLMath.h"

#include <boost/compute/utility/source.hpp>

const char* xreg::kOPENCL_MATH_UTILS_SRC = BOOST_COMPUTE_STRINGIZE_SOURCE(

float3 xregFloat4HmgToFloat3(const float4 a)
{
  return (float3) (a.x, a.y, a.z);
}

float xregFloat1Norm(const float x)
{
  return fabs(x);
}

float xregFloat2Norm(const float2 x)
{
  return sqrt((x.x * x.x) + (x.y * x.y));
}

float xregFloat3Norm(const float3 x)
{
  return sqrt((x.x * x.x) + (x.y * x.y) + (x.z * x.z));
}

float xregFloat4HmgNorm(const float4 x)
{
  return sqrt((x.x * x.x) + (x.y * x.y) + (x.z * x.z));
}

float xregFloat4Norm(const float4 x)
{
  return sqrt((x.x * x.x) + (x.y * x.y) + (x.z * x.z)  + (x.w * x.w));
}

float xregFloat8Norm(const float8 x)
{
  return sqrt((x.s0 * x.s0) + (x.s1 * x.s1) + (x.s2 * x.s2)  + (x.s3 * x.s3) +
              (x.s4 * x.s4) + (x.s5 * x.s5) + (x.s6 * x.s6)  + (x.s7 * x.s7));
}

float xregFloat16Norm(const float16 x)
{
  return sqrt((x.s0 * x.s0) + (x.s1 * x.s1) + (x.s2 * x.s2)  + (x.s3 * x.s3) +
              (x.s4 * x.s4) + (x.s5 * x.s5) + (x.s6 * x.s6)  + (x.s7 * x.s7) +
              (x.s8 * x.s8) + (x.s9 * x.s9) + (x.sa * x.sa)  + (x.sb * x.sb) +
              (x.sc * x.sc) + (x.sd * x.sd) + (x.se * x.se)  + (x.sf * x.sf));
}

float2 xregFloat2Normalize(const float2 x)
{
  return x / xregFloat2Norm(x);
}

float3 xregFloat3Normalize(const float3 x)
{
  return x / xregFloat3Norm(x);
}

float4 xregFloat4Normalize(const float4 x)
{
  return x / xregFloat4Norm(x);
}

float8 xregFloat8Normalize(const float8 x)
{
  return x / xregFloat8Norm(x);
}

float16 xregFloat16Normalize(const float16 x)
{
  return x / xregFloat16Norm(x);
}

float xregFloat1NormSq(const float x)
{
  return x * x;
}

float xregFloat2NormSq(const float2 x)
{
  return (x.x * x.x) + (x.y * x.y);
}

float xregFloat3NormSq(const float3 x)
{
  return (x.x * x.x) + (x.y * x.y) + (x.z * x.z);
}

float xregFloat4HmgNormSq(const float4 x)
{
  return (x.x * x.x) + (x.y * x.y) + (x.z * x.z);
}

float xregFloat4NormSq(const float4 x)
{
  return (x.x * x.x) + (x.y * x.y) + (x.z * x.z)  + (x.w * x.w);
}

float xregFloat8NormSq(const float8 x)
{
  return (x.s0 * x.s0) + (x.s1 * x.s1) + (x.s2 * x.s2)  + (x.s3 * x.s3) +
         (x.s4 * x.s4) + (x.s5 * x.s5) + (x.s6 * x.s6)  + (x.s7 * x.s7);
}

float xregFloat16NormSq(const float16 x)
{
  return (x.s0 * x.s0) + (x.s1 * x.s1) + (x.s2 * x.s2)  + (x.s3 * x.s3) +
         (x.s4 * x.s4) + (x.s5 * x.s5) + (x.s6 * x.s6)  + (x.s7 * x.s7) +
         (x.s8 * x.s8) + (x.s9 * x.s9) + (x.sa * x.sa)  + (x.sb * x.sb) +
         (x.sc * x.sc) + (x.sd * x.sd) + (x.se * x.se)  + (x.sf * x.sf);
}

float3 xregFrm4x4XformFloat3Pt(const float16 frm, const float3 a)
{
  float3 b;

  b.x = (frm.s0 * a.x) + (frm.s1 * a.y) + (frm.s2 * a.z) + frm.s3;
  b.y = (frm.s4 * a.x) + (frm.s5 * a.y) + (frm.s6 * a.z) + frm.s7;
  b.z = (frm.s8 * a.x) + (frm.s9 * a.y) + (frm.sa * a.z) + frm.sb;

  return b;
}

float3 xregFrm4x4XformFloat3Vec(const float16 frm, const float3 a)
{
  float3 b;

  b.x = (frm.s0 * a.x) + (frm.s1 * a.y) + (frm.s2 * a.z);
  b.y = (frm.s4 * a.x) + (frm.s5 * a.y) + (frm.s6 * a.z);
  b.z = (frm.s8 * a.x) + (frm.s9 * a.y) + (frm.sa * a.z);

  return b;
}

float4 xregFrm4x4XformFloat4Pt(const float16 frm, const float4 a)
{
  float4 b;

  b.x = (frm.s0 * a.x) + (frm.s1 * a.y) + (frm.s2 * a.z) + frm.s3;
  b.y = (frm.s4 * a.x) + (frm.s5 * a.y) + (frm.s6 * a.z) + frm.s7;
  b.z = (frm.s8 * a.x) + (frm.s9 * a.y) + (frm.sa * a.z) + frm.sb;
  b.w = 1;

  return b;
}

float4 xregFrm4x4XformFloat4Vec(const float16 frm, const float4 a)
{
  float4 b;

  b.x = (frm.s0 * a.x) + (frm.s1 * a.y) + (frm.s2 * a.z);
  b.y = (frm.s4 * a.x) + (frm.s5 * a.y) + (frm.s6 * a.z);
  b.z = (frm.s8 * a.x) + (frm.s9 * a.y) + (frm.sa * a.z);
  b.w = 0;

  return b;
}

float16 xregFrm4x4Composition(const float16 f, const float16 g)
{
  float16 h;

  h.s0 = (f.s0 * g.s0) + (f.s1 * g.s4) + (f.s2 * g.s8);
  h.s1 = (f.s0 * g.s1) + (f.s1 * g.s5) + (f.s2 * g.s9);
  h.s2 = (f.s0 * g.s2) + (f.s1 * g.s6) + (f.s2 * g.sa);
  h.s3 = f.s3 + (f.s0 * g.s3) + (f.s1 * g.s7) + (f.s2 * g.sb);
  h.s4 = (f.s4 * g.s0) + (f.s5 * g.s4) + (f.s6 * g.s8);
  h.s5 = (f.s4 * g.s1) + (f.s5 * g.s5) + (f.s6 * g.s9);
  h.s6 = (f.s4 * g.s2) + (f.s5 * g.s6) + (f.s6 * g.sa);
  h.s7 = f.s7 + (f.s4 * g.s3) + (f.s5 * g.s7) + (f.s6 * g.sb);
  h.s8 = (f.s8 * g.s0) + (f.s9 * g.s4) + (f.sa * g.s8);
  h.s9 = (f.s8 * g.s1) + (f.s9 * g.s5) + (f.sa * g.s9);
  h.sa = (f.s8 * g.s2) + (f.s9 * g.s6) + (f.sa * g.sa);
  h.sb = f.sb + (f.s8 * g.s3) + (f.s9 * g.s7) + (f.sa * g.sb);
  h.sc = 0;
  h.sd = 0;
  h.se = 0;
  h.sf = 1;

  return h;
}

float16 xregFrm4x4SE3Inv(const float16 h)
{
  float16 h_inv;

  // transpose for rotation inverse: R^-1 = R^T

  // row 1
  h_inv.s0 = h.s0;
  h_inv.s1 = h.s4;
  h_inv.s2 = h.s8;

  // row 2
  h_inv.s4 = h.s1;
  h_inv.s5 = h.s5;
  h_inv.s6 = h.s9;

  // row 3
  h_inv.s8 = h.s2;
  h_inv.s9 = h.s6;
  h_inv.sa = h.sa;

  // translation inverse: t^-1 = -R^T * t
  h_inv.s3 = -((h_inv.s0 * h.s3) + (h_inv.s1 * h.s7) + (h_inv.s2 * h.sb));
  h_inv.s7 = -((h_inv.s4 * h.s3) + (h_inv.s5 * h.s7) + (h_inv.s6 * h.sb));
  h_inv.sb = -((h_inv.s8 * h.s3) + (h_inv.s9 * h.s7) + (h_inv.sa * h.sb));

  h_inv.sc = 0;
  h_inv.sd = 0;
  h_inv.se = 0;
  h_inv.sf = 1;

  return h_inv;
}

float2 xregFrm3x3XformFloat2Pt(const float16 frm, const float2 a)
{
  float2 b;

  b.x = (frm.s0 * a.x) + (frm.s1 * a.y) + frm.s2;
  b.y = (frm.s3 * a.x) + (frm.s4 * a.y) + frm.s5;

  return b;
}

float2 xregFrm3x3XformFloat2Vec(const float16 frm, const float2 a)
{
  float2 b;

  b.x = (frm.s0 * a.x) + (frm.s1 * a.y);
  b.y = (frm.s3 * a.x) + (frm.s4 * a.y);

  return b;
}

float16 xregFrm3x3Identity()
{
  float16 A;

  A.s0 = 1;
  A.s1 = 0;
  A.s2 = 0;

  A.s3 = 0;
  A.s4 = 1;
  A.s5 = 0;

  A.s6 = 0;
  A.s7 = 0;
  A.s8 = 1;

  return A;
}

float16 xregFrm4x4Identity()
{
  float16 A;

  A.s0 = 1;
  A.s1 = 0;
  A.s2 = 0;
  A.s3 = 0;

  A.s4 = 0;
  A.s5 = 1;
  A.s6 = 0;
  A.s7 = 0;

  A.s8 = 0;
  A.s9 = 0;
  A.sa = 1;
  A.sb = 0;

  A.sc = 0;
  A.sd = 0;
  A.se = 0;
  A.sf = 1;

  return A;
}

float xregFloat2Inner(const float2 x, const float2 y)
{
  return (x.x * y.x) + (x.y * y.y);
}

float xregFloat3Inner(const float3 x, const float3 y)
{
  return (x.x * y.x) + (x.y * y.y) + (x.z * y.z);
}

float xregFloat4HmgInner(const float4 x, const float4 y)
{
  return (x.x * y.x) + (x.y * y.y) + (x.z * y.z);
}

float xregFloat4Inner(const float4 x, const float4 y)
{
  return (x.x * y.x) + (x.y * y.y) + (x.z * y.z) + (x.w * y.w);
}

float xregFloat8Inner(const float8 x, const float8 y)
{
  return (x.s0 * y.s0) + (x.s1 * y.s1) + (x.s2 * y.s2) + (x.s3 * y.s3) +
         (x.s4 * y.s4) + (x.s5 * y.s5) + (x.s6 * y.s6) + (x.s7 * y.s7);
}

float xregFloat16Inner(const float16 x, const float16 y)
{
  return (x.s0 * y.s0) + (x.s1 * y.s1) + (x.s2 * y.s2) + (x.s3 * y.s3) +
         (x.s4 * y.s4) + (x.s5 * y.s5) + (x.s6 * y.s6) + (x.s7 * y.s7) +
         (x.s8 * y.s8) + (x.s9 * y.s9) + (x.sa * y.sa) + (x.sb * y.sb) +
         (x.sc * y.sc) + (x.sd * y.sd) + (x.se * y.se) + (x.sf * y.sf);
}

// Useful for debugging:

void xregPrintMat4x4(const float16 x)
{
  printf("[ %+9.3f , %+9.3f , %+9.3f , %+9.3f ;\n"
         "  %+9.3f , %+9.3f , %+9.3f , %+9.3f ;\n"
         "  %+9.3f , %+9.3f , %+9.3f , %+9.3f ;\n"
         "  %+9.3f , %+9.3f , %+9.3f , %+9.3f ]\n",
         x.s0, x.s1, x.s2, x.s3,
         x.s4, x.s5, x.s6, x.s7,
         x.s8, x.s9, x.sa, x.sb,
         x.sc, x.sd, x.se, x.sf);
}

);


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_TOOLBOX_ARITHMETIC_FUNCTIONS_SCALAR_MAX_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_ARITHMETIC_FUNCTIONS_SCALAR_MAX_HPP_INCLUDED
#include <boost/simd/toolbox/arithmetic/functions/max.hpp>
#include <boost/simd/include/functions/scalar/is_nan.hpp>
#include <boost/simd/include/constants/nan.hpp>
#include <boost/mpl/max.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_SIMD_FUNCTOR_IMPLEMENTATION ( boost::simd::tag::max_, tag::cpu_
                                    , (A0)
                                    , (scalar_< arithmetic_<A0> >)
                                      (scalar_< arithmetic_<A0> >)
                                    )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      return (a0 > a1) ? a0 : a1;
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION ( boost::simd::tag::max_, tag::cpu_
                                    , (A0)(A1)
                                    , (mpl_integral_< scalar_< fundamental_<A0> > >)
                                      (scalar_< arithmetic_<A1> >)
                                    )
  {
    typedef A1 result_type;
    BOOST_FORCEINLINE result_type operator()(A0 const&, A1 a1) const
    {
      return (A0::value > a1) ? A0::value : a1;
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION ( boost::simd::tag::max_, tag::cpu_
                                    , (A0)(A1)
                                    , (scalar_< arithmetic_<A0> >)
                                      (mpl_integral_< scalar_< fundamental_<A1> > >)
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE result_type operator()(A0 a0, A1 const&) const
    {
      return (a0 > A1::value) ? a0 : A1::value;
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION ( boost::simd::tag::max_, tag::cpu_
                                    , (A0)(A1)
                                    , (mpl_integral_< scalar_< fundamental_<A0> > >)
                                      (mpl_integral_< scalar_< fundamental_<A1> > >)
                                    )
  {
    typedef typename  boost::mpl::max<A0,A1>::type result_type;

    BOOST_FORCEINLINE result_type operator()(A0 const&, A1 const&) const
    {
      return result_type();
    }
  };

  BOOST_SIMD_FUNCTOR_IMPLEMENTATION ( boost::simd::tag::max_, tag::cpu_
                                    , (A0)
                                    , (scalar_< floating_<A0> >)
                                      (scalar_< floating_<A0> >)
                                    )
  {
    typedef A0 result_type;
    BOOST_SIMD_FUNCTOR_CALL_REPEAT(2)
    {
      if (is_nan(a0) || is_nan(a1)) return Nan<result_type>();
      return (a0 > a1) ? a0 : a1;
    }
  };
} } }

#endif


/*

PICCANTE
The hottest HDR imaging library!
http://vcg.isti.cnr.it/piccante

Copyright (C) 2014-2016
Visual Computing Laboratory - ISTI CNR
http://vcg.isti.cnr.it
Library author: Francesco Banterle
This file author: Giorgio Marcias

This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.

*/

#ifndef PIC_ALGORITHMS_MITSUNAGA_NAYAR_CRF_HPP
#define PIC_ALGORITHMS_MITSUNAGA_NAYAR_CRF_HPP

#include<algorithm>
#include<limits>
#include<vector>

#include "../base.hpp"

#ifndef PIC_DISABLE_EIGEN

#ifndef PIC_EIGEN_NOT_BUNDLED
    #include "../externals/Eigen/LU"
#else
    #include <Eigen/LU>
#endif

#endif

namespace pic {

/**
 * @brief MitsunagaNayarClassic computes the inverse CRF of a camera as a polynomial function.
 * @param samples           Sample array of size nSamples x #exposures.
 * @param nSamples          Number of samples, for each exposure.
 * @param exposures         Array of exposure timings (size: #exposures = 'Q' as in the Mitsunaga & Nayar paper).
 * @param coefficients      The output coefficients ('c' in the paper) resulting from the computation.
 * @param computeRatios     false if exact exposures are passed, true to approximate exposure ratios as in the paper.
 * @param R                 The output estimated exposure ratios, i.e. R[q1][q2] = 'R_{q1,q2}' as in the book.
 * @param eps               Threshold for stopping the approximation process.
 * @param max_iterations    Maximum number of iterations.
 * @return The error as in the paper.
 */
PIC_INLINE float MitsunagaNayarClassic(int *samples, const std::size_t nSamples, const std::vector<float> &exposures,
                                   std::vector<float> &coefficients, const bool computeRatios, std::vector<float> &R,
                                   const float eps, const std::size_t max_iterations)
{
#ifndef PIC_DISABLE_EIGEN
    float eval, val, tmp1, tmp2;
    const std::size_t Q = exposures.size();
    const std::size_t N = coefficients.size() - 1;

    const float Mmax = 1.f;

    for (float &_c : coefficients) {
        _c = 0.f;
    }

    if (!samples || Q < 2 || coefficients.size() < 2) {
        return std::numeric_limits<float>::infinity();
    }

    R.assign(Q < 2 ? 0 : Q - 1, 1.f);
    for (int q = 0; q < R.size(); ++q) {
        R[q] = exposures[q] / exposures[q+1];
    }

    //Check valid samples
    std::vector<std::vector<float>> g(nSamples, std::vector<float>(Q, 0.f));
    std::size_t P = 0;
    for (std::size_t p = 0; p < nSamples; ++p) {
        bool valid = false;
        for (std::size_t q = 0; q < Q-1; ++q) {
            if (samples[p * Q + q] >= 0 && samples[p * Q + q+1] >= 0) {
                valid = true;
                break;
            }
        }
        if (valid) {
            for (std::size_t q = 0; q < Q; ++q) {
                if (samples[p * Q + q] >= 0) {
                    g[P][q] = samples[p * Q + q] / 255.f;
                } else {
                    g[P][q] = -1.f;
                }
            }
            ++P;
        }
    }
    g.resize(P);

    if (g.empty()) {
        return std::numeric_limits<float>::infinity();
    }

    //Precompute test with exponentials
    std::vector<Eigen::VectorXf> test;
    if (computeRatios) {
        test.assign(256, Eigen::VectorXf::Zero(N+1));
        for (std::size_t i = 0; i < 256; ++i) {
            test[i][0] = 1.f;
            for (std::size_t n = 1; n <= N; ++n) {
                test[i][n] = (i / 255.f) * test[i][n-1];
            }
        }
    }

    //Precompute M with exponentials
    std::vector<std::vector<std::vector<float>>> M(P,
                                                   std::vector<std::vector<float>>(Q,
                                                                                std::vector<float>(N+1, 0.f)));
    for (std::size_t p = 0; p < P; ++p) {
        for (std::size_t q = 0; q < Q; ++q) {
            M[p][q][0] = 1.f;
            for (std::size_t n = 1; n <= N; ++n) {
                M[p][q][n] = g[p][q] * M[p][q][n-1];
            }
        }
    }

    std::vector<std::vector<std::vector<float>>> d(P,
                                                   std::vector<std::vector<float>>(Q-1,
                                                                                std::vector<float>(N+1, 1.f)));
    Eigen::MatrixXf A = Eigen::MatrixXf::Zero(N, N);
    Eigen::VectorXf x, b = Eigen::VectorXf::Zero(N);
    Eigen::VectorXf c(N+1), prev_c = Eigen::VectorXf::Zero(N+1);

    std::size_t iter = 0;

    do {
        //Compute d
        for (std::size_t p = 0; p < P; ++p) {
            for (std::size_t q = 0; q < Q-1; ++q) {
                if (g[p][q] >= 0.f && g[p][q+1] >= 0.f) {
                    for (std::size_t n = 0; n <= N; ++n) {
                        d[p][q][n] = M[p][q][n] - R[q] * M[p][q+1][n];
                    }
                } else {
                    d[p][q].assign(N+1, 0.f);
                }
            }
        }

        //Build the matrix A of the linear system
        A.setZero(N, N);
        for (std::size_t i = 0; i < N; ++i) {
            for (std::size_t j = 0; j < N; ++j) {
                for (std::size_t p = 0; p < P; ++p) {
                    for (std::size_t q = 0; q < Q - 1; ++q) {
                        A(i, j) += d[p][q][i] * (d[p][q][j] - d[p][q][N]);
                    }
                }
            }
        }

        //Build the vector of knowns b
        b.setZero(N);
        for (std::size_t i = 0; i < N; ++i) {
            for (std::size_t p = 0; p < P; ++p) {
                for (std::size_t q = 0; q < Q - 1; ++q) {
                    b(i) -= Mmax * d[p][q][i] * d[p][q][N];
                }
            }
        }

        //Solve the linear system
        x = A.partialPivLu().solve(b);
        c << x, Mmax - x.sum();

        if (computeRatios) {
            //Evaluate approximation increment
            eval = std::numeric_limits<float>::lowest();
            for (const Eigen::VectorXf &_M : test) {
                val = std::abs((c - prev_c).dot(_M));
                if (val > eval) {
                    eval = val;
                }
            }

            //Update R
            for (std::size_t q = 0; q < Q-1; ++q) {
                R[q] = 0.f;
                tmp1 = 0.f;
                tmp2 = 0.f;
                for (std::size_t p = 0; p < P; ++p) {
                    if (g[p][q] >= 0.f && g[p][q+1] >= 0.f) {
                        for (std::size_t n = 0; n <= N; ++n) {
                            tmp1 += c[n] * M[p][q][n];
                            tmp2 += c[n] * M[p][q+1][n];
                        }
                    }
                }
                R[q] += tmp1 / tmp2;
            }

            ++iter;
        }
    } while (computeRatios && eval > eps && iter < max_iterations);

    for (std::size_t n = 0; n <= N; ++n) {
        coefficients[n] = c[n];
    }

    //Evaluate error
    eval = 0.f;
    for (std::size_t q = 0; q < Q-1; ++q) {
        for (std::size_t p = 0; p < P; ++p) {
            if (g[p][q] >= 0.f && g[p][q+1] >= 0.f) {
                val = 0.f;
                for (std::size_t n = 0; n <= N; ++n) {
                    val += coefficients[n] * (M[p][q][n] - R[q] * M[p][q+1][n]);
                }
                eval += val * val;
            }
        }
    }

    return eval;
#else
    return -1.0f;
#endif
}

/**
 * @brief MitsunagaNayarFull computes the inverse CRF of a camera as a polynomial function, using all exposure ratios.
 * @param samples           Sample array of size nSamples x #exposures.
 * @param nSamples          Number of samples, for each exposure.
 * @param exposures         Array of exposure timings (size: #exposures = 'Q' as in the Mitsunaga & Nayar paper).
 * @param coefficients      The output coefficients ('c' in the paper) resulting from the computation.
 * @param computeRatios     false if exact exposures are passed, true to approximate exposure ratios as in the paper.
 * @param R                 The output estimated exposure ratios, i.e. R[q1][q2] = 'R_{q1,q2}' as in the book.
 * @param eps               Threshold for stopping the approximation process.
 * @param max_iterations    Maximum number of iterations.
 * @return The error as in the paper.
 */
PIC_INLINE float MitsunagaNayarFull(int *samples, const std::size_t nSamples, const std::vector<float> &exposures,
                                std::vector<float> &coefficients, bool computeRatios, std::vector<std::vector<float>> &R,
                                const float eps, const std::size_t max_iterations)
{
#ifndef PIC_DISABLE_EIGEN
    float eval, val, tmp1, tmp2;
    const std::size_t Q = exposures.size();
    const std::size_t N = coefficients.size() - 1;

    const float Mmax = 1.f;

    for (float &_c : coefficients) {
        _c = 0.f;
    }
    R.assign(Q < 2 ? 0 : Q, std::vector<float>(Q < 2 ? 0 : Q, 1.f));
    for (int q1 = 0; q1 < R.size(); ++q1) {
        for (int q2 = 0; q2 < R[q1].size(); ++q2) {
            if (q2 == q1) {
                R[q1][q2] = 1.f;
            } else {
                R[q1][q2] = exposures[q1] / exposures[q2];
            }
        }
    }
    if (!samples || Q < 2 || coefficients.size() < 2) {
        return std::numeric_limits<float>::infinity();
    }

    //Check valid samples
    std::vector<std::vector<float>> g(nSamples, std::vector<float>(Q, 0.f));
    std::size_t P = 0;
    for (std::size_t p = 0; p < nSamples; ++p) {
        std::size_t valid = 0;
        for (std::size_t q = 0; q < Q; ++q) {
            if (samples[p * Q + q] >= 0) {
                ++valid;
            }
        }
        if (valid > 1) {
            for (std::size_t q = 0; q < Q; ++q) {
                if (samples[p * Q + q] >= 0) {
                    g[P][q] = samples[p * Q + q] / 255.f;
                } else {
                    g[P][q] = -1.f;
                }
            }
            ++P;
        }
    }
    g.resize(P);

    if (g.empty()) {
        return std::numeric_limits<float>::infinity();
    }

    //Precompute test with exponentials
    std::vector<Eigen::VectorXf> test;
    if (computeRatios) {
        test.assign(256, Eigen::VectorXf::Zero(N+1));
        for (std::size_t i = 0; i < 256; ++i) {
            test[i][0] = 1.f;
            for (std::size_t n = 1; n <= N; ++n) {
                test[i][n] = (i / 255.f) * test[i][n-1];
            }
        }
    }

    //Precompute M with exponentials
    std::vector<std::vector<std::vector<float>>> M(P,
                                                   std::vector<std::vector<float>>(Q,
                                                                                std::vector<float>(N+1, 0.f)));
    for (std::size_t p = 0; p < P; ++p) {
        for (std::size_t q = 0; q < Q; ++q) {
            M[p][q][0] = 1.f;
            for (std::size_t n = 1; n <= N; ++n) {
                M[p][q][n] = g[p][q] * M[p][q][n-1];
            }
        }
    }

    std::vector<std::vector<std::vector<std::vector<float>>>> d(P,
                                                                std::vector<std::vector<std::vector<float>>>(Q,
                                                                            std::vector<std::vector<float>>(Q,
                                                                                std::vector<float>(N+1, 1.f))));
    Eigen::MatrixXf A = Eigen::MatrixXf::Zero(N, N);
    Eigen::VectorXf x, b = Eigen::VectorXf::Zero(N);
    Eigen::VectorXf c(N+1), prev_c = Eigen::VectorXf::Zero(N+1);

    std::size_t iter = 0;

    do {
        //Compute d
        for (std::size_t p = 0; p < P; ++p) {
            for (std::size_t q1 = 0; q1 < Q; ++q1) {
                for (std::size_t q2 = 0; q2 < Q; ++q2) {
                    d[p][q1][q2].assign(N+1, 0.f);
                    if (q2 != q1) {
                        for (std::size_t n = 0; n <= N; ++n) {
                            if (g[p][q1] >= 0.f && g[p][q2] >= 0.f) {
                                d[p][q1][q2][n] = M[p][q1][n] - R[q1][q2] * M[p][q2][n];
                            } else {
                                d[p][q1][q2][n] = 0.f;
                            }
                        }
                    }
                }
            }
        }

        //Build the matrix A of the linear system
        A.setZero(N, N);
        for (std::size_t i = 0; i < N; ++i) {
            for (std::size_t j = 0; j < N; ++j) {
                for (std::size_t p = 0; p < P; ++p) {
                    for (std::size_t q1 = 0; q1 < Q; ++q1) {
                        for (std::size_t q2 = 0; q2 < Q; ++q2) {
                            if (q2 != q1) {
                                A(i, j) += d[p][q1][q2][i] * (d[p][q1][q2][j] - d[p][q1][q2][N]);
                            }
                        }
                    }
                }
            }
        }

        //Build the vector of knowns b
        b.setZero(N);
        for (std::size_t i = 0; i < N; ++i) {
            for (std::size_t p = 0; p < P; ++p) {
                for (std::size_t q1 = 0; q1 < Q; ++q1) {
                    for (std::size_t q2 = 0; q2 < Q; ++q2) {
                        if (q2 != q1) {
                            b(i) -= Mmax * d[p][q1][q2][i] * d[p][q1][q2][N];
                        }
                    }
                }
            }
        }

        //Solve the linear system
        x = A.partialPivLu().solve(b);
        c << x, Mmax - x.sum();

        if (computeRatios) {
            //Evaluate approximation increment
            eval = std::numeric_limits<float>::lowest();
            for (const Eigen::VectorXf &_M : test) {
                val = std::abs((c - prev_c).dot(_M));
                if (val > eval) {
                    eval = val;
                }
            }

            //Update R
            for (std::size_t q1 = 0; q1 < Q; ++q1) {
                for (std::size_t q2 = 0; q2 < Q; ++q2) {
                    R[q1][q2] = 0.f;
                    tmp1 = 0.f;
                    tmp2 = 0.f;
                    for (std::size_t p = 0; p < P; ++p) {
                        if (g[p][q1] >= 0.f && g[p][q2] >= 0.f) {
                            for (std::size_t n = 0; n <= N; ++n) {
                                tmp1 += c[n] * M[p][q1][n];
                                tmp2 += c[n] * M[p][q2][n];
                            }
                        }
                    }
                    R[q1][q2] += tmp1 / tmp2;
                }
            }

            ++iter;
        }
    } while (computeRatios && eval > eps && iter < max_iterations);

    for (std::size_t n = 0; n <= N; ++n) {
        coefficients[n] = c[n];
    }

    //Evaluate error
    eval = 0.f;
    for (std::size_t q1 = 0; q1 < Q; ++q1) {
        for (std::size_t q2 = 0; q2 < Q; ++q2) {
            if (q2 != q1) {
                for (std::size_t p = 0; p < P; ++p) {
                    if (g[p][q1] >= 0.f && g[p][q2] >= 0.f) {
                        val = 0.f;
                        for (std::size_t n = 0; n <= N; ++n) {
                            val += coefficients[n] * (M[p][q1][n] - R[q1][q2] * M[p][q2][n]);
                        }
                        eval += val * val;
                    }
                }
            }
        }
    }

    return eval;
#else
    return -1.0f;
#endif
}

}

#endif // PIC_ALGORITHMS_MITSUNAGA_NAYAR_CRF_HPP


/**
 * @file set_cover_test.cpp
 * @brief
 * @author Piotr Smulewicz
 * @version 1.0
 * @date 2014-02-17
 */

#include "test_utils/set_cover_check.hpp"
#include "test_utils/test_result_check.hpp"
#include "test_utils/logger.hpp"

#include "paal/greedy/set_cover/set_cover.hpp"
#include "paal/utils/functors.hpp"

#include <boost/test/unit_test.hpp>
#include <boost/range/irange.hpp>

#include <vector>
#include <iterator>

BOOST_AUTO_TEST_CASE(SetCover) {
    const int OPTIMAL = 2;
    std::vector<std::vector<int>> sets_element = { { 1, 2 },
                                                   { 3, 4, 5, 6 },
                                                   { 7, 8, 9, 10, 11, 12, 13,
                                                     14 },
                                                   { 1, 3, 5, 7, 9, 11, 13 },
                                                   { 2, 4, 6, 8, 10, 12, 0 } };
    auto costs = paal::utils::return_one_functor();
    auto sets = boost::irange(0, 5);
    auto set_to_elements = paal::utils::make_array_to_functor(sets_element);
    std::vector<int> result;
    auto element_index = paal::utils::identity_functor{};
    auto cost =
        paal::greedy::set_cover(sets, costs, set_to_elements,
                                std::back_inserter(result), element_index);
    double approximation_ratio =
        set_cover_result_check(sets, set_to_elements, result);
    check_result(cost, OPTIMAL, approximation_ratio);
}


//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#ifndef GF2_MST_HPP
#define GF2_MST_HPP

#include <boost/config.hpp>
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/prim_minimum_spanning_tree.hpp>

namespace rapter
{

namespace mst
{
    typedef std::pair< int, int > Edge;
    template <typename _Scalar>
    class MST
    {
        public:
            //addEdge()
    };

template <typename _Scalar>
int mstMain()
{
    using namespace boost;

    typedef adjacency_list < vecS
                           , vecS
                           , undirectedS
                           , property<vertex_distance_t, _Scalar>
                           , property < edge_weight_t, _Scalar >
                           > Graph;

    const int num_nodes = 5;
    Edge edges[] = { Edge(0, 2), Edge(1, 3), Edge(1, 4), Edge(2, 1), Edge(2, 3),
                  Edge(3, 4), Edge(4, 0)
                };
    _Scalar weights[] = { 1, 1, 2, 7, 3, 1, 1 };
    int num_edges = sizeof(edges) / sizeof(Edge);
    Graph g(edges, edges + num_edges, weights, num_nodes);
    typename property_map<Graph, edge_weight_t>::type weightmap = get(edge_weight, g);
    std::vector < typename graph_traits < Graph >::vertex_descriptor > p( num_vertices(g) );

    prim_minimum_spanning_tree(g, &p[0]);
    std::fstream dotfile("mst.dot");
    if ( !dotfile.is_open() )
    {
        std::cerr << "[" << __func__ << "]: " << "could not open mst.dot" << std::endl;
        return EXIT_FAILURE;
    }

    dotfile << "digraph {\nedge [dir=none]\n";
    // Original
    for ( std::size_t i = 0; i != num_edges; ++i )
    {
        dotfile << "V" << edges[i].first << " -> " << "V" << edges[i].second << std::endl;
    }
    // MST
    for (std::size_t i = 0; i != p.size(); ++i)
        if (p[i] != i)
        {
            std::cout << "parent[" << i << "] = " << p[i] << std::endl;
            dotfile << "v" << p[i] << " -> " << "v" << i << std::endl;
        }
        else
            std::cout << "parent[" << i << "] = no parent" << std::endl;

    dotfile << "}\n";
    dotfile.close();
    std::system("dot -Tpng mst.dot -o mst.png && eog mst.png");

    return EXIT_SUCCESS;
}

} //...ns mst

} //...ns GF2

#endif // GF2_MST_HPP


#pragma once

#include <armadillo>

#include "heattransfer/radial.hpp"

/*!
 * \brief Implements 1d radial unsteady heat transfer.
 *
 * This is documented in Jan Fredrik Helgaker's PhD thesis and in
 *  - Sensitivity of pipeline gas flow model to the selection of the equation of state
 *  - Transient flow in natural gas pipeline - The effect of pipeline thermal model
 */
class UnsteadyHeatTransfer : public RadialHeatTransfer
{
public:
    /*!
     * \brief Construct from complete description of pipeline and surroundings.
     * \param diameter Inner diameter [m]
     * \param pipeWall PipeWall instance
     * \param burialDepth Distance from top of pipe to top of burial medium [m]
     * \param burialMedium BurialMedium instance
     * \param ambientFluid AmbientFluid instanc.
     */
    UnsteadyHeatTransfer(
            const double diameter,
            const PipeWall& pipeWall,
            const double burialDepth,
            const BurialMedium& burialMedium,
            const AmbientFluid& ambientFluid);

    /*!
     * \brief Evaluate 1d radial unsteady heat transfer model.
     *
     * Operates on a HeatTransferState and returns a new HeatTransferState.
     * Requires the discretization temperature HeatTransferState::m_temperature.
     *
     * \param current Current HeatTransferState
     * \param timeStep Time step [s]
     * \param ambientTemperature Ambient temperature [K]
     * \param gasPressure Gas pressure [Pa]
     * \param gasTemperature Gas temperature [K]
     * \param gasReynoldsNumber Reynolds number of gas [-]
     * \param gasHeatCapacity Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return HeatTransferState with new heat flux.
     */
    virtual HeatTransferState evaluate(
            const HeatTransferState& current,
            const double timeStep,
            const double ambientTemperature,
            const double gasPressure,
            const double gasTemperature,
            const double gasReynoldsNumber,
            const double gasHeatCapacity,
            const double gasViscosity) const override;

    /*!
     * \brief Internal method used for evaluating the unsteady heat transfer model.
     *
     * This is exposed for testing purposes. We typically use pointers anyway,
     * so this is not accessible without casting to UnsteadyHeatTransfer.
     *
     * \param shellTemperature The temperature of each discretization layer [K]
     * \param timeStep Time step [s]
     * \param ambientTemperature Ambient temperature [K]
     * \param gasPressure Gas pressure [Pa]
     * \param gasTemperature Gas temperature [K]
     * \param gasReynoldsNumber Reynolds number of gas [-]
     * \param gasHeatCapacity Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return HeatTransferState with new heat flux.
     */
    HeatTransferState evaluateInternal(
            const arma::vec& shellTemperature,
            const double timeStep,
            const double ambientTemperature,
            const double gasPressure,
            const double gasTemperature,
            const double gasReynoldsNumber,
            const double gasHeatCapacity,
            const double gasViscosity) const;

    /*!
     * \brief Thermalize the unsteady heat transfer model to steady state.
     *
     * This just performs an infinite time step to find the temperature
     * distribution in the discretization layers at steady state.
     * This could also be done analytically, if we had the analytic solution.
     *
     * \param ambientTemperature Ambient temperature [K]
     * \param gasPressure Gas pressure [Pa]
     * \param gasTemperature Gas temperature [K]
     * \param gasReynoldsNumber Reynolds number of gas [-]
     * \param gasHeatCapacity Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return HeatTransferState with new heat flux.
     */
    HeatTransferState thermalizeToSteadyState(
            const double ambientTemperature,
            const double gasPressure,
            const double gasTemperature,
            const double gasReynoldsNumber,
            const double gasHeatCapacity,
            const double gasViscosity) const;

private:
    //! Heat transfer coefficient for each discretization layer.
    //! This is the k_i from eq. (2.26) in JFH PhD thesis.
    arma::vec m_heatTransferCoefficient;

    /*!
     * \brief Internal (private) method used for solving the equations in the 1d
     * radial unsteady heat transfer model.
     *
     * \param shellTemperature The temperature of each discretization layer [K]
     * \param timeStep Time step [s]
     * \param ambientTemperature Ambient temperature [K]
     * \param gasPressure Gas pressure [Pa]
     * \param gasTemperature Gas temperature [K]
     * \param gasReynoldsNumber Reynolds number of gas [-]
     * \param gasHeatCapacity Gas heat capacity (\f$c_p\f$) [J/(kg K)]
     * \param gasViscosity Gas dynamic viscosity [Pa s] = [kg/m*s]
     * \return HeatTransferState with new heat flux.
     */
    arma::vec solveEquations(
            const arma::vec& shellTemperature,
            const double timeStep,
            const double gasPressure,
            const double gasTemperature,
            const double ambientTemperature,
            const double gasReynoldsNumber,
            const double gasHeatCapacity,
            const double gasViscosity) const;
};


#ifndef QUANTUM_GENERAL
#define QUANTUM_GENERAL

#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <Eigen/KroneckerProduct>

typedef std::complex<float> Complex;
typedef Eigen::Matrix<Complex, Eigen::Dynamic, 1> Vector;
typedef Eigen::SparseMatrix<Complex> Matrix;

int pow2(int exp);
void printBin(int N, int BITS);
//return |0>
Vector zero(void);

//return |1>
Vector one(void);

//returns |N> for a space of size NQUBITS
Vector basis(int N, int NQUBITS);
#endif


#include <iostream>
#include <cmath>
#include <algorithm>
#include <vector>
#include <boost/multiprecision/cpp_int.hpp>
using namespace boost::multiprecision;
using namespace std;
int main() {
    cpp_int n, m; cin >> n;
    long long int cnt = 0;
    m = n;
    while (m > 0) {
        m /= 10;
        cnt++;
    }
    cpp_int com = (cpp_int)pow(10, cnt) - 1, c = (cpp_int)pow(10, cnt - 1), ans = 0;

    while(true) {
        if (n < com) com -= c;
        else break;
    }
    while (com > 0) {
        ans += (com % 10);
        com /= 10;
    }
    cout << ans << endl;
}


#ifndef INITIALIZERS_HPP
#define INITIALIZERS_HPP

#include <boost/random.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <Eigen/Dense>

using Matrix = Eigen::MatrixXd;
using MatrixType = Eigen::Ref<const Eigen::MatrixXd>;

namespace hmm {
  namespace initializers {

    // random number generator
    static boost::mt19937 rng;

    /**
     * Base initializer class (virtual).
     * Subclasses should be implemented as functors
     * templated by parameters.
     * Calling the functor should return an Eigen matrix.
     *
     */
    struct Initializer {
    public:
      virtual MatrixType operator()() = 0;
    };

    /**
     * Initialize a matrix of zeros.
     * @tparam n_rows Number of rows
     * @tparam n_cols Number of columns
     *
     */
    template<std::size_t n_rows, std::size_t n_cols>
    struct ZeroInitializer : public Initializer {
    public:
      MatrixType operator()() override {
        return Matrix::Zero(n_rows, n_cols);
      }
    };

    /**
     * Initialize a matrix with uniformly distributed values.
     * @tparam n_rows Number of rows
     * @tparam n_cols Number of columns
     * @param lower Support lower bound for uniform distribution
     * @param upper Support upper bound for uniform distribution
     *
     */
    template<std::size_t n_rows, std::size_t n_cols>
    struct UniformInitializer : public Initializer {
    public:
      UniformInitializer(const double lower, const double upper)
        : m_lower(lower),
          m_upper(upper),
          m_unifd(boost::random::uniform_real_distribution<>(lower, upper)) {}

      MatrixType operator()() override {
        return Matrix::Zero(n_rows, n_cols)
          .unaryExpr([&](double t){ return m_unifd(rng); });
      }

    private:
      double m_lower;
      double m_upper;
      boost::random::uniform_real_distribution<double> m_unifd;
    };

    /**
     * Initialize a matrix with normally distributed values.
     * @tparam n_rows Number of rows
     * @tparam n_cols Number of columns
     * @param mean Location parameter for the normal distribution
     * @param var Variance parameter for the normal distribution
     *
     */
    template<std::size_t n_rows, std::size_t n_cols>
    struct NormalInitializer : public Initializer {
    public:
      NormalInitializer(const double mean, const double var)
        : m_mean(mean),
          m_var(var),
          m_gaussd(boost::normal_distribution<>(mean, var)) {}
      MatrixType operator()() override {
        boost::variate_generator<boost::mt19937&,
                                 boost::normal_distribution<>> gaussvars(rng, m_gaussd);
        return Matrix::Zero(n_rows, n_cols)
          .unaryExpr([&](double t){ return gaussvars(); });
      }

    private:
      double m_mean;
      double m_var;
      boost::normal_distribution<double> m_gaussd;
    };
  }
}

#endif


/*
 * The MIT License
 *
 * Copyright 2017 Christoph Wurst <christoph@winzerhof-wurst.at>.
 *
 * 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 <cstdlib>
#include <boost/spirit/include/qi.hpp>
#include <boost/phoenix.hpp>

using namespace std;
namespace qi = boost::spirit::qi;

struct print_functor {

	void operator()(int const &i) const {
		cout << i << endl;
	}
};

/**
 * expr = term *( '+' term | '-' term )
 * term = fact *( '*' fact | '/' fact )
 * fact = double | '(' expr ')'
 */
template<typename Iterator>
struct simple_calc_grammar : qi::grammar<Iterator, qi::space_type> {

	simple_calc_grammar() : simple_calc_grammar::base_type(expr) {
		expr = term >> *('+' >> term | '-' >> term);
		term = fact >> *('*' >> fact | '/' >> fact);
		fact = qi::double_ | '(' >> expr >> ')';
	}

	qi::rule<Iterator, qi::space_type> expr, term, fact;
};

/**
 * expr = term *( '+' term | '-' term )
 * term = fact *( '*' fact | '/' fact )
 * fact = double | '(' expr ')'
 */
template<typename Iterator>
struct advanced_calc_grammar : qi::grammar<Iterator, double(), qi::space_type> {

	advanced_calc_grammar() : advanced_calc_grammar::base_type(expr) {
		expr = term [ qi::_val = qi::_1 ]
			>>
			*('+' >> term [ qi::_val += qi::_1 ]
			| '-' >> term [ qi::_val += qi::_1 ]
			);
		term = fact [qi::_val = qi::_1]
			>>
			*('*' >> fact [ qi::_val *= qi::_1 ]
			| '/' >> fact [ qi::_val /= qi::_1 ]
			);
		fact = qi::double_ [ qi::_val = qi::_1 ]
			| '(' >> expr [ qi::_val = qi::_1 ]
			>> ')';
	}

	qi::rule<Iterator, double(), qi::space_type> expr, term, fact;
};

/*
 *
 */
int main(int argc, char** argv) {
	string input;
	int output;

	getline(cin, input);
	auto begin = input.begin();
	auto end = input.end();

	advanced_calc_grammar<decltype(begin) > grammar;
	bool success = qi::phrase_parse(begin,
		end,
		grammar,
		qi::space,
		output);

	if (success) {
		cout << "Matched. Result=" << output << endl;
	} else {
		cout << "Error!" << endl;
	}

	return 0;
}


#ifndef MOCHIMOCHI_BINARY_OML_INTERFACE_HPP_
#define MOCHIMOCHI_BINARY_OML_INTERFACE_HPP_

#include <string>
#include <Eigen/Dense>

using namespace std;

/**
 * The BinaryOML interface declares the operations that all concrete BinaryOML must implement.
 */
class BinaryOML {
 public:
  virtual ~BinaryOML() {}
  virtual bool update(const Eigen::VectorXd& feature, const int label) = 0;
  virtual int predict(const Eigen::VectorXd& x) const = 0;
  virtual void save(const string& filename) = 0;
  virtual void load(const string& filename) = 0;
  virtual string name() const = 0;
};

#endif


//=======================================================================
// Copyright 1997-2001 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================

#include <boost/config.hpp>
#include <iostream>
#include <vector>
#include <boost/graph/strong_components.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/graph_utility.hpp>
/*
  Sample output:
  A directed graph:
  a --> b f h
  b --> c a
  c --> d b
  d --> e
  e --> d
  f --> g
  g --> f d
  h --> i
  i --> h j e c
  j -->

  Total number of components: 4
  Vertex a is in component 3
  Vertex b is in component 3
  Vertex c is in component 3
  Vertex d is in component 0
  Vertex e is in component 0
  Vertex f is in component 1
  Vertex g is in component 1
  Vertex h is in component 3
  Vertex i is in component 3
  Vertex j is in component 2
 */

int main(int, char*[])
{
  using namespace boost;
  const char* name = "abcdefghij";

  GraphvizDigraph G;
  read_graphviz("scc.dot", G);

  std::cout << "A directed graph:" << std::endl;
  print_graph(G, name);
  std::cout << std::endl;

  typedef graph_traits<GraphvizGraph>::vertex_descriptor Vertex;

  std::vector<int> component(num_vertices(G)), discover_time(num_vertices(G));
  std::vector<default_color_type> color(num_vertices(G));
  std::vector<Vertex> root(num_vertices(G));
  int num = strong_components(G, &component[0],
                              root_map(&root[0]).
                              color_map(&color[0]).
                              discover_time_map(&discover_time[0]));

  std::cout << "Total number of components: " << num << std::endl;
  std::vector<int>::size_type i;
  for (i = 0; i != component.size(); ++i)
    std::cout << "Vertex " << name[i]
         <<" is in component " << component[i] << std::endl;

  return 0;
}


#pragma once

#include "common.hpp"
#include "variant.hpp"

#include <set>
#include <optional>
#include <vector>
#include <random>

#include <boost/range.hpp>

// Mostly stuff for simple property-based testing

namespace ckvs { namespace utils {

template <typename ContainterT,
          typename IterT   = decltype(std::begin(std::declval<ContainterT &>())),
          typename ResultT = std::vector<boost::iterator_range<IterT>>>
auto split_to_random_parts(ContainterT && v, const size_t nParts, std::default_random_engine & gen)
  -> std::optional<ResultT>
{
  std::optional<ResultT> result{std::nullopt};
  if(nParts > std::size(v))
    return result;

  result = ResultT{};
  std::set<size_t> random_cuts;

  std::uniform_int_distribution<size_t> rnd{1ull, std::size(v) - 1};
  while(std::size(random_cuts) != nParts - 1)
    random_cuts.emplace(rnd(gen));

  result->reserve(nParts);

  auto to_iter = [&v](const size_t idx) {
    auto result = std::begin(v);
    std::advance(result, idx);
    return result;
  };

  size_t prev_cut = 0;
  for(const auto cut_point : random_cuts)
  {
    result->emplace_back(to_iter(prev_cut), to_iter(cut_point));
    prev_cut = cut_point;
  }
  result->emplace_back(to_iter(prev_cut), std::end(v));

  return result;
}

template <
  typename T,
  typename = std::enable_if_t<std::is_arithmetic_v<T>>,
  typename DisT =
    std::conditional_t<std::is_floating_point_v<T>, std::uniform_real_distribution<T>, std::uniform_int_distribution<T>>>
void random_value(T & val, std::default_random_engine & gen)
{
  DisT dis(std::numeric_limits<T>::min(), std::numeric_limits<T>::max() - 1);
  val = dis(gen);
}

inline void random_value(const size_t                 min_length,
                         const size_t                 max_length,
                         std::string &                s,
                         std::default_random_engine & gen)
{
  const size_t len = std::uniform_int_distribution<size_t>{min_length, max_length}(gen);

  std::uniform_int_distribution<short> char_gen{32, 126};
  s.resize(len);
  for(char & c : s)
    while(!isalnum(c = static_cast<char>(char_gen(gen))))
      continue;
}

template <typename VariantT>
void random_variant(VariantT & var, const size_t max_binary_size, std::default_random_engine & gen)
{
  default_init_variant(var, gen() % std::variant_size_v<VariantT>);
  std::visit(overloaded{[&](auto && val) { random_value(val, gen); },
                        [&](std::string & str) { random_value(1ull, max_binary_size, str, gen); }},
             var);
}

inline uint64_t fast_thread_local_rand(const uint64_t min, const uint64_t max)
{
  thread_local static std::default_random_engine              gen{std::random_device{}()};
  thread_local static std::uniform_int_distribution<uint64_t> rnd;
  using range_t = std::uniform_int_distribution<uint64_t>::param_type;
  return rnd(gen, range_t{min, max});
}

}}