#include "Memoization.hpp"
#include <cmath>
#include <complex>
#include <cstdlib>
#include <ctime>
#include <iostream>

#include <boost/timer/timer.hpp> // timers
#include "boost/iostreams/stream.hpp"
#include "boost/iostreams/device/null.hpp"

typedef std::complex<double> cdT;
using boost::timer::cpu_timer;
using boost::timer::cpu_times;
using boost::timer::nanosecond_type;
/*! compile with icpc/clang/g++ -std=c++14 test_Memoization.cpp -I/home/stobbe/software/include/boost -L/home/stobbe/software/lib -lboost_timer -lboost_system
 */

inline cdT iexp(double a, double phi){
	return std::exp(a + std::complex<double>(0.0,phi));
}

void test_perf(){
	// setting up primitive container as comparison
	int N = 2000;
	cdT **test_arr = new cdT*[N];
	for(int i = 0; i < N; ++i) {
    	test_arr[i] = new cdT[N];
	}
	boost::iostreams::stream< boost::iostreams::null_sink > nullStream( ( boost::iostreams::null_sink() ) );

	// setting up timer
	nanosecond_type last(0);
	cpu_timer timer;

	// setting up memoize
	auto memo_iexp = Memoize::memoize( iexp );

	for(int a = 0; a < N; a++){
		for(int phi_i = 0; phi_i < N; phi_i++ ){
			double phi = phi_i*2*3.14159265/N;
			test_arr[a][phi_i] = iexp(a,phi);

		}
	}

  	cpu_times elapsed_times(timer.elapsed());
  	nanosecond_type elapsed(elapsed_times.system + elapsed_times.user);
	std::cout << "naive array initialized, time:\t" << timer.format(5) << std::endl;
	timer.start();
	for(int a = 0; a < N; a++){
		for(int phi_i = 0; phi_i < N; phi_i++ ){
			double phi = phi_i*2*3.14159265/N;
			memo_iexp(a,phi);

		}
	}
	std::cout << "memoized initialized, time:\t" << timer.format(5) << std::endl;
	std::srand(std::time(0));
	timer.start();

	for(int phi_i = 0; phi_i < N; phi_i++ ){
		for(int a = 0; a < N; a++){
			auto a_r = std::rand()%N;
			auto phi_r = std::rand()%N;
			double phi = phi_r*2*3.14159265/N;
			nullStream << test_arr[a_r][phi_r] << phi;

		}
	}
	std::cout << "naive array read, time:\t" << timer.format(5) << std::endl;
	timer.start();

	for(int phi_i = 0; phi_i < N; phi_i++ ){
		for(int a = 0; a < N; a++){
			auto a_r = std::rand()%N;
			auto phi_r = std::rand()%N;
			double phi = phi_r*2*3.14159265/N;
			nullStream << memo_iexp(a_r,phi) << phi;

		}
	}
	std::cout << "memoized read, time:\t" << timer.format(5) << std::endl;

	for(int i = 0; i < N; ++i) {
    	delete [] test_arr[i];
	}
	delete [] test_arr;
}

int main(void)
{
	test_perf();
    return 0;
}


#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

#include <boost/mpl/vector.hpp>
#include <boost/mpl/at.hpp>
#include <boost/mpl/quote.hpp>
#include <boost/mpl/protect.hpp>
#include <boost/mpl/bind.hpp>
#include <boost/mpl/list.hpp>

#include <cmath>
#include <boost/safe_float.hpp>
#include <boost/safe_float/convenience.hpp>
#include <boost/safe_float/policy/check_division_overflow.hpp>
#include <boost/safe_float/policy/check_division_underflow.hpp>
#include <boost/safe_float/policy/check_division_inexact.hpp>
#include <boost/safe_float/policy/check_division_invalid_result.hpp>
#include <boost/safe_float/policy/check_division_by_zero.hpp>

//types to be tested
using test_types=boost::mpl::list<
    float, double, long double
>;

using namespace boost::safe_float;

/**
  This test suite checks different policies on division operations using default parameters for the other policies.
  */
BOOST_AUTO_TEST_SUITE( safe_float_division_test_suite )

