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

#ifndef BOOST_HANA_TEST_LAWS_SEARCHABLE_HPP
#define BOOST_HANA_TEST_LAWS_SEARCHABLE_HPP

#include <boost/hana/all.hpp>
#include <boost/hana/all_of.hpp>
#include <boost/hana/any.hpp>
#include <boost/hana/any_of.hpp>
#include <boost/hana/assert.hpp>
#include <boost/hana/at_key.hpp>
#include <boost/hana/bool.hpp>
#include <boost/hana/concat.hpp>
#include <boost/hana/concept/comparable.hpp>
#include <boost/hana/concept/searchable.hpp>
#include <boost/hana/concept/sequence.hpp>
#include <boost/hana/contains.hpp>
#include <boost/hana/core/is_a.hpp>
#include <boost/hana/core/when.hpp>
#include <boost/hana/equal.hpp>
#include <boost/hana/find.hpp>
#include <boost/hana/find_if.hpp>
#include <boost/hana/for_each.hpp>
#include <boost/hana/functional/always.hpp>
#include <boost/hana/functional/capture.hpp>
#include <boost/hana/functional/compose.hpp>
#include <boost/hana/functional/partial.hpp>
#include <boost/hana/is_disjoint.hpp>
#include <boost/hana/is_subset.hpp>
#include <boost/hana/lazy.hpp>
#include <boost/hana/none.hpp>
#include <boost/hana/none_of.hpp>
#include <boost/hana/not.hpp>
#include <boost/hana/optional.hpp>
#include <boost/hana/transform.hpp>
#include <boost/hana/tuple.hpp>

