//
// Copyright 2019 Mateusz Loskot <mateusz at loskot dot net>
//
// 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
//
#define BOOST_TEST_MODULE gil/test/extension/numeric/matrix3x2
#include "unit_test.hpp"

#include <boost/gil/extension/numeric/affine.hpp>
#include <boost/gil/extension/numeric/resample.hpp>
#include <boost/gil/extension/numeric/sampler.hpp>
#include <boost/gil.hpp>

namespace gil = boost::gil;

namespace {
constexpr double HALF_PI = 1.57079632679489661923;
}

BOOST_AUTO_TEST_CASE(matrix3x2_default_constructor)
{
    gil::matrix3x2<int> m1;
    BOOST_TEST(m1.a == 1);
    BOOST_TEST(m1.b == 0);
    BOOST_TEST(m1.c == 0);
    BOOST_TEST(m1.d == 1);
    BOOST_TEST(m1.e == 0);
    BOOST_TEST(m1.f == 0);
}

BOOST_AUTO_TEST_CASE(matrix3x2_parameterized_constructor)
{
    gil::matrix3x2<int> m1(1, 2, 3, 4, 5, 6);
    BOOST_TEST(m1.a == 1);
    BOOST_TEST(m1.b == 2);
    BOOST_TEST(m1.c == 3);
    BOOST_TEST(m1.d == 4);
    BOOST_TEST(m1.e == 5);
    BOOST_TEST(m1.f == 6);
}

BOOST_AUTO_TEST_CASE(matrix3x2_copy_constructor)
{
    gil::matrix3x2<int> m1(1, 2, 3, 4, 5, 6);
    gil::matrix3x2<int> m2(m1);
    BOOST_TEST(m2.a == 1);
    BOOST_TEST(m2.b == 2);
    BOOST_TEST(m2.c == 3);
    BOOST_TEST(m2.d == 4);
    BOOST_TEST(m2.e == 5);
    BOOST_TEST(m2.f == 6);
}

BOOST_AUTO_TEST_CASE(matrix3x2_assignment_operator)
{
    gil::matrix3x2<int> m1(1, 2, 3, 4, 5, 6);
    gil::matrix3x2<int> m2;
    m2 = m1;
    BOOST_TEST(m2.a == 1);
    BOOST_TEST(m2.b == 2);
    BOOST_TEST(m2.c == 3);
    BOOST_TEST(m2.d == 4);
    BOOST_TEST(m2.e == 5);
    BOOST_TEST(m2.f == 6);
}

BOOST_AUTO_TEST_CASE(matrix3x2_multiplication_assignment)
{
    gil::matrix3x2<int> m1;
    gil::matrix3x2<int> m2;
    m2 *= m1;
    BOOST_TEST(m2.a == 1);
    BOOST_TEST(m2.b == 0);
    BOOST_TEST(m2.c == 0);
    BOOST_TEST(m2.d == 1);
    BOOST_TEST(m2.e == 0);
    BOOST_TEST(m2.f == 0);

    gil::matrix3x2<int> m3(0, 0, 0, 0, 0, 0);
    m2 *= m3;
    BOOST_TEST(m2.a == 0);
    BOOST_TEST(m2.b == 0);
    BOOST_TEST(m2.c == 0);
    BOOST_TEST(m2.d == 0);
    BOOST_TEST(m2.e == 0);
    BOOST_TEST(m2.f == 0);
}

BOOST_AUTO_TEST_CASE(matrix3x2_matrix3x2_multiplication)
{
    gil::matrix3x2<int> m1;
    gil::matrix3x2<int> m2(0, 0, 0, 0, 0, 0);
    gil::matrix3x2<int> m3;
    m3 = m1 * m2;
    BOOST_TEST(m3.a == 0);
    BOOST_TEST(m3.b == 0);
    BOOST_TEST(m3.c == 0);
    BOOST_TEST(m3.d == 0);
    BOOST_TEST(m3.e == 0);
    BOOST_TEST(m3.f == 0);
}

BOOST_AUTO_TEST_CASE(matrix3x2_vector_multiplication)
{
    gil::matrix3x2<int> m1;
    gil::point<int> v1{2, 4};

    gil::point<int> v2 = v1 * m1;
    BOOST_TEST(v2.x == 2);
    BOOST_TEST(v2.y == 4);

    gil::point<int> v3 = gil::transform(m1, v1);
    BOOST_TEST(v3.x == 2);
    BOOST_TEST(v3.y == 4);
}

BOOST_AUTO_TEST_CASE(matrix3x2_get_rotate)
{
    auto m1 = gil::matrix3x2<double>::get_rotate(HALF_PI);
    BOOST_TEST(m1.a == std::cos(HALF_PI), btt::tolerance(0.03));
    BOOST_TEST(m1.b == 1);
    BOOST_TEST(m1.c == -1);
    BOOST_TEST(m1.d == std::cos(HALF_PI), btt::tolerance(0.03));
    BOOST_TEST(m1.e == 0);
    BOOST_TEST(m1.f == 0);
}

BOOST_AUTO_TEST_CASE(matrix3x2_get_scale)
{
    gil::matrix3x2<int> m1;
    m1 = gil::matrix3x2<int>::get_scale(2);
    BOOST_TEST(m1.a == 2);
    BOOST_TEST(m1.b == 0);
    BOOST_TEST(m1.c == 0);
    BOOST_TEST(m1.d == 2);
    BOOST_TEST(m1.e == 0);
    BOOST_TEST(m1.f == 0);
    m1 = gil::matrix3x2<int>::get_scale(2, 4);
    BOOST_TEST(m1.a == 2);
    BOOST_TEST(m1.d == 4);
    m1 = gil::matrix3x2<int>::get_scale(gil::point<int>{4, 8});
    BOOST_TEST(m1.a == 4);
    BOOST_TEST(m1.d == 8);
}

BOOST_AUTO_TEST_CASE(matrix3x2_get_translate)
{
    gil::matrix3x2<int> m1;
    m1 = gil::matrix3x2<int>::get_translate(2, 4);
    BOOST_TEST(m1.a == 1);
    BOOST_TEST(m1.b == 0);
    BOOST_TEST(m1.c == 0);
    BOOST_TEST(m1.d == 1);
    BOOST_TEST(m1.e == 2);
    BOOST_TEST(m1.f == 4);
    m1 = gil::matrix3x2<int>::get_translate(gil::point<int>{4, 8});
    BOOST_TEST(m1.e == 4);
    BOOST_TEST(m1.f == 8);
}

BOOST_AUTO_TEST_CASE(matrix3x2_transform)
{
    gil::matrix3x2<int> m1;
    gil::point<int> v1{2, 4};
    gil::point<int> v2 = gil::transform(m1, v1);
    BOOST_TEST(v2.x == 2);
    BOOST_TEST(v2.y == 4);
}


#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/topological_sort.hpp>
#include <boost/range.hpp>
#include <boost/range/adaptor/indexed.hpp>
#include <deque>
#include <iostream>
#include <list>
#include <string>
#include <type_traits>
#include <vector>

// From the docs, An adjacency_list has the following generic parameters:
// * OutEdgeList: 	The selector for the container used to
//                  represent the edge-list for each of the vertices. 	vecS
// * VertexList:	The selector for the container used to represent
//                  the vertex-list of the graph. 	vecS
// * Directed:	A selector to choose whether the graph is directed,
//              undirected, or directed with bidirectional edge access
//              (access to both out-edges and in-edges). The options are directedS,
//              undirectedS, and bidirectionalS. 	directedS
// * VertexProperties:	for specifying internal property storage. 	no_property
// * EdgeProperties:	for specifying internal property storage. 	no_property
// * GraphProperties:	for specifying property storage for the graph object. 	no_property
// * EdgeList:	The selector for the container used to represent the edge-list for the graph.

namespace boost{
enum vertex_compile_cost_t {vertex_compile_cost = 111}; // a unique #
    BOOST_INSTALL_PROPERTY(vertex, compile_cost);
}

using graph_type = boost::adjacency_list<
    boost::listS,  // by using a list,
                   // we get linear removal time!,
                   // but vertices are not int's any more.
    boost::listS,
    boost::directedS,
    // Vertex properties:
    boost::property<boost::vertex_name_t, std::string,
        boost::property<boost::vertex_compile_cost_t, float,
            boost::property<boost::vertex_distance_t, float,
                boost::property<boost::vertex_color_t, boost::default_color_type> > > >,
    boost::property<boost::edge_weight_t, float>
    >;

using name_map_t = boost::property_map<graph_type, boost::vertex_name_t>::type;
using compile_cost_map_t = boost::property_map<graph_type, boost::vertex_compile_cost_t>::type;
using distance_map_t = boost::property_map<graph_type, boost::vertex_distance_t>::type;
using color_map_t = boost::property_map<graph_type, boost::vertex_color_t>::type;

int main()
{
    graph_type g;

    name_map_t name_map = get(boost::vertex_name, g);
    compile_cost_map_t compile_cost_map = get(boost::vertex_compile_cost, g);
    // -> get the property through compile_cost_map[vertex_element]
    // -> there is a set method to set the vertex/edge...

    return 0;
}


//==============================================================================
//         Copyright 2003 - 2013   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2013   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
//==============================================================================
#include <nt2/combinatorial/include/functions/lcm.hpp>

#include <nt2/sdk/functor/meta/call.hpp>
#include <boost/simd/sdk/simd/native.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <nt2/sdk/unit/tests/type_expr.hpp>
#include <nt2/sdk/unit/module.hpp>
#include <boost/simd/sdk/config.hpp>
#include <boost/simd/sdk/simd/io.hpp>