BOOST_AUTO_TEST_CASE_TEMPLATE( safe_float_division_throws_on_overflow, FPT, test_types){
    // define two FPT numbers suppose to positive overflow
    FPT a = std::numeric_limits<FPT>::max();
    FPT b = std::numeric_limits<FPT>::min();
    // check FPT overflows to inf after add
    BOOST_CHECK(std::isinf(a/b));

    // construct safe_float version of the same two numbers
    safe_float<FPT, policy::check_division_overflow> c(std::numeric_limits<FPT>::max());
    safe_float<FPT, policy::check_division_overflow> d(std::numeric_limits<FPT>::min());

    // check the division throws
    BOOST_CHECK_THROW(c/d, std::exception);

    // define two FPT numbers suppose to negative overflow
    FPT e = std::numeric_limits<FPT>::lowest();
    FPT f = std::numeric_limits<FPT>::min();
    // check FPT overflows to inf after add
    BOOST_CHECK(std::isinf(e/f));

    // construct safe_float version of the same two numbers
    safe_float<FPT, policy::check_division_overflow> g(std::numeric_limits<FPT>::lowest());
    safe_float<FPT, policy::check_division_overflow> h(std::numeric_limits<FPT>::min());

    // check the division throws
    BOOST_CHECK_THROW(g/h, std::exception);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( safe_float_division_inexact_rounding, FPT, test_types){
    // define two FPT numbers suppose to produce inexact rounding
    FPT a = 1;
    FPT b = 1.815170982922064060217925973717001397744752466678619384765625L;

    // check multiplying and dividing do not give the same number back.
    BOOST_CHECK((a/b)*b != a);

    // construct safe_float version of the same two numbers
    safe_float<FPT, policy::check_division_inexact> c(a);
    safe_float<FPT, policy::check_division_inexact> d(b);

    // check the division throws
    BOOST_CHECK_THROW(c/d, std::exception);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( safe_float_division_underflow, FPT, test_types){
    // define two FPT numbers suppose to underflow
    FPT a = std::numeric_limits<FPT>::min();
    FPT b = std::numeric_limits<FPT>::max();

    // check dividing big numbers underflows to 0.
    BOOST_CHECK((a/b) == 0);

    // construct safe_float version of the same two numbers
    safe_float<FPT, policy::check_division_underflow> c(a);
    safe_float<FPT, policy::check_division_underflow> d(b);

    // check the division throws
    BOOST_CHECK_THROW(c/d, std::exception);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( safe_float_division_invalid_result, FPT, test_types){
    // define two FPT numbers suppose to produce a NAN
    FPT a = std::numeric_limits<FPT>::infinity();
    FPT b = std::numeric_limits<FPT>::infinity();

    // check adding produced NaN
    BOOST_CHECK(std::isnan(a/b));

    // construct safe_float version of the same two numbers
    safe_float<FPT, policy::check_division_invalid_result> c(std::numeric_limits<FPT>::infinity());
    safe_float<FPT, policy::check_division_invalid_result> d(std::numeric_limits<FPT>::infinity());

    // check the division throws
    BOOST_CHECK_THROW(c/d, std::exception);
}

BOOST_AUTO_TEST_CASE_TEMPLATE( safe_float_division_by_zero, FPT, test_types){
    // construct safe_float producing divide by zero
    safe_float<FPT, policy::check_division_by_zero> c(FPT(1));
    safe_float<FPT, policy::check_division_by_zero> d(FPT(0));

    // check the division throws
    BOOST_CHECK_THROW(c/d, std::exception);
}

BOOST_AUTO_TEST_SUITE_END()


#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
#include <vector>
#include "../model_distribution.hpp"
#include "../monte_carlo_integration.hpp"
#include "../multinomial.hpp"
#include "../multivariate_guassian.hpp"
#include "../types.hpp"
#include "../utils.hpp"
#include "gtest/gtest.h"

// Not sure why but these examples only match to a very small accuracy.

using namespace FilterModel;

namespace FilterModel {

TEST(integrate, XFromZeroToOne) {
    std::default_random_engine generator;

    std::function<double(double)> f = [](double x) { return x; };

    std::function<double(std::default_random_engine&)> sampler =
        [](std::default_random_engine& generator) {
            std::uniform_real_distribution<double> x_distribution(0.0, 1.0);

            return x_distribution(generator);
        };

    double volume = 1.0;

    double result = integrate(f, sampler, generator, 1, 100000);

    ASSERT_NEAR(result, 1.0 / 2.0, 0.01);
}

// TODO(joschnei): tests for some alpha != 1 should be written.
TEST(integrate, IntegrationCloseToExact) {
    boost::log::core::get()->set_filter(boost::log::trivial::severity >=
                                        boost::log::trivial::debug);

    std::default_random_engine generator;

    alpha_t alpha({true, true, true});
    category_counts_t object_counts({100, 100, 100});
    int n_positive = 200;
    int n_negative = 100;
    int n = n_positive + n_negative;

    delta_t delta({1.0 / 3.0, 1.0 / 2.0, 1.0 / 6.0});

    // Exact
    double sum_over_k_negative_exact = ModelDistribution::calculate_sum_over_k_negative_exact(
        n_positive, n_negative, alpha, delta, object_counts);

    // Approx
    ModelDistribution model_distribution({}, generator, false);
    double sum_over_k_negative_approx = model_distribution.calculate_sum_over_k_negative_approx_2(
        n_positive, n_negative, alpha, delta, object_counts);

    ASSERT_NEAR(sum_over_k_negative_approx, sum_over_k_negative_exact, 0.01);
}

}  // namespace FilterModel


/*=============================================================================
    Copyright (c) 2001-2007 Joel de Guzman

    Distributed under the Boost Software License, Version 1.0. (See accompanying
    file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
==============================================================================*/

#include <cmath>
#include <boost/function.hpp>
#include <boost/phoenix/core.hpp>
#include <boost/phoenix/operator.hpp>
#include <boost/phoenix/stl/cmath.hpp>
#include <boost/detail/lightweight_test.hpp>

int main()
{
    double eps = 0.000001;
    using namespace boost::phoenix::arg_names;
    boost::function<bool(double, double)> f = fabs(_1 - _2) < eps;

    double x = boost::phoenix::pow(_1,_2)(2.,0.);

    BOOST_TEST(f(0.0, 0 * eps));
    BOOST_TEST(!f(0.0, eps));
    BOOST_TEST(fabs(x-1.) < eps );
}


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

#include <boost/simd/include/functor.hpp>
#include <boost/simd/sdk/constant/register.hpp>
#include <boost/simd/sdk/constant/constant.hpp>

/*!
 * \ingroup boost_simd_constant
 * \defgroup boost_simd_constant_oneo_5 Oneo_5
 *
 * \par Description
 * Constant Oneo_5 = 1/5
 *
 * \par Header file
 *
 * \code
 * #include <nt2/include/functions/oneo_5.hpp>
 * \endcode
 *
 *
 * \synopsis
 *
 * \code
 * namespace boost::simd
 * {
 *   template <class T,class A0>
 *     meta::call<tag::oneo_5_(A0)>::type
 *     Oneo_5();
 * }
 * \endcode
 *
 *
 * \param T template parameter of Oneo_5
 *
 * \return type T value
 *
 *
**/

namespace boost { namespace simd
{
  namespace tag
  {
    /*!
     * \brief Define the tag Oneo_5 of functor Oneo_5
     *        in namespace boost::simd::tag for toolbox boost.simd.constant
    **/
    BOOST_SIMD_CONSTANT_REGISTER( Oneo_5, int, 0
                                , 0x3e4ccccd, 0x3fc999999999999all
                                );
  }

  BOOST_SIMD_CONSTANT_IMPLEMENTATION(boost::simd::tag::Oneo_5, Oneo_5)
} }

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

#endif


// Andrew Naplavkov

#ifndef BRIG_GDAL_DETAIL_TRANSFORM_HPP
#define BRIG_GDAL_DETAIL_TRANSFORM_HPP

#include <boost/geometry/geometry.hpp>
#include <brig/boost/geometry.hpp>

namespace brig { namespace gdal { namespace detail {

class transform {
  double to_x(double p, double l) const  { return coef[0] + p*coef[1] + l*coef[2]; }
  double to_y(double p, double l) const  { return coef[3] + p*coef[4] + l*coef[5]; }
  double divisor() const  { return coef[1]*coef[5] - coef[2]*coef[4]; }
  double to_p(double x, double y) const  { return (coef[5]*(x - coef[0]) - coef[2]*(y - coef[3])) / divisor(); }
  double to_l(double x, double y) const  { return (coef[1]*(y - coef[3]) - coef[4]*(x - coef[0])) / divisor(); }

public:
  double coef[6];

  boost::polygon pixel_to_proj(const boost::box& env) const;
  boost::polygon proj_to_pixel(const boost::box& env) const;
}; // transform

inline boost::polygon transform::pixel_to_proj(const boost::box& env) const
{
  using namespace std;
  using namespace brig::boost;
  const double pmin(env.min_corner().get<0>()), lmin(env.min_corner().get<1>()), pmax(env.max_corner().get<0>()), lmax(env.max_corner().get<1>());
  polygon poly;
  ::boost::geometry::append(poly, point(to_x(pmin, lmax), to_y(pmin, lmax)));
  ::boost::geometry::append(poly, point(to_x(pmax, lmax), to_y(pmax, lmax)));
  ::boost::geometry::append(poly, point(to_x(pmax, lmin), to_y(pmax, lmin)));
  ::boost::geometry::append(poly, point(to_x(pmin, lmin), to_y(pmin, lmin)));
  ::boost::geometry::append(poly, point(to_x(pmin, lmax), to_y(pmin, lmax)));
  return poly;
}

inline boost::polygon transform::proj_to_pixel(const boost::box& env) const
{
  using namespace std;
  using namespace brig::boost;
  const double xmin(env.min_corner().get<0>()), ymin(env.min_corner().get<1>()), xmax(env.max_corner().get<0>()), ymax(env.max_corner().get<1>());
  polygon poly;
  ::boost::geometry::append(poly, point(to_p(xmin, ymax), to_l(xmin, ymax)));
  ::boost::geometry::append(poly, point(to_p(xmin, ymin), to_l(xmin, ymin)));
  ::boost::geometry::append(poly, point(to_p(xmax, ymin), to_l(xmax, ymin)));
  ::boost::geometry::append(poly, point(to_p(xmax, ymax), to_l(xmax, ymax)));
  ::boost::geometry::append(poly, point(to_p(xmin, ymax), to_l(xmin, ymax)));
  return poly;
}

} } } // brig::gdal::detail

#endif // BRIG_GDAL_DETAIL_TRANSFORM_HPP


// smooth_feedback: Control theory on Lie groups
// https://github.com/pettni/smooth_feedback
//
// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
//
// Copyright (c) 2021 Petter Nilsson
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#ifndef SMOOTH__FEEDBACK__COLLOCATION__DYN_ERROR_HPP_
#define SMOOTH__FEEDBACK__COLLOCATION__DYN_ERROR_HPP_

/**
 * @file
 * @brief Collocation dynamics constraints.
 */

#include <Eigen/Core>

#include "mesh.hpp"

namespace smooth::feedback {

/**
 * @brief Calculate relative dynamics errors for each interval in mesh.
 *
 * @param f dynamics function
 * @param m Mesh
 * @param t0 initial time variable
 * @param tf final time variable
 * @param xfun state trajectory
 * @param ufun input trajectory
 *
 * @return vector with relative errors for every interval in m
 */
Eigen::VectorXd mesh_dyn_error(
  auto && f, const MeshType auto & m, const double t0, const double tf, auto && xfun, auto && ufun)
{
  using smooth::utils::zip;
  static constexpr auto Nx = Dof<std::invoke_result_t<decltype(xfun), double>>;

  static_assert(Nx > 0, "Static size required");

  const auto N = m.N_ivals();

  Eigen::VectorXd ival_errs(N);

  // for each interval
  for (auto ival = 0u, M = 0u; ival < N; M += m.N_colloc_ival(ival), ++ival) {
    const auto Kext = m.N_colloc_ival(ival);

    // evaluate xs and F at those points
    Eigen::Matrix<double, Nx, -1> Fval(Nx, Kext + 1);
    Eigen::Matrix<double, Nx, -1> Xval(Nx, Kext + 1);
    for (const auto & [j, tau] : zip(std::views::iota(0u, Kext + 1), m.interval_nodes(ival))) {
      const double tj = t0 + (tf - t0) * tau;

      // evaluate x and u values at tj using current degree polynomials
      const auto Xj = xfun(tj);
      const auto Uj = ufun(tj);

      // evaluate right-hand side of dynamics at tj
      Fval.col(j) = f(tj, Xj, Uj);

      // store x values for later comparison
      Xval.col(j) = Xj;
    }

    // "integrate" system inside interval
    const Eigen::MatrixXd Xval_est =
      Xval.col(0).replicate(1, Kext) + (tf - t0) * Fval.leftCols(Kext) * m.interval_intmat(ival);

    // absolute error in interval
    Eigen::VectorXd e_abs = (Xval_est - Xval.rightCols(Kext)).colwise().norm();
    Eigen::VectorXd e_rel = e_abs / (1. + Xval.rightCols(Kext).colwise().norm().maxCoeff());

    // mex relative error on interval
    ival_errs(ival) = e_rel.maxCoeff();
  }

  return ival_errs;
}

}  // namespace smooth::feedback

#endif  // SMOOTH__FEEDBACK__COLLOCATION__DYN_ERROR_HPP_


//  Copyright (c) 2018-2019 Cem Bassoy
//
//  Distributed under the Boost Software License, Version 1.0. (See
//  accompanying file LICENSE_1_0.txt or copy at
//  http://www.boost.org/LICENSE_1_0.txt)
//
//  The authors gratefully acknowledge the support of
//  Fraunhofer and Google in producing this work
//  which started as a Google Summer of Code project.
//

#include <boost/numeric/ublas/tensor/expression.hpp>
#include <boost/numeric/ublas/tensor/tensor.hpp>
#include <boost/test/unit_test.hpp>
#include "utility.hpp"

#include <functional>
#include <complex>

using test_types = zip<int,long,float,double,std::complex<float>>::with_t<boost::numeric::ublas::first_order, boost::numeric::ublas::last_order>;

struct fixture
{
	using extents_type = boost::numeric::ublas::shape;
	fixture()
	  : extents {
	      extents_type{},            // 0

	      extents_type{1,1},         // 1
	      extents_type{1,2},         // 2
	      extents_type{2,1},         // 3

	      extents_type{2,3},         // 4
	      extents_type{2,3,1},       // 5
	      extents_type{1,2,3},       // 6
	      extents_type{1,1,2,3},     // 7
	      extents_type{1,2,3,1,1},   // 8

	      extents_type{4,2,3},       // 9
	      extents_type{4,2,1,3},     // 10
	      extents_type{4,2,1,3,1},   // 11
	      extents_type{1,4,2,1,3,1} }  // 12
	{
	}
	std::vector<extents_type> extents;
};

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_expression_access, value,  test_types, fixture)
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;
	using tensor_expression_type  = typename tensor_type::super_type;

	for(auto const& e : extents) {

		auto v = value_type{};
		auto t = tensor_type(e);

		for(auto& tt: t){ tt = v; v+=value_type{1}; }
		const auto& tensor_expression_const = static_cast<tensor_expression_type const&>( t );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( tensor_expression_const()(i), t(i)  );

	}
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_unary_expression, value,  test_types, fixture)
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;

	auto uplus1 = std::bind(  std::plus<value_type>{}, std::placeholders::_1, value_type(1) );

	for(auto const& e : extents) {

		auto t = tensor_type(e);
		auto v = value_type{};
		for(auto& tt: t) { tt = v; v+=value_type{1}; }

		const auto uexpr = ublas::detail::make_unary_tensor_expression<tensor_type>( t, uplus1 );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( uexpr(i), uplus1(t(i))  );

		auto uexpr_uexpr = ublas::detail::make_unary_tensor_expression<tensor_type>( uexpr, uplus1 );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( uexpr_uexpr(i), uplus1(uplus1(t(i)))  );

		const auto & uexpr_e = uexpr.e;

		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(uexpr_e) >, tensor_type > )   );

		const auto & uexpr_uexpr_e_e = uexpr_uexpr.e.e;

		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(uexpr_uexpr_e_e) >, tensor_type > )   );

	}
}

