/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_TEST_LAWS_SEQUENCE_HPP
#define BOOST_HANA_TEST_LAWS_SEQUENCE_HPP

#include <boost/hana/assert.hpp>
#include <boost/hana/config.hpp>
#include <boost/hana/core/datatype.hpp>
#include <boost/hana/functional/compose.hpp>
#include <boost/hana/functional/id.hpp>
#include <boost/hana/functional/partial.hpp>
#include <boost/hana/integral_constant.hpp>
#include <boost/hana/maybe.hpp>
#include <boost/hana/sequence.hpp>

#include <laws/base.hpp>

#include <test/equivalence_class.hpp>
#include <test/identity.hpp>
#include <test/minimal_product.hpp>
#include <test/numeric.hpp>
#include <test/seq.hpp>

#include <type_traits>
#include <vector>

namespace boost { namespace hana { namespace test {
    template <typename S, typename = when<true>>
    struct TestSequence : TestSequence<S, laws> {
        using TestSequence<S, laws>::TestSequence;
    };

    template <typename S>
    struct TestSequence<S, laws> {
        static_assert(_models<Sequence, S>{}, "");

        template <int i>
        using eq = integer<i,
              Policy::Comparable
            | Policy::Constant
        >;

        template <int i>
        using cx_eq = integer<i,
              Policy::Comparable
            | Policy::Constexpr
        >;

        template <int i>
        using ord = integer<i,
              Policy::Orderable
            | Policy::Constant
        >;

        struct undefined { };

