/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */

/**
 * \file libs/numeric/ublasx/test/tril.cpp
 *
 * \brief Test suite for the lower-triangular view operation.
 *
 * \author Marco Guazzone (marco.guazzone@gmail.com)
 *
 * <hr/>
 *
 * Copyright (c) 2011, Marco Guazzone
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompwhiching file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 */

#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/triangular.hpp>
#include <boost/numeric/ublasx/detail/debug.hpp>
#include <boost/numeric/ublasx/operation/tril.hpp>
#include <complex>
#include <cstddef>
#include "libs/numeric/ublasx/test/utils.hpp"

namespace ublas = ::boost::numeric::ublas;
namespace ublasx = ::boost::numeric::ublasx;

static const double tol = 1e-5;

BOOST_UBLASX_TEST_DEF( real_square_matrix_row_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Square - Row Major - k == 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(n,n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
}

BOOST_UBLASX_TEST_DEF( real_square_matrix_col_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Square - Column Major - k == 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(n,n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
}

BOOST_UBLASX_TEST_DEF( real_square_matrix_row_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Square - Row Major - k > 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(n,n,value_type(1));

    for (::std::ptrdiff_t k = n-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < (n-k-1); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_square_matrix_col_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Square - Column Major - k > 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(n,n,value_type(1));

    for (::std::ptrdiff_t k = n-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < (n-k-1); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_square_matrix_row_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Square - Row Major - k < 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(n, n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < n; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < n; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_square_matrix_col_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Square - Column Major - k < 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(n, n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < n; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < n; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_horizontal_matrix_row_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Horizontal - Row Major - k == 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( real_horizontal_matrix_col_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Horizontal - Column Major - k == 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( real_horizontal_matrix_row_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Horizontal - Row Major - k > 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_horizontal_matrix_col_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Horizontal - Column Major - k > 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_horizontal_matrix_row_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Horizontal - Row Major - k < 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < nr; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_horizontal_matrix_col_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Horizontal - Column Major - k < 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < nr; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_vertical_matrix_row_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Vertical - Row Major - k == 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( real_vertical_matrix_col_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Vertical - Column Major - k == 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( real_vertical_matrix_row_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Vertical - Row Major - k > 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_vertical_matrix_col_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Vertical - Column Major - k > 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_vertical_matrix_row_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Vertical - Row Major - k < 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < ::std::min(nr,nc+k-1); ++i)
        {
            E(i,i+1-k) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( real_vertical_matrix_col_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Real - Vertical - Column Major - k < 0" );

    typedef double value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < ::std::min(nr,nc+k-1); ++i)
        {
            E(i,i+1-k) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_square_matrix_row_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Square - Row Major - k == 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(n,n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
}

BOOST_UBLASX_TEST_DEF( complex_square_matrix_col_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Square - Column Major - k == 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(n,n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
}

BOOST_UBLASX_TEST_DEF( complex_square_matrix_row_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Square - Row Major - k > 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(n,n,value_type(1));

    for (::std::ptrdiff_t k = n-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < (n-k-1); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_square_matrix_col_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Square - Column Major - k > 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(n,n,value_type(1));

    for (::std::ptrdiff_t k = n-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < (n-k-1); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        ////BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_square_matrix_row_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Square - Row Major - k < 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(n, n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < n; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < n; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_square_matrix_col_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Square - Column Major - k < 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t n(4);

    matrix_type A(n,n, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(n, n);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < n; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < n; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        ////BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, n, n, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_horizontal_matrix_row_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Horizontal - Row Major - k == 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( complex_horizontal_matrix_col_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Horizontal - Column Major - k == 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( complex_horizontal_matrix_row_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Horizontal - Row Major - k > 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_horizontal_matrix_col_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Horizontal - Column Major - k > 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_horizontal_matrix_row_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Horizontal - Row Major - k < 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < nr; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        ////BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_horizontal_matrix_col_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Horizontal - Column Major - k < 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(4);
    const ::std::size_t nc(6);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < nr; ++i)
        {
            E(i,static_cast< ::std::ptrdiff_t >(i+1-k)) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_vertical_matrix_row_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Vertical - Row Major - k == 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( complex_vertical_matrix_col_major_keq0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Vertical - Column Major - k == 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr,nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    BOOST_UBLASX_DEBUG_TRACE( "Input Matrix A=" << A );
    BOOST_UBLASX_DEBUG_TRACE( "tril(A)=" << X );
    BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
}

BOOST_UBLASX_TEST_DEF( complex_vertical_matrix_row_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Vertical - Row Major - k > 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    X = ublasx::tril(A);
    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_vertical_matrix_col_major_kgt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Vertical - Column Major - k > 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::scalar_matrix<value_type,ublas::lower>(nr,nc,value_type(1));

    for (::std::ptrdiff_t k = nc-1; k >= 0; --k)
    {
        X = ublasx::tril(A, k);

        for (::std::size_t i = 0; i < ::std::min(nr,static_cast< ::std::size_t >(::std::max(static_cast<int>(nc-k-1),0))); ++i)
        {
            E(i,i+k+1) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << k << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_vertical_matrix_row_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Vertical - Row Major - k < 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::row_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < ::std::min(nr,nc+k-1); ++i)
        {
            E(i,i+1-k) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

BOOST_UBLASX_TEST_DEF( complex_vertical_matrix_col_major_klt0 )
{
    BOOST_UBLASX_DEBUG_TRACE( "Test Case: Complex - Vertical - Column Major - k < 0" );

    typedef double real_type;
    typedef ::std::complex<real_type> value_type;
    typedef ublas::matrix<value_type,ublas::column_major> matrix_type;

    const ::std::size_t nr(6);
    const ::std::size_t nc(4);

    matrix_type A(nr,nc, value_type(1));

    matrix_type X;
    matrix_type E;

    E = ublasx::triangular_matrix<value_type,ublas::lower>(nr, nc);
    E(0,0) =
    E(1,0) = E(1,1) =
    E(2,0) = E(2,1) = E(2,2) =
    E(3,0) = E(3,1) = E(3,2) = E(3,3) =
    E(4,0) = E(4,1) = E(4,2) = E(4,3) =
    E(5,0) = E(5,1) = E(5,2) = E(5,3) = value_type(1);

    for (::std::size_t k = 0; k < nr; ++k)
    {
        X = ublasx::tril(A, -static_cast< ::std::ptrdiff_t >(k));

        for (::std::size_t i = k-1; k > 0 && i < ::std::min(nr,nc+k-1); ++i)
        {
            E(i,i+1-k) = value_type(0);
        }

        //BOOST_UBLASX_DEBUG_TRACE( "E=" << E );
        BOOST_UBLASX_DEBUG_TRACE( "tril(A," << -static_cast< ::std::ptrdiff_t >(k) << ")=" << X );
        BOOST_UBLASX_TEST_CHECK_MATRIX_CLOSE( X, E, nr, nc, tol );
    }
}

int main()
{
    BOOST_UBLASX_TEST_BEGIN();

    BOOST_UBLASX_TEST_DO( real_square_matrix_row_major_keq0 );
    BOOST_UBLASX_TEST_DO( real_square_matrix_col_major_keq0 );
    BOOST_UBLASX_TEST_DO( real_square_matrix_row_major_kgt0 );
    BOOST_UBLASX_TEST_DO( real_square_matrix_col_major_kgt0 );
    BOOST_UBLASX_TEST_DO( real_square_matrix_row_major_klt0 );
    BOOST_UBLASX_TEST_DO( real_square_matrix_col_major_klt0 );

    BOOST_UBLASX_TEST_DO( real_horizontal_matrix_row_major_keq0 );
    BOOST_UBLASX_TEST_DO( real_horizontal_matrix_col_major_keq0 );
    BOOST_UBLASX_TEST_DO( real_horizontal_matrix_row_major_kgt0 );
    BOOST_UBLASX_TEST_DO( real_horizontal_matrix_col_major_kgt0 );
    BOOST_UBLASX_TEST_DO( real_horizontal_matrix_row_major_klt0 );
    BOOST_UBLASX_TEST_DO( real_horizontal_matrix_col_major_klt0 );

    BOOST_UBLASX_TEST_DO( real_vertical_matrix_row_major_keq0 );
    BOOST_UBLASX_TEST_DO( real_vertical_matrix_col_major_keq0 );
    BOOST_UBLASX_TEST_DO( real_vertical_matrix_row_major_kgt0 );
    BOOST_UBLASX_TEST_DO( real_vertical_matrix_col_major_kgt0 );
    BOOST_UBLASX_TEST_DO( real_vertical_matrix_row_major_klt0 );
    BOOST_UBLASX_TEST_DO( real_vertical_matrix_col_major_klt0 );

    BOOST_UBLASX_TEST_DO( complex_square_matrix_col_major_keq0 );
    BOOST_UBLASX_TEST_DO( complex_square_matrix_row_major_keq0 );
    BOOST_UBLASX_TEST_DO( complex_square_matrix_col_major_kgt0 );
    BOOST_UBLASX_TEST_DO( complex_square_matrix_row_major_kgt0 );
    BOOST_UBLASX_TEST_DO( complex_square_matrix_row_major_klt0 );
    BOOST_UBLASX_TEST_DO( complex_square_matrix_col_major_klt0 );

    BOOST_UBLASX_TEST_DO( complex_horizontal_matrix_row_major_keq0 );
    BOOST_UBLASX_TEST_DO( complex_horizontal_matrix_col_major_keq0 );
    BOOST_UBLASX_TEST_DO( complex_horizontal_matrix_row_major_kgt0 );
    BOOST_UBLASX_TEST_DO( complex_horizontal_matrix_col_major_kgt0 );
    BOOST_UBLASX_TEST_DO( complex_horizontal_matrix_row_major_klt0 );
    BOOST_UBLASX_TEST_DO( complex_horizontal_matrix_col_major_klt0 );

    BOOST_UBLASX_TEST_DO( complex_vertical_matrix_row_major_keq0 );
    BOOST_UBLASX_TEST_DO( complex_vertical_matrix_col_major_keq0 );
    BOOST_UBLASX_TEST_DO( complex_vertical_matrix_row_major_kgt0 );
    BOOST_UBLASX_TEST_DO( complex_vertical_matrix_col_major_kgt0 );
    BOOST_UBLASX_TEST_DO( complex_vertical_matrix_row_major_klt0 );
    BOOST_UBLASX_TEST_DO( complex_vertical_matrix_col_major_klt0 );

    BOOST_UBLASX_TEST_END();
}


/* ----------------------------------------------------------------------------

 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)

 * See LICENSE for the license information

 * -------------------------------------------------------------------------- */

/**
 * @file    testRegularImplicitSchurFactor.cpp
 * @brief   unit test implicit jacobian factors
 * @author  Frank Dellaert
 * @date    Oct 20, 2013
 */

#include <gtsam/slam/JacobianFactorQ.h>
#include <gtsam/slam/JacobianFactorQR.h>
#include <gtsam/slam/RegularImplicitSchurFactor.h>
#include <gtsam/geometry/CalibratedCamera.h>
#include <gtsam/geometry/Point2.h>

#include <gtsam/linear/VectorValues.h>
#include <gtsam/linear/NoiseModel.h>
#include <gtsam/linear/GaussianFactor.h>
#include <gtsam/base/timing.h>

#include <boost/assign/list_of.hpp>
#include <boost/assign/std/vector.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/adaptor/map.hpp>
#include <CppUnitLite/TestHarness.h>

using namespace std;
using namespace boost::assign;
using namespace gtsam;

// F
const Matrix26 F0 = Matrix26::Ones();
const Matrix26 F1 = 2 * Matrix26::Ones();
const Matrix26 F3 = 3 * Matrix26::Ones();
const vector<Matrix26, Eigen::aligned_allocator<Matrix26> > FBlocks = list_of<Matrix26>(F0)(F1)(F3);
const KeyVector keys {0, 1, 3};
// RHS and sigmas
const Vector b = (Vector(6) << 1., 2., 3., 4., 5., 6.).finished();

//*************************************************************************************
TEST( regularImplicitSchurFactor, creation ) {
  // Matrix E = Matrix::Ones(6,3);
  Matrix E = Matrix::Zero(6, 3);
  E.block<2,2>(0, 0) = I_2x2;
  E.block<2,3>(2, 0) = 2 * Matrix::Ones(2, 3);
  Matrix3 P = (E.transpose() * E).inverse();
  RegularImplicitSchurFactor<CalibratedCamera> expected(keys, FBlocks, E, P, b);
  Matrix expectedP = expected.getPointCovariance();
  EXPECT(assert_equal(expectedP, P));
}

/* ************************************************************************* */
TEST( regularImplicitSchurFactor, addHessianMultiply ) {

  Matrix E = Matrix::Zero(6, 3);
  E.block<2,2>(0, 0) = I_2x2;
  E.block<2,3>(2, 0) = 2 * Matrix::Ones(2, 3);
  E.block<2,2>(4, 1) = I_2x2;
  Matrix3 P = (E.transpose() * E).inverse();

  double alpha = 0.5;
  VectorValues xvalues = map_list_of //
  (0, Vector::Constant(6, 2))//
  (1, Vector::Constant(6, 4))//
  (2, Vector::Constant(6, 0))// distractor
  (3, Vector::Constant(6, 8));

  VectorValues yExpected = map_list_of//
  (0, Vector::Constant(6, 27))//
  (1, Vector::Constant(6, -40))//
  (2, Vector::Constant(6, 0))// distractor
  (3, Vector::Constant(6, 279));

  // Create full F
  size_t M=4, m = 3, d = 6;
  Matrix F(2 * m, d * M);
  F << F0, Matrix::Zero(2, d * 3), Matrix::Zero(2, d), F1, Matrix::Zero(2, d*2), Matrix::Zero(2, d * 3), F3;

  // Calculate expected result F'*alpha*(I - E*P*E')*F*x
  KeyVector keys2{0,1,2,3};
  Vector x = xvalues.vector(keys2);
  Vector expected = Vector::Zero(24);
  RegularImplicitSchurFactor<CalibratedCamera>::multiplyHessianAdd(F, E, P, alpha, x, expected);
  EXPECT(assert_equal(expected, yExpected.vector(keys2), 1e-8));

  // Create ImplicitSchurFactor
  RegularImplicitSchurFactor<CalibratedCamera> implicitFactor(keys, FBlocks, E, P, b);

  VectorValues zero = 0 * yExpected;// quick way to get zero w right structure
  { // First Version
    VectorValues yActual = zero;
    implicitFactor.multiplyHessianAdd(alpha, xvalues, yActual);
    EXPECT(assert_equal(yExpected, yActual, 1e-8));
    implicitFactor.multiplyHessianAdd(alpha, xvalues, yActual);
    EXPECT(assert_equal(2 * yExpected, yActual, 1e-8));
    implicitFactor.multiplyHessianAdd(-1, xvalues, yActual);
    EXPECT(assert_equal(zero, yActual, 1e-8));
  }

  typedef Eigen::Matrix<double, 24, 1> DeltaX;
  typedef Eigen::Map<DeltaX> XMap;
  double* y = new double[24];
  double* xdata = x.data();

  { // Raw memory Version
    std::fill(y, y + 24, 0);// zero y !
    implicitFactor.multiplyHessianAdd(alpha, xdata, y);
    EXPECT(assert_equal(expected, XMap(y), 1e-8));
    implicitFactor.multiplyHessianAdd(alpha, xdata, y);
    EXPECT(assert_equal(Vector(2 * expected), XMap(y), 1e-8));
    implicitFactor.multiplyHessianAdd(-1, xdata, y);
    EXPECT(assert_equal(Vector(0 * expected), XMap(y), 1e-8));
  }

  // Create JacobianFactor with same error
  const SharedDiagonal model;
  JacobianFactorQ<6, 2> jfQ(keys, FBlocks, E, P, b, model);

  // error
  double expectedError = 11875.083333333334;
  {
    EXPECT_DOUBLES_EQUAL(expectedError,jfQ.error(xvalues),1e-7)
    EXPECT_DOUBLES_EQUAL(expectedError,implicitFactor.errorJF(xvalues),1e-7)
    EXPECT_DOUBLES_EQUAL(11903.500000000007,implicitFactor.error(xvalues),1e-7)
  }

  {
    VectorValues yActual = zero;
    jfQ.multiplyHessianAdd(alpha, xvalues, yActual);
    EXPECT(assert_equal(yExpected, yActual, 1e-8));
    jfQ.multiplyHessianAdd(alpha, xvalues, yActual);
    EXPECT(assert_equal(2 * yExpected, yActual, 1e-8));
    jfQ.multiplyHessianAdd(-1, xvalues, yActual);
    EXPECT(assert_equal(zero, yActual, 1e-8));
  }

  { // check hessian Diagonal
    VectorValues diagExpected = jfQ.hessianDiagonal();
    VectorValues diagActual = implicitFactor.hessianDiagonal();
    EXPECT(assert_equal(diagExpected, diagActual, 1e-8));
  }

  { // check hessian Block Diagonal
    map<Key,Matrix> BD = jfQ.hessianBlockDiagonal();
    map<Key,Matrix> actualBD = implicitFactor.hessianBlockDiagonal();
    LONGS_EQUAL(3,actualBD.size());
    EXPECT(assert_equal(BD[0],actualBD[0]));
    EXPECT(assert_equal(BD[1],actualBD[1]));
    EXPECT(assert_equal(BD[3],actualBD[3]));
  }

  { // Raw memory Version
    std::fill(y, y + 24, 0);// zero y !
    jfQ.multiplyHessianAdd(alpha, xdata, y);
    EXPECT(assert_equal(expected, XMap(y), 1e-8));
    jfQ.multiplyHessianAdd(alpha, xdata, y);
    EXPECT(assert_equal(Vector(2 * expected), XMap(y), 1e-8));
    jfQ.multiplyHessianAdd(-1, xdata, y);
    EXPECT(assert_equal(Vector(0 * expected), XMap(y), 1e-8));
  }

  VectorValues expectedVV;
  expectedVV.insert(0,-3.5*Vector::Ones(6));
  expectedVV.insert(1,10*Vector::Ones(6)/3);
  expectedVV.insert(3,-19.5*Vector::Ones(6));
  { // Check gradientAtZero
    VectorValues actual = implicitFactor.gradientAtZero();
    EXPECT(assert_equal(expectedVV, jfQ.gradientAtZero(), 1e-8));
    EXPECT(assert_equal(expectedVV, implicitFactor.gradientAtZero(), 1e-8));
  }

  // Create JacobianFactorQR
  JacobianFactorQR<6, 2> jfQR(keys, FBlocks, E, P, b, model);
    EXPECT_DOUBLES_EQUAL(expectedError, jfQR.error(xvalues),1e-7)
  EXPECT(assert_equal(expectedVV,  jfQR.gradientAtZero(), 1e-8));
  {
    const SharedDiagonal model;
    VectorValues yActual = zero;
    jfQR.multiplyHessianAdd(alpha, xvalues, yActual);
    EXPECT(assert_equal(yExpected, yActual, 1e-8));
    jfQR.multiplyHessianAdd(alpha, xvalues, yActual);
    EXPECT(assert_equal(2 * yExpected, yActual, 1e-8));
    jfQR.multiplyHessianAdd(-1, xvalues, yActual);
    EXPECT(assert_equal(zero, yActual, 1e-8));
  }

  { // Raw memory Version
    std::fill(y, y + 24, 0);// zero y !
    jfQR.multiplyHessianAdd(alpha, xdata, y);
    EXPECT(assert_equal(expected, XMap(y), 1e-8));
    jfQR.multiplyHessianAdd(alpha, xdata, y);
    EXPECT(assert_equal(Vector(2 * expected), XMap(y), 1e-8));
    jfQR.multiplyHessianAdd(-1, xdata, y);
    EXPECT(assert_equal(Vector(0 * expected), XMap(y), 1e-8));
  }
  delete [] y;
}

/* ************************************************************************* */
TEST(regularImplicitSchurFactor, hessianDiagonal)
{
  /* TESTED AGAINST MATLAB
   *  F = [Vector::Ones(2,6) zeros(2,6) zeros(2,6)
        zeros(2,6) 2*Vector::Ones(2,6) zeros(2,6)
        zeros(2,6) zeros(2,6) 3*Vector::Ones(2,6)]
      E = [[1:6] [1:6] [0.5 1:5]];
      E = reshape(E',3,6)'
      P = inv(E' * E)
      H = F' * (eye(6) - E * P * E') * F
      diag(H)
   */
  Matrix E(6,3);
  E.block<2,3>(0, 0) << 1,2,3,4,5,6;
  E.block<2,3>(2, 0) << 1,2,3,4,5,6;
  E.block<2,3>(4, 0) << 0.5,1,2,3,4,5;
  Matrix3 P = (E.transpose() * E).inverse();
  RegularImplicitSchurFactor<CalibratedCamera> factor(keys, FBlocks, E, P, b);

  // hessianDiagonal
  VectorValues expected;
  expected.insert(0, 1.195652*Vector::Ones(6));
  expected.insert(1, 4.782608*Vector::Ones(6));
  expected.insert(3, 7.043478*Vector::Ones(6));
  EXPECT(assert_equal(expected, factor.hessianDiagonal(),1e-5));

  // hessianBlockDiagonal
  map<Key,Matrix> actualBD = factor.hessianBlockDiagonal();
  LONGS_EQUAL(3,actualBD.size());
  Matrix FtE0 = F0.transpose() * E.block<2,3>(0, 0);
  Matrix FtE1 = F1.transpose() * E.block<2,3>(2, 0);
  Matrix FtE3 = F3.transpose() * E.block<2,3>(4, 0);

  // variant one
  EXPECT(assert_equal(F0.transpose()*F0-FtE0*P*FtE0.transpose(),actualBD[0]));
  EXPECT(assert_equal(F1.transpose()*F1-FtE1*P*FtE1.transpose(),actualBD[1]));
  EXPECT(assert_equal(F3.transpose()*F3-FtE3*P*FtE3.transpose(),actualBD[3]));

  // variant two
  Matrix I2 = I_2x2;
  Matrix E0 = E.block<2,3>(0, 0);
  Matrix F0t = F0.transpose();
  EXPECT(assert_equal(F0t*F0-F0t*E0*P*E0.transpose()*F0,actualBD[0]));
  EXPECT(assert_equal(F0t*(F0-E0*P*E0.transpose()*F0),actualBD[0]));

  Matrix M1 = F0t*(F0-E0*P*E0.transpose()*F0);
  Matrix M2 = F0t*F0-F0t*E0*P*E0.transpose()*F0;

  EXPECT(assert_equal(  M1 , actualBD[0] ));
  EXPECT(assert_equal(  M1 , M2 ));

  Matrix M1b = F0t*(E0*P*E0.transpose()*F0);
  Matrix M2b = F0t*E0*P*E0.transpose()*F0;
  EXPECT(assert_equal(  M1b , M2b ));

  EXPECT(assert_equal(F0t*(I2-E0*P*E0.transpose())*F0,actualBD[0]));
  EXPECT(assert_equal(F1.transpose()*F1-FtE1*P*FtE1.transpose(),actualBD[1]));
  EXPECT(assert_equal(F3.transpose()*F3-FtE3*P*FtE3.transpose(),actualBD[3]));

  // augmentedInformation (test just checks diagonals)
  Matrix actualInfo = factor.augmentedInformation();
  EXPECT(assert_equal(actualBD[0],actualInfo.block<6,6>(0,0)));
  EXPECT(assert_equal(actualBD[1],actualInfo.block<6,6>(6,6)));
  EXPECT(assert_equal(actualBD[3],actualInfo.block<6,6>(12,12)));

  // information (test just checks diagonals)
  Matrix actualInfo2 = factor.information();
  EXPECT(assert_equal(actualBD[0],actualInfo2.block<6,6>(0,0)));
  EXPECT(assert_equal(actualBD[1],actualInfo2.block<6,6>(6,6)));
  EXPECT(assert_equal(actualBD[3],actualInfo2.block<6,6>(12,12)));
}

/* ************************************************************************* */
int main(void) {
  TestResult tr;
  int result = TestRegistry::runAllTests(tr);
  return result;
}
//*************************************************************************************


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_ROUND2EVEN_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_ROUND2EVEN_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/round2even.hpp>
#include <boost/simd/include/functions/scalar/abs.hpp>
#include <boost/simd/include/constants/twotonmb.hpp>
#include <boost/simd/include/constants/zero.hpp>
#include <boost/simd/sdk/config/enforce_precision.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( round2even_, tag::cpu_
                                    , (A0)
                                    , (scalar_< floating_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      boost::simd::config::enforce_precision<A0> enforcer;

      const result_type v = boost::simd::abs(a0);
      const result_type t2n = boost::simd::Twotonmb<result_type>();
      result_type d0 = (v+t2n);
      result_type d = (d0-t2n);
      d = (v < t2n)?d:v;
      return a0 < Zero<A0>() ? -d : d;
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( round2even_, tag::cpu_
                                    , (A0)
                                    , (scalar_< integer_<A0> >)
                                    )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL(1)
    {
      return a0;
    }
  };
} } }

#endif


/*
 * From:
 * http://stackoverflow.com/questions/6142576/sample-from-multivariate-normal-gaussian-distribution-in-c
 */

#include <iostream>
#include <random>
#include <Eigen/Dense>
#include <omp.h>
#include <chrono>

#include "mvnormal.h"

using namespace std;
using namespace Eigen;
using namespace std::chrono;

std::mt19937 **bmrngs;

/*
  We need a functor that can pretend it's const,
  but to be a good random number generator
  it needs mutable state.
*/

#ifdef __INTEL_COMPILER
std::random_device srd;
#pragma omp threadprivate(srd)
#else
// use thread_local for gcc
thread_local static std::random_device srd;
#endif

#ifndef __clang__
thread_local
#endif
static std::mt19937 rng(srd());

#ifndef __clang__
thread_local
#endif
static std::normal_distribution<> nd;

double randn(double) {
  return nd(rng);
}

double randn0() {
  return nd(rng);
}

auto
nrandn(int n) -> decltype( VectorXd::NullaryExpr(n, std::cref(randn)) )
{
    return VectorXd::NullaryExpr(n, std::cref(randn));
}

void init_bmrng(int seed) {
   int nthreads = -1;
#pragma omp parallel
   {
#pragma omp single
      {
         nthreads = omp_get_num_threads();
      }
   }
   bmrngs = new std::mt19937*[nthreads];
   for (int i = 0; i < nthreads; i++) {
      bmrngs[i] = new std::mt19937(seed + i * 1999);
   }
}

void init_bmrng() {
   auto ms = (duration_cast< milliseconds >(
             system_clock::now().time_since_epoch()
         )).count();
   init_bmrng(ms);
}

void bmrandn(double* x, long n) {
#pragma omp parallel
  {
    std::uniform_real_distribution<double> unif(-1.0, 1.0);
    std::mt19937* bmrng = bmrngs[omp_get_thread_num()];
#pragma omp for schedule(static)
    for (long i = 0; i < n; i += 2) {
      double x1, x2, w;
      do {
        x1 = unif(*bmrng);
        x2 = unif(*bmrng);
        w = x1 * x1 + x2 * x2;
      } while ( w >= 1.0 );

      w = sqrt( (-2.0 * log( w ) ) / w );
      x[i] = x1 * w;
      if (i + 1 < n) {
        x[i+1] = x2 * w;
      }
    }
  }
}

double rand_unif() {
  std::uniform_real_distribution<double> unif(0.0, 1.0);
	std::mt19937* bmrng = bmrngs[omp_get_thread_num()];
	return unif(*bmrng);
}

double rand_unif(double low, double high) {
  std::uniform_real_distribution<double> unif(low, high);
	std::mt19937* bmrng = bmrngs[omp_get_thread_num()];
	return unif(*bmrng);
}

void bmrandn(MatrixXd & X) {
  long n = X.rows() * (long)X.cols();
  bmrandn(X.data(), n);
}

// to be called within OpenMP parallel loop (also from serial code is fine)
void bmrandn_single(double* x, long n) {
  std::uniform_real_distribution<double> unif(-1.0, 1.0);
  std::mt19937* bmrng = bmrngs[omp_get_thread_num()];
  for (long i = 0; i < n; i += 2) {
    double x1, x2, w;
    do {
      x1 = unif(*bmrng);
      x2 = unif(*bmrng);
      w = x1 * x1 + x2 * x2;
    } while ( w >= 1.0 );

    w = sqrt( (-2.0 * log( w ) ) / w );
    x[i] = x1 * w;
    if (i + 1 < n) {
      x[i+1] = x2 * w;
    }
  }
}

double bmrandn_single() {
  //TODO: add bmrng as input
  std::uniform_real_distribution<double> unif(-1.0, 1.0);
  std::mt19937* bmrng = bmrngs[omp_get_thread_num()];

    double x1, x2, w;
    do {
      x1 = unif(*bmrng);
      x2 = unif(*bmrng);
      w = x1 * x1 + x2 * x2;
    } while ( w >= 1.0 );

    w = sqrt( (-2.0 * log( w ) ) / w );
    return x1 * w;
}

void bmrandn_single(Eigen::VectorXd & x) {
  bmrandn_single(x.data(), x.size());
}

/** returns random number according to Gamma distribution
 *  with the given shape (k) and scale (theta). See wiki. */
double rgamma(double shape, double scale) {
  std::gamma_distribution<double> gamma(shape, scale);
  return gamma(*bmrngs[0]);
}

/** Normal(0, Lambda^-1) for nn columns */
MatrixXd MvNormal_prec(const MatrixXd & Lambda, int nn = 1)
{
  int size = Lambda.rows(); // Dimensionality (rows)

  LLT<MatrixXd> chol(Lambda);

  MatrixXd r = MatrixXd::NullaryExpr(size, nn, std::cref(randn));
	chol.matrixU().solveInPlace(r);
  return r;
}

MatrixXd MvNormal_prec(const MatrixXd & Lambda, const VectorXd & mean, int nn = 1)
{
  MatrixXd r = MvNormal_prec(Lambda, nn);
  return r.colwise() + mean;
}

MatrixXd MvNormal_prec_omp(const MatrixXd & Lambda, int nn = 1)
{
  int size = Lambda.rows(); // Dimensionality (rows)

  LLT<MatrixXd> chol(Lambda);

  MatrixXd r(size, nn);
  bmrandn(r);
  // TODO: check if solveInPlace is parallelized:
	chol.matrixU().solveInPlace(r);
  return r;
}

/*
  Draw nn samples from a size-dimensional normal distribution
  with a specified mean and covariance
*/
MatrixXd MvNormal(const MatrixXd covar, const VectorXd mean, int nn = 1)
{
  int size = mean.rows(); // Dimensionality (rows)
  MatrixXd normTransform(size,size);

  LLT<MatrixXd> cholSolver(covar);
  normTransform = cholSolver.matrixL();

  auto normSamples = MatrixXd::NullaryExpr(size, nn, std::cref(randn));
  MatrixXd samples = (normTransform * normSamples).colwise() + mean;

  return samples;
}

MatrixXd WishartUnit(int m, int df)
{
    MatrixXd c(m,m);
    c.setZero();

    for ( int i = 0; i < m; i++ ) {
        std::gamma_distribution<> gam(0.5*(df - i));
        c(i,i) = sqrt(2.0 * gam(rng));
        VectorXd r = nrandn(m-i-1);
        c.block(i,i+1,1,m-i-1) = r.transpose();
    }

    MatrixXd ret = c.transpose() * c;

#ifdef TEST_MVNORMAL
    cout << "WISHART UNIT {\n" << endl;
    cout << "  m:\n" << m << endl;
    cout << "  df:\n" << df << endl;
    cout << "  ret;\n" << ret << endl;
    cout << "  c:\n" << c << endl;
    cout << "}\n" << ret << endl;
#endif

    return ret;
}

MatrixXd Wishart(const MatrixXd &sigma, const int df)
{
//  Get R, the upper triangular Cholesky factor of SIGMA.
  auto chol = sigma.llt();

//  Get AU, a sample from the unit Wishart distribution.
  MatrixXd au = WishartUnit(sigma.cols(), df);

//  Construct the matrix A = R' * AU * R.
  MatrixXd a = chol.matrixL() * au * chol.matrixU();

#ifdef TEST_MVNORMAL
    cout << "WISHART {\n" << endl;
    cout << "  sigma::\n" << sigma << endl;
    cout << "  r:\n" << r << endl;
    cout << "  au:\n" << au << endl;
    cout << "  df:\n" << df << endl;
    cout << "  a:\n" << a << endl;
    cout << "}\n" << endl;
#endif

  return a;
}

// from julia package Distributions: conjugates/normalwishart.jl
std::pair<VectorXd, MatrixXd> NormalWishart(const VectorXd & mu, double kappa, const MatrixXd & T, double nu)
{
  MatrixXd Lam = Wishart(T, nu);
  MatrixXd mu_o = MvNormal_prec(Lam * kappa, mu);

#ifdef TEST_MVNORMAL
    cout << "NORMAL WISHART {\n" << endl;
    cout << "  mu:\n" << mu << endl;
    cout << "  kappa:\n" << kappa << endl;
    cout << "  T:\n" << T << endl;
    cout << "  nu:\n" << nu << endl;
    cout << "  mu_o\n" << mu_o << endl;
    cout << "  Lam\n" << Lam << endl;
    cout << "}\n" << endl;
#endif

  return std::make_pair(mu_o , Lam);
}

std::pair<VectorXd, MatrixXd> OldCondNormalWishart(const MatrixXd &U, const VectorXd &mu, const double kappa, const MatrixXd &T, const int nu)
{
  int N = U.cols();

  auto Um = U.rowwise().mean();

  // http://stackoverflow.com/questions/15138634/eigen-is-there-an-inbuilt-way-to-calculate-sample-covariance
  MatrixXd C = U.colwise() - Um;
  MatrixXd S = (C * C.adjoint()) / double(N - 1);
  VectorXd mu_c = (kappa*mu + N*Um) / (kappa + N);
  double kappa_c = kappa + N;
  MatrixXd T_c = ( T + N * S.transpose() + (kappa * N)/(kappa + N) * (mu - Um) * ((mu - Um).transpose())).inverse();
  int nu_c = nu + N;

#ifdef TEST_MVNORMAL
  cout << "mu_c:\n" << mu_c << endl;
  cout << "kappa_c:\n" << kappa_c << endl;
  cout << "T_c:\n" << T_c << endl;
  cout << "nu_c:\n" << nu_c << endl;
#endif

  return NormalWishart(mu_c, kappa_c, T_c, nu_c);
}

// from bpmf.jl -- verified
std::pair<VectorXd, MatrixXd> CondNormalWishart(const MatrixXd &U, const VectorXd &mu, const double kappa, const MatrixXd &T, const int nu)
{
  /// TODO: parallelize (for computing C and C * C')
  int N = U.cols();

	auto NU = U.rowwise().sum();
	auto NS = U * U.adjoint();

	int nu_c = nu + N;
	double kappa_c = kappa + N;
	auto mu_c = (kappa * mu + NU) / (kappa + N);
	auto X    = (T + NS + kappa * mu * mu.adjoint() - kappa_c * mu_c * mu_c.adjoint());
	Eigen::MatrixXd T_c = X.inverse();

#ifdef TEST_MVNORMAL
  cout << "mu_c:\n" << mu_c << endl;
  cout << "kappa_c:\n" << kappa_c << endl;
  cout << "T_c:\n" << T_c << endl;
  cout << "nu_c:\n" << nu_c << endl;
#endif

  return NormalWishart(mu_c, kappa_c, T_c, nu_c);
}

#if defined(TEST_MVNORMAL) || defined (BENCH_MVNORMAL)

int main()
{

    MatrixXd U(32,32 * 1024);
    U.setOnes();

    VectorXd mu(32);
    mu.setZero();

    double kappa = 2;

    MatrixXd T(32,32);
    T.setIdentity(32,32);
    T.array() /= 4;

    int nu = 3;

    VectorXd mu_out;
    MatrixXd T_out;

#ifdef BENCH_MVNORMAL
    for(int i=0; i<300; ++i) {
        tie(mu_out, T_out) = CondNormalWishart(U, mu, kappa, T, nu);
        cout << i << "\r" << flush;
    }
    cout << endl << flush;

    for(int i=0; i<7; ++i) {
        cout << i << ": " << (int)(100.0 * acc[i] / acc[7])  << endl;
    }
    cout << "total: " << acc[7] << endl;

    for(int i=0; i<300; ++i) {
        tie(mu_out, T_out) = OldCondNormalWishart(U, mu, kappa, T, nu);
        cout << i << "\r" << flush;
    }
    cout << endl << flush;

    cout << "total: " << acc[8] << endl;

#else
#if 1
    cout << "COND NORMAL WISHART\n" << endl;

    tie(mu_out, T_out) = CondNormalWishart(U, mu, kappa, T, nu);

    cout << "mu_out:\n" << mu_out << endl;
    cout << "T_out:\n" << T_out << endl;

    cout << "\n-----\n\n";
#endif

#if 0
    cout << "NORMAL WISHART\n" << endl;

    tie(mu_out, T_out) = NormalWishart(mu, kappa, T, nu);
    cout << "mu_out:\n" << mu_out << endl;
    cout << "T_out:\n" << T_out << endl;

#endif

#if 0
    cout << "MVNORMAL\n" << endl;
    MatrixXd out = MvNormal(T, mu, 10);
    cout << "mu:\n" << mu << endl;
    cout << "T:\n" << T << endl;
    cout << "out:\n" << out << endl;
#endif
#endif
}

#endif


/*
 Copyright (C) 2017 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

/*! \file fxvolsmile.cpp
    \brief filling non-complete matrix
*/

#include "toplevelfixture.hpp"
#include <boost/test/unit_test.hpp>
#include <qle/math/fillemptymatrix.hpp>

using namespace QuantLib;
using namespace QuantExt;
using namespace boost::unit_test_framework;
using namespace std;

BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(fillIncompleteMatrixTest)

BOOST_AUTO_TEST_CASE(testBlankLineFill) {
    BOOST_TEST_MESSAGE("Testing filling matrices with blank lines");

    Real non_val = -1;

    // empty row matrix & empty col matrix
    Matrix empty_row, empty_col;
    empty_row = Matrix(5, 5, Real(22.5));
    empty_col = Matrix(5, 5, Real(22.5));

    for (int i = 0; i < 5; i++) {
        empty_row[2][i] = non_val;
        empty_col[i][2] = non_val;
    }

    // check fails + successes
    Matrix tmp_mat_r = empty_row;
    Matrix tmp_mat_c = empty_col;
    BOOST_CHECK_THROW(fillIncompleteMatrix(tmp_mat_r, true, non_val), QuantLib::Error);
    BOOST_CHECK_THROW(fillIncompleteMatrix(tmp_mat_c, false, non_val), QuantLib::Error);

    tmp_mat_r = empty_row;
    tmp_mat_c = empty_col;
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_mat_r, false, non_val));
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_mat_c, true, non_val));

    // check vals
    Real tol = 1.0E-8;
    for (int i = 0; i < 5; i++) {
        BOOST_CHECK_CLOSE(tmp_mat_r[2][i], Real(22.5), tol);
        BOOST_CHECK_CLOSE(tmp_mat_c[i][2], Real(22.5), tol);
    }
}

BOOST_AUTO_TEST_CASE(testInterpolateOnly) {
    BOOST_TEST_MESSAGE("Testing interpolation only");

    Real non_val = -1;
    Matrix incomplete_m;
    // incomplete matrix of the form:
    /*
    1   2   3   4   5
    2   3   4   5   6
    3   4   5   6   7
    4   5   6   7   8
    5   6   7   8   9
    */
    // But center block is non_values.

    incomplete_m = Matrix(5, 5, non_val);
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            if (i == 0 || j == 0 || i == 4 || j == 4) {
                incomplete_m[i][j] = j + i + 1;
            }
        }
    }

    // fill matrix
    Matrix to_fill_row = incomplete_m;
    Matrix to_fill_col = incomplete_m;
    fillIncompleteMatrix(to_fill_row, true, non_val);
    fillIncompleteMatrix(to_fill_col, false, non_val);

    // check results
    Real tol = 1.0E-8;
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            BOOST_CHECK_CLOSE(to_fill_row[i][j], j + i + 1, tol);
            BOOST_CHECK_CLOSE(to_fill_col[i][j], j + i + 1, tol);
        }
    }
}

BOOST_AUTO_TEST_CASE(testExtrapolateOnly) {
    BOOST_TEST_MESSAGE("Testing extrapolation of edges in filling the matrix");

    Real non_val = -1;
    Matrix missing_rows, missing_cols;
    vector<vector<Real> > test_cases; // for different test cases, different missing lines
    for (int i = 0; i < 4; i++) {
        vector<Real> tmp;
        for (int j = 0; j <= i; j++) {
            tmp.push_back(j);
        }
        test_cases.push_back(tmp);
    }

    // incomplete matricies of the form:
    /*
    '   2   3   4   5       '   '   '   '   '
    '   3   4   5   6       2   3   4   5   6
    '   4   5   6   7       3   4   5   6   7
    '   5   6   7   8       4   5   6   7   8
    '   6   7   8   9       5   6   7   8   9
    */
    // incomplete_rows: some rows at leading edge are missing.
    // incomplete_cols: some cols at leading edge are missing.

    // loop over cases with different missing rows/cols
    vector<vector<Real> >::iterator cs;
    for (cs = test_cases.begin(); cs != test_cases.end(); cs++) {

        // set up empty matrices
        missing_rows = Matrix(5, 5, non_val);
        missing_cols = Matrix(5, 5, non_val);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {

                // ignore empty lines
                bool set_row = find(cs->begin(), cs->end(), i) == cs->end();
                bool set_col = find(cs->begin(), cs->end(), j) == cs->end();
                if (set_row) {
                    missing_rows[i][j] = j + i + 1;
                }
                if (set_col) {
                    missing_cols[i][j] = j + i + 1;
                }
            }
        }

        // fill matrices
        Matrix to_fill_rows = missing_rows;
        Matrix to_fill_cols = missing_cols;
        fillIncompleteMatrix(to_fill_rows, false, non_val);
        fillIncompleteMatrix(to_fill_cols, true, non_val);

        // check results
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 5; j++) {
                int last_val = cs->size();
                Real expectedVal_row;
                Real expectedVal_col;
                if (j < last_val) {
                    expectedVal_col = missing_cols[i][last_val];
                } else {
                    expectedVal_col = missing_cols[i][j];
                }
                if (i < last_val) {
                    expectedVal_row = missing_rows[last_val][j];
                } else {
                    expectedVal_row = missing_rows[i][j];
                }
                bool check_row = to_fill_rows[i][j] == expectedVal_row;
                bool check_col = to_fill_cols[i][j] == expectedVal_col;

                if (!check_row || !check_col) {
                    BOOST_FAIL("FillIncomplete matrix failed on extrapolation only tests.");
                }
            }
        }
    }
}

BOOST_AUTO_TEST_CASE(testInterpExtrap) {
    BOOST_TEST_MESSAGE("Testing interpolation and extrapolation");

    Real non_val = -1;
    Matrix incomplete_m;
    // incomplete matrix of the form:
    /*
    1   '   '   '   '
    '   2   '   '   '
    '   '   3   '   '
    '   '   '   4   '
    '   '   '   '   5
    */

    incomplete_m = Matrix(5, 5, non_val);
    for (int i = 0; i < 5; i++) {
        incomplete_m[i][i] = i;
    }

    // fill matrix
    Matrix to_fill_rows = incomplete_m;
    Matrix to_fill_cols = incomplete_m;
    fillIncompleteMatrix(to_fill_rows, true, non_val);
    fillIncompleteMatrix(to_fill_cols, false, non_val);

    // check results
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            BOOST_CHECK_EQUAL(to_fill_rows[i][j], incomplete_m[i][i]);
            BOOST_CHECK_EQUAL(to_fill_cols[i][j], incomplete_m[j][j]);
        }
    }
}

BOOST_AUTO_TEST_CASE(testSingleEntry) {

    Matrix inc = Matrix(1, 1, 22.5);
    Matrix tmp1 = inc, tmp2 = inc, tmp3 = inc, tmp4 = inc;

    // non-blank value.
    BOOST_TEST_MESSAGE("Testing single non-blank entry");
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp1, true, -1));
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp2, false, -1));
    BOOST_CHECK_EQUAL(tmp1[0][0], inc[0][0]);
    BOOST_CHECK_EQUAL(tmp2[0][0], inc[0][0]);

    // blank value.
    BOOST_TEST_MESSAGE("Testing single blank entry");
    BOOST_CHECK_THROW(fillIncompleteMatrix(tmp3, true, 22.5), QuantLib::Error);
    BOOST_CHECK_THROW(fillIncompleteMatrix(tmp4, false, 22.5), QuantLib::Error);
}