#include <nt2/include/constants/mone.hpp>
#include <nt2/include/constants/one.hpp>
#include <nt2/include/constants/zero.hpp>
#include <nt2/include/constants/inf.hpp>
#include <nt2/include/constants/minf.hpp>
#include <nt2/include/constants/nan.hpp>
#include <nt2/include/functions/splat.hpp>

NT2_TEST_CASE_TPL ( lcm_real,  NT2_SIMD_REAL_TYPES)
{
  using nt2::lcm;
  using nt2::tag::lcm_;
  using boost::simd::native;
  typedef BOOST_SIMD_DEFAULT_EXTENSION  ext_t;
  typedef native<T,ext_t>                  vT;
  typedef typename nt2::meta::call<lcm_(vT,vT)>::type r_t;
  typedef vT wished_r_t;

  // return type conformity test
  NT2_TEST_TYPE_IS(r_t, wished_r_t);

  // specific values tests
#ifndef BOOST_SIMD_NO_INVALIDS
  NT2_TEST_EQUAL(lcm(nt2::Inf<vT>(), nt2::Inf<vT>()), nt2::Nan<vT>());
  NT2_TEST_EQUAL(lcm(nt2::Inf<vT>(),nt2::splat<vT>(5)), nt2::Nan<vT>());
  NT2_TEST_EQUAL(lcm(nt2::Minf<vT>(), nt2::Minf<vT>()), nt2::Nan<vT>());
  NT2_TEST_EQUAL(lcm(nt2::Nan<vT>(), nt2::Nan<vT>()), nt2::Nan<vT>());
  NT2_TEST_EQUAL(lcm(nt2::Zero<vT>(), nt2::Zero<vT>()), nt2::Nan<vT>());
#endif
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(120),nt2::splat<vT>(80)), nt2::splat<vT>(240));
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(3),nt2::splat<vT>(15)), nt2::splat<vT>(15));
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(3),nt2::splat<vT>(5)), nt2::splat<vT>(15));
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(6),nt2::splat<vT>(15)), nt2::splat<vT>(30));
  NT2_TEST_EQUAL(lcm(nt2::Mone<vT>(), nt2::Mone<vT>()), nt2::One<vT>());
  NT2_TEST_EQUAL(lcm(nt2::One<vT>(), nt2::One<vT>()), nt2::One<vT>());
}

NT2_TEST_CASE_TPL ( lcm_int,  NT2_SIMD_INTEGRAL_TYPES)
{
  using nt2::lcm;
  using nt2::tag::lcm_;
  using boost::simd::native;
  typedef BOOST_SIMD_DEFAULT_EXTENSION  ext_t;
  typedef native<T,ext_t>                  vT;
  typedef typename nt2::meta::call<lcm_(vT,vT)>::type r_t;
  typedef vT wished_r_t;

  // return type conformity test
  NT2_TEST_TYPE_IS(r_t, wished_r_t);

  // specific values tests
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(3),nt2::splat<vT>(15)), nt2::splat<vT>(15));
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(3),nt2::splat<vT>(5)), nt2::splat<vT>(15));
  NT2_TEST_EQUAL(lcm(nt2::splat<vT>(6),nt2::splat<vT>(15)), nt2::splat<vT>(30));
  NT2_TEST_EQUAL(lcm(nt2::One<vT>(), nt2::One<vT>()), nt2::One<vT>());
}


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

#include <iostream>
#include <cmath>
#include <complex>
#include <boost/numeric/mtl/matrix/dense2D.hpp>
#include <boost/numeric/mtl/matrix/morton_dense.hpp>
#include <boost/numeric/mtl/matrix/compressed2D.hpp>
#include <boost/numeric/mtl/matrix/map_view.hpp>
#include <boost/numeric/mtl/matrix/hermitian_view.hpp>
#include <boost/numeric/mtl/matrix/inserter.hpp>
#include <boost/numeric/mtl/recursion/predefined_masks.hpp>
#include <boost/numeric/mtl/operation/print.hpp>
#include <boost/numeric/mtl/operation/set_to_zero.hpp>
#include <boost/numeric/mtl/operation/conj.hpp>
#include <boost/numeric/mtl/operation/imag.hpp>
#include <boost/numeric/mtl/operation/real.hpp>
#include <boost/numeric/mtl/operation/scale.hpp>
#include <boost/numeric/mtl/operation/hermitian.hpp>
#include <boost/numeric/mtl/operation/operators.hpp>
#include <boost/numeric/mtl/operation/mult_result.hpp>
#include <boost/numeric/mtl/utility/ashape.hpp>

using namespace std;

typedef complex<double> ct;

double value(double)
{
    return 7.0;
}

complex<double> value(complex<double>)
{
    return ct(7.0, 1.0);
}

// scaled value
double svalue(double)
{
    return 14.0;
}

ct svalue(ct)
{
    return ct(14.0, 2.0);
}

// conjugated value
double cvalue(double)
{
    return 7.0;
}

ct cvalue(ct)
{
    return ct(7.0, -1.0);
}

// complex scaled value
ct csvalue(double)
{
    return ct(0.0, 7.0);
}

ct csvalue(ct)
{
    return ct(-1.0, 7.0);
}

template <typename Matrix>
void test(Matrix& matrix, const char* name)
{
    using mtl::conj; using mtl::imag; using mtl::real;

    set_to_zero(matrix);
    typename Matrix::value_type ref(0);

    {
	mtl::mat::inserter<Matrix>  ins(matrix);
	ins(2, 3) << value(ref);
	ins(4, 3) << value(ref) + 1.0;
	ins(2, 5) << value(ref) + 2.0;
    }

    cout << "\n\n" << name << "\n";
    cout << "Original matrix:\n" << matrix << "\n";

    mtl::mat::scaled_view<double, Matrix>  scaled_matrix(2.0, matrix);
    cout << "matrix  scaled with 2.0\n" << scaled_matrix << "\n";
    MTL_THROW_IF(scaled_matrix(2, 3) != svalue(ref), mtl::runtime_error("scaling wrong"));

    cout << "matrix  scaled with 2.0 (as operator)\n" << 2.0 * matrix << "\n";
    MTL_THROW_IF((2.0 * matrix)(2, 3) != svalue(ref), mtl::runtime_error("scaling wrong"));

    mtl::mat::conj_view<Matrix>  conj_matrix(matrix);
    cout << "conjugated matrix\n" << conj_matrix << "\n";
    MTL_THROW_IF(conj_matrix(2, 3) != cvalue(ref), mtl::runtime_error(" wrong"));

    mtl::mat::scaled_view<ct, Matrix>  cscaled_matrix(ct(0.0, 1.0), matrix);
    cout << "matrix scaled with i (complex(0, 1))\n" << cscaled_matrix << "\n";
    MTL_THROW_IF(cscaled_matrix(2, 3) != csvalue(ref), mtl::runtime_error("complex scaling wrong"));

    mtl::mat::hermitian_view<Matrix>  hermitian_matrix(matrix);
    cout << "Hermitian matrix (conjugate transposed)\n" << hermitian_matrix << "\n";
    MTL_THROW_IF(hermitian_matrix(3, 2) != cvalue(ref), mtl::runtime_error("conjugate transposing  wrong"));

    cout << "matrix  scaled with 2.0 (free function)\n" << scale(2.0, matrix) << "\n";
    MTL_THROW_IF(scale(2.0, matrix)(2, 3) != svalue(ref), mtl::runtime_error("scaling wrong"));

    cout << "matrix  scaled with 2.0 (free function as mtl::scale)\n" << mtl::scale(2.0, matrix) << "\n";

#if defined(__GNUC__) && __GNUC__ == 4 && (__GNUC_MINOR__ >= 3 && __GNUC_MINOR__ <= 6)
    cout << "conjugated matrix (free function) \n" << mtl::mat::conj(matrix) << "\n";
    MTL_THROW_IF(mtl::mat::conj(matrix)(2, 3) != cvalue(ref), mtl::runtime_error("conjugating wrong"));

    cout << "imaginary part of matrix (free function) \n" << mtl::mat::imag(matrix) << "\n";
    MTL_THROW_IF(mtl::mat::imag(matrix)(2, 3) != imag(value(ref)), mtl::runtime_error("imaginary part wrong"));

    cout << "real part of matrix (free function) \n" << mtl::mat::real(matrix) << "\n";
    MTL_THROW_IF(mtl::mat::real(matrix)(2, 3) != real(value(ref)), mtl::runtime_error("real part wrong"));
#else
    cout << "conjugated matrix (free function) \n" << conj(matrix) << "\n";
    MTL_THROW_IF(conj(matrix)(2, 3) != cvalue(ref), mtl::runtime_error("conjugating wrong"));

    cout << "imaginary part of matrix (free function) \n" << imag(matrix) << "\n";
    MTL_THROW_IF(imag(matrix)(2, 3) != imag(value(ref)), mtl::runtime_error("imaginary part wrong"));

    cout << "real part of matrix (free function) \n" << real(matrix) << "\n";
    MTL_THROW_IF(real(matrix)(2, 3) != real(value(ref)), mtl::runtime_error("real part wrong"));
#endif
    cout << "negation of matrix (free function) \n" << -matrix << "\n";
    MTL_THROW_IF((-matrix)(2, 3) != -(value(ref)), mtl::runtime_error("negation wrong"));

    cout << "matrix scaled with i (complex(0, 1)) (free function)\n" << scale(ct(0.0, 1.0), matrix) << "\n";
    MTL_THROW_IF(scale(ct(0.0, 1.0), matrix)(2, 3) != csvalue(ref), mtl::runtime_error("complex scaling wrong"));

    cout << "Hermitian  matrix (conjugate transposed) (free function)\n" << hermitian(matrix) << "\n";
    MTL_THROW_IF(hermitian(matrix)(3, 2) != cvalue(ref), mtl::runtime_error("conjugate transposing wrong"));

}