        TestSequence() {
            constexpr auto list = make<S>;

            //////////////////////////////////////////////////////////////////
            // Check for basic data type consistency
            //////////////////////////////////////////////////////////////////
            struct Random;
            static_assert(std::is_same<datatype_t<decltype(list())>, S>{}, "");
            static_assert(std::is_same<datatype_t<decltype(list(1))>, S>{}, "");
            static_assert(std::is_same<datatype_t<decltype(list(1, '2'))>, S>{}, "");
            static_assert(std::is_same<datatype_t<decltype(list(1, '2', 3.3))>, S>{}, "");
            static_assert(!std::is_same<datatype_t<Random>, S>{}, "");

            //////////////////////////////////////////////////////////////////
            // Foldable -> Sequence conversion
            //////////////////////////////////////////////////////////////////
            {
            auto foldable = seq;

            BOOST_HANA_CONSTANT_CHECK(equal(
                to<S>(foldable()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                to<S>(foldable(eq<0>{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                to<S>(foldable(eq<0>{}, eq<1>{})),
                list(eq<0>{}, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                to<S>(foldable(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                to<S>(foldable(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})
            ));
            }

            //////////////////////////////////////////////////////////////////
            // make (tautological given our definition of `list`)
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                make<S>(),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                make<S>(eq<0>{}),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                make<S>(eq<0>{}, eq<1>{}),
                list(eq<0>{}, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                make<S>(eq<0>{}, eq<1>{}, eq<2>{}),
                list(eq<0>{}, eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                make<S>(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}),
                list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})
            ));

            //////////////////////////////////////////////////////////////////
            // init
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                init(list(undefined{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                init(list(eq<0>{}, undefined{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                init(list(eq<0>{}, eq<1>{}, undefined{})),
                list(eq<0>{}, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                init(list(eq<0>{}, eq<1>{}, eq<2>{}, undefined{})),
                list(eq<0>{}, eq<1>{}, eq<2>{})
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                init(list(cx_eq<1>{}, cx_eq<2>{})),
                list(cx_eq<1>{})
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                init(list(cx_eq<1>{}, cx_eq<2>{}, cx_eq<3>{})),
                list(cx_eq<1>{}, cx_eq<2>{})
            ));

            //////////////////////////////////////////////////////////////////
            // intersperse
            //////////////////////////////////////////////////////////////////
            {
            auto z = eq<999>{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(), undefined{}),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(eq<0>{}), undefined{}),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(eq<0>{}, eq<1>{}), z),
                list(eq<0>{}, z, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(eq<0>{}, eq<1>{}, eq<2>{}), z),
                list(eq<0>{}, z, eq<1>{}, z, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}), z),
                list(eq<0>{}, z, eq<1>{}, z, eq<2>{}, z, eq<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{}), z),
                list(eq<0>{}, z, eq<1>{}, z, eq<2>{}, z, eq<3>{}, z, eq<4>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                intersperse(list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{}, eq<5>{}), z),
                list(eq<0>{}, z, eq<1>{}, z, eq<2>{}, z, eq<3>{}, z, eq<4>{}, z, eq<5>{})
            ));
            }

            //////////////////////////////////////////////////////////////////
            // slice
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(), size_t<0>, size_t<0>),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(undefined{}), size_t<0>, size_t<0>),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(undefined{}, undefined{}), size_t<0>, size_t<0>),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(eq<0>{}), size_t<0>, size_t<1>),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(eq<0>{}, undefined{}), size_t<0>, size_t<1>),
                list(eq<0>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(undefined{}, eq<1>{}), size_t<1>, size_t<2>),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(undefined{}, eq<1>{}, undefined{}), size_t<1>, size_t<2>),
                list(eq<1>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(eq<0>{}, eq<1>{}), size_t<0>, size_t<2>),
                list(eq<0>{}, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(eq<0>{}, eq<1>{}, undefined{}), size_t<0>, size_t<2>),
                list(eq<0>{}, eq<1>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                slice(list(undefined{}, eq<1>{}, eq<2>{}), size_t<1>, size_t<3>),
                list(eq<1>{}, eq<2>{})
            ));

            //////////////////////////////////////////////////////////////////
            // take.at_most
            //////////////////////////////////////////////////////////////////
            {
            auto take = hana::take.at_most;

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<0>, list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<1>, list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<2>, list()),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<0>, list(eq<0>{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<1>, list(eq<0>{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<2>, list(eq<0>{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<3>, list(eq<0>{})),
                list(eq<0>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<0>, list(eq<0>{}, eq<1>{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<1>, list(eq<0>{}, eq<1>{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<2>, list(eq<0>{}, eq<1>{})),
                list(eq<0>{}, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<3>, list(eq<0>{}, eq<1>{})),
                list(eq<0>{}, eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<10>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{},  eq<4>{},  eq<5>{},  eq<6>{},
                                      eq<7>{}, eq<8>{}, eq<9>{}, eq<10>{}, eq<11>{}, eq<12>{}, eq<13>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{}, eq<5>{}, eq<6>{}, eq<7>{}, eq<8>{}, eq<9>{})
            ));
            }

            //////////////////////////////////////////////////////////////////
            // take.exactly
            //////////////////////////////////////////////////////////////////
            {
            auto take = hana::take.exactly;

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<0>, list()),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<0>, list(eq<0>{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<1>, list(eq<0>{})),
                list(eq<0>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<0>, list(eq<0>{}, eq<1>{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<1>, list(eq<0>{}, eq<1>{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<2>, list(eq<0>{}, eq<1>{})),
                list(eq<0>{}, eq<1>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take(size_t<10>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{},  eq<4>{},  eq<5>{},  eq<6>{},
                                      eq<7>{}, eq<8>{}, eq<9>{}, eq<10>{}, eq<11>{}, eq<12>{}, eq<13>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{}, eq<5>{}, eq<6>{}, eq<7>{}, eq<8>{}, eq<9>{})
            ));
            }

            //////////////////////////////////////////////////////////////////
            // remove_at
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<0>, list(eq<0>{})),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<0>, list(eq<0>{}, eq<1>{})),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<1>, list(eq<0>{}, eq<1>{})),
                list(eq<0>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<0>, list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<1>, list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<0>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<2>, list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<0>{}, eq<1>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<0>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})),
                list(eq<1>{}, eq<2>{}, eq<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<1>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})),
                list(eq<0>{}, eq<2>{}, eq<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<2>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})),
                list(eq<0>{}, eq<1>{}, eq<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<3>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<0>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{})),
                list(eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<1>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{})),
                list(eq<0>{}, eq<2>{}, eq<3>{}, eq<4>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<2>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{})),
                list(eq<0>{}, eq<1>{}, eq<3>{}, eq<4>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<3>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{}, eq<4>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at(size_t<4>, list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{})),
                list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{})
            ));

            //////////////////////////////////////////////////////////////////
            // remove_at_c
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at_c<0>(list(eq<0>{})),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at_c<0>(list(eq<0>{}, eq<1>{})),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at_c<1>(list(eq<0>{}, eq<1>{})),
                list(eq<0>{})
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at_c<0>(list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at_c<1>(list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<0>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                remove_at_c<2>(list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<0>{}, eq<1>{})
            ));

            //////////////////////////////////////////////////////////////////
            // reverse
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                reverse(list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                reverse(list(eq<0>{})),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                reverse(list(eq<0>{}, eq<1>{})),
                list(eq<1>{}, eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                reverse(list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(eq<2>{}, eq<1>{}, eq<0>{})
            ));

            BOOST_HANA_CONSTEXPR_CHECK(equal(
                reverse(list(cx_eq<1>{})),
                list(cx_eq<1>{})
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                reverse(list(cx_eq<1>{}, cx_eq<2>{})),
                list(cx_eq<2>{}, cx_eq<1>{})
            ));
            BOOST_HANA_CONSTEXPR_CHECK(equal(
                reverse(list(cx_eq<1>{}, cx_eq<2>{}, cx_eq<3>{})),
                list(cx_eq<3>{}, cx_eq<2>{}, cx_eq<1>{})
            ));

            //////////////////////////////////////////////////////////////////
            // sort
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort(list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort(list(ord<0>{})),
                list(ord<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort(list(ord<0>{}, ord<1>{})),
                list(ord<0>{}, ord<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort(list(ord<1>{}, ord<0>{})),
                list(ord<0>{}, ord<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort(list(ord<1>{}, ord<0>{}, ord<4>{}, ord<2>{})),
                list(ord<0>{}, ord<1>{}, ord<2>{}, ord<4>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort(list(ord<1>{}, ord<0>{}, ord<-4>{}, ord<2>{})),
                list(ord<-4>{}, ord<0>{}, ord<1>{}, ord<2>{})
            ));

            //////////////////////////////////////////////////////////////////
            // unzip
            //////////////////////////////////////////////////////////////////
            {
            auto t = list; // tests are unreadable otherwise. mnemonic: tuple

            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t())),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(), t())),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(eq<0>{}, eq<2>{}), t(eq<1>{}, eq<3>{}, eq<4>{}))),
                list(t(eq<0>{}, eq<1>{}), t(eq<2>{}, eq<3>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(),     t(),     t()))    ,
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(eq<0>{}), t(),     t()))    ,
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(),     t(eq<1>{}), t()))    ,
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(),     t(),     t(eq<2>{}))),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(eq<0>{}), t(eq<1>{}), t()))    ,
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(),     t(eq<1>{}), t(eq<2>{}))),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(eq<0>{}), t(),     t(eq<2>{}))),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(eq<0>{}), t(eq<1>{}), t(eq<2>{}))),
                list(t(eq<0>{}, eq<1>{}, eq<2>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                unzip(list(t(eq<0>{}, eq<3>{}), t(eq<1>{}, eq<4>{}), t(eq<2>{}, eq<5>{}))),
                list(t(eq<0>{}, eq<1>{}, eq<2>{}), t(eq<3>{}, eq<4>{}, eq<5>{}))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // span
            //////////////////////////////////////////////////////////////////
            {
            auto z = eq<999>{};
            auto prod = minimal_product;

            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(), equal.to(z)),
                prod(list(), list())
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(eq<0>{}), equal.to(z)),
                prod(list(), list(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(z), equal.to(z)),
                prod(list(z), list())
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(eq<0>{}, z), equal.to(z)),
                prod(list(), list(eq<0>{}, z))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(z, eq<0>{}), equal.to(z)),
                prod(list(z), list(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(eq<0>{}, eq<1>{}), equal.to(z)),
                prod(list(), list(eq<0>{}, eq<1>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(eq<0>{}, eq<1>{}, eq<2>{}), equal.to(z)),
                prod(list(), list(eq<0>{}, eq<1>{}, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(z, eq<1>{}, eq<2>{}), equal.to(z)),
                prod(list(z), list(eq<1>{}, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(eq<0>{}, z, eq<2>{}), equal.to(z)),
                prod(list(), list(eq<0>{}, z, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(z, z, eq<2>{}), equal.to(z)),
                prod(list(z, z), list(eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                span(list(z, z, z), equal.to(z)),
                prod(list(z, z, z), list())
            ));
            }

            //////////////////////////////////////////////////////////////////
            // take_while
            //////////////////////////////////////////////////////////////////
            {
            auto z = eq<999>{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(), not_equal.to(z)),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(eq<1>{}), not_equal.to(z)),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(z), not_equal.to(z)),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(eq<1>{}, eq<2>{}), not_equal.to(z)),
                list(eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(eq<1>{}, z), not_equal.to(z)),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(z, eq<2>{}), not_equal.to(z)),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(eq<1>{}, eq<2>{}, eq<3>{}), not_equal.to(z)),
                list(eq<1>{}, eq<2>{}, eq<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(eq<1>{}, eq<2>{}, z), not_equal.to(z)),
                list(eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(eq<1>{}, z, eq<3>{}), not_equal.to(z)),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_while(list(z, eq<2>{}, eq<3>{}), not_equal.to(z)),
                list()
            ));
            }

            //////////////////////////////////////////////////////////////////
            // take_until
            //////////////////////////////////////////////////////////////////
            {
            auto z = eq<999>{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(), equal.to(z)),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(eq<1>{}), equal.to(z)),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(z), equal.to(z)),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(eq<1>{}, eq<2>{}), equal.to(z)),
                list(eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(eq<1>{}, z), equal.to(z)),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(z, eq<2>{}), equal.to(z)),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(eq<1>{}, eq<2>{}, eq<3>{}), equal.to(z)),
                list(eq<1>{}, eq<2>{}, eq<3>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(eq<1>{}, eq<2>{}, z), equal.to(z)),
                list(eq<1>{}, eq<2>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(eq<1>{}, z, eq<3>{}), equal.to(z)),
                list(eq<1>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                take_until(list(z, eq<2>{}, eq<3>{}), equal.to(z)),
                list()
            ));
            }

            //////////////////////////////////////////////////////////////////
            // permutations
            //////////////////////////////////////////////////////////////////
            {
            auto permute = [=](auto xs) {
                return [=](auto ...expected_) {
                    auto actual = permutations(xs);
                    auto expected = list(expected_...);

                    BOOST_HANA_CONSTANT_CHECK(and_(
                        equal(length(expected), length(actual)),
                        all_of(actual, [=](auto x) { return elem(expected, x); })
                    ));
                };
            };

            BOOST_HANA_CONSTANT_CHECK(equal(permutations(list()), list(list())));

            permute(list(eq<0>{}))(list(eq<0>{}));
            permute(list(eq<0>{}, eq<1>{}))(
                list(eq<0>{}, eq<1>{}),
                list(eq<1>{}, eq<0>{})
            );
            permute(list(eq<0>{}, eq<1>{}, eq<2>{}))(
                list(eq<0>{}, eq<1>{}, eq<2>{}),
                list(eq<0>{}, eq<2>{}, eq<1>{}),
                list(eq<1>{}, eq<0>{}, eq<2>{}),
                list(eq<1>{}, eq<2>{}, eq<0>{}),
                list(eq<2>{}, eq<0>{}, eq<1>{}),
                list(eq<2>{}, eq<1>{}, eq<0>{})
            );
            }

            //////////////////////////////////////////////////////////////////
            // group
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list()),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{})),
                list(list(eq<0>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<0>{})),
                list(list(eq<0>{}, eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<1>{})),
                list(list(eq<0>{}), list(eq<1>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<0>{}, eq<0>{})),
                list(list(eq<0>{}, eq<0>{}, eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<0>{}, eq<1>{})),
                list(list(eq<0>{}, eq<0>{}), list(eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<1>{}, eq<0>{})),
                list(list(eq<0>{}),
                     list(eq<1>{}),
                     list(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<1>{}, eq<0>{}, eq<0>{})),
                list(list(eq<1>{}),
                     list(eq<0>{}, eq<0>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<0>{}, eq<1>{}, eq<1>{})),
                list(list(eq<0>{}, eq<0>{}),
                     list(eq<1>{}, eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group(list(eq<0>{}, eq<0>{}, eq<1>{}, eq<1>{}, eq<2>{}, eq<2>{}, eq<2>{})),
                list(list(eq<0>{}, eq<0>{}),
                     list(eq<1>{}, eq<1>{}),
                     list(eq<2>{}, eq<2>{}, eq<2>{}))
            ));

            //////////////////////////////////////////////////////////////////
            // partition
            //////////////////////////////////////////////////////////////////
            {
            auto prod = minimal_product;
            auto pred = in ^ list(eq<-1>{}, eq<-2>{}, eq<-3>{}, eq<-4>{}, eq<-5>{});

            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(), pred),
                prod(list(), list())
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(eq<0>{}), pred),
                prod(list(),
                     list(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(eq<0>{}, eq<1>{}), pred),
                prod(list(),
                     list(eq<0>{}, eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(eq<-1>{}), pred),
                prod(list(eq<-1>{}),
                     list())
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(eq<-1>{}, eq<0>{}, eq<2>{}), pred),
                prod(list(eq<-1>{}),
                     list(eq<0>{}, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(eq<0>{}, eq<-3>{}, eq<2>{}, eq<-5>{}, eq<6>{}), pred),
                prod(list(eq<-3>{}, eq<-5>{}),
                     list(eq<0>{}, eq<2>{}, eq<6>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                partition(list(eq<-1>{}, eq<2>{}, eq<-3>{}, eq<0>{}, eq<-3>{}, eq<4>{}), pred),
                prod(list(eq<-1>{}, eq<-3>{}, eq<-3>{}),
                     list(eq<2>{}, eq<0>{}, eq<4>{}))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // scanl
            //////////////////////////////////////////////////////////////////
            {
            _injection<0> f{};
            auto s = eq<999>{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl(list(), s, f),
                list(s)
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl(list(eq<0>{}), s, f),
                list(s, f(s, eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl(list(eq<0>{}, eq<1>{}), s, f),
                list(s, f(s, eq<0>{}), f(f(s, eq<0>{}), eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl(list(eq<0>{}, eq<1>{}, eq<2>{}), s, f),
                list(
                    s,
                    f(s, eq<0>{}),
                    f(f(s, eq<0>{}), eq<1>{}),
                    f(f(f(s, eq<0>{}), eq<1>{}), eq<2>{})
                )
            ));
            }

            //////////////////////////////////////////////////////////////////
            // scanr
            //////////////////////////////////////////////////////////////////
            {
            _injection<0> f{};
            auto s = eq<999>{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr(list(), s, f),
                list(s)
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr(list(eq<0>{}), s, f),
                list(
                    f(eq<0>{}, s),
                    s
                )
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr(list(eq<0>{}, eq<1>{}), s, f),
                list(
                    f(eq<0>{}, f(eq<1>{}, s)),
                    f(eq<1>{}, s),
                    s
                )
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr(list(eq<0>{}, eq<1>{}, eq<2>{}), s, f),
                list(
                    f(eq<0>{}, f(eq<1>{}, f(eq<2>{}, s))),
                    f(eq<1>{}, f(eq<2>{}, s)),
                    f(eq<2>{}, s),
                    s
                )
            ));
            }

            //////////////////////////////////////////////////////////////////
            // scanl1
            //////////////////////////////////////////////////////////////////
            {
            _injection<0> f{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl1(list(), f),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl1(list(eq<0>{}), f),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl1(list(eq<0>{}, eq<1>{}), f),
                list(eq<0>{}, f(eq<0>{}, eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl1(list(eq<0>{}, eq<1>{}, eq<2>{}), f),
                list(
                    eq<0>{},
                    f(eq<0>{}, eq<1>{}),
                    f(f(eq<0>{}, eq<1>{}), eq<2>{})
                )
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanl1(list(eq<0>{}, eq<1>{}, eq<2>{}, eq<3>{}), f),
                list(
                    eq<0>{},
                    f(eq<0>{}, eq<1>{}),
                    f(f(eq<0>{}, eq<1>{}), eq<2>{}),
                    f(f(f(eq<0>{}, eq<1>{}), eq<2>{}), eq<3>{})
                )
            ));
            }

            //////////////////////////////////////////////////////////////////
            // scanr1
            //////////////////////////////////////////////////////////////////
            {
            _injection<0> f{};

            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr1(list(), f),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr1(list(eq<0>{}), f),
                list(eq<0>{})
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr1(list(eq<0>{}, eq<1>{}), f),
                list(
                    f(eq<0>{}, eq<1>{}),
                    eq<1>{}
                )
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                scanr1(list(eq<0>{}, eq<1>{}, eq<2>{}), f),
                list(
                    f(eq<0>{}, f(eq<1>{}, eq<2>{})),
                    f(eq<1>{}, eq<2>{}),
                    eq<2>{}
                )
            ));
            }

            //////////////////////////////////////////////////////////////////
            // unfoldl
            //////////////////////////////////////////////////////////////////
            {
            auto prod = minimal_product;
            _injection<0> f{};
            auto stop_at = [=](auto stop) {
                return [=](auto x) {
                    return hana::if_(hana::equal(stop, x),
                        hana::nothing,
                        hana::just(prod(hana::succ(x), f(x)))
                    );
                };
            };

            BOOST_HANA_CONSTANT_CHECK(equal(
                unfoldl<S>(stop_at(int_<0>), int_<0>),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unfoldl<S>(stop_at(int_<1>), int_<0>),
                list(f(int_<0>))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unfoldl<S>(stop_at(int_<2>), int_<0>),
                list(f(int_<1>), f(int_<0>))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unfoldl<S>(stop_at(int_<3>), int_<0>),
                list(f(int_<2>), f(int_<1>), f(int_<0>))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                unfoldl<S>(stop_at(int_<4>), int_<0>),
                list(f(int_<3>), f(int_<2>), f(int_<1>), f(int_<0>))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // unfoldr
            //////////////////////////////////////////////////////////////////
            {
            auto prod = minimal_product;
            _injection<0> f{};
            auto stop_at = [=](auto stop) {
                return [=](auto x) {
                    return hana::if_(hana::equal(stop, x),
                        nothing,
                        hana::just(prod(f(x), hana::succ(x)))
                    );
                };
            };

            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                unfoldr<S>(stop_at(int_<0>), int_<0>),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                unfoldr<S>(stop_at(int_<1>), int_<0>),
                list(f(int_<0>))
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                unfoldr<S>(stop_at(int_<2>), int_<0>),
                list(f(int_<0>), f(int_<1>))
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                unfoldr<S>(stop_at(int_<3>), int_<0>),
                list(f(int_<0>), f(int_<1>), f(int_<2>))
            ));
            BOOST_HANA_CONSTANT_CHECK(hana::equal(
                unfoldr<S>(stop_at(int_<4>), int_<0>),
                list(f(int_<0>), f(int_<1>), f(int_<2>), f(int_<3>))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // Make sure unfold{r,l} can be reversed under certain conditions.
            //////////////////////////////////////////////////////////////////
            {
            auto prod = minimal_product;
            auto z = eq<999>{};
            auto f = prod;
            auto g = [=](auto k) {
                return if_(equal(k, z), nothing, just(k));
            };

            // Make sure the special conditions are met
            BOOST_HANA_CONSTANT_CHECK(equal(
                g(z),
                nothing
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                g(f(eq<0>{}, z)),
                just(prod(eq<0>{}, z))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                g(f(z, eq<0>{})),
                just(prod(z, eq<0>{}))
            ));

            // Make sure the reversing works
            auto lists = list(
                list(),
                list(eq<0>{}),
                list(eq<0>{}, eq<1>{}),
                list(eq<0>{}, eq<1>{}, eq<2>{})
            );
            for_each(lists, [=](auto xs) {
                BOOST_HANA_CONSTANT_CHECK(equal(
                    unfoldl<S>(g, foldl(xs, z, f)),
                    xs
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    unfoldr<S>(g, foldr(xs, z, f)),
                    xs
                ));
            });
            }

            //////////////////////////////////////////////////////////////////
            // sort_by
            //////////////////////////////////////////////////////////////////
            {
            auto pred = [](auto x, auto y) {
                return less(x.unwrap, y.unwrap);
            };
            auto a = [](auto z) { return test::tag(eq<999>{}, z); };
            auto b = [](auto z) { return test::tag(eq<888>{}, z); };

            auto check = [=](auto ...sorted) {
                auto perms = transform(
                    permutations(list(a(sorted)...)),
                    partial(sort_by, pred)
                );
                BOOST_HANA_CONSTANT_CHECK(all_of(perms, [=](auto xs) {
                    return equal(xs, list(a(sorted)...));
                }));
            };

            check();
            check(ord<1>{});
            check(ord<1>{}, ord<2>{});
            check(ord<1>{}, ord<2>{}, ord<3>{});

            // check stability
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(a(ord<1>{}), b(ord<1>{}))),
                list(a(ord<1>{}), b(ord<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(b(ord<1>{}), a(ord<1>{}))),
                list(b(ord<1>{}), a(ord<1>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(a(ord<1>{}), b(ord<1>{}), a(ord<2>{}), b(ord<2>{}))),
                list(a(ord<1>{}), b(ord<1>{}), a(ord<2>{}), b(ord<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(a(ord<1>{}), a(ord<2>{}), b(ord<1>{}), b(ord<2>{}))),
                list(a(ord<1>{}), b(ord<1>{}), a(ord<2>{}), b(ord<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(b(ord<1>{}), a(ord<1>{}), a(ord<2>{}), b(ord<2>{}))),
                list(b(ord<1>{}), a(ord<1>{}), a(ord<2>{}), b(ord<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(a(ord<2>{}), b(ord<1>{}), b(ord<2>{}), a(ord<1>{}))),
                list(b(ord<1>{}), a(ord<1>{}), a(ord<2>{}), b(ord<2>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                sort_by(pred, list(a(ord<1>{}), a(ord<3>{}), b(ord<1>{}), a(ord<2>{}), b(ord<3>{}))),
                list(a(ord<1>{}), b(ord<1>{}), a(ord<2>{}), a(ord<3>{}), b(ord<3>{}))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // group_by
            //////////////////////////////////////////////////////////////////
            {
            auto a = [](auto z) { return test::tag(eq<999>{}, z); };
            auto b = [](auto z) { return test::tag(eq<888>{}, z); };

            BOOST_HANA_CONSTEXPR_LAMBDA auto pred = [](auto x, auto y) {
                return equal(x.unwrap, y.unwrap);
            };

            BOOST_HANA_CONSTANT_CHECK(equal(
                group_by(pred, list()),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                group_by(pred, list(a(eq<0>{}))),
                list(list(a(eq<0>{})))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group_by(pred, list(a(eq<0>{}), b(eq<0>{}))),
                list(list(a(eq<0>{}), b(eq<0>{})))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group_by(pred, list(a(eq<0>{}), b(eq<0>{}), a(eq<1>{}))),
                list(list(a(eq<0>{}), b(eq<0>{})), list(a(eq<1>{})))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group_by(pred, list(a(eq<0>{}), b(eq<0>{}), a(eq<1>{}), b(eq<1>{}))),
                list(list(a(eq<0>{}), b(eq<0>{})), list(a(eq<1>{}), b(eq<1>{})))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                group_by(pred, list(a(eq<0>{}), b(eq<0>{}), a(eq<1>{}), b(eq<1>{}), b(eq<0>{}))),
                list(list(a(eq<0>{}), b(eq<0>{})), list(a(eq<1>{}), b(eq<1>{})), list(b(eq<0>{})))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // zip.shortest
            //////////////////////////////////////////////////////////////////
            {
            auto zip = hana::zip.shortest;
            auto t = list; // tests are unreadable otherwise. mnemonic: tuple

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{})),
                list(t(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}, eq<1>{})),
                list(t(eq<0>{}), t(eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(t(eq<0>{}), t(eq<1>{}), t(eq<2>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}), list(eq<1>{})),
                list(t(eq<0>{}, eq<1>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}), list(eq<1>{}), list(eq<2>{})),
                list(t(eq<0>{}, eq<1>{}, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}, eq<3>{}), list(eq<1>{}, eq<4>{}), list(eq<2>{}, eq<5>{}, eq<8>{})),
                list(t(eq<0>{}, eq<1>{}, eq<2>{}), t(eq<3>{}, eq<4>{}, eq<5>{}))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // zip.unsafe
            //////////////////////////////////////////////////////////////////
            {
            auto zip = hana::zip.unsafe;
            auto t = list; // tests are unreadable otherwise. mnemonic: tuple

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{})),
                list(t(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}, eq<1>{})),
                list(t(eq<0>{}), t(eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(t(eq<0>{}), t(eq<1>{}), t(eq<2>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}), list(eq<1>{})),
                list(t(eq<0>{}, eq<1>{}))
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}), list(eq<1>{}), list(eq<2>{})),
                list(t(eq<0>{}, eq<1>{}, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(list(eq<0>{}, eq<3>{}), list(eq<1>{}, eq<4>{}), list(eq<2>{}, eq<5>{})),
                list(t(eq<0>{}, eq<1>{}, eq<2>{}), t(eq<3>{}, eq<4>{}, eq<5>{}))
            ));
            }

            //////////////////////////////////////////////////////////////////
            // zip.unsafe.with
            //////////////////////////////////////////////////////////////////
            {
            _injection<0> f{};
            auto zip = hana::zip.unsafe.with;

            // zip 1
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{})),
                list(f(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}, eq<1>{})),
                list(f(eq<0>{}), f(eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(f(eq<0>{}), f(eq<1>{}), f(eq<2>{}))
            ));

            // zip 2
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}), list(eq<-1>{})),
                list(f(eq<1>{}, eq<-1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}, eq<2>{}), list(eq<-1>{}, eq<-2>{})),
                list(f(eq<1>{}, eq<-1>{}), f(eq<2>{}, eq<-2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}, eq<2>{}, eq<3>{}), list(eq<-1>{}, eq<-2>{}, eq<-3>{})),
                list(f(eq<1>{}, eq<-1>{}), f(eq<2>{}, eq<-2>{}), f(eq<3>{}, eq<-3>{}))
            ));

            // zip 3
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(),          list(),          list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}), list(eq<1>{}), list(eq<2>{})),
                list(f(eq<0>{}, eq<1>{}, eq<2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}, eq<1>{}), list(eq<2>{}, eq<3>{}), list(eq<4>{}, eq<5>{})),
                list(f(eq<0>{}, eq<2>{}, eq<4>{}), f(eq<1>{}, eq<3>{}, eq<5>{}))
            ));

            // zip 4
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f,
                    list(eq<11>{}, eq<12>{}, eq<13>{}),
                    list(eq<21>{}, eq<22>{}, eq<23>{}),
                    list(eq<31>{}, eq<32>{}, eq<33>{}),
                    list(eq<41>{}, eq<42>{}, eq<43>{})
                ),
                list(
                    f(eq<11>{}, eq<21>{}, eq<31>{}, eq<41>{}),
                    f(eq<12>{}, eq<22>{}, eq<32>{}, eq<42>{}),
                    f(eq<13>{}, eq<23>{}, eq<33>{}, eq<43>{})
                )
            ));

            // zip 5
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f,
                    list(eq<11>{}, eq<12>{}, eq<13>{}, eq<14>{}),
                    list(eq<21>{}, eq<22>{}, eq<23>{}, eq<24>{}),
                    list(eq<31>{}, eq<32>{}, eq<33>{}, eq<34>{}),
                    list(eq<41>{}, eq<42>{}, eq<43>{}, eq<44>{}),
                    list(eq<51>{}, eq<52>{}, eq<53>{}, eq<54>{})
                ),
                list(
                    f(eq<11>{}, eq<21>{}, eq<31>{}, eq<41>{}, eq<51>{}),
                    f(eq<12>{}, eq<22>{}, eq<32>{}, eq<42>{}, eq<52>{}),
                    f(eq<13>{}, eq<23>{}, eq<33>{}, eq<43>{}, eq<53>{}),
                    f(eq<14>{}, eq<24>{}, eq<34>{}, eq<44>{}, eq<54>{})
                )
            ));
            }

            //////////////////////////////////////////////////////////////////
            // zip.shortest.with
            //////////////////////////////////////////////////////////////////
            {
            _injection<0> f{};
            auto zip = hana::zip.shortest.with;

            // zip 1
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{})),
                list(f(eq<0>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}, eq<1>{})),
                list(f(eq<0>{}), f(eq<1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}, eq<1>{}, eq<2>{})),
                list(f(eq<0>{}), f(eq<1>{}), f(eq<2>{}))
            ));

            // zip 2
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(undefined{}), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list(undefined{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}), list(eq<-1>{})),
                list(f(eq<1>{}, eq<-1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}, eq<2>{}), list(eq<-1>{})),
                list(f(eq<1>{}, eq<-1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}), list(eq<-1>{}, eq<-2>{})),
                list(f(eq<1>{}, eq<-1>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}, eq<2>{}), list(eq<-1>{}, eq<-2>{})),
                list(f(eq<1>{}, eq<-1>{}), f(eq<2>{}, eq<-2>{}))
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<1>{}, eq<2>{}, eq<3>{}, eq<4>{}), list(eq<-1>{}, eq<-2>{}, eq<-3>{})),
                list(f(eq<1>{}, eq<-1>{}), f(eq<2>{}, eq<-2>{}), f(eq<3>{}, eq<-3>{}))
            ));

            // zip 3
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list(), list()),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(undefined{}), list(), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list(undefined{}), list()),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list(), list(undefined{})),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(), list(undefined{}), list(undefined{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(undefined{}), list(), list(undefined{})),
                list()
            ));
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(undefined{}, list(undefined{}), list(undefined{}), list()),
                list()
            ));

            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f, list(eq<0>{}), list(eq<1>{}), list(eq<2>{})),
                list(f(eq<0>{}, eq<1>{}, eq<2>{}))
            ));

            // zip 4
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f,
                    list(eq<11>{}, eq<12>{}, eq<13>{}, eq<14>{}),
                    list(eq<21>{}, eq<22>{}, eq<23>{}),
                    list(eq<31>{}, eq<32>{}, eq<33>{}, eq<34>{}),
                    list(eq<41>{}, eq<42>{}, eq<43>{}, eq<44>{}, eq<45>{})
                ),
                list(
                    f(eq<11>{}, eq<21>{}, eq<31>{}, eq<41>{}),
                    f(eq<12>{}, eq<22>{}, eq<32>{}, eq<42>{}),
                    f(eq<13>{}, eq<23>{}, eq<33>{}, eq<43>{})
                )
            ));

            // zip 5
            BOOST_HANA_CONSTANT_CHECK(equal(
                zip(f,
                    list(eq<11>{}, eq<12>{}, eq<13>{}, eq<14>{}),
                    list(eq<21>{}, eq<22>{}, eq<23>{}, eq<24>{}, eq<25>{}),
                    list(eq<31>{}, eq<32>{}, eq<33>{}, eq<34>{}),
                    list(eq<41>{}, eq<42>{}, eq<43>{}, eq<44>{}, eq<45>{}, eq<46>{}),
                    list(eq<51>{}, eq<52>{}, eq<53>{}, eq<54>{}, eq<55>{})
                ),
                list(
                    f(eq<11>{}, eq<21>{}, eq<31>{}, eq<41>{}, eq<51>{}),
                    f(eq<12>{}, eq<22>{}, eq<32>{}, eq<42>{}, eq<52>{}),
                    f(eq<13>{}, eq<23>{}, eq<33>{}, eq<43>{}, eq<53>{}),
                    f(eq<14>{}, eq<24>{}, eq<34>{}, eq<44>{}, eq<54>{})
                )
            ));
            }
        }
    };
}}} // end namespace boost::hana::test