BOOST_AUTO_TEST_CASE(testEmptyMatrix) {
    BOOST_TEST_MESSAGE("testing empty matrices");

    Matrix m;
    BOOST_CHECK_THROW(fillIncompleteMatrix(m, true, -1), QuantLib::Error);
    BOOST_CHECK_THROW(fillIncompleteMatrix(m, false, -1), QuantLib::Error);
}

BOOST_AUTO_TEST_CASE(testFullMatrix) {
    BOOST_TEST_MESSAGE("tesing full matrices");

    // set up matrices
    Matrix full_single = Matrix(1, 1, 22.5);
    Matrix full = Matrix(5, 5, 22.5);
    Matrix tmp_single_r = full_single;
    Matrix tmp_single_c = full_single;
    Matrix tmp_r = full;
    Matrix tmp_c = full;

    // "fill"
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_single_r, true, -1));
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_single_c, false, -1));
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_r, true, -1));
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_c, false, -1));

    // check results
    BOOST_CHECK_EQUAL(tmp_single_r[0][0], full_single[0][0]);
    BOOST_CHECK_EQUAL(tmp_single_c[0][0], full_single[0][0]);
    for (int i = 0; i < 5; i++) {
        for (int j = 0; j < 5; j++) {
            BOOST_CHECK_EQUAL(tmp_r[i][j], full[i][j]);
            BOOST_CHECK_EQUAL(tmp_c[i][j], full[i][j]);
        }
    }
}