BOOST_FIXTURE_TEST_CASE_TEMPLATE( test_tensor_binary_expression, value,  test_types, fixture)
{
	using namespace boost::numeric;
	using value_type  = typename value::first_type;
	using layout_type = typename value::second_type;
	using tensor_type = ublas::tensor<value_type, layout_type>;

	auto uplus1 = std::bind(  std::plus<value_type>{}, std::placeholders::_1, value_type(1) );
	auto uplus2 = std::bind(  std::plus<value_type>{}, std::placeholders::_1, value_type(2) );
	auto bplus  = std::plus <value_type>{};
	auto bminus = std::minus<value_type>{};

	for(auto const& e : extents) {

		auto t = tensor_type(e);
		auto v = value_type{};
		for(auto& tt: t){ tt = v; v+=value_type{1}; }

		auto uexpr1 = ublas::detail::make_unary_tensor_expression<tensor_type>( t, uplus1 );
		auto uexpr2 = ublas::detail::make_unary_tensor_expression<tensor_type>( t, uplus2 );

		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(uexpr1.e) >, tensor_type > )   );
		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(uexpr2.e) >, tensor_type > )   );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( uexpr1(i), uplus1(t(i))  );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( uexpr2(i), uplus2(t(i))  );

		auto bexpr_uexpr = ublas::detail::make_binary_tensor_expression<tensor_type>( uexpr1, uexpr2, bplus );

		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(bexpr_uexpr.el.e) >, tensor_type > )   );
		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(bexpr_uexpr.er.e) >, tensor_type > )   );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( bexpr_uexpr(i), bplus(uexpr1(i),uexpr2(i))  );

		auto bexpr_bexpr_uexpr = ublas::detail::make_binary_tensor_expression<tensor_type>( bexpr_uexpr, t, bminus );

		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(bexpr_bexpr_uexpr.el.el.e) >, tensor_type > )   );
		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(bexpr_bexpr_uexpr.el.er.e) >, tensor_type > )   );
		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(bexpr_bexpr_uexpr.er) >, tensor_type > )   );
		BOOST_CHECK( ( std::is_same_v< std::decay_t< decltype(bexpr_bexpr_uexpr.er) >, tensor_type > )   );

		for(auto i = 0ul; i < t.size(); ++i)
			BOOST_CHECK_EQUAL( bexpr_bexpr_uexpr(i), bminus(bexpr_uexpr(i),t(i))  );

	}

}


