#include "../../def_submodule.hpp"

#include "../../../../../type/math/coord.hpp"
#include "../../../../../type/math/matrix.hpp"
#include "../../../../../type/math/affine.hpp"

#include <boost/python.hpp>

typedef GS_DDMRM::S_IceRay::S_type::GT_size                GTs_size;
typedef GS_DDMRM::S_IceRay::S_type::GT_scalar              GTs_scalar;
typedef GS_DDMRM::S_IceRay::S_type::S_coord::GT_scalar3D   GTs_coord3D;
typedef GS_DDMRM::S_IceRay::S_type::S_matrix::GT_scalar3D  GTs_matrix3D;
typedef GS_DDMRM::S_IceRay::S_type::S_affine::GT_scalar3D  GTs_affine3D;

namespace{

GTs_scalar const& GFs_get( GTs_affine3D & P_affine, GTs_size P_row, GTs_size const& P_column )
 {
  return P_affine.matrix()[ P_row ][ P_column ];
 }

GTs_affine3D & GFs_set( GTs_affine3D & P_affine, GTs_size P_row, GTs_size const& P_column, GTs_scalar const& P_value )
 {
  P_affine.matrix()[ P_row ][ P_column ] = P_value;
   return P_affine;
 }

GTs_coord3D GFs_rowGet( GTs_affine3D & P_affine, GTs_size P_row  )
 {
  GTs_coord3D Ir_row;
  Ir_row[0] = P_affine.matrix()[ P_row][ 0 ];
  Ir_row[1] = P_affine.matrix()[ P_row][ 1 ];
  Ir_row[2] = P_affine.matrix()[ P_row][ 2 ];

  return Ir_row;
 }

GTs_affine3D & GFs_rowSet( GTs_affine3D & P_affine, GTs_size P_row, GTs_coord3D const& P_value )
 {
  P_affine.matrix()[ P_row][ 0 ] = P_value[0];
  P_affine.matrix()[ P_row][ 1 ] = P_value[1];
  P_affine.matrix()[ P_row][ 2 ] = P_value[2];
  return P_affine;
 }

GTs_coord3D GFs_columnGet( GTs_affine3D & P_affine, GTs_size P_column  )
 {
  GTs_coord3D Ir_column;

  Ir_column[0] = P_affine.matrix()[ 0 ][ P_column ];
  Ir_column[1] = P_affine.matrix()[ 1 ][ P_column ];
  Ir_column[2] = P_affine.matrix()[ 2 ][ P_column ];
  return Ir_column;
 }

GTs_affine3D &  GFs_columnSet( GTs_affine3D & P_affine, GTs_size P_column, GTs_coord3D const& P_value )
 {
  P_affine.matrix()[ 0 ][ P_column ] = P_value[0];
  P_affine.matrix()[ 1 ][ P_column ] = P_value[1];
  P_affine.matrix()[ 2 ][ P_column ] = P_value[2];
  return P_affine;
 }

GTs_affine3D &  GFs_scale1( GTs_affine3D & P_affine, GTs_scalar const& P_value )
 {
  P_affine.matrix()[ 0 ][ 0 ] *= P_value;
  P_affine.matrix()[ 1 ][ 1 ] *= P_value;
  P_affine.matrix()[ 2 ][ 2 ] *= P_value;
  return P_affine;
 }

GTs_affine3D &  GFs_scale3( GTs_affine3D & P_affine, GTs_coord3D const& P_value )
 {
  P_affine.matrix()[ 0 ][ 0 ] *= P_value[0];
  P_affine.matrix()[ 1 ][ 1 ] *= P_value[1];
  P_affine.matrix()[ 2 ][ 2 ] *= P_value[2];
  return P_affine;
 }

GTs_affine3D &  GFs_load( GTs_affine3D & P_affine, GTs_coord3D const& P_x, GTs_coord3D const& P_y, GTs_coord3D const& P_z, GTs_coord3D const& P_move )
 {
  ::math::linear::affine::system( P_affine, P_move, P_x, P_y, P_z );

  return P_affine;
 }

 GTs_affine3D
 GFs_lookAt
  (
    GTs_coord3D const& P_eye
   ,GTs_coord3D const& P_view
   ,GTs_coord3D const& P_up
  )
  {
   GTs_affine3D Ir_world;
   ::math::linear::affine::look_at( Ir_world, P_eye, P_view, P_up );

   return Ir_world;
  }

}

void expose_math_type_affine()
 {
  //MAKE_SUBMODULE( IceRay );
  MAKE_SUBMODULE( library   );
  MAKE_SUBMODULE( math   );

  typedef  GTs_coord3D const& (GTs_affine3D::*Tf_getCoord3D )(void) const;
  Tf_getCoord3D I_getCoord3D = &GTs_affine3D::vector;

  //typedef  GTs_affine3D     & (GTs_affine3D::*Tf_setCoord3D )( GTs_coord3D const&);
  //Tf_setCoord3D I_setCoord3D = &GTs_affine3D::vector;

  typedef  GTs_matrix3D const& (GTs_affine3D::*Tf_getMatrix3D )(void) const;
  Tf_getMatrix3D I_getMatrix3D = &GTs_affine3D::matrix;

  //typedef  GTs_affine3D     & (GTs_affine3D::*Tf_setMatrix3D )( GTs_matrix3D const&);
  //Tf_setMatrix3D I_setMatrix3D = &GTs_affine3D::matrix;

  boost::python::class_<GTs_affine3D>( "MathTypeAffine3D" )
/*    .def( boost::python::init<>() )
    .def( boost::python::init< GTs_coord3D >() )
    .def( boost::python::init< GTs_matrix3D >() )
    .def( boost::python::init< GTs_matrix3D, GTs_coord3D >() )
    .def( "load",     &GFs_load, boost::python::return_value_policy<boost::python::reference_existing_object>()  )
    .def( "coord",    I_getCoord3D,  boost::python::return_value_policy<boost::python::copy_const_reference>() )
    //.def( "coord",    I_setCoord3D,  boost::python::return_value_policy<boost::python::reference_existing_object>() )
    .def( "matrix",   I_getMatrix3D, boost::python::return_value_policy<boost::python::copy_const_reference>() )
    //.def( "matrix",   I_setMatrix3D, boost::python::return_value_policy<boost::python::reference_existing_object>() )

    .def( "element", &GFs_get,       boost::python::return_value_policy<boost::python::copy_const_reference>()  )
    .def( "element", &GFs_set,       boost::python::return_value_policy<boost::python::reference_existing_object>()  )
    .def( "row",     &GFs_rowGet    )
    .def( "row",     &GFs_rowSet,    boost::python::return_value_policy<boost::python::reference_existing_object>()  )
    .def( "column",  &GFs_columnGet )
    .def( "column",  &GFs_columnSet, boost::python::return_value_policy<boost::python::reference_existing_object>()  )

    .def( "scale",  &GFs_scale1, boost::python::return_value_policy<boost::python::reference_existing_object>()  )
    .def( "scale",  &GFs_scale3, boost::python::return_value_policy<boost::python::reference_existing_object>()  )
    //.def( "move" )
    //.def( "rotate_x" )
    //.def( "rotate_y" )
    //.def( "rotate_z" )
    //.def( "rotate_axis" )
    //.def( "load",   <GTs_matrix3D, GTs_coord3D > )

    //.def( boost::python::self + boost::python::self )
    //.def( boost::python::self - boost::python::self )
    //.def( boost::python::self * GTs_scalar3D::T_value() )
    //.def( GTs_scalar3D::T_value() * boost::python::self  )
    //.def( boost::python::self / GTs_scalar3D::T_value() )
    //.def( boost::python::self += boost::python::self )
    //.def( boost::python::self -= boost::python::self )
*/
  ;

    boost::python::def("MathAffine3D_lookAt",    GFs_lookAt );

 }


#include <iostream>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/count.hpp>
#include <boost/accumulators/statistics/mean.hpp>

using namespace boost::accumulators;
const int N = 5;
const int M = 2;

int main()
{
  accumulator_set<int, features<tag::count, tag::mean>> acc;

  for (int i = 0; i < N; i++) {
    acc(i);
  }

  return count(acc) == N && mean(acc) == M ? 0 : -1;
}


//=============================================================================
//
//  CLASS NConstraintInterfaceAD
//
//=============================================================================

#ifndef COMISO_NCONSTRAINTINTERFACEAD_HH
#define COMISO_NCONSTRAINTINTERFACEAD_HH

//== COMPILE-TIME PACKAGE REQUIREMENTS ========================================
#include <CoMISo/Config/config.hh>
#if COMISO_ADOLC_AVAILABLE
#if COMISO_EIGEN3_AVAILABLE

//== INCLUDES =================================================================

#include <CoMISo/Config/CoMISoDefines.hh>
#include "SuperSparseMatrixT.hh"

#include <boost/shared_array.hpp>

#include <adolc/adolc.h>
#include <adolc/adouble.h>
#include <adolc/drivers/drivers.h>
#include <adolc/sparse/sparsedrivers.h>
#include <adolc/taping.h>

#include "NConstraintInterface.hh"
#include "NProblemInterfaceAD.hpp"

#include "TapeIDSingleton.hh"

#define EIGEN_YES_I_KNOW_SPARSE_MODULE_IS_NOT_STABLE_YET
#include <Eigen/Sparse>

//== FORWARDDECLARATIONS ======================================================

//== NAMESPACES ===============================================================

namespace COMISO {

//== CLASS DEFINITION =========================================================

/** \class NProblemInterfaceAD NProblemInterfaceAD.hpp

    The problem interface using automatic differentiation.
 */
class COMISODLLEXPORT NConstraintInterfaceAD : public NConstraintInterface {
public:

    // Define Sparse Datatypes
    typedef NConstraintInterface::SVectorNC SVectorNC;
    typedef NConstraintInterface::SMatrixNC SMatrixNC;

    typedef NConstraintInterface::ConstraintType ConstraintType;

    /// Default constructor
    NConstraintInterfaceAD(NProblemInterfaceAD& _problem, int _n_unknowns,
                           const ConstraintType _type = NC_EQUAL, double _eps = 1e-6) :
        NConstraintInterface(_type, _eps),
        problem_(_problem),
        n_unknowns_(_n_unknowns),
        type_(_type),
        function_evaluated_(false),
        use_tape_(true),
        constant_hessian_evaluated_(false),
        tape_(static_cast<short int>(TapeIDSingleton::Instance()->requestId())) {

        for(size_t i = 0; i < 11; ++i) tape_stats_[i] = 0;
    }

    /// Destructor
    virtual ~NConstraintInterfaceAD() {
        TapeIDSingleton::Instance()->releaseId(static_cast<size_t>(tape_));
    }

    /**
     * \brief Only override this function
     */
    virtual adouble evaluate(const adouble* _x) = 0;

public:

    virtual int n_unknowns() {
        return n_unknowns_;
    }

    virtual double eval_constraint(const double* _x) {

        double y = 0.0;

        if(!function_evaluated_ || !use_tape_) {

            adouble y_d = 0.0;

            boost::shared_array<adouble> x_d_ptr = problem_.x_d_ptr();

            trace_on(tape_); // Start taping

            // Fill data vector
            for(int i = 0; i < n_unknowns_; ++i) {
                x_d_ptr[i] <<= _x[i];
            }

            // Call virtual function to compute
            // functional value
            y_d = evaluate(x_d_ptr.get());

            y_d >>= y;

            trace_off();

#ifdef ADOLC_STATS
            tapestats(tape_, tape_stats_);
            std::cout << "Status values for tape " << tape_ << std::endl;
            std::cout << "===============================================" << std::endl;
            std::cout << "Number of independent variables:\t" << tape_stats_[0] << std::endl;
            std::cout << "Number of dependent variables:\t\t" << tape_stats_[1] << std::endl;
            std::cout << "Max. number of live active variables:\t" << tape_stats_[2] << std::endl;
            std::cout << "Size of value stack:\t\t\t" << tape_stats_[3] << std::endl;
            std::cout << "Buffer size:\t\t\t\t" << tape_stats_[4] << std::endl;
            std::cout << "Total number of operations recorded:\t" << tape_stats_[5] << std::endl;
            std::cout << "Other stats [6]:\t\t\t" << tape_stats_[6] << std::endl;
            std::cout << "Other stats [7]:\t\t\t" << tape_stats_[7] << std::endl;
            std::cout << "Other stats [8]:\t\t\t" << tape_stats_[8] << std::endl;
            std::cout << "Other stats [9]:\t\t\t" << tape_stats_[9] << std::endl;
            std::cout << "Other stats [10]:\t\t\t" << tape_stats_[10] << std::endl;
            std::cout << "===============================================" << std::endl;
#endif

            function_evaluated_ = true;

        } else {

            double ay[1] = {0.0};

            int ec = function(tape_, 1, n_unknowns_, const_cast<double*>(_x), ay);

#ifdef ADOLC_RET_CODES
            std::cout << "Info: function() returned code " << ec << std::endl;
#endif

            y = ay[0];
        }

        return y;
    }