int main(int argc, char* argv[])
{
    using namespace mtl;
    unsigned size= 7;
    if (argc > 1) size= atoi(argv[1]);

    dense2D<double>                                      dr(size, size);
    dense2D<double, mat::parameters<col_major> >      dc(size, size);
    morton_dense<double, recursion::morton_z_mask>       mzd(size, size);
    morton_dense<double, recursion::doppled_2_row_mask>  d2r(size, size);
    compressed2D<double>                                 cr(size, size);
    compressed2D<double, mat::parameters<col_major> > cc(size, size);

    dense2D<complex<double> >                            drc(size, size);
    compressed2D<complex<double> >                       crc(size, size);

    test(dr, "Dense row major");
    test(dc, "Dense column major");
    test(mzd, "Morton Z-order");
    test(d2r, "Hybrid 2 row-major");
    test(cr, "Compressed row major");
    test(cc, "Compressed column major");
    test(drc, "Dense row major complex");
    test(crc, "Compressed row major complex");

    double p(2.0);
    dr=-dr;

#if defined(__GNUC__) && __GNUC__ == 4 && (__GNUC_MINOR__ >= 3 && __GNUC_MINOR__ <= 6)
    std::cout << "Only for gcc 4.4.\n";
    dr=mat::real(dr);
    dr=mat::conj(dr)+p*mat::imag(dr);
#else
    dr=real(dr);
    dr=conj(dr)+p*imag(dr);
#endif
    return 0;
}


#include <metal.hpp>

#include "example.hpp"

#if !defined(METAL_WORKAROUND)

#include <array>
#include <chrono>
#include <complex>
#include <tuple>
#include <type_traits>
#include <utility>

/// [make_array]
template<typename... Xs,
    typename R = std::array<std::common_type_t<Xs...>, sizeof...(Xs)>
>
constexpr R make_array(Xs&&... xs) {
    return R{{std::forward<Xs>(xs)...}};
}
/// [make_array]

/// [tuples]
using namespace std::chrono;
using namespace std::literals::chrono_literals;
using namespace std::literals::complex_literals;

auto tup1 = std::make_tuple(42ns, 0x42, 42.f);
auto tup2 = std::make_tuple(42us, 042L, 42.L);
auto tup3 = std::make_tuple(42ms, 42LL, 42.i);
/// [tuples]

#if 0
/// [naive_array_of_tuples]
auto array_of_tuples =  make_array(tup1, tup2, tup3);
/// [naive_array_of_tuples]
#endif

#include <boost/hana/config.hpp>

#if defined(BOOST_HANA_CONFIG_GCC) && \
    BOOST_HANA_CONFIG_GCC < BOOST_HANA_CONFIG_VERSION(5, 4, 0)
#   define SKIP
#endif

#if defined(BOOST_HANA_CONFIG_CLANG) && \
    BOOST_HANA_CONFIG_CLANG < BOOST_HANA_CONFIG_VERSION(3, 5, 0)
#   define SKIP
#endif

#if !defined(SKIP)

#include <boost/hana/ext/std/tuple.hpp>
#include <boost/hana/transform.hpp>
#include <boost/hana/type.hpp>
#include <boost/hana/unpack.hpp>
#include <boost/hana/zip.hpp>

namespace hana {
/// [hana_common_tuple_t]
template<typename... xs>
using hana_common_tuple_t = typename decltype(
    boost::hana::unpack(
        boost::hana::zip_with(
            boost::hana::template_<std::common_type_t>,
            boost::hana::zip_with(boost::hana::decltype_, std::declval<xs>())...
        ),
        boost::hana::template_<std::tuple>
    )
)::type;
/// [hana_common_tuple_t]

/// [hana_make_array_of_tuples]
template<typename... Xs,
    typename R = std::array<std::common_type_t<Xs...>, sizeof...(Xs)>
>
constexpr R hana_make_array(Xs&&... xs) {
    return R{{std::forward<Xs>(xs)...}};
}

template<typename Head, typename... Tail,
    typename R = std::array<hana_common_tuple_t<std::decay_t<Head>, std::decay_t<Tail>...>, 1 + sizeof...(Tail)>
>
constexpr R hana_make_array(Head&& head, Tail&&... tail) {
    return R{{std::forward<Head>(head), std::forward<Tail>(tail)...}};
}
/// [hana_make_array_of_tuples]

#if !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 5000
/// [hana_array_of_tuples]
auto array_of_tuples = hana_make_array(tup1, tup2, tup3);

IS_SAME(decltype(array_of_tuples), std::array<std::tuple<nanoseconds, long long, std::complex<double>>, 3>);
/// [hana_array_of_tuples]
#endif

#if 0
/// [hana_array_of_numbers]
IS_SAME(decltype(hana_make_array(42, 42L, 42LL)), std::array<long long, 3>);
/// [hana_array_of_numbers]
#endif
}
#endif

namespace
{
/// [common_tuple_t]
template<typename... xs>
using common_tuple_t = metal::apply<
    std::common_type_t<metal::lambda<std::tuple>, metal::as_lambda<xs>...>,
    metal::transform<metal::lambda<std::common_type_t>, metal::as_list<xs>...>
>;
/// [common_tuple_t]

/// [make_array_of_tuples]
template<typename... Xs,
    typename R = std::array<std::common_type_t<Xs...>, sizeof...(Xs)>
>
constexpr R make_array(Xs&&... xs) {
    return R{{std::forward<Xs>(xs)...}};
}

template<typename Head, typename... Tail,
    typename R = std::array<common_tuple_t<std::decay_t<Head>, std::decay_t<Tail>...>, 1 + sizeof...(Tail)>
>
constexpr R make_array(Head&& head, Tail&&... tail) {
    return R{{std::forward<Head>(head), std::forward<Tail>(tail)...}};
}
/// [make_array_of_tuples]

#if !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 5000
/// [array_of_tuples]
auto array_of_tuples = make_array(tup1, tup2, tup3);

IS_SAME(decltype(array_of_tuples), std::array<std::tuple<nanoseconds, long long, std::complex<double>>, 3>);
/// [array_of_tuples]
#endif

/// [array_of_numbers]
IS_SAME(decltype(make_array(42, 42L, 42LL)), std::array<long long, 3>);
/// [array_of_numbers]
}
#endif


/*
 * lg.cpp
 *
 * Author: E. Pschernig (epschern@cosy.sbg.ac.at), P. Wild (pwild@cosy.sbg.ac.at)
 *
 * Generates an iris code from iris texture using the LG (Log Gabor) algorithm
 *
 */
#include <cstdio>
#include <map>
#include <vector>
#include <string>
#include <cstring>
#include <ctime>
#include <fstream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>

using namespace std;
using namespace cv;

/** no globbing in win32 mode **/
int _CRT_glob = 0;

/** Program modes **/
static const int MODE_MAIN = 1, MODE_HELP = 2;

/*
 * Print command line usage for this program
 */
void printUsage() {
	printf("+-----------------------------------------------------------------------------+\n");
	printf("| lg - Iris-code generation (feature extraction) using the LG algorithm       |\n");
	printf("|                                                                             |\n");
	printf("| Libor Masek, Peter Kovesi. MATLAB Source Code for a Biometric Identification|\n");
	printf("| System Based on Iris Patterns. The School of Computer Science and Software  |\n");
	printf("| Engineering, The University of Western Australia. 2003.                     |\n");
	printf("|                                                                             |\n");
	printf("| MODES                                                                       |\n");
	printf("|                                                                             |\n");
    printf("| (# 1) LG iris code extraction from iris textures                            |\n");
    printf("| (# 2) usage                                                                 |\n");
    printf("|                                                                             |\n");
    printf("| ARGUMENTS                                                                   |\n");
    printf("|                                                                             |\n");
    printf("+------+------------+---+---+-------------------------------------------------+\n");
    printf("| Name | Parameters | # | ? | Description                                     |\n");
    printf("+------+------------+---+---+-------------------------------------------------+\n");
    printf("| -i   | infile     | 1 | N | input iris texture (use * as wildcard, all other|\n");
    printf("|      |            |   |   | file parameters may refer to n-th * with ?n)    |\n");
    printf("| -o   | outfile    | 1 | N | output iris code image                          |\n");
    printf("| -m   | inmaskfile | 1 | Y | noise pixelmask (0: noise, 255: noise-free)     |\n");
    printf("|      | outmaskfile|   |   | code bitmask (0: noise, 1: noise-free, off)     |\n");
    printf("| -q   |            | 1 | Y | quiet mode on (off)                             |\n");
    printf("| -t   |            | 1 | Y | time progress on (off)                          |\n");
    printf("| -h   |            | 2 | N | prints usage                                    |\n");
    printf("+------+------------+---+---+-------------------------------------------------+\n");
    printf("|                                                                             |\n");
    printf("| EXAMPLE USAGE                                                               |\n");
    printf("|                                                                             |\n");
    printf("| -i s1.tiff -o s1.png                                                        |\n");
    printf("| -i *.tiff -o ?1.png -q -t                                                   |\n");
    printf("| -i *.tiff -im ?1_mask.png -o ?1_code.png -om ?1_codemask.png -q -t          |\n");
    printf("|                                                                             |\n");
    printf("| AUTHORS                                                                     |\n");
    printf("|                                                                             |\n");
    printf("| Elias Pschernig (epschern@cosy.sbg.ac.at)                                   |\n");
    printf("| Peter Wild (pwild@cosy.sbg.ac.at)                                           |\n");
    printf("|                                                                             |\n");
    printf("| COPYRIGHT                                                                   |\n");
    printf("|                                                                             |\n");
    printf("| (C) 2012 All rights reserved. Do not distribute without written permission. |\n");
    printf("+-----------------------------------------------------------------------------+\n");
}

/** ------------------------------- image processing functions ------------------------------- **/

