// ---------------------------------------------------------------------
//
// Copyright (c) 2014 - 2020 by the IBAMR developers
// All rights reserved.
//
// This file is part of IBAMR.
//
// IBAMR is free software and is distributed under the 3-clause BSD
// license. The full text of the license can be found in the file
// COPYRIGHT at the top level directory of IBAMR.
//
// ---------------------------------------------------------------------

/////////////////////////////// INCLUDES /////////////////////////////////////

#include "ibamr/IBFECentroidPostProcessor.h"
#include "ibamr/IBFEDirectForcingKinematics.h"
#include "ibamr/IBFEMethod.h"
#include "ibamr/IBFEPostProcessor.h"

#include "ibtk/FEDataInterpolation.h"
#include "ibtk/FEDataManager.h"
#include "ibtk/libmesh_utilities.h"

#include "tbox/Utilities.h"

#include "libmesh/dof_map.h"
#include "libmesh/enum_fe_family.h"
#include "libmesh/enum_order.h"
#include "libmesh/enum_quadrature_type.h"
#include "libmesh/equation_systems.h"
#include "libmesh/libmesh_config.h"
#include "libmesh/mesh_base.h"
#include "libmesh/numeric_vector.h"
#include "libmesh/point.h"
#include "libmesh/quadrature.h"
#include "libmesh/system.h"
#include "libmesh/tensor_value.h"
#include "libmesh/type_tensor.h"
#include "libmesh/type_vector.h"
#include "libmesh/variant_filter_iterator.h"
#include "libmesh/vector_value.h"

#include "ibamr/namespaces.h" // IWYU pragma: keep

IBTK_DISABLE_EXTRA_WARNINGS
#include <boost/multi_array.hpp>
IBTK_ENABLE_EXTRA_WARNINGS

#include <memory>
#include <string>
#include <utility>
#include <vector>

namespace libMesh
{
class Elem;
} // namespace libMesh

/////////////////////////////// NAMESPACE ////////////////////////////////////

namespace IBAMR
{
/////////////////////////////// STATIC ///////////////////////////////////////

namespace
{
inline void
get_x_and_FF(libMesh::VectorValue<double>& x,
             libMesh::TensorValue<double>& FF,
             const std::vector<double>& x_data,
             const std::vector<VectorValue<double> >& grad_x_data,
             const unsigned int dim = NDIM)
{
    x.zero();
    FF.zero();
    for (unsigned int i = 0; i < dim; ++i)
    {
        x(i) = x_data[i];
        for (unsigned int j = 0; j < dim; ++j)
        {
            FF(i, j) = grad_x_data[i](j);
        }
    }
    for (unsigned int i = dim; i < LIBMESH_DIM; ++i)
    {
        FF(i, i) = 1.0;
    }
    return;
}
} // namespace

/////////////////////////////// PUBLIC ///////////////////////////////////////

IBFECentroidPostProcessor::IBFECentroidPostProcessor(std::string name, FEDataManager* fe_data_manager)
    : IBFEPostProcessor(std::move(name), fe_data_manager)
{
    // intentionally blank
    return;
} // IBFECentroidPostProcessor

void
IBFECentroidPostProcessor::registerScalarVariable(const std::string& name,
                                                  libMesh::FEFamily fe_family,
                                                  libMesh::Order fe_order,
                                                  ScalarMeshFcnPtr fcn,
                                                  const std::vector<SystemData>& system_data,
                                                  void* fcn_ctx)
{
    TBOX_ASSERT(fe_family == MONOMIAL);
    TBOX_ASSERT(fe_order == CONSTANT);
    IBFEPostProcessor::registerScalarVariable(name, fe_family, fe_order, fcn, system_data, fcn_ctx);
    return;
} // registerScalarVariable

void
IBFECentroidPostProcessor::registerVectorVariable(const std::string& name,
                                                  libMesh::FEFamily fe_family,
                                                  libMesh::Order fe_order,
                                                  VectorMeshFcnPtr fcn,
                                                  const std::vector<SystemData>& system_data,
                                                  void* fcn_ctx,
                                                  unsigned int dim)
{
    TBOX_ASSERT(fe_family == MONOMIAL);
    TBOX_ASSERT(fe_order == CONSTANT);
    IBFEPostProcessor::registerVectorVariable(name, fe_family, fe_order, fcn, system_data, fcn_ctx, dim);
    return;
} // registerVectorVariable

void
IBFECentroidPostProcessor::registerTensorVariable(const std::string& name,
                                                  libMesh::FEFamily fe_family,
                                                  libMesh::Order fe_order,
                                                  TensorMeshFcnPtr fcn,
                                                  const std::vector<SystemData>& system_data,
                                                  void* fcn_ctx,
                                                  unsigned int dim)
{
    TBOX_ASSERT(fe_family == MONOMIAL);
    TBOX_ASSERT(fe_order == CONSTANT);
    IBFEPostProcessor::registerTensorVariable(name, fe_family, fe_order, fcn, system_data, fcn_ctx, dim);
    return;
} // registerTensorVariable

/////////////////////////////// PROTECTED ////////////////////////////////////

void
IBFECentroidPostProcessor::reconstructVariables(double data_time)
{
    EquationSystems* equation_systems = d_fe_data_manager->getEquationSystems();
    MeshBase& mesh = equation_systems->get_mesh();
    const int dim = mesh.mesh_dimension();
    std::unique_ptr<QBase> qrule = QBase::build(QGAUSS, NDIM, CONSTANT);

    // Set up all system data required to evaluate the mesh functions.
    FEDataInterpolation fe(dim, d_fe_data_manager->getFEData());
    fe.attachQuadratureRule(qrule.get());
    fe.evalQuadraturePoints();

    auto& X_system = equation_systems->get_system<System>(IBFEMethod::COORDS_SYSTEM_NAME);
    NumericVector<double>& X_data = *(X_system.current_local_solution);
    copy_and_synch(*X_system.solution, X_data);
    std::vector<int> vars(NDIM);
    for (unsigned int d = 0; d < NDIM; ++d) vars[d] = d;
    const size_t X_sys_idx = fe.registerInterpolatedSystem(X_system, vars, vars, &X_data);

    const size_t num_scalar_vars = d_scalar_var_systems.size();
    std::vector<const DofMap*> scalar_var_dof_maps(num_scalar_vars);
    std::vector<std::vector<unsigned int> > scalar_var_dof_indices(num_scalar_vars);
    std::vector<std::vector<size_t> > scalar_var_fcn_system_idxs(num_scalar_vars);
    for (unsigned int k = 0; k < num_scalar_vars; ++k)
    {
        scalar_var_dof_maps[k] = &d_scalar_var_systems[k]->get_dof_map();
        fe.setupInterpolatedSystemDataIndexes(
            scalar_var_fcn_system_idxs[k], d_scalar_var_system_data[k], equation_systems);
    }

    const size_t num_vector_vars = d_vector_var_systems.size();
    std::vector<const DofMap*> vector_var_dof_maps(num_vector_vars);
    std::vector<std::vector<std::vector<unsigned int> > > vector_var_dof_indices(num_vector_vars);
    std::vector<std::vector<size_t> > vector_var_fcn_system_idxs(num_vector_vars);
    for (unsigned int k = 0; k < num_vector_vars; ++k)
    {
        vector_var_dof_maps[k] = &d_vector_var_systems[k]->get_dof_map();
        vector_var_dof_indices[k].resize(d_vector_var_dims[k]);
        fe.setupInterpolatedSystemDataIndexes(
            vector_var_fcn_system_idxs[k], d_vector_var_system_data[k], equation_systems);
    }

    const size_t num_tensor_vars = d_tensor_var_systems.size();
    std::vector<const DofMap*> tensor_var_dof_maps(num_tensor_vars);
    std::vector<boost::multi_array<std::vector<unsigned int>, 2> > tensor_var_dof_indices(num_tensor_vars);
    std::vector<std::vector<size_t> > tensor_var_fcn_system_idxs(num_tensor_vars);
    for (unsigned int k = 0; k < num_tensor_vars; ++k)
    {
        tensor_var_dof_maps[k] = &d_tensor_var_systems[k]->get_dof_map();
        using array_type = boost::multi_array<std::vector<unsigned int>, 2>;
        array_type::extent_gen extents;
        tensor_var_dof_indices[k].resize(extents[d_tensor_var_dims[k]][d_tensor_var_dims[k]]);
        fe.setupInterpolatedSystemDataIndexes(
            tensor_var_fcn_system_idxs[k], d_tensor_var_system_data[k], equation_systems);
    }

    fe.init();

    const std::vector<libMesh::Point>& q_point = fe.getQuadraturePoints();

    const std::vector<std::vector<std::vector<double> > >& fe_interp_var_data = fe.getVarInterpolation();
    const std::vector<std::vector<std::vector<VectorValue<double> > > >& fe_interp_grad_var_data =
        fe.getGradVarInterpolation();

    std::vector<std::vector<const std::vector<double>*> > scalar_var_data(num_scalar_vars),
        vector_var_data(num_vector_vars), tensor_var_data(num_tensor_vars);
    std::vector<std::vector<const std::vector<VectorValue<double> >*> > scalar_grad_var_data(num_scalar_vars),
        vector_grad_var_data(num_vector_vars), tensor_grad_var_data(num_tensor_vars);

    // Reconstruct the variables via simple function evaluation.
    TensorValue<double> FF_qp, VV;
    VectorValue<double> V, x_qp;
    double v;
    const auto el_begin = mesh.active_local_elements_begin();
    const auto el_end = mesh.active_local_elements_end();
    for (auto el_it = el_begin; el_it != el_end; ++el_it)
    {
        auto elem = *el_it;
        fe.reinit(elem);
        fe.collectDataForInterpolation(elem);
        fe.interpolate(elem);
        const unsigned int n_qp = qrule->n_points();
        for (unsigned int qp = 0; qp < n_qp; ++qp)
        {
            const libMesh::Point& X_qp = q_point[qp];
            const std::vector<double>& x_data = fe_interp_var_data[qp][X_sys_idx];
            const std::vector<VectorValue<double> >& grad_x_data = fe_interp_grad_var_data[qp][X_sys_idx];
            get_x_and_FF(x_qp, FF_qp, x_data, grad_x_data);

            // Scalar-valued variables.
            for (unsigned int k = 0; k < num_scalar_vars; ++k)
            {
                fe.setInterpolatedDataPointers(
                    scalar_var_data[k], scalar_grad_var_data[k], scalar_var_fcn_system_idxs[k], elem, qp);
                scalar_var_dof_maps[k]->dof_indices(elem, scalar_var_dof_indices[k], 0);
                d_scalar_var_fcns[k](v,
                                     FF_qp,
                                     x_qp,
                                     X_qp,
                                     elem,
                                     scalar_var_data[k],
                                     scalar_grad_var_data[k],
                                     data_time,
                                     d_scalar_var_fcn_ctxs[k]);
                d_scalar_var_systems[k]->solution->set(scalar_var_dof_indices[k][0], v);
            }

            // Vector-valued variables.
            for (unsigned int k = 0; k < num_vector_vars; ++k)
            {
                fe.setInterpolatedDataPointers(
                    vector_var_data[k], vector_grad_var_data[k], vector_var_fcn_system_idxs[k], elem, qp);
                for (unsigned int i = 0; i < d_vector_var_dims[k]; ++i)
                {
                    vector_var_dof_maps[k]->dof_indices(elem, vector_var_dof_indices[k][i], i);
                }
                d_vector_var_fcns[k](V,
                                     FF_qp,
                                     x_qp,
                                     X_qp,
                                     elem,
                                     vector_var_data[k],
                                     vector_grad_var_data[k],
                                     data_time,
                                     d_vector_var_fcn_ctxs[k]);
                for (unsigned int i = 0; i < d_vector_var_dims[k]; ++i)
                {
                    d_vector_var_systems[k]->solution->set(vector_var_dof_indices[k][i][0], V(i));
                }
            }

            // Tensor-valued variables.
            for (unsigned int k = 0; k < num_tensor_vars; ++k)
            {
                fe.setInterpolatedDataPointers(
                    tensor_var_data[k], tensor_grad_var_data[k], tensor_var_fcn_system_idxs[k], elem, qp);
                for (unsigned int i = 0; i < d_tensor_var_dims[k]; ++i)
                {
                    for (unsigned int j = 0; j < d_tensor_var_dims[k]; ++j)
                    {
                        tensor_var_dof_maps[k]->dof_indices(
                            elem, tensor_var_dof_indices[k][i][j], j + i * d_tensor_var_dims[k]);
                    }
                }
                d_tensor_var_fcns[k](VV,
                                     FF_qp,
                                     x_qp,
                                     X_qp,
                                     elem,
                                     tensor_var_data[k],
                                     tensor_grad_var_data[k],
                                     data_time,
                                     d_tensor_var_fcn_ctxs[k]);
                for (unsigned int i = 0; i < d_tensor_var_dims[k]; ++i)
                {
                    for (unsigned int j = 0; j < d_tensor_var_dims[k]; ++j)
                    {
                        d_tensor_var_systems[k]->solution->set(tensor_var_dof_indices[k][i][j][0], VV(i, j));
                    }
                }
            }
        }
    }

    // Close all vectors.
    for (unsigned int k = 0; k < num_scalar_vars; ++k)
    {
        d_scalar_var_systems[k]->solution->close();
    }
    for (unsigned int k = 0; k < num_vector_vars; ++k)
    {
        d_vector_var_systems[k]->solution->close();
    }
    for (unsigned int k = 0; k < num_tensor_vars; ++k)
    {
        d_tensor_var_systems[k]->solution->close();
    }
    return;
} // reconstructVariables

/////////////////////////////// PRIVATE //////////////////////////////////////

/////////////////////////////// NAMESPACE ////////////////////////////////////

} // namespace IBAMR