#endif // !BOOST_HANA_TEST_LAWS_SEQUENCE_HPP


// 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.

#ifndef MTL_PRINT_MATRIX_INCLUDE
#define MTL_PRINT_MATRIX_INCLUDE

#include <cstddef>
#include <iostream>
#include <sstream>
#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/utility/range_generator.hpp>

namespace mtl { namespace mat {

template <typename Matrix>
std::ostream& print_matrix(Matrix const& matrix, std::ostream& out= std::cout, std::size_t width= 3, std::size_t precision= 2)
{
    // typedef typename Collection<Matrix>::size_type size_type;
    // all indices will start from 0; otherwise wrong
    for (std::size_t r= 0, nr= num_rows(matrix); r < nr; ++r) {
	out << '[';
	for (std::size_t c= 0, nc= num_cols(matrix); c < nc; ++c) {
	    if (precision)
		out.precision(precision);
	    out.fill (' '); out.width (width);
#ifdef MTL_PRINT_STRING_TMP // probably very slow but looks better for certain types
	    std::ostringstream st;
	    st << matrix(r, c) << (c + 1 < nc ? " " : "]\n");
	    out << std::right << st.str();
#else
	    out << matrix(r, c) << (c + 1 < nc ? " " : "]\n");
#endif
	}
    }
    return out;
}

}} // namespace mtl::matrix