    virtual void eval_gradient(const double* _x, SVectorNC& _g) {

        if(!function_evaluated_ || !use_tape_) {
            // Evaluate original functional
            eval_constraint(_x);
        }

        boost::shared_array<double> grad_p = problem_.grad_ptr();

        _g.resize(n_unknowns_);
        _g.setZero();

        int ec = gradient(tape_, n_unknowns_, _x, grad_p.get());

        if(ec < 0) {
            // Retape function if return code indicates discontinuity
            function_evaluated_ = false;
#ifdef ADOLC_RET_CODES
            std::cout << __FUNCTION__ << " invokes retaping of function due to discontinuity! Return code: " << ec << std::endl;
#endif
            eval_constraint(_x);
            ec = gradient(tape_, n_unknowns_, _x, grad_p.get());
        }

#ifdef ADOLC_RET_CODES
        std::cout << "Info: gradient() returned code " << ec << std::endl;
#endif

        for(int i = 0; i < n_unknowns_; ++i) {
            _g.coeffRef(i) += grad_p[i];
        }
    }

    virtual void eval_hessian(const double* _x, SMatrixNC& _H) {

        _H.resize(n_unknowns_, n_unknowns_);

        if(constant_hessian() && constant_hessian_evaluated_) {
            _H = constant_hessian_;
            return;
        }

        if(!function_evaluated_ || !use_tape_) {
            // Evaluate original functional
            eval_constraint(_x);
        }

        if(sparse_hessian()) {

            int nz = 0;
            int opt[2] = {0, 0};

            unsigned int* r_ind = NULL;
            unsigned int* c_ind = NULL;
            double* val = NULL;

            int ec = sparse_hess(tape_, n_unknowns_, 0, _x, &nz, &r_ind, &c_ind, &val, opt);
            if(ec < 0) {
                // Retape function if return code indicates discontinuity
                function_evaluated_ = false;
#ifdef ADOLC_RET_CODES
                std::cout << __FUNCTION__ << " invokes retaping of function due to discontinuity! Return code: " << ec << std::endl;
#endif
                eval_constraint(_x);
                ec = sparse_hess(tape_, n_unknowns_, 0, _x, &nz, &r_ind, &c_ind, &val, opt);
            }

            assert(nz >= 0);
            assert(r_ind != NULL);
            assert(c_ind != NULL);
            assert(val != NULL);

#ifdef ADOLC_RET_CODES
            std::cout << "Info: sparse_hessian() returned code " << ec << std::endl;
#endif

            for(int i = 0; i < nz; ++i) {

                _H(r_ind[i], c_ind[i]) += val[i];
            }

            if(constant_hessian()) {
                constant_hessian_ = _H;
                constant_hessian_evaluated_ = true;
            }

            delete[] r_ind;
            delete[] c_ind;
            delete[] val;

        } else {

            double** h_ptr = problem_.dense_hessian_ptr();

            int ec = hessian(tape_, n_unknowns_, const_cast<double*>(_x), h_ptr);

            if(ec < 0) {
                // Retape function if return code indicates discontinuity
                function_evaluated_ = false;
#ifdef ADOLC_RET_CODES
                std::cout << __FUNCTION__ << " invokes retaping of function due to discontinuity! Return code: " << ec << std::endl;
#endif
                eval_constraint(_x);
                ec = hessian(tape_, n_unknowns_, const_cast<double*>(_x), h_ptr);
            }

#ifdef ADOLC_RET_CODES
            std::cout << "Info: hessian() returned code " << ec << std::endl;
#endif

            for(int i = 0; i < n_unknowns_; ++i) {
                for(int j = 0; j <= i; ++j) {

                    _H(i, j) += h_ptr[i][j];

                    if(i != j) {
                        _H(j, i) += h_ptr[i][j];
                    }
                }
            }

            if(constant_hessian()) {
                constant_hessian_ = _H;
                constant_hessian_evaluated_ = true;
            }
        }
    }

    /** \brief Use tape
     * Set this to false if the energy functional
     * is discontinuous (so that the operator tree
     * has to be re-established at each evaluation)
     */
    bool use_tape() const {
        return use_tape_;
    }

    void use_tape(bool _b) {
        use_tape_ = _b;
    }

    /**
     * \brief Indicate whether the hessian is sparse.
     * If so, the computations (as well as the memory
     * consumption) can be performed more efficiently.
     */
    virtual bool sparse_hessian() {
        return false;
    }

private:

    // Reference to associated objective function
    NProblemInterfaceAD& problem_;

    // Number of unknowns
    int n_unknowns_;

    // Constraint type
    ConstraintType type_;

    size_t tape_stats_[11];

    bool function_evaluated_;
    bool use_tape_;

    SMatrixNC constant_hessian_;
    bool constant_hessian_evaluated_;

    const short int tape_;
};

//=============================================================================
}// namespace COMISO
//=============================================================================
#endif // COMISO_ADOLC_AVAILABLE
//=============================================================================
#endif // COMISO_EIGEN3_AVAILABLE
//=============================================================================
#endif // ACG_NCONSTRAINTINTERFACEAD_HH defined
//=============================================================================


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

// #define MTL_VERBOSE_TEST

#include <iostream>

#include <boost/numeric/mtl/mtl.hpp>
#include <boost/numeric/itl/itl.hpp>

const int size = 3, N = size * size;

int max_iter= 0;

template <typename Matrix, typename Solver>
void test1(const char* solver_name, const char* matrix_name)
{
    mtl::io::tout << solver_name << " on " << matrix_name << std::endl;;

    Matrix                             A;
    laplacian_setup(A, size, size);

    mtl::dense_vector<double>          x(N, 1.0), b(N);
    b = A * x;
    x= 0;

    Solver s(A);
    s.iteration_ref().set_max_iterations(N+1);

#ifdef MTL_VERBOSE_TEST
    s.iteration_ref().set_quite(false);
    s.iteration_ref().set_cycle(1);
#else
    s.iteration_ref().suppress_resume(true);
#endif

    s(x, b);
    int i= s.iteration().iterations();
    if (i > max_iter)
	max_iter= i;
    // MTL_THROW_IF(size == 3 && i > 9, mtl::runtime_error("Too many iterations in solver"));
    if (size == 3 && i > 9)
	std::cout << "Solver \"" << solver_name << "\" converges slowly!\n";
}

// same without iter
template <typename Matrix, typename Solver>
void test1a(const char* solver_name, const char* matrix_name)
{
    mtl::io::tout << solver_name << " on " << matrix_name << std::endl;;

    Matrix                             A;
    laplacian_setup(A, size, size);

    mtl::dense_vector<double>          x(N, 1.0), b(N);
    b = A * x;
    x= 0;

    Solver s(A);
    s.step(x, b);
}

template <typename Matrix>
int test2(const char* matrix_name)
{
    test1<Matrix, itl::cg_solver<Matrix> >("CG", matrix_name);
    test1<Matrix, itl::cg_solver<Matrix, itl::pc::ilu_0<Matrix> > >("CG with ILU_0", matrix_name);
    test1<Matrix, itl::cgs_solver<Matrix> >("CGS", matrix_name);
    test1<Matrix, itl::cgs_solver<Matrix, itl::pc::ilu_0<Matrix> > >("CGS with ILU_0", matrix_name);

    test1<Matrix, itl::bicg_solver<Matrix> >("BiCG", matrix_name);
    test1<Matrix, itl::bicg_solver<Matrix, itl::pc::ilu_0<Matrix> > >("BiCG with ILU_0", matrix_name);
    test1<Matrix, itl::bicgstab_solver<Matrix> >("BiCGStab", matrix_name);
    test1<Matrix, itl::bicgstab_solver<Matrix, itl::pc::ilu_0<Matrix> > >("BiCGStab with ILU_0", matrix_name);
    test1<Matrix, itl::bicgstab_2_solver<Matrix> >("BiCGStab(2)", matrix_name);
    test1<Matrix, itl::bicgstab_2_solver<Matrix, itl::pc::ilu_0<Matrix> > >("BiCGStab(2) with ILU_0", matrix_name);
    test1<Matrix, itl::bicgstab_ell_solver<Matrix> >("BiCGStab(ell)", matrix_name);
    test1<Matrix, itl::bicgstab_ell_solver<Matrix, itl::pc::ilu_0<Matrix> > >("BiCGStab(ell) with ILU_0", matrix_name);
    test1<Matrix, itl::gmres_solver<Matrix> >("GMRES", matrix_name);
    test1<Matrix, itl::gmres_solver<Matrix, itl::pc::ilu_0<Matrix> > >("GMRES with ILU_0", matrix_name);
    test1<Matrix, itl::gmres_solver<Matrix, itl::pc::ilu_0<Matrix>, itl::pc::ilu_0<Matrix> > >("GMRES with ILU_0 from left and right", matrix_name);

    test1<Matrix, itl::qmr_solver<Matrix> >("QMR", matrix_name);
    test1<Matrix, itl::qmr_solver<Matrix, itl::pc::ilu_0<Matrix> > >("QMR with ILU_0", matrix_name);
    test1<Matrix, itl::tfqmr_solver<Matrix> >("TFQMR", matrix_name);
    test1<Matrix, itl::tfqmr_solver<Matrix, itl::pc::ilu_0<Matrix> > >("TFQMR with ILU_0", matrix_name);

    test1<Matrix, itl::idr_s_solver<Matrix> >("IDR(s)", matrix_name);
    test1<Matrix, itl::idr_s_solver<Matrix, itl::pc::ilu_0<Matrix> > >("IDR(s) with ILU_0", matrix_name);

    typedef itl::cg_solver<Matrix>                           cg1_type;
    typedef itl::cg_solver<Matrix, itl::pc::ilu_0<Matrix> >  cg2_type;

    test1a<Matrix, itl::repeating_solver<cg1_type, 3, true> >("3 iterations CG", matrix_name);
    test1a<Matrix, itl::repeating_solver<cg2_type, 3, true> >("3 iterations CG with ILU_0", matrix_name);

    return 0;
}

int main()
{
    test2<mtl::compressed2D<double> >("Compressed2D");
    //test<mtl::sparse_banded<double> >();
    // std::cout << "Largest iteration number is " << max_iter << '\n';

    return 0;
}


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

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

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

#include <boost/simd/function/horn.hpp>
#include <boost/simd/function/horn1.hpp>
#include <boost/simd/arch/common/detail/tags.hpp>
#include <boost/simd/constant/invlog10_2.hpp>
#include <boost/simd/constant/invlog_2.hpp>
#include <boost/simd/constant/log10_2hi.hpp>
#include <boost/simd/constant/log10_2lo.hpp>
#include <boost/simd/constant/log_10.hpp>
#include <boost/simd/constant/log_2.hpp>
#include <boost/simd/constant/log_2hi.hpp>
#include <boost/simd/constant/log_2lo.hpp>
#include <boost/simd/constant/maxlog.hpp>
#include <boost/simd/constant/maxlog10.hpp>
#include <boost/simd/constant/maxlog2.hpp>
#include <boost/simd/constant/minlog.hpp>
#include <boost/simd/constant/minlog10.hpp>
#include <boost/simd/constant/minlog2.hpp>
#include <boost/simd/constant/two.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/function/simd/is_greater_equal.hpp>
#include <boost/simd/function/simd/is_less_equal.hpp>
#include <boost/simd/function/simd/fma.hpp>
#include <boost/simd/function/simd/fnms.hpp>
#include <boost/simd/function/simd/inc.hpp>
#include <boost/simd/function/simd/oneplus.hpp>
#include <boost/simd/function/simd/oneminus.hpp>
#include <boost/simd/function/simd/round2even.hpp>
#include <boost/simd/function/simd/sqr.hpp>
#include <boost/simd/logical.hpp>
#include <boost/dispatch/meta/scalar_of.hpp>