/*******************************************************
* Copyright (C) 2020 All rights reserved.
*
* File:    dataframe.cpp
* Author:  xiaoran (xiaoranone@gmail.com)
* Date:    2020/09/10 16:20:03
*
******************************************************/

#include "dataframe.h"
#include "fishml/comon/utils.h"

#include <iostream>
#include <ifstream>
#include <ofstream>
#include <boost/algorithm/string.hpp>

namespace fishml {
namespace data {

// default DataFrame
DataFrame::DataFrame() {}

// default ~DataFrame
DataFrame::~DataFrame() {}

// Eigen::MatrixXd for init
DataFrame::DataFrame(Eigen::MatrixXd& mat) {
    data = mat;
}

// vector for init
template<typename T>
DataFrame::DataFrame(vector<vector<T>>& mat) {
    construct_from_vector(data);
}

// construct data from vector
Status DataFrame::construct_from_vector(vector<vector<T>>& mat) {
    if (mat.empty() || mat[0].empty()) {
        return Status::kError;
    }
    int rows = mat.size();
    int cols = mat[0].size();
    data = Eigen::Map<Eigen::Matrix<T, rows, cols> >(mat);
    return Status::kOk;
}

// read csv
Status DataFrame::read_csv(const std::string file_name, const std::string sep=",", bool header=false) {
    std::ifstream fin(file_name);
    if (!fin) { // failed to open file
        return Status::kIOError;
    }
    std::string temp_s;
    std::vector<std::vector<double>> vec_mat;
    while (std::getline(fin, temp_s)) {
        std::vector<std::string> temp_vec;
        boost::algorithm::split(temp_vec, temp_s, boost::algorithm::is_any_of(sep));
        std::vector<double> temp_d;
        fishml::core::stod_vector(temp_d, temp_vec);
        vec_mat.emplace_back(temp_d);
    }
    if (Status::kOk != construct_from_vector(vec_mat)) {
        return Status::kError;
    }
    return Status::kOk;
}

// write csv
Status DataFrame::write_csv(const std::string file_name, const std::string sep=",", bool header=false) {

    return Status::kOk;
}

} // fishml::data
} // fishml