#include <laws/base.hpp>
#include <support/numeric.hpp>

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

    template <typename S>
    struct TestSearchable<S, laws> {
        template <typename Searchables, typename Keys>
        TestSearchable(Searchables searchables, Keys keys) {
            hana::for_each(searchables, [](auto xs) {
                static_assert(Searchable<decltype(xs)>{}, "");
            });

            auto predicates = hana::concat(
                hana::to_tuple(hana::transform(keys, equal.to)),
                hana::make_tuple(
                    hana::always(false_c),
                    hana::always(true_c)
                )
            );

            hana::for_each(searchables, hana::capture(searchables, keys, predicates)(
            [](auto searchables, auto keys, auto predicates, auto xs) {
                hana::for_each(predicates, hana::capture(xs)(
                [](auto xs, auto p) {
                    // any_of(xs, p) <=> !all_of(xs, negated p)
                    //               <=> !none_of(xs, p)
                    BOOST_HANA_CHECK(
                        hana::any_of(xs, p) ^iff^
                        hana::not_(hana::all_of(xs, hana::compose(not_, p)))
                    );

                    BOOST_HANA_CHECK(
                        hana::any_of(xs, p) ^iff^
                        hana::not_(hana::none_of(xs, p))
                    );
                }));

                // any(xs)  <=> any_of(xs, id)
                // all(xs)  <=> all_of(xs, id)
                // none(xs) <=> none_of(xs, id)
                auto all_logicals = hana::all_of(xs, [](auto x) {
                    return hana::bool_c<hana::Logical<decltype(x)>::value>;
                });
                only_when_(all_logicals, hana::make_lazy([](auto xs) {
                    BOOST_HANA_CHECK(
                        hana::any(xs) ^iff^ hana::any_of(xs, id)
                    );

                    BOOST_HANA_CHECK(
                        hana::all(xs) ^iff^ hana::all_of(xs, hana::id)
                    );

                    BOOST_HANA_CHECK(
                        hana::none(xs) ^iff^ hana::none_of(xs, hana::id)
                    );
                })(xs));

                // find_if(xs, always(false_c)) == nothing
                BOOST_HANA_CONSTANT_CHECK(hana::equal(
                    hana::find_if(xs, hana::always(hana::false_c)),
                    hana::nothing
                ));

                hana::for_each(searchables, hana::capture(xs)([](auto xs, auto ys) {
                    // is_subset(xs, ys) <=> all_of(xs, [](auto x) { return contains(ys, x); })
                    BOOST_HANA_CHECK(
                        hana::is_subset(xs, ys) ^iff^
                            hana::all_of(xs, hana::partial(hana::contains, ys))
                    );

                    // is_disjoint(xs, ys) <=> none_of(xs, [](auto x) { return contains(ys, x); })
                    BOOST_HANA_CHECK(
                        hana::is_disjoint(xs, ys) ^iff^
                            hana::none_of(xs, hana::partial(hana::contains, ys))
                    );
                }));

                hana::for_each(keys, hana::capture(xs)([](auto xs, auto key) {
                    // find(xs, x) == find_if(xs, [](auto y) { return y == x; })
                    BOOST_HANA_CHECK(hana::equal(
                        hana::find(xs, key),
                        hana::find_if(xs, hana::equal.to(key))
                    ));

                    // contains(xs, x) <=> any_of(xs, [](auto y) { return y == x; })
                    BOOST_HANA_CHECK(
                        hana::contains(xs, key) ^iff^
                        hana::any_of(xs, hana::equal.to(key))
                    );

                    only_when_(hana::contains(xs, key),
                        hana::make_lazy([](auto xs, auto key) {
                            // at_key(xs, key) == find(xs, key).value()
                            BOOST_HANA_CHECK(hana::equal(
                                hana::at_key(xs, key),
                                hana::find(xs, key).value()
                            ));
                        })(xs, key)
                    );
                }));
            }));
        }
    };

    template <typename S>
    struct TestSearchable<S, when<Sequence<S>::value>>
        : TestSearchable<S, laws>
    {
        template <int i>
        using x = _constant<i>;

        template <int = 0>
        struct invalid { };

        struct undefined { };

        template <typename Xs, typename Keys>
        TestSearchable(Xs xs, Keys keys)
            : TestSearchable<S, laws>{xs, keys}
        {
            constexpr auto list = make<S>;

            BOOST_HANA_CONSTEXPR_LAMBDA auto is_even = [](auto x) {
                return x % 2 == 0;
            };

            auto c = numeric; // a minimal comparable
            auto logical = numeric;

            //////////////////////////////////////////////////////////////////
            // any_of
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(
                hana::not_(hana::any_of(list(), equal.to(x<9>{})))
            );

            BOOST_HANA_CONSTANT_CHECK(
                hana::not_(hana::any_of(list(x<0>{}), equal.to(x<9>{})))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}), equal.to(x<0>{}))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, invalid<1>{}), equal.to(x<0>{}))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, invalid<1>{}, invalid<2>{}), equal.to(x<0>{}))
            );

            BOOST_HANA_CONSTANT_CHECK(
                hana::not_(hana::any_of(list(x<0>{}, x<1>{}), equal.to(x<9>{})))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, x<1>{}), equal.to(x<1>{}))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, x<1>{}, invalid<2>{}), equal.to(x<1>{}))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, x<1>{}, invalid<2>{}, invalid<3>{}), equal.to(x<1>{}))
            );

            BOOST_HANA_CONSTANT_CHECK(
                hana::not_(hana::any_of(list(x<0>{}, x<1>{}, x<2>{}), equal.to(x<9>{})))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, x<1>{}, x<2>{}), equal.to(x<2>{}))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, x<1>{}, x<2>{}, nothing), equal.to(x<2>{}))
            );
            BOOST_HANA_CONSTANT_CHECK(
                hana::any_of(list(x<0>{}, x<1>{}, x<2>{}, nothing, nothing), equal.to(x<2>{}))
            );

            BOOST_HANA_CONSTANT_CHECK(
                hana::not_(hana::any_of(list(), invalid<>{}))
            );

            BOOST_HANA_CONSTEXPR_CHECK(
                hana::any_of(list(c(0)), equal.to(c(0)))
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                hana::not_(hana::any_of(list(c(0)), equal.to(c(1))))
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                hana::not_(hana::any_of(list(1), is_even))
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                hana::any_of(list(2), is_even)
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                hana::any_of(list(1, 2), is_even)
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                hana::not_(hana::any_of(list(1, 3), is_even))
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                hana::any_of(list(1, 3, 4), is_even)
            );

            //////////////////////////////////////////////////////////////////
            // any
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(hana::not_(hana::any(list())));
            BOOST_HANA_CONSTEXPR_CHECK(hana::any(list(logical(true))));
            BOOST_HANA_CONSTEXPR_CHECK(hana::not_(hana::any(list(logical(false)))));

            //////////////////////////////////////////////////////////////////
            // all_of
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(
                all_of(list(), invalid<>{})
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                all_of(list(c(0)), equal.to(c(0)))
            );
            BOOST_HANA_CONSTEXPR_CHECK(
                not_(all_of(list(c(0)), equal.to(c(1))))
            );
            BOOST_HANA_CONSTEXPR_CHECK(not_(
                all_of(list(c(0), c(1)), equal.to(c(0)))
            ));
            BOOST_HANA_CONSTEXPR_CHECK(
                all_of(list(c(0), c(0)), equal.to(c(0)))
            );

            BOOST_HANA_CONSTEXPR_CHECK(not_(all_of(list(1), is_even)));
            BOOST_HANA_CONSTEXPR_CHECK(all_of(list(2), is_even));
            BOOST_HANA_CONSTEXPR_CHECK(all_of(list(2, 4), is_even));
            BOOST_HANA_CONSTEXPR_CHECK(not_(all_of(list(1, 2), is_even)));
            BOOST_HANA_CONSTEXPR_CHECK(not_(all_of(list(1, 3), is_even)));
            BOOST_HANA_CONSTEXPR_CHECK(not_(all_of(list(1, 3, 4), is_even)));

            //////////////////////////////////////////////////////////////////
            // all
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(all(list()));
            BOOST_HANA_CONSTEXPR_CHECK(all(list(logical(true))));
            BOOST_HANA_CONSTEXPR_CHECK(not_(all(list(logical(false)))));
            BOOST_HANA_CONSTEXPR_CHECK(all(list(logical(true), logical(true))));
            BOOST_HANA_CONSTEXPR_CHECK(not_(all(list(logical(true), logical(false)))));

            //////////////////////////////////////////////////////////////////
            // none_of
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(none_of(list(), invalid<>{}));
            BOOST_HANA_CONSTEXPR_CHECK(none_of(list(c(0)), equal.to(c(1))));
            BOOST_HANA_CONSTEXPR_CHECK(not_(none_of(list(c(0)), equal.to(c(0)))));

            BOOST_HANA_CONSTEXPR_CHECK(none_of(list(1), is_even));
            BOOST_HANA_CONSTEXPR_CHECK(not_(none_of(list(2), is_even)));
            BOOST_HANA_CONSTEXPR_CHECK(not_(none_of(list(1, 2), is_even)));
            BOOST_HANA_CONSTEXPR_CHECK(none_of(list(1, 3), is_even));
            BOOST_HANA_CONSTEXPR_CHECK(not_(none_of(list(1, 3, 4), is_even)));

            //////////////////////////////////////////////////////////////////
            // none
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTANT_CHECK(none(list()));
            BOOST_HANA_CONSTEXPR_CHECK(none(list(logical(false))));
            BOOST_HANA_CONSTEXPR_CHECK(not_(none(list(logical(true)))));

            //////////////////////////////////////////////////////////////////
            // find_if
            //////////////////////////////////////////////////////////////////
            {
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(), equal.to(x<9>{})),
                    nothing
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}), equal.to(x<9>{})),
                    nothing
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}), equal.to(x<0>{})),
                    just(x<0>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, invalid<1>{}), equal.to(x<0>{})),
                    just(x<0>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, invalid<1>{}, invalid<2>{}), equal.to(x<0>{})),
                    just(x<0>{})
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}), equal.to(x<9>{})),
                    nothing
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}), equal.to(x<1>{})),
                    just(x<1>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}, invalid<2>{}), equal.to(x<1>{})),
                    just(x<1>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}, invalid<2>{}, invalid<3>{}), equal.to(x<1>{})),
                    just(x<1>{})
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}, x<2>{}), equal.to(x<9>{})),
                    nothing
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}, x<2>{}), equal.to(x<2>{})),
                    just(x<2>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}, x<2>{}, nothing), equal.to(x<2>{})),
                    just(x<2>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(list(x<0>{}, x<1>{}, x<2>{}, nothing, nothing), equal.to(x<2>{})),
                    just(x<2>{})
                ));

                // Make sure find_if works with an lvalue sequence. hana::tuple
                // used to have a bug that broke this.
                auto const const_lvalue = list(x<0>{}, x<1>{}, x<2>{});
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(const_lvalue, equal.to(x<1>{})),
                    just(x<1>{})
                ));

                auto lvalue = list(x<0>{}, x<1>{}, x<2>{});
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find_if(lvalue, equal.to(x<1>{})),
                    just(x<1>{})
                ));
            }

            //////////////////////////////////////////////////////////////////
            // find
            //////////////////////////////////////////////////////////////////
            {
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(), invalid<>{}),
                    nothing
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}), x<9>{}),
                    nothing
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}), x<0>{}),
                    just(x<0>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, invalid<1>{}), x<0>{}),
                    just(x<0>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, invalid<1>{}, invalid<2>{}), x<0>{}),
                    just(x<0>{})
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}), x<9>{}),
                    nothing
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}), x<1>{}),
                    just(x<1>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}, invalid<2>{}), x<1>{}),
                    just(x<1>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}, invalid<2>{}, invalid<3>{}), x<1>{}),
                    just(x<1>{})
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}, x<2>{}), x<9>{}),
                    nothing
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}, x<2>{}), x<2>{}),
                    just(x<2>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}, x<2>{}, nothing), x<2>{}),
                    just(x<2>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    find(list(x<0>{}, x<1>{}, x<2>{}, nothing, nothing), x<2>{}),
                    just(x<2>{})
                ));
            }

            //////////////////////////////////////////////////////////////////
            // at_key
            //////////////////////////////////////////////////////////////////
            {
                BOOST_HANA_CONSTANT_CHECK(equal(
                    at_key(list(x<0>{}), x<0>{}),
                    x<0>{}
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    at_key(list(x<0>{}, x<1>{}), x<0>{}),
                    x<0>{}
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    at_key(list(x<0>{}, x<1>{}), x<1>{}),
                    x<1>{}
                ));

                BOOST_HANA_CONSTANT_CHECK(equal(
                    at_key(list(x<0>{}, x<1>{}, x<2>{}), x<0>{}),
                    x<0>{}
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    at_key(list(x<0>{}, x<1>{}, x<2>{}), x<1>{}),
                    x<1>{}
                ));
                BOOST_HANA_CONSTANT_CHECK(equal(
                    at_key(list(x<0>{}, x<1>{}, x<2>{}), x<2>{}),
                    x<2>{}
                ));
            }

            //////////////////////////////////////////////////////////////////
            // contains
            //////////////////////////////////////////////////////////////////
            {
                BOOST_HANA_CONSTANT_CHECK(not_(
                    contains(list(), undefined{})
                ));

                BOOST_HANA_CONSTANT_CHECK(not_(
                    contains(list(x<0>{}), x<999>{})
                ));

                BOOST_HANA_CONSTANT_CHECK(
                    contains(list(x<0>{}), x<0>{})
                );

                BOOST_HANA_CONSTANT_CHECK(
                    contains(list(x<0>{}, x<1>{}), x<1>{})
                );

                BOOST_HANA_CONSTANT_CHECK(
                    contains(list(x<0>{}, x<1>{}, x<3>{}), x<3>{})
                );

                BOOST_HANA_CONSTEXPR_CHECK(contains(list(c(0)), c(0)));
                BOOST_HANA_CONSTEXPR_CHECK(not_(contains(list(c(0)), c(1))));

                // infix application
                BOOST_HANA_CONSTANT_CHECK(
                    list(x<0>{}, x<1>{}) ^contains^ x<1>{}
                );
            }

            //////////////////////////////////////////////////////////////////
            // in
            //////////////////////////////////////////////////////////////////
            BOOST_HANA_CONSTEXPR_CHECK(c(0) ^in^ list(c(0)));
            BOOST_HANA_CONSTEXPR_CHECK(not_(c(1) ^in^ list(c(0))));

            //////////////////////////////////////////////////////////////////
            // is_subset
            //////////////////////////////////////////////////////////////////
            {
                BOOST_HANA_CONSTANT_CHECK(is_subset(
                    list(), list(undefined{})
                ));

                BOOST_HANA_CONSTANT_CHECK(not_(is_subset(
                    list(undefined{}), list()
                )));

                BOOST_HANA_CONSTEXPR_CHECK(is_subset(
                    list(c(0)), list(c(0))
                ));

                BOOST_HANA_CONSTEXPR_CHECK(is_subset(
                    list(c(0)), list(c(0), c(1))
                ));

                BOOST_HANA_CONSTEXPR_CHECK(not_(is_subset(
                    list(c(0)), list(c(1))
                )));

                // infix application
                BOOST_HANA_CONSTANT_CHECK(
                    list(x<0>{}) ^is_subset^ list(x<1>{}, x<0>{})
                );

                BOOST_HANA_CONSTANT_CHECK(
                    list(x<0>{}, x<1>{}) ^is_subset^ list(x<1>{}, x<0>{})
                );
            }

            //////////////////////////////////////////////////////////////////
            // is_disjoint
            //////////////////////////////////////////////////////////////////
            {
                BOOST_HANA_CONSTANT_CHECK(is_disjoint(
                    list(),
                    list()
                ));

                BOOST_HANA_CONSTANT_CHECK(is_disjoint(
                    list(),
                    list(undefined{})
                ));
                BOOST_HANA_CONSTANT_CHECK(is_disjoint(
                    list(undefined{}),
                    list()
                ));

                BOOST_HANA_CONSTANT_CHECK(is_disjoint(
                    list(x<0>{}),
                    list(x<1>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(is_disjoint(
                    list(x<1>{}),
                    list(x<0>{})
                ));
                BOOST_HANA_CONSTANT_CHECK(not_(is_disjoint(
                    list(x<0>{}),
                    list(x<0>{})
                )));

                BOOST_HANA_CONSTANT_CHECK(not_(is_disjoint(
                    list(x<0>{}, x<1>{}),
                    list(x<0>{})
                )));
                BOOST_HANA_CONSTANT_CHECK(not_(is_disjoint(
                    list(x<0>{}),
                    list(x<0>{}, x<1>{})
                )));
                BOOST_HANA_CONSTANT_CHECK(not_(is_disjoint(
                    list(x<1>{}, x<0>{}),
                    list(x<0>{}, x<1>{})
                )));
                BOOST_HANA_CONSTANT_CHECK(not_(is_disjoint(
                    list(x<0>{}, x<1>{}),
                    list(x<1>{}, x<2>{})
                )));
                BOOST_HANA_CONSTANT_CHECK(is_disjoint(
                    list(x<0>{}, x<1>{}),
                    list(x<2>{}, x<3>{})
                ));
            }
        }
    };
}}} // end namespace boost::hana::test