//////////////////////////////////////////////////////////////////////////////


/*
 * A block comment w/ alerts:
 * NOTE some note
 * WARNING some warning
 * ATTENTION attention text
 * TODO todo text
 * TBD to be defined
 * FIXME to be fixed later
 */

#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/bool.hpp>

// Use predefined standard macros
#define MY_THROW(Ex) throw Ex << exception::location_info(__FILE__, __LINE__)
// GCC specific predefined macros
#if defined(__GNUC__) && defined(__linux__)
# define MY_OS "Linux"
#endif

namespace mpl {
/**
 * \brief True variadic analog of \c boost::mpl::or_
 *
 * \attention Generic template isn't defined
 */
template <typename...>
struct v_or;

/**
 * \brief True variadic analog of \c boost::mpl::or_
 *
 * \note Specialization to accept at least two parameters
 * \todo Assert that all given types are \e mpl booleans
 */
template <typename T1, typename T2, typename... Tail>
struct v_or<T1, T2, Tail...>
  : boost::mpl::eval_if<
      T1
    , boost::mpl::true_
    , v_or<T2, Tail...>
    >::type
{
    static_assert(sizeof...(Tail) != 0, "Impossible!");
};

/**
 * \brief True variadic analog of \c boost::mpl::or_
 *
 * \note Single parameter specialization
 */
template <typename T>
struct v_or<T> : boost::mpl::bool_<T::type::value>
{
};

}                                                           // namespace mpl

namespace sample { namespace details {
constexpr std::size_t pow_bytes(const std::size_t what, const unsigned d)
{
    return d ? 1'024 * pow_bytes(what, d - 1) : what;
}
}                                                           // namespace details

/// User defined literal: bytes
constexpr std::size_t operator"" _bytes(const unsigned long long size)
{
    return size;
}
/// User defined literal: kibibytes (2^10)
constexpr std::size_t operator"" _KiB(const unsigned long long size)
{
    return zencxx::details::pow_bytes(size, 1);
}
/// User defined literal: mebibytes (2^20)
constexpr std::size_t operator"" _MiB(const unsigned long long size)
{
    return zencxx::details::pow_bytes(size, 2);
}
/// \warning Invalid user defined literal: missed leading underscore
constexpr std::size_t operator"" GiB(const unsigned long long size)
{
    return zencxx::details::pow_bytes(size, 2);
}

constexpr std::size_t BUFFER_SIZE = 10_Mib;                 // user defined literal (const)
constexpr std::size_t MESSAGE_SIZE = 100_Kib;
constexpr std::size_t MAX_PKT_LENGTH = 100'500_bytes;       // user defined literal w/ delimiters
extern thread_local std::string s_thread_name;              // prefixed static variable
extern std::string g_app_name;                              // prefixed global variable
// names w/ leading underscore are reserved by C++ Standard
std::string _reserved;
}                                                           // namespace sample

namespace chars {
const char a = 'a';
const char hex_esc = '\x1b';
const char oct_esc = '\033';
const char cr = '\n';
const char lf = '\r';
const char lf = '\z';                                       // not a valid esc char
const char tab = '\t';
const int multi = 'abcd';
const wchar_t b = L'b';
const wchar_t b_multi = L'abcdefgh';
const char16_t c = u'c';
const char16_t c = u'\u0bac';
const char16_t c_multi = u'abcd';                           // unicode multichars are not allowed
const char32_t d = U'd';
const char32_t d = U'\U12345678';
const char32_t d_multi = U'ab';                             // unicode multichars are not allowed
}                                                           // namespace strings

namespace strings {
const char* a = "Hello\n";
const wchar_t* b = L"Hello\x0d";
const char16_t* c = u"Hello\015";
const char32_t* d = U"Hello\13";
const char* e = u8"Hello UTF-8";
const char* f = R"-(
    Raw string literal
    (no \t esc sequences here \x21)
  )-";
const wchar_t* g = LR"**(Hello %-03s %d %4i %%)**";         // printf-like format string
const char16_t* h = uR"!(Hello)!";
const char32_t* i = UR"@@@(Hello)@@@";
const std::string j = u8R"++(Hello)++";
const std::string h = u8"привет"_RU;                        // user defined literal
}                                                           // namespace strings

namespace numbers {
constexpr int a = 123;                                      // decimal
constexpr int a1 = -123'456;                                // decimal w/ delimiters
constexpr int au = 123u;                                    // unsigned decimal
constexpr long al = 123l;                                   // long decimal
constexpr long al = 123l;                                   // long decimal
constexpr long long all = 123ll;                            // long long decimal
constexpr unsigned long long aull = +123ull;                // unsigned long long decimal
constexpr auto a_invalid = 123uull;
constexpr int b = 0123;                                     // octal
constexpr int b1 = -0'123'456;                              // octal w/ delimiters
constexpr int octal_invalid = -0678;
constexpr auto c = 0x123;                                   // hex
constexpr auto c = 0x1234'5678'9abc;                        // hex w/ delimiters
constexpr auto z = 0b1010110001110;                         // binary w/ delimiters
constexpr auto z1 = 0b1'0101'1000'1110;                     // binary w/ delimiters
constexpr auto binary_invalid = 0b012;

const auto d = 10.;
const auto e = 10.01;
const auto f = .01f;

const auto g = 10E10f;
const auto h = +10E10;
const auto i = -10E10;

const auto j = 10E+10;
const auto k = +10E+10f;
const auto l = -10E+10;

const auto m = 10e-10;
const auto o = +10e-10;
const auto p = -10e-10f;

const auto q = 10.01E10;
const auto s = 10.01E+10;
const auto t = 10.01E-10l;

const auto u = 10f;                                         // user defined literals must have a leading underscore
}                                                           // namespace numbers

extern void foo() __attribute__((weak));                    // GCC specific attributes

class base
{
public:
    virtual ~base() {}
    virtual void foo() const volatile = 0;
};

class derived : public base
{
    virtual void foo() const volatile override final
    {
        std::cout << __PRETTY_FUNCTION__ << ": " << __DATE__<< std::endl;
    }
    // C++11 attributes
    void exit() [[noreturn, deprecated("Use exit(int) instead")]]
    {
        exit(other_);
    }
    void exit(int a) [[noreturn, gcc::visibility("default")]]
    {
        /// GCC builtins
        if (__builtin_expect(a == 0, 1))
        {
            // ...
        }
    }
    alignas(long) int other_;                               // google code style compatible member name
    int m_member;                                           // prefixed data member
};

// Commented by preprocessor
#if 0
boost::optional<std::string> m_commented;
#else
int not_commended;
#endif

#if true
char also_not_commented;
# if 0
std::string comment = "comment";
# endif
#else
char other_commented;
#endif

// Modelines: switch to C++ mode
// kate: hl C++;
// kate: indent-width 4;


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

#include <nt2/include/functions/simd/plus.hpp>
#include <nt2/include/functions/simd/splat.hpp>
#include <nt2/include/functions/simd/modulo.hpp>
#include <nt2/include/functions/simd/divides.hpp>
#include <nt2/include/functions/simd/bitwise_cast.hpp>
#include <boost/dispatch/meta/as_unsigned.hpp>
#include <boost/fusion/adapted/array.hpp>
#include <boost/fusion/include/size.hpp>
#include <boost/fusion/include/at.hpp>
#include <boost/mpl/int.hpp>

namespace nt2 { namespace result_of
{
  //============================================================================
  // Case for no base index
  //============================================================================
  template<class Size,class Position>
  struct as_subscript
  {
    typedef typename meta::strip<Position>::type                      base_t;
    typedef typename boost::dispatch::meta::as_unsigned<base_t>::type type_t;
    typedef boost::fusion::result_of::size<Size>                      dims_t;
    typedef boost::array<type_t,dims_t::value>                        type;

    BOOST_DISPATCH_FORCE_INLINE type
    operator()(const Size& size, const Position& pos) const
    {
      type sub;
      type_t p = bitwise_cast<type_t>(pos);

      eval( sub, p, size
          , boost::mpl::int_<0>()
          , boost::mpl::int_<dims_t::value>()
          , boost::mpl::bool_<dims_t::value==2>()
          );

      return sub;
    }

    // Normal case
    template<class N, class M>
    BOOST_DISPATCH_FORCE_INLINE void
    eval( type& sub, const type_t& p
        , Size const& s, N const&, M const& m
        , boost::mpl::false_ const&
        ) const
    {
      BOOST_AUTO_TPL(dim, boost::fusion::at_c<N::value>(s));
      type_t ls     = splat<type_t>(dim?dim:1);

      sub[N::value] = p % ls;
      eval( sub, p/ls, s
          , boost::mpl::int_<N::value+1>()
          , m
          , boost::mpl::bool_<(N::value+1 == M::value-2)>()
          );
    }

    template<class N, class M>
    BOOST_DISPATCH_FORCE_INLINE void
    eval( type& sub, type_t const& p
        , Size const& s, N const&, M const& m
        , boost::mpl::true_ const&
        ) const

    {
      BOOST_AUTO_TPL(dim, boost::fusion::at_c<N::value>(s));
      type_t ls     = splat<type_t>(dim?dim:1);

      sub[N::value]   = p % ls;
      sub[N::value+1] = p / ls;
    }

    BOOST_DISPATCH_FORCE_INLINE void
    eval( type& sub, const type_t& p, Size const&
        , boost::mpl::int_<0> const &, boost::mpl::int_<1> const &
        , boost::mpl::false_ const&
        ) const
    {
      sub[0] = p;
    }
  };
} }

namespace nt2
{
  template<class Size,class Position>
  typename result_of::as_subscript<Size,Position>::type
  as_subscript(Size const& sz, Position const& p)
  {
    return result_of::as_subscript<Size,Position>()(sz,p);
  }
}
#endif


/**TODO:  Add copyright*/

#define BOOST_TEST_MODULE ModelKernal test suite
#include <boost/test/included/unit_test.hpp>
#include <EvoNet/ml/ModelKernal.h>

