#include <scitbx/array_family/boost_python/flex_fwd.h>

#include <boost/python/module.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>
#include <scitbx/array_family/boost_python/utils.h>
#include <scitbx/array_family/versa.h>
#include <scitbx/array_family/accessors/c_grid.h>
#include <string>

#include <fftw3.h>

namespace {

  namespace af = scitbx::af;

  void
  complex_to_complex_in_place(
    af::ref<std::complex<double> > const& data,
    int exp_sign)
  {
    SCITBX_ASSERT(exp_sign == FFTW_FORWARD || exp_sign == FFTW_BACKWARD);
    int n = static_cast<int>(data.size());
    fftw_complex *in = reinterpret_cast<fftw_complex*>(data.begin());
    fftw_plan p = fftw_plan_dft_1d(
      n, in, in, exp_sign, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
  }

  void
  complex_to_complex_3d_in_place(
    af::ref<std::complex<double>, af::c_grid<3> > const& data,
    int exp_sign)
  {
    SCITBX_ASSERT(exp_sign == FFTW_FORWARD || exp_sign == FFTW_BACKWARD);
    int nx = static_cast<int>(data.accessor()[0]);
    int ny = static_cast<int>(data.accessor()[1]);
    int nz = static_cast<int>(data.accessor()[2]);
    fftw_complex *in = reinterpret_cast<fftw_complex*>(data.begin());
    fftw_plan p = fftw_plan_dft_3d(
      nx, ny, nz, in, in, exp_sign, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
  }

  af::versa<std::complex<double>, af::flex_grid<> >
  real_to_complex_in_place(
    af::versa<double, af::flex_grid<> >& data)
  {
    af::boost_python::assert_0_based_1d(data.accessor());
    int m = static_cast<int>(data.accessor().all()[0]);
    int n = static_cast<int>(data.accessor().focus()[0]);
    SCITBX_ASSERT(m == 2*(n/2+1));
    double* in = data.begin();
    fftw_complex *out = reinterpret_cast<fftw_complex*>(in);
    fftw_plan p = fftw_plan_dft_r2c_1d(
      n, in, out, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    return af::versa<std::complex<double>, af::flex_grid<> >(
      data.handle(), af::flex_grid<>(m/2));
  }

  af::versa<double, af::flex_grid<> >
  complex_to_real_in_place(
    af::versa<std::complex<double>, af::flex_grid<> >& data,
    int n)
  {
    af::boost_python::assert_0_based_1d(data.accessor());
    SCITBX_ASSERT(!data.accessor().is_padded());
    int m = static_cast<int>(data.accessor().all()[0]) * 2;
    SCITBX_ASSERT(m == 2*(n/2+1));
    fftw_complex *in = reinterpret_cast<fftw_complex*>(data.begin());
    double* out = reinterpret_cast<double*>(in);
    fftw_plan p = fftw_plan_dft_c2r_1d(
      n, in, out, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    return af::versa<double, af::flex_grid<> >(
      data.handle(), af::flex_grid<>(m).set_focus(n));
  }

  af::versa<std::complex<double>, af::flex_grid<> >
  real_to_complex_3d_in_place(
    af::versa<double, af::flex_grid<> >& data)
  {
    af::boost_python::assert_0_based_3d(data.accessor());
    int mx = static_cast<int>(data.accessor().all()[0]);
    int my = static_cast<int>(data.accessor().all()[1]);
    int mz = static_cast<int>(data.accessor().all()[2]);
    int nx = static_cast<int>(data.accessor().focus()[0]);
    int ny = static_cast<int>(data.accessor().focus()[1]);
    int nz = static_cast<int>(data.accessor().focus()[2]);
    SCITBX_ASSERT(mx == nx);
    SCITBX_ASSERT(my == ny);
    SCITBX_ASSERT(mz == 2*(nz/2+1));
    double* in = data.begin();
    fftw_complex *out = reinterpret_cast<fftw_complex*>(in);
    fftw_plan p = fftw_plan_dft_r2c_3d(
      nx, ny, nz, in, out, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    return af::versa<std::complex<double>, af::flex_grid<> >(
      data.handle(),
      af::flex_grid<>((af::adapt(af::tiny<int, 3>(mx,my,mz/2)))));
  }

  af::versa<double, af::flex_grid<> >
  complex_to_real_3d_in_place(
    af::versa<std::complex<double>, af::flex_grid<> >& data,
    af::tiny<int, 3> const& n)
  {
    af::boost_python::assert_0_based_3d(data.accessor());
    SCITBX_ASSERT(!data.accessor().is_padded());
    int mx = static_cast<int>(data.accessor().all()[0]);
    int my = static_cast<int>(data.accessor().all()[1]);
    int mz = static_cast<int>(data.accessor().all()[2]) * 2;
    int nx = n[0];
    int ny = n[1];
    int nz = n[2];
    SCITBX_ASSERT(mx == nx);
    SCITBX_ASSERT(my == ny);
    SCITBX_ASSERT(mz == 2*(nz/2+1));
    fftw_complex *in = reinterpret_cast<fftw_complex*>(data.begin());
    double* out = reinterpret_cast<double*>(in);
    fftw_plan p = fftw_plan_dft_c2r_3d(
      nx, ny, nz, in, out, FFTW_ESTIMATE);
    fftw_execute(p);
    fftw_destroy_plan(p);
    return af::versa<double, af::flex_grid<> >(
      data.handle(),
      af::flex_grid<>((af::adapt(af::tiny<int, 3>(nx,ny,mz))))
        .set_focus(af::adapt(n)));
  }

  void
  wrap_fftw3()
  {
    using namespace boost::python;
    scope().attr("fftw_version") = std::string(fftw_version);
    def("complex_to_complex_in_place", complex_to_complex_in_place, (
      arg("data"), arg("exp_sign")));
    def("complex_to_complex_3d_in_place", complex_to_complex_3d_in_place, (
      arg("data"), arg("exp_sign")));
    def("real_to_complex_in_place", real_to_complex_in_place, (
      arg("data")));
    def("complex_to_real_in_place", complex_to_real_in_place, (
      arg("data"), arg("n")));
    def("real_to_complex_3d_in_place", real_to_complex_3d_in_place, (
      arg("data")));
    def("complex_to_real_3d_in_place", complex_to_real_3d_in_place, (
      arg("data"), arg("n")));
  }

} // namespace <anonymous>

BOOST_PYTHON_MODULE(fftw3tbx_ext)
{
  wrap_fftw3();
}


#pragma once
#include <vizkit3d_debug_drawings/commands/DrawCommand.hpp>
#include <string>
#include <Eigen/Core>
#include <vizkit3d_debug_drawings/commands/BoostSerializationHelpers.hpp>

namespace osg
{
    class Node;
}

namespace vizkit3dDebugDrawings
{
class DrawAxesCommand : public DrawCommand
{
    friend class boost::serialization::access;
    template<class Archive>
    void serialize(Archive & ar, const unsigned int version)
    {
        // serialize base class information
        ar & boost::serialization::base_object<DrawCommand>(*this);
        ar & position;
        ar & orientation;
        ar & scale;
    }

public:
    DrawAxesCommand();

    DrawAxesCommand(const std::string& drawingChannel, const Eigen::Vector3d& position,
                     const Eigen::Quaterniond& orientation, const Eigen::Vector3d& scale);

    virtual osg::ref_ptr<osgviz::Object> createPrimitive() const;

    virtual DrawAxesCommand* clone() const;

private:
    Eigen::Vector3d position;
    Eigen::Quaterniond orientation;
    Eigen::Vector3d scale;

};
}


//==============================================================================
//         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 NT2_EULER_FUNCTIONS_SCALAR_GAMMAINCINV_HPP_INCLUDED
#define NT2_EULER_FUNCTIONS_SCALAR_GAMMAINCINV_HPP_INCLUDED

#include <nt2/euler/functions/gammaincinv.hpp>
#include <nt2/include/functions/scalar/tofloat.hpp>
#include <nt2/include/functions/scalar/is_equal.hpp>
#include <nt2/include/constants/inf.hpp>
#include <nt2/include/constants/one.hpp>
#include <nt2/sdk/error/policies.hpp>
#include <boost/math/special_functions/gamma.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( gammaincinv_, tag::cpu_
                              , (A0)(A1)
                            , (scalar_< arithmetic_<A0> >)
                              (scalar_< arithmetic_<A1> >)
                            )
  {

    typedef typename boost::dispatch::meta::as_floating<A0, A1>::type result_type;

    NT2_FUNCTOR_CALL(2)
    {
      return nt2::gammaincinv(nt2::tofloat(a0), nt2::tofloat(a1));
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( gammaincinv_, tag::cpu_
                              , (A0)(A1)
                            , (scalar_< floating_<A0> >)
                              (scalar_< floating_<A1> >)
                            )
  {

    typedef A0 result_type;

    BOOST_FORCEINLINE result_type operator()(const A0& x, const A1& a) const
    {
      if(eq(x, nt2::One<A0>())) return nt2::Inf<A0>();
      return boost::math::gamma_p_inv(a, x, nt2_policy());
    }
  };
} }

#endif


#define ARMA_DONT_PRINT_ERRORS

#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <armadillo>

namespace py = pybind11;

using namespace arma;

typedef py::array_t<double, py::array::f_style | py::array::forcecast> array_tf;
typedef py::array_t<double, py::array::c_style | py::array::forcecast> array_tc;

cube array_to_cube(array_tf m) {

    py::buffer_info _m_buff = m.request();
    int n_rows = _m_buff.shape[0];
    int n_cols = _m_buff.shape[1];
    int n_slices = _m_buff.shape[2];

    cube _m_arma((double *)_m_buff.ptr, n_rows, n_cols, n_slices);

    return _m_arma;
}

mat array_to_mat(array_tf m) {

    py::buffer_info _m_buff = m.request();
    int n_rows = _m_buff.shape[0];
    int n_cols = _m_buff.shape[1];

    mat _m_arma((double *)_m_buff.ptr, n_rows, n_cols);

    return _m_arma;
}

vec array_to_vec(array_tf m) {

    py::buffer_info _m_buff = m.request();
    int n_rows = _m_buff.shape[0];

    vec _m_vec((double *)_m_buff.ptr, n_rows);

    return _m_vec;
}

array_tf cube_to_array(cube m) {

    auto _m_array = array_tf({m.n_rows, m.n_cols, m.n_slices});

    py::buffer_info _m_buff = _m_array.request();
    std::memcpy(_m_buff.ptr, m.memptr(), sizeof(double) * m.n_rows * m.n_cols * m.n_slices);

    return _m_array;
}

array_tf mat_to_array(mat m) {

    auto _m_array = array_tf({m.n_rows, m.n_cols});

    py::buffer_info _m_buff = _m_array.request();
    std::memcpy(_m_buff.ptr, m.memptr(), sizeof(double) * m.n_rows * m.n_cols);

    return _m_array;
}

array_tf vec_to_array(vec m) {

    auto _m_array = array_tf({m.n_rows});

    py::buffer_info _m_buff = _m_array.request();
    std::memcpy(_m_buff.ptr, m.memptr(), sizeof(double) * m.n_rows);

    return _m_array;
}

py::tuple policy_divergence(array_tf _p_K, array_tf _p_kff, array_tf _p_sigma_ctl,
                            array_tf _q_K, array_tf _q_kff, array_tf _q_sigma_ctl,
                            array_tf _mu_x, array_tf _sigma_x,
                            int dm_state, int dm_act, int nb_steps) {

    cube p_K = array_to_cube(_p_K);
    mat p_kff = array_to_mat(_p_kff);
    cube p_sigma_ctl = array_to_cube(_p_sigma_ctl);

    cube q_K = array_to_cube(_q_K);
    mat q_kff = array_to_mat(_q_kff);
    cube q_sigma_ctl = array_to_cube(_q_sigma_ctl);

    mat mu_x  = array_to_mat(_mu_x);
    cube sigma_x = array_to_cube(_sigma_x);

    vec kl(nb_steps);

    for(int i = 0; i < nb_steps; i++) {
        mat q_lambda_ctl = inv_sympd(q_sigma_ctl.slice(i));

        mat diff_K = (q_K.slice(i) - p_K.slice(i)).t() * q_lambda_ctl * (q_K.slice(i) - p_K.slice(i));
        mat diff_crs = (q_K.slice(i) - p_K.slice(i)).t() * q_lambda_ctl * (- q_kff.col(i) + p_kff.col(i));
        mat diff_kff = (- q_kff.col(i) + p_kff.col(i)).t() * q_lambda_ctl * (- q_kff.col(i) + p_kff.col(i));

        kl(i) = as_scalar(0.5 * log( det(q_sigma_ctl.slice(i)) / det(p_sigma_ctl.slice(i)) )
		                  + 0.5 * trace(q_lambda_ctl * p_sigma_ctl.slice(i))
		                  - 0.5 * dm_act
		                  + 0.5 * trace(diff_K * sigma_x.slice(i))
		                  + 0.5 * mu_x.col(i).t() * diff_K * mu_x.col(i)
		                  - mu_x.col(i).t() * diff_crs
		                  + 0.5 * diff_kff);
    }

    array_tf _kl = vec_to_array(kl);

    py::tuple output = py::make_tuple(_kl);
    return output;
}

py::tuple gaussian_divergence(array_tf _mu_p, array_tf _sigma_p,
                              array_tf _mu_q, array_tf _sigma_q,
                              int dm_state, int nb_steps) {

    mat mu_p = array_to_mat(_mu_p);
    cube sigma_p = array_to_cube(_sigma_p);

    mat mu_q = array_to_mat(_mu_q);
    cube sigma_q = array_to_cube(_sigma_q);

    vec kl(nb_steps);

    for(int i = 0; i < nb_steps; i++) {
        mat lambda_q = inv_sympd(sigma_q.slice(i));

        vec diff = mu_q.col(i) - mu_p.col(i);
        double quad = as_scalar(diff.t() * lambda_q * diff);
        double _trace = as_scalar(trace(lambda_q * sigma_p.slice(i)));
        double log_det = as_scalar(log( det(sigma_q.slice(i)) / det(sigma_p.slice(i)) ));

        kl(i) = 0.5 * (_trace + quad + log_det - dm_state);
    }

    array_tf _kl = vec_to_array(kl);

    py::tuple output = py::make_tuple(_kl);
    return output;
}

py::tuple gaussian_interp_w2(array_tf _mu_q, array_tf _sigma_q,
                             array_tf _mu_p, array_tf _sigma_p,
                             double alpha, int dim, int nb_steps) {

    mat mu_q = array_to_mat(_mu_q);
    cube sigma_q = array_to_cube(_sigma_q);

    mat mu_p = array_to_mat(_mu_p);
    cube sigma_p = array_to_cube(_sigma_p);

    mat mu(dim, nb_steps);
    cube sigma(dim, dim, nb_steps);

    for(int i = 0; i < nb_steps; i++) {
        mu.col(i) = (1. - alpha) * mu_q.col(i) + alpha * mu_p.col(i);

        mat chol_sigma_q = real(sqrtmat(sigma_q.slice(i)));
        mat inv_chol_sigma_q = inv_sympd(chol_sigma_q);
        mat _sigma = (1. - alpha) * sigma_q.slice(i)
                     + alpha * real(sqrtmat(chol_sigma_q * sigma_p.slice(i) * chol_sigma_q));

        sigma.slice(i) = inv_chol_sigma_q * _sigma * _sigma * inv_chol_sigma_q;
    }

    // transform outputs to numpy
    array_tf _mu = mat_to_array(mu);
    array_tf _sigma = cube_to_array(sigma);

    py::tuple output = py::make_tuple(_mu, _sigma);
    return output;
}

py::tuple gaussian_interp_kl(array_tf _mu_q, array_tf _sigma_q,
                             array_tf _mu_p, array_tf _sigma_p,
                             double alpha, int dim, int nb_steps) {

    mat mu_q = array_to_mat(_mu_q);
    cube sigma_q = array_to_cube(_sigma_q);

    mat mu_p = array_to_mat(_mu_p);
    cube sigma_p = array_to_cube(_sigma_p);

    mat mu(dim, nb_steps);
    cube sigma(dim, dim, nb_steps);

    for(int i = 0; i < nb_steps; i++) {
        mat lambda_q = inv_sympd(sigma_q.slice(i));
        mat lambda_p = inv_sympd(sigma_p.slice(i));

        sigma.slice(i) = inv_sympd(alpha * lambda_p + (1. - alpha) * lambda_q);
        mu.col(i) = sigma.slice(i) * (alpha * lambda_p * mu_p.col(i) + (1. - alpha) * lambda_q * mu_q.col(i));
    }

    // transform outputs to numpy
    array_tf _mu = mat_to_array(mu);
    array_tf _sigma = cube_to_array(sigma);

    py::tuple output = py::make_tuple(_mu, _sigma);
    return output;
}

double quad_expectation(array_tf _mu, array_tf _sigma_s,
                        array_tf _Q, array_tf _q, double _q0) {

    vec mu  = array_to_vec(_mu);
    mat sigma_s = array_to_mat(_sigma_s);

    mat Q = array_to_mat(_Q);
    vec q = array_to_vec(_q);

	double result = as_scalar(mu.t() * Q * mu) + as_scalar(mu.t() * q) + _q0 + trace(Q * sigma_s);
	return result;
}

py::tuple cubature_forward_pass(array_tf _mu_x0, array_tf _sigma_x0,
                                array_tf _mu_param, array_tf _sigma_param, array_tf _sigma_dyn,
                                array_tf _K, array_tf _kff, array_tf _sigma_ctl,
                                int dm_state, int dm_act, int nb_steps) {

    // inputs
    vec mu_x0 = array_to_vec(_mu_x0);
    mat sigma_x0 = array_to_mat(_sigma_x0);

    mat mu_param = array_to_mat(_mu_param);
    cube sigma_param = array_to_cube(_sigma_param);
    cube sigma_dyn = array_to_cube(_sigma_dyn);

    mat A(dm_state, dm_state);
    mat B(dm_state, dm_act);
    vec c(dm_state);

    // cubature

    // particles for state, action, constant, ...
    // ... sigma_param, sigma_dyn
    int dm_augmented = dm_state + dm_act + 1 + dm_state;
    vec mu_augmented(dm_augmented);
    mat chol_sigma_augmented = 1e-8 * eye(dm_augmented, dm_augmented);

    mat input_cubature_points(dm_augmented, 2 * dm_augmented);
    mat output_cubature_points(dm_state, 2 * dm_augmented);

    cube K = array_to_cube(_K);
    mat kff = array_to_mat(_kff);
    cube sigma_ctl = array_to_cube(_sigma_ctl);

    // outputs
    mat mu_x(dm_state, nb_steps + 1);
    cube sigma_x(dm_state, dm_state, nb_steps + 1);

    mat mu_u(dm_act, nb_steps);
    cube sigma_u(dm_act, dm_act, nb_steps);

    mat mu_xu(dm_state + dm_act, nb_steps + 1);
    cube sigma_xu(dm_state + dm_act, dm_state + dm_act, nb_steps + 1);

    mu_x.col(0) = mu_x0;
    sigma_x.slice(0) = sigma_x0;

    for (int i = 0; i < nb_steps; i++) {
        // get matrices from parameter distribution mean vector
        // reshape here is appled column-wise
        mat At = mu_param(span(0, dm_state * dm_state - 1), i);
        mat Bt = mu_param(span(dm_state * dm_state,
                               dm_state * dm_state + dm_state * dm_act - 1), i);
        vec ct = mu_param(span(dm_state * dm_state + dm_state * dm_act,
                               dm_state * dm_state + dm_state * dm_act + dm_state - 1), i);

        A = reshape(At, size(A));
        B = reshape(Bt, size(B));
        c = reshape(ct, size(c));

        // mu_u = K * mu_x + k
        mu_u.col(i) = K.slice(i) * mu_x.col(i) + kff.col(i);

        // sigma_u = sigma_ctl + K * sigma_x * K_T
        sigma_u.slice(i) = sigma_ctl.slice(i) + K.slice(i) * sigma_x.slice(i) * K.slice(i).t();
        sigma_u.slice(i) = 0.5 * (sigma_u.slice(i) + sigma_u.slice(i).t());
        sigma_u.slice(i) += 1e-8 * eye(dm_act, dm_act);

        // sigma_xu =   [[sigma_x,      sigma_x * K_T],
        //               [K * sigma_x,    sigma_u    ]]
        sigma_xu.slice(i) = join_vert(join_horiz(sigma_x.slice(i), sigma_x.slice(i) * K.slice(i).t()),
                                      join_horiz(K.slice(i) * sigma_x.slice(i), sigma_u.slice(i)));
        sigma_xu.slice(i) = 0.5 * (sigma_xu.slice(i) + sigma_xu.slice(i).t());
        sigma_xu.slice(i) += 1e-8 * eye(dm_state + dm_act, dm_state + dm_act);

        // mu_xu =  [[mu_x],
        //           [mu_u]],
        mu_xu.col(i) = join_vert(mu_x.col(i), mu_u.col(i));

        // form augmented state mean and covariance
        mu_augmented = join_vert(mu_xu.col(i), ones(1), zeros<vec>(dm_state));

        chol_sigma_augmented.fill(0.0);
        chol_sigma_augmented.submat(0, 0, dm_state + dm_act - 1, dm_state + dm_act - 1) = chol(symmatu(sigma_xu.slice(i)), "lower");
        chol_sigma_augmented.submat(dm_state + dm_act + 1, dm_state + dm_act + 1, dm_augmented - 1, dm_augmented - 1) = eye(dm_state, dm_state);

        // calculate cubature points
        input_cubature_points = join_horiz(chol_sigma_augmented, - chol_sigma_augmented);
        input_cubature_points *= sqrt(dm_augmented);
        input_cubature_points.each_col() += mu_augmented;

        // propagate cubature points
         for (int j = 0; j < 2 * dm_augmented; j++){
            vec vec_xu = input_cubature_points.col(j);

            mat mat_xu = kron(vec_xu(span(0, dm_state + dm_act)).t(), eye(dm_state, dm_state));
            mat total_covar = sigma_dyn.slice(i) + mat_xu * sigma_param.slice(i) * mat_xu.t();
            total_covar = 0.5 * (total_covar + total_covar.t());

            mat chol_covar = chol(symmatu(total_covar), "lower");
            output_cubature_points.col(j) = join_horiz(A, B, c, chol_covar) * vec_xu;
        }

        // estimate new mean and covariance
        mu_x.col(i+1) = mean(output_cubature_points, 1);

        output_cubature_points.each_col() -= mu_x.col(i+1);

        sigma_x.slice(i+1).fill(0.0);
        output_cubature_points.each_col([&sigma_x, i] (vec& col){
            sigma_x.slice(i+1) += col * col.t();
        });
        sigma_x.slice(i+1) /= 2 * dm_augmented;
        sigma_x.slice(i+1) = 0.5 * (sigma_x.slice(i+1) + sigma_x.slice(i+1).t());

        if(i == nb_steps - 1) {
            mu_xu.col(i+1) = join_vert(mu_x.col(i+1), zeros<vec>(dm_act));
            sigma_xu.slice(i+1).submat(0, 0, dm_state - 1, dm_state - 1) = sigma_x.slice(i+1);
        }
    }

    // transform outputs to numpy
    array_tf _mu_x = mat_to_array(mu_x);
    array_tf _sigma_x = cube_to_array(sigma_x);
    array_tf _mu_u =  mat_to_array(mu_u);
    array_tf _sigma_u = cube_to_array(sigma_u);
    array_tf _mu_xu =  mat_to_array(mu_xu);
    array_tf _sigma_xu = cube_to_array(sigma_xu);

    py::tuple output = py::make_tuple(_mu_x, _sigma_x, _mu_u, _sigma_u, _mu_xu, _sigma_xu);
    return output;
}

py::tuple policy_augment_cost(array_tf _Cxx, array_tf _cx, array_tf _Cuu,
                              array_tf _cu, array_tf _Cxu, array_tf _c0,
                              array_tf _K, array_tf _kff, array_tf _sigma_ctl,
                              array_tf _alpha, int dm_state, int dm_act, int nb_steps) {

    // inputs
    cube Cxx = array_to_cube(_Cxx);
    mat cx = array_to_mat(_cx);
    cube Cuu = array_to_cube(_Cuu);
    mat cu = array_to_mat(_cu);
    cube Cxu = array_to_cube(_Cxu);
    vec c0 = array_to_vec(_c0);

    cube K = array_to_cube(_K);
    mat kff = array_to_mat(_kff);
    cube sigma_ctl = array_to_cube(_sigma_ctl);

    vec alpha = array_to_vec(_alpha);

    // outputs
    cube agCxx(dm_state, dm_state, nb_steps + 1);
    mat agcx(dm_state, nb_steps + 1);
    cube agCuu(dm_act, dm_act, nb_steps + 1);
    mat agcu(dm_act, nb_steps + 1);
    cube agCxu(dm_state, dm_act, nb_steps + 1);
    vec agc0(nb_steps + 1);

    for (int i = 0; i < nb_steps; i++) {
        mat lambda_ctl = inv_sympd(sigma_ctl.slice(i));

        agCxx.slice(i) = Cxx.slice(i) + 0.5 * alpha(i) * K.slice(i).t() * lambda_ctl * K.slice(i);
        agCuu.slice(i) = Cuu.slice(i) + 0.5 * alpha(i) * lambda_ctl;
        agCxu.slice(i) = Cxu.slice(i) - 0.5 * alpha(i) * K.slice(i).t() * lambda_ctl;
        agcx.col(i) = cx.col(i) + alpha(i) * K.slice(i).t() * lambda_ctl * kff.col(i);
        agcu.col(i) = cu.col(i) - alpha(i) * lambda_ctl * kff.col(i);
        agc0(i) = as_scalar(c0(i) + 0.5 * alpha(i) * log( det(2. * datum::pi * sigma_ctl.slice(i)) )
                            + 0.5 * alpha(i) * kff.col(i).t() * lambda_ctl * kff.col(i));
    }

    // last time step
    agCxx.slice(nb_steps) = Cxx.slice(nb_steps);
    agcx.col(nb_steps) = cx.col(nb_steps);
    agCuu.slice(nb_steps) = Cuu.slice(nb_steps);
    agcu.col(nb_steps) = cu.col(nb_steps);
    agCxu.slice(nb_steps) = Cxu.slice(nb_steps);
    agc0(nb_steps) = c0(nb_steps);

    // transform outputs to numpy
    array_tf _agCxx = cube_to_array(agCxx);
    array_tf _agcx = mat_to_array(agcx);
    array_tf _agCuu =  cube_to_array(agCuu);
    array_tf _agcu = mat_to_array(agcu);
    array_tf _agCxu =  cube_to_array(agCxu);
    array_tf _agc0 = vec_to_array(agc0);

    py::tuple output = py::make_tuple(_agCxx, _agcx, _agCuu, _agcu, _agCxu, _agc0);
    return output;
}

py::tuple policy_backward_pass(array_tf _Cxx, array_tf _cx, array_tf _Cuu,
                               array_tf _cu, array_tf _Cxu, array_tf _c0,
                               array_tf _mu_param, array_tf _sigma_param, array_tf _sigma_dyn,
                               array_tf _alpha, int dm_state, int dm_act, int nb_steps) {

    // inputs
    cube Cxx = array_to_cube(_Cxx);
    mat cx = array_to_mat(_cx);
    cube Cuu = array_to_cube(_Cuu);
    mat cu = array_to_mat(_cu);
    cube Cxu = array_to_cube(_Cxu);
    vec c0 = array_to_vec(_c0);

    mat mu_param = array_to_mat(_mu_param);
    cube sigma_param = array_to_cube(_sigma_param);
    cube sigma_dyn = array_to_cube(_sigma_dyn);

    vec alpha = array_to_vec(_alpha);

    mat A(dm_state, dm_state);
    mat B(dm_state, dm_act);
    vec c(dm_state);

    mat P(dm_state + dm_act + 1, dm_state + dm_act + 1);
    mat Pxx(dm_state, dm_state);
    mat Pxu(dm_state, dm_act);
    mat Puu(dm_act, dm_act);
    vec px(dm_state);
    vec pu(dm_act);
    double p0;

    // outputs
    cube Q(dm_state + dm_act, dm_state + dm_act, nb_steps);
    cube Qxx(dm_state, dm_state, nb_steps);
    cube Qux(dm_act, dm_state, nb_steps);
    cube Quu(dm_act, dm_act, nb_steps);
    cube Quu_inv(dm_act, dm_act, nb_steps);
    mat qx(dm_state, nb_steps);
    mat qu(dm_act, nb_steps);
    vec q0(nb_steps);

    cube V(dm_state, dm_state, nb_steps + 1);
    mat v(dm_state, nb_steps + 1);
    vec v0(nb_steps + 1);

    cube K(dm_act, dm_state, nb_steps);
    mat kff(dm_act, nb_steps);
    cube sigma_ctl(dm_act, dm_act, nb_steps);
    cube lambda_ctl(dm_act, dm_act, nb_steps);

    int _diverge = -1;

    // last time step
    V.slice(nb_steps) = Cxx.slice(nb_steps);
    v.col(nb_steps) = cx.col(nb_steps);
    v0(nb_steps) = c0(nb_steps);

	for(int i = nb_steps - 1; i>= 0; --i)
	{
        // get matrices from parameter distribution mean vector
        // reshape here is appled column-wise
        mat At = mu_param(span(0, dm_state * dm_state - 1), i);
        mat Bt = mu_param(span(dm_state * dm_state, dm_state * dm_state + dm_state * dm_act - 1), i);
        vec ct = mu_param(span(dm_state * dm_state + dm_state * dm_act, dm_state * dm_state + dm_state * dm_act + dm_state - 1), i);

        A = reshape(At, size(A));
        B = reshape(Bt, size(B));
        c = reshape(ct, size(c));

        // extra terms due to parameter distribution
         for (int j = 0; j < dm_state + dm_act + 1; j++){
            for (int k = 0; k < dm_state + dm_act + 1; k++){
                P(j, k) = trace(sigma_param.slice(i).submat(j * dm_state, k * dm_state,
                                                            (j + 1) * dm_state - 1, (k + 1) * dm_state - 1) * V.slice(i+1));
            }
         }

        Pxx = P.submat(0, 0, dm_state - 1, dm_state - 1);
        Puu = P.submat(dm_state, dm_state, dm_state + dm_act - 1, dm_state + dm_act - 1);
        Pxu = P.submat(0, dm_state, dm_state - 1, dm_state + dm_act - 1);

        px = P.submat(0, dm_state + dm_act, dm_state - 1, dm_state + dm_act);
        pu = P.submat(dm_state, dm_state + dm_act, dm_state + dm_act - 1, dm_state + dm_act);
        p0 = P(dm_state + dm_act, dm_state + dm_act);

        Qxx.slice(i) = - (Cxx.slice(i) + A.t() * V.slice(i+1) * A + Pxx) / alpha(i);
        Quu.slice(i) = - (Cuu.slice(i) + B.t() * V.slice(i+1) * B + Puu) / alpha(i);
        Qux.slice(i) = - (Cxu.slice(i) + A.t() * V.slice(i+1) * B + Pxu).t() / alpha(i);

        qu.col(i) = - (cu.col(i) + 2.0 * B.t() * V.slice(i+1) * c + B.t() * v.col(i+1) + 2. * pu) / alpha(i);
        qx.col(i) = - (cx.col(i) + 2.0 * A.t() * V.slice(i+1) * c + A.t() * v.col(i+1) + 2. * px) / alpha(i);
        q0(i) = - as_scalar(c0(i) + v0(i+1) + c.t() * V.slice(i+1) * c +
                            + trace(V.slice(i+1) * sigma_dyn.slice(i)) + v.col(i+1).t() * c + p0) / alpha(i);

        if ((Quu.slice(i)).is_sympd()) {
            _diverge = i;
            break;
        }

        Quu_inv.slice(i) = inv(Quu.slice(i));
        K.slice(i) = - Quu_inv.slice(i) * Qux.slice(i);
        kff.col(i) = - 0.5 * Quu_inv.slice(i) * qu.col(i);

        sigma_ctl.slice(i) = - 0.5 * Quu_inv.slice(i);
        sigma_ctl.slice(i) = 0.5 * (sigma_ctl.slice(i).t() + sigma_ctl.slice(i));

        lambda_ctl.slice(i) = - (Quu.slice(i).t() + Quu.slice(i));
        lambda_ctl.slice(i) = 0.5 * (lambda_ctl.slice(i).t() + lambda_ctl.slice(i));

        V.slice(i) = - alpha(i) * (Qxx.slice(i) + Qux.slice(i).t() * K.slice(i));
        V.slice(i) = 0.5 * (V.slice(i) + V.slice(i).t());

        v.col(i) = - alpha(i) * (qx.col(i) + 2. * Qux.slice(i).t() * kff.col(i));
        v0(i) = - alpha(i) * (as_scalar(0.5 * qu.col(i).t() * kff.col(i)) + q0(i)
                              + 0.5 * (dm_act * log (2. * datum::pi) - log(det(- 2. * Quu.slice(i)))));
	}

    // transform outputs to numpy
    array_tf _Qxx = cube_to_array(Qxx);
    array_tf _Qux = cube_to_array(Qux);
    array_tf _Quu = cube_to_array(Quu);

    array_tf _qx = mat_to_array(qx);
    array_tf _qu = mat_to_array(qu);
    array_tf _q0 = mat_to_array(q0);

    array_tf _V = cube_to_array(V);
    array_tf _v = mat_to_array(v);
    array_tf _v0 = vec_to_array(v0);

    array_tf _K = cube_to_array(K);
    array_tf _kff = mat_to_array(kff);
    array_tf _sigma_ctl = cube_to_array(sigma_ctl);

    py::tuple output =  py::make_tuple(_Qxx, _Qux, _Quu, _qx, _qu, _q0,
                                        _V, _v, _v0,
                                        _K, _kff, _sigma_ctl, _diverge);

    return output;
}

py::tuple parameter_augment_cost(array_tf _mu_nominal, array_tf _sigma_nominal,
                                 double beta, int dm_param, int nb_steps) {

    // inputs
    mat mu_nominal = array_to_mat(_mu_nominal);
    cube sigma_nominal = array_to_cube(_sigma_nominal);

    // outputs
    cube agCxx(dm_param, dm_param, nb_steps);
    mat agcx(dm_param, nb_steps);
    vec agc0(nb_steps);

    for (int i = 0; i < nb_steps; i++) {
        mat lambda_nominal = inv_sympd(sigma_nominal.slice(i));

        agCxx.slice(i) = 0.5 * beta * lambda_nominal;
        agcx.col(i) = - beta * lambda_nominal * mu_nominal.col(i);
        agc0(i) = as_scalar(0.5 * beta * log( det(2. * datum::pi * sigma_nominal.slice(i)) )
                            + 0.5 * beta * mu_nominal.col(i).t() * lambda_nominal * mu_nominal.col(i));
    }

    // transform outputs to numpy
    array_tf _agCxx = cube_to_array(agCxx);
    array_tf _agcx = mat_to_array(agcx);
    array_tf _agc0 = vec_to_array(agc0);

    py::tuple output = py::make_tuple(_agCxx, _agcx, _agc0);
    return output;
}

py::tuple regularized_parameter_augment_cost(array_tf _mu_last, array_tf _sigma_last,
                                             double eta, int dm_param, int nb_steps) {

    // inputs
    mat mu_last = array_to_mat(_mu_last);
    cube sigma_last = array_to_cube(_sigma_last);

    // outputs
    cube agCxx(dm_param, dm_param, nb_steps);
    mat agcx(dm_param, nb_steps);
    vec agc0(nb_steps);

    for (int i = 0; i < nb_steps; i++) {
        mat lambda_last = inv_sympd(sigma_last.slice(i));

        agCxx.slice(i) = 0.5 * eta * lambda_last;
        agcx.col(i) = - eta * lambda_last * mu_last.col(i);
        agc0(i) = as_scalar(0.5 * eta * log( det(2. * datum::pi * sigma_last.slice(i)) )
                            + 0.5 * eta * mu_last.col(i).t() * lambda_last * mu_last.col(i));
    }

    // transform outputs to numpy
    array_tf _agCxx = cube_to_array(agCxx);
    array_tf _agcx = mat_to_array(agcx);
    array_tf _agc0 = vec_to_array(agc0);

    py::tuple output = py::make_tuple(_agCxx, _agcx, _agc0);
    return output;
}

py::tuple parameter_backward_pass(array_tf _mu_x, array_tf _sigma_x,
                                  array_tf _K, array_tf _kff, array_tf _sigma_ctl, array_tf _sigma_dyn,
                                  array_tf _cx, array_tf _Cxx, array_tf _Cuu,
                                  array_tf _cu, array_tf _Cxu, array_tf _c0,
                                  array_tf _agCpp, array_tf _agcp, array_tf _agc0,
                                  double beta, double eta, int dm_state, int dm_act, int dm_param, int nb_steps) {

    // inputs
    mat mu_x = array_to_mat(_mu_x);
    cube sigma_x = array_to_cube(_sigma_x);

    cube K = array_to_cube(_K);
    mat kff = array_to_mat(_kff);
    cube sigma_ctl = array_to_cube(_sigma_ctl);

    cube sigma_dyn = array_to_cube(_sigma_dyn);

    cube Cxx = array_to_cube(_Cxx);
    mat cx = array_to_mat(_cx);
    cube Cuu = array_to_cube(_Cuu);
    mat cu = array_to_mat(_cu);
    cube Cxu = array_to_cube(_Cxu);
    vec c0 = array_to_vec(_c0);

    cube agCpp = array_to_cube(_agCpp);
    mat agcp = array_to_mat(_agcp);
    vec agc0 = array_to_vec(_agc0);

    // recreate state-action-offset dist.
    mat mu_u(dm_act, nb_steps);
    cube sigma_u(dm_act, dm_act, nb_steps);

    mat mu_xu(dm_state + dm_act + 1, nb_steps + 1);
    cube sigma_xu(dm_state + dm_act + 1, dm_state + dm_act + 1, nb_steps + 1);

    for (int i = 0; i < nb_steps; i++) {
        // mu_u = K * mu_x + k
        mu_u.col(i) = K.slice(i) * mu_x.col(i) + kff.col(i);

        // sigma_u = sigma_ctl + K * sigma_x * K_T
        sigma_u.slice(i) = sigma_ctl.slice(i) + K.slice(i) * sigma_x.slice(i) * K.slice(i).t();
        sigma_u.slice(i) = 0.5 * (sigma_u.slice(i) + sigma_u.slice(i).t());
        sigma_u.slice(i) += 1e-8 * eye(dm_act, dm_act);

        // sigma_xu =   [[sigma_x,        sigma_x * K_T,   0.],
        //               [K * sigma_x,    sigma_u,         0.],
        //               [0.              0.               0.]]
        sigma_xu.slice(i) = join_vert( join_horiz(sigma_x.slice(i), sigma_x.slice(i) * K.slice(i).t(), zeros(dm_state, 1)),
                                       join_horiz(K.slice(i) * sigma_x.slice(i), sigma_u.slice(i), zeros(dm_act, 1)),
                                       join_horiz(zeros(1, dm_state), zeros(1, dm_act), zeros(1, 1)) );
        sigma_xu.slice(i) = 0.5 * (sigma_xu.slice(i) + sigma_xu.slice(i).t());
        sigma_xu.slice(i) += 1e-8 * eye(dm_state + dm_act + 1, dm_state + dm_act + 1);

        // mu_xu =  [[mu_x],
        //           [mu_u],
        //           [1],
        mu_xu.col(i) = join_vert(mu_x.col(i), mu_u.col(i), ones(1));
    }

    mu_xu.col(nb_steps) = join_vert(mu_x.col(nb_steps), zeros(dm_act), ones(1));
    sigma_xu.slice(nb_steps).submat(0, 0, dm_state - 1, dm_state - 1) = sigma_x.slice(nb_steps);

    // temp
    mat W(dm_param, dm_param);
    vec w(dm_param);

    mat A(dm_state, dm_state);
    mat B(dm_state, dm_act);
    vec c(dm_state);

    mat A_cl(dm_state, dm_state);
    vec c_cl(dm_state);
    mat sigma_block(dm_state + dm_act + 1, dm_state + dm_act + 1);

    mat P(dm_state + dm_act + 1, dm_state + dm_act + 1);
    mat Pxx(dm_state, dm_state);
    mat Pxu(dm_state, dm_act);
    mat Puu(dm_act, dm_act);
    vec px(dm_state);
    vec pu(dm_act);
    double p0;

    // outputs
    mat mu_optimal(dm_param, nb_steps);
    cube sigma_optimal(dm_param, dm_param, nb_steps);

    cube V(dm_state, dm_state, nb_steps + 1);
    mat v(dm_state, nb_steps + 1);
    vec v0(nb_steps + 1);

    int _diverge = -1;

    // last time step
    V.slice(nb_steps) = - Cxx.slice(nb_steps);
    v.col(nb_steps) = - cx.col(nb_steps);
    v0(nb_steps) = - c0(nb_steps);

	for(int i = nb_steps - 1; i >= 0; --i)
	{
	    mat mat_mu_xu = kron(mu_xu.col(i).t(), eye(dm_state, dm_state));

	    mat Vpp = mat_mu_xu.t() * V.slice(i + 1) * mat_mu_xu + kron(sigma_xu.slice(i), V.slice(i + 1));
	    vec vp =  mat_mu_xu.t() * v.col(i + 1);

        W = 2.0 * (agCpp.slice(i) + Vpp) / (beta + eta);
        W = 0.5 * (W.t() + W);

        w = - (agcp.col(i) + vp) / (beta + eta);

        try {
            sigma_optimal.slice(i) = inv_sympd(W);
        } catch ( const std::runtime_error ) {
            _diverge = i;
            break;
        }
        sigma_optimal.slice(i) = 0.5 * (sigma_optimal.slice(i).t() + sigma_optimal.slice(i));

        mu_optimal.col(i) = sigma_optimal.slice(i) * w;

        mat At = mu_optimal(span(0, dm_state * dm_state - 1), i);
        mat Bt = mu_optimal(span(dm_state * dm_state, dm_state * dm_state + dm_state * dm_act - 1), i);
        vec ct = mu_optimal(span(dm_state * dm_state + dm_state * dm_act, dm_state * dm_state + dm_state * dm_act + dm_state - 1), i);

        A = reshape(At, size(A));
        B = reshape(Bt, size(B));
        c = reshape(ct, size(c));

        // extra terms due to parameter distribution
         for (int j = 0; j < dm_state + dm_act + 1; j++){
            for (int k = 0; k < dm_state + dm_act + 1; k++){
                P(j, k) = trace(sigma_optimal.slice(i).submat(j * dm_state, k * dm_state,
                                                              (j + 1) * dm_state - 1, (k + 1) * dm_state - 1) * V.slice(i+1));
            }
         }

        Pxx = P.submat(0, 0, dm_state - 1, dm_state - 1);
        Puu = P.submat(dm_state, dm_state, dm_state + dm_act - 1, dm_state + dm_act - 1);
        Pxu = P.submat(0, dm_state, dm_state - 1, dm_state + dm_act - 1);

        px = P.submat(0, dm_state + dm_act, dm_state - 1, dm_state + dm_act);
        pu = P.submat(dm_state, dm_state + dm_act, dm_state + dm_act - 1, dm_state + dm_act);
        p0 = P(dm_state + dm_act, dm_state + dm_act);

        A_cl = A + B * K.slice(i);
        c_cl = c + B * kff.col(i);
        sigma_block.submat(dm_state, dm_state, dm_state + dm_act - 1, dm_state + dm_act - 1) = sigma_ctl.slice(i);

        V.slice(i) = (- Cxx.slice(i) + Pxx) + K.slice(i).t() * (- Cuu.slice(i) + Puu) * K.slice(i)
                      + A_cl.t() * V.slice(i + 1) * A_cl + 2. * (- Cxu.slice(i) + Pxu) * K.slice(i);
        V.slice(i) = 0.5 * (V.slice(i) + V.slice(i).t());

        v.col(i) = (- cx.col(i) + 2. * px) + 2. * K.slice(i).t() * (- Cuu.slice(i) + Puu) * kff.col(i)
                    + 2. * (- Cxu.slice(i) + Pxu) * kff.col(i) + K.slice(i).t() * (- cu.col(i) + 2. * pu)
                    + 2. * A_cl.t() * V.slice(i + 1) * c_cl + A_cl.t() * v.col(i + 1);

        v0(i) = as_scalar( (- c0(i) + p0) + kff.col(i).t() * (- Cuu.slice(i) + Puu) * kff.col(i) + kff.col(i).t() * (- cu.col(i) + 2. * pu)
                            - trace(Cuu.slice(i + 1) * sigma_ctl.slice(i)) + v0(i + 1) + trace(V.slice(i + 1) * sigma_dyn.slice(i))
                            + mu_optimal.col(i).t() * kron(sigma_block, V.slice(i + 1)) * mu_optimal.col(i) + trace(kron(sigma_block, V.slice(i + 1)) * sigma_optimal.slice(i))
                            + c_cl.t() * V.slice(i + 1) * c_cl + c_cl.t() * v.col(i + 1) );
	}

    // transform outputs to numpy
    array_tf _V = cube_to_array(V);
    array_tf _v = mat_to_array(v);
    array_tf _v0 = vec_to_array(v0);

    array_tf _mu_optimal = mat_to_array(mu_optimal);
    array_tf _sigma_optimal = cube_to_array(sigma_optimal);

    py::tuple output = py::make_tuple(_V, _v, _v0, _mu_optimal, _sigma_optimal, _diverge);

    return output;
}

PYBIND11_MODULE(core, m)
{
    m.def("policy_divergence", &policy_divergence);
    m.def("gaussian_divergence", &gaussian_divergence);
    m.def("gaussian_interp_w2", &gaussian_interp_w2);
    m.def("gaussian_interp_kl", &gaussian_interp_kl);
    m.def("quad_expectation", &quad_expectation);
    m.def("cubature_forward_pass", &cubature_forward_pass);
    m.def("policy_augment_cost", &policy_augment_cost);
    m.def("policy_backward_pass", &policy_backward_pass);
    m.def("parameter_augment_cost", &parameter_augment_cost);
    m.def("parameter_backward_pass", &parameter_backward_pass);
    m.def("regularized_parameter_augment_cost", &regularized_parameter_augment_cost);
}


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

  @copyright 2016 NumScale SAS

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

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-reduction
    Function object implementing isincluded capabilities

    Returns @ref True is only if all bits set in x are also set in y

    @par Semantic:

    For every parameters of type T0, T1 of same bit size:

    @code
    bool r = isincluded(x,y);
    @endcode

    is similar to:

    @code
    bool r = all((x|y) == y);
    @endcode
  **/
  template<typename T0, typename T1>
  bool isincluded(T0 const& x, T1 const& y);
} }
#endif

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

#endif


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

#include <iostream>
#include <cmath>
#include <boost/numeric/mtl/mtl.hpp>

using namespace std;

template <typename Vector>
void one_d_iteration(char const* name, const Vector & vector, size_t check_index, typename Vector::value_type check)
{
    namespace traits = mtl::traits;
    typename traits::index<Vector>::type                               index(vector);
    typename traits::const_value<Vector>::type                         const_value(vector);
    typedef  mtl::tag::nz                                              tag;
    typedef typename traits::range_generator<tag, Vector>::type        cursor_type;
    typedef typename traits::range_generator<tag, Vector>::complexity  complexity;

    cout << name << "\nElements: " << complexity() << '\n';
    for (cursor_type cursor = mtl::begin<tag>(vector), cend = mtl::end<tag>(vector); cursor != cend; ++cursor) {
	cout << "vector[" << index(*cursor) << "] = "
	     << const_value(*cursor) << '\n';
	if (index(*cursor) == check_index
	    && const_value(*cursor) != check)
	    throw "wrong check value";
    }
}

template <typename Vector>
void test(Vector& v, const char* name)
{
    using mtl::sum; using mtl::product;

    typedef mtl::tag::iter::all iall;
    typedef typename mtl::traits::range_generator<iall, Vector>::type Iter;

    std::cout << "\n" << name << "  --- v = " << v << "\n"; std::cout.flush();

    for (Iter iter(mtl::begin<iall>(v)), iend(mtl::end<iall>(v)); iter != iend; ++iter)
	cout << *iter << ", ";
    cout << "\n";

    one_d_iteration(name, v, 2, 8.0f);

    std::cout << "one_norm(v) = " << one_norm(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(one_norm(v) != 15.0, mtl::runtime_error("one_norm wrong"));

    std::cout << "two_norm(v) = " << two_norm(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(two_norm(v) < 9.6436 || two_norm(v) > 9.6437, mtl::runtime_error("two_norm wrong"));

    std::cout << "infinity_norm(v) = " << infinity_norm(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(infinity_norm(v) != 8.0, mtl::runtime_error("infinity_norm wrong"));

    std::cout << "sum(v) = " << sum(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(sum(v) != 15.0, mtl::runtime_error("sum wrong"));

    std::cout << "product(v) = " << product(v) << "\n"; std::cout.flush();
    MTL_THROW_IF(product(v) != 80.0, mtl::runtime_error("product wrong"));

    mtl::dense_vector<float>   u(3), w(3);
    u= 3.0; w= 5.0;

    std::cout << "u= v + w:\n"; std::cout.flush();
    u= v + w;
    cout << "u: " << u << "\n"; std::cout.flush();
    MTL_THROW_IF(u[0] != 7.0, mtl::runtime_error("wrong"));

    std::cout << "u= v + w + v + w:\n"; std::cout.flush();
    u= v + w + v + w;
    cout << "u: " << u << "\n"; std::cout.flush();
    MTL_THROW_IF(u[0] != 14.0, mtl::runtime_error("wrong"));

    std::cout << "u= v + (w= v + v);:\n"; std::cout.flush();
    u= v + (w= v + v);
    cout << "u: " << u << ", w: " << w << "\n"; std::cout.flush();
    MTL_THROW_IF(w[0] != 4.0, mtl::runtime_error("w wrong"));
    MTL_THROW_IF(u[0] != 6.0, mtl::runtime_error("u wrong"));

    std::cout << name << "  --- u+= dot<12>(v, w) * w;:\n"; std::cout.flush();
    u+= mtl::dot<12>(v, w) * w;
    cout << "u: " << u << ", v: " << v << ", w: " << w << "\n"; std::cout.flush();
    MTL_THROW_IF(u[0] != 750.0, mtl::runtime_error("u wrong"));

    Vector q(sub_vector(v, 1, 4));
    MTL_THROW_IF(q[1] != 8.f, mtl::runtime_error("Wrong value in q"));
    MTL_THROW_IF(size(q) != 2, mtl::runtime_error("Wrong size of q"));
    std::cout << "sub_vector(v, 1, 4) == " << q << "\n";

    using mtl::irange;
    Vector r(v[irange(1, 4)]);
    MTL_THROW_IF(r[1] != 8.f, mtl::runtime_error("Wrong value in r"));
    MTL_THROW_IF(size(r) != 2, mtl::runtime_error("Wrong size of r"));
    std::cout << "v[irange(1, 4)] == " << r << "\n";

}

template <typename Vector>
void test2(Vector& v)
{
#if 0
    typedef typename mtl::traits::category<Vector>::type cat;
    typedef typename mtl::traits::is_vector<Vector>::type isv;
    typedef typename mtl::Collection<Vector>::value_type value_type;
    typedef typename mtl::traits::category<value_type>::type vcat;
    typedef typename mtl::ashape::ashape<value_type>::type ashape;

    std::cout << "Category of v is " << typeid(cat).name() << '\n';
    std::cout << "is vector " << typeid(isv).name() << '\n';
    std::cout << "value type is " << typeid(value_type).name() << '\n';
    std::cout << "Category of value is " << typeid(vcat).name() << '\n';
    std::cout << "ashape of value is " << typeid(ashape).name() << '\n';
#endif
    set_to_zero(v);
}

int main(int, char**)
{
    mtl::dense_vector<float> v(3);
    v= 2, 5, 8;
    test(v, "Reference");

    mtl::dense2D<float> A(3, 3);
    A= 1, 2, 3,
       4, 5, 6,
       7, 8, 9;

    mtl::vec::strided_vector_ref<float> stref(A[mtl::iall][2]);
    test2(v);
    test2(stref);

    mtl::vec::strided_vector_ref<float> x(3, &A[0][1], 3);

    const mtl::dense2D<float> B(A);
    mtl::vec::strided_vector_ref<const float> xc(3, &B[0][1], 3);

    test(x, "test float");
    test(xc, "test const float");

    return 0;
}


#ifndef GRAPH_NODE
#define GRAPH_NODE

#include <Eigen/Dense>
#include <memory>
#include <stddef.h>

class GraphNode {
public:
  // constructors
  GraphNode() = delete;
  GraphNode(const size_t num_graph_node,
            const Eigen::VectorXd &weights_node_vertex);

  // destructors
  ~GraphNode() = default;

  // member
  Eigen::VectorXd get_weights() const;
  double get_min_vertex_weight() const;
  size_t get_min_vertex_node_idx() const;
  size_t get_node_number() const;
  void print_vertex_weights() const;
  double get_last_cummulated_weight() const;
  size_t get_predecessor_node() const;
  void reset_cummulated_weight();
  void set_predecessor_node(const size_t &predecessor);
  void set_cummulated_weight(double current_weights_sum);

private:
  size_t num_node_; // index of the node within the graph
  Eigen::VectorXd vertex_weights_;
  double min_vertex_weight_;
  size_t min_vertex_weight_idx_;
  double last_cummulated_weight_;
  size_t predecessor_node_;
};

class CompGraphNode {
public:
  CompGraphNode() = default;

  int operator()(const std::shared_ptr<GraphNode> &node_1,
                 const std::shared_ptr<GraphNode> &node_2) {
    bool result = (node_1->get_last_cummulated_weight() >
                   node_2->get_last_cummulated_weight());
    return result;
  }
};

#endif /* GRAPH_NODE */


/*
@copyright Louis Dionne 2015
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/hana/ext/boost/mpl/integral_c.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/comparable.hpp>
#include <laws/orderable.hpp>

#include <boost/mpl/int.hpp>
#include <boost/mpl/integral_c.hpp>
using namespace boost::hana;
namespace mpl = boost::mpl;

int main() {
    //////////////////////////////////////////////////////////////////////////
    // Setup for the laws below
    //////////////////////////////////////////////////////////////////////////
    auto ints = make<Tuple>(
        mpl::int_<-10>{}, mpl::int_<-2>{}, mpl::integral_c<int, 0>{},
        mpl::integral_c<int, 1>{}, mpl::integral_c<int, 3>{}
    );

    //////////////////////////////////////////////////////////////////////////
    // Comparable and Orderable
    //////////////////////////////////////////////////////////////////////////
    {
        // laws
        test::TestComparable<ext::boost::mpl::IntegralC<int>>{ints};
        test::TestOrderable<ext::boost::mpl::IntegralC<int>>{ints};
    }
}


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/eigen.h>

#include <glog/logging.h>
#include <math.h>
#include <Eigen/Dense>
#include <complex>
#include <algorithm>

#include "theia/math/polynomial.h"

// for overloaded function in CameraInstrinsicsModel
template <typename... Args>
using overload_cast_ = pybind11::detail::overload_cast_impl<Args...>;

namespace py = pybind11;
#include <vector>
#include <iostream>
#include <pybind11/numpy.h>

namespace py = pybind11;

namespace pytheia {
namespace math {

void pytheia_math_classes(py::module &m) {
  m.def("FindQuadraticPolynomialRoots", theia::FindQuadraticPolynomialRoots);
}

void pytheia_math(py::module &m) {
    py::module m_submodule = m.def_submodule("math");
    pytheia_math_classes(m_submodule);
}

}
}


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*                                                                           */
/*  This file is part of the library KASKADE 7                               */
/*    see http://www.zib.de/projects/kaskade7-finite-element-toolbox         */
/*                                                                           */
/*  Copyright (C) 2002-2015 Zuse Institute Berlin                            */
/*                                                                           */
/*  KASKADE 7 is distributed under the terms of the ZIB Academic License.    */
/*    see $KASKADE/academic.txt                                              */
/*                                                                           */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <iostream>

//#define BOOST_DISABLE_ASSERTS

#include <boost/timer/timer.hpp>

#include "dune/grid/config.h"
#include "dune/grid/uggrid.hh"
#include "dune/istl/solvers.hh"

#include "fem/assemble.hh"
#include "fem/lagrangespace.hh"
#include "fem/embedded_errorest.hh"
#include "linalg/trivialpreconditioner.hh"
#include "linalg/jacobiPreconditioner.hh"
#include "linalg/uzawa.hh"
#include "linalg/direct.hh"
#include "io/vtk.hh"
#include "utilities/kaskopt.hh" // property_tree
#include "utilities/gridGeneration.hh"

using namespace Kaskade;
#include "stokes-adaptive.hh"

int main(int argc, char *argv[])
{
  using namespace boost::fusion;

  int verbosity = 1;  // print to console if arguments are changed
  bool dump = false; // do not write properties into file
  std::unique_ptr<boost::property_tree::ptree> pt = getKaskadeOptions(argc, argv, verbosity, dump);

  std::cout << "Start Stokes tutorial programm" << std::endl;
  std::cout << "using addaptive refinement based on embedded error estimation." << std::endl;
  std::cout << "Note: use FixedFractionCriterion as refinement strategy!" << std::endl << std::endl;

  constexpr int dim = 2;
  constexpr int uIdx = 0;
  constexpr int pIdx = 1;

  // command line parameters
  int refinements = getParameter(pt, "refinements", 2);   // initial mesh is refined "refinements" times
  int order = getParameter(pt, "order", 3);               // order for velocity space, order for pressure space is (order-1)
  int steps = getParameter(pt, "steps", 5);               // adaptive refinement steps

  bool direct = getParameter(pt, "direct", true);
  DirectType directType = static_cast<DirectType>(getParameter(pt, "directSolver", 4)); // 4: DirectType::UMFPACK3264

  // grid generation
  using Grid = Dune::UGGrid<dim>;
  using H1Space = FEFunctionSpace<ContinuousLagrangeMapper<double,Grid::LeafGridView> >;
  using Spaces = vector<H1Space const*,H1Space const*>;
  using VariableDescriptions = vector<Variable<SpaceIndex<1>,Components<2>,VariableId<uIdx> >,
                                      Variable<SpaceIndex<0>,Components<1>,VariableId<pIdx> > >;
  using VariableSet = VariableSetDescription<Spaces,VariableDescriptions>;
  using CoefficientVectors = VariableSet::CoefficientVectorRepresentation<>::type;
  using Functional = StokesFunctional<double,VariableSet>;
  using Assembler = VariationalFunctionalAssembler<LinearizationAt<Functional> >;

  Dune::FieldVector<double,dim> x0(0.0), length(1.0);
  GridManager<Grid> gridManager( createRectangle<Grid>(x0,length,1.0));
  gridManager.globalRefine(refinements);

  // construct involved spaces.
  H1Space pressureSpace(gridManager,gridManager.grid().leafGridView(),order-1);
  H1Space velocitySpace(gridManager,gridManager.grid().leafGridView(),order);

  Spaces spaces(&pressureSpace,&velocitySpace);

  // construct variable list.
  std::string varNames[2] = { "u", "p" };

  VariableSet variableSet(spaces,varNames);

  // construct variational functional.
  Functional F;

  // construct Galerkin representation
  Assembler assembler(spaces);
  VariableSet::VariableSet x(variableSet);
  VariableSet::VariableSet dx(variableSet);

  // accuracy in space
  std::vector<std::pair<double,double> > tol(variableSet.noOfVariables);
  tol[0] = std::make_pair(1e0,1e-2);   // u
  tol[1] = std::make_pair(1e0,1e-2);   // p
  bool accurate = 0;

  for (int i=0; i<steps; ++i)    // adaptive refinement loop
  {
    std::cout << "\nstep = " << i+1 << std::endl;
    size_t nnz = assembler.nnz(0,2,0,2,false);
    size_t dof = variableSet.degreesOfFreedom(0,2);
    std::cout << "overall degrees of freedom: " << dof << std::endl;
    //std::cout << "(structurally) nonzero elements: " << nnz << std::endl;

    x = 0;
    assembler.assemble(linearization(F,x));

    if(direct)
    {
      CoefficientVectors solution(VariableSet::CoefficientVectorRepresentation<>::init(spaces));
      CoefficientVectors rhs(assembler.rhs());

      // solve performing one Newton step
      directInverseOperator(AssembledGalerkinOperator<Assembler>(assembler),directType).applyscaleadd(-1.0,rhs,solution);
      x.data = solution.data;
    }
    else // Uzawa Solver
    {
      using VectorOfU = VariableSet::CoefficientVectorRepresentation<uIdx,uIdx+1>::type;
      using VectorOfP = VariableSet::CoefficientVectorRepresentation<pIdx,pIdx+1>::type;
      using Assembler_UU = AssembledGalerkinOperator<Assembler,uIdx,uIdx+1,uIdx,uIdx+1>;
      using Assembler_PU = AssembledGalerkinOperator<Assembler,pIdx,pIdx+1,uIdx,uIdx+1>;
      using Assembler_UP = AssembledGalerkinOperator<Assembler,uIdx,uIdx+1,pIdx,pIdx+1>;
      using PreconAdapt = MatrixRepresentedOperator<MatrixAsTriplet<double>, VectorOfP, VectorOfP>;
      using UzSo = UzawaSolver<VectorOfU,VectorOfP>;

      Assembler_UU A(assembler);
      Assembler_PU B(assembler);
      Assembler_UP Bt(assembler);

      JacobiPreconditioner<Assembler_UU> jacobiPreconditioner(A);
      // inexact inner solver for upper left block
      Dune::CGSolver<VectorOfU> cg(A,jacobiPreconditioner,1e-14,300,0);

      TrivialPreconditioner<PreconAdapt> trivialPreconditioner;

      VectorOfU f(assembler.rhs<uIdx,uIdx+1>());
      VectorOfP g(assembler.rhs<pIdx,pIdx+1>());
      VectorOfU u(VariableSet::CoefficientVectorRepresentation<uIdx,uIdx+1>::init(spaces));
      VectorOfP p(VariableSet::CoefficientVectorRepresentation<pIdx,pIdx+1>::init(spaces));

      UzSo uzawa(A,cg,B,Bt,trivialPreconditioner,1e-4,100,2);

      Dune::InverseOperatorResult res;
      UzSo::Domain solution(vector<VectorOfU,VectorOfP>(u,p));
      UzSo::Range rhs(vector<VectorOfU,VectorOfP>(f,g));
      rhs *= -1.0; // change sign as rhs is -'F while the assembler returns F'
      uzawa.apply(solution,rhs,res);

      at_c<uIdx>(x.data).coefficients() = at_c<0>(at_c<uIdx>(solution.data).data);
      at_c<pIdx>(x.data).coefficients() = at_c<0>(at_c<pIdx>(solution.data).data);
    }

    writeVTKFile(gridManager.grid().leafGridView(),x,"stokes-adaptive", IoOptions(), 1);

    VariableSet::VariableSet e = x;
    projectHierarchically(variableSet,e);
    e -= x;

    accurate = embeddedErrorEstimator(variableSet,e,x,IdentityScaling(),tol,gridManager,1);
    dof = variableSet.degreesOfFreedom(0,2);
    std::cout << "degrees of freedom after refinement = " << dof << std::endl;

  }

  return 0;
}


//---------------------------------------------------------------------------//
// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020-2021 Nikita Kaskov <nbering@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//

#define BOOST_TEST_MODULE exponentiation_components_test

#include <boost/test/unit_test.hpp>

#include <nil/crypto3/algebra/curves/mnt4.hpp>
#include <nil/crypto3/algebra/fields/mnt4/base_field.hpp>
#include <nil/crypto3/algebra/fields/mnt4/scalar_field.hpp>
#include <nil/crypto3/algebra/fields/arithmetic_params/mnt4.hpp>
#include <nil/crypto3/algebra/curves/params/multiexp/mnt4.hpp>
#include <nil/crypto3/algebra/curves/params/wnaf/mnt4.hpp>
#include <nil/crypto3/algebra/curves/mnt6.hpp>
#include <nil/crypto3/algebra/fields/mnt6/base_field.hpp>
#include <nil/crypto3/algebra/fields/mnt6/scalar_field.hpp>
#include <nil/crypto3/algebra/fields/arithmetic_params/mnt6.hpp>
#include <nil/crypto3/algebra/curves/params/multiexp/mnt6.hpp>
#include <nil/crypto3/algebra/curves/params/wnaf/mnt6.hpp>

#include <nil/crypto3/algebra/random_element.hpp>

#include <nil/crypto3/zk/components/algebra/fields/exponentiation.hpp>
#include <nil/crypto3/zk/components/algebra/fields/element_fp4.hpp>
#include <nil/crypto3/zk/components/algebra/fields/element_fp6_2over3.hpp>

using namespace nil::crypto3::zk;
using namespace nil::crypto3::algebra;

template<typename FpkT, template<class> class Fpk_variableT, template<class> class Fpk_mul_componentT,
         template<class> class Fpk_sqr_componentT>
void test_exponentiation_component(const typename FpkT::modulus_type &power) {
    typedef typename FpkT::base_field_type FieldType;

    components::blueprint<FieldType> bp;
    Fpk_variableT<FpkT> x(bp);
    Fpk_variableT<FpkT> x_to_power(bp);
    components::exponentiation_component<FpkT, Fpk_variableT, Fpk_mul_componentT, Fpk_sqr_componentT,
                             typename FpkT::modulus_type>
        exp_component(bp, x, power, x_to_power);
    exp_component.generate_r1cs_constraints();

    for (std::size_t i = 0; i < 10; ++i) {
        const typename FpkT::value_type x_val = random_element<FpkT>();
        x.generate_r1cs_witness(x_val);
        exp_component.generate_r1cs_witness();
        const typename FpkT::value_type res = x_to_power.get_element();
        BOOST_CHECK(bp.is_satisfied());
        BOOST_CHECK(res == (x_val.pow(power)));
    }
    std::cout << "Number of constraints: " << bp.num_constraints() << std::endl;
    std::cout << "Power: " << power << std::endl;
}

BOOST_AUTO_TEST_SUITE(exponentiation_component_test_suite)

BOOST_AUTO_TEST_CASE(exponentiation_component_mnt4_298_test_case) {

    std::cout << "Testing mnt4<298>: " << std::endl;

    test_exponentiation_component<curves::mnt4<298>::pairing::fqk_type, components::element_fp4,
        components::element_fp4_mul, components::element_fp4_squared>(
            curves::mnt4<298>::pairing::final_exponent_last_chunk_abs_of_w0);

}

BOOST_AUTO_TEST_CASE(exponentiation_component_mnt6_298_test_case) {

    std::cout << "Testing mnt6<298>: " << std::endl;

    test_exponentiation_component<curves::mnt6<298>::pairing::fqk_type, components::element_fp6_2over3,
        components::element_fp6_2over3_mul, components::element_fp6_2over3_squared>(
            curves::mnt6<298>::pairing::final_exponent_last_chunk_abs_of_w0);
}

BOOST_AUTO_TEST_SUITE_END()


// This file is part of Eigen, a lightweight C++ template library
// for linear algebra.
//
// Copyright (C) 2016
// Mehdi Goli    Codeplay Software Ltd.
// Ralph Potter  Codeplay Software Ltd.
// Luke Iwanski  Codeplay Software Ltd.
// Contact: <eigen@codeplay.com>
// Benoit Steiner <benoit.steiner.goog@gmail.com>
//
// 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/.

#define EIGEN_TEST_NO_LONGDOUBLE
#define EIGEN_TEST_NO_COMPLEX

#define EIGEN_DEFAULT_DENSE_INDEX_TYPE int64_t
#define EIGEN_USE_SYCL

#include "main.h"

#include <Eigen/CXX11/Tensor>

using Eigen::Tensor;

template <typename DataType, int DataLayout, typename IndexType>
static void test_static_chip_sycl(const Eigen::SyclDevice& sycl_device)
{
  IndexType sizeDim1 = 2;
  IndexType sizeDim2 = 3;
  IndexType sizeDim3 = 5;
  IndexType sizeDim4 = 7;
  IndexType sizeDim5 = 11;

  array<IndexType, 5> tensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4, sizeDim5}};
  array<IndexType, 4> chip1TensorRange = {{sizeDim2, sizeDim3, sizeDim4, sizeDim5}};

  Tensor<DataType, 5, DataLayout,IndexType> tensor(tensorRange);
  Tensor<DataType, 4, DataLayout,IndexType> chip1(chip1TensorRange);

  tensor.setRandom();

  const size_t tensorBuffSize =tensor.size()*sizeof(DataType);
  const size_t chip1TensorBuffSize =chip1.size()*sizeof(DataType);
  DataType* gpu_data_tensor  = static_cast<DataType*>(sycl_device.allocate(tensorBuffSize));
  DataType* gpu_data_chip1  = static_cast<DataType*>(sycl_device.allocate(chip1TensorBuffSize));

  TensorMap<Tensor<DataType, 5, DataLayout,IndexType>> gpu_tensor(gpu_data_tensor, tensorRange);
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip1(gpu_data_chip1, chip1TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_tensor, tensor.data(), tensorBuffSize);
  gpu_chip1.device(sycl_device)=gpu_tensor.template chip<0l>(1l);
  sycl_device.memcpyDeviceToHost(chip1.data(), gpu_data_chip1, chip1TensorBuffSize);

  VERIFY_IS_EQUAL(chip1.dimension(0), sizeDim2);
  VERIFY_IS_EQUAL(chip1.dimension(1), sizeDim3);
  VERIFY_IS_EQUAL(chip1.dimension(2), sizeDim4);
  VERIFY_IS_EQUAL(chip1.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim2; ++i) {
    for (IndexType j = 0; j < sizeDim3; ++j) {
      for (IndexType k = 0; k < sizeDim4; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip1(i,j,k,l), tensor(1l,i,j,k,l));
        }
      }
    }
  }

  array<IndexType, 4> chip2TensorRange = {{sizeDim1, sizeDim3, sizeDim4, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> chip2(chip2TensorRange);
  const size_t chip2TensorBuffSize =chip2.size()*sizeof(DataType);
  DataType* gpu_data_chip2  = static_cast<DataType*>(sycl_device.allocate(chip2TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip2(gpu_data_chip2, chip2TensorRange);

  gpu_chip2.device(sycl_device)=gpu_tensor.template chip<1l>(1l);
  sycl_device.memcpyDeviceToHost(chip2.data(), gpu_data_chip2, chip2TensorBuffSize);

  VERIFY_IS_EQUAL(chip2.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip2.dimension(1), sizeDim3);
  VERIFY_IS_EQUAL(chip2.dimension(2), sizeDim4);
  VERIFY_IS_EQUAL(chip2.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim3; ++j) {
      for (IndexType k = 0; k < sizeDim4; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip2(i,j,k,l), tensor(i,1l,j,k,l));
        }
      }
    }
  }

  array<IndexType, 4> chip3TensorRange = {{sizeDim1, sizeDim2, sizeDim4, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> chip3(chip3TensorRange);
  const size_t chip3TensorBuffSize =chip3.size()*sizeof(DataType);
  DataType* gpu_data_chip3  = static_cast<DataType*>(sycl_device.allocate(chip3TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip3(gpu_data_chip3, chip3TensorRange);

  gpu_chip3.device(sycl_device)=gpu_tensor.template chip<2l>(2l);
  sycl_device.memcpyDeviceToHost(chip3.data(), gpu_data_chip3, chip3TensorBuffSize);

  VERIFY_IS_EQUAL(chip3.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip3.dimension(1), sizeDim2);
  VERIFY_IS_EQUAL(chip3.dimension(2), sizeDim4);
  VERIFY_IS_EQUAL(chip3.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim2; ++j) {
      for (IndexType k = 0; k < sizeDim4; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip3(i,j,k,l), tensor(i,j,2l,k,l));
        }
      }
    }
  }

  array<IndexType, 4> chip4TensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> chip4(chip4TensorRange);
  const size_t chip4TensorBuffSize =chip4.size()*sizeof(DataType);
  DataType* gpu_data_chip4  = static_cast<DataType*>(sycl_device.allocate(chip4TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip4(gpu_data_chip4, chip4TensorRange);

  gpu_chip4.device(sycl_device)=gpu_tensor.template chip<3l>(5l);
  sycl_device.memcpyDeviceToHost(chip4.data(), gpu_data_chip4, chip4TensorBuffSize);

  VERIFY_IS_EQUAL(chip4.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip4.dimension(1), sizeDim2);
  VERIFY_IS_EQUAL(chip4.dimension(2), sizeDim3);
  VERIFY_IS_EQUAL(chip4.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim2; ++j) {
      for (IndexType k = 0; k < sizeDim3; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip4(i,j,k,l), tensor(i,j,k,5l,l));
        }
      }
    }
  }

  array<IndexType, 4> chip5TensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4}};
  Tensor<DataType, 4, DataLayout,IndexType> chip5(chip5TensorRange);
  const size_t chip5TensorBuffSize =chip5.size()*sizeof(DataType);
  DataType* gpu_data_chip5  = static_cast<DataType*>(sycl_device.allocate(chip5TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip5(gpu_data_chip5, chip5TensorRange);

  gpu_chip5.device(sycl_device)=gpu_tensor.template chip<4l>(7l);
  sycl_device.memcpyDeviceToHost(chip5.data(), gpu_data_chip5, chip5TensorBuffSize);

  VERIFY_IS_EQUAL(chip5.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip5.dimension(1), sizeDim2);
  VERIFY_IS_EQUAL(chip5.dimension(2), sizeDim3);
  VERIFY_IS_EQUAL(chip5.dimension(3), sizeDim4);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim2; ++j) {
      for (IndexType k = 0; k < sizeDim3; ++k) {
        for (IndexType l = 0; l < sizeDim4; ++l) {
          VERIFY_IS_EQUAL(chip5(i,j,k,l), tensor(i,j,k,l,7l));
        }
      }
    }
  }

  sycl_device.deallocate(gpu_data_tensor);
  sycl_device.deallocate(gpu_data_chip1);
  sycl_device.deallocate(gpu_data_chip2);
  sycl_device.deallocate(gpu_data_chip3);
  sycl_device.deallocate(gpu_data_chip4);
  sycl_device.deallocate(gpu_data_chip5);
}

template <typename DataType, int DataLayout, typename IndexType>
static void test_dynamic_chip_sycl(const Eigen::SyclDevice& sycl_device)
{
  IndexType sizeDim1 = 2;
  IndexType sizeDim2 = 3;
  IndexType sizeDim3 = 5;
  IndexType sizeDim4 = 7;
  IndexType sizeDim5 = 11;

  array<IndexType, 5> tensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4, sizeDim5}};
  array<IndexType, 4> chip1TensorRange = {{sizeDim2, sizeDim3, sizeDim4, sizeDim5}};

  Tensor<DataType, 5, DataLayout,IndexType> tensor(tensorRange);
  Tensor<DataType, 4, DataLayout,IndexType> chip1(chip1TensorRange);

  tensor.setRandom();

  const size_t tensorBuffSize =tensor.size()*sizeof(DataType);
  const size_t chip1TensorBuffSize =chip1.size()*sizeof(DataType);
  DataType* gpu_data_tensor  = static_cast<DataType*>(sycl_device.allocate(tensorBuffSize));
  DataType* gpu_data_chip1  = static_cast<DataType*>(sycl_device.allocate(chip1TensorBuffSize));

  TensorMap<Tensor<DataType, 5, DataLayout,IndexType>> gpu_tensor(gpu_data_tensor, tensorRange);
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip1(gpu_data_chip1, chip1TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_tensor, tensor.data(), tensorBuffSize);
  gpu_chip1.device(sycl_device)=gpu_tensor.chip(1l,0l);
  sycl_device.memcpyDeviceToHost(chip1.data(), gpu_data_chip1, chip1TensorBuffSize);

  VERIFY_IS_EQUAL(chip1.dimension(0), sizeDim2);
  VERIFY_IS_EQUAL(chip1.dimension(1), sizeDim3);
  VERIFY_IS_EQUAL(chip1.dimension(2), sizeDim4);
  VERIFY_IS_EQUAL(chip1.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim2; ++i) {
    for (IndexType j = 0; j < sizeDim3; ++j) {
      for (IndexType k = 0; k < sizeDim4; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip1(i,j,k,l), tensor(1l,i,j,k,l));
        }
      }
    }
  }

  array<IndexType, 4> chip2TensorRange = {{sizeDim1, sizeDim3, sizeDim4, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> chip2(chip2TensorRange);
  const size_t chip2TensorBuffSize =chip2.size()*sizeof(DataType);
  DataType* gpu_data_chip2  = static_cast<DataType*>(sycl_device.allocate(chip2TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip2(gpu_data_chip2, chip2TensorRange);

  gpu_chip2.device(sycl_device)=gpu_tensor.chip(1l,1l);
  sycl_device.memcpyDeviceToHost(chip2.data(), gpu_data_chip2, chip2TensorBuffSize);

  VERIFY_IS_EQUAL(chip2.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip2.dimension(1), sizeDim3);
  VERIFY_IS_EQUAL(chip2.dimension(2), sizeDim4);
  VERIFY_IS_EQUAL(chip2.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim3; ++j) {
      for (IndexType k = 0; k < sizeDim4; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip2(i,j,k,l), tensor(i,1l,j,k,l));
        }
      }
    }
  }

  array<IndexType, 4> chip3TensorRange = {{sizeDim1, sizeDim2, sizeDim4, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> chip3(chip3TensorRange);
  const size_t chip3TensorBuffSize =chip3.size()*sizeof(DataType);
  DataType* gpu_data_chip3  = static_cast<DataType*>(sycl_device.allocate(chip3TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip3(gpu_data_chip3, chip3TensorRange);

  gpu_chip3.device(sycl_device)=gpu_tensor.chip(2l,2l);
  sycl_device.memcpyDeviceToHost(chip3.data(), gpu_data_chip3, chip3TensorBuffSize);

  VERIFY_IS_EQUAL(chip3.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip3.dimension(1), sizeDim2);
  VERIFY_IS_EQUAL(chip3.dimension(2), sizeDim4);
  VERIFY_IS_EQUAL(chip3.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim2; ++j) {
      for (IndexType k = 0; k < sizeDim4; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip3(i,j,k,l), tensor(i,j,2l,k,l));
        }
      }
    }
  }

  array<IndexType, 4> chip4TensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> chip4(chip4TensorRange);
  const size_t chip4TensorBuffSize =chip4.size()*sizeof(DataType);
  DataType* gpu_data_chip4  = static_cast<DataType*>(sycl_device.allocate(chip4TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip4(gpu_data_chip4, chip4TensorRange);

  gpu_chip4.device(sycl_device)=gpu_tensor.chip(5l,3l);
  sycl_device.memcpyDeviceToHost(chip4.data(), gpu_data_chip4, chip4TensorBuffSize);

  VERIFY_IS_EQUAL(chip4.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip4.dimension(1), sizeDim2);
  VERIFY_IS_EQUAL(chip4.dimension(2), sizeDim3);
  VERIFY_IS_EQUAL(chip4.dimension(3), sizeDim5);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim2; ++j) {
      for (IndexType k = 0; k < sizeDim3; ++k) {
        for (IndexType l = 0; l < sizeDim5; ++l) {
          VERIFY_IS_EQUAL(chip4(i,j,k,l), tensor(i,j,k,5l,l));
        }
      }
    }
  }

  array<IndexType, 4> chip5TensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4}};
  Tensor<DataType, 4, DataLayout,IndexType> chip5(chip5TensorRange);
  const size_t chip5TensorBuffSize =chip5.size()*sizeof(DataType);
  DataType* gpu_data_chip5  = static_cast<DataType*>(sycl_device.allocate(chip5TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip5(gpu_data_chip5, chip5TensorRange);

  gpu_chip5.device(sycl_device)=gpu_tensor.chip(7l,4l);
  sycl_device.memcpyDeviceToHost(chip5.data(), gpu_data_chip5, chip5TensorBuffSize);

  VERIFY_IS_EQUAL(chip5.dimension(0), sizeDim1);
  VERIFY_IS_EQUAL(chip5.dimension(1), sizeDim2);
  VERIFY_IS_EQUAL(chip5.dimension(2), sizeDim3);
  VERIFY_IS_EQUAL(chip5.dimension(3), sizeDim4);

  for (IndexType i = 0; i < sizeDim1; ++i) {
    for (IndexType j = 0; j < sizeDim2; ++j) {
      for (IndexType k = 0; k < sizeDim3; ++k) {
        for (IndexType l = 0; l < sizeDim4; ++l) {
          VERIFY_IS_EQUAL(chip5(i,j,k,l), tensor(i,j,k,l,7l));
        }
      }
    }
  }
  sycl_device.deallocate(gpu_data_tensor);
  sycl_device.deallocate(gpu_data_chip1);
  sycl_device.deallocate(gpu_data_chip2);
  sycl_device.deallocate(gpu_data_chip3);
  sycl_device.deallocate(gpu_data_chip4);
  sycl_device.deallocate(gpu_data_chip5);
}

template <typename DataType, int DataLayout, typename IndexType>
static void test_chip_in_expr(const Eigen::SyclDevice& sycl_device) {

  IndexType sizeDim1 = 2;
  IndexType sizeDim2 = 3;
  IndexType sizeDim3 = 5;
  IndexType sizeDim4 = 7;
  IndexType sizeDim5 = 11;

  array<IndexType, 5> tensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4, sizeDim5}};
  array<IndexType, 4> chip1TensorRange = {{sizeDim2, sizeDim3, sizeDim4, sizeDim5}};

  Tensor<DataType, 5, DataLayout,IndexType> tensor(tensorRange);

  Tensor<DataType, 4, DataLayout,IndexType> chip1(chip1TensorRange);
  Tensor<DataType, 4, DataLayout,IndexType> tensor1(chip1TensorRange);
  tensor.setRandom();
  tensor1.setRandom();

  const size_t tensorBuffSize =tensor.size()*sizeof(DataType);
  const size_t chip1TensorBuffSize =chip1.size()*sizeof(DataType);
  DataType* gpu_data_tensor  = static_cast<DataType*>(sycl_device.allocate(tensorBuffSize));
  DataType* gpu_data_chip1  = static_cast<DataType*>(sycl_device.allocate(chip1TensorBuffSize));
  DataType* gpu_data_tensor1  = static_cast<DataType*>(sycl_device.allocate(chip1TensorBuffSize));

  TensorMap<Tensor<DataType, 5, DataLayout,IndexType>> gpu_tensor(gpu_data_tensor, tensorRange);
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_chip1(gpu_data_chip1, chip1TensorRange);
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_tensor1(gpu_data_tensor1, chip1TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_tensor, tensor.data(), tensorBuffSize);
  sycl_device.memcpyHostToDevice(gpu_data_tensor1, tensor1.data(), chip1TensorBuffSize);
  gpu_chip1.device(sycl_device)=gpu_tensor.template chip<0l>(0l) + gpu_tensor1;
  sycl_device.memcpyDeviceToHost(chip1.data(), gpu_data_chip1, chip1TensorBuffSize);

  for (int i = 0; i < sizeDim2; ++i) {
    for (int j = 0; j < sizeDim3; ++j) {
      for (int k = 0; k < sizeDim4; ++k) {
        for (int l = 0; l < sizeDim5; ++l) {
          float expected = tensor(0l,i,j,k,l) + tensor1(i,j,k,l);
          VERIFY_IS_EQUAL(chip1(i,j,k,l), expected);
        }
      }
    }
  }

  array<IndexType, 3> chip2TensorRange = {{sizeDim2, sizeDim4, sizeDim5}};
  Tensor<DataType, 3, DataLayout,IndexType> tensor2(chip2TensorRange);
  Tensor<DataType, 3, DataLayout,IndexType> chip2(chip2TensorRange);
  tensor2.setRandom();
  const size_t chip2TensorBuffSize =tensor2.size()*sizeof(DataType);
  DataType* gpu_data_tensor2  = static_cast<DataType*>(sycl_device.allocate(chip2TensorBuffSize));
  DataType* gpu_data_chip2  = static_cast<DataType*>(sycl_device.allocate(chip2TensorBuffSize));
  TensorMap<Tensor<DataType, 3, DataLayout,IndexType>> gpu_tensor2(gpu_data_tensor2, chip2TensorRange);
  TensorMap<Tensor<DataType, 3, DataLayout,IndexType>> gpu_chip2(gpu_data_chip2, chip2TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_tensor2, tensor2.data(), chip2TensorBuffSize);
  gpu_chip2.device(sycl_device)=gpu_tensor.template chip<0l>(0l).template chip<1l>(2l) + gpu_tensor2;
  sycl_device.memcpyDeviceToHost(chip2.data(), gpu_data_chip2, chip2TensorBuffSize);

  for (int i = 0; i < sizeDim2; ++i) {
    for (int j = 0; j < sizeDim4; ++j) {
      for (int k = 0; k < sizeDim5; ++k) {
        float expected = tensor(0l,i,2l,j,k) + tensor2(i,j,k);
        VERIFY_IS_EQUAL(chip2(i,j,k), expected);
      }
    }
  }
  sycl_device.deallocate(gpu_data_tensor);
  sycl_device.deallocate(gpu_data_tensor1);
  sycl_device.deallocate(gpu_data_chip1);
  sycl_device.deallocate(gpu_data_tensor2);
  sycl_device.deallocate(gpu_data_chip2);
}