namespace boost { namespace simd
{
  namespace detail
  {
    namespace bd =  boost::dispatch;
    namespace bs =  boost::simd;

    template < class A0> struct exp_reduction < A0, bs::tag::exp_, double>
    {
      static BOOST_FORCEINLINE auto isgemaxlog(A0 const& a0) BOOST_NOEXCEPT
        -> decltype(is_greater_equal(a0, Maxlog<A0>()))
      {
        return is_greater_equal(a0, Maxlog<A0>());
      }

      static BOOST_FORCEINLINE auto isleminlog(A0 const& a0) BOOST_NOEXCEPT
        -> decltype(is_less_equal(a0, Minlog<A0>()))
      {
        return is_less_equal(a0, Minlog<A0>());
      }

      static BOOST_FORCEINLINE A0 reduce( A0 const& a0
                                        , A0& hi, A0& lo, A0& x) BOOST_NOEXCEPT
      {
        A0 k = round2even(Invlog_2<A0>()*a0);
        hi = fnms(k, Log_2hi<A0>(), a0); //a0-k*L
        lo = k*Log_2lo<A0>();
        x  = hi-lo;
        return k;
      }

      static BOOST_FORCEINLINE A0 approx(A0 x) BOOST_NOEXCEPT
      {
        A0 const t = sqr(x);
        return fnms(t,
                    horn<A0
                         , 0x3fc555555555553eull
                         , 0xbf66c16c16bebd93ull
                         , 0x3f11566aaf25de2cull
                         , 0xbebbbd41c5d26bf1ull
                         , 0x3e66376972bea4d0ull
                    >(t), x); //x-h*t
    }

      static BOOST_FORCEINLINE A0 finalize(A0 x, A0 c, A0 hi, A0 lo) BOOST_NOEXCEPT
      {
        return One<A0>()-(((lo-(x*c)/(Two<A0>()-c))-hi));
      }

    };

    template < class A0 > struct exp_reduction < A0, bs::tag::exp2_, double>
    {
      static BOOST_FORCEINLINE auto isgemaxlog(A0 const& a0) BOOST_NOEXCEPT
        -> decltype(is_greater_equal(a0, Maxlog2<A0>()))
      {
        return is_greater_equal(a0, Maxlog2<A0>());
      }

      static BOOST_FORCEINLINE auto isleminlog(A0 const& a0) BOOST_NOEXCEPT
        -> decltype(is_less_equal(a0, Minlog2<A0>()))
      {
        return is_less_equal(a0, Minlog2<A0>());
      }

      static BOOST_FORCEINLINE A0 reduce(A0 const& a0, A0, A0, A0& x) BOOST_NOEXCEPT
      {
        A0 k = round2even(a0);
        x = (a0 - k)*Log_2<A0>();
        return k;
      }

      static BOOST_FORCEINLINE A0 approx(A0 x) BOOST_NOEXCEPT
      {
        const A0 t =  sqr(x);
        return fnms(t,
                    horn<A0
                         , 0x3fc555555555553eull
                         , 0xbf66c16c16bebd93ull
                         , 0x3f11566aaf25de2cull
                         , 0xbebbbd41c5d26bf1ull
                         , 0x3e66376972bea4d0ull
                    > (t), x); //x-h*t
      }

      static BOOST_FORCEINLINE A0 finalize( A0 x, A0 c, A0, A0& ) BOOST_NOEXCEPT
      {
        return oneminus(((-(x*c)/(Two<A0>()-c))-x));
      }
    };

    template < class A0 > struct exp_reduction < A0, bs::tag::exp10_, double>
    {
      static BOOST_FORCEINLINE auto isgemaxlog(A0 const& a0) BOOST_NOEXCEPT
        -> decltype(is_greater_equal(a0, Maxlog10<A0>()))
      {
        return is_greater_equal(a0, Maxlog10<A0>());
      }

      static BOOST_FORCEINLINE auto isleminlog(A0 const& a0) BOOST_NOEXCEPT
        -> decltype(is_less_equal(a0, Minlog10<A0>()))
      {
        return is_less_equal(a0, Minlog10<A0>());
      }

      static BOOST_FORCEINLINE A0 reduce(A0 const& a0, A0&, A0&, A0& x) BOOST_NOEXCEPT
      {
        A0 k  = round2even(Invlog10_2<A0>()*a0);
        x = fnms(k, Log10_2hi<A0>(), a0);
        x = fnms(k, Log10_2lo<A0>(), x);
        return k;
      }

      static BOOST_FORCEINLINE A0 approx(A0 x) BOOST_NOEXCEPT
      {
        A0 xx = sqr(x);
        A0 px = x*horn<A0,
                       0x40a2b4798e134a01ull,
                       0x40796b7a050349e4ull,
                       0x40277d9474c55934ull,
                       0x3fa4fd75f3062dd4ull
                       > (xx);
        A0 x2 =  px/(horn1<A0,
                          0x40a03f37650df6e2ull,
                          0x4093e05eefd67782ull,
                          0x405545fdce51ca08ull
                     //   0x3ff0000000000000ull
                          > (xx)-px);
        return oneplus(x2+x2);
      }

      static BOOST_FORCEINLINE A0 finalize(A0, A0 c, A0,  A0 ) BOOST_NOEXCEPT
      {
        return c;
      }
    };
  }
} }
#endif


// Copyright 2018 Hans Dembinski
//
// 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 <array>
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators/weighted_sum.hpp>
#include <boost/histogram/algorithm/sum.hpp>
#include <boost/histogram/axis/integer.hpp>
#include <unordered_map>
#include <vector>
#include "throw_exception.hpp"
#include "utility_histogram.hpp"

using namespace boost::histogram;
using boost::histogram::algorithm::sum;

template <typename Tag>
void run_tests() {
  auto ax = axis::integer<>(0, 10);

  {
    auto h = make(Tag(), ax);
    std::fill(h.begin(), h.end(), 1);
    BOOST_TEST_EQ(sum(h), 12);
    BOOST_TEST_EQ(sum(h, coverage::inner), 10);
  }

  {
    auto h = make_s(Tag(), std::array<int, 12>(), ax);
    std::fill(h.begin(), h.end(), 1);
    BOOST_TEST_EQ(sum(h), 12);
    BOOST_TEST_EQ(sum(h, coverage::inner), 10);
  }

  {
    auto h = make_s(Tag(), std::unordered_map<std::size_t, int>(), ax);
    std::fill(h.begin(), h.end(), 1);
    BOOST_TEST_EQ(sum(h), 12);
    BOOST_TEST_EQ(sum(h, coverage::inner), 10);
  }

  {
    auto h = make_s(Tag(), std::vector<double>(), ax, ax);
    std::fill(h.begin(), h.end(), 1);
    BOOST_TEST_EQ(sum(h), 12 * 12);
    BOOST_TEST_EQ(sum(h, coverage::inner), 10 * 10);
  }

  {
    using W = accumulators::weighted_sum<>;
    auto h = make_s(Tag(), std::vector<W>(), axis::integer<>(0, 2),
                    axis::integer<int, axis::null_type, axis::option::none_t>(2, 4));
    W w(0, 2);
    for (auto&& x : h) {
      x = w;
      w = W(w.value() + 1, 2);
    }

    // x-axis has 4 bins, y-axis has 2 = 8 bins total with 4 inner bins

    const auto v1 = algorithm::sum(h);
    BOOST_TEST_EQ(v1.value(), 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7);
    BOOST_TEST_EQ(v1.variance(), 8 * 2);

    const auto v2 = algorithm::sum(h, coverage::inner);
    BOOST_TEST_EQ(v2.value(), 1 + 2 + 5 + 6);
    BOOST_TEST_EQ(v2.variance(), 4 * 2);
  }
}

int main() {
  run_tests<static_tag>();
  run_tests<dynamic_tag>();

  return boost::report_errors();
}


/*
BSD 3-Clause License
Copyright (c) 2020, Juan S. Campos
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. 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.
3. Neither the name of the copyright holder 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.
*/

/*
This code calculates the partial and augmented relaxations as described in
"Partial Lasserre relaxation for sparse Max-Cut" by Campos, J.S., Misener R., and Parpas, P. 2020.
*/

#include <iostream>
#include "fusion.h"
#include <fstream>
#include <string>
#include <sstream>
#include <stdio.h>
#include <typeinfo>
#include <chrono>
#include <math.h>
#include <algorithm>
#include <vector>
#include <iomanip>
#include <stdlib.h>
#include <time.h>
#include <boost/functional/hash.hpp>
#include <unordered_map>
#define BILLION 1e9

using namespace std;
using namespace mosek::fusion;
using namespace monty;

struct timespec my_start;

void read_matrix(const char *, Matrix::t&);
void read_matrix_sparse_format(const char *, Matrix::t&);

template <typename Container>
struct container_hash
{
	size_t operator()(Container const& c) const {
		return boost::hash_range(c.begin(), c.end());
	}
};