#endif // MTL_PRINT_MATRIX_INCLUDE


/*
 * @file
 * @author University of Warwick
 * @version 1.0
 *
 * @section LICENSE
 *
 * @section DESCRIPTION
 *
 * Tests for the array kernel functions from the utility operations
 *
 */

#define BOOST_TEST_MODULE ArrayKernel
#include <boost/test/unit_test.hpp>
#include <boost/test/output_test_stream.hpp>

#include <stdexcept>
#include "ArrayKernels.h"

// ============================================================
// ==================== Kernel Tests ==========================
// ============================================================
using namespace cupcfd::utility::kernels;

// ==================== add ============================
// Test 1: Test correction addition of two arrays
BOOST_AUTO_TEST_CASE(add_test1)
{
	int dest[5];

	int source1[5] = {1,2,3,4,5};
	int source2[5] = {1,2,18,4,7};

	int source1Cmp[5] = {1,2,3,4,5};
	int source2Cmp[5] = {1,2,18,4,7};
	int resultCmp[5] = {2, 4, 21, 8, 12};

	add(source1, source2, dest, 5);

	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 5, resultCmp, resultCmp + 5);
	BOOST_CHECK_EQUAL_COLLECTIONS(source1, source1 + 5, source1Cmp, source1Cmp + 5);
	BOOST_CHECK_EQUAL_COLLECTIONS(source2, source2 + 5, source2Cmp, source2Cmp + 5);
}