template <typename DataType, int DataLayout, typename IndexType>
static void test_chip_as_lvalue_sycl(const Eigen::SyclDevice& sycl_device)
{

  IndexType sizeDim1 = 2;
  IndexType sizeDim2 = 3;
  IndexType sizeDim3 = 5;
  IndexType sizeDim4 = 7;
  IndexType sizeDim5 = 11;

  array<IndexType, 5> tensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4, sizeDim5}};
  array<IndexType, 4> input2TensorRange = {{sizeDim2, sizeDim3, sizeDim4, sizeDim5}};

  Tensor<DataType, 5, DataLayout,IndexType> tensor(tensorRange);
  Tensor<DataType, 5, DataLayout,IndexType> input1(tensorRange);
  Tensor<DataType, 4, DataLayout,IndexType> input2(input2TensorRange);
  input1.setRandom();
  input2.setRandom();

  const size_t tensorBuffSize =tensor.size()*sizeof(DataType);
  const size_t input2TensorBuffSize =input2.size()*sizeof(DataType);
  DataType* gpu_data_tensor  = static_cast<DataType*>(sycl_device.allocate(tensorBuffSize));
  DataType* gpu_data_input1  = static_cast<DataType*>(sycl_device.allocate(tensorBuffSize));
  DataType* gpu_data_input2  = static_cast<DataType*>(sycl_device.allocate(input2TensorBuffSize));

  TensorMap<Tensor<DataType, 5, DataLayout,IndexType>> gpu_tensor(gpu_data_tensor, tensorRange);
  TensorMap<Tensor<DataType, 5, DataLayout,IndexType>> gpu_input1(gpu_data_input1, tensorRange);
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_input2(gpu_data_input2, input2TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_input1, input1.data(), tensorBuffSize);
  gpu_tensor.device(sycl_device)=gpu_input1;
  sycl_device.memcpyHostToDevice(gpu_data_input2, input2.data(), input2TensorBuffSize);
  gpu_tensor.template chip<0l>(1l).device(sycl_device)=gpu_input2;
  sycl_device.memcpyDeviceToHost(tensor.data(), gpu_data_tensor, tensorBuffSize);

  for (int i = 0; i < sizeDim1; ++i) {
    for (int j = 0; j < sizeDim2; ++j) {
      for (int k = 0; k < sizeDim3; ++k) {
        for (int l = 0; l < sizeDim4; ++l) {
          for (int m = 0; m < sizeDim5; ++m) {
            if (i != 1) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input2(j,k,l,m));
            }
          }
        }
      }
    }
  }

  gpu_tensor.device(sycl_device)=gpu_input1;
  array<IndexType, 4> input3TensorRange = {{sizeDim1, sizeDim3, sizeDim4, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> input3(input3TensorRange);
  input3.setRandom();

  const size_t input3TensorBuffSize =input3.size()*sizeof(DataType);
  DataType* gpu_data_input3  = static_cast<DataType*>(sycl_device.allocate(input3TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_input3(gpu_data_input3, input3TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_input3, input3.data(), input3TensorBuffSize);
  gpu_tensor.template chip<1l>(1l).device(sycl_device)=gpu_input3;
  sycl_device.memcpyDeviceToHost(tensor.data(), gpu_data_tensor, tensorBuffSize);

  for (int i = 0; i < sizeDim1; ++i) {
    for (int j = 0; j < sizeDim2; ++j) {
      for (int k = 0; k <sizeDim3; ++k) {
        for (int l = 0; l < sizeDim4; ++l) {
          for (int m = 0; m < sizeDim5; ++m) {
            if (j != 1) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input3(i,k,l,m));
            }
          }
        }
      }
    }
  }

  gpu_tensor.device(sycl_device)=gpu_input1;
  array<IndexType, 4> input4TensorRange = {{sizeDim1, sizeDim2, sizeDim4, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> input4(input4TensorRange);
  input4.setRandom();

  const size_t input4TensorBuffSize =input4.size()*sizeof(DataType);
  DataType* gpu_data_input4  = static_cast<DataType*>(sycl_device.allocate(input4TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_input4(gpu_data_input4, input4TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_input4, input4.data(), input4TensorBuffSize);
  gpu_tensor.template chip<2l>(3l).device(sycl_device)=gpu_input4;
  sycl_device.memcpyDeviceToHost(tensor.data(), gpu_data_tensor, tensorBuffSize);

  for (int i = 0; i < sizeDim1; ++i) {
    for (int j = 0; j < sizeDim2; ++j) {
      for (int k = 0; k <sizeDim3; ++k) {
        for (int l = 0; l < sizeDim4; ++l) {
          for (int m = 0; m < sizeDim5; ++m) {
            if (k != 3) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input4(i,j,l,m));
            }
          }
        }
      }
    }
  }

  gpu_tensor.device(sycl_device)=gpu_input1;
  array<IndexType, 4> input5TensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim5}};
  Tensor<DataType, 4, DataLayout,IndexType> input5(input5TensorRange);
  input5.setRandom();

  const size_t input5TensorBuffSize =input5.size()*sizeof(DataType);
  DataType* gpu_data_input5  = static_cast<DataType*>(sycl_device.allocate(input5TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_input5(gpu_data_input5, input5TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_input5, input5.data(), input5TensorBuffSize);
  gpu_tensor.template chip<3l>(4l).device(sycl_device)=gpu_input5;
  sycl_device.memcpyDeviceToHost(tensor.data(), gpu_data_tensor, tensorBuffSize);

  for (int i = 0; i < sizeDim1; ++i) {
    for (int j = 0; j < sizeDim2; ++j) {
      for (int k = 0; k <sizeDim3; ++k) {
        for (int l = 0; l < sizeDim4; ++l) {
          for (int m = 0; m < sizeDim5; ++m) {
            if (l != 4) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input5(i,j,k,m));
            }
          }
        }
      }
    }
  }
  gpu_tensor.device(sycl_device)=gpu_input1;
  array<IndexType, 4> input6TensorRange = {{sizeDim1, sizeDim2, sizeDim3, sizeDim4}};
  Tensor<DataType, 4, DataLayout,IndexType> input6(input6TensorRange);
  input6.setRandom();

  const size_t input6TensorBuffSize =input6.size()*sizeof(DataType);
  DataType* gpu_data_input6  = static_cast<DataType*>(sycl_device.allocate(input6TensorBuffSize));
  TensorMap<Tensor<DataType, 4, DataLayout,IndexType>> gpu_input6(gpu_data_input6, input6TensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_input6, input6.data(), input6TensorBuffSize);
  gpu_tensor.template chip<4l>(5l).device(sycl_device)=gpu_input6;
  sycl_device.memcpyDeviceToHost(tensor.data(), gpu_data_tensor, tensorBuffSize);

  for (int i = 0; i < sizeDim1; ++i) {
    for (int j = 0; j < sizeDim2; ++j) {
      for (int k = 0; k <sizeDim3; ++k) {
        for (int l = 0; l < sizeDim4; ++l) {
          for (int m = 0; m < sizeDim5; ++m) {
            if (m != 5) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input6(i,j,k,l));
            }
          }
        }
      }
    }
  }

  gpu_tensor.device(sycl_device)=gpu_input1;
  Tensor<DataType, 5, DataLayout,IndexType> input7(tensorRange);
  input7.setRandom();

  DataType* gpu_data_input7  = static_cast<DataType*>(sycl_device.allocate(tensorBuffSize));
  TensorMap<Tensor<DataType, 5, DataLayout,IndexType>> gpu_input7(gpu_data_input7, tensorRange);

  sycl_device.memcpyHostToDevice(gpu_data_input7, input7.data(), tensorBuffSize);
  gpu_tensor.chip(0l,0l).device(sycl_device)=gpu_input7.chip(0l,0l);
  sycl_device.memcpyDeviceToHost(tensor.data(), gpu_data_tensor, tensorBuffSize);

  for (int i = 0; i < sizeDim1; ++i) {
    for (int j = 0; j < sizeDim2; ++j) {
      for (int k = 0; k <sizeDim3; ++k) {
        for (int l = 0; l < sizeDim4; ++l) {
          for (int m = 0; m < sizeDim5; ++m) {
            if (i != 0) {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input1(i,j,k,l,m));
            } else {
              VERIFY_IS_EQUAL(tensor(i,j,k,l,m), input7(i,j,k,l,m));
            }
          }
        }
      }
    }
  }
  sycl_device.deallocate(gpu_data_tensor);
  sycl_device.deallocate(gpu_data_input1);
  sycl_device.deallocate(gpu_data_input2);
  sycl_device.deallocate(gpu_data_input3);
  sycl_device.deallocate(gpu_data_input4);
  sycl_device.deallocate(gpu_data_input5);
  sycl_device.deallocate(gpu_data_input6);
  sycl_device.deallocate(gpu_data_input7);

}