#endif // !BOOST_HANA_TEST_LAWS_SEARCHABLE_HPP


/*-----------------------------------------------------------------------------+
Copyright (c) 2008-2009: Joachim Faulhaber
+------------------------------------------------------------------------------+
   Distributed under the Boost Software License, Version 1.0.
      (See accompanying file LICENCE.txt or copy at
           http://www.boost.org/LICENSE_1_0.txt)
+-----------------------------------------------------------------------------*/
#ifndef BOOST_ICL_TYPE_TRAITS_HAS_SET_SEMANTICS_HPP_JOFA_100829
#define BOOST_ICL_TYPE_TRAITS_HAS_SET_SEMANTICS_HPP_JOFA_100829

#include <boost/mpl/or.hpp>
#include <boost/mpl/and.hpp>
#include <boost/icl/type_traits/is_set.hpp>
#include <boost/icl/type_traits/is_map.hpp>
#include <boost/icl/type_traits/codomain_type_of.hpp>

namespace boost{ namespace icl
{
    template <class Type> struct has_set_semantics
    {
        typedef has_set_semantics<Type> type;
        BOOST_STATIC_CONSTANT(bool,
            value = (mpl::or_< is_set<Type>
                             , mpl::and_< is_map<Type>
                                        , has_set_semantics
                                          <typename codomain_type_of<Type>::type >
                                        >
                             >::value));
    };

}} // namespace boost icl

#endif


///////////////////////////////////////////////////////////////////////////////
//  Copyright Christopher Kormanyos 2021 - 2022.
//  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 <cstdint>
#include <ctime>
#include <random>
#include <sstream>
#include <string>

#include <boost/version.hpp>

#if !defined(BOOST_VERSION)
#error BOOST_VERSION is not defined. Ensure that <boost/version.hpp> is properly included.
#endif

#if ((BOOST_VERSION >= 107900) && !defined(BOOST_MP_STANDALONE))
#define BOOST_MP_STANDALONE
#endif

#if (BOOST_VERSION < 108000)
#if defined(__GNUC__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wsign-conversion"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
#endif

#if (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 12))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wrestrict"
#endif

#if (BOOST_VERSION < 108000)
#if (defined(__clang__) && (__clang_major__ > 9)) && !defined(__APPLE__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-copy"
#endif
#endif

#include <boost/multiprecision/cpp_int.hpp>

#include <math/wide_integer/uintwide_t.h>
#include <test/test_uintwide_t.h>

namespace local_float_convert
{
  auto engine_man() -> std::mt19937&                                                         { static std::mt19937                                                         my_engine_man; return my_engine_man; } // NOLINT(cert-msc32-c,cert-msc51-cpp)
  auto engine_sgn() -> std::ranlux24_base&                                                   { static std::ranlux24_base                                                   my_engine_sgn; return my_engine_sgn; } // NOLINT(cert-msc32-c,cert-msc51-cpp)
  auto engine_e10() -> std::linear_congruential_engine<std::uint32_t, 48271, 0, 2147483647>& { static std::linear_congruential_engine<std::uint32_t, 48271, 0, 2147483647> my_engine_e10; return my_engine_e10; } // NOLINT(cert-msc32-c,cert-msc51-cpp,cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)

  template<typename UnsignedIntegralType>
  auto unsigned_dist_maker(const UnsignedIntegralType lo,
                           const UnsignedIntegralType hi) -> std::uniform_int_distribution<UnsignedIntegralType>
  {
    return std::uniform_int_distribution<UnsignedIntegralType>(lo, hi);
  };

  template<typename FloatingPointType,
           const std::int32_t LoExp10,
           const std::int32_t HiExp10>
  auto get_random_float() -> FloatingPointType
  {
    using local_builtin_float_type = FloatingPointType;

    static std::uniform_real_distribution<local_builtin_float_type>
    dist_man
    (
      local_builtin_float_type(0.0F),
      local_builtin_float_type(1.0F)
    );

    static auto dist_sgn = unsigned_dist_maker(static_cast<unsigned>(UINT8_C(0)),
                                               static_cast<unsigned>(UINT8_C(1)));

    static auto dist_e10 = unsigned_dist_maker(LoExp10, HiExp10);

    using std::pow;

    const auto p10 = dist_e10(engine_e10());

    const local_builtin_float_type e10 = pow(local_builtin_float_type(10.0F),
                                             local_builtin_float_type(p10));

    const local_builtin_float_type a = dist_man(engine_man()) * e10;

    const bool is_neg = (dist_sgn(engine_sgn()) != 0);

    const local_builtin_float_type f = ((!is_neg) ? a : -a);

    return f;
  }

  template<const std::size_t MaxDigitsToGet,
           const std::size_t MinDigitsToGet = 2U>
  auto get_random_digit_string(std::string& str) -> void // NOLINT(google-runtime-references)
  {
    static_assert(MinDigitsToGet >=  2U, "Error: The minimum number of digits to get must be  2 or more");

    static auto dist_sgn = unsigned_dist_maker(static_cast<unsigned>(UINT8_C(0)),
                                               static_cast<unsigned>(UINT8_C(1)));

    static auto dist_len = unsigned_dist_maker(MinDigitsToGet, MaxDigitsToGet);

    static auto dist_first = unsigned_dist_maker(static_cast<unsigned>(UINT8_C(1)),
                                                 static_cast<unsigned>(UINT8_C(9)));

    static auto dist_following = unsigned_dist_maker(static_cast<unsigned>(UINT8_C(0)),
                                                     static_cast<unsigned>(UINT8_C(9)));

    const bool is_neg = (dist_sgn(engine_sgn()) != 0);

    const auto len = dist_len(engine_e10());

    auto pos = static_cast<std::string::size_type>(0U);

    if(is_neg)
    {
      str.resize(len + 1U);

      str.at(pos) = '-';

      ++pos;
    }
    else
    {
      str.resize(len);
    }

    str.at(pos) =
      static_cast<char>
      (
          dist_first(engine_man())
        + static_cast<std::uniform_int_distribution<unsigned>::result_type>(UINT32_C(0x30))
      );

    ++pos;

    while(pos < str.length())
    {
      str.at(pos) =
        static_cast<char>
        (
            dist_following(engine_man())
          + static_cast<std::uniform_int_distribution<unsigned>::result_type>(UINT32_C(0x30))
        );

      ++pos;
    }
  }

  template<typename UnsignedIntegralType>
  static auto hexlexical_cast(const UnsignedIntegralType& u) -> std::string
  {
    std::stringstream ss;

    ss << std::hex << u;

    return ss.str();
  }
} // namespace local_float_convert

#if defined(WIDE_INTEGER_NAMESPACE)
auto WIDE_INTEGER_NAMESPACE::math::wide_integer::test_uintwide_t_float_convert() -> bool
#else
auto math::wide_integer::test_uintwide_t_float_convert() -> bool
#endif
{
  constexpr auto digits2 = static_cast<unsigned>(256U);

  using boost_uint_backend_type =
    boost::multiprecision::cpp_int_backend<digits2,
                                           digits2,
                                           boost::multiprecision::unsigned_magnitude>;
  using boost_sint_backend_type =
    boost::multiprecision::cpp_int_backend<digits2,
                                           digits2,
                                           boost::multiprecision::signed_magnitude>;

  using boost_uint_type = boost::multiprecision::number<boost_uint_backend_type, boost::multiprecision::et_on>;
  using boost_sint_type = boost::multiprecision::number<boost_sint_backend_type, boost::multiprecision::et_on>;

  #if defined(WIDE_INTEGER_HAS_LIMB_TYPE_UINT64)
  using local_limb_type = std::uint64_t;
  #else
  using local_limb_type = std::uint32_t;
  #endif

  using local_uint_type = math::wide_integer::uintwide_t<digits2, local_limb_type, void>;
  using local_sint_type = math::wide_integer::uintwide_t<digits2, local_limb_type, void, true>;

  local_float_convert::engine_man().seed(static_cast<typename std::mt19937::result_type>                                                        (std::clock()));
  local_float_convert::engine_sgn().seed(static_cast<typename std::ranlux24_base::result_type>                                                  (std::clock()));
  local_float_convert::engine_e10().seed(static_cast<typename std::linear_congruential_engine<std::uint32_t, 48271, 0, 2147483647>::result_type>(std::clock())); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)

  bool result_is_ok = true;

  for(auto i = static_cast<std::size_t>(0U); i < static_cast<std::size_t>(UINT32_C(0x80000)); ++i)
  {
    const auto f = local_float_convert::get_random_float<float, -1, 27>();

    auto n_boost = static_cast<boost_sint_type>(f);
    auto n_local = static_cast<local_sint_type>(f);

    const std::string str_boost_signed = local_float_convert::hexlexical_cast(static_cast<boost_uint_type>(n_boost));
    const std::string str_local_signed = local_float_convert::hexlexical_cast(static_cast<local_uint_type>(n_local));

    result_is_ok = ((str_boost_signed == str_local_signed) && result_is_ok);
  }

  for(auto i = static_cast<std::size_t>(0U); i < static_cast<std::size_t>(UINT32_C(0x80000)); ++i)
  {
    const auto d = local_float_convert::get_random_float<double, -1, 75>();

    auto n_boost = static_cast<boost_sint_type>(d);
    auto n_local = static_cast<local_sint_type>(d);

    const std::string str_boost_signed = local_float_convert::hexlexical_cast(static_cast<boost_uint_type>(n_boost));
    const std::string str_local_signed = local_float_convert::hexlexical_cast(static_cast<local_uint_type>(n_local));

    result_is_ok = ((str_boost_signed == str_local_signed) && result_is_ok);
  }

  local_float_convert::engine_man().seed(static_cast<typename std::mt19937::result_type>                                                        (std::clock()));
  local_float_convert::engine_sgn().seed(static_cast<typename std::ranlux24_base::result_type>                                                  (std::clock()));
  local_float_convert::engine_e10().seed(static_cast<typename std::linear_congruential_engine<std::uint32_t, 48271, 0, 2147483647>::result_type>(std::clock())); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)

  for(auto i = static_cast<std::size_t>(0U); i < static_cast<std::size_t>(UINT32_C(0x100000)); ++i)
  {
    std::string str_digits;

    local_float_convert::get_random_digit_string<31U>(str_digits); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)

    const auto n_boost = boost_sint_type(str_digits.c_str());
    const auto n_local = local_sint_type(str_digits.c_str());

    const auto f_boost = static_cast<float>(n_boost);
    const auto f_local = static_cast<float>(n_local);

    using std::fabs;

    constexpr auto cast_tol_float = float(std::numeric_limits<float>::epsilon() * 2.0F);

    const float closeness      = fabs(1.0F - fabs(f_boost / f_local));
    const bool  result_f_is_ok = (closeness < cast_tol_float);

    result_is_ok = (result_f_is_ok && result_is_ok);
  }

  for(auto i = static_cast<std::size_t>(0U); i < static_cast<std::size_t>(UINT32_C(0x40000)); ++i)
  {
    std::string str_digits;

    local_float_convert::get_random_digit_string<71U>(str_digits); // NOLINT(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)

    const auto n_boost = boost_sint_type(str_digits.c_str());
    const auto n_local = local_sint_type(str_digits.c_str());

    const auto d_boost = static_cast<double>(n_boost);
    const auto d_local = static_cast<double>(n_local);

    using std::fabs;

    constexpr auto cast_tol_double = static_cast<double>(std::numeric_limits<double>::epsilon() * 2.0);

    const double closeness      = fabs(1.0 - fabs(d_boost / d_local));
    const bool   result_f_is_ok = (closeness < cast_tol_double);

    result_is_ok = (result_f_is_ok && result_is_ok);
  }

  return result_is_ok;
}