int main(int argc, char ** argv)
{
 	 // Order of arguments: max_size_cliques (r), no_subsets to add (p), type of heuristic (H1 to H5), name  of the instance
	char aux_string[300];
	// Read weight matrix W
	// First row must contain the number of nodes and edges
	Matrix::t W;
	sprintf(aux_string,"graphs/%s.txt",argv[4]);
	read_matrix_sparse_format(aux_string, W);

	int n = W->numColumns();
	int max_size_clique = atoi(argv[1]);// 0,2,3 ...
	int no_subsets = atoi(argv[2]);// 0,1,2,3 ...
	char heuristic [300];
	sprintf(heuristic,"%s",argv[3]);// H1, H2, ..., H5

	int total_cliques;
	double time_creating_mosek = 0;
	double time_solving_SDP = 0;
	double time_creating_cliques = 0;
	double time_generating_LB = 0;
	double time_total = 0;

	auto start = std::chrono::high_resolution_clock::now();
	auto start_creating = std::chrono::high_resolution_clock::now();

    // Read maximal cliques and subset generated from matlab
    Matrix::t cliques;
    Matrix::t first_second_order;

   	sprintf(aux_string,"clique_%s_%d_%d_%s.txt",argv[4], max_size_clique, no_subsets,heuristic);
	read_matrix(aux_string, cliques);
    sprintf(aux_string,"clique_aux_%s_%d_%d_%s.txt",argv[4], max_size_clique, no_subsets,heuristic);
	read_matrix(aux_string, first_second_order);
	auto finish = std::chrono::high_resolution_clock::now();
	std::chrono::duration<double> elapsed = finish - start;
	cout<<"time reading W, cliques, and first_second_order: "<<elapsed.count()<<endl;

	start = std::chrono::high_resolution_clock::now();
	total_cliques = (cliques->numRows());

	vector <int> size_cliques(total_cliques);
	vector <vector<int>> idx_cliques(total_cliques);
	vector <int> size_blk(total_cliques);
	int total_var_all_blocks = 0;
	for (int i = 0; i < total_cliques; i++)
    {
		int aux_count = 0;

	    for(int j=0; j < n; j++)
	    {
			if(cliques->get(i,j)>0)
			{
				aux_count = aux_count + 1;
				idx_cliques[i].push_back(j);
			}
		}
		size_cliques[i] = aux_count;
		if((first_second_order->get(i,0) == 1) | (aux_count == 1))
		{
			size_blk[i] = aux_count + 1;
		}
		else
		{
			size_blk[i] = (aux_count + 2)*(aux_count + 1)/2 - aux_count;
		}
		total_var_all_blocks = total_var_all_blocks + (size_blk[i]*size_blk[i]);
    }
    finish = std::chrono::high_resolution_clock::now();
	elapsed = finish - start;
	cout<<"time creating size_cliques and idx_cliques: "<<elapsed.count()<<endl;

    // Generate unordered_map containing the monomials needed for the cliques
    start = std::chrono::high_resolution_clock::now();
    vector <int> alpha (4,0);
    unordered_map<vector<int>, int, container_hash<vector<int>>> map;

    for(int i=0; i< n; i++)
    {
		alpha[0] = i;
		map.insert({alpha,i});
	}
	int value_map = n;

    for (int i = 0; i < total_cliques; i++)
    {
		int degree = 2;
		if(first_second_order->get(i,0) == 2)
		{
			degree = 4;
		}

		for(int j=2; j<=degree; j++)
		{
			if(j <= size_cliques[i])
			{
				vector <int> perm(size_cliques[i], 0);
                fill_n(perm.begin(), j, 1);
                int count_aux = 0;
                do
                {
                    for (int k = 0; k < size_cliques[i]; k++)
                    {
                        if (perm[k] == 1)
                        {
                            alpha[count_aux] = idx_cliques[i][k];
                            count_aux = count_aux + 1;
                        }
                    }
                    count_aux = 0;
                    if(map.find(alpha) == map.end())
                    {
                        map.insert({alpha,value_map});
                        value_map = value_map + 1;
                    }
                    fill(alpha.begin(), alpha.end(),0);
                } while (prev_permutation(perm.begin(), perm.end()));
			}
		}
	}

	int total_y = map.size();
	finish = std::chrono::high_resolution_clock::now();
	elapsed = finish - start;
	cout<<"time creating unordered_map: "<<elapsed.count()<<endl;

    // Generating SDP problem: max b*y, st. Aty - C = S, S>=0

    // Creating mosek model
    Model::t M = new Model("sdo1");
    // Creating SDP blocks and lifted monomials variable and calculating norm_C
    double norm_C = 0;
    Variable::t S[total_cliques];
    for(int i=0; i<total_cliques; i++)
    {
		sprintf(aux_string,"S_%d",i);
	    S[i] = M->variable(aux_string, Domain::inPSDCone(size_blk[i]));
	    norm_C = max(norm_C, sqrt(double(size_blk[i])));
	}

	norm_C = (max(1.0,norm_C));

	// Creating b
	auto bsubi = new_array_ptr<int, 1>(n*(n-1)*0.5 + n);
	auto bsubj = new_array_ptr<int, 1>(n*(n-1)*0.5 + n);
	auto bcof  = new_array_ptr<double, 1>(n*(n-1)*0.5 + n);
	double obj_constant = 0;

	fill(alpha.begin(), alpha.end(),0);
	double norm_b = 0;
	vector<double> pert(n);
	srand(time(NULL));
	double perturbation = 0;//////////////////////////////
	for(int i = 0; i < n; i++)
	{
		pert[i] = perturbation*(double(rand())/RAND_MAX);
		norm_b = norm_b + pert[i];

		for(int j = i+1; j < n; j++)
		{
			norm_b = 0.25*4*W->get(i,j)*W->get(i,j) + norm_b;
		}
	}
	norm_b = max(1.0,sqrt(norm_b));
	int aux_count = 0;
	for(int i = 0; i < n; i++)
	{
		alpha[0] = i;
		alpha[1] = 0;
		(*bsubi)[aux_count] = map[alpha];
		(*bsubj)[aux_count] = 0;
		(*bcof)[aux_count] = pert[i]/norm_b;
		aux_count = aux_count + 1;
		for(int j = i+1; j < n; j++)
		{
			alpha[0] = i;
			alpha[1] = j;
			(*bsubi)[aux_count] = map[alpha];
			(*bsubj)[aux_count] = 0;
			(*bcof)[aux_count] = -0.25*2*(W->get(i,j))/norm_b;
			obj_constant = obj_constant + W->get(i,j);
			aux_count = aux_count + 1;
		}
	}
	obj_constant = 2*obj_constant*0.25;
	Matrix::t b = Matrix::sparse(total_y, 1, bsubi, bsubj, bcof);

	// Generating A
    start = std::chrono::high_resolution_clock::now();
    int total_constraints = total_var_all_blocks;

	Expression::t Ax_b;
	int count_const;
	vector<double> norm_A(total_cliques);
	for(int i=0; i < total_cliques; i++)
    {
		int size_vec_blk_i = size_blk[i]*size_blk[i];
		int size_blk_i = size_blk[i];
		int size_clique_i = size_cliques[i];
		int total_constraints = size_blk_i*(size_blk_i - 1);
		count_const = 0;
		auto msubi = new_array_ptr<int, 1>(total_constraints);
	    auto msubj = new_array_ptr<int, 1>(total_constraints);
	    auto mcof  = new_array_ptr<double, 1>(total_constraints);
	    norm_A[i] = max(1.0, sqrt(double(total_constraints)));

		fill(alpha.begin(), alpha.end(),0);
		double const_coef = 1/norm_A[i];
		for(int j = 1; j <= size_clique_i; j++)
		{
			alpha[0] = idx_cliques[i][j-1];
			(*msubi)[count_const] = j;
			(*msubj)[count_const] = map[alpha];
			(*mcof)[count_const] = const_coef;
			(*msubi)[count_const+1] = j*(size_blk_i);
			(*msubj)[count_const+1] = map[alpha];
			(*mcof)[count_const+1] = const_coef;
			count_const = count_const + 2;
		}

		for(int j = 0; j < size_clique_i; j++)
		{
			for(int k = j + 1; k < size_cliques[i]; k++)
			{
				alpha[0] = idx_cliques[i][j];
				alpha[1] = idx_cliques[i][k];
				(*msubi)[count_const] = (j+1)*(size_blk[i]) + k + 1;
				(*msubj)[count_const] = map[alpha];
				(*mcof)[count_const] = const_coef;
				(*msubi)[count_const+1] = (k+1)*(size_blk[i]) + j + 1;
				(*msubj)[count_const+1] = map[alpha];
				(*mcof)[count_const+1] = const_coef;
				count_const = count_const + 2;
			}
		}

		if((first_second_order->get(i,0) == 2) & (size_clique_i > 1))
		{
			fill(alpha.begin(), alpha.end(),0);
			int count_aux = 1;
		    for(int k = 0; k < size_clique_i; k++)
		    {
				for(int l = k+1; l < size_clique_i; l++)
				{
			        alpha[0] = idx_cliques[i][k];
				    alpha[1] = idx_cliques[i][l];
				    (*msubi)[count_const] = (size_clique_i) + count_aux;
				    (*msubj)[count_const] = map[alpha];
				    (*mcof)[count_const] = const_coef;
				    (*msubi)[count_const+1] = ((size_clique_i+count_aux)*size_blk_i);
				    (*msubj)[count_const+1] = map[alpha];
				    (*mcof)[count_const+1] = const_coef;
				    count_const = count_const + 2;
				    count_aux = count_aux + 1;
			    }
			}

		    for(int j = 0; j < size_clique_i; j++)
		    {
				count_aux = 1;
			    for(int k = 0; k < size_clique_i-1; k++)
			    {
					for(int l = k+1; l < size_clique_i; l++)
					{
				        fill(alpha.begin(), alpha.end(),0);
				        if(j == k)
				        {
							alpha[0] = idx_cliques[i][l];
						}
						else if(j == l)
						{
							alpha[0] = idx_cliques[i][k];
						}
						else if(j<k)
						{
							alpha[0] = idx_cliques[i][j];
							alpha[1] = idx_cliques[i][k];
							alpha[2] = idx_cliques[i][l];
						}
						else if (j<l)
						{
							alpha[0] = idx_cliques[i][k];
							alpha[1] = idx_cliques[i][j];
							alpha[2] = idx_cliques[i][l];
						}
						else
						{
							alpha[0] = idx_cliques[i][k];
							alpha[1] = idx_cliques[i][l];
							alpha[2] = idx_cliques[i][j];
						}

				        (*msubi)[count_const] = (j+1)*size_blk_i + size_clique_i + count_aux;
				        (*msubj)[count_const] = map[alpha];
				        (*mcof)[count_const] = const_coef;
				        (*msubi)[count_const+1] = (size_clique_i + count_aux)*size_blk_i + j + 1;
				        (*msubj)[count_const+1] = map[alpha];
				        (*mcof)[count_const+1] = const_coef;
				        count_const = count_const + 2;
				        count_aux = count_aux + 1;
			        }
				}
		    }

		    count_aux = 0;
		    int count_aux2;
		    for(int j = 0; j < size_clique_i-1; j++)
		    {
				for(int k = j+1; k < size_clique_i; k++)
			    {
					count_aux = count_aux + 1;
					count_aux2 = 0;
					for(int l = 0; l < size_clique_i-1; l++)
					{
						for(int m = l+1; m < size_clique_i; m++)
					    {
							count_aux2 = count_aux2 + 1;
							if((j==l) & (k==m))
							{
								continue;
							}
							fill(alpha.begin(), alpha.end(),0);
				            if(j == l)
				            {
								if(k<m)
								{
								    alpha[0] = idx_cliques[i][k];
								    alpha[1] = idx_cliques[i][m];
								}
								else
								{
									alpha[0] = idx_cliques[i][m];
								    alpha[1] = idx_cliques[i][k];
								}
						    }
						    else if(k == m)
						    {
							    if(j<l)
								{
								    alpha[0] = idx_cliques[i][j];
								    alpha[1] = idx_cliques[i][l];
								}
								else
								{
									alpha[0] = idx_cliques[i][l];
								    alpha[1] = idx_cliques[i][j];
								}
						    }
						    else if(j == m)
						    {
							    alpha[0] = idx_cliques[i][l];
							    alpha[1] = idx_cliques[i][k];

						    }
						    else if(k == l)
						    {
							    alpha[0] = idx_cliques[i][j];
							    alpha[1] = idx_cliques[i][m];

						    }
						    else if(k<l)
						    {
							    alpha[0] = idx_cliques[i][j];
							    alpha[1] = idx_cliques[i][k];
							    alpha[2] = idx_cliques[i][l];
							    alpha[3] = idx_cliques[i][m];
						    }
						    else if(m<j)
						    {
							    alpha[0] = idx_cliques[i][l];
							    alpha[1] = idx_cliques[i][m];
							    alpha[2] = idx_cliques[i][j];
							    alpha[3] = idx_cliques[i][k];
						    }
						    else if((j<l) & (m<k))
						    {
							    alpha[0] = idx_cliques[i][j];
							    alpha[1] = idx_cliques[i][l];
							    alpha[2] = idx_cliques[i][m];
							    alpha[3] = idx_cliques[i][k];
						    }
						    else if((j<l) & (k<m))
						    {
							    alpha[0] = idx_cliques[i][j];
							    alpha[1] = idx_cliques[i][l];
							    alpha[2] = idx_cliques[i][k];
							    alpha[3] = idx_cliques[i][m];
						    }
						    else if((l<j) & (m<k))
						    {
							    alpha[0] = idx_cliques[i][l];
							    alpha[1] = idx_cliques[i][j];
							    alpha[2] = idx_cliques[i][m];
							    alpha[3] = idx_cliques[i][k];
						    }
						    else if((l<j) & (k<m))
						    {
							    alpha[0] = idx_cliques[i][l];
							    alpha[1] = idx_cliques[i][j];
							    alpha[2] = idx_cliques[i][k];
							    alpha[3] = idx_cliques[i][m];
						    }

				            (*msubi)[count_const] = (size_clique_i + count_aux)*size_blk_i + size_clique_i + count_aux2;
				            (*msubj)[count_const] = map[alpha];
				            (*mcof)[count_const] = const_coef;
				            count_const = count_const + 1;
						}
			        }
				}
		    }
	    }

	    auto msubid = new_array_ptr<int, 1>(size_blk_i);
	    auto msubjd = new_array_ptr<int, 1>(size_blk_i);
	    auto mcofd  = new_array_ptr<double, 1>(size_blk_i);
	    for (int j = 0; j < size_blk[i]; j++)
	    {
			(*msubid)[j] = (j*size_blk_i)+j;
			(*msubjd)[j] = 0;
			(*mcofd)[j] = 1.0/(norm_C*norm_A[i]);
		}

	    Matrix::t A = Matrix::sparse(total_y,size_vec_blk_i, msubj, msubi, mcof);

	    if (i==0)
	    {
			Ax_b = Expr::mul(A, Expr::reshape(Expr::mul(S[i],-1),size_vec_blk_i));
		}
		else
		{
	        Ax_b = Expr::add(Ax_b, Expr::mul(A, Expr::reshape(Expr::mul(S[i],-1),size_vec_blk_i)));
	    }

	}
	Ax_b = Expr::sub(Ax_b,b);
	Constraint::t con = M->constraint(Ax_b, Domain::equalsTo(0.0));
	cout<<"size map: "<<map.size()<<endl;
	cout<<"Total_constraints: "<<b->numRows()<<endl;

    finish = std::chrono::high_resolution_clock::now();
    elapsed = finish - start;
    cout<<"Time creating constraints: "<<elapsed.count()<<endl;

    // Creating objective function

    Expression::t obj = Expr::constTerm(0);
    for (int i=0;i<total_cliques;i++)
    {
		int size_blk_i = size_blk[i];
	    auto msubid = new_array_ptr<int, 1>(size_blk_i);
	    auto msubjd = new_array_ptr<int, 1>(size_blk_i);
	    auto mcofd  = new_array_ptr<double, 1>(size_blk_i);
	    for (int j = 0; j < size_blk_i; j++)
	    {
			(*msubid)[j] = j;
			(*msubjd)[j] = j;
			(*mcofd)[j] = 1.0/(norm_C*norm_A[i]);
		}

		Matrix::t D_ones = Matrix::sparse(size_blk_i,size_blk_i, msubid, msubjd, mcofd);
		obj = Expr::add(obj, Expr::dot(S[i],D_ones));

	}

    auto finish_creating = std::chrono::high_resolution_clock::now();
	std::chrono::duration<double> elapsed_creating = finish_creating - start_creating;
    //Solve SDP
  	M->setLogHandler([=](const std::string & msg) { std::cout << msg << std::flush; } );
	M->objective(ObjectiveSense::Minimize, obj);
	M->setSolverParam("optimizerMaxTime", 10800);

	auto start_solving_SDP = std::chrono::high_resolution_clock::now();

	M->solve();
	finish = std::chrono::high_resolution_clock::now();
	elapsed = finish - start_solving_SDP;
	time_solving_SDP = elapsed.count();

	std::cout << std::fixed;
    std::cout << std::setprecision(4);
	cout<<"time creating problem: "<<elapsed_creating.count()<<endl;
	cout<<"time solving SDP: "<<elapsed.count()<<endl;
	cout<<"Obj_value_partial_relaxation: "<<M->primalObjValue()*norm_b*norm_C + obj_constant<<endl;
	return 0;
}