#define BOOST_TEST_MODULE "Test LocalLengthGroupSpace class"

#include <boost/test/unit_test.hpp>
#include <cstdio>
#include "IO.hpp"
#include "group/GlobalGroupSpace.hpp"
#include "TimeSeriesSet.hpp"
#include "distance/Distance.hpp"
#include "Exception.hpp"
#include "group/Group.hpp"

#define TOLERANCE 1e-9

using namespace genex;

struct MockData
{
  data_t dat[7] = {110, 116, 118, 117, 16.5, 112, 112};
  std::string test_group_5_10_space = "datasets/test/test_group_5_10_space.txt";
  std::string test_group_5_10_different_space = "datasets/test/test_group_5_10_different_space.txt";
  std::string ItalyPowerDemand = "datasets/test/ItalyPowerDemand_DATA";
};

BOOST_AUTO_TEST_CASE( local_length_group_space, *boost::unit_test::tolerance(EPS) )
{
  MockData data;

  TimeSeriesSet tsSet;
  tsSet.loadData(data.test_group_5_10_different_space, 5, 0, " ");

  BOOST_CHECK_EQUAL(tsSet.getItemCount(), 5);
  BOOST_CHECK_EQUAL(tsSet.getMaxLength(), 10);

  GlobalGroupSpace gSet(tsSet);
  gSet.group("euclidean", 0.5);
  auto best = gSet.getBestMatch(tsSet.getTimeSeries(0, 0, 10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,4,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,6,9));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,2,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,3,7));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,0,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,4,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,6,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,2,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,3,7));
  BOOST_TEST((best.dist) == 0);
  TimeSeries ts1(data.dat, 0,0,7);
  best = gSet.getBestMatch(ts1);
  BOOST_TEST((best.dist)> 0);
}

BOOST_AUTO_TEST_CASE( local_length_group_space_multi_threaded, *boost::unit_test::tolerance(EPS) )
{
  MockData data;

  TimeSeriesSet tsSet;
  tsSet.loadData(data.test_group_5_10_different_space, 5, 0, " ");

  BOOST_CHECK_EQUAL(tsSet.getItemCount(), 5);
  BOOST_CHECK_EQUAL(tsSet.getMaxLength(), 10);

  GlobalGroupSpace gSet(tsSet);
  gSet.groupMultiThreaded("euclidean", 0.5, 4);
  auto best = gSet.getBestMatch(tsSet.getTimeSeries(0, 0, 10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,4,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,6,9));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,2,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(0,3,7));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,0,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,4,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,6,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,2,10));
  BOOST_TEST((best.dist) == 0);
  best = gSet.getBestMatch(tsSet.getTimeSeries(4,3,7));
  BOOST_TEST((best.dist) == 0);
  TimeSeries ts1(data.dat, 0,0,7);
  best = gSet.getBestMatch(ts1);
  BOOST_TEST((best.dist)> 0);
}

BOOST_AUTO_TEST_CASE( traverse_order )
{
  setWarpingBandRatio(0.4);
  vector<int> order = generateTraverseOrder(3, 7);
  vector<int> expected = { 3, 2, 4, 5 };
  BOOST_CHECK_EQUAL_COLLECTIONS(order.begin(), order.end(), expected.begin(), expected.end());
}

BOOST_AUTO_TEST_CASE( global_group_space_save_load )
{
  // Set up
  MockData data;
  std::string fname = "global_group_space_save_load.z";
  TimeSeriesSet tsSet;
  tsSet.loadData(data.ItalyPowerDemand, 5, 0, " ");

  GlobalGroupSpace ggs(tsSet);
  ggs.group("euclidean", 0.5);

  // Save
  saveToFile(ggs, fname);

  // Load
  GlobalGroupSpace ggs2(tsSet);
  loadFromFile(ggs2, fname);

  // Compare
  auto best = ggs.getBestMatch(tsSet.getTimeSeries(0,4,10));
  auto best2 = ggs2.getBestMatch(tsSet.getTimeSeries(0,4,10));
  bool ok = best == best2;
  BOOST_TEST( ok );
  BOOST_CHECK_EQUAL( ggs.getDistanceName(), ggs2.getDistanceName() );
  BOOST_CHECK_EQUAL( ggs.getThreshold(), ggs2.getThreshold() );
  BOOST_CHECK_EQUAL( ggs.getTotalNumberOfGroups(), ggs2.getTotalNumberOfGroups() );
  remove(fname.c_str());
}


// Copyright Louis Dionne 2013-2016
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#include <boost/hana/assert.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/less.hpp>
#include <boost/hana/plus.hpp>
#include <boost/hana/while.hpp>

#include <vector>
namespace hana = boost::hana;
using namespace hana::literals;