template<typename DataType, typename dev_Selector> void sycl_chipping_test_per_device(dev_Selector s){
  QueueInterface queueInterface(s);
  auto sycl_device = Eigen::SyclDevice(&queueInterface);
  test_static_chip_sycl<DataType, RowMajor, int64_t>(sycl_device);
  test_static_chip_sycl<DataType, ColMajor, int64_t>(sycl_device);
  test_dynamic_chip_sycl<DataType, RowMajor, int64_t>(sycl_device);
  test_dynamic_chip_sycl<DataType, ColMajor, int64_t>(sycl_device);
  test_chip_in_expr<DataType, RowMajor, int64_t>(sycl_device);
  test_chip_in_expr<DataType, ColMajor, int64_t>(sycl_device);
  test_chip_as_lvalue_sycl<DataType, RowMajor, int64_t>(sycl_device);
  test_chip_as_lvalue_sycl<DataType, ColMajor, int64_t>(sycl_device);
}
EIGEN_DECLARE_TEST(cxx11_tensor_chipping_sycl)
{
  for (const auto& device :Eigen::get_sycl_supported_devices()) {
    CALL_SUBTEST(sycl_chipping_test_per_device<float>(device));
  }
}


//  Boost integer/static_min_max.hpp header file  ----------------------------//