void read_matrix(const char *filename, Matrix::t& result)
{
	int row, col;
	int i = 0,j = 0;
    ifstream fin(filename);
	if (!fin.is_open())
	{
		cout << "Error opening file: "<<filename;
		exit(1);
	}

	string line, aux;
	// First line must have in the first two positions the number of rows and columns
	getline(fin, line);
	stringstream stream(line);
	getline(stream,aux,',');
	row = stoi(aux);
	getline(stream,aux,',');
	col = stoi(aux);

	auto msubi = new_array_ptr<int, 1>(row*col);
	auto msubj = new_array_ptr<int, 1>(row*col);
	auto mcof  = new_array_ptr<double, 1>(row*col);
	int aux2 = 0;
	while (getline(fin, line))
   	{
		stringstream stream(line);
		while(stream.good())
		{
			getline(stream,aux,',');
			(*msubi)[aux2] = i;
			(*msubj)[aux2] = j;
			(*mcof)[aux2] = stod(aux);
			j++;
			aux2 = aux2 + 1;
			if (j == col)
			{
				i = i+1;
				j = 0;
			}
		}
	}
	result = Matrix::sparse(row, col, msubi, msubj, mcof);
}

void read_matrix_sparse_format(const char *filename, Matrix::t& result)
{
	// reads symmetric  matrix in sparse format given as
	// n nnz
	// i_1 j_1 val_1
	// i_2 j_2 val_2
	// ...
	// i1_nnz j1_nnz val_nnz
	// where n is the dimension of the matrix, and nnz the non-zero elements.
	// It gives only the upper triangular values.

	int row, col;
	string val, i, j;
    ifstream fin(filename);
	if (!fin.is_open())
	{
		cout << "Error opening file: "<<filename;
		exit(1);
	}

	string line, aux;
	// First line must have the dimension of the square matrix and the number of non-zeros
	getline(fin, line);
	stringstream stream(line);
	getline(stream,aux,' ');
	row = stoi(aux);
	col = row;
	getline(stream,aux,' ');
	int nnz = stoi(aux);
	auto msubi = new_array_ptr<int, 1>(2*nnz);
	auto msubj = new_array_ptr<int, 1>(2*nnz);
	auto mcof  = new_array_ptr<double, 1>(2*nnz);
	int aux2 = 0;
	while(fin >> i >> j >> val)
	{
		(*msubi)[aux2] = stod(i)-1;
		(*msubj)[aux2] = stod(j)-1;
		(*msubi)[aux2+1] = stod(j)-1;
		(*msubj)[aux2+1] = stod(i)-1;
		(*mcof) [aux2] = stod(val);
		(*mcof) [aux2+1] = stod(val);
		aux2 = aux2+2;
	}

	result = Matrix::sparse(row, col, msubi, msubj, mcof);
}


static bool eigen_did_assert = false;
#define eigen_assert(X) if(!eigen_did_assert && !(X)){ std::cout << "### Assertion raised in " << __FILE__ << ":" << __LINE__ << ":\n" #X << "\n### The following would happen without assertions:\n"; eigen_did_assert = true;}

#include <iostream>
#include <Eigen/Eigen>

#ifndef M_PI
#define M_PI 3.1415926535897932384626433832795
#endif

using namespace Eigen;
using namespace std;

int main(int, char**)
{
  cout.precision(3);
  int data[] = {1,2,3,4,5,6,7,8,9};
Map<RowVectorXi> v(data,4);
cout << "The mapped vector v is: " << v << "\n";
new (&v) Map<RowVectorXi>(data+4,5);
cout << "Now v is: " << v << "\n";
  return 0;
}


//==================================================================================================
/*!

  Copyright 2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/function/scalar/predecessor.hpp>
#include <simd_test.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/constant/eps.hpp>
#include <boost/simd/constant/halfeps.hpp>
#include <boost/simd/constant/valmin.hpp>
#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/constant/bitincrement.hpp>
#include <boost/simd/constant/two.hpp>
#include <boost/simd/constant/four.hpp>
#include <boost/simd/constant/mthree.hpp>

STF_CASE_TPL (" predecessor real",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::predecessor;
  using r_t = decltype(predecessor(T()));

  STF_TYPE_IS(r_t, T);

  // specific values tests
#ifndef STF_NO_INVALIDS
  STF_EQUAL(predecessor(bs::Inf<T>()), bs::Valmax<r_t>());
  STF_IEEE_EQUAL(predecessor(bs::Minf<T>()), bs::Nan<r_t>());
  STF_IEEE_EQUAL(predecessor(bs::Nan<T>()), bs::Nan<r_t>());
#endif
  STF_EQUAL(predecessor(bs::Mone<T>()), bs::Mone<r_t>()-bs::Eps<r_t>());
  STF_EQUAL(predecessor(bs::One<T>()), bs::One<r_t>()-bs::Halfeps<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>()), bs::Minf<r_t>());
#if !defined(STF_NO_DENORMALS)
  STF_EQUAL(predecessor(bs::Zero<T>()), -bs::Bitincrement<T>());
#endif
} // end of test for floating_

STF_CASE_TPL (" predecessor ui ",  STF_UNSIGNED_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::predecessor;
  using r_t = decltype(predecessor(T()));

  // return type conformity test
  STF_TYPE_IS(r_t, T);

  // specific values tests
  STF_EQUAL(predecessor(bs::One<T>()), bs::Zero<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>()), bs::Valmin<r_t>());
  STF_EQUAL(predecessor(bs::Zero<T>()), bs::Zero<r_t>());
} // end of test for unsigned_int_

STF_CASE_TPL (" predecessor si",  STF_SIGNED_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;

  using bs::predecessor;

  using r_t = decltype(predecessor(T()));

  // return type conformity test
  STF_TYPE_IS(r_t, T);

  // specific values tests
  STF_EQUAL(predecessor(bs::Mone<T>()), -bs::Two<r_t>());
  STF_EQUAL(predecessor(bs::One<T>()), bs::Zero<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>()), bs::Valmin<r_t>());
  STF_EQUAL(predecessor(bs::Zero<T>()), bs::Mone<r_t>());
} // end of test for signed_int_

STF_CASE_TPL (" predecessor real 2",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::predecessor;

  using iT = bd::as_integer_t<T>;
  using r_t = decltype(predecessor(T(), iT()));

  // return type conformity test
  STF_TYPE_IS(r_t, T);

#ifndef STF_NO_INVALIDS
  STF_IEEE_EQUAL(predecessor(bs::Minf<T>(), bs::Two<iT>()), bs::Nan<r_t>());
  STF_IEEE_EQUAL(predecessor(bs::Nan<T>(), bs::Two<iT>()), bs::Nan<r_t>());
#endif
  STF_EQUAL(predecessor(bs::Mone<T>(), bs::Two<iT>()), bs::Mone<r_t>()-bs::Eps<r_t>()-bs::Eps<r_t>());
  STF_EQUAL(predecessor(bs::One<T>(), bs::Two<iT>()), bs::One<r_t>()-bs::Eps<r_t>());
  STF_IEEE_EQUAL(predecessor(bs::Valmin<T>(), bs::Two<iT>()), bs::Nan<r_t>());
  STF_IEEE_EQUAL(predecessor(bs::Valmin<T>(), bs::Four<iT>()), bs::Nan<r_t>());
#if !defined(STF_NO_DENORMALS)
  STF_EQUAL(predecessor(bs::Zero<T>(), bs::Two<iT>()), -bs::Bitincrement<r_t>()-bs::Bitincrement<r_t>());
#endif
}

STF_CASE_TPL (" predecessorui_2",  STF_UNSIGNED_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::predecessor;
  using iT = bd::as_integer_t<T>;
  using r_t = decltype(predecessor(T(), iT()));

  // return type conformity test
  STF_TYPE_IS(r_t, T);
  STF_EQUAL(predecessor(bs::Four<T>(), bs::Two<iT>()), bs::Two<r_t>());
  STF_EQUAL(predecessor(bs::One<T>(), bs::Two<iT>()), bs::Zero<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>(), bs::Two<iT>()), bs::Valmin<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>(), bs::Four<iT>()), bs::Valmin<r_t>());
}

STF_CASE_TPL (" predecessorsi_2",  STF_SIGNED_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::predecessor;
  using iT = bd::as_integer_t<T>;
  using r_t = decltype(predecessor(T(), iT()));

  // return type conformity test
  STF_TYPE_IS(r_t, T);
  STF_EQUAL(predecessor(bs::Mone<T>(), bs::Two<iT>()), bs::Mthree<r_t>());
  STF_EQUAL(predecessor(bs::One<T>(), bs::Two<iT>()), bs::Mone<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>(), bs::Two<iT>()), bs::Valmin<r_t>());
  STF_EQUAL(predecessor(bs::Valmin<T>(), bs::Four<iT>()), bs::Valmin<r_t>());
} // end of test for signed_int_


//==================================================================================================
/**
  EVE - Expressive Vector Engine
  Copyright : EVE Contributors & Maintainers
  SPDX-License-Identifier: MIT
**/
//==================================================================================================
#include "test.hpp"
#include <boost/math/special_functions/hermite.hpp>
#include <eve/function/hermite.hpp>
#include <eve/function/diff/hermite.hpp>

//==================================================================================================
//== Types tests
//==================================================================================================
EVE_TEST_TYPES( "Check return types of hermite on wide"
        , eve::test::simd::ieee_reals

        )
<typename T>(eve::as<T>)
{
  using v_t = eve::element_type_t<T>;
  using wi_t = eve::as_integer_t<T>;
  using i_t  = eve::as_integer_t<v_t>;
  TTS_EXPR_IS( eve::hermite(i_t(), T())  , T);
  TTS_EXPR_IS( eve::hermite(wi_t(), T())  , T);
  TTS_EXPR_IS( eve::hermite(i_t(), v_t())  , v_t);
  TTS_EXPR_IS( eve::hermite(wi_t(), v_t())  , T);

};