BOOST_AUTO_TEST_CASE(testSingleRowCol) {
    Matrix single_row = Matrix(1, 5, 22.5);
    Matrix single_col = Matrix(5, 1, 22.5);
    single_row[0][3] = -1; // single blank entry
    single_col[3][0] = -1; // single blank entry
    Matrix tmp_row = single_row;
    Matrix tmp_col = single_col;

    BOOST_CHECK_THROW(fillIncompleteMatrix(tmp_row, false, -1), QuantLib::Error);
    BOOST_CHECK_THROW(fillIncompleteMatrix(tmp_col, true, -1), QuantLib::Error);

    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_row, true, -1));
    BOOST_CHECK_NO_THROW(fillIncompleteMatrix(tmp_col, false, -1));
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


#include "Bezier.h"

#include <boost/test/unit_test.hpp>

using namespace boost;
using namespace Eigen;

namespace
{
	template<typename T>
	bool InRange(T x, T min, T max)
	{
		return x >= min && x <= max;
	}
}

BOOST_AUTO_TEST_CASE(Bezier_Range)
{
	Bezier spline({0.93f, 0.55f}, {0.25f, 0.79f});
	float epsilon(0.001f);

	BOOST_CHECK_EQUAL(spline.Solve(0.0f, epsilon), 0.0f);
	BOOST_CHECK_EQUAL(spline.Solve(1.0f, epsilon), 1.0f);

	for (float x(0.0f); x <= 1.0f; x += 0.125f)
		BOOST_CHECK(InRange(spline.Solve(x, epsilon), 0.0f, 1.0f));
}