// ==================== uniqueCount ==========================
// Test 1: Test correct count of unique elements in arbitrary array
BOOST_AUTO_TEST_CASE(uniqueCount_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count = uniqueCount(source, 11);

	BOOST_CHECK_EQUAL(count, 3);
}

// Test 2: Test correct count of zero in array with no unique elements
BOOST_AUTO_TEST_CASE(uniqueCount_test2)
{
	// The kernel expects sorted arrays only
	int source[14] = {1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};

	int count = uniqueCount(source, 14);

	BOOST_CHECK_EQUAL(count, 0);
}

// Test 3: Test correct count of unique elements when all elements are unique
BOOST_AUTO_TEST_CASE(uniqueCount_test3)
{
	// The kernel expects sorted arrays only
	int source[6] = {1, 2, 3, 4, 7, 325};

	int count = uniqueCount(source, 6);

	BOOST_CHECK_EQUAL(count, 6);
}

// Test 4: Test correct count when only first element is unique
BOOST_AUTO_TEST_CASE(uniqueCount_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};

	int count = uniqueCount(source, 11);

	BOOST_CHECK_EQUAL(count, 1);
}

// Test 5: Test correct count when only last element is unique
BOOST_AUTO_TEST_CASE(uniqueCount_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325};

	int count = uniqueCount(source, 11);

	BOOST_CHECK_EQUAL(count, 1);
}

// Test 6: Test correct count when last two elements are unique
BOOST_AUTO_TEST_CASE(uniqueCount_test6)
{
	// The kernel expects sorted arrays only
	int source[10] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 325};

	int count = uniqueCount(source, 10);

	BOOST_CHECK_EQUAL(count, 2);
}

// ================= uniqueArray ===========================

// Test 1: Find the unique elements for an arbitrary array
BOOST_AUTO_TEST_CASE(uniqueArray_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count = uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 3);

	int result[3];
	int result_cmp[3] = {2, 7, 325};

	uniqueArray(source, result, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, result_cmp, result_cmp + 3);
}

// Test 2: Check that the function runs to completion without error when there are
// no unique elements
BOOST_AUTO_TEST_CASE(uniqueArray_test2)
{
	// The kernel expects sorted arrays only
	int source[14] = {1, 1, 1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};
	int count = uniqueCount(source, 14);
	BOOST_CHECK_EQUAL(count, 0);

	int result[0];
	// Theoretically, it should never copy. If it does, it will go out of bounds in memory.
	uniqueArray(source, result, 14);
}

// Test 3: Check that the result is correct when the entire source is unique elements
BOOST_AUTO_TEST_CASE(uniqueArray_test3)
{
	// The kernel expects sorted arrays only
	int source[6] = {1, 2, 3, 4, 7, 325};
	int count = uniqueCount(source, 6);
	BOOST_CHECK_EQUAL(count, 6);

	int result[6];
	int result_cmp[6] = {1, 2, 3, 4, 7, 325};
	uniqueArray(source, result, 6);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 6, result_cmp, result_cmp + 6);
}

// Test 4: Check that the result is correct when only the first element is unique
BOOST_AUTO_TEST_CASE(uniqueArray_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 3, 3, 4, 4, 7, 7, 325, 325};
	int count = uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 1);

	int result[1];
	int result_cmp[1] = {1};
	uniqueArray(source, result, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 1, result_cmp, result_cmp + 1);
}

// Test 5: Check that the result is correct when only the last element is unique
BOOST_AUTO_TEST_CASE(uniqueArray_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 7, 325};
	int count = uniqueCount(source, 11);
	BOOST_CHECK_EQUAL(count, 1);

	int result[1];
	int result_cmp[1] = {325};
	uniqueArray(source, result, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 1, result_cmp, result_cmp + 1);
}

// Test 6: Check that the result is correct when the last two elements are unique
BOOST_AUTO_TEST_CASE(uniqueArray_test6)
{
	// The kernel expects sorted arrays only
	int source[10] = {1, 1, 2, 2, 3, 3, 4, 4, 7, 325};
	int count = uniqueCount(source, 10);
	BOOST_CHECK_EQUAL(count, 2);

	int result[2];
	int result_cmp[2] = {7, 325};
	uniqueArray(source, result, 10);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 2, result_cmp, result_cmp + 2);
}

// ==================== distinctCount ==========================

// Test 1: Find the correct number of distinct elements
BOOST_AUTO_TEST_CASE(distinctCount_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 6);
}

// Test 2: Find correct number of distinct elements with no unique elements
BOOST_AUTO_TEST_CASE(distinctCount_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 4);
}

// Test 3: Find correct number of distinct elements with all unique elements
BOOST_AUTO_TEST_CASE(distinctCount_test3)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 11);
}

// Test 4: Find correct number of distinct elements when only first is unique
BOOST_AUTO_TEST_CASE(distinctCount_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 2);
}

// Test 5: Find correct number of distinct elements when only last is distinct
BOOST_AUTO_TEST_CASE(distinctCount_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 2);
}

// Test 6: Find correct number of distinct elements when first two are unique
BOOST_AUTO_TEST_CASE(distinctCount_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 3);
}

// Test 7: Find correct number of distinct elements when last two are unique
BOOST_AUTO_TEST_CASE(distinctCount_int_lastTwoDistinct)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 3};
	int count = distinctCount(source, 11);

	BOOST_CHECK_EQUAL(count, 3);
}

// ==================== DistinctArray ==========================

// Test 1: Find Array of Distinct Elements
BOOST_AUTO_TEST_CASE(distinctArray_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int * dest = (int *) malloc(sizeof(int) * 6);
	int resultCmp[6] = {1,2,3,4,7,325};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 6, resultCmp, resultCmp + 6);
}

// Test 2: Find Array of Distinct Elements when no unique elements
BOOST_AUTO_TEST_CASE(distinctArray_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
	int * dest = (int *) malloc(sizeof(int) * 4);
	int resultCmp[4] = {1,2,3,4};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 4, resultCmp, resultCmp + 4);

}

// Test 3: Find Array of Distinct Elements when all are unique
BOOST_AUTO_TEST_CASE(distinctArray_test3)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int * dest = (int *) malloc(sizeof(int) * 11);
	int resultCmp[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 11, resultCmp, resultCmp + 11);
}

// Test 4: Find array of distinct elements when only first is unique
BOOST_AUTO_TEST_CASE(distinctArray_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int resultCmp[2] = {1, 2};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
}

// Test 5: Find array of distinct elements when only last is unique
BOOST_AUTO_TEST_CASE(distinctArray_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int resultCmp[2] = {1, 2};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
}

// Test 6: Find array of distinct elements when only first two are unique
BOOST_AUTO_TEST_CASE(distinctArray_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int resultCmp[6] = {1, 2, 3};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
}