//==================================================================================================
//== hermite tests
//==================================================================================================
EVE_TEST( "Check behavior of hermite on wide"
        , eve::test::simd::ieee_reals
        , eve::test::generate(eve::test::between(-1.0, 1.0), eve::test::as_integer(eve::test::ramp(0)))
        )
  <typename T, typename I>(T const& a0,I const & i0)
{
 using v_t = eve::element_type_t<T>;
  auto eve__hermitev  =  [](auto n, auto x) { return eve::hermite(n, x); };
  for(unsigned int n=0; n < 5; ++n)
  {
    auto boost_hermite =  [&](auto i, auto) { return boost::math::hermite(n, a0.get(i)); };
    TTS_ULP_EQUAL(eve__hermitev(n, a0), T(boost_hermite), 16);
  }
  auto boost_hermitev =  [&](auto i, auto) { return boost::math::hermite(i0.get(i), a0.get(i)); };
  TTS_ULP_EQUAL(eve__hermitev(i0    , a0), T(boost_hermitev), 16);
  for(unsigned int j=0; j < eve::cardinal_v<T>; ++j)
  {
    auto boost_hermite2 =  [&](auto i, auto) { return boost::math::hermite(i0.get(i), a0.get(j)); };
    TTS_ULP_EQUAL(eve__hermitev(i0 , a0.get(j)), T(boost_hermite2), 32);
  }
  for(unsigned int j=0; j < eve::cardinal_v<T>; ++j)
  {
    for(unsigned int n=0; n < eve::cardinal_v<T>; ++n)
    {
      TTS_ULP_EQUAL(eve__hermitev(i0.get(j) , a0.get(n)), v_t(boost::math::hermite(i0.get(j), a0.get(n))), 32);
    }
  }
};

//==================================================================================================
//== hermite diff tests
//==================================================================================================
EVE_TEST( "Check behavior of diff hermite on wide"
        , eve::test::simd::ieee_reals
        , eve::test::generate(eve::test::between(-1.0, 1.0), eve::test::as_integer(eve::test::ramp(0)))
        )
  <typename T, typename I>(T const& a0,I const & i0)
{
  auto boost_hermderiv = [](auto n,  auto x){return 2*x*boost::math::hermite(n, x)-boost::math::hermite(n+1, x); };

  using v_t = eve::element_type_t<T>;
  auto eve__hermitev  =  [](auto n, auto x) { return eve::diff(eve::hermite)(n, x); };
  for(unsigned int n=0; n < 5; ++n)
  {
    auto boost_hermite =  [&](auto i, auto) { return boost_hermderiv(n, a0.get(i)); };
    TTS_ULP_EQUAL(eve__hermitev(n, a0), T(boost_hermite), 32);
  }
  auto boost_hermitev =  [&](auto i, auto) { return boost_hermderiv(i0.get(i), a0.get(i)); };
  TTS_ULP_EQUAL(eve__hermitev(i0    , a0), T(boost_hermitev), 32);
  for(unsigned int j=0; j < eve::cardinal_v<T>; ++j)
  {
    auto boost_hermite2 =  [&](auto i, auto) { return boost_hermderiv(i0.get(i), a0.get(j)); };
    TTS_ULP_EQUAL(eve__hermitev(i0 , a0.get(j)), T(boost_hermite2), 48);
  }
  for(unsigned int j=0; j < eve::cardinal_v<T>; ++j)
  {
    for(unsigned int n=0; n < eve::cardinal_v<T>; ++n)
    {
      TTS_ULP_EQUAL(eve__hermitev(i0.get(j) , a0.get(n)), v_t(boost_hermderiv(i0.get(j), a0.get(n))), 100);
    }
  }
};


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

#include <boost/property_tree/ptree.hpp>
#include "clotho/utility/clotho_strings.hpp"

template < class RealType >
struct normal_distribution_parameter {
    static constexpr RealType   DEFAULT_MEAN = 0.0;
    static constexpr RealType   DEFAULT_SIGMA = 1.0;

    RealType m_mean, m_sigma;

    normal_distribution_parameter( RealType m = DEFAULT_MEAN, RealType s = DEFAULT_SIGMA ):
        m_mean(m)
        , m_sigma(s)
    {}

    normal_distribution_parameter( boost::property_tree::ptree & config ) :
        m_mean( DEFAULT_MEAN )
        , m_sigma( DEFAULT_SIGMA )
    {
        m_mean = config.get< RealType >( MEAN_K, m_mean );
        m_sigma = config.get< RealType >( SIGMA_K, m_sigma );

        config.put( MEAN_K, m_mean );
        config.put( SIGMA_K, m_sigma );
    }

    void write_parameter( boost::property_tree::ptree & l ) {
        l.put( MEAN_K, m_mean );
        l.put( SIGMA_K, m_sigma );
    }

    virtual ~normal_distribution_parameter() {}
};

#endif  // NORMAL_DISTRIBUTION_PARAMETER_HPP_


//==================================================================================================
/*!

  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/function/scalar/sincospi.hpp>
#include <scalar_test.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/constant/mzero.hpp>
#include <boost/simd/function/sinpi.hpp>
#include <boost/simd/function/cospi.hpp>

STF_CASE_TPL (" sincospi",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;

  using bs::sincospi;
  T a[] = {bs::Zero<T>(), bs::One<T>(), T(120), T(180),
           T(90), bs::Inf<T>(), bs::Minf<T>(), bs::Nan<T>()};
  size_t N =  sizeof(a)/sizeof(T);

  STF_EXPR_IS( (sincospi(T()))
             , (std::pair<T,T>)
             );

  {
    for(size_t i=0; i < N; ++i)
    {
      std::pair<T,T> p = sincospi(a[i]);
      STF_IEEE_EQUAL(p.first,  bs::sinpi(a[i]));
      STF_IEEE_EQUAL(p.second, bs::cospi(a[i]));
      std::pair<T,T> q = bs::restricted_(sincospi)(a[i]);
      STF_IEEE_EQUAL(q.first,  bs::restricted_(bs::sinpi)(a[i]));
      STF_IEEE_EQUAL(q.second, bs::restricted_(bs::cospi)(a[i]));
    }
  }

}


#ifndef VOLINT_H
#define VOLINT_H

        /*******************************************************
        *                                                      *
        *  volInt.c                                            *
        *                                                      *
        *  This code computes volume integrals needed for      *
        *  determining mass properties of polyhedral bodies.   *
        *                                                      *
        *  For more information, see the accompanying README   *
        *  file, and the paper                                 *
        *                                                      *
        *  Brian Mirtich, "Fast and Accurate Computation of    *
        *  Polyhedral Mass Properties," journal of graphics    *
        *  tools, volume 1, number 1, 1996.                    *
        *                                                      *
        *  This source code is public domain, and may be used  *
        *  in any way, shape or form, free of charge.          *
        *                                                      *
        *  Copyright 1995 by Brian Mirtich                     *
        *                                                      *
        *  mirtich@cs.berkeley.edu                             *
        *  http://www.cs.berkeley.edu/~mirtich                 *
        *                                                      *
        *******************************************************/

/*
        Revision history

        26 Jan 1996     Program creation.

         3 Aug 1996     Corrected bug arising when polyhedron density
                        is not 1.0.  Changes confined to function main().
                        Thanks to Zoran Popovic for catching this one.

        27 May 1997     Corrected sign error in translation of inertia
                        product terms to center of mass frame.  Changes
                        confined to function main().  Thanks to
                        Chris Hecker.
*/

// Modified for tractor_converter

#include <boost/container_hash/hash.hpp>

#include <stdexcept>

#include <algorithm>
#include <cmath>
#include <utility>
#include <limits>
#include <vector>
#include <deque>
#include <map>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <functional>
#include <numeric>
#include <iterator>

namespace volInt{

namespace exception{
  struct negative_volume : public virtual std::runtime_error
  {
    using std::runtime_error::runtime_error;
  };
  struct zero_volume : public virtual std::runtime_error
  {
    using std::runtime_error::runtime_error;
  };
} // namespace exception

namespace invalid{
  const int vert_id =         -1;
  const int vertNorm_id =     -1;

  const int bodyColorOffset = -1;
  const int bodyColorShift =  -1;

  const int ref_vert_ind =    -1;

  const int wheel_id =        -1;
  const int weapon_id =       -1;
  const int wheel_weapon_id = -1;
} // namespace invalid

enum class rotation_axis : std::size_t {x = 0, y = 1, z = 2};

double degrees_to_radians(double degrees);

double sicher_angle_to_radians(int sicher_angle);
int radians_to_sicher_angle(double radians);

void rotate_point_by_axis(std::vector<double> &point,
                          double angle_sin,
                          double angle_cos,
                          rotation_axis axis);
void rotate_point_by_axis(std::vector<double> &point,
                          double angle,
                          rotation_axis axis);

std::vector<double> vector_scale(double norm, const std::vector<double> &vec);
void vector_scale_self(double norm, std::vector<double> &vec);

void vector_invert_self(std::vector<double> &vec);

std::vector<double> vector_plus(const std::vector<double> &first,
                                const std::vector<double> &second);
void vector_plus_self(std::vector<double> &first,
                      const std::vector<double> &second);
std::vector<double> vector_minus(const std::vector<double> &first,
                                 const std::vector<double> &second);
void vector_minus_self(std::vector<double> &first,
                       const std::vector<double> &second);

std::vector<double> vector_multiply(const std::vector<double> &first,
                                    const std::vector<double> &second);
void vector_multiply_self(std::vector<double> &first,
                          const std::vector<double> &second);
std::vector<double> vector_multiply(const std::vector<double> &vec,
                                    double num);
void vector_multiply_self(std::vector<double> &vec,
                          double num);

std::vector<double> vector_divide(const std::vector<double> &first,
                                  const std::vector<double> &second);
void vector_divide_self(std::vector<double> &first,
                        const std::vector<double> &second);
std::vector<double> vector_divide(const std::vector<double> &vec,
                                  double num);
void vector_divide_self(std::vector<double> &vec,
                        double num);

double vector_length(const std::vector<double> &vec);

double vector_length_between(
  const std::vector<double> &first,
  const std::vector<double> &second);

double vector_dot_product(
  const std::vector<double> &first,
  const std::vector<double> &second);

double vector_angle(
  const std::vector<double> &first,
  const std::vector<double> &second);

std::vector<double> vector_2d_divide(const std::vector<double> &vec,
                                     double num);

double vector_2d_length(const std::vector<double> &vec);

template<typename T>
std::vector<std::vector<T>> get_groups_of_connected_items(
  std::vector<T> orig_vec,
  std::function<bool(T first, T second)> check_connected_func)
{
  std::vector<std::vector<T>> groups;
  std::size_t orig_vec_size = orig_vec.size();
  groups.reserve(orig_vec_size);

  std::unordered_set<T> items_to_check(orig_vec.begin(), orig_vec.end());

  for(std::size_t cur_group_id = 0; !items_to_check.empty(); ++cur_group_id)
  {
    groups.push_back(std::vector<T>());
    groups[cur_group_id].reserve(orig_vec_size);

    T cur_item = *items_to_check.begin();
    items_to_check.erase(items_to_check.begin());

    std::unordered_set<T> connected_items_to_check({cur_item});
    connected_items_to_check.reserve(orig_vec_size);

    // Iterating over first element in group and all connected elements.
    while(!connected_items_to_check.empty())
    {
      T cur_item = *connected_items_to_check.begin();
      connected_items_to_check.erase(connected_items_to_check.begin());
      groups[cur_group_id].push_back(cur_item);

      std::unordered_set<T> new_connected_items;
      new_connected_items.reserve(orig_vec_size);

      for(auto cur_item_to_cmp : items_to_check)
      {
        if(check_connected_func(cur_item, cur_item_to_cmp))
        {
          new_connected_items.insert(cur_item_to_cmp);
        }
      }

      // Erasing all newly found connected items from items_to_check.
      for(auto new_connected_item : new_connected_items)
      {
        items_to_check.erase(items_to_check.find(new_connected_item));
      }

      connected_items_to_check.insert(new_connected_items.begin(),
                                      new_connected_items.end());
    }
  }

  groups.shrink_to_fit();

  return groups;
}

namespace calc_norms{
  unsigned long long int normal_to_key(const std::vector<double> &norm);
  std::vector<double> key_to_normal(unsigned long long int key);
} // namespace calc_norms

// ============================================================================
// Macros.
// ============================================================================

#define VOLINT_SQR(x)  ((x) * (x))
#define VOLINT_CUBE(x) ((x) * (x) * (x))

// ============================================================================
// Constants.
// ============================================================================

#define VOLINT_X 0
#define VOLINT_Y 1
#define VOLINT_Z 2

const double vector_scale_val = 1.0;

//// VANGERS SOURCE
//// How angle conversions work in Vangers code.
//#define M_PI 3.14159265358979323846
//
//Pi_len = 11
//const int Pi = 1 << Pi_len;
//2048
//
//#define GTOR(x) (double(x) * (M_PI / double(Pi)))
//#define RTOG(x) (round(x *   (double(Pi) / M_PI)))

#ifndef M_PI
  #define M_PI 3.14159265358979323846
#endif
const int sicher_angle_Pi = 2048; // "1 << Pi_len" where "Pi_len = 11".

const unsigned int min_float_precision = 3;
const double distinct_distance = 1.0 / std::pow(10, min_float_precision);
const double sqr_distinct_distance = VOLINT_SQR(distinct_distance);
const double density = 1.0;

const std::size_t axes_num = 3;
const std::size_t axes_2d_num = 2;

const std::vector<std::vector<std::size_t>> axes_by_plane =
  {
    {1, 2}, // x axis.
    {0, 2}, // y axis.
    {0, 1}, // z axis.
  };

const std::vector<std::vector<std::size_t>> axes_by_plane_continuous =
  {
    {1, 2}, // x axis.
    {2, 0}, // y axis.
    {0, 1}, // z axis.
  };

namespace color_ids {
  const unsigned int zero_reserved =    0;
  const unsigned int body =             1;
  const unsigned int max_colors_ids =   25;
  const unsigned int invalid_color_id = 1000003;
} // namespace color_ids

namespace calc_norms{
  const std::size_t expected_connected_polygons_per_vertex = 10;
  const std::size_t expected_connected_polygons_per_polygon = 10;