BOOST_AUTO_TEST_CASE(Bezier_Line)
{
	Bezier spline({0.1f, 0.1f}, {0.9f, 0.9f});
	float epsilon(0.001f);

	for (float x(0.0f); x <= 1.0f; x += 0.125f)
		BOOST_CHECK_CLOSE(x, spline.Solve(x, epsilon), 1.0f);
}


/*
*   Copyright (c) 2010-2016, MIT Probabilistic Computing Project
*
*   Licensed under the Apache License, Version 2.0 (the "License");
*   you may not use this file except in compliance with the License.
*   You may obtain a copy of the License at
*
*       http://www.apache.org/licenses/LICENSE-2.0
*
*   Unless required by applicable law or agreed to in writing, software
*   distributed under the License is distributed on an "AS IS" BASIS,
*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*   See the License for the specific language governing permissions and
*   limitations under the License.
*/

// Program to generate test values for the modified Bessel function of
// the first kind using boost.  Usage:
//
//      c++ -o bessel bessel.cpp
//      ./bessel

#include <boost/math/distributions/normal.hpp>
#include <boost/math/special_functions/bessel.hpp>
#include <cstdio>

int main(int argc, char **argv) {
    const size_t n = 100;
    const double lo = -709;
    const double hi = +709;
    const double w = (hi - lo)/n;
    unsigned nu;
    size_t i;

    (void)argc;
    (void)argv;

    for (nu = 0; nu < 2; nu++) {
        if (printf("static const double i%ue[][2] = {\n", nu) < 0) {
            perror("printf");
            return 1;
        }
        if (printf("    // Uniform [-709, +709] grid\n") < 0) {
            perror("printf");
            return 1;
        }
        for (i = 0; i < n; i++) {
            const double x = lo + i*w;
            const double y = boost::math::cyl_bessel_i(nu, x);
            if (printf("    { %.17e, %.17e },\n", x, y) < 0) {
                perror("printf");
                return 1;
            }
        }
        if (printf("    // N(0, 1e-4) grid\n") < 0) {
            perror("printf");
            return 1;
        }
        for (i = 0; i < n; i++) {
            boost::math::normal norm(0, 1e-4);
            const double p = static_cast<double>(i + 1)/(n + 2);
            const double x = boost::math::quantile(norm, p);
            const double y = boost::math::cyl_bessel_i(nu, x);
            if (printf("    { %.17e, %.17e },\n", x, y) < 0) {
                perror("printf");
                return 1;
            }
        }
        if (printf("    // N(15, 1e-4) grid\n") < 0) {
            perror("printf");
            return 1;
        }
        for (i = 0; i < n; i++) {
            boost::math::normal norm(15, 1e-4);
            const double p = static_cast<double>(i + 1)/(n + 2);
            const double x = boost::math::quantile(norm, p);
            const double y = boost::math::cyl_bessel_i(nu, x);
            if (printf("    { %.17e, %.17e },\n", x, y) < 0) {
                perror("printf");
                return 1;
            }
        }
        if (printf("};\n") < 0) {
            perror("printf");
            return 1;
        }
    }

    return 0;
}