/**
 * Convolution with Log Gabor Kernels
 */
void gabor_convolve(Mat& dst, const Mat& src, int w, int h, int nscales, double min_wavelength, double mult, double sigmaOnf)
{
	float * data = (float *) src.data;
    Mat filter(1,w,CV_64FC2);
    filter.setTo(0);
    double *fData = (double *) filter.data;
    int ndata_half = w / 2;
    Mat radius(1,ndata_half + 1,CV_64FC1);
    double *rData = (double *) radius.data;
    for (int i = 0; i <= ndata_half; i++, rData++)
    {
    	*rData = 0.5 * i / ndata_half;
    }
    rData = (double *) radius.data;
    rData[0] = 1;
    Mat signal(1,w,CV_64FC2);
    signal.setTo(0);
    double *sData = (double*) signal.data;
    double wavelength = min_wavelength;
    for (int s = 0; s < nscales; s++)
    {
        double fo = 1.0 / wavelength;
        for (int i = 0; i <= ndata_half; i++)
        {
            double alpha = log(rData[i] / fo);
            double beta = log(sigmaOnf);
            fData[2*i] = exp(-(alpha * alpha) / (2 * beta * beta));
        }
        fData[0] = 0;

        for (int r = 0; r < h; r++)
        {
        	signal.setTo(0);
            for (int i = 0; i < w; i++)
            {
                sData[2*i] = data[r * w + i];
            }
            cv::dft(signal,signal,CV_DXT_FORWARD, 1);
            cv::mulSpectrums(signal,filter,signal,DFT_COMPLEX_OUTPUT);
            Mat part(dst,Rect(s*h*w+r*w,0,w,1));
            cv::dft(signal,part,CV_DXT_INV_SCALE, 1);
        }
        wavelength *= mult;
    }
}

/*
 * The LG feature extraction algorithm
 *
 * code: Code matrix
 * texture: texture matrix
 * M: row height (use 5)
 * N: number of rows (use 10); we usually expect the texture to be sized 512x64, M = 5 and N = 10.
 *  This means we should end up with 10 1-D vectors of 512 samples.
 */
void featureExtract(Mat& code, Mat& codeMask, const Mat& texture, const Mat& textureMask, const int M = 5, const int N = 10)
{
	CV_Assert(texture.size() == Size(512,64));
	CV_Assert(code.size() == Size(512 * N * 2 / 8,1));
	CV_Assert(textureMask.empty() || codeMask.empty() || codeMask.size() == code.size());
	bool useMask = !textureMask.empty() && !codeMask.empty();
	uchar * textureData = texture.data;
	int tOffset = texture.step;
	int w = texture.cols;
	uchar * textureMaskData = textureMask.data;
	int tmOffset = textureMask.step;
	Mat noise;
	uchar * codeMaskData = codeMask.data;
	if (useMask) codeMask.setTo(255);
	Mat s(1,N*w, CV_32FC1);
	float * sData = (float *) s.data;
    /* Downscale by M in vertical direction, so we get N 1-D signals s. */
	for (int x = 0; x < w; x++)
    {
        for (int i = 0; i < N; i++)
        {
            float sum = 0;
            int noisefree = 0;
            for (int j = 0; j < M; j++)
            {
            	int y = (j + i * M);
            	if (!useMask || textureMaskData[ y * tmOffset + x] != 0){
            		sum += textureData[y * tOffset + x];
            		noisefree++;
            	}
            }
            sData[i * w + x] = (noisefree != 0) ? sum / noisefree : 128;
            if (useMask && noisefree == 0) {
            	int idx = (2*i*w + x)/8;
            	int bit = (2*i*w + x) % 8;
            	codeMaskData[idx] &= (0xff ^ 1 << (7-bit));
            	idx = (2*i*w + w + x)/8;
            	bit = (2*i*w + w + x) % 8;
            	//if (bit == 8) { idx++; bit = 0; }
            	codeMaskData[idx] &= (0xff ^ 1 << (7-bit));
            }
            /* normalize the signals from 0..255 to -1..1 range, probably not necessary */
        }
    }
    for (int i = 0; i < N; i++)
    {
        for (int j = 0; j < w; j++)
        {
        	sData[i * w + j] /= 255;
        	sData[i * w + j] -= 0.5;
        }
    }
    int nscales = 1;
    double min_wavelength = 36;
    double mult = 1.0;
    double sigmaOnf = 0.5;
    Mat conv(1,nscales*N*w,CV_64FC2);
    gabor_convolve(conv,s, w, N, nscales, min_wavelength, mult, sigmaOnf);
    code.setTo(0);
    uchar * codeData = code.data;
    int bitpos = 0;
    double * convData = (double *) conv.data;
    int rowlen = w/8;
    codeData[0] = 0;
    codeData[rowlen] = 0;
    for (int k = 0; k < nscales; k++)
    {
    	for (int y = 0; y < N; y++)
    	{
    		for (int x = 0; x < w; x++)
    		{
    			if (useMask) {
    				bool H3 = ((convData[0] * convData[0]) + (convData[1] * convData[1])) < 0.00000001; // abs
    				if (H3) {
						*codeMaskData &= (0xff ^ 1 << (7-bitpos));
						codeMaskData[rowlen] &= (0xff ^ 1 << (7-bitpos));
					}
    			}
    			bool H1 = *convData > 0; // re
    			convData++;
    			bool H2 = *convData > 0; // im
    			convData++;
    			if (bitpos == 8) { codeData++; codeData[0] = 0; codeData[rowlen] = 0; bitpos = 0; if (useMask) {codeMaskData++;}}
    			if (H1)	codeData[0] |= (1 << bitpos);
    			if (H2) codeData[rowlen] |= (1 << bitpos);
    			bitpos++;
    		}
    		codeData += rowlen;
    		if (useMask) codeMaskData += rowlen;
    	}
    }
}

/** ------------------------------- commandline functions ------------------------------- **/

/**
 * Parses a command line
 * This routine should be called for parsing command lines for executables.
 * Note, that all options require '-' as prefix and may contain an arbitrary
 * number of optional arguments.
 *
 * cmd: commandline representation
 * argc: number of parameters
 * argv: string array of argument values
 */
void cmdRead(map<string ,vector<string> >& cmd, int argc, char *argv[]){
	for (int i=1; i< argc; i++){
		char * argument = argv[i];
		if (strlen(argument) > 1 && argument[0] == '-' && (argument[1] < '0' || argument[1] > '9')){
			cmd[argument]; // insert
			char * argument2;
			while (i + 1 < argc && (strlen(argument2 = argv[i+1]) <= 1 || argument2[0] != '-'  || (argument2[1] >= '0' && argument2[1] <= '9'))){
				cmd[argument].push_back(argument2);
				i++;
			}
		}
		else {
			CV_Error(CV_StsBadArg,"Invalid command line format");
		}
	}
}

/**
 * Checks, if each command line option is valid, i.e. exists in the options array
 *
 * cmd: commandline representation
 * validOptions: list of valid options separated by pipe (i.e. |) character
 */
void cmdCheckOpts(map<string ,vector<string> >& cmd, const string validOptions){
	vector<string> tokens;
	const string delimiters = "|";
	string::size_type lastPos = validOptions.find_first_not_of(delimiters,0); // skip delimiters at beginning
	string::size_type pos = validOptions.find_first_of(delimiters, lastPos); // find first non-delimiter
	while (string::npos != pos || string::npos != lastPos){
		tokens.push_back(validOptions.substr(lastPos,pos - lastPos)); // add found token to vector
		lastPos = validOptions.find_first_not_of(delimiters,pos); // skip delimiters
		pos = validOptions.find_first_of(delimiters,lastPos); // find next non-delimiter
	}
	sort(tokens.begin(), tokens.end());
	for (map<string, vector<string> >::iterator it = cmd.begin(); it != cmd.end(); it++){
		if (!binary_search(tokens.begin(),tokens.end(),it->first)){
			CV_Error(CV_StsBadArg,"Command line parameter '" + it->first + "' not allowed.");
			tokens.clear();
			return;
		}
	}
	tokens.clear();
}

/*
 * Checks, if a specific required option exists in the command line
 *
 * cmd: commandline representation
 * option: option name
 */
void cmdCheckOptExists(map<string ,vector<string> >& cmd, const string option){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it == cmd.end()) CV_Error(CV_StsBadArg,"Command line parameter '" + option + "' is required, but does not exist.");
}

/*
 * Checks, if a specific option has the appropriate number of parameters
 *
 * cmd: commandline representation
 * option: option name
 * size: appropriate number of parameters for the option
 */
void cmdCheckOptSize(map<string ,vector<string> >& cmd, const string option, const unsigned int size = 1){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it->second.size() != size) CV_Error(CV_StsBadArg,"Command line parameter '" + option + "' has unexpected size.");
}

/*
 * Checks, if a specific option has the appropriate number of parameters
 *
 * cmd: commandline representation
 * option: option name
 * min: minimum appropriate number of parameters for the option
 * max: maximum appropriate number of parameters for the option
 */
void cmdCheckOptRange(map<string ,vector<string> >& cmd, string option, unsigned int min = 0, unsigned int max = 1){
	map<string, vector<string> >::iterator it = cmd.find(option);
	unsigned int size = it->second.size();
	if (size < min || size > max) CV_Error(CV_StsBadArg,"Command line parameter '" + option + "' is out of range.");
}

/*
 * Returns the list of parameters for a given option
 *
 * cmd: commandline representation
 * option: name of the option
 */
vector<string> * cmdGetOpt(map<string ,vector<string> >& cmd, const string option){
	map<string, vector<string> >::iterator it = cmd.find(option);
	return (it != cmd.end()) ? &(it->second) : 0;
}

/*
 * Returns number of parameters in an option
 *
 * cmd: commandline representation
 * option: name of the option
 */