// Test 7: Find array of distinct elements when only last two are unique
BOOST_AUTO_TEST_CASE(distinctArray_test7)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 7};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int resultCmp[3] = {1, 2, 7};

	distinctArray(source, dest, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
}

// ==================== DistinctWithCount ======================

// Test 1: Find Array of Distinct Elements with correct number of instances of each element
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test1)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 2, 3, 3, 4, 4, 7, 325};
	int * dest = (int *) malloc(sizeof(int) * 6);
	int * count = (int *) malloc(sizeof(int) * 6);

	int resultCmp[6] = {1,2,3,4,7,325};
	int countCmp[6] = {4,1,2,2,1,1};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 6, resultCmp, resultCmp + 6);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 6, countCmp, countCmp + 6);
}

// Test 2: Find Array of Distinct Elements with correct number of instances of each element with no unique elements
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test2)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 2, 2, 2, 3, 3, 4, 4, 4, 4};
	int * dest = (int *) malloc(sizeof(int) * 4);
	int * count = (int *) malloc(sizeof(int) * 4);

	int resultCmp[4] = {1,2,3,4};
	int countCmp[4] = {2, 3, 2, 4};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 4, resultCmp, resultCmp + 4);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 4, countCmp, countCmp + 4);
}

// Test 3: Find Array of Distinct Elements with correct number of instances of each element with all unique elements
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test3)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int * dest = (int *) malloc(sizeof(int) * 11);
	int * count = (int *) malloc(sizeof(int) * 11);

	int resultCmp[11] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15};
	int countCmp[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 11, resultCmp, resultCmp + 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 11, countCmp, countCmp + 11);
}

// Test 4: Find Array of Distinct Elements with correct number of instances of each element when only first is unique
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test4)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int * count = (int *) malloc(sizeof(int) * 2);

	int resultCmp[2] = {1, 2};
	int countCmp[2] = {1, 10};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 2, countCmp, countCmp + 2);
}

// Test 5: Find Array of Distinct Elements with correct number of instances of each element when only last is unique
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test5)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	int * dest = (int *) malloc(sizeof(int) * 2);
	int * count = (int *) malloc(sizeof(int) * 2);

	int resultCmp[2] = {1, 2};
	int countCmp[2] = {10, 1};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 2, resultCmp, resultCmp + 2);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 2, countCmp, countCmp + 2);
}

// Test 6: Find Array of Distinct Elements with correct number of instances of each element when only first two are unique
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test6)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int * count = (int *) malloc(sizeof(int) * 3);

	int resultCmp[6] = {1, 2, 3};
	int countCmp[3] = {1, 1, 9};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 3, countCmp, countCmp + 3);
}

// Test 7: Find Array of Distinct Elements with correct number of instances of each element when only last tow are unique
BOOST_AUTO_TEST_CASE(distinctArrayWithCount_test7)
{
	// The kernel expects sorted arrays only
	int source[11] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 7};
	int * dest = (int *) malloc(sizeof(int) * 3);
	int * count = (int *) malloc(sizeof(int) * 3);

	int resultCmp[3] = {1, 2, 7};
	int countCmp[3] = {9, 1, 1};

	distinctArray(source, dest, count, 11);
	BOOST_CHECK_EQUAL_COLLECTIONS(dest, dest + 3, resultCmp, resultCmp + 3);
	BOOST_CHECK_EQUAL_COLLECTIONS(count, count + 3, countCmp, countCmp + 3);
}

//====================== Minus Count ===========================

// Test 1: Get correct number of elements left in minus array when first array is larger
BOOST_AUTO_TEST_CASE(minusCount_test1)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};

	int count = minusCount(source1, 7, source2, 4);

	BOOST_CHECK_EQUAL(count, 4);
}

// Test 2: Get correct number of elements left in minus array when second array is larger
BOOST_AUTO_TEST_CASE(minusCount_test2)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};

	int count = minusCount(source1, 7, source2, 8);

	BOOST_CHECK_EQUAL(count, 4);
}

// Test 3: Get correct number of elements left in minus array when first array is all same element
BOOST_AUTO_TEST_CASE(minusCount_test3)
{
	int source1[7] = {1, 1, 1, 1, 1, 1, 1};
	int source2[4] = {2, 8, 21, 22};

	int count = minusCount(source1, 7, source2, 4);

	BOOST_CHECK_EQUAL(count, 7);
}

// Test 4: Get correct number of elements left in minus array when arrays are the same
BOOST_AUTO_TEST_CASE(minusCount_test4)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};

	int count = minusCount(source1, 4, source2, 4);

	BOOST_CHECK_EQUAL(count, 0);
}

//====================== Minus Array ===========================

// Test 1: Get correct set minus array when first array is larger
BOOST_AUTO_TEST_CASE(minusArray_test1)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};
	int result[4];
	int resultCmp[4] = {1, 4, 10, 12};
	cupcfd::error::eCodes status;

	status = minusArray(source1, 7, source2, 4, result, 4);

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, resultCmp, resultCmp + 4);
}

// Test 2: Get correct set minus array when second array is larger
BOOST_AUTO_TEST_CASE(minusArray_test2)
{
	int source1[7] = {1, 2, 2, 4, 8, 10, 12};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};
	int result[4];
	int resultCmp[4] = {1, 4, 10, 12};
	cupcfd::error::eCodes status;

	status = minusArray(source1, 7, source2, 8, result, 4);

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, resultCmp, resultCmp + 4);
}

// Test 3: Get correct set minus array when first array is all same element
BOOST_AUTO_TEST_CASE(minusArray_test3)
{
	int source1[7] = {1, 1, 1, 1, 1, 1, 1};
	int source2[4] = {2, 8, 21, 22};
	int result[7];
	int resultCmp[7] = {1, 1, 1, 1, 1, 1, 1};
	cupcfd::error::eCodes status;

	status = minusArray(source1, 7, source2, 4, result, 7);

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 7, resultCmp, resultCmp + 7);
}

// Test 4: Get correct set minus array when arrays have same contents
BOOST_AUTO_TEST_CASE(minusArray_test4)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};
	int result[0];
	int resultCmp[0];
	cupcfd::error::eCodes status;

	status = minusArray(source1, 4, source2, 4, result, 0);

	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 0, resultCmp, resultCmp + 0);
}

//====================== Intersect Count ===========================

// Test 1: Get correct set intersect count when first array is larger and
// first + last elements to be discarded
BOOST_AUTO_TEST_CASE(intersectCount_test1)
{
	int source1[6] = {1, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};

	int count = intersectCount(source1, 6, source2, 4);

	// Duplicates should not count, so only 2 and 8 are in both.
	BOOST_CHECK_EQUAL(count, 2);
}

// Test 2: Get correct set intersect count when first array is larger and
// first to be discarded
BOOST_AUTO_TEST_CASE(intersectCount_test2)
{
	int source1[6] = {1, 2, 4, 8, 10, 22};
	int source2[4] = {2, 8, 21, 22};

	int count = intersectCount(source1, 6, source2, 4);

	// Duplicates should not count, so only 2 and 8 are in both.
	BOOST_CHECK_EQUAL(count, 3);
}

// Test 3: Get correct set intersect count when second array is larger
BOOST_AUTO_TEST_CASE(intersectCount_test3)
{
	int source1[6] = {1, 2, 4, 8, 10, 200};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};

	int count = intersectCount(source1, 7, source2, 8);

	// Duplicates should not count, so only 2, 8 and 200 are in both.
	BOOST_CHECK_EQUAL(count, 3);
}

// Test 4: Get correct set intersect count when no elements are in both arrays
BOOST_AUTO_TEST_CASE(intersectCount_test4)
{
	int source1[1] = {1};
	int source2[4] = {2, 8, 21, 22};

	int count = intersectCount(source1, 1, source2, 4);

	BOOST_CHECK_EQUAL(count, 0);
}

// Test 5: Get correct set intersect count when all elements are in both arrays
BOOST_AUTO_TEST_CASE(intersectCount_test5)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};

	int count = intersectCount(source1, 4, source2, 4);

	// Arrays are same, so all elements are in intersect
	BOOST_CHECK_EQUAL(count, 4);
}

//====================== Intersect Array ===========================

// Test 1: Get correct set intersect array when first array is larger and
// first + last elements to be discarded
BOOST_AUTO_TEST_CASE(intersectArray_test1)
{
	int source1[6] = {1, 2, 4, 8, 10, 12};
	int source2[4] = {2, 8, 21, 22};

	int result[2];
	int nResult = 2;
	int cmp[2] = {2, 8};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 6, source2, 4, result, nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 2, cmp, cmp + 2);
}

// Test 2: Get correct set intersect array when first array is larger and
// first to be discarded
BOOST_AUTO_TEST_CASE(intersectArray_test2)
{
	int source1[6] = {1, 2, 4, 8, 10, 22};
	int source2[4] = {2, 8, 21, 22};

	int result[3];
	int nResult = 3;
	int cmp[3] = {2, 8, 22};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 6, source2, 4, result, nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, cmp, cmp + 3);
}