  const double to_integer_multiply = std::pow(10, min_float_precision);
  const unsigned long long int upper_bound =
    std::round(2 * vector_scale_val * to_integer_multiply);

  const unsigned long long int upper_bound_shift =
    static_cast<unsigned long long int>(std::log2(upper_bound)) + 1;

  const std::vector<unsigned long long int> to_key_shift =
    {
      0,
      upper_bound_shift,
      2 * upper_bound_shift,
    };

  const unsigned long long int key_to_normal_mask =
    (1 << upper_bound_shift) - 1;
} // namespace calc_norms

namespace generate_bound{
  typedef std::deque<std::size_t> layer_vert_inds;
  typedef std::map<std::size_t, layer_vert_inds> layers_inds_of_axis;
  typedef std::vector<layers_inds_of_axis> layers_inds_by_axis;

  const std::size_t expected_inter_verts_per_edge = 10;

  const std::size_t plane_extrs_num = 4;
  // Assumes that get_planes_4_extreme_points() generates points in this order.
  //   y
  // 3   2
  //       x
  // 0   1
  const std::vector<std::vector<std::size_t>> extr_lines =
    {
      {0, 1},
      {1, 2},
      {2, 3},
      {3, 0},
      {0, 1, 2, 3},
    };
  const std::size_t plane_middle_extr_num = extr_lines.size();

  const std::size_t end_z_layers_num_mechos = 2;
  const std::size_t end_z_layers_num_other = 3;

  enum class model_type{mechos, other};

  namespace model{

    const std::size_t verts_per_poly = 4;

    const std::vector<std::size_t> zero_reserved_face_inds = {4, 5, 6, 7};

    const std::size_t z_layers_num = 3;
    const std::size_t num_verts_per_z_layer = 9;
    const std::size_t num_verts = z_layers_num * num_verts_per_z_layer;
    // 6 sides * 4 polygons per side.
    const std::size_t num_faces = 6 * 4;

    //    y
    // e3 m2 e2
    // m3 m4 m1 x
    // e0 m0 e1
    // Converting to:
    //   y
    // 0 1 2
    // 3 4 5 x
    // 6 7 8
    const std::vector<std::size_t> extr_to_end =   {6, 8, 2, 0};
    const std::vector<std::size_t> middle_to_end = {7, 5, 1, 3, 4};

    const std::vector<std::vector<int>> face_ind_to_vert_inds =
      {
        // Getting top and low sides.
        // top
        // <-
        //  /\
        // 0 1 2
        // 3 4 5
        // 6 7 8
        {0, 3, 4, 1},
        {1, 4, 5, 2},
        {3, 6, 7, 4},
        {4, 7, 8, 5},
        // low
        // ->
        //  \/
        // 18 19 20
        // 21 22 23
        // 24 25 26
        {18, 19, 22, 21},
        {19, 20, 23, 22},
        {21, 22, 25, 24},
        {22, 23, 26, 25},

        // Getting front and back sides.
        // back
        // <-
        //  /\
        // 6  7  8
        // 15 16 17
        // 24 25 26
        {6,  15, 16, 7},
        {7,  16, 17, 8},
        {15, 24, 25, 16},
        {16, 25, 26, 17},
        // front
        // ->
        //  \/
        // 0  1  2
        // 9  10 11
        // 18 19 20
        {0,  1,  10, 9},
        {1,  2,  11, 10},
        {9,  10, 19, 18},
        {10, 11, 20, 19},

        // Getting left and right sides.
        // left
        // <-
        //  /\
        // 0  3  6
        // 9  12 15
        // 18 21 24
        {0,  9,  12, 3},
        {3,  12, 15, 6},
        {9,  18, 21, 12},
        {12, 21, 24, 15},
        // right
        // ->
        //  \/
        // 2  5  8
        // 11 14 17
        // 20 23 26
        {2,  5,  14, 11},
        {5,  8,  17, 14},
        {11, 14, 23, 20},
        {14, 17, 26, 23},
      };

    // low
    //        y
    //     18 19 20
    // -x  21 22 23  x
    //     24 25 26
    //       -y
    const std::vector<std::vector<std::size_t>> min_verts_to_adjust_by_wheel =
      {
        {18, 21, 24}, // -x
        {24, 25, 26}, // -y
      };
    const std::vector<std::vector<std::size_t>> max_verts_to_adjust_by_wheel =
      {
        {20, 23, 26}, // x
        {18, 19, 20}, // y
      };
    const std::unordered_map<std::size_t, std::vector<std::size_t>>
      min_layer_verts_to_center_by_extremes =
        {
          {22, {18, 20, 24, 26}},
          {19, {18, 20}},
          {23, {20, 26}},
          {25, {24, 26}},
          {21, {18, 24}},
        };
  } // namespace model
} // namespace generate_bound

// ============================================================================
// Data structures.
// ============================================================================

struct model_extreme_points
{

  model_extreme_points();
  model_extreme_points(const std::vector<double> &max,
                       const std::vector<double> &min);
  model_extreme_points(std::vector<double> &&max, std::vector<double> &&min);

  std::vector<double>       &max();
  const std::vector<double> &max() const;
  std::vector<double>       &min();
  const std::vector<double> &min() const;

  double xmax() const;
  double ymax() const;
  double zmax() const;

  double xmin() const;
  double ymin() const;
  double zmin() const;

  void set_xmax(double new_xmax);
  void set_ymax(double new_ymax);
  void set_zmax(double new_zmax);

  void set_xmin(double new_xmin);
  void set_ymin(double new_ymin);
  void set_zmin(double new_zmin);

  void get_most_extreme_cmp_cur(const model_extreme_points &other);
  void get_most_extreme_cmp_cur(const std::vector<double>  &point);
  void get_most_extreme(const std::vector<std::vector<double>> &points);

  std::vector<double> get_center();

  std::pair<std::vector<double>, std::vector<double>> extreme_points_pair;

};

struct model_offset
{

  model_offset();
  model_offset(const std::vector<double> &offset_point_arg);
  model_offset(std::vector<double>      &&offset_point_arg);

  double x_off() const;
  double y_off() const;
  double z_off() const;

  void set_x_off(double new_x_off);
  void set_y_off(double new_y_off);
  void set_z_off(double new_z_off);

  std::vector<double> offset_point;

};

typedef struct face
{
  face(int numVerts_arg);

  int numVerts;
  unsigned int color_id;
  int wheel_id;
  int weapon_id;
  std::vector<double> norm;
  double w;
  std::vector<int> verts;
  std::vector<int> vertNorms;
} FACE;

typedef struct polyhedron
{

  polyhedron();
  polyhedron(
    int numVerts_arg,
    int numVertNorms_arg,
    int numFaces_arg,
    int verts_per_poly_arg);

  void invertVertNorms();
  void reverse_polygons_orientation();

  std::vector<double> face_calc_normal(std::size_t face_ind);
  double face_calc_offset_w(           std::size_t face_ind);
  void faces_calc_params(); // Must be called again if model was moved.
  void faces_calc_params_inv_neg_vol();

  double get_vertex_angle(std::size_t face_ind, std::size_t vert_ind);

  void recalc_vertNorms(double max_smooth_angle);

  double check_volume();

  void get_extreme_points();

  std::vector<double> get_model_center();

  void move_model_to_point(const std::vector<double> &point);
  void move_model_to_point_inv_neg_vol(const std::vector<double> &point);

  void move_coord_system_to_point(const std::vector<double> &point);
  void move_coord_system_to_point_inv_neg_vol(
    const std::vector<double> &point);
  void move_coord_system_to_center();

  void rotate_by_axis(double angle, rotation_axis axis);

  void set_color_id(unsigned int new_color_id,
                    int new_wheel_id =  invalid::wheel_id,
                    int new_weapon_id = invalid::weapon_id);

  bool find_ref_points();

  void calculate_rmax();
  void calculate_c3d_properties();

  std::vector<std::vector<std::vector<double>>>
    get_planes_4_extreme_points() const;
  static std::vector<std::unordered_map<std::size_t, double>>
    get_verts_plane_lengths_rel_points(
      std::size_t perpendicular_axis,
      const std::vector<std::vector<double>> &verts_arg,
      const std::vector<std::vector<double>> &points_2d,
      const generate_bound::layer_vert_inds &vert_inds);
  static generate_bound::layer_vert_inds get_min_length_layer_points(
    const std::vector<std::unordered_map<std::size_t, double>> &
      verts_plane_lengths_rel_points);
  static double get_plane_area_from_points(
    std::size_t perpendicular_axis,
    const std::vector<std::vector<double>> &verts_arg,
    const generate_bound::layer_vert_inds &vert_inds);
  static std::vector<std::vector<double>> get_extr_middle_points(
    std::size_t perpendicular_axis,
    const std::vector<std::vector<double>> &verts_arg,
    const generate_bound::layer_vert_inds &layer_extrs);
  polyhedron extr_inds_to_bound(
    const std::vector<std::vector<double>> &verts_arg,
    const generate_bound::layers_inds_of_axis &extr_inds,
    const generate_bound::layers_inds_of_axis &middle_inds,
    generate_bound::model_type type,
    const model_extreme_points *wheel_params_extremes = nullptr) const;
  polyhedron generate_bound_model(
    const generate_bound::model_type type,
    const std::size_t layers_num,
    const double area_threshold_multiplier,
    const model_extreme_points *wheel_params_extremes = nullptr) const;

  std::pair<std::vector<double>, std::vector<double>> &extreme_points_pair();
  const std::pair<std::vector<double>, std::vector<double>> &
    extreme_points_pair() const;
  std::vector<double>       &max_point();
  const std::vector<double> &max_point() const;
  std::vector<double>       &min_point();
  const std::vector<double> &min_point() const;

  double xmax() const;
  double ymax() const;
  double zmax() const;

  double xmin() const;
  double ymin() const;
  double zmin() const;

  void set_xmax(double new_xmax);
  void set_ymax(double new_ymax);
  void set_zmax(double new_zmax);

  void set_xmin(double new_xmin);
  void set_ymin(double new_ymin);
  void set_zmin(double new_zmin);

  std::vector<double>       &offset_point();
  const std::vector<double> &offset_point() const;

  double x_off() const;
  double y_off() const;
  double z_off() const;

  void set_x_off(double new_x_off);
  void set_y_off(double new_y_off);
  void set_z_off(double new_z_off);

  int numVerts, numVertNorms, numFaces, numVertTotal, numVertsPerPoly;
  model_extreme_points extreme_points;
  model_offset offset;
  double rmax;
  double volume;
  std::vector<double> rcm;
  std::vector<std::vector<double>> J;
  int bodyColorOffset, bodyColorShift;
  std::vector<std::vector<double>> verts;
  std::vector<std::vector<double>> vertNorms;
  std::vector<face> faces;

  std::pair<int, int>        ref_vert_one_ind;
  const std::vector<double> *ref_vert_one;
  std::pair<int, int>        ref_vert_two_ind;
  const std::vector<double> *ref_vert_two;
  std::pair<int, int>        ref_vert_three_ind;
  const std::vector<double> *ref_vert_three;

  std::vector<double> ref_vert_two_rel_to_one;
  std::vector<double> ref_vert_three_rel_to_one;

  double ref_angle;

  std::unordered_set<std::size_t> wheels;
  std::unordered_set<std::size_t> wheels_steer;
  std::unordered_set<std::size_t> wheels_non_steer;
  std::unordered_set<std::size_t> wheels_ghost;
  std::unordered_set<std::size_t> wheels_non_ghost;

  // Holds wheel id in case model itself is a wheel.
  // Otherwise value is invalid::wheel_id, which is -1.
  int wheel_id;

  // Used only when converting from *.obj to *.m3d/*.a3d.
  std::string wavefront_obj_path;

  // Used only when converting from *.m3d/*.a3d to *.obj.
  bool volume_overwritten;
  bool rcm_overwritten;
  bool J_overwritten;
} POLYHEDRON;

// ============================================================================
// Globals.
// ============================================================================

static int A; // alpha
static int B; // beta
static int C; // gamma

// Projection integrals.
static double P1, Pa, Pb, Paa, Pab, Pbb, Paaa, Paab, Pabb, Pbbb;

// Face integrals.
static double Fa, Fb, Fc, Faa, Fbb, Fcc, Faaa, Fbbb, Fccc, Faab, Fbbc, Fcca;

// Volume integrals.
static double T0, T1[3], T2[3], TP[3];

// ============================================================================
// Compute mass properties.
// ============================================================================

// Compute various integrations over projection of face.
void compProjectionIntegrals(POLYHEDRON *p, FACE *f);

void compFaceIntegrals(FACE *f);

void compVolumeIntegrals(POLYHEDRON *p);

} // namespace volInt

