//
// 	Copyright (c) 2020, Cem Bassoy, cem.bassoy@gmail.com
//
//  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
//  Google and Fraunhofer IOSB, Ettlingen, Germany
//

#include <vector>
#include <array>

#include <boost/test/unit_test.hpp>
#include <boost/numeric/ublas/tensor/extents.hpp>

BOOST_AUTO_TEST_SUITE(test_shape_functions)

struct fixture_extents_dynamic_rank
{
  using shape_t = boost::numeric::ublas::extents<>;

  static inline auto n     = shape_t{};
  static inline auto n1    = shape_t{1};
  static inline auto n2    = shape_t{2};
  static inline auto n11   = shape_t{1,1};
  static inline auto n12   = shape_t{1,2};
  static inline auto n21   = shape_t{2,1};
  static inline auto n22   = shape_t{2,2};
  static inline auto n32   = shape_t{3,2};
  static inline auto n111  = shape_t{1,1,1};
  static inline auto n211  = shape_t{2,1,1};
  static inline auto n121  = shape_t{1,2,1};
  static inline auto n112  = shape_t{1,1,2};
  static inline auto n123  = shape_t{1,2,3};
  static inline auto n321  = shape_t{3,2,1};
  static inline auto n213  = shape_t{2,1,3};
  static inline auto n432  = shape_t{4,3,2};
};

struct fixture_extents_static_rank
{
  template<std::size_t N>
  using extents_static_rank = boost::numeric::ublas::extents<N>;

  static constexpr inline auto n     = extents_static_rank<0>{};
  static constexpr inline auto n1    = extents_static_rank<1>{1};
  static constexpr inline auto n2    = extents_static_rank<1>{2};
  static constexpr inline auto n11   = extents_static_rank<2>{{1,1}};
  static constexpr inline auto n12   = extents_static_rank<2>{{1,2}};
  static constexpr inline auto n21   = extents_static_rank<2>{{2,1}};
  static constexpr inline auto n22   = extents_static_rank<2>{{2,2}};
  static constexpr inline auto n32   = extents_static_rank<2>{{3,2}};
  static constexpr inline auto n111  = extents_static_rank<3>{{1,1,1}};
  static constexpr inline auto n211  = extents_static_rank<3>{{2,1,1}};
  static constexpr inline auto n121  = extents_static_rank<3>{{1,2,1}};
  static constexpr inline auto n112  = extents_static_rank<3>{{1,1,2}};
  static constexpr inline auto n123  = extents_static_rank<3>{{1,2,3}};
  static constexpr inline auto n321  = extents_static_rank<3>{{3,2,1}};
  static constexpr inline auto n213  = extents_static_rank<3>{{2,1,3}};
  static constexpr inline auto n432  = extents_static_rank<3>{{4,3,2}};

  static constexpr inline auto tuple = std::make_tuple( n,n1,n2,n11,n12,n21,n22,n32,n111,n211,n121,n112,n123,n321,n213,n432 );

};

struct fixture_extents_static
{
  template<std::size_t ... ns>
  using extents_static = boost::numeric::ublas::extents<ns...>;