// Test 3: Get correct set intersect array when second array is larger
BOOST_AUTO_TEST_CASE(intersectArray_test3)
{
	int source1[6] = {1, 2, 4, 8, 10, 200};
	int source2[8] = {2, 8, 21, 22, 100, 102, 200, 400};

	int result[3];
	int nResult = 3;
	int cmp[3] = {2, 8, 200};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 7, source2, 8, result, nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 3, cmp, cmp + 3);
}

// Test 4: Get correct set intersect array when no elements are in both arrays
BOOST_AUTO_TEST_CASE(intersectArray_test4)
{
	int source1[1] = {1};
	int source2[4] = {2, 8, 21, 22};

	int result[0];
	int nResult = 0;
	int cmp[0];

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 1, source2, 4, result, nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 0, cmp, cmp + 0);
}

// Test 5: Get correct set intersect array when all elements are in both arrays
BOOST_AUTO_TEST_CASE(intersectArray_test5)
{
	int source1[4] = {2, 8, 21, 22};
	int source2[4] = {2, 8, 21, 22};

	int result[4];
	int nResult = 4;
	int cmp[4] = {2, 8, 21, 22};

	cupcfd::error::eCodes status;
	status = intersectArray(source1, 4, source2, 4, result, nResult);
	BOOST_CHECK_EQUAL(status, cupcfd::error::E_SUCCESS);
	BOOST_CHECK_EQUAL_COLLECTIONS(result, result + 4, cmp, cmp + 4);
}

// === randomUniform ===
// ToDo: Is it possible to generate a suitable test for this?


/*    Copyright (c) 2010-2015, Delft University of Technology
 *    All rights reserved.
 *
 *    Redistribution and use in source and binary forms, with or without modification, are
 *    permitted provided that the following conditions are met:
 *      - Redistributions of source code must retain the above copyright notice, this list of
 *        conditions and the following disclaimer.
 *      - Redistributions in binary form must reproduce the above copyright notice, this list of
 *        conditions and the following disclaimer in the documentation and/or other materials
 *        provided with the distribution.
 *      - Neither the name of the Delft University of Technology nor the names of its contributors
 *        may be used to endorse or promote products derived from this software without specific
 *        prior written permission.
 *
 *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
 *    OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 *    MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *    COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *    GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 *    AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 *    OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *    Changelog
 *      YYMMDD    Author            Comment
 *      120720    A. Ronse          First creation of the unit test.
 *      120724    K. Kumar          Addition of extensive comments and tests for
 *                                  updateAndGetAcceleration functions.
 *      120821    K. Kumar          Rewrote tests to make use of updated DerivedAccelerationModel
 *                                  class, AnotherDerivedAccelerationModel class, and new TestBody
 *                                  class.
 *      121123    S. Billemont      Changed boost::assign usage to push_back(), to ensure
 *                                  compatibility with MSVC.
 *
 *    References
 *
 *    Notes:
 *      Test tolerance was set at 5.0e-15 (or 5.0e-7 for floats) instead of epsilon due to
 *      rounding errors in Eigen types with entries over a number of orders of magnitude,
 *      presumably causing the observed larger than epsilon relative differences between
 *      expected and computed values.
 *
 */

#define BOOST_TEST_MAIN

#include <vector>

#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>

#include <Eigen/Core>

#include "Tudat/Basics/testMacros.h"

#include "Tudat/Astrodynamics/BasicAstrodynamics/accelerationModel.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/UnitTests/testAccelerationModels.h"
#include "Tudat/Astrodynamics/BasicAstrodynamics/UnitTests/testBody.h"
#include "Tudat/Mathematics/BasicMathematics/linearAlgebraTypes.h"

namespace tudat
{
namespace unit_tests
{

using basic_astrodynamics::AccelerationModel;
using basic_astrodynamics::updateAndGetAcceleration;
using boost::assign::list_of;

BOOST_AUTO_TEST_SUITE( test_accelerationModel )

//! Test whether DerivedAccelerationModel (acceleration data type=Eigen::Vector3d) functions
//! correctly.
BOOST_AUTO_TEST_CASE( test_derived3dAccelerationModel )
{
    using basic_astrodynamics::AccelerationModel3dPointer;

    // Shortcuts.
    typedef TestBody< 3, double > TestBody3d;
    typedef boost::shared_ptr< TestBody3d > TestBody3dPointer;
    typedef DerivedAccelerationModel< > DerivedAccelerationModel3d;

    // Create body with initial state and time.
    TestBody3dPointer body = boost::make_shared< TestBody3d >(
                ( basic_mathematics::Vector6d( ) << 1.1, 2.2, 3.3, -0.1, 0.2, 0.3 ).finished( ), 2.0 );

    // Create acceleration model using DerivedAccelerationModel class, and pass pointers to
    // functions in body.
    AccelerationModel3dPointer accelerationModel3d
            = boost::make_shared< DerivedAccelerationModel3d >(
                boost::bind( &TestBody3d::getCurrentPosition, body ),
                boost::bind( &TestBody3d::getCurrentTime, body ) );

    // Declare container of computed accelerations.
    std::vector< Eigen::Vector3d > computedAccelerations( 3 );

    // Get acceleration vector before members are updated.
    computedAccelerations.at( 0 ) = accelerationModel3d->getAcceleration( );

    // Update time and state.
    body->setCurrentTimeAndState(
                -1.1, ( basic_mathematics::Vector6d( )
                        << -0.45, 10.63, -9.81, 0.11, 0.22, 0.33 ).finished( ) );

    // Update acceleration model members.
    accelerationModel3d->updateMembers( );

    // Get acceleration vector, now after members have been updated.
    computedAccelerations.at( 1 ) = accelerationModel3d->getAcceleration( );

    // Update time and state.
    body->setCurrentTimeAndState(
                4.6, ( basic_mathematics::Vector6d( )
                       << -87.685, 101.44, -1.38, -0.12, 0.23, -0.34 ).finished( ) );

    // Update and get acceleration with single function.
    computedAccelerations.at( 2 ) = updateAndGetAcceleration( accelerationModel3d );

    // Set expected accelerations.
    std::vector< Eigen::Vector3d > expectedAccelerations;
    expectedAccelerations.push_back( Eigen::Vector3d(   1.1,     2.2,   3.3  ) / (  2.0 *  2.0 ) );
    expectedAccelerations.push_back( Eigen::Vector3d(  -0.45,   10.63, -9.81 ) / ( -1.1 * -1.1 ) );
    expectedAccelerations.push_back( Eigen::Vector3d( -87.685, 101.44, -1.38 ) / (  4.6 *  4.6 ) );

    // Check that the acceleration vectors before and after the update match expected values.
    for ( unsigned int i = 0; i < computedAccelerations.size( ); i++ )
    {
        TUDAT_CHECK_MATRIX_BASE( computedAccelerations.at( i ), expectedAccelerations.at( i ) )
                BOOST_CHECK_CLOSE_FRACTION( computedAccelerations.at( i ).coeff( row, col ),
                                   expectedAccelerations.at( i ).coeff( row, col ), 5.0e-15 );
    }
}

//! Test whether AnotherDerivedAccelerationModel (acceleration data type=Eigen::Vector2f) functions
//! correctly.
BOOST_AUTO_TEST_CASE( test_derived2fAccelerationModel )
{
    // NOTE: For this test, it is imperative that the float values are given with the "f"
    // suffix, to ensure that their precision is indeed of float-type.

    // Shortcuts.
    typedef TestBody< 2, float > TestBody2f;
    typedef boost::shared_ptr< TestBody2f > TestBody2fPointer;
    typedef AccelerationModel< Eigen::Vector2f > AccelerationModel2f;
    typedef boost::shared_ptr< AccelerationModel2f > AccelerationModel2fPointer;
    typedef AnotherDerivedAccelerationModel< Eigen::Vector2f, Eigen::Vector2f,
            Eigen::Vector2f, float > AnotherDerivedAccelerationModel2f;

    // Create body with initial state and time.
    TestBody2fPointer body = boost::make_shared< TestBody2f >(
                ( Eigen::VectorXf( 4 ) << -0.3f, 4.5f, 0.1f, 0.2f ).finished( ), -2.3f );

    // Create acceleration model using AnotherDerivedAccelerationModel class, and pass pointers to
    // functions in body.
    AccelerationModel2fPointer accelerationModel2f
            = boost::make_shared< AnotherDerivedAccelerationModel2f >(
                boost::bind( &TestBody2f::getCurrentPosition, body ),
                boost::bind( &TestBody2f::getCurrentVelocity, body ),
                boost::bind( &TestBody2f::getCurrentTime, body ) );

    // Declare container of computed accelerations.
    std::vector< Eigen::Vector2f > computedAccelerations( 3 );

    // Get acceleration vector before members are updated.
    computedAccelerations.at( 0 ) = accelerationModel2f->getAcceleration( );

    // Update time and state.
    body->setCurrentTimeAndState(
                0.33f, ( Eigen::VectorXf( 4 ) << 1.34f, 2.65f, -0.23f, 0.1f ).finished( ) );

    // Update acceleration model members.
    accelerationModel2f->updateMembers( );

    // Get acceleration vector, now after members have been updated.
    computedAccelerations.at( 1 ) = accelerationModel2f->getAcceleration( );

    // Update time and state.
    body->setCurrentTimeAndState(
                -10.3f, ( Eigen::VectorXf( 4 ) << -98.99f, 1.53f, 1.23f, -0.11f ).finished( ) );

    // Update and get acceleration with single function.
    computedAccelerations.at( 2 ) = updateAndGetAcceleration( accelerationModel2f );

    // Set expected accelerations.
    std::vector< Eigen::Vector2f > expectedAccelerations;
    expectedAccelerations.push_back( 0.5 * Eigen::Vector2f( -0.3f, 4.5f )
                                     / ( 3.2 * ( -2.3f + 3.4 ) * -2.3f )
                                     + Eigen::Vector2f( 0.1f, 0.2f ) / -2.3f );
    expectedAccelerations.push_back( 0.5 * Eigen::Vector2f( 1.34f, 2.65f )
                                     / ( 3.2 * ( 0.33f + 3.4 ) * 0.33f )
                                     + Eigen::Vector2f( -0.23f, 0.1f ) / 0.33f );
    expectedAccelerations.push_back( 0.5 * Eigen::Vector2f( -98.99f, 1.53f )
                                     / ( 3.2 * ( -10.3f + 3.4 ) * -10.3f )
                                     + Eigen::Vector2f( 1.23f, -0.11f ) / -10.3f );

    // Check that the acceleration vectors before and after the update match expected values.
    for ( unsigned int i = 0; i < computedAccelerations.size( ); i++ )
    {
        TUDAT_CHECK_MATRIX_BASE( computedAccelerations.at( i ), expectedAccelerations.at( i ) )
                BOOST_CHECK_CLOSE_FRACTION( computedAccelerations.at( i ).coeff( row, col ),
                                   expectedAccelerations.at( i ).coeff( row, col ), 5.0e-7 );
    }
}

//! Test whether DerivedAccelerationModel (acceleration data type=Eigen::Vector1i) functions
//! correctly.
BOOST_AUTO_TEST_CASE( test_derived1iAccelerationModel )
{
    // Shortcuts.
    typedef TestBody< 1, int > TestBody1i;
    typedef boost::shared_ptr< TestBody1i > TestBody1iPointer;
    typedef AccelerationModel< int > AccelerationModel1i;
    typedef boost::shared_ptr< AccelerationModel1i > AccelerationModel1iPointer;
    typedef DerivedAccelerationModel< int, int, int > DerivedAccelerationModel1i;

    // Create body with initial state and time.
    TestBody1iPointer body = boost::make_shared< TestBody1i >( Eigen::Vector2i( 20, 1 ), 2 );

    // Create acceleration model using DerivedAccelerationModel class, and pass pointers to
    // functions in body.
    AccelerationModel1iPointer accelerationModeli1
            = boost::make_shared< DerivedAccelerationModel1i >(
                boost::bind( ( &TestBody1i::getCurrentPosition ), body ),
                boost::bind( ( &TestBody1i::getCurrentTime ), body ) );

    // Declare container of computed accelerations.
    std::vector< int > computedAccelerations( 3 );

    // Get scalar acceleration before members are updated.
    computedAccelerations.at( 0 ) = accelerationModeli1->getAcceleration( );

    // Update time and state.
    body->setCurrentTimeAndState( -3, Eigen::Vector2i( 90, 3 ) );

    // Update acceleration model members.
    accelerationModeli1->updateMembers( );

    // Get scalar acceleration, now after members have been updated.
    computedAccelerations.at( 1 ) = accelerationModeli1->getAcceleration( );

    // Update time and state.
    body->setCurrentTimeAndState( 4, Eigen::Vector2i( 16, -4 ) );

    // Update and get scalar acceleration with single function.
    computedAccelerations.at( 2 ) = updateAndGetAcceleration( accelerationModeli1 );

    // Set expected accelerations.
    const std::vector< int > expectedAccelerations
            = list_of( 20 / ( 2 * 2 ) )( 90 / ( -3 * -3 ) )( 16 / ( 4 * 4 ) );

    // Check that the acceleration vectors before and after the update match expected values.
    for ( unsigned int i = 0; i < computedAccelerations.size( ); i++ )
    {
        BOOST_CHECK_EQUAL( computedAccelerations.at( i ), expectedAccelerations.at( i ) );
    }
}

BOOST_AUTO_TEST_SUITE_END( )

} // namespace unit_tests
} // namespace tudat