#include <boost/integer/common_factor_ct.hpp>


//==================================================================================================
/**
  EVE - Expressive Vector Engine
  Copyright : EVE Contributors & Maintainers
  SPDX-License-Identifier: MIT
**/
//==================================================================================================
#include <eve/function/ellint_3.hpp>
#include <boost/math/special_functions/ellint_3.hpp>
#include <boost/math/special_functions/ellint_1.hpp>
#include <eve/wide.hpp>

TTS_CASE_TPL("Check eve::ellint_3 behavior", EVE_TYPE)
{
  auto boost_el3 = [](auto n,  auto phi,  auto k){return boost::math::ellint_3(k, n, phi);};

  using elt_t = eve::element_type_t<T>;
  TTS_ULP_EQUAL(eve::ellint_3(T(0.4), T(0), T(0.2)),       T(boost_el3(elt_t(0.4), elt_t(0), elt_t(0.2) )),      1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(1.0), T(0.25), T(0.5)),    T(boost_el3(elt_t(1),   elt_t(0.25), elt_t(0.5) )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0.0), T(0.25), T(0.5)),    T(boost_el3(elt_t(0),   elt_t(0.25), elt_t(0.5) )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0.3), T(0.25), T(0)  ),    T(boost_el3(elt_t(0.3), elt_t(0.25), elt_t(0)   )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(1.2), T(0.25), T(0.25)),   T(boost_el3(elt_t(1.2), elt_t(0.25), elt_t(0.25) )),  1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0),   T(1.5), T(1)),       T(boost_el3(elt_t(0), elt_t(1.5), elt_t(1) )),      1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0),   T(2), T(1)),       T(eve::inf(eve::as<T>())),      1.0);
}

TTS_CASE_TPL("Check eve::ellint_3 behavior", EVE_TYPE)
{
  auto boost_el3 = [](auto n, auto k){return boost::math::ellint_3(k, n);};

  using elt_t = eve::element_type_t<T>;
  TTS_ULP_EQUAL(eve::ellint_3(T(0.4), T(0.2)),    T(boost_el3(elt_t(0.4),  elt_t(0.2) )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0.9), T(0.5)),    T(boost_el3(elt_t(0.9),  elt_t(0.5) )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0.0), T(0.5)),    T(boost_el3(elt_t(0),    elt_t(0.5) )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0.3), T(0)  ),    T(boost_el3(elt_t(0.3),  elt_t(0)   )),   1.0);
  TTS_ULP_EQUAL(eve::ellint_3(T(0.8), T(0.25)),   T(boost_el3(elt_t(0.8),  elt_t(0.25) )),  1.0);
}