  static inline auto n     = extents_static<>      {};
  static inline auto n1    = extents_static<1>     {};
  static inline auto n2    = extents_static<2>     {};
  static inline auto n11   = extents_static<1,1>   {};
  static inline auto n12   = extents_static<1,2>   {};
  static inline auto n21   = extents_static<2,1>   {};
  static inline auto n22   = extents_static<2,2>   {};
  static inline auto n32   = extents_static<3,2>   {};
  static inline auto n111  = extents_static<1,1,1> {};
  static inline auto n211  = extents_static<2,1,1> {};
  static inline auto n121  = extents_static<1,2,1> {};
  static inline auto n112  = extents_static<1,1,2> {};
  static inline auto n123  = extents_static<1,2,3> {};
  static inline auto n321  = extents_static<3,2,1> {};
  static inline auto n213  = extents_static<2,1,3> {};
  static inline auto n432  = extents_static<4,3,2> {};

};

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_is_scalar,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("is_scalar"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_scalar( n    ));
  BOOST_CHECK (  ub::is_scalar( n1   ));
  BOOST_CHECK ( !ub::is_scalar( n2   ));
  BOOST_CHECK (  ub::is_scalar( n11  ));
  BOOST_CHECK ( !ub::is_scalar( n12  ));
  BOOST_CHECK ( !ub::is_scalar( n21  ));
  BOOST_CHECK ( !ub::is_scalar( n22  ));
  BOOST_CHECK ( !ub::is_scalar( n32  ));
  BOOST_CHECK (  ub::is_scalar( n111 ));
  BOOST_CHECK ( !ub::is_scalar( n211 ));
  BOOST_CHECK ( !ub::is_scalar( n121 ));
  BOOST_CHECK ( !ub::is_scalar( n112 ));
  BOOST_CHECK ( !ub::is_scalar( n123 ));
  BOOST_CHECK ( !ub::is_scalar( n321 ));
  BOOST_CHECK ( !ub::is_scalar( n213 ));
  BOOST_CHECK ( !ub::is_scalar( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_is_scalar,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("is_scalar"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_scalar( n    ));
  BOOST_CHECK (  ub::is_scalar( n1   ));
  BOOST_CHECK ( !ub::is_scalar( n2   ));
  BOOST_CHECK (  ub::is_scalar( n11  ));
  BOOST_CHECK ( !ub::is_scalar( n12  ));
  BOOST_CHECK ( !ub::is_scalar( n21  ));
  BOOST_CHECK ( !ub::is_scalar( n22  ));
  BOOST_CHECK ( !ub::is_scalar( n32  ));
  BOOST_CHECK (  ub::is_scalar( n111 ));
  BOOST_CHECK ( !ub::is_scalar( n211 ));
  BOOST_CHECK ( !ub::is_scalar( n121 ));
  BOOST_CHECK ( !ub::is_scalar( n112 ));
  BOOST_CHECK ( !ub::is_scalar( n123 ));
  BOOST_CHECK ( !ub::is_scalar( n321 ));
  BOOST_CHECK ( !ub::is_scalar( n213 ));
  BOOST_CHECK ( !ub::is_scalar( n432 ));

}

BOOST_FIXTURE_TEST_CASE(test_extents_static_is_scalar,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("is_scalar"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_scalar( n    ));
//FIXME:  BOOST_CHECK (  ub::is_scalar( n1   ));
  BOOST_CHECK ( !ub::is_scalar( n2   ));
  BOOST_CHECK (  ub::is_scalar( n11  ));
  BOOST_CHECK ( !ub::is_scalar( n12  ));
  BOOST_CHECK ( !ub::is_scalar( n21  ));
  BOOST_CHECK ( !ub::is_scalar( n22  ));
  BOOST_CHECK ( !ub::is_scalar( n32  ));
  BOOST_CHECK (  ub::is_scalar( n111 ));
  BOOST_CHECK ( !ub::is_scalar( n211 ));
  BOOST_CHECK ( !ub::is_scalar( n121 ));
  BOOST_CHECK ( !ub::is_scalar( n112 ));
  BOOST_CHECK ( !ub::is_scalar( n123 ));
  BOOST_CHECK ( !ub::is_scalar( n321 ));
  BOOST_CHECK ( !ub::is_scalar( n213 ));
  BOOST_CHECK ( !ub::is_scalar( n432 ));

}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_is_vector,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("is_vector"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_vector( n    ));
  BOOST_CHECK (  ub::is_vector( n1   ));
  BOOST_CHECK (  ub::is_vector( n2   ));
  BOOST_CHECK (  ub::is_vector( n11  ));
  BOOST_CHECK (  ub::is_vector( n12  ));
  BOOST_CHECK (  ub::is_vector( n21  ));
  BOOST_CHECK ( !ub::is_vector( n22  ));
  BOOST_CHECK ( !ub::is_vector( n32  ));
  BOOST_CHECK (  ub::is_vector( n111 ));
  BOOST_CHECK (  ub::is_vector( n211 ));
  BOOST_CHECK (  ub::is_vector( n121 ));
  BOOST_CHECK ( !ub::is_vector( n112 ));
  BOOST_CHECK ( !ub::is_vector( n123 ));
  BOOST_CHECK ( !ub::is_vector( n321 ));
  BOOST_CHECK ( !ub::is_vector( n213 ));
  BOOST_CHECK ( !ub::is_vector( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_is_vector,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("is_vector"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_vector( n    ));
  BOOST_CHECK (  ub::is_vector( n1   ));
  BOOST_CHECK (  ub::is_vector( n2   ));
  BOOST_CHECK (  ub::is_vector( n11  ));
  BOOST_CHECK (  ub::is_vector( n12  ));
  BOOST_CHECK (  ub::is_vector( n21  ));
  BOOST_CHECK ( !ub::is_vector( n22  ));
  BOOST_CHECK ( !ub::is_vector( n32  ));
  BOOST_CHECK (  ub::is_vector( n111 ));
  BOOST_CHECK (  ub::is_vector( n211 ));
  BOOST_CHECK (  ub::is_vector( n121 ));
  BOOST_CHECK ( !ub::is_vector( n112 ));
  BOOST_CHECK ( !ub::is_vector( n123 ));
  BOOST_CHECK ( !ub::is_vector( n321 ));
  BOOST_CHECK ( !ub::is_vector( n213 ));
  BOOST_CHECK ( !ub::is_vector( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_is_vector,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("is_vector"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_vector( n1   ));
//FIXME:  BOOST_CHECK (  ub::is_vector( n2   ));
  BOOST_CHECK (  ub::is_vector( n11  ));
  BOOST_CHECK (  ub::is_vector( n12  ));
  BOOST_CHECK (  ub::is_vector( n21  ));
  BOOST_CHECK ( !ub::is_vector( n22  ));
  BOOST_CHECK ( !ub::is_vector( n32  ));
  BOOST_CHECK (  ub::is_vector( n111 ));
  BOOST_CHECK (  ub::is_vector( n211 ));
  BOOST_CHECK (  ub::is_vector( n121 ));
  BOOST_CHECK ( !ub::is_vector( n112 ));
  BOOST_CHECK ( !ub::is_vector( n123 ));
  BOOST_CHECK ( !ub::is_vector( n321 ));
  BOOST_CHECK ( !ub::is_vector( n213 ));
  BOOST_CHECK ( !ub::is_vector( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_is_matrix,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("is_matrix"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_matrix( n    ));
  BOOST_CHECK (  ub::is_matrix( n1   ));
  BOOST_CHECK (  ub::is_matrix( n2   ));
  BOOST_CHECK (  ub::is_matrix( n11  ));
  BOOST_CHECK (  ub::is_matrix( n12  ));
  BOOST_CHECK (  ub::is_matrix( n21  ));
  BOOST_CHECK (  ub::is_matrix( n22  ));
  BOOST_CHECK (  ub::is_matrix( n32  ));
  BOOST_CHECK (  ub::is_matrix( n111 ));
  BOOST_CHECK (  ub::is_matrix( n211 ));
  BOOST_CHECK (  ub::is_matrix( n121 ));
  BOOST_CHECK ( !ub::is_matrix( n112 ));
  BOOST_CHECK ( !ub::is_matrix( n123 ));
  BOOST_CHECK (  ub::is_matrix( n321 ));
  BOOST_CHECK ( !ub::is_matrix( n213 ));
  BOOST_CHECK ( !ub::is_matrix( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_is_matrix,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("is_matrix"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_matrix( n    ));
  BOOST_CHECK (  ub::is_matrix( n1   ));
  BOOST_CHECK (  ub::is_matrix( n2   ));
  BOOST_CHECK (  ub::is_matrix( n11  ));
  BOOST_CHECK (  ub::is_matrix( n12  ));
  BOOST_CHECK (  ub::is_matrix( n21  ));
  BOOST_CHECK (  ub::is_matrix( n22  ));
  BOOST_CHECK (  ub::is_matrix( n32  ));
  BOOST_CHECK (  ub::is_matrix( n111 ));
  BOOST_CHECK (  ub::is_matrix( n211 ));
  BOOST_CHECK (  ub::is_matrix( n121 ));
  BOOST_CHECK ( !ub::is_matrix( n112 ));
  BOOST_CHECK ( !ub::is_matrix( n123 ));
  BOOST_CHECK (  ub::is_matrix( n321 ));
  BOOST_CHECK ( !ub::is_matrix( n213 ));
  BOOST_CHECK ( !ub::is_matrix( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_is_matrix,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("is_matrix"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_matrix( n    ));
//FIXME:  BOOST_CHECK ( !ub::is_matrix( n1   ));
  BOOST_CHECK ( !ub::is_matrix( n2   ));
  BOOST_CHECK (  ub::is_matrix( n11  ));
  BOOST_CHECK (  ub::is_matrix( n12  ));
  BOOST_CHECK (  ub::is_matrix( n21  ));
  BOOST_CHECK (  ub::is_matrix( n22  ));
  BOOST_CHECK (  ub::is_matrix( n32  ));
  BOOST_CHECK (  ub::is_matrix( n111 ));
  BOOST_CHECK (  ub::is_matrix( n211 ));
  BOOST_CHECK (  ub::is_matrix( n121 ));
  BOOST_CHECK ( !ub::is_matrix( n112 ));
  BOOST_CHECK ( !ub::is_matrix( n123 ));
  BOOST_CHECK (  ub::is_matrix( n321 ));
  BOOST_CHECK ( !ub::is_matrix( n213 ));
  BOOST_CHECK ( !ub::is_matrix( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_is_tensor,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("is_tensor"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_tensor( n    ));
  BOOST_CHECK ( !ub::is_tensor( n1   ));
  BOOST_CHECK ( !ub::is_tensor( n2   ));
  BOOST_CHECK ( !ub::is_tensor( n11  ));
  BOOST_CHECK ( !ub::is_tensor( n12  ));
  BOOST_CHECK ( !ub::is_tensor( n21  ));
  BOOST_CHECK ( !ub::is_tensor( n22  ));
  BOOST_CHECK ( !ub::is_tensor( n32  ));
  BOOST_CHECK ( !ub::is_tensor( n111 ));
  BOOST_CHECK ( !ub::is_tensor( n211 ));
  BOOST_CHECK ( !ub::is_tensor( n121 ));
  BOOST_CHECK (  ub::is_tensor( n112 ));
  BOOST_CHECK (  ub::is_tensor( n123 ));
  BOOST_CHECK ( !ub::is_tensor( n321 ));
  BOOST_CHECK (  ub::is_tensor( n213 ));
  BOOST_CHECK (  ub::is_tensor( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_is_tensor,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("is_tensor"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_tensor( n    ));
  BOOST_CHECK ( !ub::is_tensor( n1   ));
  BOOST_CHECK ( !ub::is_tensor( n2   ));
  BOOST_CHECK ( !ub::is_tensor( n11  ));
  BOOST_CHECK ( !ub::is_tensor( n12  ));
  BOOST_CHECK ( !ub::is_tensor( n21  ));
  BOOST_CHECK ( !ub::is_tensor( n22  ));
  BOOST_CHECK ( !ub::is_tensor( n32  ));
  BOOST_CHECK ( !ub::is_tensor( n111 ));
  BOOST_CHECK ( !ub::is_tensor( n211 ));
  BOOST_CHECK ( !ub::is_tensor( n121 ));
  BOOST_CHECK (  ub::is_tensor( n112 ));
  BOOST_CHECK (  ub::is_tensor( n123 ));
  BOOST_CHECK ( !ub::is_tensor( n321 ));
  BOOST_CHECK (  ub::is_tensor( n213 ));
  BOOST_CHECK (  ub::is_tensor( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_is_tensor,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("is_tensor"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK ( !ub::is_tensor( n    ));
//FIXME:  BOOST_CHECK ( !ub::is_tensor( n1   ));
  BOOST_CHECK ( !ub::is_tensor( n2   ));
  BOOST_CHECK ( !ub::is_tensor( n11  ));
  BOOST_CHECK ( !ub::is_tensor( n12  ));
  BOOST_CHECK ( !ub::is_tensor( n21  ));
  BOOST_CHECK ( !ub::is_tensor( n22  ));
  BOOST_CHECK ( !ub::is_tensor( n32  ));
  BOOST_CHECK ( !ub::is_tensor( n111 ));
  BOOST_CHECK ( !ub::is_tensor( n211 ));
  BOOST_CHECK ( !ub::is_tensor( n121 ));
  BOOST_CHECK (  ub::is_tensor( n112 ));
  BOOST_CHECK (  ub::is_tensor( n123 ));
  BOOST_CHECK ( !ub::is_tensor( n321 ));
  BOOST_CHECK (  ub::is_tensor( n213 ));
  BOOST_CHECK (  ub::is_tensor( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_is_valid,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("is_valid"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK (  ub::is_valid( n1   ));
  BOOST_CHECK (  ub::is_valid( n2   ));
  BOOST_CHECK (  ub::is_valid( n11  ));
  BOOST_CHECK (  ub::is_valid( n12  ));
  BOOST_CHECK (  ub::is_valid( n21  ));
  BOOST_CHECK (  ub::is_valid( n22  ));
  BOOST_CHECK (  ub::is_valid( n32  ));
  BOOST_CHECK (  ub::is_valid( n111 ));
  BOOST_CHECK (  ub::is_valid( n211 ));
  BOOST_CHECK (  ub::is_valid( n121 ));
  BOOST_CHECK (  ub::is_valid( n112 ));
  BOOST_CHECK (  ub::is_valid( n123 ));
  BOOST_CHECK (  ub::is_valid( n321 ));
  BOOST_CHECK (  ub::is_valid( n213 ));
  BOOST_CHECK (  ub::is_valid( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_is_valid,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("is_valid"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK (  ub::is_valid( n    ));
  BOOST_CHECK (  ub::is_valid( n1   ));
  BOOST_CHECK (  ub::is_valid( n2   ));
  BOOST_CHECK (  ub::is_valid( n11  ));
  BOOST_CHECK (  ub::is_valid( n12  ));
  BOOST_CHECK (  ub::is_valid( n21  ));
  BOOST_CHECK (  ub::is_valid( n22  ));
  BOOST_CHECK (  ub::is_valid( n32  ));
  BOOST_CHECK (  ub::is_valid( n111 ));
  BOOST_CHECK (  ub::is_valid( n211 ));
  BOOST_CHECK (  ub::is_valid( n121 ));
  BOOST_CHECK (  ub::is_valid( n112 ));
  BOOST_CHECK (  ub::is_valid( n123 ));
  BOOST_CHECK (  ub::is_valid( n321 ));
  BOOST_CHECK (  ub::is_valid( n213 ));
  BOOST_CHECK (  ub::is_valid( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_is_valid,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("is_valid"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK (  ub::is_valid( n    ));
//FIXME:  BOOST_CHECK (  ub::is_valid( n1   ));
//FIXME:  BOOST_CHECK (  ub::is_valid( n2   ));
  BOOST_CHECK (  ub::is_valid( n11  ));
  BOOST_CHECK (  ub::is_valid( n12  ));
  BOOST_CHECK (  ub::is_valid( n21  ));
  BOOST_CHECK (  ub::is_valid( n22  ));
  BOOST_CHECK (  ub::is_valid( n32  ));
  BOOST_CHECK (  ub::is_valid( n111 ));
  BOOST_CHECK (  ub::is_valid( n211 ));
  BOOST_CHECK (  ub::is_valid( n121 ));
  BOOST_CHECK (  ub::is_valid( n112 ));
  BOOST_CHECK (  ub::is_valid( n123 ));
  BOOST_CHECK (  ub::is_valid( n321 ));
  BOOST_CHECK (  ub::is_valid( n213 ));
  BOOST_CHECK (  ub::is_valid( n432 ));
}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_product,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("product"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK_EQUAL ( ub::product( n    ), 0U);
  BOOST_CHECK_EQUAL ( ub::product( n1   ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n2   ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n11  ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n12  ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n21  ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n22  ), 4U);
  BOOST_CHECK_EQUAL ( ub::product( n32  ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n111 ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n211 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n121 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n112 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n123 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n321 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n213 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n432 ),24U);
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_product,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("product"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK_EQUAL ( ub::product( n    ), 0U);
  BOOST_CHECK_EQUAL ( ub::product( n1   ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n2   ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n11  ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n12  ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n21  ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n22  ), 4U);
  BOOST_CHECK_EQUAL ( ub::product( n32  ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n111 ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n211 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n121 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n112 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n123 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n321 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n213 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n432 ),24U);
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_product,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("product"))
{

  namespace ub = boost::numeric::ublas;
  BOOST_CHECK_EQUAL ( ub::product( n    ), 0U);
//FIXME:  BOOST_CHECK_EQUAL ( ub::product( n1   ), 1U);
//FIXME:  BOOST_CHECK_EQUAL ( ub::product( n2   ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n11  ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n12  ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n21  ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n22  ), 4U);
  BOOST_CHECK_EQUAL ( ub::product( n32  ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n111 ), 1U);
  BOOST_CHECK_EQUAL ( ub::product( n211 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n121 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n112 ), 2U);
  BOOST_CHECK_EQUAL ( ub::product( n123 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n321 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n213 ), 6U);
  BOOST_CHECK_EQUAL ( ub::product( n432 ),24U);
}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_equal,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("equal"))
{
  BOOST_CHECK (  n   == n   );
  BOOST_CHECK (  n1  == n1  );
  BOOST_CHECK (  n2  == n2  );
  BOOST_CHECK (  n11 == n11 );
  BOOST_CHECK (  n12 == n12 );
  BOOST_CHECK (  n21 == n21 );
  BOOST_CHECK (  n22 == n22 );
  BOOST_CHECK (  n32 == n32 );
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_rank_equal,
                        fixture_extents_static_rank,
                        *boost::unit_test::label("extents_static_rank")
                          *boost::unit_test::label("equal"))
{
  BOOST_CHECK (  n   == n   );
  BOOST_CHECK (  n1  == n1  );
  BOOST_CHECK (  n2  == n2  );
  BOOST_CHECK (  n11 == n11 );
  BOOST_CHECK (  n12 == n12 );
  BOOST_CHECK (  n21 == n21 );
  BOOST_CHECK (  n22 == n22 );
  BOOST_CHECK (  n32 == n32 );
}

BOOST_FIXTURE_TEST_CASE(test_extents_static_equal,
                        fixture_extents_static,
                        *boost::unit_test::label("extents_static")
                          *boost::unit_test::label("equal"))
{
  BOOST_CHECK (  n   == n   );
  BOOST_CHECK (  n1  == n1  );
  BOOST_CHECK (  n2  == n2  );
  BOOST_CHECK (  n11 == n11 );
  BOOST_CHECK (  n12 == n12 );
  BOOST_CHECK (  n21 == n21 );
  BOOST_CHECK (  n22 == n22 );
  BOOST_CHECK (  n32 == n32 );
}

BOOST_FIXTURE_TEST_CASE(test_extents_dynamic_rank_not_equal,
                        fixture_extents_dynamic_rank,
                        *boost::unit_test::label("extents_dynamic_rank")
                          *boost::unit_test::label("not_equal"))
{
  BOOST_CHECK (  ! (n   != n )  );
  BOOST_CHECK (  ! (n1  != n1)  );
  BOOST_CHECK (  ! (n2  != n2)  );
  BOOST_CHECK (  ! (n11 != n11) );
  BOOST_CHECK (  ! (n12 != n12) );
  BOOST_CHECK (  ! (n21 != n21) );
  BOOST_CHECK (  ! (n22 != n22) );
  BOOST_CHECK (  ! (n32 != n32) );
  BOOST_CHECK (   (n2  != n1)  );
  BOOST_CHECK (   (n11 != n12) );
  BOOST_CHECK (   (n12 != n21) );
  BOOST_CHECK (   (n21 != n22) );
  BOOST_CHECK (   (n22 != n32) );
}

BOOST_AUTO_TEST_SUITE_END()


#define BOOST_TEST_MODULE "test_static_matrix"

#ifdef UNITTEST_FRAMEWORK_LIBRARY_EXIST
#include <boost/test/unit_test.hpp>
#else
#define BOOST_TEST_NO_LIB
#include <boost/test/included/unit_test.hpp>
#endif

#include "../src/Matrix.hpp"
template<std::size_t N, std::size_t M>
using MatrixNMd = ax::Matrix<double, N, M>;

#include "test_Defs.hpp"
using ax::test::tolerance;
using ax::test::seed;
using ax::test::Dim_N;
using ax::test::Dim_M;

#include <random>

BOOST_AUTO_TEST_CASE(MatrixNd_Ctor)
{
    const MatrixNMd<Dim_N, Dim_M> mat;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat(i,j), 0e0);

    const MatrixNMd<Dim_N, Dim_M> mat1(1e0);

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat1(i,j), 1e0);
            else
                BOOST_CHECK_EQUAL(mat1(i,j), 0e0);

    const MatrixNMd<Dim_N, Dim_M> mat2(mat1);

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat2(i,j), 1e0);
            else
                BOOST_CHECK_EQUAL(mat2(i,j), 0e0);

    MatrixNMd<Dim_N, Dim_M> mat3;
    mat3 = mat1;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat3(i,j), 1e0);
            else
                BOOST_CHECK_EQUAL(mat3(i,j), 0e0);
}

BOOST_AUTO_TEST_CASE(MatrixNd_Add)
{
    const MatrixNMd<Dim_N, Dim_M> mat1(1e0);
    const MatrixNMd<Dim_N, Dim_M> mat2(2e0);
    const MatrixNMd<Dim_N, Dim_M> mat3(mat1 + mat2);

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat3(i,j), 3e0);
            else
                BOOST_CHECK_EQUAL(mat3(i,j), 0e0);

    const MatrixNMd<Dim_N, Dim_M> mat4 = mat1 + mat2;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat4(i,j), 3e0);
            else
                BOOST_CHECK_EQUAL(mat4(i,j), 0e0);

    MatrixNMd<Dim_N, Dim_M> mat5;
    mat5 = mat1 + mat2;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat5(i,j), 3e0);
            else
                BOOST_CHECK_EQUAL(mat5(i,j), 0e0);

    MatrixNMd<Dim_N, Dim_M> mat6;
    mat6 += mat1;
    mat6 += mat2;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat6(i,j), 3e0);
            else
                BOOST_CHECK_EQUAL(mat6(i,j), 0e0);

    // ~~~~~~~~~~~~~~~~ random ~~~~~~~~~~~~~~~~

    std::mt19937 mt(seed);
    std::uniform_real_distribution<double> randreal(0e0, 1e0);

    std::array<std::array<double, Dim_M>, Dim_N> rand1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand1[i][j] = randreal(mt);

    std::array<std::array<double, Dim_M>, Dim_N> rand2;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand2[i][j] = randreal(mt);

    MatrixNMd<Dim_N, Dim_M> mat7;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat7(i, j) = rand1[i][j];

    MatrixNMd<Dim_N, Dim_M> mat8;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat8(i, j) = rand2[i][j];

    const MatrixNMd<Dim_N, Dim_M> mat9 = mat7 + mat8;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat9(i,j), rand1[i][j] + rand2[i][j]);

    const MatrixNMd<Dim_N, Dim_M> mat10 = mat7 + mat7 + mat7 + mat7 + mat7;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_CLOSE(mat10(i,j), rand1[i][j] * 5, tolerance);
}

BOOST_AUTO_TEST_CASE(MatrixNd_Sub)
{
    const MatrixNMd<Dim_N, Dim_M> mat1(3e0);
    const MatrixNMd<Dim_N, Dim_M> mat2(1e0);
    const MatrixNMd<Dim_N, Dim_M> mat3(mat1 - mat2);

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat3(i,j), 2e0);
            else
                BOOST_CHECK_EQUAL(mat3(i,j), 0e0);

    const MatrixNMd<Dim_N, Dim_M> mat4 = mat1 - mat2;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat4(i,j), 2e0);
            else
                BOOST_CHECK_EQUAL(mat4(i,j), 0e0);

    MatrixNMd<Dim_N, Dim_M> mat5;
    mat5 = mat1 - mat2;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat5(i,j), 2e0);
            else
                BOOST_CHECK_EQUAL(mat5(i,j), 0e0);

    MatrixNMd<Dim_N, Dim_M> mat6(mat1);
    mat6 -= mat2;

    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            if(i == j)
                BOOST_CHECK_EQUAL(mat6(i,j), 2e0);
            else
                BOOST_CHECK_EQUAL(mat6(i,j), 0e0);

    // ~~~~~~~~~~~~~~~~ random ~~~~~~~~~~~~~~~~

    std::mt19937 mt(seed);
    std::uniform_real_distribution<double> randreal(0e0, 1e0);

    std::array<std::array<double, Dim_M>, Dim_N> rand1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand1[i][j] = randreal(mt);

    std::array<std::array<double, Dim_M>, Dim_N> rand2;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand2[i][j] = randreal(mt);

    MatrixNMd<Dim_N, Dim_M> mat7;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat7(i, j) = rand1[i][j];

    MatrixNMd<Dim_N, Dim_M> mat8;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat8(i, j) = rand2[i][j];

    const MatrixNMd<Dim_N, Dim_M> mat9 = mat7 - mat8;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat9(i,j), rand1[i][j] - rand2[i][j]);

    const MatrixNMd<Dim_N, Dim_M> zero;
    const MatrixNMd<Dim_N, Dim_M> mat10 =
        zero - mat7 - mat7 - mat7 - mat7 - mat7;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_CLOSE(mat10(i,j), rand1[i][j] * (-5e0), tolerance);
}

BOOST_AUTO_TEST_CASE(MatrixNd_Scalar_Maltiple)
{
    std::mt19937 mt(seed);
    std::uniform_real_distribution<double> randreal(0e0, 1e0);

    std::array<std::array<double, Dim_M>, Dim_N> rand1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand1[i][j] = randreal(mt);

    MatrixNMd<Dim_N, Dim_M> mat1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat1(i, j) = rand1[i][j];

    const MatrixNMd<Dim_N, Dim_M> mat2(mat1 * 2e0);
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat2(i,j), rand1[i][j] * 2e0);

    const MatrixNMd<Dim_N, Dim_M> mat3 = mat1 * 2e0;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat3(i,j), rand1[i][j] * 2e0);

    const MatrixNMd<Dim_N, Dim_M> mat4(2e0 * mat1);
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat4(i,j), rand1[i][j] * 2e0);

    const MatrixNMd<Dim_N, Dim_M> mat5 = 2e0 * mat1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat5(i,j), rand1[i][j] * 2e0);

    MatrixNMd<Dim_N, Dim_M> mat6;
    mat6 = mat1 * 2e0;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat6(i,j), rand1[i][j] * 2e0);

    mat6 = 2e0 * mat1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat6(i,j), rand1[i][j] * 2e0);

    MatrixNMd<Dim_N, Dim_M> mat7(mat1);
    mat7 *= 2e0;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat7(i,j), rand1[i][j] * 2e0);
}

BOOST_AUTO_TEST_CASE(MatrixNd_Scalar_Division)
{
    std::mt19937 mt(seed);
    std::uniform_real_distribution<double> randreal(0e0, 1e0);

    std::array<std::array<double, Dim_M>, Dim_N> rand1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand1[i][j] = randreal(mt);

    MatrixNMd<Dim_N, Dim_M> mat1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat1(i, j) = rand1[i][j];

    const MatrixNMd<Dim_N, Dim_M> mat2(mat1 / 2e0);
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat2(i,j), rand1[i][j] / 2e0);

    const MatrixNMd<Dim_N, Dim_M> mat3 = mat1 / 2e0;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat3(i,j), rand1[i][j] / 2e0);

    MatrixNMd<Dim_N, Dim_M> mat4;
    mat4 = mat1 / 2e0;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat4(i,j), rand1[i][j] / 2e0);

    MatrixNMd<Dim_N, Dim_M> mat5(mat1);
    mat5 /= 2e0;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat5(i,j), rand1[i][j] / 2e0);
}

BOOST_AUTO_TEST_CASE(MatrixNd_Multiple)
{
    std::mt19937 mt(seed);
    std::uniform_real_distribution<double> randreal(0e0, 1e0);

    std::array<std::array<double, Dim_M>, Dim_N> rand1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand1[i][j] = randreal(mt);

    std::array<std::array<double, Dim_N>, Dim_M> rand2;
    for(std::size_t i=0; i<Dim_M; ++i)
        for(std::size_t j=0; j<Dim_N; ++j)
            rand2[i][j] = randreal(mt);

    MatrixNMd<Dim_N, Dim_M> mat1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat1(i, j) = rand1[i][j];

    MatrixNMd<Dim_M, Dim_N> mat2;
    for(std::size_t i=0; i<Dim_M; ++i)
        for(std::size_t j=0; j<Dim_N; ++j)
            mat2(i, j) = rand2[i][j];

    const MatrixNMd<Dim_N, Dim_N> mat3 = mat1 * mat2;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_N; ++j)
        {
            double value = 0e0;
            for(std::size_t k=0; k<Dim_M; ++k)
                value += rand1[i][k] * rand2[k][j];

            BOOST_CHECK_EQUAL(mat3(i,j), value);
        }

    const MatrixNMd<Dim_M, Dim_M> mat4 = mat2 * mat1;
    for(std::size_t i=0; i<Dim_M; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
        {
            double value = 0e0;
            for(std::size_t k=0; k<Dim_N; ++k)
                value += rand2[i][k] * rand1[k][j];

            BOOST_CHECK_EQUAL(mat4(i,j), value);
        }
}

BOOST_AUTO_TEST_CASE(MatrixNd_transpose)
{
    std::mt19937 mt(seed);
    std::uniform_real_distribution<double> randreal(0e0, 1e0);

    std::array<std::array<double, Dim_M>, Dim_N> rand1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            rand1[i][j] = randreal(mt);

    MatrixNMd<Dim_N, Dim_M> mat1;
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            mat1(i, j) = rand1[i][j];

    const MatrixNMd<Dim_M, Dim_N> mat2 = transpose(mat1);
    for(std::size_t i=0; i<Dim_N; ++i)
        for(std::size_t j=0; j<Dim_M; ++j)
            BOOST_CHECK_EQUAL(mat2(j, i), rand1[i][j]);
}