unsigned int cmdSizePars(map<string ,vector<string> >& cmd, const string option){
	map<string, vector<string> >::iterator it = cmd.find(option);
	return (it != cmd.end()) ? it->second.size() : 0;
}

/*
 * Returns a specific parameter type (int) given an option and parameter index
 *
 * cmd: commandline representation
 * option: name of option
 * param: name of parameter
 */
int cmdGetParInt(map<string ,vector<string> >& cmd, string option, unsigned int param = 0){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it != cmd.end()) {
		if (param < it->second.size()) {
			return atoi(it->second[param].c_str());
		}
	}
	return 0;
}

/*
 * Returns a specific parameter type (float) given an option and parameter index
 *
 * cmd: commandline representation
 * option: name of option
 * param: name of parameter
 */
float cmdGetParFloat(map<string ,vector<string> >& cmd, const string option, const unsigned int param = 0){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it != cmd.end()) {
		if (param < it->second.size()) {
			return atof(it->second[param].c_str());
		}
	}
	return 0;
}

/*
 * Returns a specific parameter type (string) given an option and parameter index
 *
 * cmd: commandline representation
 * option: name of option
 * param: name of parameter
 */
string cmdGetPar(map<string ,vector<string> >& cmd, const string option, const unsigned int param = 0){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it != cmd.end()) {
		if (param < it->second.size()) {
			return it->second[param];
		}
	}
	return 0;
}

/** ------------------------------- timing functions ------------------------------- **/

/**
 * Class for handling timing progress information
 */
class Timing{
public:
	/** integer indicating progress with respect tot total **/
	int progress;
	/** total count for progress **/
	int total;

	/*
	 * Default constructor for timing initializing time.
	 * Automatically calls init()
	 *
	 * seconds: update interval in seconds
	 * eraseMode: if true, outputs sends erase characters at each print command
	 */
	Timing(long seconds, bool eraseMode){
		updateInterval = seconds;
		progress = 1;
		total = 100;
		eraseCount=0;
		erase = eraseMode;
		init();
	}

	/*
	 * Destructor
	 */
	~Timing(){}

	/*
	 * Initializes timing variables
	 */
	void init(void){
		start = boost::posix_time::microsec_clock::universal_time();
		lastPrint = start - boost::posix_time::seconds(updateInterval);
	}

	/*
	 * Clears printing (for erase option only)
	 */
	void clear(void){
		string erase(eraseCount,'\r');
		erase.append(eraseCount,' ');
		erase.append(eraseCount,'\r');
		printf("%s",erase.c_str());
		eraseCount = 0;
	}

	/*
	 * Updates current time and returns true, if output should be printed
	 */
	bool update(void){
		current = boost::posix_time::microsec_clock::universal_time();
		return ((current - lastPrint > boost::posix_time::seconds(updateInterval)) || (progress == total));
	}

	/*
	 * Prints timing object to STDOUT
	 */
	void print(void){
		lastPrint = current;
		float percent = 100.f * progress / total;
		boost::posix_time::time_duration passed = (current - start);
		boost::posix_time::time_duration togo = passed * (total - progress) / max(1,progress);
		if (erase) {
			string erase(eraseCount,'\r');
			printf("%s",erase.c_str());
			int newEraseCount = (progress != total) ? printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03i Remaining ca. %i:%02i:%02i.%03i)",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000),togo.hours(),togo.minutes(),togo.seconds(),(int)(togo.total_milliseconds() % 1000)) : printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03d)",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000));
			if (newEraseCount < eraseCount) {
				string erase(newEraseCount-eraseCount,' ');
				erase.append(newEraseCount-eraseCount,'\r');
				printf("%s",erase.c_str());
			}
			eraseCount = newEraseCount;
		}
		else {
			eraseCount = (progress != total) ? printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03i Remaining ca. %i:%02i:%02i.%03i)\n",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000),togo.hours(),togo.minutes(),togo.seconds(),(int)(togo.total_milliseconds() % 1000)) : printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03d)\n",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000));
		}
	}
private:
	long updateInterval;
	boost::posix_time::ptime start;
	boost::posix_time::ptime current;
	boost::posix_time::ptime lastPrint;
	int eraseCount;
	bool erase;
};

/** ------------------------------- file pattern matching functions ------------------------------- **/

/*
 * Formats a given string, such that it can be used as a regular expression
 * I.e. escapes special characters and uses * and ? as wildcards
 *
 * pattern: regular expression path pattern
 * pos: substring starting index
 * n: substring size
 *
 * returning: escaped substring
 */
string patternSubstrRegex(string& pattern, size_t pos, size_t n){
	string result;
	for (size_t i=pos, e=pos+n; i < e; i++ ) {
		char c = pattern[i];
		if ( c == '\\' || c == '.' || c == '+' || c == '[' || c == '{' || c == '|' || c == '(' || c == ')' || c == '^' || c == '$' || c == '}' || c == ']') {
			result.append(1,'\\');
			result.append(1,c);
		}
		else if (c == '*'){
			result.append("([^/\\\\]*)");
		}
		else if (c == '?'){
			result.append("([^/\\\\])");
		}
		else {
			result.append(1,c);
		}
	}
	return result;
}

/*
 * Converts a regular expression path pattern into a list of files matching with this pattern by replacing wildcards
 * starting in position pos assuming that all prior wildcards have been resolved yielding intermediate directory path.
 * I.e. this function appends the files in the specified path according to yet unresolved pattern by recursive calling.
 *
 * pattern: regular expression path pattern
 * files: the list to which new files can be applied
 * pos: an index such that positions 0...pos-1 of pattern are already considered/matched yielding path
 * path: the current directory (or empty)
 */
void patternToFiles(string& pattern, vector<string>& files, const size_t& pos, const string& path){
	size_t first_unknown = pattern.find_first_of("*?",pos); // find unknown * in pattern
	if (first_unknown != string::npos){
		size_t last_dirpath = pattern.find_last_of("/\\",first_unknown);
		size_t next_dirpath = pattern.find_first_of("/\\",first_unknown);
		if (next_dirpath != string::npos){
			boost::regex expr((last_dirpath != string::npos && last_dirpath > pos) ? patternSubstrRegex(pattern,last_dirpath+1,next_dirpath-last_dirpath-1) : patternSubstrRegex(pattern,pos,next_dirpath-pos));
			boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
			try {
				for ( boost::filesystem::directory_iterator itr( ((path.length() > 0) ? path + pattern[pos-1] : (last_dirpath != string::npos && last_dirpath > pos) ? "" : "./") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) : "")); itr != end_itr; ++itr )
				{
					if (boost::filesystem::is_directory(itr->path())){
						boost::filesystem::path p = itr->path().filename();
						string s =  p.string();
						if (boost::regex_match(s.c_str(), expr)){
							patternToFiles(pattern,files,(int)(next_dirpath+1),((path.length() > 0) ? path + pattern[pos-1] : "") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) + pattern[last_dirpath] : "") + s);
						}
					}
				}
			}
			catch (boost::filesystem::filesystem_error &e){}
		}
		else {
			boost::regex expr((last_dirpath != string::npos && last_dirpath > pos) ? patternSubstrRegex(pattern,last_dirpath+1,pattern.length()-last_dirpath-1) : patternSubstrRegex(pattern,pos,pattern.length()-pos));
			boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
			try {
				for ( boost::filesystem::directory_iterator itr(((path.length() > 0) ? path +  pattern[pos-1] : (last_dirpath != string::npos && last_dirpath > pos) ? "" : "./") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) : "")); itr != end_itr; ++itr )
				{
					boost::filesystem::path p = itr->path().filename();
					string s =  p.string();
					if (boost::regex_match(s.c_str(), expr)){
						files.push_back(((path.length() > 0) ? path + pattern[pos-1] : "") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) + pattern[last_dirpath] : "") + s);
					}
				}
			}
			catch (boost::filesystem::filesystem_error &e){}
		}
	}
	else { // no unknown symbols
		boost::filesystem::path file(((path.length() > 0) ? path + "/" : "") + pattern.substr(pos,pattern.length()-pos));
		if (boost::filesystem::exists(file)){
			files.push_back(file.string());
		}
	}
}

/**
 * Converts a regular expression path pattern into a list of files matching with this pattern
 *
 * pattern: regular expression path pattern
 * files: the list to which new files can be applied
 */
void patternToFiles(string& pattern, vector<string>& files){
	patternToFiles(pattern,files,0,"");
}

/*
 * Renames a given filename corresponding to the actual file pattern using a renaming pattern.
 * Wildcards can be referred to as ?1, ?2, ... in the order they appeared in the file pattern.
 *
 * pattern: regular expression path pattern
 * renamePattern: renaming pattern using ?1, ?2, ... as placeholders for wildcards
 * infile: path of the file (matching with pattern) to be renamed
 * outfile: path of the renamed file
 * par: used parameter (default: '?')
 */
void patternFileRename(string& pattern, const string& renamePattern, const string& infile, string& outfile, const char par = '?'){
	size_t first_unknown = renamePattern.find_first_of(par,0); // find unknown ? in renamePattern
	if (first_unknown != string::npos){
		string formatOut = "";
		for (size_t i=0, e=renamePattern.length(); i < e; i++ ) {
			char c = renamePattern[i];
			if ( c == par && i+1 < e) {
				c = renamePattern[i+1];
				if (c > '0' && c <= '9'){
					formatOut.append(1,'$');
					formatOut.append(1,c);
				}
				else {
					formatOut.append(1,par);
					formatOut.append(1,c);
				}
				i++;
			}
			else {
				formatOut.append(1,c);
			}
		}
		boost::regex patternOut(patternSubstrRegex(pattern,0,pattern.length()));
		outfile = boost::regex_replace(infile,patternOut,formatOut,boost::match_default | boost::format_perl);
	} else {
		outfile = renamePattern;
	}
}