#endif // VOLINT_H


/*
	Authors: Darya Filippova, Geet Duggal, Rob Patro
	dfilippo | geet | robp @cs.cmu.edu
        See LICENSE.txt included with this distribution.
*/

#include <iomanip>
#include <limits>

#include <fstream>

#include <boost/range/adaptors.hpp>
#include <boost/range/irange.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/median.hpp>

#include "ArmatusParams.hpp"

ArmatusParams::ArmatusParams(std::shared_ptr<SparseMatrix> Ap, double gammap, size_t Kp, int minMeanSamples) :
    A(Ap),
    sums(ArmatusParams::SymmetricMatrix(Ap->size1(), Ap->size2())),
    n(Ap->size1()),
    K(Kp),
    minMeanSamples(minMeanSamples),
    gamma(gammap),
    mu(std::vector<double>(Ap->size1()+1))
{
 computeSumMuSigma_();
}

void ArmatusParams::computeSumMuSigma_() {
	using namespace boost::accumulators;
	using namespace boost::range;
	// Vector to hold accumulators that will compute the mean
	// for domains of each size.
	std::vector<accumulator_set<double,stats<tag::mean, tag::count>>> acc(n+1);
	//std::vector<accumulator_set<double,stats<tag::median(with_p_square_quantile), tag::count>>> acc(n+1);

	// A reference will be easier to work with here
	SparseMatrix& M = *A;

	for (size_t i : boost::irange(size_t{0}, n)) {
		sums(i, i) = M(i, i);
	}

	for (size_t i : boost::irange(size_t{1}, n)) {
		std::vector<double> columnSums(i+1);
		columnSums[i] = M(i, i);
		for (size_t j : boost::adaptors::reverse(boost::irange(size_t{0}, i))) {
			columnSums[j] = columnSums[j+1] + M(j, i);
			sums(j, i) = sums(j, i-1) + columnSums[j];
			int d_i = d(j,i);
			assert(d_i >= 0);
			double s = sums(j, i) / std::pow(static_cast<double>(d_i), gamma);
		    acc[d_i](s);
		}
	}

	for (size_t i : boost::irange(size_t{0}, n+1)) {
		mu[i] = mean(acc[i]);
        //mu[i] = median(acc[i]);
		// Require at least 100 samples to compute a Z-score
		if (boost::accumulators::count(acc[i]) < minMeanSamples) {
			mu[i] = std::numeric_limits<double>::max();
		}
	}
}


//==================================================================================================
/**
  EVE - Expressive Vector Engine
  Copyright : EVE Contributors & Maintainers
  SPDX-License-Identifier: MIT
**/
//==================================================================================================
#include "test.hpp"
#include <eve/module/polynomial.hpp>
#include <boost/math/special_functions/chebyshev.hpp>

// //==================================================================================================
// //== Types tests
// //==================================================================================================
// EVE_TEST_TYPES( "Check return types of tchebytchev on wide"
//         , eve::test::simd::ieee_reals

//         )
// <typename T>(eve::as<T>)
// {
//   using v_t = eve::element_type_t<T>;
//   using wi_t = eve::as_integer_t<T>;
//   using i_t  = eve::as_integer_t<v_t>;
//   TTS_EXPR_IS( eve::tchebytchev(i_t(), T())  , T);
//   TTS_EXPR_IS( eve::tchebytchev(wi_t(), T())  , T);
//   TTS_EXPR_IS( eve::tchebytchev(i_t(), v_t())  , v_t);
//   TTS_EXPR_IS( eve::tchebytchev(wi_t(), v_t())  , T);

//   using eve::kind_1;
//   TTS_EXPR_IS( kind_1(eve::tchebytchev)(i_t(), T())  , T);
//   TTS_EXPR_IS( kind_1(eve::tchebytchev)(wi_t(), T())  , T);
//   TTS_EXPR_IS( kind_1(eve::tchebytchev)(i_t(), v_t())  , v_t);
//   TTS_EXPR_IS( kind_1(eve::tchebytchev)(wi_t(), v_t())  , T);

//   using eve::kind_2;
//   TTS_EXPR_IS( kind_2(eve::tchebytchev)(i_t(), T())  , T);
//   TTS_EXPR_IS( kind_2(eve::tchebytchev)(wi_t(), T())  , T);
//   TTS_EXPR_IS( kind_2(eve::tchebytchev)(i_t(), v_t())  , v_t);
//   TTS_EXPR_IS( kind_2(eve::tchebytchev)(wi_t(), v_t())  , T);
// };

// //==================================================================================================
// //== tchebytchev tests
// //==================================================================================================
// EVE_TEST( "Check behavior of tchebytchev on wide"
//         , eve::test::simd::ieee_reals
//         , eve::test::generate(eve::test::between(-1.0, 1.0), eve::test::between(1.0, 10.0), eve::test::as_integer(eve::test::ramp(0)))
//         )
//   <typename T, typename I>(T const& a0, T const& a1, I const & i0)
// {
// // using v_t = eve::element_type_t<T>;
//   auto eve__tchebytchev  =  [](uint32_t n, auto x) { return eve::tchebytchev(n, x); };
//   for(unsigned int n=0; n < 6; ++n)
//   {
//     auto boost_tchebytchev =  [&](auto i, auto e) { return boost::math::chebyshev_t((unsigned int)i, e); };
//     TTS_ULP_EQUAL(eve__tchebytchev(n, a0), map(boost_tchebytchev, n, a0), 32);
//     TTS_ULP_EQUAL(eve__tchebytchev(n, a1), map(boost_tchebytchev, n, a1), 32);
//   }
//   auto boost_tchebytchev =  [&](auto i, auto e) { return boost::math::chebyshev_t(i, e); };
//   TTS_ULP_EQUAL(eve::tchebytchev(i0    , a0), map(boost_tchebytchev, i0, a0), 64);
//   TTS_ULP_EQUAL(eve::tchebytchev(i0    , a1), map(boost_tchebytchev, i0, a1), 64);
// };

// EVE_TEST( "Check behavior of kind_2(tchebytchev) on wide"
//         , eve::test::simd::ieee_reals
//         , eve::test::generate(eve::test::between(-1.0, 1.0), eve::test::between(1.0, 10.0), eve::test::as_integer(eve::test::ramp(0)))
//         )
//   <typename T, typename I>(T const& a0, T const& a1, I const & i0)
// {
//   using eve::kind_2;
//   auto eve__tchebytchev  =  [](uint32_t n, auto x) { return eve::kind_2(eve::tchebytchev)(n, x); };
//   for(unsigned int n=0; n < 6; ++n)
//   {
//     auto boost_tchebytchev_u =  [&](auto i, auto e) { return boost::math::chebyshev_u((unsigned int)i, e); };
//     TTS_ULP_EQUAL(eve__tchebytchev(n, a0), map(boost_tchebytchev_u, n, a0), 1000);
//     TTS_ULP_EQUAL(eve__tchebytchev(n, a1), map(boost_tchebytchev_u, n, a1), 1000);
//   }
//   auto boost_tchebytchev_u =  [&](auto i, auto e) { return boost::math::chebyshev_u(i, e); };
//   TTS_ULP_EQUAL(kind_2(eve::tchebytchev)(i0    , a0), map(boost_tchebytchev_u, i0, a0), 64);
//   TTS_ULP_EQUAL(kind_2(eve::tchebytchev)(i0    , a1), map(boost_tchebytchev_u, i0, a1), 64);
// };

// EVE_TEST( "Check behavior of successor(tchebytchev)"
//         , eve::test::simd::ieee_reals
//         , eve::test::generate(eve::test::between(-1.0, 1.0), eve::test::between(1.0, 10.0))
//         )
//   <typename T>(T const& a0, T const&)
// {
//   auto t3 = eve::tchebytchev(3, a0);
//   auto t4 = eve::tchebytchev(4, a0);
//   auto t5 = eve::tchebytchev(5, a0);
//   TTS_ULP_EQUAL(eve::successor(eve::tchebytchev)(a0, t4, t3), t5, 64);
//   using eve::kind_2;
//   auto u3 = kind_2(eve::tchebytchev)(3, a0);
//   auto u4 = kind_2(eve::tchebytchev)(4, a0);
//   auto u5 = kind_2(eve::tchebytchev)(5, a0);
//  TTS_ULP_EQUAL(eve::successor(eve::tchebytchev)(a0, u4, u3), u5, 100);
// };

//==================================================================================================
//== tchebytchev diff tests
//==================================================================================================
EVE_TEST( "Check behavior of diff tchebytchev on wide"
        , eve::test::simd::ieee_reals
        , eve::test::generate(eve::test::between(-1.0, 1.0))
        )
  <typename T>(T const& a0)
{

 for(int i=1; i < 10 ; ++i)
 {
   auto dt = eve::diff(eve::tchebytchev)(i, a0);
   auto u =  eve::kind_2(eve::tchebytchev)(i-1, a0);
   auto bdt1 = [&i](auto e){return boost::math::chebyshev_t_prime(i, e); };
   TTS_ULP_EQUAL(dt, u*i, 10);
   TTS_ULP_EQUAL(dt, eve::detail::map(bdt1, a0), 1000);
 }

 // there is no implementation in boost of chebyshev_u_prime so we will defer the test to the availability of
 // complex (vand) or ad (der) to test the derivative of second kind polynomials.
//  for(int i=1; i < 10 ; ++i)
//  {
//    auto dt = eve::diff(eve::tchebytchev)(i, a0);
//    auto bdt2 = [&i](auto e){return boost::math::chebyshev_u_prime(i, e); };
//    TTS_ULP_EQUAL(dt, eve::detail::map(bdt2, a0), 1000);
//  }
};