#if (BOOST_VERSION < 108000)
#if (defined(__clang__) && (__clang_major__ > 9)) && !defined(__APPLE__)
#pragma GCC diagnostic pop
#endif
#endif

#if (defined(__GNUC__) && !defined(__clang__) && (__GNUC__ >= 12))
#pragma GCC diagnostic pop
#endif

#if (BOOST_VERSION < 108000)
#if defined(__GNUC__)
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#pragma GCC diagnostic pop
#endif
#endif


/**
 * @file dataloader.hpp
 * @author Kartik Dutt
 *
 * Definition of Dataloader for popular datasets.
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#include <mlpack/core/data/scaler_methods/min_max_scaler.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <mlpack/core/math/shuffle_data.hpp>
#include <mlpack/core/data/split_data.hpp>
#include <boost/property_tree/ptree.hpp>
#include <augmentation/augmentation.hpp>
#include <dataloader/datasets.hpp>
#include <mlpack/prereqs.hpp>
#include <boost/foreach.hpp>
#include <mlpack/core.hpp>
#include <utils/utils.hpp>
#include <set>

#ifndef MODELS_DATALOADER_HPP
#define MODELS_DATALOADER_HPP

/**
 * Dataloader class to load popular datasets.
 *
 * @code
 * // Create a dataloader for any popular dataset.
 * // Set parameters for dataset.
 * const string datasetName = "mnist";
 * bool shuffleData = true;
 * double ratioForTrainTestSplit = 0.75;
 *
 * // Create the DataLoader object.
 * DataLoader<> dataloader(datasetName, shuffleData,
 *    ratioForTrainTestSplit);
 *
 * // Use the dataloader for training.
 * model.Train(dataloader.TrainFeatures(), dataloader.TrainLabels());
 *
 * // Use the dataloader for prediction.
 * model.Predict(dataloader.TestFeatures(), dataloader.TestLabels());
 * @endcode
 *
 * @tparam DatasetX Datatype for loading input features.
 * @tparam DatasetY Datatype for prediction features.
 * @tparam ScalerType mlpack's Scaler Object for scaling features.
 */
template<
  typename DatasetX = arma::mat,
  typename DatasetY = arma::mat,
  class ScalerType = mlpack::data::MinMaxScaler
>
class DataLoader
{
 public:
  //! Create DataLoader object.
  DataLoader();

  /**
   * Constructor for DataLoader. This is used for loading popular Datasets such as
   * MNIST, ImageNet, Pascal VOC and many more.
   *
   * @param datasetPath Path or name of dataset.
   * @param shuffle whether or not to shuffle the data.
   * @param validRatio Ratio of dataset to be used for validation set.
   * @param useScaler Use feature scaler for pre-processing the dataset.
   * @param augmentation Adds augmentation to training data only.
   * @param augmentationProbability Probability of applying augmentation on dataset.
   */
  DataLoader(const std::string& dataset,
             const bool shuffle,
             const double validRatio = 0.25,
             const bool useScaler = true,
             const std::vector<std::string> augmentation =
                 std::vector<std::string>(),
             const double augmentationProbability = 0.2);

  /**
   * Function to load and preprocess train or test data stored in CSV files.
   *
   * @param datasetPath Path to the dataset.
   * @param loadTrainData Boolean to determine whether data will be stored for
   *                      training or testing. If true, data will be loaded for training.
   *                      Note: This option augmentation to NULL, set ratio to 1 and
   *                      scaler will be used to only transform the test data.
   * @param shuffle Boolean to determine whether or not to shuffle the data.
   * @param validRatio Ratio of dataset to be used for validation set.
   * @param useScaler Fits the scaler on training data and transforms dataset.
   * @param dropHeader Drops the first row from CSV.
   * @param startInputFeatures First Index which will be fed into the model as input.
   *                           Note: Indicies are wrapped and -1 implies last
   *                           column.
   * @param endInputFeature Last Index which will be fed into the model as input.
   *                        Note: Indicies are wrapped and -1 implies last
   *                        column.
   * @param startPredictionFeatures First Index which be predicted by the model as output.
   *                                Note: Indicies are wrapped and -1 implies last
   *                                column.
   * @param endPredictionFeatures Last Index which be predicted by the model as output.
   *                              Note: Indicies are wrapped and -1 implies last
   *                              column.
   * @param augmentation Vector strings of augmentations supported by mlpack.
   * @param augmentationProbability Probability of applying augmentation to a particular cell.
   */
  void LoadCSV(const std::string& datasetPath,
               const bool loadTrainData = true,
               const bool shuffle = true,
               const double validRatio = 0.25,
               const bool useScaler = false,
               const bool dropHeader = false,
               const int startInputFeatures = -1,
               const int endInputFeatures = -1,
               const int startPredictionFeatures = -1,
               const int endPredictionFeatures = -1,
               const std::vector<std::string> augmentation =
                   std::vector<std::string>(),
               const double augmentationProbability = 0.2);

  /**
   * Loads object detection dataset. It requires a single annotation file in XML format.
   * Each XML file should correspond to a single image in images folder.
   *
   * XML file should containg the following :
   * 1. Each XML file should be wrapped in annotation tag.
   * 2. Filename of image in images folder will be depicted by filename tag.
   * 3. Object tag depicting characteristics of bounding box.
   * 4. Each object tag should contain name tag i.e. class of the object.
   * 5. Each object tag should contain bndbox tag containing xmin, ymin, xmax, ymax.
   *
   * NOTE : Labels are assigned using classes vector. Set verbose to 1 to print labels
   * and their corresponding class. The labels type should be field type here.
   *
   * @param pathToAnnotations Path to the folder containing XML type annotation files.
   * @param pathToImages Path to folder containing images corresponding to annotations.
   * @param classes Vector of strings containing list of classes. Labels are assigned
   *                according to this vector.
   * @param validRatio Ratio of dataset that will be used for validation.
   * @param shuffle Boolean to determine whether the dataset is shuffled.
   * @param augmentation Vector strings of augmentations supported by mlpack.
   * @param augmentationProbability Probability of applying augmentation
   *                                to a particular image.
   * @param absolutePath Boolean to determine if absolute path is used. Defaults to false.
   * @param baseXMLTag XML tag name which wraps around the annotation file.
   * @param imageNameXMLTag XML tag name which holds the value of image filename.
   * @param objectXMLTag XML tag name which holds details of bounding box i.e. class and
   *                     coordinates of bounding box.
   * @param bndboxXMLTag XML tag name which holds coordinates of bounding box.
   * @param classNameXMLTag XML tag name inside objectXMLTag which holds the name of the
   *                        class of bounding box.
   * @param x1XMLTag XML tag name inside bndboxXMLTag which hold value of lower most
   *                 x coordinate of bounding box.
   * @param y1XMLTag XML tag name inside bndboxXMLTag which hold value of lower most
   *                 y coordinate of bounding box.
   * @param x2XMLTag XML tag name inside bndboxXMLTag which hold value of upper most
   *                 x coordinate of bounding box.
   * @param y2XMLTag XML tag name inside bndboxXMLTag which hold value of upper most
   *                 y coordinate of bounding box.
   */
  void LoadObjectDetectionDataset(const std::string& pathToAnnotations,
                                  const std::string& pathToImages,
                                  const std::vector<std::string>& classes,
                                  const double validRatio = 0.2,
                                  const bool shuffle = true,
                                  const std::vector<std::string>& augmentation =
                                      std::vector<std::string>(),
                                  const double augmentationProbability = 0.2,
                                  const bool absolutePath = false,
                                  const std::string& baseXMLTag = "annotation",
                                  const std::string& imageNameXMLTag =
                                      "filename",
                                  const std::string& sizeXMLTag = "size",
                                  const std::string& objectXMLTag = "object",
                                  const std::string& bndboxXMLTag = "bndbox",
                                  const std::string& classNameXMLTag = "name",
                                  const std::string& x1XMLTag = "xmin",
                                  const std::string& y1XMLTag = "ymin",
                                  const std::string& x2XMLTag = "xmax",
                                  const std::string& y2XMLTag = "ymax");