//
// Copyright (c) 2002--2010
// Toon Knapen, Karl Meerbergen, Kresimir Fresl,
// Thomas Klimpel and Rutger ter Borg
//
// 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)
//
// THIS FILE IS AUTOMATICALLY GENERATED
// PLEASE DO NOT EDIT!
//

#ifndef BOOST_NUMERIC_BINDINGS_LAPACK_COMPUTATIONAL_TPTRI_HPP
#define BOOST_NUMERIC_BINDINGS_LAPACK_COMPUTATIONAL_TPTRI_HPP

#include <boost/assert.hpp>
#include <boost/numeric/bindings/begin.hpp>
#include <boost/numeric/bindings/diag_tag.hpp>
#include <boost/numeric/bindings/is_mutable.hpp>
#include <boost/numeric/bindings/remove_imaginary.hpp>
#include <boost/numeric/bindings/size.hpp>
#include <boost/numeric/bindings/stride.hpp>
#include <boost/numeric/bindings/uplo_tag.hpp>
#include <boost/numeric/bindings/value_type.hpp>
#include <boost/static_assert.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/type_traits/remove_const.hpp>

//
// The LAPACK-backend for tptri is the netlib-compatible backend.
//
#include <boost/numeric/bindings/lapack/detail/lapack.h>
#include <boost/numeric/bindings/lapack/detail/lapack_option.hpp>

namespace boost {
namespace numeric {
namespace bindings {
namespace lapack {

//
// The detail namespace contains value-type-overloaded functions that
// dispatch to the appropriate back-end LAPACK-routine.
//
namespace detail {

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * float value-type.
//
template< typename UpLo, typename Diag >
inline std::ptrdiff_t tptri( const UpLo, const Diag, const fortran_int_t n,
        float* ap ) {
    fortran_int_t info(0);
    LAPACK_STPTRI( &lapack_option< UpLo >::value, &lapack_option<
            Diag >::value, &n, ap, &info );
    return info;
}

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * double value-type.
//
template< typename UpLo, typename Diag >
inline std::ptrdiff_t tptri( const UpLo, const Diag, const fortran_int_t n,
        double* ap ) {
    fortran_int_t info(0);
    LAPACK_DTPTRI( &lapack_option< UpLo >::value, &lapack_option<
            Diag >::value, &n, ap, &info );
    return info;
}

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * complex<float> value-type.
//
template< typename UpLo, typename Diag >
inline std::ptrdiff_t tptri( const UpLo, const Diag, const fortran_int_t n,
        std::complex<float>* ap ) {
    fortran_int_t info(0);
    LAPACK_CTPTRI( &lapack_option< UpLo >::value, &lapack_option<
            Diag >::value, &n, ap, &info );
    return info;
}

//
// Overloaded function for dispatching to
// * netlib-compatible LAPACK backend (the default), and
// * complex<double> value-type.
//
template< typename UpLo, typename Diag >
inline std::ptrdiff_t tptri( const UpLo, const Diag, const fortran_int_t n,
        std::complex<double>* ap ) {
    fortran_int_t info(0);
    LAPACK_ZTPTRI( &lapack_option< UpLo >::value, &lapack_option<
            Diag >::value, &n, ap, &info );
    return info;
}

} // namespace detail

//
// Value-type based template class. Use this class if you need a type
// for dispatching to tptri.
//
template< typename Value >
struct tptri_impl {

    typedef Value value_type;
    typedef typename remove_imaginary< Value >::type real_type;

    //
    // Static member function, that
    // * Deduces the required arguments for dispatching to LAPACK, and
    // * Asserts that most arguments make sense.
    //
    template< typename MatrixAP >
    static std::ptrdiff_t invoke( MatrixAP& ap ) {
        namespace bindings = ::boost::numeric::bindings;
        typedef typename result_of::uplo_tag< MatrixAP >::type uplo;
        typedef typename result_of::diag_tag< MatrixAP >::type diag;
        BOOST_STATIC_ASSERT( (bindings::is_mutable< MatrixAP >::value) );
        BOOST_ASSERT( bindings::size_column(ap) >= 0 );
        return detail::tptri( uplo(), diag(), bindings::size_column(ap),
                bindings::begin_value(ap) );
    }

};

//
// Functions for direct use. These functions are overloaded for temporaries,
// so that wrapped types can still be passed and used for write-access. In
// addition, if applicable, they are overloaded for user-defined workspaces.
// Calls to these functions are passed to the tptri_impl classes. In the
// documentation, most overloads are collapsed to avoid a large number of
// prototypes which are very similar.
//

//
// Overloaded function for tptri. Its overload differs for
//
template< typename MatrixAP >
inline std::ptrdiff_t tptri( MatrixAP& ap ) {
    return tptri_impl< typename bindings::value_type<
            MatrixAP >::type >::invoke( ap );
}

} // namespace lapack
} // namespace bindings
} // namespace numeric
} // namespace boost

#endif