/** ------------------------------- Program ------------------------------- **/

/*
 * Main program
 */
int main(int argc, char *argv[])
{
	int mode = MODE_HELP;
	map<string,vector<string> > cmd;
	try {
		cmdRead(cmd,argc,argv);
    	if (cmd.size() == 0 || cmdGetOpt(cmd,"-h") != 0) mode = MODE_HELP;
    	else mode = MODE_MAIN;
    	if (mode == MODE_MAIN){
			// validate command line
			cmdCheckOpts(cmd,"-i|-o|-m|-q|-t");
			cmdCheckOptExists(cmd,"-i");
			cmdCheckOptSize(cmd,"-i",1);
			string inFiles = cmdGetPar(cmd,"-i");
			cmdCheckOptExists(cmd,"-o");
			cmdCheckOptSize(cmd,"-o",1);
			string outFiles = cmdGetPar(cmd,"-o");
			string imaskFiles, omaskFiles;
			if (cmdGetOpt(cmd,"-m") != 0){
				cmdCheckOptSize(cmd,"-m",2);
				imaskFiles = cmdGetPar(cmd,"-m", 0);
				omaskFiles = cmdGetPar(cmd,"-m", 1);
			}
			bool quiet = false;
			if (cmdGetOpt(cmd,"-q") != 0){
				cmdCheckOptSize(cmd,"-q",0);
				quiet = true;
			}
			bool time = false;
			if (cmdGetOpt(cmd,"-t") != 0){
				cmdCheckOptSize(cmd,"-t",0);
				time = true;
			}
			// starting routine
			Timing timing(1,quiet);
			vector<string> files;
			patternToFiles(inFiles,files);
			CV_Assert(files.size() > 0);
			timing.total = files.size();
			int m = 5, n = 10;
			Mat code (1,512*n*2/8,CV_8UC1);
			Mat codeMask(1,512*n*2/8,CV_8UC1);
			for (vector<string>::iterator inFile = files.begin(); inFile != files.end(); ++inFile, timing.progress++){
				if (!quiet) printf("Loading texture '%s' ...\n", (*inFile).c_str());;
				Mat img = imread(*inFile, CV_LOAD_IMAGE_GRAYSCALE);
				CV_Assert(img.data != 0);
				CV_Assert(img.size() == Size(512,64));
				Mat mask = Mat();
				if (!imaskFiles.empty()) {
					string imaskfile;
					patternFileRename(inFiles,imaskFiles,*inFile,imaskfile);
					if (!quiet) printf("Loading mask image '%s' ...\n", imaskfile.c_str());;
					mask = imread(imaskfile, CV_LOAD_IMAGE_GRAYSCALE);
					CV_Assert(mask.data != 0);
					CV_Assert(mask.size() == Size(512,64));
				}
				if (!quiet) printf("Creating iris-code ...\n");

				featureExtract(code, codeMask, img, mask, m, n);
				string outfile;
				patternFileRename(inFiles,outFiles,*inFile,outfile);
				if (!quiet) printf("Storing code '%s' ...\n", outfile.c_str());
				if (!imwrite(outfile,code)) CV_Error(CV_StsError,"Could not save image '" + outfile + "'");
				if (!imaskFiles.empty()) {
					string omaskfile;
					patternFileRename(inFiles,omaskFiles,*inFile,omaskfile);
					if (!quiet) printf("Storing code-mask '%s' ...\n", omaskfile.c_str());
					if (!imwrite(omaskfile,codeMask)) CV_Error(CV_StsError,"Could not save image '" + omaskfile + "'");
				}
				if (time && timing.update()) timing.print();
			}
			if (time && quiet) timing.clear();
    	}
    	else if (mode == MODE_HELP){
			// validate command line
			cmdCheckOpts(cmd,"-h");
			if (cmdGetOpt(cmd,"-h") != 0) cmdCheckOptSize(cmd,"-h",0);
			// starting routine
			printUsage();
    	}
    }
	catch (...){
	   	printf("Exit with errors.\n");
	   	exit(EXIT_FAILURE);
	}
    return EXIT_SUCCESS;
}


#include <string>
#include <iostream>
#include <fstream>

#include <Eigen/Core>
#include <Eigen/Geometry>

using namespace std;

// path to trajectory file
string fInputPath = "./tum_06_gt.txt";
string fOutPath = "./tum06.txt";
double init_time = 1317355712.01035; // sequence 04 --> 0 frame in the raw data 0930-16
                                // 1317354639.90

int main(int argc, char **argv)
{
    ofstream foutPose(fOutPath, ios::out);
    fstream fInPose(fInputPath);
    string single_line;
    double timeStamp;
    double tx, ty, tz, qx, qy, qz, qw;

    while( getline(fInPose, single_line) )
    {
        stringstream in_stream(single_line);
        in_stream >> timeStamp >> tx >> ty >> tz >> qx >> qy >> qz >> qw;

        foutPose.setf(ios::fixed, ios::floatfield);
        foutPose.precision(5);
        foutPose << timeStamp+init_time << " " << tx << " " << ty << " " << tz << " "
                << qx << " " << qy << " " << qz << " " << qw << endl;
    }

    foutPose.close();

    return 0;

}


/* ----------------------------------------------------------------------------
 * GTDynamics Copyright 2020, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * See LICENSE for the license information
 * -------------------------------------------------------------------------- */

/**
 * @file  StaticWrenchFactor.cpp
 * @brief Wrench balance factor for a stationary link.
 * @author Frank Dellaert, Mandy Xie, Yetong Zhang, and Gerry Chen
 */

#include "gtdynamics/statics/StaticWrenchFactor.h"

#include <gtsam/base/Matrix.h>
#include <gtsam/base/OptionalJacobian.h>
#include <gtsam/base/Vector.h>
#include <gtsam/geometry/Pose3.h>
#include <gtsam/nonlinear/Values.h>

#include <boost/optional.hpp>
#include <vector>

#include "gtdynamics/statics/Statics.h"

using gtsam::Matrix;
using gtsam::Pose3;
using gtsam::Values;
using gtsam::Vector;
using gtsam::Vector6;

namespace gtdynamics {

StaticWrenchFactor::StaticWrenchFactor(
    const std::vector<DynamicsSymbol> &wrench_keys, gtsam::Key pose_key,
    const gtsam::noiseModel::Base::shared_ptr &cost_model, double mass,
    const boost::optional<gtsam::Vector3> &gravity)
    : Base(cost_model, wrench_keys), mass_(mass), gravity_(gravity) {
  keys_.push_back(pose_key);
}

Vector StaticWrenchFactor::unwhitenedError(
    const Values &x, boost::optional<std::vector<Matrix> &> H) const {
  if (!this->active(x)) {
    return Vector::Zero(this->dim());
  }

  // Collect external wrenches.
  std::vector<Vector6> external_wrenches;
  for (auto key = keys_.cbegin(); key != keys_.cend() - 1; ++key) {
    external_wrenches.push_back(x.at<Vector6>(*key));
  }

  // H->back() will contain Jacobian with respect to pose.
  return ResultantWrench(external_wrenches, mass_, x.at<Pose3>(keys_.back()),
                         gravity_, H);
}

}  // namespace gtdynamics


//
// Add --log_level=message to see the messages!
//
#define BOOST_TEST_MODULE "FullyConnectedNetwork Tests"

#include <boost/test/unit_test.hpp>

#include <sstream>

#include "core/training.h"
#include "core/updaters.h"
#include "core/utils.h"
#include "core/functions.h"
#include "layers/fclayer.h"
#include "layers/smaxlayer.h"
#include "networks/fcnn.h"

#include "timer.h"
#include "test_utils.h"
#include "mnist_test.h"

using namespace std;
using namespace boost;
using namespace boost::unit_test;
using namespace yann;
using namespace yann::test;

#define MNIST_TEST_FOLDER   "../data/mnist/"

// Enums to simplify tests
enum ActivationMode
{
 Sigmoid,
 Softmax,
};

struct FcnnTestFixture
{

  MnistTest _mnist_test;

  FcnnTestFixture()
  {
    BOOST_TEST_MESSAGE("*** Reading MNIST dataset ...");
    _mnist_test.read(MNIST_TEST_FOLDER);
    BOOST_TEST_MESSAGE(_mnist_test);
  }
  ~FcnnTestFixture()
  {

  }

  unique_ptr<Network> create_fcnn_for_mnist(
      const vector<MatrixSize> & hidden_layer_sizes,
      const unique_ptr<CostFunction> & cost,
      enum ActivationMode activ_mode)
  {
    YANN_CHECK_GT(hidden_layer_sizes.size(), 0); // at least one hidden layer required

    // add input / output layers based on mnist data
    std::vector<MatrixSize> layer_sizes = _mnist_test.get_layer_sizes(hidden_layer_sizes);
    YANN_CHECK_GE(layer_sizes.size(), 2); // at least input + output

    // create params
    vector<FullyConnectedNetwork::Params> params(layer_sizes.size() - 1);
    for (size_t ii = 0; ii < params.size(); ++ii) {
      params[ii]._input_size  = layer_sizes[ii];
      params[ii]._output_size = layer_sizes[ii + 1];
      switch(activ_mode) {
        case Sigmoid:
          params[ii]._activation_function = make_unique<SigmoidFunction>();
          break;
        case Softmax:
          // Softmax only on the last layer
          if (ii < hidden_layer_sizes.size()) {
            params[ii]._activation_function = make_unique<SigmoidFunction>();
          } else {
            params[ii]._activation_function = make_unique<IdentityFunction>();
          }
          break;
      }
    }

    // create network
    auto fcnn = FullyConnectedNetwork::create(params);
    fcnn->set_cost_function(cost);