int main() {
    // while_ with a Constant condition (loop is unrolled at compile-time)
    {
        std::vector<int> ints;
        auto final_state = hana::while_(hana::less.than(10_c), 0_c, [&](auto i) {
            ints.push_back(i);
            return i + 1_c;
        });

        // The state is known at compile-time
        BOOST_HANA_CONSTANT_CHECK(final_state == 10_c);

        BOOST_HANA_RUNTIME_CHECK(ints == std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
    }

    // while_ with a constexpr or runtime condition (loop is not unrolled)
    {
        std::vector<int> ints;
        int final_state = hana::while_(hana::less.than(10), 0, [&](int i) {
            ints.push_back(i);
            return i + 1;
        });

        // The state is known only at runtime, or at compile-time if constexpr
        BOOST_HANA_RUNTIME_CHECK(final_state == 10);

        BOOST_HANA_RUNTIME_CHECK(ints == std::vector<int>{0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
    }
}


/**TODO:  Add copyright*/

#define BOOST_TEST_MODULE TensorCollectionFile test suite
#include <boost/test/included/unit_test.hpp>
#include <TensorBase/io/TensorCollectionFile.h>
#include <TensorBase/ml/TensorCollectionDefaultDevice.h>

using namespace TensorBase;
using namespace std;

BOOST_AUTO_TEST_SUITE(TensorCollectionFile1)

BOOST_AUTO_TEST_CASE(constructorDefaultDevice)
{
  TensorCollectionFile<Eigen::DefaultDevice>* ptr = nullptr;
  TensorCollectionFile<Eigen::DefaultDevice>* nullPointer = nullptr;
  ptr = new TensorCollectionFile<Eigen::DefaultDevice>();
  BOOST_CHECK_NE(ptr, nullPointer);
}

BOOST_AUTO_TEST_CASE(destructorDefaultDevice)
{
  TensorCollectionFile<Eigen::DefaultDevice>* ptr = nullptr;
	ptr = new TensorCollectionFile<Eigen::DefaultDevice>();
  delete ptr;
}

BOOST_AUTO_TEST_CASE(storeAndLoadBinaryDefaultDevice)
{
  // Set up the device
  Eigen::DefaultDevice device;

  // Setup the tensor collection
  Eigen::Tensor<std::string, 1> dimensions1(1), dimensions2(1), dimensions3(1);
  dimensions1(0) = "x";
  dimensions2(0) = "y";
  dimensions3(0) = "z";
  int nlabels1 = 2, nlabels2 = 3, nlabels3 = 5;
  Eigen::Tensor<int, 2> labels1(1, nlabels1), labels2(1, nlabels2), labels3(1, nlabels3);
  labels1.setConstant(1);
  labels2.setConstant(2);
  labels3.setConstant(3);

  // Setup Table 1 axes
  TensorTableDefaultDevice<float, 3> tensorTable1("1");
  tensorTable1.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable1.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", dimensions2, labels2)));
  tensorTable1.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("3", dimensions3, labels3)));
  tensorTable1.setAxes(device);
  std::shared_ptr<TensorTable<float, Eigen::DefaultDevice, 3>> tensorTable1_ptr = std::make_shared<TensorTableDefaultDevice<float, 3>>(tensorTable1);

  // Setup Table 1 data
  Eigen::Tensor<float, 3> tensor1_values(Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
  for (int k = 0; k < nlabels3; ++k) {
    for (int j = 0; j < nlabels2; ++j) {
      for (int i = 0; i < nlabels1; ++i) {
        tensor1_values(i, j, k) = i + j * nlabels1 + k * nlabels1*nlabels2;
      }
    }
  }
  tensorTable1_ptr->setData(tensor1_values);

  // Setup Table 2 axes
  TensorTableDefaultDevice<int, 2> tensorTable2("2");
  tensorTable2.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable2.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", dimensions2, labels2)));
  tensorTable2.setAxes(device);
  std::shared_ptr<TensorTable<int, Eigen::DefaultDevice, 2>> tensorTable2_ptr = std::make_shared<TensorTableDefaultDevice<int, 2>>(tensorTable2);

  // Setup Table 2 data
  Eigen::Tensor<int, 2> tensor2_values(Eigen::array<Eigen::Index, 2>({ nlabels1, nlabels2 }));
  for (int j = 0; j < nlabels2; ++j) {
    for (int i = 0; i < nlabels1; ++i) {
      tensor2_values(i, j) = i + j * nlabels1;
    }
  }
  tensorTable2_ptr->setData(tensor2_values);

  // Setup Table 3 axes
  TensorTableDefaultDevice<char, 3> tensorTable3("3");
  tensorTable3.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable3.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", dimensions2, labels2)));
  tensorTable3.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("3", dimensions3, labels3)));
  tensorTable3.setAxes(device);
  std::shared_ptr<TensorTable<char, Eigen::DefaultDevice, 3>> tensorTable3_ptr = std::make_shared<TensorTableDefaultDevice<char, 3>>(tensorTable3);

  // Setup Table 3 data
  Eigen::Tensor<char, 3> tensor3_values(Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
  tensor3_values.setConstant('a');
  tensorTable3_ptr->setData(tensor3_values);

  TensorCollectionDefaultDevice tensorCollection("1");
  tensorCollection.addTensorTable(tensorTable1_ptr, "1");
  tensorCollection.addTensorTable(tensorTable2_ptr, "1");
  tensorCollection.addTensorTable(tensorTable3_ptr, "1");
  auto tensorCollectionPtr = std::make_shared<TensorCollectionDefaultDevice>(tensorCollection);

  // Store the Tensor Collection
  TensorCollectionFile<Eigen::DefaultDevice> data;
  std::string filename = "TensorCollectionFileTest.dat";
  data.storeTensorCollectionBinary(filename, tensorCollectionPtr, device);

  // Load the Tensor Collection
  std::shared_ptr<TensorCollection<Eigen::DefaultDevice>> tensorCollectionTestPtr;
  data.loadTensorCollectionBinary(filename, tensorCollectionTestPtr, device);

  // Test the serialized metadata
  BOOST_CHECK(*(tensorCollectionPtr.get()) == *(tensorCollectionTestPtr.get()));

  // Test the binarized tensor axes data and tensor data
  // NOTE: tests only work on the DefaultDevice
  {// Table 1 Axis 1
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("1")->getAxes().at("1")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels1);
    for (int j = 0; j < nlabels1; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels1(0, j));
    }
  }
  {// Table 1 Axis 2
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("1")->getAxes().at("2")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels2);
    for (int j = 0; j < nlabels2; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels2(0, j));
    }
  }
  {// Table 1 Axis 3
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("1")->getAxes().at("3")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels3);
    for (int j = 0; j < nlabels3; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels3(0, j));
    }
  }
  {// Table 1 Data
    std::shared_ptr<float[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("1")->getDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<float, 3>> data_values(labels_ptr.get(), Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
    for (int k = 0; k < nlabels3; ++k) {
      for (int j = 0; j < nlabels2; ++j) {
        for (int i = 0; i < nlabels1; ++i) {
          BOOST_CHECK_EQUAL(data_values(i, j, k), tensor1_values(i, j, k));
        }
      }
    }
  }

  {// Table 2 Axis 1
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("2")->getAxes().at("1")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels1);
    for (int j = 0; j < nlabels1; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels1(0, j));
    }
  }
  {// Table 2 Axis 2
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("2")->getAxes().at("2")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels2);
    for (int j = 0; j < nlabels2; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels2(0, j));
    }
  }
  {// Table 2 Data
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("2")->getDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> data_values(labels_ptr.get(), Eigen::array<Eigen::Index, 2>({ nlabels1, nlabels2 }));
    for (int j = 0; j < nlabels2; ++j) {
      for (int i = 0; i < nlabels1; ++i) {
        BOOST_CHECK_EQUAL(data_values(i, j), tensor2_values(i, j));
      }
    }
  }

  {// Table 3 Axis 1
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("3")->getAxes().at("1")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels1);
    for (int j = 0; j < nlabels1; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels1(0, j));
    }
  }
  {// Table 3 Axis 2
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("3")->getAxes().at("2")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels2);
    for (int j = 0; j < nlabels2; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels2(0, j));
    }
  }
  {// Table 3 Axis 3
    std::shared_ptr<int[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("3")->getAxes().at("3")->getLabelsDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<int, 2>> labels_values(labels_ptr.get(), 1, nlabels3);
    for (int j = 0; j < nlabels3; ++j) {
      BOOST_CHECK_EQUAL(labels_values(0, j), labels3(0, j));
    }
  }
  {// Table 3 Data
    std::shared_ptr<char[]> labels_ptr;
    tensorCollectionTestPtr->tables_.at("3")->getDataPointer(labels_ptr);
    Eigen::TensorMap<Eigen::Tensor<char, 3>> data_values(labels_ptr.get(), Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
    for (int k = 0; k < nlabels3; ++k) {
      for (int j = 0; j < nlabels2; ++j) {
        for (int i = 0; i < nlabels1; ++i) {
          BOOST_CHECK_EQUAL(data_values(i, j, k), tensor3_values(i, j, k));
        }
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getTensorTableHeadersDefaultDevice)
{
  // Set up the device
  Eigen::DefaultDevice device;

  // Setup the tensor collection
  Eigen::Tensor<std::string, 1> dimensions1(1), dimensions2(1), dimensions3(1);
  dimensions1(0) = "x";
  dimensions2(0) = "y";
  dimensions3(0) = "z";
  int nlabels1 = 2, nlabels2 = 3, nlabels3 = 5;
  Eigen::Tensor<int, 2> labels1(1, nlabels1), labels2(1, nlabels2), labels3(1, nlabels3);
  labels1.setValues({ {0, 1} });
  labels2.setValues({ {0, 1, 2} });
  labels3.setValues({ {0, 1, 2, 3, 4} });

  // Setup Table 1 axes
  TensorTableDefaultDevice<float, 3> tensorTable1("1");
  tensorTable1.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable1.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", dimensions2, labels2)));
  tensorTable1.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("3", dimensions3, labels3)));
  tensorTable1.setAxes(device);
  std::shared_ptr<TensorTable<float, Eigen::DefaultDevice, 3>> tensorTable1_ptr = std::make_shared<TensorTableDefaultDevice<float, 3>>(tensorTable1);

  // Setup Table 1 data
  Eigen::Tensor<float, 3> tensor1_values(Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
  for (int k = 0; k < nlabels3; ++k) {
    for (int j = 0; j < nlabels2; ++j) {
      for (int i = 0; i < nlabels1; ++i) {
        tensor1_values(i, j, k) = i + j * nlabels1 + k * nlabels1*nlabels2;
      }
    }
  }
  tensorTable1_ptr->setData(tensor1_values);

  // Setup Table 2 axes
  TensorTableDefaultDevice<int, 3> tensorTable2("2");
  labels1.setValues({ {2, 3} });
  tensorTable2.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable2.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", dimensions2, labels2)));
  tensorTable2.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("3", dimensions3, labels3)));
  tensorTable2.setAxes(device);
  std::shared_ptr<TensorTable<int, Eigen::DefaultDevice, 3>> tensorTable2_ptr = std::make_shared<TensorTableDefaultDevice<int, 3>>(tensorTable2);

  // Setup Table 2 data
  Eigen::Tensor<int, 3> tensor2_values(Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
  for (int k = 0; k < nlabels3; ++k) {
    for (int j = 0; j < nlabels2; ++j) {
      for (int i = 0; i < nlabels1; ++i) {
        tensor2_values(i, j, k) = i + j * nlabels1 + k * nlabels1*nlabels2;
      }
    }
  }
  tensorTable2_ptr->setData(tensor2_values);

  // Setup Table 3 axes
  TensorTableDefaultDevice<char, 3> tensorTable3("3");
  labels1.setValues({ {4, 5} });
  tensorTable3.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable3.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", dimensions2, labels2)));
  tensorTable3.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("3", dimensions3, labels3)));
  tensorTable3.setAxes(device);
  std::shared_ptr<TensorTable<char, Eigen::DefaultDevice, 3>> tensorTable3_ptr = std::make_shared<TensorTableDefaultDevice<char, 3>>(tensorTable3);

  // Setup Table 3 data
  Eigen::Tensor<char, 3> tensor3_values(Eigen::array<Eigen::Index, 3>({ nlabels1, nlabels2, nlabels3 }));
  tensor3_values.setConstant('a');
  tensorTable3_ptr->setData(tensor3_values);

  TensorCollectionDefaultDevice tensorCollection("1");
  tensorCollection.addTensorTable(tensorTable1_ptr, "1");
  tensorCollection.addTensorTable(tensorTable2_ptr, "1");
  tensorCollection.addTensorTable(tensorTable3_ptr, "1");
  std::shared_ptr<TensorCollection<Eigen::DefaultDevice>> tensorCollectionPtr = std::make_shared<TensorCollectionDefaultDevice>(tensorCollection);

  // Test making the output header names
  TensorCollectionFile<Eigen::DefaultDevice> data;
  std::pair<std::map<std::string, std::vector<std::string>>, std::map<std::string, std::vector<std::string>>> headers = data.getTensorTableHeaders("1", tensorCollectionPtr, device);
  BOOST_CHECK(headers.first.at("2") == std::vector<std::string>({ "y" }));
  BOOST_CHECK(headers.first.at("3") == std::vector<std::string>({ "z" }));
  BOOST_CHECK(headers.second.at("1") == std::vector<std::string>({ "0", "1" }));
  BOOST_CHECK(headers.second.at("2") == std::vector<std::string>({ "2", "3" }));
  BOOST_CHECK(headers.second.at("3") == std::vector<std::string>({ "4", "5" }));

  // Test writing to disk
  data.storeTensorTableAsCsv("Table1.csv", "1", tensorCollectionPtr, device);

  // Make the minimal tensorCollection for reading from .csv
  std::map<std::string, int> shard_span = {
    {"1", 2}, {"2", 2}, {"3", 2} };

  // Setup Table 1 axes
  TensorTableDefaultDevice<float, 3> tensorTable1_min("1");
  labels1.setValues({ {0, 1} });
  tensorTable1_min.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  auto axis2_min_ptr = std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("2", 1, 0));
  axis2_min_ptr->setDimensions(dimensions2);
  tensorTable1_min.addTensorAxis(axis2_min_ptr);
  auto axis3_min_ptr = std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("3", 1, 0));
  axis3_min_ptr->setDimensions(dimensions3);
  tensorTable1_min.addTensorAxis(axis3_min_ptr);
  tensorTable1_min.setAxes(device);
  auto tensorTable1_min_ptr = std::make_shared<TensorTableDefaultDevice<float, 3>>(tensorTable1_min);
  tensorTable1_min_ptr->setData();
  tensorTable1_min_ptr->setShardSpans(shard_span);

  // Setup Table 2 axes
  TensorTableDefaultDevice<int, 3> tensorTable2_min("2");
  labels1.setValues({ {2, 3} });
  tensorTable2_min.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable2_min.addTensorAxis(axis2_min_ptr);
  tensorTable2_min.addTensorAxis(axis3_min_ptr);
  tensorTable2_min.setAxes(device);
  auto tensorTable2_min_ptr = std::make_shared<TensorTableDefaultDevice<int, 3>>(tensorTable2_min);
  tensorTable2_min_ptr->setData();
  tensorTable2_min_ptr->setShardSpans(shard_span);

  // Setup Table 3 axes
  TensorTableDefaultDevice<char, 3> tensorTable3_min("3");
  labels1.setValues({ {4, 5} });
  tensorTable3_min.addTensorAxis(std::make_shared<TensorAxisDefaultDevice<int>>(TensorAxisDefaultDevice<int>("1", dimensions1, labels1)));
  tensorTable3_min.addTensorAxis(axis2_min_ptr);
  tensorTable3_min.addTensorAxis(axis3_min_ptr);
  tensorTable3_min.setAxes(device);
  auto tensorTable3_min_ptr = std::make_shared<TensorTableDefaultDevice<char, 3>>(tensorTable3_min);
  tensorTable3_min_ptr->setData();
  tensorTable3_min_ptr->setShardSpans(shard_span);

  TensorCollectionDefaultDevice tensorCollection_min("1");
  tensorCollection_min.addTensorTable(tensorTable1_min_ptr, "1");
  tensorCollection_min.addTensorTable(tensorTable2_min_ptr, "1");
  tensorCollection_min.addTensorTable(tensorTable3_min_ptr, "1");
  std::shared_ptr<TensorCollection<Eigen::DefaultDevice>> tensorCollectionMinPtr = std::make_shared<TensorCollectionDefaultDevice>(tensorCollection_min);

  // Test reading from disk
  data.loadTensorTableFromCsv("Table1.csv", "1", tensorCollectionMinPtr, device);

  // Check the metadata
  BOOST_CHECK(*(tensorCollectionMinPtr.get()) == *(tensorCollectionPtr.get()));

  // Check the table data
  for (int k = 0; k < nlabels3; ++k) {
    for (int j = 0; j < nlabels2; ++j) {
      for (int i = 0; i < nlabels1; ++i) {
        BOOST_CHECK_EQUAL(tensorTable1_min_ptr->getData()(i, j, k), tensor1_values(i, j, k));
        BOOST_CHECK_EQUAL(tensorTable2_min_ptr->getData()(i, j, k), tensor2_values(i, j, k));
        BOOST_CHECK_EQUAL(tensorTable3_min_ptr->getData()(i, j, k), tensor3_values(i, j, k));
      }
    }
  }

  // Check the axes
  for (int i = 0; i < nlabels2; ++i) {
    BOOST_CHECK_EQUAL(axis2_min_ptr->getLabels()(0, i), labels2(0, i));
  }
  for (int i = 0; i < nlabels3; ++i) {
    BOOST_CHECK_EQUAL(axis3_min_ptr->getLabels()(0, i), labels3(0, i));
  }
}