  /**
   * Load all images from directory.
   *
   * @param imagesPath Path to all images.
   * @param dataset Armadillo type where images will be loaded.
   * @param labels Armadillo type where labels will be loaded.
   * @param imageWidth Width of images in dataset.
   * @param imageHeight Height of images in dataset.
   * @param imageDepth Depth of images in dataset.
   * @param label Label which will be assigned to image.
   * @param augmentation Vector strings of augmentations supported by mlpack.
   * @param augmentationProbability Probability of applying augmentation
   *                                to a particular image.
   */
  void LoadAllImagesFromDirectory(const std::string& imagesPath,
                                  DatasetX& dataset,
                                  DatasetY& labels,
                                  const size_t imageWidth,
                                  const size_t imageHeight,
                                  const size_t imageDepth,
                                  const size_t label = 0);

  /**
   * Load all images from directory.
   *
   * @param pathToDataset Path to all folders containing all images.
   * @param imageWidth Width of images in dataset.
   * @param imageHeight Height of images in dataset.
   * @param imageDepth Depth of images in dataset.
   * @param trainData Determines whether data is training set or test set.
   * @param shuffle Boolean to determine whether or not to shuffle the data.
   * @param validRatio Ratio of dataset to be used for validation set.
   * @param augmentation Vector strings of augmentations supported by mlpack.
   * @param augmentationProbability Probability of applying augmentation
   *                                to a particular image.
   */
  void LoadImageDatasetFromDirectory(const std::string& pathToDataset,
                                     const size_t imageWidth,
                                     const size_t imageHeight,
                                     const size_t imageDepth,
                                     const bool trainData = true,
                                     const double validRatio = 0.2,
                                     const bool shuffle = true,
                                     const std::vector<std::string>&
                                      augmentation = std::vector<std::string>(),
                                     const double augmentationProbability =
                                        0.2);

  //! Get the training dataset features.
  DatasetX TrainFeatures() const { return trainFeatures; }

  //! Modify the training dataset features.
  DatasetX& TrainFeatures() { return trainFeatures; }

  //! Get the training dataset labels.
  DatasetY TrainLabels() const { return trainLabels; }
  //! Modify the training dataset labels.
  DatasetY& TrainLabels() { return trainLabels; }

  //! Get the test dataset features.
  DatasetX TestFeatures() const { return testFeatures; }
  //! Modify the test dataset features.
  DatasetX& TestFeatures() { return testFeatures; }

  //! Get the test dataset labels.
  DatasetY TestLabels() const { return testLabels; }
  //! Modify the test dataset labels.
  DatasetY& TestLabels() { return testLabels; }

  //! Get the validation dataset features.
  DatasetX ValidFeatures() const { return validFeatures; }
  //! Modify the validation dataset features.
  DatasetX& ValidFeatures() { return validFeatures; }

  //! Get the validation dataset labels.
  DatasetY ValidLabels() const { return validLabels; }
  //! Modify the validation dataset labels.
  DatasetY& ValidLabels() { return validLabels; }

  //! Get the training dataset.
  std::tuple<DatasetX, DatasetY> TrainSet() const
  {
    return std::tuple<DatasetX, DatasetY>(trainFeatures, trainLabels);
  }

  //! Get the validation dataset.
  std::tuple<DatasetX, DatasetY> ValidSet() const
  {
    return std::tuple<DatasetX, DatasetY>(validFeatures, validLabels);
  }

  //! Get the testing dataset.
  std::tuple<DatasetX, DatasetY> TestSet() const
  {
    return std::tuple<DatasetX, DatasetY>(testFeatures, testLabels);
  }

  //! Get the Scaler.
  ScalerType Scaler() const { return scaler; }
  //! Modify the Scaler.
  ScalerType& Scaler() { return scaler; }

 private:
  /**
   * Downloads and checks hash for given dataset.
   *
   * @param dataset Name of the data set which will be downloaded.
   */
  void DownloadDataset(const std::string& dataset)
  {
    if (datasetMap[dataset].zipFile && (!Utils::PathExists(
        datasetMap[dataset].trainPath) ||
        !Utils::PathExists(datasetMap[dataset].testPath) ||
        !Utils::PathExists(datasetMap[dataset].trainingImagesPath) ||
        !Utils::PathExists(datasetMap[dataset].trainingAnnotationPath) ||
        !Utils::PathExists(datasetMap[dataset].testingImagesPath)))
    {
      Utils::DownloadFile(datasetMap[dataset].datasetURL,
          datasetMap[dataset].datasetPath, dataset + "_training_data.",
          false, false, datasetMap[dataset].serverName,
          datasetMap[dataset].zipFile);

      if (!Utils::CompareCRC32(datasetMap[dataset].datasetPath,
          datasetMap[dataset].datasetHash))
      {
        mlpack::Log::Fatal << "Corrupted Data for " << dataset <<
            " downloaded." << std::endl;
      }

      return;
    }

    if (!Utils::PathExists(datasetMap[dataset].trainPath))
    {
      Utils::DownloadFile(datasetMap[dataset].trainDownloadURL,
          datasetMap[dataset].trainPath, dataset + "_training_data.",
          false, false, datasetMap[dataset].serverName);

      if (!Utils::CompareCRC32(datasetMap[dataset].trainPath,
          datasetMap[dataset].trainHash))
      {
        mlpack::Log::Fatal << "Corrupted Training Data for " <<
            dataset << " downloaded." << std::endl;
      }
    }

    if (!Utils::PathExists(datasetMap[dataset].testPath))
    {
      Utils::DownloadFile(datasetMap[dataset].trainDownloadURL,
          datasetMap[dataset].testPath, dataset + "_testing_data.",
          false, false, datasetMap[dataset].serverName);

      if (!Utils::CompareCRC32(datasetMap[dataset].testPath,
          datasetMap[dataset].testHash))
        {
          mlpack::Log::Fatal << "Corrupted Testing Data for " <<
            dataset << " downloaded." << std::endl;
        }
    }
  }

  /**
   * Intializes dataset map to provide access to dataset details.
   */
  void InitializeDatasets()
  {
    datasetMap.insert({"mnist", Datasets<DatasetX, DatasetY>::MNIST()});
    datasetMap.insert({"voc-detection",
        Datasets<DatasetX, DatasetY>::VOCDetection()});
    datasetMap.insert({"cifar10", Datasets<DatasetX, DatasetY>::CIFAR10()});
  }

  /**
   * Utility Function to wrap indices.
   *
   * @param index Index that will wraped over the length.
   * @param length Size / length of array.
   */

  size_t WrapIndex(int index, size_t length)
  {
    if (index < 0)
      return length - size_t(std::abs(index));

    return index;
  }

  /**
   * Performs train test split.
   *
   * @tparam DatasetType Type of input dataset.
   * @tparam LabelsType Type of input labels.
   *
   * @param dataset Features of dataset.
   * @param labels Labels of the dataset.
   * @param validRatio Ratio for train-test split.
   * @param shuffle Boolean to determine shuffling of dataset.
   */
  void TrainTestSplit(DatasetX& dataset,
                      std::deque<arma::vec>& labels,
                      DatasetX& /* trainFeatures */,
                      arma::field<arma::vec>& /* trainLabels */,
                      DatasetX& /* validFeatures */,
                      arma::field<arma::vec>& /* validLabels */,
                      const double validRatio,
                      const bool shuffle)
  {
    const size_t validSize = static_cast<size_t>(dataset.n_cols * validRatio);
    const size_t trainSize = dataset.n_cols - validSize;

    arma::uvec order = arma::linspace<arma::uvec>(0, dataset.n_cols - 1,
        dataset.n_cols);
    if (shuffle)
      order = arma::shuffle(order);

    if (trainSize > 0)
    {
      trainLabels.set_size(1, trainSize);
      trainFeatures = dataset.cols(order.subvec(0, trainSize - 1));

      // Field type has fixed size so we can't use span and assignment
      // operator.
      for (size_t i = 0; i < trainSize; i++)
        trainLabels(0, i) = labels[i];
    }

    if (validSize <= dataset.n_cols)
    {
      validFeatures = dataset.cols(order.subvec(trainSize,
          dataset.n_cols - 1));
      validLabels.set_size(1, validSize);
      for (size_t i = trainSize; i < dataset.n_cols; i++)
        validLabels(0, i - trainSize) = labels[i];
    }
    return;
  }