    if(activ_mode == Softmax) {
      auto smax_layer = make_unique<SoftmaxLayer>(fcnn->get_output_size());
      fcnn->append_layer(std::move(smax_layer));
    }

    // done
    return fcnn;
  }

  pair<double, Value> test_fcnn_mnist(
      const std::unique_ptr<Layer::Updater> & updater,
      DataSource_Stochastic::Mode mode,
      const MatrixSize & training_batch_size,
      const vector<MatrixSize> & hidden_layer_sizes,
      const unique_ptr<CostFunction> & cost,
      enum ActivationMode activ_mode,
      size_t epochs,
      const MatrixSize & test_batch_size = 100)
  {
    // setup
    auto fcnn = create_fcnn_for_mnist(hidden_layer_sizes, cost, activ_mode);
    YANN_CHECK(fcnn);
    fcnn->init(Layer::InitMode_Random, Layer::InitContext(12345));

    // create trainer
    Trainer trainer(updater->copy());
    trainer.set_batch_progress_callback(batch_progress_callback);

    BOOST_TEST_MESSAGE("*** Training against MNIST dataset with ");
    BOOST_TEST_MESSAGE(" fcnn: " << fcnn->get_info());
    BOOST_TEST_MESSAGE(" trainer: " << trainer.get_info());
    BOOST_TEST_MESSAGE(" epochs: " << epochs);

    auto res = _mnist_test.train_and_test(
        *fcnn, trainer, mode, training_batch_size, epochs, test_batch_size);
    BOOST_TEST_MESSAGE("*** Success rate: " << (res.first * 100)
                       << "% Loss: " << res.second
                       << " after " << epochs << " epochs");
    return res;
  }

  double test_fcnn_mnist_find_best(const std::unique_ptr<Layer::Updater> & updater,
                                   DataSource_Stochastic::Mode mode,
                                   const MatrixSize & training_batch_size,
                                   const Range& hidden_layers_num_range,
                                   const Range& hidden_layer_size_range,
                                   const unique_ptr<CostFunction> & cost,
                                   enum ActivationMode activ_mode,
                                   size_t epochs)
  {
    // run various networks and record results
    size_t best_hidden_layers_num = 0, best_hidden_layer_size = 0;
    double best_success_rate = 0;
    for (size_t hidden_layers_num = hidden_layers_num_range.min();
        hidden_layers_num <= hidden_layers_num_range.max(); hidden_layers_num +=
            hidden_layers_num_range.step()) {
      for (size_t hidden_layer_size = hidden_layer_size_range.min();
          hidden_layer_size <= hidden_layer_size_range.max();
          hidden_layer_size += hidden_layer_size_range.step()) {

        BOOST_TEST_MESSAGE("*** MNIST find best test with");
        BOOST_TEST_MESSAGE(" hidden_layers_num = " << hidden_layers_num);
        BOOST_TEST_MESSAGE(" hidden_layer_size = " << hidden_layer_size);

        vector<MatrixSize> hidden_layer_sizes;
        hidden_layer_sizes.insert(hidden_layer_sizes.end(), hidden_layers_num,
                                  hidden_layer_size);

        auto res = test_fcnn_mnist(
            updater, mode, training_batch_size, hidden_layer_sizes, cost, activ_mode, epochs);

        // update best
        if (res.first > best_success_rate) {
          best_success_rate = res.first;
          best_hidden_layers_num = hidden_layers_num;
          best_hidden_layer_size = hidden_layer_size;
        }
      }
    }

    BOOST_TEST_MESSAGE(
        "*** MNIST best success rate: " << best_success_rate << " with "
            << best_hidden_layers_num << " hidden layers num" << " and "
            << best_hidden_layer_size << " hidden layer size");
    return best_success_rate;
  }
};
// struct FcnnTestFixture

BOOST_FIXTURE_TEST_SUITE(FcnnTest, FcnnTestFixture);

BOOST_AUTO_TEST_CASE(Fcnn_IO_Test)
{
  BOOST_TEST_MESSAGE("*** FullyConnectedNetwork IO test ...");
  vector<MatrixSize> layers( { 3, 2, 1 });

  auto one = FullyConnectedNetwork::create(layers);
  YANN_CHECK(one);
  one->init(Layer::InitMode_Random);

  BOOST_TEST_MESSAGE("fcnn before writing to file: " << "\n" << *one);
  ostringstream oss;
  oss << *one;
  BOOST_CHECK(!oss.fail());

  auto two = FullyConnectedNetwork::create(layers);
  YANN_CHECK(two);
  std::istringstream iss(oss.str());
  iss >> *two;
  BOOST_CHECK(!iss.fail());
  BOOST_TEST_MESSAGE("fcnn after loading from file: " << "\n" << *two);

  BOOST_CHECK(one->is_equal(*two, TEST_TOLERANCE));
}

BOOST_AUTO_TEST_CASE(Training_StochasticGD_Test)
{

  BOOST_TEST_MESSAGE("*** FullyConnectedNetwork training test ...");

  const MatrixSize input_size = 4;
  const MatrixSize output_size = 2;
  const MatrixSize training_batch_size = 4;
  const MatrixSize testing_batch_size = 2;
  const size_t epochs = 100;
  VectorBatch training_inputs, training_outputs;
  VectorBatch testing_inputs, testing_outputs;

  resize_batch(training_inputs,  training_batch_size, input_size);
  resize_batch(training_outputs, training_batch_size, output_size);
  resize_batch(testing_inputs,   testing_batch_size,  input_size);
  resize_batch(testing_outputs,  testing_batch_size,  output_size);

  training_inputs  << 0, 0, 0, 1,
                      0, 0, 1, 0,
                      0, 1, 0, 0,
                      1, 0, 0, 0;
  training_outputs << 0, 1,
                      0, 1,
                      1, 0,
                      1, 0;

  testing_inputs   << 0, 0, 1, 1,
                      1, 1, 0, 0;
  testing_outputs  << 0, 1,
                      1, 0;

  // training
  auto fcnn = FullyConnectedNetwork::create( { 4, 2 });
  YANN_CHECK(fcnn);

  Trainer trainer(make_unique<Updater_GradientDescent>(3.0, 0.0));
  DataSource_Stochastic data_source(
      training_inputs,
      training_outputs,
      DataSource_Stochastic::Sequential,
      4);
  BOOST_TEST_MESSAGE("trainer: " << trainer.get_info());
  {
    Timer timer("Initializing FullyConnectedNetwork");
    fcnn->init(Layer::InitMode_Random, Layer::InitContext(12345));
    BOOST_TEST_MESSAGE(timer);
  }

  {
    Timer timer("Training");
    trainer.train(*fcnn, data_source, training_batch_size, epochs);

    BOOST_TEST_MESSAGE(timer);
  }
  BOOST_TEST_MESSAGE("After training:\n" << *fcnn);

  // testing
  VectorBatch res;
  for (MatrixSize ii = 0; ii < testing_batch_size; ii++) {
    VectorBatch in = get_batch(testing_inputs, ii); // copy
    VectorBatch out = get_batch(testing_outputs, ii); // copy
    res.resizeLike(out);

    fcnn->calculate(in, res);

    Value cost = fcnn->cost(res, out);
    BOOST_TEST_MESSAGE("test " << ii << " input: " << in);
    BOOST_TEST_MESSAGE("test " << ii << " expected ouput: " << out);
    BOOST_TEST_MESSAGE("test " << ii << " actual output: " << res);
    BOOST_TEST_MESSAGE("test " << ii << " cost/loss: " << cost);

    BOOST_CHECK_LE(cost, 0.01);
  }
}

BOOST_AUTO_TEST_CASE(Mnist_Sigmoid_CrossEntropy_Test)
{
  // reduce the test size to two labels to make it faster
  BOOST_TEST_MESSAGE("*** Filtering test set...");
  _mnist_test.filter(1, 2000, 1000); // only allow 0,1 images; 1000 count
  BOOST_TEST_MESSAGE("*** Filtered test set: " << "\n" << _mnist_test);

  auto res = test_fcnn_mnist(
      make_unique<Updater_GradientDescent>(2.0, 0.0),
      DataSource_Stochastic::Sequential, // reduce test variability
      10,   // batch size
      {20},
      make_unique<CrossEntropyCost>(),
      Sigmoid,
      3);
  BOOST_CHECK_GE(res.first, 0.995);  // > 99.5%
  BOOST_CHECK_LE(res.second, 0.05); // < 0.4 per test
}

BOOST_AUTO_TEST_CASE(Mnist_StochasticGD_Quadratic_Sigmoid_Test)
{
  auto res = test_fcnn_mnist(
      make_unique<Updater_GradientDescent>(3.0, 0.0),
      DataSource_Stochastic::Sequential,
      10, // batch size
      {20},
      make_unique<QuadraticCost>(),
      Sigmoid,
      3 // epochs
  );
  BOOST_CHECK_GE(res.first, 0.90); // > 90%
  BOOST_CHECK_LE(res.second, 0.20);// < 0.2 per test
}

BOOST_AUTO_TEST_CASE(Mnist_HellingerDistance_Softmax_Test)
{
  // reduce the test size to two labels to make it faster
  BOOST_TEST_MESSAGE("*** Filtering test set...");
  _mnist_test.filter(1, 2000, 1000); // only allow 0,1 images; 1000 count
  BOOST_TEST_MESSAGE("*** Filtered test set: " << "\n" << _mnist_test);

  auto res = test_fcnn_mnist(
      make_unique<Updater_GradientDescent>(5.0, 0.0),
      DataSource_Stochastic::Sequential, // reduce tests variability
      10, // batch size
      {20},
      make_unique<HellingerDistanceCost>(0.0001),
      Softmax,
      5 // epochs
  );
  BOOST_CHECK_GE(res.first, 0.98); // > 98%
  BOOST_CHECK_LE(res.second, 0.40);// < 0.4 per test
}