BOOST_AUTO_TEST_SUITE_END()


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

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

template <typename Matrix>
void test(const char* A_string, const Matrix&)
{
    std::cout << A_string << " is " << mtl::static_num_rows<Matrix>::value << "x"
	      << mtl::static_num_cols<Matrix>::value << "\n";

    if (mtl::static_num_rows<Matrix>::value != 3)
	throw "Wrong number of rows";
    if (mtl::static_num_cols<Matrix>::value != 2)
	throw "Wrong number of columns";
    if (mtl::static_size<Matrix>::value != 6)
	throw "Wrong size";
}

int main(int, char**)
{
    using namespace mtl;

    typedef mat::parameters<tag::row_major, mtl::index::c_index, mtl::fixed::dimensions<3, 2>, true> fmat_para;
    float ma[3][2]= {{2., 3.}, {4., 5.}, {6., 7.}};

    dense2D<float, fmat_para>        A_dense(ma);
    morton_dense<float, recursion::morton_z_mask, fmat_para>   A_morton(ma);
    compressed2D<float, fmat_para>   A_compressed(ma);

    test("Dense matrix", A_dense);
    test("Morton dense matrix", A_morton);
    test("Compressed sparse matrix", A_compressed);

    return 0;
}


//Copyright (c) 2015 Zachary Kann
//
//Permission is hereby granted, free of charge, to any person obtaining a copy
//of this software and associated documentation files (the "Software"), to deal
//in the Software without restriction, including without limitation the rights
//to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//copies of the Software, and to permit persons to whom the Software is
//furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included in all
//copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//SOFTWARE.

// ---
// Author: Zachary Kann

// Functions for performing various statistics calculations.

#include <armadillo>

#ifndef _Z_STAT_HPP_
#define _Z_STAT_HPP_

// Calculates linear regression for some variable y over an evenly-spaced
// interval in x.
extern double LinearFitSlope(const double xinterval, const arma::rowvec& y,
                             const int startfit, const int endfit);

// Calculates slope of exponential fit for some variable y over an evenly-spaced
// interval in x.
extern double ExponentialFitSlope(const double xinterval,
                                  const arma::irowvec& y, const int startfit,
                                  const int endfit);

#endif