//  (C) Copyright Daryle Walker 2001.
//  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)

//  See http://www.boost.org for updates, documentation, and revision history.

#ifndef BOOST_INTEGER_STATIC_MIN_MAX_HPP
#define BOOST_INTEGER_STATIC_MIN_MAX_HPP

#include <boost/integer_fwd.hpp>  // self include

namespace geofeatures_boost {} namespace boost = geofeatures_boost; namespace geofeatures_boost
{

//  Compile-time extrema class declarations  ---------------------------------//
//  Get the minimum or maximum of two values, signed or unsigned.

template <static_min_max_signed_type Value1, static_min_max_signed_type Value2>
struct static_signed_min
{
    BOOST_STATIC_CONSTANT(static_min_max_signed_type, value = (Value1 > Value2) ? Value2 : Value1 );
};

template <static_min_max_signed_type Value1, static_min_max_signed_type Value2>
struct static_signed_max
{
    BOOST_STATIC_CONSTANT(static_min_max_signed_type, value = (Value1 < Value2) ? Value2 : Value1 );
};

template <static_min_max_unsigned_type Value1, static_min_max_unsigned_type Value2>
struct static_unsigned_min
{
    BOOST_STATIC_CONSTANT(static_min_max_unsigned_type, value
     = (Value1 > Value2) ? Value2 : Value1 );
};

template <static_min_max_unsigned_type Value1, static_min_max_unsigned_type Value2>
struct static_unsigned_max
{
    BOOST_STATIC_CONSTANT(static_min_max_unsigned_type, value
     = (Value1 < Value2) ? Value2 : Value1 );
};

}  // namespace geofeatures_boost

#endif  // BOOST_INTEGER_STATIC_MIN_MAX_HPP