  /**
   * Performs train/test split.
   *
   * @tparam DatasetType Type of input dataset.
   * @tparam LabelsType Type of input labels.
   *
   * @param dataset Features of dataset.
   * @param labels Labels of the dataset.
   * @param validRatio Ratio for train-test split.
   * @param shuffle Boolean to determine shuffling of dataset.
   */
  void TrainTestSplit(DatasetX& dataset,
                      std::deque<arma::vec>& labels,
                      DatasetX& /* trainFeatures */,
                      arma::mat& /* trainLabels */,
                      DatasetX& /* validFeatures */,
                      arma::mat& /* validLabels */,
                      const double validRatio,
                      const bool shuffle)
  {
    // Calculate number of objects in the image.
    size_t numberOfObjects = labels[0].n_rows;
    DatasetY labelsTemp(numberOfObjects, labels.size());

    for (size_t i = 0; i < labels.size(); i++)
      labelsTemp.col(i) = labels[i];

    DatasetX completeDataset = arma::join_cols(dataset, labelsTemp);
    mlpack::data::Split(completeDataset, trainFeatures, validFeatures,
        validRatio, shuffle);

    // Features are all rows except the last 5 rows which correspond
    // to bounding box.
    trainLabels = trainFeatures.rows(trainFeatures.n_rows -
        numberOfObjects, trainFeatures.n_rows - 1);
    trainFeatures = trainFeatures.rows(0, trainFeatures.n_rows -
        numberOfObjects - 1);

    validLabels = validFeatures.rows(validFeatures.n_rows -
        numberOfObjects, validFeatures.n_rows - 1);
    validFeatures = validFeatures.rows(0, validFeatures.n_rows -
        numberOfObjects - 1);
    return;
  }

  //! Locally stored mappings for some well known datasets.
  std::unordered_map<std::string,
      DatasetDetails<DatasetX, DatasetY>> datasetMap;

  //! Locally stored input features for training.
  DatasetX trainFeatures;
  //! Locally stored input features for testing.
  DatasetX validFeatures;
  //! Locally stored input features for validation.
  DatasetX testFeatures;

  //! Locally stored labels for training.
  DatasetY trainLabels;
  //! Locally stored labels for validation.
  DatasetY validLabels;
  //! Locally stored labels for testing.
  DatasetY testLabels;

  //! Locally Stored scaler.
  ScalerType scaler;

  //! Locally stored path of dataset.
  std::string trainDatasetPath;

  //! Locally stored path of dataset.
  std::string testDatasetPath;

  //! Locally stored ratio for train-test split.
  double ratio;

  //! Locally stored augmentation.
  std::vector<std::string> augmentation;

  //! Locally stored augmentation probability.
  double augmentationProbability;
};

#include "dataloader_impl.hpp" // Include implementation.

#endif


#ifndef HSubproblemOptimization
#define HSubproblemOptimization

#include "SubproblemDefinitions.hpp"
#include "VectorOperations.hpp"
#include <Eigen/Core>
#include <vector>
#include <nlopt.hpp>
#include <cassert>
#include <iomanip>

#define NLOPT_ALG LN_COBYLA
//#define NLOPT_ALG LD_CCSAQ
//#define NLOPT_ALG LD_SLSQP