BOOST_AUTO_TEST_CASE(Mnist_StochasticGD_Quadratic_Sigmoid_Regularization_Test)
{
  auto res = test_fcnn_mnist(
      make_unique<Updater_GradientDescent>(3.0, 0.0005),
      DataSource_Stochastic::Sequential, // reduce tests variability
      10, // batch size
      {20},
      make_unique<QuadraticCost>(),
      Sigmoid,
      3);
  BOOST_CHECK_GE(res.first, 0.85); // > 85%
  BOOST_CHECK_LE(res.second, 0.25);// < 0.25 per test
}

BOOST_AUTO_TEST_CASE(Mnist_StochasticGDWithMomentum_CrossEntropy_Sigmoid_Regularization_Test)
{
  const double alpha = 0.5;
  const double beta = 0.9;
  auto res = test_fcnn_mnist(
      make_unique<Updater_GradientDescentWithMomentum>(alpha, beta),
      DataSource_Stochastic::Sequential, // reduce tests variability
      10, // batch size
      {20},
      make_unique<CrossEntropyCost>(),
      Sigmoid,
      3);
  BOOST_CHECK_GE(res.first, 0.90);// > 90%
  BOOST_CHECK_LE(res.second, 0.50);// < 0.5 per test
}

BOOST_AUTO_TEST_CASE(Mnist_FindBest_StochasticGD_Quadratic_Sigmoid_Test, * disabled())
{
  double res = test_fcnn_mnist_find_best(
      make_unique<Updater_GradientDescent>(3.0, 0.0),
      DataSource_Stochastic::Sequential, // reduce tests variability
      10, // batch size
      {0, 2, 1}, {10, 20, 5},
      make_unique<QuadraticCost>(),
      Sigmoid,
      2);
  BOOST_CHECK_GE(res, 0.90); // > 90%
}

BOOST_AUTO_TEST_CASE(Mnist_FindBest_StochasticGD_CrossEntropy_Sigmoid_Test, * disabled())
{
  double res = test_fcnn_mnist_find_best(
      make_unique<Updater_GradientDescent>(3.0, 0.0),
      DataSource_Stochastic::Sequential, // reduce tests variability
      10, // batch size
      {0, 2, 1}, {10, 20, 5},
      make_unique<CrossEntropyCost>(),
      Sigmoid,
      2);
  BOOST_CHECK_GE(res, 0.90); // > 90%
}

// The best result so far:
//
// Success rate for epoch 10: against training dataset: 99.9983% against test dataset: 97.88%
// Cost per test for epoch 10: against training dataset: 0.00392456 against test dataset: 0.332359
//
BOOST_AUTO_TEST_CASE(Mnist_LargeFCNN_Test, * disabled())
{
  const vector<MatrixSize> hidden_layers = { 1000 };
  enum ActivationMode activ_mode = Sigmoid;
  unique_ptr<CostFunction> cost_func = make_unique<CrossEntropyCost>();
  const size_t training_batch_size = 10;
  const size_t testing_batch_size = 100;
  // const double alpha = 1.0;
  // const double beta = 0.9;
  // const size_t search_epochs = 5;
  const size_t epochs = 10;

  // Network
  auto fcnn = create_fcnn_for_mnist(
      hidden_layers,
      cost_func,
      activ_mode);
  YANN_CHECK(fcnn);

  // auto layer = dynamic_cast<FullyConnectedLayer*>(fcnn->get_layer(0));
  // YANN_CHECK(layer);
  // layer->set_sampling_rate(0.01); // 1%
  fcnn->init(Layer::InitMode_Random, Layer::InitContext(12345));

  // Trainer
  unique_ptr<Layer::Updater> updater = make_unique<Updater_AdaDelta>();
      // make_unique<Updater_AdaGrad>();
      // make_unique<Updater_GradientDescentWithMomentum>(alpha, beta, search_epochs);

  Trainer trainer(updater);
  trainer.set_batch_progress_callback(batch_progress_callback);

  // Run test
  BOOST_TEST_MESSAGE("*** Training against MNIST dataset with ");
  BOOST_TEST_MESSAGE(" fcnn: " << fcnn->get_info());
  BOOST_TEST_MESSAGE(" trainer: " << trainer.get_info());
  BOOST_TEST_MESSAGE(" epochs: " << epochs);
  auto res = _mnist_test.train_and_test(
      *fcnn,
      trainer,
      DataSource_Stochastic::Random,
      training_batch_size,
      epochs,
      testing_batch_size);

  BOOST_TEST_MESSAGE("*** Success rate: " << (res.first * 100)
                     << "% Loss: " << res.second
                     << " after " << epochs << " epochs");
}

BOOST_AUTO_TEST_SUITE_END()


/*
Copyright 2015 Glen Joseph Fernandes
(glenjofe@gmail.com)

Distributed under the Boost Software License, Version 1.0.
(http://www.boost.org/LICENSE_1_0.txt)
*/
#ifndef BOOST_ALIGN_ALIGN_DOWN_HPP
#define BOOST_ALIGN_ALIGN_DOWN_HPP

#include <boost/align/detail/align_down.hpp>

namespace boost {
namespace alignment {

BOOST_CONSTEXPR inline std::size_t
align_down(std::size_t value, std::size_t alignment) BOOST_NOEXCEPT
{
    return value & ~(alignment - 1);
}

} /* alignment */
} /* boost */

#endif


//=======================================================================
// Copyright 2001 Jeremy G. Siek, Andrew Lumsdaine, Lie-Quan Lee,
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>

using namespace boost;

template < typename Graph > void
generic_foo(Graph & g)
{
  // Access descriptor types
  typedef typename graph_traits < Graph >::vertex_descriptor Vertex;
  typedef typename graph_traits < Graph >::edge_descriptor Edge;
  // Access category types
  typedef typename graph_traits < Graph >::directed_category Dir;
  typedef typename graph_traits < Graph >::edge_parallel_category Par;
  // Access iterator types...
  // Access size types...
  // Now do something useful...
}

template < typename Graph > void
generic_bar(Graph & g)
{
  // Declare some vertex and edge descriptor variables
  typename graph_traits < Graph >::vertex_descriptor u, v;
  typename graph_traits < Graph >::edge_descriptor e1, e2;
  // Set u and e1 to valid descriptors...
  v = u;                        // Make v a handle to the same vertex as u.
  e2 = e1;                      // Make e2 a handle to the same edge as e1.
  assert(u == v);               // Do u and v identify the same vertex? Yes
  assert(!(u != v));            // Do u and v identify different vertices? No
  assert(e1 == e2);             // Do e1 and e2 identify the same edge? Yes
  assert(!(e1 != e2));          // Do e1 and e2 identify different edges? No
}

// This version of foo gets called when g is directed
template < typename Graph > void
foo_dispatch(Graph & g, boost::directed_tag)
{
  //...
}

// This version of foo gets called when g is undirected
template < typename Graph > void
foo_dispatch(Graph & g, boost::undirected_tag)
{
  //...
}

template < typename Graph > void
foo(Graph & g)
{
  typedef typename boost::graph_traits < Graph >::directed_category Cat;
  foo_dispatch(g, Cat());
}

template < typename Digraph > void
foo(Digraph & digraph,
    typename graph_traits < Digraph >::vertex_descriptor u,
    typename graph_traits < Digraph >::vertex_descriptor v)
{
  typedef typename graph_traits < Digraph >::edge_descriptor edge_t;
  std::pair<edge_t, bool> e1, e2;
  e1 = edge(u, v, digraph);
  e2 = edge(v, u, digraph);
  assert(e1.first != e2.first);
}
template < typename Undigraph > void
bar(Undigraph & undigraph,
    typename graph_traits < Undigraph >::vertex_descriptor u,
    typename graph_traits < Undigraph >::vertex_descriptor v)
{
  typedef typename graph_traits < Undigraph >::edge_descriptor edge_t;
  std::pair<edge_t, bool> e1, e2;
  e1 = edge(u, v, undigraph);
  e2 = edge(v, u, undigraph);
  assert(e1.first == e2.first);
}

int
main()
{

  boost::adjacency_list < vecS, vecS, directedS > g(2);
  add_edge(0, 1, g);
  add_edge(1, 0, g);
  generic_foo(g);
  generic_bar(g);
  foo(g);
  foo(g, vertex(0, g), vertex(1, g));

  boost::adjacency_list < vecS, vecS, undirectedS > ug(2);
  add_edge(0, 1, g);
  bar(ug, vertex(0, g), vertex(1, g));

  return 0;
}


#pragma once

#include <boost/algorithm/string.hpp>

#include <parse.hpp>

// Always do the simplest test case first, and then move up from there
BOOST_AUTO_TEST_CASE(test_gradient_bound) {

    const std::vector<std::pair<std::string, Coeff64>> input = {
        {"trig(0)", 0},
        {"trig(x)", 1},
        {"trig(y)", 1},
        {"-2trig(2x)", 4},
        {"2trig(3x-5y)-8trig(5x+6y)", 104},
    };

    for (const auto& p : input) {

        const auto string_sin = boost::replace_all_copy(p.first, "trig", "sin");
        const auto expr_sin = parse_lin_com_map_sin_xy(string_sin);

        const auto sin_bound = gradient_bounds(expr_sin);

        BOOST_TEST(sin_bound.first + sin_bound.second == p.second);

        const auto string_cos = boost::replace_all_copy(p.first, "trig", "cos");
        const auto expr_cos = parse_lin_com_map_cos_xy(string_cos);

        const auto cos_bound = gradient_bounds(expr_cos);

        BOOST_TEST(cos_bound.first + cos_bound.second == p.second);
    }
}