using namespace EvoNet;
using namespace std;

BOOST_AUTO_TEST_SUITE(ModelKernal1)

BOOST_AUTO_TEST_CASE(constructorDefaultDevice)
{
	ModelKernalDefaultDevice<float>* ptr = nullptr;
	ModelKernalDefaultDevice<float>* nullPointer = nullptr;
	ptr = new ModelKernalDefaultDevice<float>();
	BOOST_CHECK_NE(ptr, nullPointer);
}

BOOST_AUTO_TEST_CASE(destructorDefaultDevice)
{
	ModelKernalDefaultDevice<float>* ptr = nullptr;
	ptr = new ModelKernalDefaultDevice<float>();
	delete ptr;
}

BOOST_AUTO_TEST_CASE(nodeActivationDefaultDevice)
{
	ModelKernalDefaultDevice<float> kernal;
	const int device_id = 0;

	std::shared_ptr<ActivationTensorOp<float, Eigen::DefaultDevice>> activation_function = std::make_shared<ReLUTensorOp<float, Eigen::DefaultDevice>>(ReLUTensorOp<float, Eigen::DefaultDevice>());
	const int batch_size = 4;
	const int memory_size = 2;
	const int layer_size = 2;
	const int source_time_step = 0;
	const int node_time_step = 0;

	const int layer_byte_size = batch_size * memory_size * layer_size;
	float* h_node_input = new float[layer_byte_size];
	float* d_node_input = new float[layer_byte_size];
	float* h_node_output = new float[layer_byte_size];
	float* d_node_output = new float[layer_byte_size];
	float* h_node_dt = new float[layer_byte_size];
	float* d_node_dt = new float[layer_byte_size];

	//assert(cudaSetDevice(device_id) == cudaSuccess); // is this needed?

	//// allocate memory
	//std::size_t bytes = batch_size * memory_size * layer_size * sizeof(float);
	//assert(cudaHostAlloc((void**)(&h_node_input), bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_node_input), bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_node_output), bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_node_output), bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_node_dt), bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_node_dt), bytes) == cudaSuccess);

	Eigen::TensorMap<Eigen::Tensor<float, 3>> node_input(h_node_input, batch_size, memory_size, layer_size);
	node_input.setValues({ {{-1, 1}, {0, 0}},
		{{-2, 2}, {0, 0}},
		{{-3, 3}, {0, 0}},
		{{-4, 4}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 3>> node_output(h_node_output, batch_size, memory_size, layer_size);
	node_output.setConstant(0);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> node_dt(h_node_dt, batch_size, memory_size, layer_size);
	node_dt.setConstant(1);

	// Set up the device
	Eigen::DefaultDevice device;
	//cudaStream_t stream; // The stream will be destroyed by GpuStreamDevice once the function goes out of scope!
	//assert(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking) == cudaSuccess);
	//Eigen::GpuStreamDevice stream_device(&stream, 0);
	//Eigen::GpuDevice device(&stream_device);

	bool success = kernal.executeNodeActivation(
		h_node_input,
		d_node_input,
		h_node_output,
		d_node_output,
		h_node_dt,
		d_node_dt,
		activation_function,
		batch_size,
		memory_size,
		layer_size,
		node_time_step,
		device,
		true,
		true);

	//// Synchronize the stream
	//cudaError_t err = cudaStreamQuery(stream);
	//assert(cudaStreamSynchronize(stream) == cudaSuccess);
	//assert(cudaStreamDestroy(stream) == cudaSuccess);

	Eigen::Tensor<float, 3> expected_output(batch_size, memory_size, layer_size);
	expected_output.setValues({ {{0, 1}, {0, 0}},
		{{0, 2}, {0, 0}},
		{{0, 3}, {0, 0}},
		{{0, 4}, {0, 0}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int node_iter = 0; node_iter < layer_size; ++node_iter) {
				//std::cout << "[Output] Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Node Iter: " << node_iter << " = " << node_output(batch_iter, memory_iter, node_iter) << std::endl;
				BOOST_CHECK_CLOSE(node_output(batch_iter, memory_iter, node_iter), expected_output(batch_iter, memory_iter, node_iter), 1e-4);
			}
		}
	}

	// release resources
	delete[] h_node_input;
	delete[] d_node_input;
	delete[] h_node_output;
	delete[] d_node_output;
	delete[] h_node_dt;
	delete[] d_node_dt;
	//assert(cudaFreeHost(h_node_input) == cudaSuccess);
	//assert(cudaFree(d_node_input) == cudaSuccess);
	//assert(cudaFreeHost(h_node_output) == cudaSuccess);
	//assert(cudaFree(d_node_output) == cudaSuccess);
	//assert(cudaFreeHost(h_node_dt) == cudaSuccess);
	//assert(cudaFree(d_node_dt) == cudaSuccess);
}

BOOST_AUTO_TEST_CASE(nodeDerivativeDefaultDevice)
{
	ModelKernalDefaultDevice<float> kernal;
	const int device_id = 0;

	std::shared_ptr<ActivationTensorOp<float, Eigen::DefaultDevice>> activation_grad_function = std::make_shared<ReLUGradTensorOp<float, Eigen::DefaultDevice>>(ReLUGradTensorOp<float, Eigen::DefaultDevice>());
	const int batch_size = 4;
	const int memory_size = 2;
	const int layer_size = 2;
	const int source_time_step = 0;
	const int node_time_step = 0;

	const int layer_byte_size = batch_size * memory_size * layer_size;
	float* h_node_output = new float[layer_byte_size];
	float* d_node_output = new float[layer_byte_size];
	float* h_node_derivative = new float[layer_byte_size];
	float* d_node_derivative = new float[layer_byte_size];

	//assert(cudaSetDevice(device_id) == cudaSuccess); // is this needed?

	//// allocate memory
	//std::size_t bytes = batch_size * memory_size * layer_size * sizeof(float);
	//assert(cudaHostAlloc((void**)(&h_node_output), bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_node_output), bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_node_derivative), bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_node_derivative), bytes) == cudaSuccess);

	Eigen::TensorMap<Eigen::Tensor<float, 3>> node_output(h_node_output, batch_size, memory_size, layer_size);
	node_output.setValues({ {{-1, 1}, {0, 0}},
		{{-2, 2}, {0, 0}},
		{{-3, 3}, {0, 0}},
		{{-4, 4}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 3>> node_derivative(h_node_derivative, batch_size, memory_size, layer_size);
	node_derivative.setConstant(0);

	//// Set up the device
	Eigen::DefaultDevice device;
	//cudaStream_t stream; // The stream will be destroyed by GpuStreamDevice once the function goes out of scope!
	//assert(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking) == cudaSuccess);
	//Eigen::GpuStreamDevice stream_device(&stream, 0);
	//Eigen::GpuDevice device(&stream_device);

	bool success = kernal.executeNodeDerivative(
		h_node_output,
		d_node_output,
		h_node_derivative,
		d_node_derivative,
		activation_grad_function,
		batch_size,
		memory_size,
		layer_size,
		node_time_step,
		device,
		true,
		true);

	//// Synchronize the stream
	//cudaError_t err = cudaStreamQuery(stream);
	//assert(cudaStreamSynchronize(stream) == cudaSuccess);
	//assert(cudaStreamDestroy(stream) == cudaSuccess);

	Eigen::Tensor<float, 3> expected_derivative(batch_size, memory_size, layer_size);
	expected_derivative.setValues({ {{0, 1}, {0, 0}},
		{{0, 1}, {0, 0}},
		{{0, 1}, {0, 0}},
		{{0, 1}, {0, 0}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int node_iter = 0; node_iter < layer_size; ++node_iter) {
				//std::cout << "[Derivative] Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Node Iter: " << node_iter << " = " << node_derivative(batch_iter, memory_iter, node_iter) << std::endl;
				BOOST_CHECK_CLOSE(node_derivative(batch_iter, memory_iter, node_iter), expected_derivative(batch_iter, memory_iter, node_iter), 1e-4);
			}
		}
	}

	// release resources
	delete[] h_node_output;
	delete[] d_node_output;
	delete[] h_node_derivative;
	delete[] d_node_derivative;
	//assert(cudaFreeHost(h_node_output) == cudaSuccess);
	//assert(cudaFree(d_node_output) == cudaSuccess);
	//assert(cudaFreeHost(h_node_derivative) == cudaSuccess);
	//assert(cudaFree(d_node_derivative) == cudaSuccess);
}

BOOST_AUTO_TEST_CASE(forwardPropogationDefaultDevice)
{
	ModelKernalDefaultDevice<float> kernal;
	const int device_id = 0;

	std::shared_ptr<IntegrationTensorOp<float, Eigen::DefaultDevice>> integration_function = std::make_shared<SumTensorOp<float, Eigen::DefaultDevice>>(SumTensorOp<float, Eigen::DefaultDevice>());
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_steps = 0;
	const int sink_time_step = 0;

	float* h_source_outputs = new float[batch_size * memory_size * source_layer_size];
	float* d_source_outputs = new float[batch_size * memory_size * source_layer_size];
	float* h_weights = new float[source_layer_size, sink_layer_size];
	float* d_weights = new float[source_layer_size, sink_layer_size];
	float* h_sink_input = new float[batch_size * memory_size * sink_layer_size];
	float* d_sink_input = new float[batch_size * memory_size * sink_layer_size];

	//assert(cudaSetDevice(device_id) == cudaSuccess); // is this needed?

	//// allocate memory
	//std::size_t source_bytes = batch_size * memory_size * source_layer_size * sizeof(float);
	//std::size_t sink_bytes = batch_size * memory_size * sink_layer_size * sizeof(float);
	//std::size_t weight_bytes = source_layer_size * sink_layer_size * sizeof(float);
	//assert(cudaHostAlloc((void**)(&h_source_outputs), source_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_source_outputs), source_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_weights), weight_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_weights), weight_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_sink_input), sink_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_sink_input), sink_bytes) == cudaSuccess);

	Eigen::TensorMap<Eigen::Tensor<float, 3>> source_output(h_source_outputs, batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight(h_weights, source_layer_size, sink_layer_size);
	weight.setConstant(1);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> sink_input(h_sink_input, batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	// Set up the device
	Eigen::DefaultDevice device;
	//cudaStream_t stream; // The stream will be destroyed by GpuStreamDevice once the function goes out of scope!
	//assert(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking) == cudaSuccess);
	//Eigen::GpuStreamDevice stream_device(&stream, 0);
	//Eigen::GpuDevice device(&stream_device);

	bool success = kernal.executeForwardPropogation(
		h_source_outputs,
		d_source_outputs,
		h_weights,
		d_weights,
		h_sink_input,
		d_sink_input,
		integration_function,
		batch_size,
		memory_size,
		source_layer_size,
		sink_layer_size,
		source_time_steps,
		sink_time_step,
		device,
		true,
		true);

	//// Synchronize the stream
	//cudaError_t err = cudaStreamQuery(stream);
	//assert(cudaStreamSynchronize(stream) == cudaSuccess);
	//assert(cudaStreamDestroy(stream) == cudaSuccess);

	Eigen::Tensor<float, 3> expected_input(batch_size, memory_size, sink_layer_size);
	expected_input.setValues({ {{2}, {0}},
		{{4}, {0}},
		{{6}, {0}},
		{{8}, {0}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int node_iter = 0; node_iter < sink_layer_size; ++node_iter) {
				//std::cout << "[Input] Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Node Iter: " << node_iter << " = " << sink_input(batch_iter, memory_iter, node_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, node_iter), expected_input(batch_iter, memory_iter, node_iter), 1e-4);
			}
		}
	}

	// release resources
	delete[] h_source_outputs;
	delete[] d_source_outputs;
	delete[] h_sink_input;
	delete[] d_sink_input;
}

BOOST_AUTO_TEST_CASE(backwardPropogationDefaultDevice)
{
	const int device_id = 0;
	ModelKernalDefaultDevice<float> kernal;

	std::shared_ptr<IntegrationErrorTensorOp<float, Eigen::DefaultDevice>> integration_function = std::make_shared<SumErrorTensorOp<float, Eigen::DefaultDevice>>(SumErrorTensorOp<float, Eigen::DefaultDevice>());
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 0;

	float* h_source_errors = new float[batch_size * memory_size * source_layer_size];
	float* d_source_errors = new float[batch_size * memory_size * source_layer_size];
	float* h_source_inputs = new float[batch_size * memory_size * source_layer_size];
	float* d_source_inputs = new float[batch_size * memory_size * source_layer_size];
	float* h_weights = new float[source_layer_size, sink_layer_size];
	float* d_weights = new float[source_layer_size, sink_layer_size];
	float* h_sink_error = new float[batch_size * memory_size * sink_layer_size];
	float* d_sink_error = new float[batch_size * memory_size * sink_layer_size];
	float* h_sink_output = new float[batch_size * memory_size * sink_layer_size];
	float* d_sink_output = new float[batch_size * memory_size * sink_layer_size];
	float* h_sink_derivative = new float[batch_size * memory_size * sink_layer_size];
	float* d_sink_derivative = new float[batch_size * memory_size * sink_layer_size];

	//assert(cudaSetDevice(device_id) == cudaSuccess); // is this needed?

	//// allocate memory
	//std::size_t source_bytes = batch_size * memory_size * source_layer_size * sizeof(float);
	//std::size_t sink_bytes = batch_size * memory_size * sink_layer_size * sizeof(float);
	//std::size_t weight_bytes = source_layer_size * sink_layer_size * sizeof(float);
	//assert(cudaHostAlloc((void**)(&h_source_errors), source_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_source_errors), source_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_source_inputs), source_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_source_inputs), source_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_weights), weight_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_weights), weight_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_sink_error), sink_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_sink_error), sink_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_sink_derivative), sink_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_sink_derivative), sink_bytes) == cudaSuccess);
	//assert(cudaHostAlloc((void**)(&h_sink_output), sink_bytes, cudaHostAllocDefault) == cudaSuccess);
	//assert(cudaMalloc((void**)(&d_sink_output), sink_bytes) == cudaSuccess);

	Eigen::TensorMap<Eigen::Tensor<float, 3>> source_error(h_source_errors, batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 3>> source_input(h_source_inputs, batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight(h_weights, source_layer_size, sink_layer_size);
	weight.setConstant(1);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> sink_derivative(h_sink_derivative, batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> sink_error(h_sink_error, batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> sink_output(h_sink_output, batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	// Set up the device
	Eigen::DefaultDevice device;
	//cudaStream_t stream; // The stream will be destroyed by GpuStreamDevice once the function goes out of scope!
	//assert(cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking) == cudaSuccess);
	//Eigen::GpuStreamDevice stream_device(&stream, 0);
	//Eigen::GpuDevice device(&stream_device);

	bool success = kernal.executeBackwardPropogation(
		h_source_errors,
		d_source_errors,
		h_source_inputs,
		d_source_inputs,
		h_sink_output,
		d_sink_output,
		h_weights,
		d_weights,
		h_sink_error,
		d_sink_error,
		h_sink_derivative,
		d_sink_derivative,
		source_layer_size,
		integration_function,
		batch_size,
		memory_size,
		source_layer_size,
		sink_layer_size,
		source_time_step,
		sink_time_step,
		device,
		true,
		true);

	//// Synchronize the stream
	//cudaError_t err = cudaStreamQuery(stream);
	//assert(cudaStreamSynchronize(stream) == cudaSuccess);
	//assert(cudaStreamDestroy(stream) == cudaSuccess);

	Eigen::Tensor<float, 3> expected_error(batch_size, memory_size, sink_layer_size);
	expected_error.setValues({ {{4}, {0}},
		{{8}, {0}},
		{{12}, {0}},
		{{16}, {0}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int node_iter = 0; node_iter < sink_layer_size; ++node_iter) {
				//std::cout << "[Sink Error] Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Node Iter: " << node_iter << " = " << sink_error(batch_iter, memory_iter, node_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, node_iter), expected_error(batch_iter, memory_iter, node_iter), 1e-4);
			}
		}
	}

	//assert(cudaFreeHost(h_source_errors) == cudaSuccess);
	//assert(cudaFree(d_source_errors) == cudaSuccess);
	//assert(cudaFreeHost(h_source_inputs) == cudaSuccess);
	//assert(cudaFree(d_source_inputs) == cudaSuccess);
	//assert(cudaFreeHost(h_weights) == cudaSuccess);
	//assert(cudaFree(d_weights) == cudaSuccess);
	//assert(cudaFreeHost(h_sink_error) == cudaSuccess);
	//assert(cudaFree(d_sink_error) == cudaSuccess);
	//assert(cudaFreeHost(h_sink_derivative) == cudaSuccess);
	//assert(cudaFree(d_sink_derivative) == cudaSuccess);
	//assert(cudaFreeHost(h_sink_output) == cudaSuccess);
	//assert(cudaFree(d_sink_output) == cudaSuccess);
}

BOOST_AUTO_TEST_CASE(modelErrorDefaultDevice)
{
	const int device_id = 0;
	ModelKernalDefaultDevice<float> kernal;

	std::shared_ptr<LossFunctionTensorOp<float, Eigen::DefaultDevice>> loss_function = std::make_shared<MSELossTensorOp<float, Eigen::DefaultDevice>>(MSELossTensorOp<float, Eigen::DefaultDevice>());
	std::shared_ptr<LossFunctionGradTensorOp<float, Eigen::DefaultDevice>> loss_grad_function = std::make_shared<MSELossGradTensorOp<float, Eigen::DefaultDevice>>(MSELossGradTensorOp<float, Eigen::DefaultDevice>());
	const int batch_size = 4;
	const int memory_size = 2;
	const int layer_size = 2;
	const int time_step = 0;

	float* h_predicted = new float[batch_size * memory_size * layer_size];
	float* d_predicted = new float[batch_size * memory_size * layer_size];
	float* h_node_errors = new float[batch_size * memory_size * layer_size];
	float* d_node_errors = new float[batch_size * memory_size * layer_size];
	float* h_model_error = new float[batch_size * memory_size];
	float* d_model_error = new float[batch_size * memory_size];

	Eigen::TensorMap<Eigen::Tensor<float, 3>> predicted(h_predicted, batch_size, memory_size, layer_size);
	predicted.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 2>> model_error(h_model_error, batch_size, memory_size);
	model_error.setConstant(0);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> node_error(h_node_errors, batch_size, memory_size, layer_size);
	node_error.setConstant(0);
	Eigen::Tensor<float, 2> expected(batch_size, layer_size);
	expected.setConstant(1);

	// Set up the device
	Eigen::DefaultDevice device;

	bool success = kernal.executeModelErrors(
		expected,
		h_predicted,
		d_predicted,
		h_model_error,
		d_model_error,
		h_node_errors,
		d_node_errors,
		loss_function,
		loss_grad_function,
		batch_size,
		memory_size,
		layer_size,
		time_step,
		device,
		true,
		true);

	Eigen::Tensor<float, 2> expected_model_error(batch_size, memory_size);
	expected_model_error.setValues({ {0, 0}, {0.5, 0}, {2.0, 0}, {4.5, 0} });
	Eigen::Tensor<float, 3> expected_node_error(batch_size, memory_size, layer_size);
	expected_node_error.setValues({
		{ {0, 0 }, { 0, 0 } },
		{ {-0.5, -0.5 }, { 0, 0 } },
		{ {-1, -1 }, { 0, 0 } },
		{ {-1.5, -1.5 }, { 0, 0 } } });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			//std::cout << "[Model Error] Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << " = " << model_error(batch_iter, memory_iter) << std::endl;
			BOOST_CHECK_CLOSE(model_error(batch_iter, memory_iter), expected_model_error(batch_iter, memory_iter), 1e-4);
			for (int node_iter = 0; node_iter < layer_size; ++node_iter) {
				//std::cout << "[Node Error] Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Node Iter: " << node_iter << " = " << node_error(batch_iter, memory_iter, node_iter) << std::endl;
				BOOST_CHECK_CLOSE(node_error(batch_iter, memory_iter, node_iter), expected_node_error(batch_iter, memory_iter, node_iter), 1e-4);
			}
		}
	}

  // release resources
  delete[] h_predicted;
  delete[] d_predicted;
  delete[] h_model_error;
  delete[] d_model_error;
  delete[] h_node_errors;
  delete[] d_node_errors;
}

BOOST_AUTO_TEST_CASE(modelMetricDefaultDevice)
{
  const int device_id = 0;
  ModelKernalDefaultDevice<float> kernal;

  std::shared_ptr<MetricFunctionTensorOp<float, Eigen::DefaultDevice>> metric_function = std::make_shared<MAETensorOp<float, Eigen::DefaultDevice>>(MAETensorOp<float, Eigen::DefaultDevice>());
  const int batch_size = 4;
  const int memory_size = 2;
  const int layer_size = 2;
  const int n_metrics = 1;
  const int time_step = 0;
  const int metric_index = 0;

  float* h_predicted = new float[batch_size * memory_size * layer_size];
  float* d_predicted = new float[batch_size * memory_size * layer_size];
  float* h_model_metric = new float[n_metrics * memory_size];
  float* d_model_metric = new float[n_metrics * memory_size];

  Eigen::TensorMap<Eigen::Tensor<float, 3>> predicted(h_predicted, batch_size, memory_size, layer_size);
  predicted.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::TensorMap<Eigen::Tensor<float, 2>> model_metric(h_model_metric, n_metrics, memory_size);
  model_metric.setConstant(0);
  Eigen::Tensor<float, 2> expected(batch_size, layer_size);
  expected.setConstant(1);

  // Set up the device
  Eigen::DefaultDevice device;

  bool success = kernal.executeModelMetric(
    expected,
    h_predicted,
    d_predicted,
    h_model_metric,
    d_model_metric,
    metric_function,
    batch_size,
    memory_size,
    layer_size,
    n_metrics,
    time_step,
    metric_index,
    device,
    true,
    true);

  Eigen::Tensor<float, 2> expected_model_metric(n_metrics, memory_size);
  expected_model_metric.setValues({ {1.5, 0} });

  for (int metric_iter = 0; metric_iter < n_metrics; ++metric_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      //std::cout << "[Model Metric] Metric iter: " << metric_iter << ", Memory Iter: " << memory_iter << " = " << model_metric(metric_iter, memory_iter) << std::endl;
      BOOST_CHECK_CLOSE(model_metric(metric_iter, memory_iter), expected_model_metric(metric_iter, memory_iter), 1e-4);
    }
  }

  // release resources
  delete[] h_predicted;
  delete[] d_predicted;
  delete[] h_model_metric;
  delete[] d_model_metric;
}

BOOST_AUTO_TEST_CASE(weightErrorDefaultDevice)
{
	const int device_id = 0;
	ModelKernalDefaultDevice<float> kernal;

	std::shared_ptr<IntegrationWeightGradTensorOp<float, Eigen::DefaultDevice>> integration_function = std::make_shared<SumWeightGradTensorOp<float, Eigen::DefaultDevice>>(SumWeightGradTensorOp<float, Eigen::DefaultDevice>());
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;

	float* h_sink_errors = new float[batch_size * memory_size * sink_layer_size];
	float* d_sink_errors = new float[batch_size * memory_size * sink_layer_size];
	float* h_source_outputs = new float[batch_size * memory_size * source_layer_size];
	float* d_source_outputs = new float[batch_size * memory_size * source_layer_size];
	float* h_source_inputs = new float[batch_size * memory_size * source_layer_size];
	float* d_source_inputs = new float[batch_size * memory_size * source_layer_size];
	float* h_weight = new float[source_layer_size * sink_layer_size];
	float* d_weight = new float[source_layer_size * sink_layer_size];
	float* h_weight_error = new float[source_layer_size * sink_layer_size];
	float* d_weight_error = new float[source_layer_size * sink_layer_size];

	Eigen::TensorMap<Eigen::Tensor<float, 3>> sink_error(h_sink_errors, batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 3>> source_output(h_source_outputs, batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 3>> source_input(h_source_inputs, batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight(h_weight, source_layer_size, sink_layer_size);
	weight.setConstant(1);
	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight_error(h_weight_error, source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	// Set up the device
	Eigen::DefaultDevice device;

	bool success = kernal.executeWeightErrors(
		h_sink_errors,
		d_sink_errors,
		h_source_outputs,
		d_source_outputs,
		h_source_inputs,
		d_source_inputs,
		source_layer_size,
		integration_function,
		h_weight,
		d_weight,
		h_weight_error,
		d_weight_error,
		batch_size,
		memory_size,
		source_layer_size,
		sink_layer_size,
		device,
		true,
		true);

	Eigen::Tensor<float, 2> expected_weight_error(source_layer_size, sink_layer_size);
	expected_weight_error.setValues({ {-4.75}, {-4.75} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected_weight_error(source_iter, sink_iter), 1e-4);
		}
	}

  // release resources
  delete[] h_sink_errors;
  delete[] d_sink_errors;
  delete[] h_source_outputs;
  delete[] d_source_outputs;
  delete[] h_source_inputs;
  delete[] d_source_inputs;
  delete[] h_weight_error;
  delete[] d_weight_error;
  delete[] h_weight;
  delete[] d_weight;
}

BOOST_AUTO_TEST_CASE(sharedWeightErrorsDefaultDevice) {
	ModelKernalDefaultDevice<float> kernal;

	const int source_layer_size = 3;
	const int sink_layer_size = 2;
	const int n_shared_weights = 2;

	float* h_shared_weights = new float[source_layer_size * sink_layer_size * n_shared_weights];
	float* d_shared_weights = new float[source_layer_size * sink_layer_size * n_shared_weights];
	float* h_weight_error = new float[source_layer_size * sink_layer_size];
	float* d_weight_error = new float[source_layer_size * sink_layer_size];

	Eigen::TensorMap<Eigen::Tensor<float, 3>> shared_weights(h_shared_weights, source_layer_size, sink_layer_size, n_shared_weights);
	shared_weights.setValues({
		{{1, 0}, {1, 0}},
		{{0, 1}, {0, 1}},
    {{0, 0}, {0, 0}}
		});
	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight_error(h_weight_error, source_layer_size, sink_layer_size);
	weight_error.setValues({ {1, 2}, {5, 6}, {3, 4} });

	// Set up the device
	Eigen::DefaultDevice device;

	bool success = kernal.executeSharedWeightErrors(
		h_weight_error,
		d_weight_error,
		h_shared_weights,
		d_shared_weights,
		source_layer_size,
		sink_layer_size,
		n_shared_weights,
		device,
		true,
		true);

	Eigen::Tensor<float, 2> expected_weight_error(source_layer_size, sink_layer_size);
	expected_weight_error.setValues({ {3, 3}, {11, 11}, {3, 4} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected_weight_error(source_iter, sink_iter), 1e-4);
		}
	}

  // release resources
  delete[] h_weight_error;
  delete[] d_weight_error;
  delete[] h_shared_weights;
  delete[] d_shared_weights;
}

BOOST_AUTO_TEST_CASE(weightUpdateDefaultDevice)
{
	const int device_id = 0;
	ModelKernalDefaultDevice<float> kernal;

	std::shared_ptr<SolverTensorOp<float, Eigen::DefaultDevice>> solver_function = std::make_shared<SGDTensorOp<float, Eigen::DefaultDevice>>(SGDTensorOp<float, Eigen::DefaultDevice>());
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
  const int iter = 0;

	float* h_solver_params = new float[source_layer_size * sink_layer_size * 3];
	float* d_solver_params = new float[source_layer_size * sink_layer_size * 3];
	float* h_weight = new float[source_layer_size * sink_layer_size];
	float* d_weight = new float[source_layer_size * sink_layer_size];
	float* h_weight_error = new float[source_layer_size * sink_layer_size];
	float* d_weight_error = new float[source_layer_size * sink_layer_size];

	Eigen::TensorMap<Eigen::Tensor<float, 3>> solver_params(h_solver_params, source_layer_size, sink_layer_size, 3);
	solver_params.setValues({ {{0.01, 0.99, 0.0}},
		{{0.01, 0.99, 0.0}} });
	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight(h_weight, source_layer_size, sink_layer_size);
	weight.setConstant(1);
	Eigen::TensorMap<Eigen::Tensor<float, 2>> weight_error(h_weight_error, source_layer_size, sink_layer_size);
	weight_error.setValues({ {-0.2},	{-20} });

	// Set up the device
	Eigen::DefaultDevice device;

	bool success = kernal.executeWeightUpdate(
		h_weight,
		d_weight,
		h_solver_params,
		d_solver_params,
		h_weight_error,
		d_weight_error,
		solver_function,
		source_layer_size,
		sink_layer_size,
    iter,
		device,
		true,
		true);

	Eigen::Tensor<float, 2> expected_weights(source_layer_size, sink_layer_size);
	expected_weights.setValues({ {1.002}, {1.2} });

	Eigen::Tensor<float, 3> expected_params(source_layer_size, sink_layer_size, 3);
	expected_params.setValues({ {{0.01, 0.99, -0.002}},
		{{0.01, 0.99, -0.2}} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight(source_iter, sink_iter), expected_weights(source_iter, sink_iter), 1e-4);
			for (int param_iter = 0; param_iter < 3; ++param_iter) {
				//std::cout << "[Params] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << ", Param Iter: " << param_iter << " = " << solver_params(source_iter, sink_iter, param_iter) << std::endl;
				BOOST_CHECK_CLOSE(solver_params(source_iter, sink_iter, param_iter), expected_params(source_iter, sink_iter, param_iter), 1e-4);
			}
		}
	}

  // release resources
  delete[] h_solver_params;
  delete[] d_solver_params;
  delete[] h_weight;
  delete[] d_weight;
  delete[] h_weight_error;
  delete[] d_weight_error;
}

BOOST_AUTO_TEST_SUITE_END()


#include <boost/filesystem.hpp>
#include "gtest/gtest.h"

#include "test/setup.h"
#include "perf_measure.h"
#include "utils/image_io.h"
#include "utils/recipe_io.h"

#include "recipe/Recipe.h"
#include "recipe/client_preprocessing.h"
#include "recipe/server_preprocessing.h"
#include "filters/local_laplacian/hl_local_laplacian.h"

namespace fs = boost::filesystem;
using namespace std;

class RecipeTest : public testing::Test {
protected:
    RecipeTest() {
        unprocessed_path = TestParams::fixture_path / "0001.jpg";
        processed_path   = TestParams::fixture_path / "0001-processed.jpg";
    }

    fs::path unprocessed_path;
    fs::path processed_path;
    fs::path output_path;
};

TEST_F(RecipeTest, fitHighQuality){
    Image<uint32_t> unprocessed = jpeg_load(unprocessed_path.c_str());

    int levels  = 10;
    float alpha = 2.0f;
    float beta  = 1.0f;
    Image<uint32_t> processed(unprocessed.width(),
        unprocessed.height(), unprocessed.channels());
    hl_local_laplacian(levels, alpha/(levels-1), beta, unprocessed, processed);

    // Fit recipe
    xform::Recipe recipe(unprocessed, processed);

    // hp, lp, qtable
    std::shared_ptr<Image<uint32_t> > lp_res = recipe.lowpass_residual();
    std::shared_ptr<Image<float> > hp_coefs  = recipe.highpass_coefficients();
    std::vector<float> qTable = recipe.qtable();

    // Reconstruct recipe
    Image<uint32_t> reconstructed;
    recipe.reconstruct_image(unprocessed, reconstructed);

    jpeg_save(reconstructed,100,(TestParams::output_path/"recipe_fitHighQuality.jpg").c_str());
}

TEST_F(RecipeTest, fitDegraded){
    // Load high quality and degrade inputs
    Image<uint32_t> unprocessed = jpeg_load(unprocessed_path.c_str());
    Image<uint32_t> unprocessed_lowres =
        jpeg_load((TestParams::fixture_path / "0001-degraded4x.jpg").c_str());

    // Process ground-truth
    int levels  = 10;
    float alpha = 2.0f;
    float beta  = 1.0f;
    Image<uint32_t> processed(unprocessed.width(),
        unprocessed.height(), unprocessed.channels());
    hl_local_laplacian(levels, alpha/(levels-1), beta, unprocessed, processed);

    // Compute statistics of high-quality input
    uint8_t *hdata = nullptr;
    unsigned long hdatasize = 0;
    client_preprocessing(unprocessed, &hdata, &hdatasize);

    // Generate some noise data
    Image<float> noise(100,100);
    char * noisedata = reinterpret_cast<char*>(noise.data());
    memset(noisedata,0,sizeof(float)*100*100);
    unsigned long noisedatasize = 100*100;

    int upsampling_factor = 4;
    int width             = unprocessed.width();
    int height            = unprocessed.height();

    // Assemble proxy
    Image<uint32_t> proxy_unprocessed = server_preprocessing(
        unprocessed_lowres,
        hdata, hdatasize,
        noisedata,noisedatasize,
        upsampling_factor,
        width, height
    );

    // Process proxy
    Image<uint32_t> proxy_processed(proxy_unprocessed.width(),
        proxy_unprocessed.height(), proxy_unprocessed.channels());
    hl_local_laplacian(levels,
        alpha/(levels-1), beta, proxy_unprocessed, proxy_processed);

    // Fit recipe on proxy pair
    xform::Recipe recipe(proxy_unprocessed, proxy_processed);

    // hp, lp, qtable
    std::shared_ptr<Image<uint32_t> > lp_res = recipe.lowpass_residual();
    std::shared_ptr<Image<float> > hp_coefs  = recipe.highpass_coefficients();
    std::vector<float> qTable = recipe.qtable();

    // Reconstruct recipe
    Image<uint32_t> reconstructed;
    recipe.reconstruct_image(unprocessed, reconstructed);

    // Debug output
    jpeg_save(reconstructed, 100, (TestParams::output_path     / "recipe_fitDegraded_reconstructed.jpg").c_str());
    jpeg_save(proxy_unprocessed, 100, (TestParams::output_path / "recipe_fitDegraded_proxy_input.jpg").c_str());
    jpeg_save(proxy_processed, 100, (TestParams::output_path   / "recipe_fitDegraded_proxy_output.jpg").c_str());
    jpeg_save(processed, 100, (TestParams::output_path         / "recipe_fitDegraded_ground_truth.jpg").c_str());
}


#ifndef ISOLATOR_SHREDDER_HPP
#define ISOLATOR_SHREDDER_HPP

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/random/uniform_01.hpp>

#include "common.hpp"
#include "nlopt/nlopt.h"

// A fast, generic, univariate slice sampler implementatation.
class Shredder
{
    public:
        Shredder(double lower_limit, double upper_limit, double tolerance);
        virtual ~Shredder();

        // change the tolerance
        void set_tolerance(double tolerance);

        // Generate a new sample given the current value x0.
        double sample(rng_t& rng, double x0);

        // Generate a maximum likelihood estimate
        double optimize(double x0);

        // These are kept as public fields to ease debugging and diagnostics.
        double x_min, x_max;

    protected:
        // Probability function. Return the log-probibility at x, as well as the
        // derivative d.
        virtual double f(double x, double& d) = 0;

        // Bounds on the parameter being sampled over
        double lower_limit, upper_limit, tolerance;

        boost::random::uniform_01<double> random_uniform_01;

    private:
        double find_slice_edge(double x0, double slice_height, double lp0,
                               double d0, int direction);

        nlopt_opt opt;

        friend double shredder_opt_objective(unsigned int _n, const double* _x,
                                             double* _grad, void* data);
};

// Some common distribution functions, with derivatives
//

#if 0
class PoissonLogPdf
{
    public:
        double f(float lambda, unsigned int k);
        double df_dlambda(float lambda, unsigned int k);
};
#endif

class NormalLogPdf
{
    public:
        double f(double mu, double sigma, const double* xs, size_t n);
        float f(float mu, float sigma, const float* xs, size_t n);

        double df_dx(double mu, double sigma, const double* xs, size_t n);

        double df_dmu(double mu, double sigma, const double* xs, size_t n);
        float df_dmu(float mu, float sigma, const float* xs, size_t n);

        double df_dsigma(double mu, double sigma, const double* xs, size_t n);
        float df_dsigma(float mu, float sigma, const float* xs, size_t n);

        double f(double mu, double sigma, double x);
        double df_dx(double mu, double sigma, double x);
};

class LogNormalLogPdf
{
    public:
        double f(double mu, double sigma, const double* xs, size_t n);
        double df_dx(double mu, double sigma, double x);
        double df_dmu(double mu, double sigma, const double* xs, size_t n);
        double df_dsigma(double mu, double sigma, const double* xs, size_t n);
};

#if 0
class StudentsTLogPdf
{
    public:
        double f(double nu, double mu, double sigma, const double* xs, size_t n);
        float f(float nu, float mu, float sigma, const float* xs, size_t n);
        double df_dx(double nu, double mu, double sigma, const double* xs, size_t n);
        double df_dmu(double nu, double mu, double sigma, const double* xs, size_t n);
        float df_dmu(float nu, float mu, float sigma, const float* xs, size_t n);
        double df_dsigma(double nu, double mu, double sigma, const double* xs, size_t n);
};
#endif

#if 0
class GammaLogPdf
{
    public:
        double f(double alpha, double beta, const double* xs, size_t n);
        float f(float alpha, float beta, const float* xs, size_t n);
        double df_dx(double alpha, double beta, const double* xs, size_t n);
        double df_dalpha(double alpha, double beta, const double* xs, size_t n);
        double df_dbeta(double alpha, double beta, const double* xs, size_t n);
        float df_dbeta(float alpha, float beta, const float* xs, size_t n);
};
#endif

#if 0
// Gamma parameterized by mean and shape
class AltGammaLogPdf
{
    public:
        double f(double mean, double shape, const double* xs, size_t n);
        float f(float mean, float shape, const float* xs, size_t n);
        double f(double mean, double shape, double x);
        float f(float mean, float shape, float x);
        double df_dx(double mean, double shape, const double* xs, size_t n);
        double df_dx(double mean, double shape, double x);
        double df_dmean(double mean, double shape, const double* xs, size_t n);
        float df_dmean(float mean, float shape, const float* xs, size_t n);
        double df_dshape(double shape, double mean, const double* xs, size_t n);
        float df_dshape(float shape, float mean, const float* xs, size_t n);
        float df_dshape(float mean, float shape, float x);
};
#endif

class InvGammaLogPdf
{
    public:
        double f(double alpha, double beta, const double* xs, size_t n);
        double df_dx(double alpha, double beta, const double* xs, size_t n);
        double df_dalpha(double alpha, double beta, const double* xs, size_t n);
        double df_dbeta(double alpha, double beta, const double* xs, size_t n);
};

class SqInvGammaLogPdf
{
    public:
        double f(double alpha, double beta, const double* xs, size_t n);
        double df_dx(double alpha, double beta, const double* xs, size_t n);
        double df_dalpha(double alpha, double beta, const double* xs, size_t n);
        double df_dbeta(double alpha, double beta, const double* xs, size_t n);
};

class BetaLogPdf
{
    public:
        double f(double alpha, double beta, double x);
        double df_dx(double alpha, double beta, double x);
        double df_dgamma(double gamma, double c, double x);
};

class DirichletLogPdf
{
    public:
        double f(double alpha,
                 const boost::numeric::ublas::matrix<double>* mean,
                 const boost::numeric::ublas::matrix<double>* data,
                 size_t n, size_t m);

        double df_dalpha(double alpha,
                         const boost::numeric::ublas::matrix<double>* mean,
                         const boost::numeric::ublas::matrix<double>* data,
                         size_t n, size_t m);
};

// 1-d logistic normal
class LogisticNormalLogPdf
{
    public:
        double f(double mu, double sigma, double x);
        double df_dx(double mu, double sigma, double x);

        // TODO: do these when we need them
        //double df_dmu(double x, double mu, double sigma);
        //double df_dsigma(double x, double mu ,double sigma);
};

#endif


/**
 * merge_pc.cpp: class file for merging multiple point clouds into one
 * Author: Ravi Joshi
 * Date: 2018/05/14
 */

// ros headers
#include <ros/ros.h>
#include <sensor_msgs/PointCloud2.h>
#include <message_filters/subscriber.h>

// tf header
#include <tf/transform_listener.h>

// pcl_ros header
#include <pcl_ros/transforms.h>

// Eigen header
#include <Eigen/Dense>

// for thread related support
#include <boost/thread.hpp>

// for locking using mutex
#include <pthread.h>

// we are using 3 kinects
#define KINECT_COUNT 3

// mutex for locking and unlocking point cloud
static pthread_mutex_t my_mutex;

class PointCloudSubscriber
{
private:
  Eigen::Matrix4f trans;
  ros::Subscriber subscriber;
  sensor_msgs::PointCloud2 temp_cloud;

  void callback(const sensor_msgs::PointCloud2ConstPtr& msg);

  inline void transformPointCloud(const Eigen::Matrix4f& transform,
                                  const sensor_msgs::PointCloud2& in,
                                  sensor_msgs::PointCloud2& out);

public:
  // the latest available point cloud data
  sensor_msgs::PointCloud2 point_cloud;

  void setup(Eigen::Matrix4f transformation, ros::NodeHandle& node_handle,
             std::string topic_name, int queue_size);

  PointCloudSubscriber(){};
};

// source:
// https://github.com/ros-perception/perception_pcl/blob/indigo-devel/pcl_ros/src/transforms.cpp#L106
inline void
PointCloudSubscriber::transformPointCloud(const Eigen::Matrix4f& transform,
                                          const sensor_msgs::PointCloud2& in,
                                          sensor_msgs::PointCloud2& out)
{
  const size_t point_step = in.point_step;
  const size_t data_size = in.data.size();
  const size_t n = in.width * in.height;

  // copy the other data
  out.header = in.header;
  out.height = in.height;
  out.width = in.width;
  out.fields = in.fields;
  out.is_bigendian = in.is_bigendian;
  out.point_step = point_step;
  out.row_step = in.row_step;
  out.is_dense = in.is_dense;
  out.data.resize(data_size);

  // copy complete point cloud data since it contain colors as well
  std::memcpy(&out.data[0], &in.data[0], data_size);

  size_t index;
  Eigen::Vector4f pt_out;
  Eigen::Vector4f pt_in(0, 0, 0, 1);  // 4x1 vector
  for (size_t i = 0; i < n; i++)
  {
    index = i * point_step;
    /*
     * Eigen::Vector4f pt_in (*(float*)&in.data[index + 0],
     *                        *(float*)&in.data[index + 4],
     *                        *(float*)&in.data[index + 8], 1);
     */

    // fill emements of the vector by first 3 float elements (12 bytes)
    std::memcpy(&pt_in, &in.data[index], 12);

    // perform the transformation here
    // we don't want to make  copy of the data hence using noalias()
    pt_out.noalias() = transform * pt_in;

    // copy the data into output
    // we just want to copy first 3 float elements (12 bytes)
    std::memcpy(&out.data[index], &pt_out[0], 12);
  }
}

void PointCloudSubscriber::callback(const sensor_msgs::PointCloud2ConstPtr& msg)
{
  // we work with a temporary cloud i.e., temp_cloud once temp_cloud is
  // transformed it is simply assigned to point_cloud
  transformPointCloud(trans, *msg, temp_cloud);

  // make sure that other threads cannot access the point cloud while we are
  // assigning it
  pthread_mutex_lock(&my_mutex);
  point_cloud = temp_cloud;
  pthread_mutex_unlock(&my_mutex);
}

void PointCloudSubscriber::setup(Eigen::Matrix4f transformation,
                                 ros::NodeHandle& node_handle,
                                 std::string topic_name, int queue_size)
{
  trans = transformation;
  subscriber = node_handle.subscribe<sensor_msgs::PointCloud2>(
      topic_name, queue_size, &PointCloudSubscriber::callback, this,
      ros::TransportHints().tcpNoDelay());
}

class MergePC
{
private:
  int freq;
  std::string base_frame_id, pc1_frame_id, pc2_frame_id, pc3_frame_id;
  PointCloudSubscriber pc_sub1, pc_sub2, pc_sub3;
  ros::Publisher merged_pc_pub;
  boost::shared_ptr<ros::AsyncSpinner> spinner;

  Eigen::Matrix4f* fetchTransformations(double wait_time);

  inline void combinePointClouds(const sensor_msgs::PointCloud2& cloud1,
                                 const sensor_msgs::PointCloud2& cloud2,
                                 const sensor_msgs::PointCloud2& cloud3,
                                 sensor_msgs::PointCloud2& cloud_out);

public:
  MergePC();
  void run();
};

// source:
// https://github.com/ros-perception/perception_pcl/blob/118784a4dd06437847182fe5e77829003d103def/pcl_conversions/include/pcl_conversions/pcl_conversions.h#L612
inline void MergePC::combinePointClouds(const sensor_msgs::PointCloud2& cloud1,
                                        const sensor_msgs::PointCloud2& cloud2,
                                        const sensor_msgs::PointCloud2& cloud3,
                                        sensor_msgs::PointCloud2& cloud_out)
{
  // assign cloud 1 to output cloud
  cloud_out = cloud1;

  // get the lengths of each cloud
  size_t cloud1_len = cloud1.data.size();
  size_t cloud2_len = cloud2.data.size();
  size_t cloud3_len = cloud3.data.size();

  // set the parameters of output cloud
  cloud_out.height = 1;
  cloud_out.width = cloud1.width * cloud1.height +
                    cloud2.width * cloud2.height + cloud3.width * cloud3.height;

  cloud_out.is_dense =
      (!cloud1.is_dense || !cloud2.is_dense || !cloud3.is_dense) ? false : true;

  // reserve the memory
  cloud_out.data.resize(cloud1_len + cloud2_len + cloud3_len);

  // copy cloud 2 to output cloud
  std::memcpy(&cloud_out.data[cloud1_len], &cloud2.data[0], cloud2_len);

  // copy cloud 3 to output cloud
  std::memcpy(&cloud_out.data[cloud1_len + cloud2_len], &cloud3.data[0],
              cloud3_len);
}

Eigen::Matrix4f* MergePC::fetchTransformations(double wait_time)
{
  tf::TransformListener listener;
  tf::StampedTransform t1, t2, t3;

  try
  {
    listener.waitForTransform(base_frame_id, pc1_frame_id, ros::Time(0),
                              ros::Duration(wait_time));
    listener.lookupTransform(base_frame_id, pc1_frame_id, ros::Time(0), t1);
    listener.lookupTransform(base_frame_id, pc2_frame_id, ros::Time(0), t2);
    listener.lookupTransform(base_frame_id, pc3_frame_id, ros::Time(0), t3);
  }
  catch (tf::TransformException ex)
  {
    ROS_ERROR_STREAM("Unable to fetch static transformations. " << ex.what());
    ros::shutdown();
  }

  // create the array on heap
  Eigen::Matrix4f* transformations = new Eigen::Matrix4f[KINECT_COUNT];

  tf::StampedTransform tf_trans[] = { t1, t2, t3 };
  for (size_t i = 0; i < KINECT_COUNT; i++)
  {
    Eigen::Matrix4f eigen_transform;
    pcl_ros::transformAsMatrix(tf_trans[i], eigen_transform);
    transformations[i] = eigen_transform;
  }

  return transformations;
}

MergePC::MergePC()
{
  ros::NodeHandle nh("~");

  std::string pc1_topic, pc2_topic, pc3_topic, merge_pc_topic;
  nh.getParam("pc1_topic", pc1_topic);
  nh.getParam("pc2_topic", pc2_topic);
  nh.getParam("pc3_topic", pc3_topic);
  nh.getParam("merge_pc_topic", merge_pc_topic);

  nh.getParam("base_frame_id", base_frame_id);
  nh.getParam("pc1_frame_id", pc1_frame_id);
  nh.getParam("pc2_frame_id", pc2_frame_id);
  nh.getParam("pc3_frame_id", pc3_frame_id);

  double wait_time;
  nh.getParam("wait_time", wait_time);
  nh.getParam("freq", freq);

  Eigen::Matrix4f* transformations = fetchTransformations(wait_time);
  pc_sub1.setup(transformations[0], nh, pc1_topic, 1);
  pc_sub2.setup(transformations[1], nh, pc2_topic, 1);
  pc_sub2.setup(transformations[2], nh, pc3_topic, 1);

  // create a publisher for publishing merged point cloud
  merged_pc_pub = nh.advertise<sensor_msgs::PointCloud2>(merge_pc_topic, 1);

  // get the concurrent hardware thread count
  const int spported_threads = boost::thread::hardware_concurrency();
  ROS_DEBUG_STREAM("Number of heardware supported threads for this CPU is "
                   << spported_threads);

  // we need one thread for each kinect
  const int desired_threads = KINECT_COUNT;

  // show warning to user
  if (desired_threads > spported_threads)
    ROS_WARN_STREAM(
        "It is suggested to upgrade the CPU for better performance");
  else
    ROS_DEBUG_STREAM("Initializing " << desired_threads
                                     << " additional threads");

  // lazy initialization for boost::shared_ptr
  // source: https://stackoverflow.com/a/12997218/1175065
  spinner.reset(new ros::AsyncSpinner(desired_threads));
}

void MergePC::run()
{
  // make sure we have the data. so we need to start the spinner
  spinner->start();

  ros::Rate loop_rate(freq);
  sensor_msgs::PointCloud2 cloud_out;

  while (ros::ok())
  {
    combinePointClouds(pc_sub1.point_cloud, pc_sub2.point_cloud,
                       pc_sub3.point_cloud, cloud_out);

    // we need to set header before publishing it
    cloud_out.header.frame_id = base_frame_id;
    cloud_out.header.stamp = ros::Time::now();
    merged_pc_pub.publish(cloud_out);

    loop_rate.sleep();
  }
}

int main(int argc, char** argv)
{
  ros::init(argc, argv, "merge_point_clouds_node",
            ros::init_options::AnonymousName);

  MergePC merge_point_clouds;
  merge_point_clouds.run();

  return 0;
}


/*
 *                   GridGain Community Edition Licensing
 *                   Copyright 2019 GridGain Systems, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License") modified with Commons Clause
 * Restriction; 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.
 *
 * Commons Clause Restriction
 *
 * The Software is provided to you by the Licensor under the License, as defined below, subject to
 * the following condition.
 *
 * Without limiting other conditions in the License, the grant of rights under the License will not
 * include, and the License does not grant to you, the right to Sell the Software.
 * For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you
 * under the License to provide to third parties, for a fee or other consideration (including without
 * limitation fees for hosting or consulting/ support services related to the Software), a product or
 * service whose value derives, entirely or substantially, from the functionality of the Software.
 * Any license notice or attribution required by the License must also include this Commons Clause
 * License Condition notice.
 *
 * For purposes of the clause above, the “Licensor” is Copyright 2019 GridGain Systems, Inc.,
 * the “License” is the Apache License, Version 2.0, and the Software is the GridGain Community
 * Edition software provided with this notice.
 */

#ifndef _MSC_VER
#   define BOOST_TEST_DYN_LINK
#endif

#include <boost/test/unit_test.hpp>

#include "sql_test_suite_fixture.h"

using namespace ignite;

using namespace boost::unit_test;

BOOST_FIXTURE_TEST_SUITE(SqlValueExpressionTestSuite, ignite::SqlTestSuiteFixture)

BOOST_AUTO_TEST_CASE(TestCase)
{
    TestType in;

    in.i32Field = 82;

    testCache.Put(1, in);

    SqlTestSuiteFixture::CheckSingleResult<SQLINTEGER>(
        "SELECT "
            "CASE i32Field WHEN 82 "
                "THEN (i32Field / 2) "
                "ELSE (i32Field / 3) "
            "END "
        "FROM TestType", in.i32Field / 2);

    SqlTestSuiteFixture::CheckSingleResult<SQLINTEGER>(
        "SELECT "
            "CASE i32Field WHEN 22 "
                "THEN (i32Field / 2) "
                "ELSE (i32Field / 3) "
            "END "
        "FROM TestType", in.i32Field / 3);
}

BOOST_AUTO_TEST_CASE(TestCast)
{
    TestType in;

    in.i32Field = 12345;
    in.strField = "54321";

    testCache.Put(1, in);

    CheckSingleResult<SQLINTEGER>("SELECT CAST(strField AS INT) + i32Field FROM TestType",
        common::LexicalCast<int32_t>(in.strField) + in.i32Field);

    CheckSingleResult<std::string>("SELECT CAST(i32Field AS VARCHAR) || strField FROM TestType",
        common::LexicalCast<std::string>(in.i32Field) + in.strField);
}

BOOST_AUTO_TEST_CASE(TestCoalesce)
{
    CheckSingleResult<std::string>("SELECT COALESCE('One', 'Two', 'Three')", "One");
    CheckSingleResult<std::string>("SELECT COALESCE(NULL, 'Two', 'Three')", "Two");
    CheckSingleResult<std::string>("SELECT COALESCE(NULL, 'Two', NULL)", "Two");
    CheckSingleResult<std::string>("SELECT COALESCE(NULL, NULL, 'Three')", "Three");
}

BOOST_AUTO_TEST_CASE(TestNullif)
{
    TestType in;

    in.strField = "SomeValue";

    testCache.Put(1, in);

    CheckSingleResult<std::string>("SELECT NULLIF(strField, 'blablabla') FROM TestType", in.strField);
    CheckSingleResult<std::string>("SELECT NULLIF(strField, 'SomeValue') FROM TestType", "");
}

BOOST_AUTO_TEST_SUITE_END()


//==============================================================================
//         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
//==============================================================================
/*!
 * \file
**/
#ifndef BOOST_SIMD_TOOLBOX_CONSTANT_CONSTANTS_REAL_SPLAT_HPP_INCLUDED
#define BOOST_SIMD_TOOLBOX_CONSTANT_CONSTANTS_REAL_SPLAT_HPP_INCLUDED

#include <boost/simd/include/functor.hpp>
#include <boost/simd/sdk/meta/float.hpp>
#include <boost/simd/sdk/meta/double.hpp>
#include <boost/simd/sdk/constant/constant.hpp>

/*!
 * \ingroup boost_simd_constant
 * \defgroup boost_simd_pure_constant_real_splat Real_splat
 *
 * \par Description
 * TODO Put description here
 *
 * \par Header file
 *
 * \code
 * #include <nt2/include/functions/real_splat.hpp>
 * \endcode
 *
 *
 * \synopsis
 *
 * \code
 * namespace boost::simd
 * {
 *   template <class T,class A0>
 *     meta::call<tag::real_splat_(A0)>::type
 *     Real_splat();
 * }
 * \endcode
 *
 *
 * \param T template parameter of Real_splat
 *
 * \return type T value
 *
 *
**/

namespace boost { namespace simd
{
  namespace tag
  {
    /*!
     * \brief Define the tag Real_splat of functor Real_splat
     *        in namespace boost::simd::tag for toolbox boost.simd.constant
    **/
    template<boost::simd::uint32_t F,boost::simd::uint64_t D>
    struct Realpattern : ext::pure_constant_< Realpattern<F, D> >
    {
      template<class Target, class Dummy=void>
      struct apply {};

      template<class T,class Dummy>
      struct apply<boost::dispatch::meta::single_<T>,Dummy> : meta::single_<F> {};

      template<class T, class Dummy>
      struct apply<boost::dispatch::meta::double_<T>,Dummy> : meta::double_<D> {};
    };
  }
  //////////////////////////////////////////////////////////////////////////////
  // Generic real value splatter from a bit patterns
  //////////////////////////////////////////////////////////////////////////////
  template<class Target, boost::simd::uint64_t D, boost::simd::uint32_t F>
  inline Target real_constant()
  {
    boost::dispatch::functor< tag::Realpattern<F,D> > callee;
    return callee( boost::dispatch::meta::as_<Target>() );
  }

  //////////////////////////////////////////////////////////////////////////////
  // Generic real value splatter from a bit patterns of float
  //////////////////////////////////////////////////////////////////////////////
  template<class Target, boost::simd::uint32_t F>
  inline Target single_constant()
  {
    typename dispatch::make_functor<tag::Realpattern<F,0>, Target>::type callee;
    return callee( boost::dispatch::meta::as_<Target>() );
  }

  //////////////////////////////////////////////////////////////////////////////
  // Generic real value splatter from a bit patterns of double
  //////////////////////////////////////////////////////////////////////////////
  template<class Target, boost::simd::uint64_t D>
  inline Target double_constant()
  {
    typename dispatch::make_functor<tag::Realpattern<0,D>, Target>::type callee;
    return callee( boost::dispatch::meta::as_<Target>() );
  }

  //////////////////////////////////////////////////////////////////////////////
  // Generic real value splatter from a bit patterns dependant on target type
  //////////////////////////////////////////////////////////////////////////////
  template<class Target, uint64_t Value> inline Target Const()
  {
    typedef tag::Realpattern<boost::simd::uint32_t(Value),Value> patterns;
    typename dispatch::make_functor<patterns, Target>::type callee;
    return callee( boost::dispatch::meta::as_<Target>() );
  }
} }

#include <boost/simd/sdk/constant/common.hpp>

#endif


// Distributed under the MIT License.
// See LICENSE.txt for details.

#pragma once

#include <array>
#include <boost/functional/hash.hpp>
#include <cstddef>
#include <utility>

#include "DataStructures/DataBox/Prefixes.hpp"
#include "DataStructures/DataVector.hpp"
#include "DataStructures/FixedHashMap.hpp"
#include "DataStructures/Tensor/Slice.hpp"
#include "DataStructures/Tensor/Tensor.hpp"
#include "DataStructures/Variables.hpp"
#include "Domain/ElementMap.hpp"
#include "Domain/LogicalCoordinates.hpp"
#include "Domain/Structure/Direction.hpp"
#include "Domain/Structure/DirectionMap.hpp"
#include "Domain/Structure/Element.hpp"
#include "Domain/Structure/ElementId.hpp"
#include "Domain/Structure/MaxNumberOfNeighbors.hpp"
#include "Domain/Structure/Neighbors.hpp"
#include "Evolution/DgSubcell/NeighborData.hpp"
#include "Evolution/DgSubcell/SliceData.hpp"
#include "Evolution/Systems/ScalarAdvection/FiniteDifference/Reconstructor.hpp"
#include "Evolution/Systems/ScalarAdvection/Tags.hpp"
#include "NumericalAlgorithms/Spectral/Mesh.hpp"
#include "NumericalAlgorithms/Spectral/Spectral.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/MakeArray.hpp"
#include "Utilities/TMPL.hpp"

namespace TestHelpers {
/*!
 * \brief Defines functions useful for testing advection subcell
 */
namespace ScalarAdvection::fd {
template <size_t Dim, typename F>
FixedHashMap<maximum_number_of_neighbors(Dim) + 1,
             std::pair<Direction<Dim>, ElementId<Dim>>,
             evolution::dg::subcell::NeighborData,
             boost::hash<std::pair<Direction<Dim>, ElementId<Dim>>>>
compute_neighbor_data(const Mesh<Dim>& subcell_mesh,
                      const tnsr::I<DataVector, Dim, Frame::ElementLogical>&
                          volume_logical_coords,
                      const DirectionMap<Dim, Neighbors<Dim>>& neighbors,
                      const size_t ghost_zone_size,
                      const F& compute_variables_of_neighbor_data) {
  FixedHashMap<maximum_number_of_neighbors(Dim) + 1,
               std::pair<Direction<Dim>, ElementId<Dim>>,
               evolution::dg::subcell::NeighborData,
               boost::hash<std::pair<Direction<Dim>, ElementId<Dim>>>>
      neighbor_data{};
  for (const auto& [direction, neighbors_in_direction] : neighbors) {
    REQUIRE(neighbors_in_direction.size() ==
            1);  // currently only support one neighbor in each direction
    const ElementId<Dim>& neighbor_id = *neighbors_in_direction.begin();

    auto neighbor_logical_coords = volume_logical_coords;
    neighbor_logical_coords.get(direction.dimension()) +=
        direction.sign() * 2.0;
    const auto neighbor_vars_for_reconstruction =
        compute_variables_of_neighbor_data(neighbor_logical_coords);

    DirectionMap<Dim, bool> directions_to_slice{};
    directions_to_slice[direction.opposite()] = true;
    const auto sliced_data = evolution::dg::subcell::detail::slice_data_impl(
        gsl::make_span(neighbor_vars_for_reconstruction.data(),
                       neighbor_vars_for_reconstruction.size()),
        subcell_mesh.extents(), ghost_zone_size, directions_to_slice);
    REQUIRE(sliced_data.size() == 1);
    REQUIRE(sliced_data.contains(direction.opposite()));
    neighbor_data[std::pair{direction, neighbor_id}].data_for_reconstruction =
        sliced_data.at(direction.opposite());
  }
  return neighbor_data;
}

template <size_t Dim, typename Reconstructor>
void test_reconstructor(const size_t points_per_dimension,
                        const Reconstructor& derived_reconstructor) {
  // 1. create the scalar field U to reconstruct being linear to coords
  // 2. send through reconstruction
  // 3. check if U was reconstructed correctly

  const ::ScalarAdvection::fd::Reconstructor<Dim>& reconstructor =
      derived_reconstructor;
  static_assert(
      tmpl::list_contains_v<
          typename ::ScalarAdvection::fd::Reconstructor<Dim>::creatable_classes,
          Reconstructor>);

  // create an element and its neighbor elements
  DirectionMap<Dim, Neighbors<Dim>> neighbors{};
  for (size_t i = 0; i < 2 * Dim; ++i) {
    neighbors[gsl::at(Direction<Dim>::all_directions(), i)] =
        Neighbors<Dim>{{ElementId<Dim>{i + 1, {}}}, {}};
  }
  const Element<Dim> element{ElementId<Dim>{0, {}}, neighbors};

  // a simple, linear form of the scalar field U for testing purpose
  //   * U(x)   = x + 1       (for 1D)
  //   * U(x,y) = x + 2y + 1  (for 2D)
  using scalar_u = ::ScalarAdvection::Tags::U;
  const auto compute_solution = [](const auto& coords) {
    Variables<tmpl::list<scalar_u>> vars{get<0>(coords).size(), 0.0};
    for (size_t i = 0; i < Dim; ++i) {
      get(get<scalar_u>(vars)) += (i + 1) * coords.get(i);
    }
    get(get<scalar_u>(vars)) += 1.0;
    return vars;
  };

  // compute neighbor data
  const Mesh<Dim> subcell_mesh{points_per_dimension,
                               Spectral::Basis::FiniteDifference,
                               Spectral::Quadrature::CellCentered};
  auto logical_coords = logical_coordinates(subcell_mesh);
  const FixedHashMap<maximum_number_of_neighbors(Dim) + 1,
                     std::pair<Direction<Dim>, ElementId<Dim>>,
                     evolution::dg::subcell::NeighborData,
                     boost::hash<std::pair<Direction<Dim>, ElementId<Dim>>>>
      neighbor_data = compute_neighbor_data(
          subcell_mesh, logical_coords, element.neighbors(),
          reconstructor.ghost_zone_size(), compute_solution);

  // create Variables on lower and upper faces to perform reconstruction
  using dg_package_data_argument_tags =
      tmpl::list<scalar_u,
                 ::Tags::Flux<scalar_u, tmpl::size_t<Dim>, Frame::Inertial>,
                 ::ScalarAdvection::Tags::VelocityField<Dim>>;
  const size_t reconstructed_num_pts =
      (subcell_mesh.extents(0) + 1) *
      subcell_mesh.extents().slice_away(0).product();
  std::array<Variables<dg_package_data_argument_tags>, Dim> vars_on_lower_face =
      make_array<Dim>(
          Variables<dg_package_data_argument_tags>(reconstructed_num_pts));
  std::array<Variables<dg_package_data_argument_tags>, Dim> vars_on_upper_face =
      make_array<Dim>(
          Variables<dg_package_data_argument_tags>(reconstructed_num_pts));

  Variables<tmpl::list<scalar_u>> volume_vars{
      subcell_mesh.number_of_grid_points()};
  volume_vars.assign_subset(compute_solution(logical_coords));

  // call the reconstruction
  dynamic_cast<const Reconstructor&>(reconstructor)
      .reconstruct(make_not_null(&vars_on_lower_face),
                   make_not_null(&vars_on_upper_face), volume_vars, element,
                   neighbor_data, subcell_mesh);

  for (size_t dim = 0; dim < Dim; ++dim) {
    // construct face-centered coordinates
    const auto basis = make_array<Dim>(Spectral::Basis::FiniteDifference);
    auto quadrature = make_array<Dim>(Spectral::Quadrature::CellCentered);
    auto extents = make_array<Dim>(points_per_dimension);
    gsl::at(extents, dim) = points_per_dimension + 1;
    gsl::at(quadrature, dim) = Spectral::Quadrature::FaceCentered;
    const Mesh<Dim> face_centered_mesh{extents, basis, quadrature};
    auto logical_coords_face_centered = logical_coordinates(face_centered_mesh);

    // check reconstructed values for reconstruct() function
    Variables<dg_package_data_argument_tags> expected_face_values{
        face_centered_mesh.number_of_grid_points()};
    expected_face_values.assign_subset(
        compute_solution(logical_coords_face_centered));

    CHECK_ITERABLE_APPROX(get<scalar_u>(gsl::at(vars_on_lower_face, dim)),
                          get<scalar_u>(expected_face_values));
    CHECK_ITERABLE_APPROX(get<scalar_u>(gsl::at(vars_on_upper_face, dim)),
                          get<scalar_u>(expected_face_values));

    // check reconstructed values for reconstruct_fd_neighbor() function
    Variables<dg_package_data_argument_tags> vars_on_mortar_face{0};
    Variables<dg_package_data_argument_tags> expected_mortar_face_values{0};

    for (size_t upper_or_lower = 0; upper_or_lower < 2; ++upper_or_lower) {
      // iterate over upper and lower direction
      Direction<Dim> direction =
          gsl::at(Direction<Dim>::all_directions(), 2 * dim + upper_or_lower);

      if constexpr (Dim == 1) {
        // 1D mortar has only one face point
        vars_on_mortar_face.initialize(1);
        expected_mortar_face_values.initialize(1);
      } else {
        const size_t num_mortar_face_pts =
            subcell_mesh.extents().slice_away(direction.dimension()).product();
        vars_on_mortar_face.initialize(num_mortar_face_pts);
        expected_mortar_face_values.initialize(num_mortar_face_pts);
      }

      // call the reconstruction
      dynamic_cast<const Reconstructor&>(reconstructor)
          .reconstruct_fd_neighbor(make_not_null(&vars_on_mortar_face),
                                   volume_vars, element, neighbor_data,
                                   subcell_mesh, direction);

      // slice face-centered variables to get the values on the mortar
      data_on_slice(make_not_null(&get<scalar_u>(expected_mortar_face_values)),
                    get<scalar_u>(expected_face_values),
                    face_centered_mesh.extents(), direction.dimension(),
                    direction.side() == Side::Lower
                        ? 0
                        : extents[direction.dimension()] - 1);

      CHECK_ITERABLE_APPROX(get<scalar_u>(vars_on_mortar_face),
                            get<scalar_u>(expected_mortar_face_values));
    }
  }
}

}  // namespace ScalarAdvection::fd
}  // namespace TestHelpers


//////////////////////////////////////////////////////////////////////////////
// cross_validation::estimator::concept::predictor.hpp                      //
//                                                                          //
//  (C) Copyright 2009 Erwann Rogard                                        //
//  Use, modification and distribution are subject to the                   //
//  Boost Software License, Version 1.0. (See accompanying file             //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)        //
//////////////////////////////////////////////////////////////////////////////
#ifndef  BOOST_STATISTICS_DETAIL_CROSS_VALIDATION_ESTIMATOR_CONCEPT_PREDICTOR_HPP_ER_2009
#define  BOOST_STATISTICS_DETAIL_CROSS_VALIDATION_ESTIMATOR_CONCEPT_PREDICTOR_HPP_ER_2009
#include <boost/concept_check.hpp>
#include <boost/statistics/detail/cross_validation/estimator/meta/estimator.hpp>
#include <boost/statistics/detail/cross_validation/estimator/meta/input_data_type.hpp>
#include <boost/statistics/detail/cross_validation/estimator/meta/output_data_type.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace cross_validation{
namespace estimator{
namespace concept{

    // Refines Predictor with a return type
    template <typename B>
    class Predictor2 : public B
    {
        typedef typename B::estimator_type  e_;
        typedef typename B::input_data_type i_;
        typedef typename B::output_data_type o_;

        public:

        BOOST_CONCEPT_USAGE(Predictor2)
        {
            o = e.predict( i );
        }

    private:
        e_ e;
        i_ i;
        o_ o;

    };

}// concept
}// estimator
}// cross_validation
}// detail
}// statistics
}// boost

#endif


#include <boost/hana/fwd/intersection.hpp>


/*!
 * @file
 * Contains unit tests for `boost::mpl11::compose`.
 */

#include <boost/mpl11/functional.hpp>

#include <boost/mpl11/detail/dependent.hpp>
#include <boost/mpl11/detail/std_is_same.hpp>

using namespace boost::mpl11;
using detail::std_is_same;

struct w { using type = w; };
struct x { using type = x; };
struct y { using type = y; };
template <typename ...xs> struct f { using type = f<typename xs::type...>; };
template <typename ...xs> struct g { using type = g<typename xs::type...>; };
template <typename ...xs> struct h { using type = h<typename xs::type...>; };

template <template <typename ...> class ...fs>
struct composing {
    template <typename ...args>
    struct with {
        template <typename result>
        struct is : detail::dependent<result>::template type<composing> {
            static_assert(std_is_same<
                typename compose<lift<fs>...>::type::
                template apply<args...>::type,
                result
            >::value, "");
        };
    };
};

struct tests
    : composing<f>
        ::with<>::is<f<>>
        ::with<w>::is<f<w>>
        ::with<w, x>::is<f<w, x>>
        ::with<w, x, y>::is<f<w, x, y>>

    , composing<f, g>
        ::with<>::is<f<g<>>>
        ::with<w>::is<f<g<w>>>
        ::with<w, x>::is<f<g<w>, x>>
        ::with<w, x, y>::is<f<g<w>, x, y>>

    , composing<f, g, h>
        ::with<>::is<f<g<h<>>>>
        ::with<w>::is<f<g<h<w>>>>
        ::with<w, x>::is<f<g<h<w>>, x>>
        ::with<w, x, y>::is<f<g<h<w>>, x, y>>
{ };

int main() { }