template <class TSurrogateModel>
class SubproblemOptimization : public VectorOperations {
  private:
    int dim;
    int number_constraints;
    nlopt::opt opt_criticality_measure;
    nlopt::opt opt_trial_point;
    nlopt::opt opt_restore_feasibility;
    std::vector<double> lb, ub;
    std::vector<double> lower_bounds, upper_bounds;
    double constraint_tolerance;
    std::vector<double> abs_tol;
    double rel_tol;
    std::vector<double> x;
    double optimization_result;
    SubproblemOptimization *me;
    std::vector< SubproblemData<TSurrogateModel, SubproblemOptimization> > SpD;
    SubproblemDefinitions<TSurrogateModel, SubproblemOptimization> subproblems;
    void set_feasibility_thresholds ( std::vector<double> const& );
    bool point_is_feasible;
    void set_local_lower_bounds (std::vector<double> const& );
    void set_local_upper_bounds (std::vector<double> const& );
  public:
    VectorOperations vo;
    std::vector<double> best_point;
    std::vector<double> *inner_boundary_constant;
    double *delta;
    std::vector<double> feasibility_thresholds;
    std::vector<TSurrogateModel> *surrogate_models;
    std::vector<double> criticality_gradient;
    SubproblemOptimization ( std::vector<TSurrogateModel>&, double&, std::vector<double>&);
    double compute_criticality_measure ( std::vector<double>& );
    double compute_trial_point ( std::vector<double>& );
    double restore_feasibility ( std::vector<double>& );
    void set_lower_bounds ( std::vector<double> const&);
    void set_upper_bounds ( std::vector<double> const&);
};

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
SubproblemOptimization<TSurrogateModel>::SubproblemOptimization (
                             std::vector<TSurrogateModel> &surrogate_models_input,
                             double &delta_input, std::vector<double> &inner_boundary_path_input) :
                             delta ( &delta_input ),
                             inner_boundary_constant ( &inner_boundary_path_input ),
                             surrogate_models ( &surrogate_models_input ),
                             opt_criticality_measure (nlopt::NLOPT_ALG,
                               surrogate_models_input[0].dimension( ) ),
                             opt_trial_point (nlopt::NLOPT_ALG,
                               surrogate_models_input[0].dimension( ) ),
                             opt_restore_feasibility (nlopt::NLOPT_ALG,
                               surrogate_models_input[0].dimension( ) )
{
  me = this;
  dim = (*surrogate_models)[0].dimension( );
  number_constraints = (*surrogate_models).size( )-1;

  feasibility_thresholds.resize( number_constraints );
  criticality_gradient.resize( dim );
  best_point.resize( dim );

  constraint_tolerance = 1e-12;

  lb.resize( dim );
  ub.resize( dim );

  double opt_time = 1e-1;
  int opt_eval = 1000;
  rel_tol = 1e-11; //1e-11
  double abs_ftol = 1e-5;
  for (int i = 0; i < dim; ++i) {
    abs_tol.push_back( 1e-5 ); //1e-5
    x.push_back ( 0e0 );
    lb[i] = -1e0;
    ub[i] = 1e0;
  }

  SubproblemData<TSurrogateModel, SubproblemOptimization> SpD_prototype;
  SpD_prototype.me = this;
  SpD_prototype.vo = &vo;

//  SpD_prototype.vector.resize( dim );
//  SpD_prototype.constraint_number = 0;
//  SpD.push_back( SpD_prototype );

  int SpDlen = (number_constraints==0)?1:number_constraints;
  for (int i = 0; i < SpDlen; ++i ) {
//    SpD_prototype.constraint_number = i;
    SpD.push_back( SpD_prototype );
    SpD[ i ].constraint_number = i;
  }

  //opt_criticality_measure.set_ftol_abs( abs_ftol );
  //opt_criticality_measure.set_ftol_rel( rel_tol );
  //opt_criticality_measure.set_xtol_abs( abs_tol );
  opt_criticality_measure.set_xtol_rel( rel_tol );
  //opt_criticality_measure.set_maxtime( opt_time );
  opt_criticality_measure.set_maxeval( opt_eval );
  opt_criticality_measure.set_min_objective ( subproblems.opt_criticality_measure_obj, &SpD[0] );
  opt_criticality_measure.add_inequality_constraint(
    subproblems.trustregion_constraint, &SpD[0], constraint_tolerance );
  for (int i = 0; i < number_constraints; ++i)
    opt_criticality_measure.add_inequality_constraint(
      subproblems.constraints_for_subproblems, &SpD[i], constraint_tolerance );

  //opt_trial_point.set_ftol_abs( abs_ftol );
  //opt_trial_point.set_ftol_rel( rel_tol );
  opt_trial_point.set_xtol_abs( abs_tol );
  opt_trial_point.set_xtol_rel( rel_tol );
  //opt_trial_point.set_maxtime( opt_time );
  opt_trial_point.set_maxeval( opt_eval );
  opt_trial_point.set_min_objective ( subproblems.opt_trial_point_obj, &SpD[0] );
  opt_trial_point.add_inequality_constraint(
    subproblems.trustregion_constraint, &SpD[0], constraint_tolerance );
  for (int i = 0; i < number_constraints; ++i)
    opt_trial_point.add_inequality_constraint(
      subproblems.constraints_for_subproblems, &SpD[i], constraint_tolerance );

  //opt_restore_feasibility.set_ftol_abs( abs_ftol );
  //opt_restore_feasibility.set_ftol_rel( rel_tol );
  opt_restore_feasibility.set_xtol_abs( abs_tol );
  opt_restore_feasibility.set_xtol_rel( rel_tol );
  //opt_restore_feasibility.set_maxtime( opt_time );
  opt_restore_feasibility.set_maxeval( opt_eval );
  opt_restore_feasibility.set_min_objective ( subproblems.opt_restore_feasibility_obj, &SpD[0] );
  opt_restore_feasibility.add_inequality_constraint(
    subproblems.trustregion_constraint, &SpD[0], constraint_tolerance );
  for (int i = 0; i < number_constraints; ++i)
    opt_restore_feasibility.add_inequality_constraint(
      subproblems.constraints_for_subproblems, &SpD[i], constraint_tolerance );
  return;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
void SubproblemOptimization<TSurrogateModel>::set_lower_bounds (
  std::vector<double> const &lower_bounds_input )
{
  lower_bounds = lower_bounds_input;
  return;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
void SubproblemOptimization<TSurrogateModel>::set_upper_bounds (
  std::vector<double> const &upper_bounds_input )
{
  upper_bounds = upper_bounds_input;
  return;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
void SubproblemOptimization<TSurrogateModel>::set_feasibility_thresholds (
  std::vector<double> const &x )
{
  point_is_feasible = true;
  for ( int i = 0; i < number_constraints; i++ ) {
    feasibility_thresholds.at( i ) = (*surrogate_models)[i+1].evaluate ( x );
    if ( feasibility_thresholds.at( i ) < 0e0 ) feasibility_thresholds.at( i ) = 0e0;
    else point_is_feasible = false;
  }
  /*
  std::cout << "#M2# Subproblem Testing feasiblity: " << std::endl;
  std::cout << "#M2#      at point: [";
  for(int i = 0; i < x.size(); ++i)
    std::cout << x[i] << ', ';
  std::cout << std::endl;
  std::cout << "#M2#      Feasiblity thresholds:";
  for ( int i = 0; i < number_constraints; i++ ) {
    std::cout << feasibility_thresholds.at( i ) << ", ";
  }
  std::cout << std::endl;
  std::cout << "#M2#      feasible: " << point_is_feasible << std::endl;
  */
  return;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
void SubproblemOptimization<TSurrogateModel>::set_local_lower_bounds (
  std::vector<double> const &x )
{
  if ( !lower_bounds.empty() ) {
    for ( int i = 0; i < dim; ++i ) {
      lb[i] = -(x[i] - lower_bounds[i])/(*delta);
      if ( lb[i] > 0e0 ) lb[i] = 0e0;
      if ( lb[i] < -1e0 ) lb[i] = -1e0;
    }
  }
  return;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
void SubproblemOptimization<TSurrogateModel>::set_local_upper_bounds (
  std::vector<double> const &x )
{
  if ( !upper_bounds.empty() ) {
    for ( int i = 0; i < dim; ++i ) {
      ub[i] = (upper_bounds[i] - x[i])/(*delta);
      if ( ub[i] < 0e0 ) ub[i] = 0e0;
      if ( ub[i] > 1e0 ) ub[i] = 1e0;
    }
  }
  return;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
double SubproblemOptimization<TSurrogateModel>::compute_criticality_measure (
                                                std::vector<double> &x )
{
  double lambda_g = *delta;
  best_point = x;

  for ( int i = 0; i < dim; ++i )
    x[i] = 0e0;

  set_local_lower_bounds( best_point );
  opt_criticality_measure.set_lower_bounds ( lb );

  set_local_upper_bounds( best_point );
  opt_criticality_measure.set_upper_bounds ( ub );

  set_feasibility_thresholds ( x );

  if ( point_is_feasible ) {
    criticality_gradient = (*surrogate_models)[0].gradient ( );
  } else {
    set_zero( criticality_gradient );
    for ( int i = 1; i < number_constraints+1; ++i )
      add( 2e0*feasibility_thresholds.at(i-1) + lambda_g,
           (*surrogate_models)[i].gradient ( ),
           criticality_gradient );
  }
  int errmess = -2541981;
  try {
   errmess =  opt_criticality_measure.optimize ( x, optimization_result );
  } catch ( ... ) {};

  add( *delta, x, best_point );
  x = best_point;

  return fabs( optimization_result ) / *delta ;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
double SubproblemOptimization<TSurrogateModel>::compute_trial_point (
                                                std::vector<double> &x )
{
  int errmess = -2541981;

  best_point = x;
  //std::cout << "-----------------------------1-----------------------" << std::endl;
  for ( int i = 0; i < dim; ++i )
    x[i] = 0e0;

  set_local_lower_bounds( best_point );
  opt_trial_point.set_lower_bounds ( lb );
  set_local_upper_bounds( best_point );
  opt_trial_point.set_upper_bounds ( ub );

  /*std::cout << "TrialPointOpt: " << std::endl;
  std::cout << "LowerBound: " << std::endl;
  for (int i = 0; i < dim; ++i)
    std::cout << lb[i] << " ";
  std::cout << "\nUpperBound: " << std::endl;
  for (int i = 0; i < dim; ++i)
    std::cout << ub[i] << " ";
  std::cout << "\nbest_point: " << std::endl;
  for (int i = 0; i < dim; ++i)
    std::cout << best_point[i] << " ";
  std::cout << "\n x: " << std::endl;
  for (int i = 0; i < dim; ++i)
    std::cout << x[i] << " ";
  std::cout << std::endl;*/

  set_feasibility_thresholds ( x );

  //std::cout << "#M3: Point is feasible: " << point_is_feasible << std::endl;
  if ( !point_is_feasible ) {
    //std::cout << "#M3: FEASIBILITY RESTORARION: " << std::endl;
    //opt_restore_feasibility.optimize ( x, optimization_result );for ( int i = 0; i < dim; ++i )

    opt_restore_feasibility.set_lower_bounds ( lb );
    opt_restore_feasibility.set_upper_bounds ( ub );

    try{
      errmess = opt_restore_feasibility.optimize ( x, optimization_result );
    } catch ( ... ) { };

    set_feasibility_thresholds ( x );
    //std::cout << "#M4: Point is feasible: " << point_is_feasible << std::endl;
    if ( point_is_feasible ) {
      /*std::cout << "      LowerBound: " << std::endl;
      for (int i = 0; i < dim; ++i)
        std::cout << std::setprecision(16) << "      " << lb[i] << " ";
      std::cout << "\n      UpperBound: " << std::endl;
      for (int i = 0; i < dim; ++i)
        std::cout << std::setprecision(16) << "      " << ub[i] << " ";
      std::cout << "\n      best_point: " << std::endl;
      for (int i = 0; i < dim; ++i)
        std::cout << std::setprecision(16) << "      " << best_point[i] << " ";
      std::cout << "\n      x: " << std::endl;
      for (int i = 0; i < dim; ++i)
        std::cout << std::setprecision(16) << "      " << x[i] << " ";
      std::cout << std::endl;*/
      for (int i = 0; i < dim; ++i)
        if(x[i] > ub[i]){
          //std::cout << std::setprecision(16) << "Setting x[i] to upper bound: " << ub[i] << std::endl;
          x[i] = ub[i];
        }else if(x[i] < lb[i]){
          //std::cout << std::setprecision(16) << "Setting x[i] to lower bound: " << lb[i] << std::endl;
          x[i] = lb[i];
        }

      //std::cout << "#M3 Inner: Classic optimization!" << std::endl;
      try{
        errmess = opt_trial_point.optimize ( x, optimization_result );
      } catch ( ... ) { };
    }
   // assert( false );
  } else {
    //std::cout << "#M3: Classic optimization!" << std::endl;
    try{
      errmess = opt_trial_point.optimize ( x, optimization_result );
    } catch ( ... ) { };
  }

  add( *delta, x, best_point );
  x = best_point;
  //std::cout << "-----------------------------2-----------------------" << std::endl;

  optimization_result = (*surrogate_models)[0].evaluate(x);

  return optimization_result;
}
//--------------------------------------------------------------------------------

//--------------------------------------------------------------------------------
template<class TSurrogateModel>
double SubproblemOptimization<TSurrogateModel>::restore_feasibility (
                                                std::vector<double> &x )
{

  best_point = x;

  for ( int i = 0; i < dim; ++i )
    x[i] = 0e0;

  set_local_lower_bounds( best_point );
  opt_restore_feasibility.set_lower_bounds ( lb );

  set_local_upper_bounds( best_point );
  opt_restore_feasibility.set_upper_bounds ( ub );

  set_feasibility_thresholds ( x );

  int errmess;
  try{
    errmess = opt_restore_feasibility.optimize ( x, optimization_result );
  } catch ( ... ) { };

  add( *delta, x, best_point );
  x = best_point;

  return optimization_result;
}
//--------------------------------------------------------------------------------

#endif


/*
Brian Staber (brian.staber@gmail.com)
*/

#include "Epetra_ConfigDefs.h"
#ifdef HAVE_MPI
#include "mpi.h"
#include "Epetra_MpiComm.h"
#else
#include "Epetra_SerialComm.h"
#endif

#include "Teuchos_CommandLineProcessor.hpp"
#include "Teuchos_StandardCatchMacros.hpp"
#include "Teuchos_ParameterList.hpp"
#include "Teuchos_XMLParameterListCoreHelpers.hpp"

#include "neumannInnerSurface_StochasticPolyconvexHGO.hpp"
#include <boost/math/special_functions/gamma.hpp>

#include "shinozukapp.hpp"

int main(int argc, char *argv[]){

    std::string    xmlInFileName = "";
    std::string    extraXmlFile = "";
    std::string    xmlOutFileName = "paramList.out";

    Teuchos::CommandLineProcessor  clp(false);
    clp.setOption("xml-in-file",&xmlInFileName,"The XML file to read into a parameter list");
    clp.setDocString("TO DO.");

    Teuchos::CommandLineProcessor::EParseCommandLineReturn
    parse_return = clp.parse(argc,argv);
    if( parse_return != Teuchos::CommandLineProcessor::PARSE_SUCCESSFUL ) {
        std::cout << "\nEnd Result: TEST FAILED" << std::endl;
        return parse_return;
    }

#ifdef HAVE_MPI
    MPI_Init(&argc, &argv);
    Epetra_MpiComm Comm(MPI_COMM_WORLD);
#else
    Epetra_SerialComm Comm;
#endif

    Teuchos::RCP<Teuchos::ParameterList> paramList = Teuchos::rcp(new Teuchos::ParameterList);
    if(xmlInFileName.length()) {
        Teuchos::updateParametersFromXmlFile(xmlInFileName, inoutArg(*paramList));
    }

    if (Comm.MyPID()==0){
        paramList->print(std::cout,2,true,true);
    }

    Teuchos::RCP<neumannInnerSurface_StochasticPolyconvexHGO> my_interface
    = Teuchos::rcp(new neumannInnerSurface_StochasticPolyconvexHGO(Comm,*paramList));

    std::ifstream parameters_file_1, parameters_file_2, parameters_file_3, parameters_file_4;

    std::string path1 = "/Users/Brian/Documents/Thesis/Trilinos_results/arteries/gmrf_neumann/a100_gamma3_delta010/";
    parameters_file_1.open(path1+"w1.txt");
    parameters_file_2.open(path1+"w2.txt");
    parameters_file_3.open(path1+"w3.txt");
    parameters_file_4.open(path1+"w4.txt");

    unsigned int n_cells_p1_med = 297828;
    unsigned int n_nodes_p1_med = 58464;

    Epetra_Map StandardMap(int(n_nodes_p1_med),0,Comm);

    Epetra_Vector c1(StandardMap);
    Epetra_Vector c2(StandardMap);
    Epetra_Vector u1(StandardMap);
    Epetra_Vector mu4(StandardMap);

    std::string path = "/Users/Brian/Documents/Thesis/Trilinos/arteries/mesh/connectivity_p1_media.txt";
    my_interface->get_media(n_cells_p1_med,n_nodes_p1_med,path);

    if (parameters_file_1.is_open() && parameters_file_2.is_open() && parameters_file_3.is_open() && parameters_file_4.is_open()){

        for (unsigned nmc=0; nmc<1; ++nmc){
            for (int i=0; i<n_nodes_p1_med; ++i){
                parameters_file_1 >> my_interface->w1_gmrf(i);
                parameters_file_2 >> my_interface->w2_gmrf(i);
                parameters_file_3 >> my_interface->w3_gmrf(i);
                parameters_file_4 >> my_interface->w4_gmrf(i);
            }
            for (int j=0; j<n_nodes_p1_med; ++j){
                if (StandardMap.MyGID(j)){
                    c1[StandardMap.LID(j)]  = my_interface->icdf_gamma(my_interface->w1_gmrf[j],my_interface->alpha1,my_interface->alpha2);
                    c2[StandardMap.LID(j)]  = my_interface->icdf_gamma(my_interface->w2_gmrf[j],my_interface->alpha3,my_interface->alpha4);
                    u1[StandardMap.LID(j)]  = my_interface->icdf_beta(my_interface->w3_gmrf[j],my_interface->tau1,my_interface->tau2);
                    mu4[StandardMap.LID(j)] = my_interface->icdf_gamma(my_interface->w4_gmrf[j],my_interface->alpha5,my_interface->alpha6);
                }
            }
        }
        Comm.Barrier();
        parameters_file_1.close();
        parameters_file_2.close();
        parameters_file_3.close();
        parameters_file_4.close();
    }
    else{
        std::cout << "Couldn't open one of the parameters_file.\n";
    }

    int error;
    int NumTargetElements = 0;
    if (Comm.MyPID()==0){
        NumTargetElements = n_nodes_p1_med;
    }
    Epetra_Map MapOnRoot(-1,NumTargetElements,0,Comm);
    Epetra_Export ExportOnRoot(StandardMap,MapOnRoot);
    Epetra_MultiVector lhs_root(MapOnRoot,true);
    lhs_root.Export(c1,ExportOnRoot,Insert);
    std::string filename = "/Users/Brian/Documents/Thesis/Trilinos_results/arteries/boost_gmrf/gmrf_nodes/c1_test.mtx";
    error = EpetraExt::MultiVectorToMatrixMarketFile(filename.c_str(),lhs_root,0,0,false);

    lhs_root.PutScalar(0.0);
    lhs_root.Export(c2,ExportOnRoot,Insert);
    filename = "/Users/Brian/Documents/Thesis/Trilinos_results/arteries/boost_gmrf/gmrf_nodes/c2_test.mtx";
    error = EpetraExt::MultiVectorToMatrixMarketFile(filename.c_str(),lhs_root,0,0,false);

    lhs_root.PutScalar(0.0);
    lhs_root.Export(u1,ExportOnRoot,Insert);
    filename = "/Users/Brian/Documents/Thesis/Trilinos_results/arteries/boost_gmrf/gmrf_nodes/u1_test.mtx";
    error = EpetraExt::MultiVectorToMatrixMarketFile(filename.c_str(),lhs_root,0,0,false);

    lhs_root.PutScalar(0.0);
    lhs_root.Export(mu4,ExportOnRoot,Insert);
    filename = "/Users/Brian/Documents/Thesis/Trilinos_results/arteries/boost_gmrf/gmrf_nodes/mu4_test.mtx";
    error = EpetraExt::MultiVectorToMatrixMarketFile(filename.c_str(),lhs_root,0,0,false);

#ifdef HAVE_MPI
    MPI_Finalize();
#endif
return 0;

}


//$Id: utils.h 611 2012-07-18 14:14:00Z koskos $

#ifndef _UTILS_H
#define _UTILS_H

#define IA 16807
#define IM 2147483647
#define AM (1.0 / IM)
#define IQ 127773
#define IR 2836
#define NTAB 32
#define NDIV (1 + (IM - 1) / NTAB)
#define EPS 1.2e-7
#define RNMX (1.0 - EPS)
#define PI 3.14159265358979323846

#define LOW_POS_DOUBLE 1e-300
#define BIG_POS_DOUBLE 1e300
#define LOW_NEG_DOUBLE -1e-300
#define BIG_NEG_DOUBLE -1e300
#define LOW_POS_FLOAT 1e-8
#define BIG_POS_FLOAT 1e8
#define LOW_NEG_FLOAT -1e-8
#define BIG_NEG_FLOAT -1e8
#define BIG_POS_INT 1000000000
#define BIG_NEG_INT -1000000000

#include <string>
#include <vector>
#include <queue>
#include <map>
#include <bitset>
#include <list>
#include <unordered_map>
#include <bitset>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <fstream>
#include <iomanip>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include <pthread.h>
#include <exception>
#include <boost/algorithm/string.hpp>
#include <boost/program_options.hpp>
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include <boost/iostreams/filter/bzip2.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/lexical_cast.hpp>

// using namespace std;
namespace bio = boost::iostreams;
namespace bpo = boost::program_options;
namespace bid = boost::uuids;

/******************************************************/
/*                  UTILS STATISTICS                  */
/******************************************************/
namespace putils {
void initRandom(long s);
double getRandom();
std::string getRandomID();
int getRandom(int);
long getSeed();
void normalise(std::vector<double> &v);
int sample(std::vector<double> &v, double sum);
double entropy(std::vector<double> &v);
double KLdistance(std::vector<double> &P, std::vector<double> &Q);
};

/******************************************************/
/*                  UTILS ALGORITHM                   */
/******************************************************/
namespace autils {
int max(std::vector<double> &v);
int max(std::vector<int> &v);
void findUniqueSet(std::vector<bool> &B, std::vector<int> &U); // ?
void decompose(int min, std::vector<std::vector<int>> &B,
               std::vector<std::vector<std::vector<int>>> &BB); //?
int checkDuo(int pa1, int pa2, int ca1, int ca2);
int checkTrio(int fa1, int fa2, int ma1, int ma2, int ca1, int ca2);
};

/******************************************************/
/*                  UTILS STRING                      */
/******************************************************/
namespace sutils {
int tokenize(std::string &, std::vector<std::string> &);
int tokenize(std::string &, std::vector<std::string> &, int);
std::string uint2str(unsigned int n);
std::string int2str(int n);
std::string int2str(std::vector<int> &v);
std::string long2str(long int n);
std::string double2str(double n, int prc = 4);
std::string double2str(std::vector<double> &v, int prc = 4);
std::string bool2str(std::vector<bool> &v);
std::string date2str(time_t *t, std::string format);
};

/******************************************************/
/*                  UTILS FILE                        */
/******************************************************/
namespace futils {
bool isFile(std::string f);
bool createFile(std::string f);
std::string extensionFile(std::string &filename);
void bool2binary(std::vector<bool> &V, std::ostream &fd);
bool binary2bool(std::vector<bool> &V, std::istream &fd);
};

/******************************************************/
/*                  EXCEPTIONS                        */
/******************************************************/
class myException : public std::exception {
public:
  explicit myException(std::string msg) : msg_(msg) {}

  virtual ~myException() throw() {}

  virtual const char *what() const throw() { return msg_.c_str(); }

private:
  std::string msg_;
};

/******************************************************/
/*                  INPUT FILE                        */
/******************************************************/
class ifile : public bio::filtering_istream {
private:
  std::string m_ext;
  std::string file;
  std::ifstream fd;
  bool m_isGood = false;

public:
  ifile();
  ifile(std::string filename, bool binary = false, std::string ext = "");
  ~ifile();
  std::string name();
  bool open(std::string filename, bool binary = false, std::string ext = "");
  bool readString(std::string &);
  void close();
  bool isGood() { return m_isGood; };
};

/******************************************************/
/*                  OUTPUT FILE                       */
/******************************************************/
class ofile : public bio::filtering_ostream {
private:
  std::string file;
  std::ofstream fd;

public:
  ofile();
  ofile(std::string filename, bool binary = false);
  ~ofile();
  std::string name();
  bool open(std::string filename, bool binary = false);
  void writeString(std::string &);
  void close();
};

/******************************************************/
/*                  LOG FILE                          */
/******************************************************/
class lfile {
private:
  std::string file;
  std::ofstream fd;
  bool verboseC;
  bool verboseL;

public:
  lfile();
  ~lfile();
  std::string name();
  bool open(std::string filename = "file.log");
  void close();
  std::string getPrefix();
  void muteL();
  void unmuteL();
  void muteC();
  void unmuteC();
  void print(std::string s);
  void printC(std::string s);
  void printL(std::string s);
  void println(std::string s);
  void printlnC(std::string s);
  void printlnL(std::string s);
  void warning(std::string s);
  void error(std::string s);
};

#endif