// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2015 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2015 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2015 Mateusz Loskot, London, UK.

// This file was modified by Oracle on 2015-2020.
// Modifications copyright (c) 2015-2020, Oracle and/or its affiliates.

// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle
// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

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

#ifndef BOOST_GEOMETRY_STRATEGY_CARTESIAN_ENVELOPE_HPP
#define BOOST_GEOMETRY_STRATEGY_CARTESIAN_ENVELOPE_HPP

#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>

#include <boost/geometry/algorithms/detail/envelope/initialize.hpp>

#include <boost/geometry/strategy/cartesian/envelope_box.hpp>
#include <boost/geometry/strategy/cartesian/envelope_segment.hpp>
#include <boost/geometry/strategy/cartesian/expand_box.hpp>
#include <boost/geometry/strategy/cartesian/expand_segment.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace envelope
{

template <typename CalculationType = void>
class cartesian
{
public:
    typedef cartesian_tag cs_tag;

    // Linestring, Ring, Polygon

    template <typename Range>
    static inline typename boost::range_iterator<Range const>::type begin(Range const& range)
    {
        return boost::begin(range);
    }

    template <typename Range>
    static inline typename boost::range_iterator<Range const>::type end(Range const& range)
    {
        return boost::end(range);
    }

    // MultiLinestring, MultiPolygon

    template <typename Box>
    struct multi_state
    {
        multi_state()
            : m_initialized(false)
        {}

        void apply(Box const& single_box)
        {
            if (! m_initialized)
            {
                m_box = single_box;
                m_initialized = true;
            }
            else
            {
                strategy::expand::cartesian_box::apply(m_box, single_box);
            }
        }

        void result(Box & box)
        {
            if (m_initialized)
            {
                box = m_box;
            }
            else
            {
                geometry::detail::envelope::initialize<Box, 0, dimension<Box>::value>::apply(box);
            }
        }

    private:
        bool m_initialized;
        Box m_box;
    };
};

#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

namespace services
{

template <typename Tag, typename CalculationType>
struct default_strategy<Tag, cartesian_tag, CalculationType>
{
    typedef strategy::envelope::cartesian<CalculationType> type;
};

} // namespace services

#endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS

}} // namespace strategy::envelope

}} //namepsace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGY_CARTESIAN_ENVELOPE_HPP


#include <boost/tree_view/depth_first.hpp>
#include <libs/tree_view/example/depth_first.h>

void example_depth_first(std::ostream& os){
    os << "-> example_depth_first " << std::endl;

    using namespace boost;

    const unsigned n_branches = 3;
    typedef tree_view::dynamic_stage<n_branches>     stage_;
    typedef tree_view::node<n_branches>              node_;
    typedef tree_view::depth_first<n_branches>       depth_first_;

    using namespace boost;
    const unsigned n_stages = 4;
    const node_ root_node;

    depth_first_ visit(n_stages);

    do{
        bool b = visit.is_subtree();
        if(b){
            os << '[' << visit.node() << ']';
        }else{
            os << visit.node();
        }
        ++visit;
    }while(
        !(visit.is_subtree() && (visit.node()==root_node))
    );
    os << "<-";
}


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<boost::int64_t, 6, 4> test_long_6_4(const Eigen::Matrix<boost::int64_t, 6, 4>& M) { return M; }
void export_long_6_4() { boost::python::def("test_long_6_4", test_long_6_4); }


#pragma once

#ifndef ORDER_H
#define ORDER_H

#include <boost/multiprecision/gmp.hpp>
#include "./token.hpp"

/***************************** The definition of the class that describes a regular (i.e., trader) order *********************************/
class Order {
    public:
        const int order_index;
        const int sell_token_index;
        const int buy_token_index;
        const bool is_sell_order;
        const bool is_liquidity_order;
        const bool is_partial_fill;
        const boost::multiprecision::mpz_int sell_amount;
        const boost::multiprecision::mpz_int buy_amount;
        const int fee_token_index;
        const boost::multiprecision::mpz_int fee_amount;
        const int cost_token_index;
        const boost::multiprecision::mpz_int cost_amount;

        // Constructor
        Order(int _order_index, int _sell_token_index, int _buy_token_index, bool _is_sell_order, bool _is_liquidity_order, bool _is_partial_fill, boost::multiprecision::mpz_int _sell_amount, boost::multiprecision::mpz_int _buy_amount, int _fee_token_index, boost::multiprecision::mpz_int _fee_amount, int _cost_token_index, boost::multiprecision::mpz_int _cost_amount): order_index(_order_index),  sell_token_index(_sell_token_index), buy_token_index(_buy_token_index), is_sell_order(_is_sell_order), is_liquidity_order(_is_liquidity_order), is_partial_fill(_is_partial_fill), sell_amount(_sell_amount), buy_amount(_buy_amount), fee_token_index(_fee_token_index), fee_amount(_fee_amount), cost_token_index(_cost_token_index), cost_amount(_cost_amount) {
        }
};
/*******************************************************************************************************************************************/

// An auxiliary function that prints an order in a readable form.
void print_order(Order &o, std::vector<Token> &_tokens);

#endif


//
// Created by julian on 5/17/18.
//

#ifndef VALUATION_EIGENTESTS_HPP
#define VALUATION_EIGENTESTS_HPP

#include <gtest/gtest.h>
#include <Eigen/Dense>
#include <vector>

#endif //VALUATION_EIGENTESTS_HPP


#pragma once
#include "lue/framework/test/stream.hpp"
#include "lue/framework/core/span.hpp"
#include <boost/range/irange.hpp>
#include <boost/test/test_tools.hpp>

namespace utf = boost::unit_test;
namespace tt = boost::test_tools;

namespace std {
namespace experimental {

template<
    typename Element,
    lue::Rank rank>
bool compare(
    lue::DynamicSpan<Element, rank> const& lhs,
    lue::DynamicSpan<Element, rank> const& rhs)
{
    // Both spans are equal if the extents in all dimensions are
    // equal. This means that finding a dimension for which this is not
    // the case must not result in a match.

    auto const idxs = boost::irange<lue::Index>(0, rank);

    return std::find_if(idxs.begin(), idxs.end(),

            [&lhs, &rhs](lue::Index const idx)
            {
                return lhs.extent(idx) != rhs.extent(idx);
            }

        ) == idxs.end();
}

template<
    typename Element>
bool operator==(
    lue::DynamicSpan<Element, 1> const& lhs,
    lue::DynamicSpan<Element, 1> const& rhs)
{
    return compare<Element, 1>(lhs, rhs);
}

template<
    typename Element>
bool operator==(
    lue::DynamicSpan<Element, 2> const& lhs,
    lue::DynamicSpan<Element, 2> const& rhs)
{
    return compare<Element, 2>(lhs, rhs);
}

}  // namespace experimental
}  // namespace std

namespace lue {
namespace test {

template<
    typename PartitionData>
void check_array_partition_data_is_equal(
    PartitionData const& partition_data1,
    PartitionData const& partition_data2)
{
    BOOST_REQUIRE_EQUAL(partition_data1.shape(), partition_data2.shape());
    BOOST_REQUIRE_EQUAL(partition_data1.span(), partition_data2.span());

    BOOST_CHECK_EQUAL_COLLECTIONS(
        partition_data1.begin(), partition_data1.end(),
        partition_data2.begin(), partition_data2.end());
}

template<
    typename PartitionData>
void check_array_partition_data_is_close(
    PartitionData const& partition_data1,
    PartitionData const& partition_data2)
{
    BOOST_REQUIRE_EQUAL(partition_data1.shape(), partition_data2.shape());
    BOOST_REQUIRE_EQUAL(partition_data1.span(), partition_data2.span());

    for(int i = 0; i < partition_data1.nr_elements(); ++i)
    {
        BOOST_TEST_CONTEXT("index " << i)
        {
            BOOST_TEST(
                partition_data1[i] == partition_data2[i], tt::tolerance(1e-6));
        }
    }
}

template<
    typename Partition>
void check_partition_is_equal(
    Partition const& partition1,
    Partition const& partition2)
{
    bool both_partitions_valid = partition1.valid() && partition2.valid();
    bool both_partitions_invalid =
        (!partition1.valid()) && (!partition2.valid());

    // XOR with bools
    BOOST_REQUIRE(both_partitions_valid != both_partitions_invalid);

    if(both_partitions_valid) {
        BOOST_REQUIRE(partition1.get_id());
        BOOST_REQUIRE(partition2.get_id());

        BOOST_REQUIRE_EQUAL(
            partition1.shape().get(), partition2.shape().get());

        check_array_partition_data_is_equal(
            partition1.data(CopyMode::copy).get(),
            partition2.data(CopyMode::copy).get());
    }
}

template<
    typename Partition>
void check_partition_is_close(
    Partition const& partition1,
    Partition const& partition2)
{
    bool both_partitions_valid = partition1.valid() && partition2.valid();
    bool both_partitions_invalid =
        (!partition1.valid()) && (!partition2.valid());

    // XOR with bools
    BOOST_REQUIRE(both_partitions_valid != both_partitions_invalid);

    if(both_partitions_valid) {
        BOOST_REQUIRE(partition1.get_id());
        BOOST_REQUIRE(partition2.get_id());

        BOOST_REQUIRE_EQUAL(
            partition1.shape().get(), partition2.shape().get());

        check_array_partition_data_is_close(
            partition1.data(CopyMode::copy).get(),
            partition2.data(CopyMode::copy).get());
    }
}

template<
    typename Partitions>
void check_partitions_are_equal(
    Partitions const& partitions1,
    Partitions const& partitions2)
{
    BOOST_REQUIRE_EQUAL(partitions1.shape(), partitions2.shape());

    for(Index p = 0; p < partitions1.nr_elements(); ++p) {
        BOOST_TEST_CONTEXT(fmt::format("Partition index: {}", p))
        check_partition_is_equal(partitions1[p], partitions2[p]);
    }
}

template<
    typename Partitions>
void check_partitions_are_close(
    Partitions const& partitions1,
    Partitions const& partitions2)
{
    BOOST_REQUIRE_EQUAL(partitions1.shape(), partitions2.shape());

    for(Index p = 0; p < partitions1.nr_elements(); ++p) {
        BOOST_TEST_CONTEXT(fmt::format("Partition index: {}", p))
        check_partition_is_close(partitions1[p], partitions2[p]);
    }
}

template<
    typename Element,
    Rank rank>
void check_arrays_are_equal(
    PartitionedArray<Element, rank> const& array1,
    PartitionedArray<Element, rank> const& array2)
{
    BOOST_REQUIRE_EQUAL(
        shape_in_partitions(array1),
        shape_in_partitions(array2));
    BOOST_REQUIRE_EQUAL(array1.shape(), array2.shape());

    check_partitions_are_equal(array1.partitions(), array2.partitions());
}

template<
    typename Element,
    Rank rank>
void check_arrays_are_close(
    PartitionedArray<Element, rank> const& array1,
    PartitionedArray<Element, rank> const& array2)
{
    BOOST_REQUIRE_EQUAL(
        shape_in_partitions(array1),
        shape_in_partitions(array2));
    BOOST_REQUIRE_EQUAL(array1.shape(), array2.shape());

    check_partitions_are_close(array1.partitions(), array2.partitions());
}

}  // namespace test
}  // namespace lue


#include <iostream>
#include <boost/algorithm/string.hpp>
#include <string>
#include <vector>
#include <cmath>

using namespace std;

double minDistance(const string& doc, const string& s1, const string& s2) {
  vector<string> tokens;
  boost::split(tokens, doc, boost::is_any_of(" "), boost::token_compress_on);

  double p1 = 0, p2 = 0; //positions of s1, s2,
  double curSz = 0, minDiff = doc.size();
  for (auto& str : tokens) {
    if (str == s1)
      p1 = curSz + s1.size() / (double)2;
    else if (str == s2)
      p2 = curSz + s2.size() / (double)2;

    if (fabs(p2 - p1) < minDiff && p1 > 0 && p2 > 0)
      minDiff = fabs(p2 - p1);

    curSz += str.size();
  };

  return minDiff;
};

int main(int argc, const char * argv[]) {
  string doc = "This is a test";
  string s1 = "is", s2 = "a";
  double dis = minDistance(doc, s1, s2);

  cout<<dis<<endl;
  return 0;
}


//
// Created by qfeng on 2019-01-21.
//

#include "encode/feat_enc_daemon.h"
#include "featpb/auc_impr_feats.pb.h"
#include <boost/thread/latch.hpp>
#include <fstream>
#include <sstream>

using namespace std;

namespace algocomp {

    void FeatEncDaemon::enc_cont_feats(const ::google::protobuf::Map<::std::string, ::algocomp::ContFeatGrp> &inp_feats,
                                       Eigen::SparseVector<float> &kv) {
        for (auto &feat_grp: inp_feats) {
            for (auto &feat: feat_grp.second.feats()) {
                // Encode continuous feature one by one
                auto k = _feat_enc->encode_single_feature("", feat.second.fname());
                auto v = feat.second.fval();
                if (k > 0) kv.insert(k) = v;

                if (_enc_mode == FeatEncMode::ACCUMULATING) {
                    // Add to map of names to encodes in accumulating mode
                    {
                        std::lock_guard<std::mutex> lock(_record_name2enc_mutex);
                        _name2enc[feat.second.fname()] = k;
                    }
                }
            }
        }
    }

    void FeatEncDaemon::enc_disc_feats(const ::google::protobuf::Map<::std::string, ::algocomp::DiscFeatGrp> &inp_feats,
                                       Eigen::SparseVector<float> &kv) {
        for (auto &feat_grp: inp_feats) {
            for (auto &feat: feat_grp.second.feats()) {
                // Encode discrete features one by one
                auto k = _feat_enc->encode_single_feature("", feat.second.fname());
                auto v = 1.0;
                if (k > 0) kv.insert(k) = v;

                if (_enc_mode == FeatEncMode::ACCUMULATING) {
                    // Add to map of names to encodes in accumulating mode
                    {
                        std::lock_guard<std::mutex> lock(_record_name2enc_mutex);
                        _name2enc[feat.second.fname()] = k;
                    }
                }
            }
        }
    }

    const optional<vector<Eigen::SparseVector<float>>>
    FeatEncDaemon::enc_on_ads_mode(const algocomp::AUCImprFeats &inp_feats) {
        try {
            int uc_disc_size = inp_feats.user_and_ctx().disc_feats_size();
            int uc_cont_size = inp_feats.user_and_ctx().cont_feats_size();

            // multi-thread in encode user-and-context
            boost::latch count_down_latch_for_uc(1);
            Eigen::SparseVector<float> uc_kv(_feat_enc->size());
            auto mth_func_for_uc = [this, &inp_feats, &uc_kv, &uc_disc_size, &uc_cont_size, &count_down_latch_for_uc](
                    int id) {

                if (_enc_scope == FeatEncScope::CONT) { // Encode only continuous features
                    uc_kv.reserve(uc_cont_size);
                    enc_cont_feats(inp_feats.user_and_ctx().cont_feats(), uc_kv);
                } else if (_enc_scope == FeatEncScope::DISC) { //Encode only discrete features
                    uc_kv.reserve(uc_disc_size);
                    enc_disc_feats(inp_feats.user_and_ctx().disc_feats(), uc_kv);
                } else { // Encode continuous and discrete features
                    uc_kv.reserve(uc_cont_size + uc_disc_size);
                    enc_cont_feats(inp_feats.user_and_ctx().cont_feats(), uc_kv);
                    enc_disc_feats(inp_feats.user_and_ctx().disc_feats(), uc_kv);
                }
                count_down_latch_for_uc.count_down();
            };
            _thp_rsrc->push(mth_func_for_uc);
            count_down_latch_for_uc.wait();

            // multi-thread in encode ads
            std::mutex res_mutex;
            boost::latch count_down_latch_for_ads(inp_feats.ads_size());
            std::vector<std::pair<int, Eigen::SparseVector<float>>> outp_;
            outp_.reserve(inp_feats.ads_size());

            auto mth_func_for_ads = [this, &uc_kv, &res_mutex, &inp_feats, &uc_disc_size, &uc_cont_size, &outp_, &count_down_latch_for_ads](
                    int id, int index) {
                auto ad = inp_feats.ads(index);
                int ad_disc_size = ad.disc_feats_size();
                int ad_cont_size = ad.cont_feats_size();

                Eigen::SparseVector<float> kv(_feat_enc->size());

                if (_enc_scope == FeatEncScope::CONT) { // Encode only continuous features
                    kv.reserve(uc_cont_size + ad_cont_size);
                    kv = uc_kv;
                    enc_cont_feats(ad.cont_feats(), kv);
                } else if (_enc_scope == FeatEncScope::DISC) { //Encode only discrete features
                    kv.reserve(uc_disc_size + ad_disc_size);
                    kv = uc_kv;
                    enc_disc_feats(ad.disc_feats(), kv);
                } else { // Encode continuous and discrete features
                    kv.reserve(uc_cont_size + uc_disc_size + ad_cont_size + ad_disc_size);
                    kv = uc_kv;
                    enc_cont_feats(ad.cont_feats(), kv);
                    enc_disc_feats(ad.disc_feats(), kv);
                }

                {
                    std::lock_guard<std::mutex> lock(res_mutex);
                    outp_.emplace_back(index, std::move(kv));
                }

                count_down_latch_for_ads.count_down();
            };

            for (int i = 0; i < inp_feats.ads_size(); ++i) {
                _thp_rsrc->push(mth_func_for_ads, i);
            }
            count_down_latch_for_ads.wait();

            // sort
            std::sort(outp_.begin(), outp_.end(),
                      [](const std::pair<int, Eigen::SparseVector<float>> &a,
                         const std::pair<int, Eigen::SparseVector<float>> &b) { return a.first < b.first; });

            std::vector<Eigen::SparseVector<float>> outp;
            outp.reserve(outp_.size());
            for (auto v: outp_) {
                outp.emplace_back(std::move(v.second));
            }

            return outp;

        } catch (const exception &ex) {
            _logger->error("Encode features(in thread type on-ads) fail: {}.", ex.what());
        }

        return std::nullopt;

    }

    const optional<vector<Eigen::SparseVector<float>>> FeatEncDaemon::enc_on_example_mode(
            const algocomp::AUCImprFeats &inp_feats) {
        try {
            std::vector<Eigen::SparseVector<float>> outp;
            outp.reserve(inp_feats.ads_size());
            boost::latch count_down_latch(1);

            auto mth_func = [this, &inp_feats, &outp, &count_down_latch](int id) {
                // encode user-and-context
                int uc_disc_size = inp_feats.user_and_ctx().disc_feats_size();
                int uc_cont_size = inp_feats.user_and_ctx().cont_feats_size();
                Eigen::SparseVector<float> uc_kv(_feat_enc->size());
                if (_enc_scope == FeatEncScope::CONT) { // Encode only continuous features
                    uc_kv.reserve(uc_cont_size);
                    enc_cont_feats(inp_feats.user_and_ctx().cont_feats(), uc_kv);
                } else if (_enc_scope == FeatEncScope::DISC) { //Encode only discrete features
                    uc_kv.reserve(uc_disc_size);
                    enc_disc_feats(inp_feats.user_and_ctx().disc_feats(), uc_kv);
                } else { // Encode continuous and discrete features
                    uc_kv.reserve(uc_cont_size + uc_disc_size);
                    enc_cont_feats(inp_feats.user_and_ctx().cont_feats(), uc_kv);
                    enc_disc_feats(inp_feats.user_and_ctx().disc_feats(), uc_kv);
                }

                // encode ads
                for (auto ad: inp_feats.ads()) {
                    // Prepare features in ad
                    int ad_disc_size = ad.disc_feats_size();
                    int ad_cont_size = ad.cont_feats_size();

                    Eigen::SparseVector<float> kv(_feat_enc->size());

                    if (_enc_scope == FeatEncScope::CONT) { // Encode only continuous features
                        kv.reserve(uc_cont_size + ad_cont_size);
                        kv = uc_kv;
                        enc_cont_feats(ad.cont_feats(), kv);
                    } else if (_enc_scope == FeatEncScope::DISC) { //Encode only discrete features
                        kv.reserve(uc_disc_size + ad_disc_size);
                        kv = uc_kv;
                        enc_disc_feats(ad.disc_feats(), kv);
                    } else { // Encode continuous and discrete features
                        kv.reserve(uc_cont_size + uc_disc_size + ad_cont_size + ad_disc_size);
                        kv = uc_kv;
                        enc_cont_feats(ad.cont_feats(), kv);
                        enc_disc_feats(ad.disc_feats(), kv);
                    }

                    outp.emplace_back(std::move(kv));
                }

                count_down_latch.count_down();
            };
            _thp_rsrc->push(mth_func);
            count_down_latch.wait();
            return outp;
        } catch (const exception &ex) {
            _logger->error("Encode features(in thread type on-ads) fail: {}.", ex.what());
        }

        return std::nullopt;
    }

    const optional<vector<Eigen::SparseVector<float>>>
    FeatEncDaemon::enc_on_none_mode(const algocomp::AUCImprFeats &inp_feats) {
        try {
            // encode user-and-context
            int uc_disc_size = inp_feats.user_and_ctx().disc_feats_size();
            int uc_cont_size = inp_feats.user_and_ctx().cont_feats_size();
            Eigen::SparseVector<float> uc_kv(_feat_enc->size());
            if (_enc_scope == FeatEncScope::CONT) { // Encode only continuous features
                uc_kv.reserve(uc_cont_size);
                enc_cont_feats(inp_feats.user_and_ctx().cont_feats(), uc_kv);
            } else if (_enc_scope == FeatEncScope::DISC) { //Encode only discrete features
                uc_kv.reserve(uc_disc_size);
                enc_disc_feats(inp_feats.user_and_ctx().disc_feats(), uc_kv);
            } else { // Encode continuous and discrete features
                uc_kv.reserve(uc_cont_size + uc_disc_size);
                enc_cont_feats(inp_feats.user_and_ctx().cont_feats(), uc_kv);
                enc_disc_feats(inp_feats.user_and_ctx().disc_feats(), uc_kv);
            }

            std::vector<Eigen::SparseVector<float>> outp;
            outp.reserve(inp_feats.ads_size());

            // encode ads
            for (auto ad: inp_feats.ads()) {
                // Prepare features in ad
                int ad_disc_size = ad.disc_feats_size();
                int ad_cont_size = ad.cont_feats_size();

                Eigen::SparseVector<float> kv(_feat_enc->size());

                if (_enc_scope == FeatEncScope::CONT) { // Encode only continuous features
                    kv.reserve(uc_cont_size + ad_cont_size);
                    kv = uc_kv;
                    enc_cont_feats(ad.cont_feats(), kv);
                } else if (_enc_scope == FeatEncScope::DISC) { //Encode only discrete features
                    kv.reserve(uc_disc_size + ad_disc_size);
                    kv = uc_kv;
                    enc_disc_feats(ad.disc_feats(), kv);
                } else { // Encode continuous and discrete features
                    kv.reserve(uc_cont_size + uc_disc_size + ad_cont_size + ad_disc_size);
                    kv = uc_kv;
                    enc_cont_feats(ad.cont_feats(), kv);
                    enc_disc_feats(ad.disc_feats(), kv);
                }
                outp.emplace_back(std::move(kv));
            }
            return outp;
        } catch (const exception &ex) {
            _logger->error("Encode features fail: {}.", ex.what());
        }
        return std::nullopt;
    }

    const optional<vector<Eigen::SparseVector<float>>> FeatEncDaemon::to_kv(const algocomp::AUCImprFeats &inp_feats) {
        if (_thread_type == ThreadType::NO) {
            return enc_on_none_mode(inp_feats);
        } else if (_thread_type == ThreadType::ON_EXAMPLE) {
            return enc_on_example_mode(inp_feats);
        } else if (_thread_type == ThreadType::ON_ADS) {
            return enc_on_ads_mode(inp_feats);
        } else {
            _logger->error("Unknown thread type.");
            return std::nullopt;
        }
    }

    const vector<string> FeatEncDaemon::to_str(const algocomp::AUCImprFeats &inp_feats) {
        if (_enc_str_format == FeatEncStrFormat::VW) {
            return to_vw(inp_feats);
        } else if (_enc_str_format == FeatEncStrFormat::LIBSVM) {
            return to_libsvm(inp_feats);
        }
    }

    const vector<string> FeatEncDaemon::to_vw(const algocomp::AUCImprFeats &inp_feats) {
        vector<string> outp;

        // Check the consistency of label size and the ad size
        if (inp_feats.labels_size() != inp_feats.ads_size()) {
            _logger->error("Inconsistent label size {} and ad size {}.", inp_feats.labels_size(), inp_feats.ads_size());
            return outp;
        }

        // Encode features to sparse kv representation
        auto kv__ = to_kv(inp_feats);

        if (kv__.has_value()) {
            auto kv_ = kv__.value();

            for (int i = 0; i < kv_.size(); ++i) {
                float label = inp_feats.labels(i);

                auto kv = kv_[i];
                ostringstream ss;

                // Format label string
                if (label > 0) ss << "1 ";
                else ss << "-1 ";
                ss << "| ";

                vector<pair<long, float>> encoded;
                for (Eigen::SparseVector<float>::InnerIterator it(kv); it; ++it) {
                    auto f_idx = it.index();
                    auto f_val = it.value();
                    encoded.emplace_back(f_idx, f_val);
                }

                if (encoded.size() > 0) {
                    // Sort kv in k's ascending order
                    std::sort(encoded.begin(), encoded.end(),
                              [](const pair<int, float> &lhs, const pair<int, float> rhs) {
                                  return lhs.first < rhs.first;
                              });
                    // Format feature strings
                    for_each(encoded.begin(), encoded.end(), [&ss](pair<int, float> f) {
                        if (f.second == 1) {
                            ss << f.first << " ";
                        } else if (f.second != 0) {
                            ss << f.first << ":" << f.second << " ";
                        }
                    });
                    outp.push_back(ss.str());
                } else outp.push_back("");
            }
        }

        return outp;
    }

    const vector<string> FeatEncDaemon::to_libsvm(const algocomp::AUCImprFeats &inp_feats) {
        vector<string> outp;

        // Check the consistency of label size and the ad size
        if (inp_feats.labels_size() != inp_feats.ads_size()) {
            _logger->error("Inconsistent label size {} and ad size {}.", inp_feats.labels_size(), inp_feats.ads_size());
            return outp;
        }

        // Encode features to sparse kv representation
        auto kv__ = to_kv(inp_feats);

        if (kv__.has_value()) {
            auto kv_ = kv__.value();

            for (int i = 0; i < kv_.size(); ++i) {
                float label = inp_feats.labels(i);

                auto kv = kv_[i];
                ostringstream ss;

                // Format label string
                if (label > 0) ss << "+1 ";
                else ss << "-1 ";

                vector<pair<long, float>> encoded;
                for (Eigen::SparseVector<float>::InnerIterator it(kv); it; ++it) {
                    auto f_idx = it.index();
                    auto f_val = it.value();
                    encoded.emplace_back(f_idx, f_val);
                }

                if (encoded.size() > 0) {
                    // Sort kv in k's ascending order
                    std::sort(encoded.begin(), encoded.end(),
                              [](const pair<int, float> &lhs, const pair<int, float> rhs) {
                                  return lhs.first < rhs.first;
                              });
                    // Format feature strings
                    for_each(encoded.begin(), encoded.end(), [&ss](pair<int, float> f) {
                        ss << f.first << ":" << f.second << " ";
                    });
                    outp.push_back(ss.str());
                } else outp.push_back("");
            }
        }

        return outp;
    }

    bool FeatEncDaemon::save_name2enc(const string &fname) {
        try {
            std::ofstream out;
            out.open(fname);

            // Save the map of names to encodes (encode first, name second)
            for (auto &item: _name2enc) {
                out << item.second << " " << item.first << endl;
            }

            out.close();
            return true;
        } catch (const exception &ex) {
            _logger->error("Save name2enc fail: {}", ex.what());
        }

        return false;
    }

    uint64_t FeatEncDaemon::encode_single_feature(const string &ns, const string &feat_name) {
        if (_feat_enc) {
            return _feat_enc->encode_single_feature(ns, feat_name);
        } else {
            _logger->error("Use empty feature encoder.");
            return 0;
        }
    }

    uint64_t FeatEncDaemon::feat_size() {
        if (_feat_enc) {
            return _feat_enc->size();
        } else return 1;
    }
}


/*
################################################################################
#                                                                              #
#                                                                              #
#                                                                              #
################################################################################
*/

#include "MolecularModelCVode.h"
#include "ODE_system.h"
#include "Param.h"

#include <iostream>
#include <string>
#include <algorithm> // min

#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>

// serialization
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/serialization/nvp.hpp>

typedef boost::archive::text_iarchive iax;
typedef boost::archive::text_oarchive oax;

#define SEC_PER_DAY 86400

namespace po = boost::program_options;
typedef MolecularModelCVode<CancerVCT::ODE_system> QSP;
using CancerVCT::Param;

int main(int argc, char* argv[])
{
	std::string _inputParam;
	std::string _outPath;
	std::string _outfile;
	std::string _savePath;
	std::string _loadFile;
	bool _save_stats = false;
	bool _save_steady_state = false;
	bool _load_steady_state = false;
	bool _use_resection = false;

	// command line options
	try {
		po::options_description desc("Allowed options");
		desc.add_options()
			("help,h", "produce help message")
			("input-file,i", po::value<std::string>(&_inputParam), "parameter file name")
			("output-path,o", po::value<std::string>(&_outPath), "output file path")
			("output-file-name,n", po::value<std::string>(&_outfile), "output file name")
			("save-path,s", po::value<std::string>(&_savePath), "folder to save steady state serialization")
			("load-file,l", po::value<std::string>(&_loadFile), "file to load steady state serialization from")
			("use-resection,r", po::bool_switch(&_use_resection), "to perform resection")
			;
		po::variables_map vm;
		po::store(po::parse_command_line(argc, argv, desc), vm);
		po::notify(vm);

		if (vm.count("help")) {
			std::cout << desc << "\n";
			return 0;
		}

		if (!vm.count("input-file"))
		{
			std::cout << desc << "\n";
			std::cerr << "no input file specified!\n";
			return 1;
		}
		if (vm.count("output-file-name"))
		{
			_save_stats = true;
			if (!vm.count("output-path"))
			{
				std::cout << desc << "\n";
				std::cerr << "Output path not specified" << std::endl;
				return 1;
			}
		}
		if (vm.count("save-path"))
		{
			_save_steady_state = true;
			CancerVCT::ODE_system::use_steady_state = true;
		}
		if (vm.count("load-file"))
		{
			_load_steady_state = true;
		}
		if (_save_steady_state && _load_steady_state)
		{
			std::cout << desc << "\n";
			std::cerr << "Cannot do both save and load steady-states"
				<< " in the same simulation" << std::endl;
			return 1;
		}
	}catch (std::exception& e) {
		std::cerr << "error: " << e.what() << "\n";
		return 1;
	}

	std::ofstream f;
	// output file
	if (_save_stats)
	{
		boost::filesystem::path pOut(_outPath);
		boost::filesystem::create_directories(pOut);// create output directory

		std::string output_file_name = _outPath + "/" + _outfile;
		f.open(output_file_name, std::ios::trunc);
	}

	// parameters
	Param params;
	params.initializeParams(_inputParam);

	CancerVCT::ODE_system::setup_class_parameters(params);

	if (_use_resection)
	{
		CancerVCT::ODE_system::use_resection = true;
	}
	// simulation object
	QSP model;
	// update variables from parameter file
	model.getSystem()->setup_instance_tolerance(params);
	model.getSystem()->setup_instance_varaibles(params);
	model.getSystem()->eval_init_assignment();

	int nTeff = 2;//cent.Teff

	// load steady-state variable
	if (_load_steady_state)
	{
		// deserialize
		QSP model_ss;
		std::ifstream in_save_ss;
		in_save_ss.open(_loadFile);
		iax iaState(in_save_ss);
		iaState >> BOOST_SERIALIZATION_NVP(model_ss);
		in_save_ss.close();

		// copy species values
		unsigned int n = model_ss.getSystem()->get_num_variables();
		for (size_t i = 0; i < n; i++)
		{
			double v = model_ss.getSystem()->getSpeciesVar(i);
			model.getSystem()->setSpeciesVar(i, v);
		}
		model.getSystem()->eval_init_assignment();
		model.getSystem()->updateVar();
	}

	//std::cout << "resection: " << CancerVCT::ODE_system::use_resection << std::endl;
	// header
	if (_save_stats)
	{
		f << "t" << model.getSystem()->getHeader() << std::endl;
	}

	// solve ODE system
	double t_start = params.getVal(0) * SEC_PER_DAY;
	double t_step = params.getVal(1) * SEC_PER_DAY ;
	int nrStep = int(params.getVal(2));
	auto t_end = t_start + t_step * nrStep;

	f << t_start << model << std::endl;

	while (t_start < t_end)
	{
		double t_remaining = t_end - t_start;
		double t_step_sim = std::min(t_remaining, t_step);

		model.solve(t_start, t_step_sim);

		t_start += t_step_sim;

		if (_save_stats)
		{
			f << t_start << model << std::endl;
		}
		//std::cout << t_start / SEC_PER_DAY << std::endl;
	}

	if (_save_stats)
	{
		f.close();
	}

	// serialization
	if (_save_steady_state)
	{
		boost::filesystem::path pOut(_savePath);
		boost::filesystem::create_directories(pOut);// create output directory
		// save
		std::ofstream out_save;
		std::string save_file_name = _savePath + "/steady-state.dat";
		out_save.open(save_file_name, std::ios::trunc);
		oax oaState(out_save);
		//CancerVCT::ODE_system::classSerialize(oaState, 0);
		oaState << BOOST_SERIALIZATION_NVP(model);
		out_save.close();
	}
	//std::cout << "End of simulation" << std::endl;

	return 0;
}


// This file is part of the dune-gdt project:
//   https://github.com/dune-community/dune-gdt
// Copyright 2010-2018 dune-gdt developers and contributors. All rights reserved.
// License: Dual licensed as BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)
//      or  GPL-2.0+ (http://opensource.org/licenses/gpl-license)
//          with "runtime exception" (http://www.dune-project.org/license.html)
// Authors:
//   Rene Milk      (2018)
//   Tobias Leibner (2017)

#ifndef DUNE_GDT_HYPERBOLIC_PROBLEMS_MOMENTMODELS_BASISFUNCTIONS_BASE_HH
#define DUNE_GDT_HYPERBOLIC_PROBLEMS_MOMENTMODELS_BASISFUNCTIONS_BASE_HH

#include <memory>
#include <vector>
#include <string>

#include <boost/math/special_functions/legendre.hpp>
#include <boost/math/special_functions/spherical_harmonic.hpp>

#include <dune/xt/common/math.hh>
#include <dune/xt/common/string.hh>
#include <dune/xt/common/tuple.hh>

#include <dune/xt/functions/affine.hh>

#include <dune/xt/grid/gridprovider/cube.hh>

#include <dune/xt/la/container.hh>

#include <dune/gdt/discretefunction/default.hh>
#include <dune/gdt/operators/l2.hh>
#include <dune/gdt/spaces/cg.hh>
#include <dune/gdt/test/hyperbolic/problems/momentmodels/triangulation.hh>

namespace Dune {
namespace GDT {
namespace Hyperbolic {
namespace Problems {

// take a DiscreteFunction and return a DiscreteFunction corresponding to component ii
template <size_t ii, class DiscreteFunctionType>
auto get_factor_discrete_function(const DiscreteFunctionType& discrete_function) ->
    typename Dune::GDT::DiscreteFunction<
        typename XT::Common::tuple_element<ii, typename DiscreteFunctionType::SpaceType::SpaceTupleType>::type,
        typename DiscreteFunctionType::VectorType>
{
  typedef typename Dune::GDT::DiscreteFunction<
      typename XT::Common::tuple_element<ii, typename DiscreteFunctionType::SpaceType::SpaceTupleType>::type,
      typename DiscreteFunctionType::VectorType>
      FactorDiscreteFunctionType;
  static_assert(ii < DiscreteFunctionType::SpaceType::num_factors, "This factor does not exist.");
  const auto& space = discrete_function.space();
  const auto& factor_space = space.template factor<ii>();
  typename DiscreteFunctionType::VectorType factor_vector(factor_space.mapper().size());
  const auto it_end = space.grid_layer().template end<0>();
  for (auto it = space.grid_layer().template begin<0>(); it != it_end; ++it) {
    const auto& entity = *it;
    for (size_t jj = 0; jj < factor_space.mapper().numDofs(entity); ++jj)
      factor_vector.set_entry(factor_space.mapper().mapToGlobal(entity, jj),
                              discrete_function.vector().get_entry(space.mapper().mapToGlobal(ii, entity, jj)));
  }
  FactorDiscreteFunctionType factor_discrete_function(factor_space);
  factor_discrete_function.vector() = factor_vector;
  //  typedef Dune::GDT::DiscreteFunctionDataHandle<FactorDiscreteFunctionType> DataHandleType;
  //  DataHandleType handle(factor_discrete_function);
  //  factor_space.grid_layer().template communicate<DataHandleType>(
  //      handle, Dune::InteriorBorder_All_Interface, Dune::ForwardCommunication);
  return factor_discrete_function;
}

// static for loop to sum components of a DiscreteFunction
template <size_t index, size_t N>
struct static_discrete_function_loop
{
  template <class DiscreteFunctionType>
  static typename DiscreteFunctionType::VectorType sum_vectors(const DiscreteFunctionType& discrete_function)
  {
    return static_discrete_function_loop<index, N / 2>::sum_vectors(discrete_function)
           + static_discrete_function_loop<index + N / 2, N - N / 2>::sum_vectors(discrete_function);
  }

  template <class DiscreteFunctionType>
  static typename DiscreteFunctionType::VectorType
  sum_vectors_divisible_by(const DiscreteFunctionType& discrete_function, const size_t divisor)
  {
    return static_discrete_function_loop<index, N / 2>::sum_vectors_divisible_by(discrete_function, divisor)
           + static_discrete_function_loop<index + N / 2, N - N / 2>::sum_vectors_divisible_by(discrete_function,
                                                                                divisor);
  }
};

// specialization to end the loop
template <size_t index>
struct static_discrete_function_loop<index, 1>
{
  template <class DiscreteFunctionType>
  static typename DiscreteFunctionType::VectorType sum_vectors(const DiscreteFunctionType& discrete_function)
  {
    return get_factor_discrete_function<index, DiscreteFunctionType>(discrete_function).vector();
  }

  template <class DiscreteFunctionType>
  static typename DiscreteFunctionType::VectorType
  sum_vectors_divisible_by(const DiscreteFunctionType& discrete_function, const size_t divisor)
  {
    if (!(index % divisor))
      return get_factor_discrete_function<index, DiscreteFunctionType>(discrete_function).vector();
    else
      return typename DiscreteFunctionType::VectorType(
          discrete_function.space().template factor<index>().mapper().size(), 0.);
  }
};

// visualizes sum of components of discrete_function
template <class DiscreteFunctionType, size_t dimRange>
void sum_visualizer(const DiscreteFunctionType& u_n, const std::string& filename_prefix, const size_t ii)
{
  auto sum_function = get_factor_discrete_function<0, DiscreteFunctionType>(u_n);
  sum_function.vector() = static_discrete_function_loop<0, dimRange>::sum_vectors(u_n);
  sum_function.visualize(filename_prefix + "_" + Dune::XT::Common::to_string(ii));
}

// visualizes sum of components with index divisible by divisor
template <class DiscreteFunctionType, size_t dimRange>
void sum_divisible_by_visualizer(const DiscreteFunctionType& u_n,
                                 const std::string& filename_prefix,
                                 const size_t ii,
                                 const size_t divisor)
{
  auto sum_function = get_factor_discrete_function<0, DiscreteFunctionType>(u_n);
  sum_function.vector() = static_discrete_function_loop<0, dimRange>::sum_vectors_divisible_by(u_n, divisor);
  sum_function.visualize(filename_prefix + "_" + Dune::XT::Common::to_string(ii));
}

// visualizes factor * component of discrete function
template <class DiscreteFunctionType, size_t dimRange, size_t component>
void component_visualizer(const DiscreteFunctionType& u_n,
                          const std::string& filename_prefix,
                          const size_t ii,
                          const double factor = 1.)
{
  auto u_n_comp = get_factor_discrete_function<component, DiscreteFunctionType>(u_n);
  u_n_comp.vector() *= factor;
  u_n_comp.visualize(filename_prefix + "_" + Dune::XT::Common::to_string(ii));
}

// see https://en.wikipedia.org/wiki/Tridiagonal_matrix#Inversion
template <class FieldType, int rows>
Dune::DynamicMatrix<FieldType> tridiagonal_matrix_inverse(const DynamicMatrix<FieldType>& matrix)
{
  typedef Dune::DynamicMatrix<FieldType> MatrixType;
  size_t cols = rows;
#ifndef NDEBUG
  for (size_t rr = 0; rr < rows; ++rr)
    for (size_t cc = 0; cc < cols; ++cc)
      if ((cc > rr + 1 || cc + 1 < rr) && XT::Common::FloatCmp::ne(matrix[rr][cc], 0.))
        DUNE_THROW(XT::Common::Exceptions::you_are_using_this_wrong, "Matrix has to be tridiagonal!");
#endif // NDEBUG
  MatrixType ret(rows, rows, 0);
  Dune::FieldVector<FieldType, rows + 1> a(0), b(0), c(0), theta(0);
  Dune::FieldVector<FieldType, rows + 2> phi(0);
  for (size_t ii = 1; ii < rows + 1; ++ii) {
    a[ii] = matrix[ii - 1][ii - 1];
    if (ii < rows) {
      b[ii] = matrix[ii - 1][ii];
      c[ii] = matrix[ii][ii - 1];
    }
  }
  theta[0] = 1;
  theta[1] = a[1];
  for (size_t ii = 2; ii < rows + 1; ++ii)
    theta[ii] = a[ii] * theta[ii - 1] - b[ii - 1] * c[ii - 1] * theta[ii - 2];
  phi[rows + 1] = 1;
  phi[rows] = a[rows];
  for (size_t ii = rows - 1; ii > 0; --ii)
    phi[ii] = a[ii] * phi[ii + 1] - b[ii] * c[ii] * phi[ii + 2];
  for (size_t ii = 1; ii < rows + 1; ++ii) {
    for (size_t jj = 1; jj < cols + 1; ++jj) {
      if (ii == jj)
        ret[ii - 1][jj - 1] = theta[ii - 1] * phi[jj + 1] / theta[rows];
      else if (ii < jj) {
        ret[ii - 1][jj - 1] = std::pow(-1, ii + jj) * theta[ii - 1] * phi[jj + 1] / theta[rows];
        for (size_t kk = ii; kk < jj; ++kk)
          ret[ii - 1][jj - 1] *= b[kk];
      } else if (ii > jj) {
        ret[ii - 1][jj - 1] = std::pow(-1, ii + jj) * theta[jj - 1] * phi[ii + 1] / theta[rows];
        for (size_t kk = jj; kk < ii; ++kk)
          ret[ii - 1][jj - 1] *= c[kk];
      }
    } // jj
  } // ii
#ifndef NDEBUG
  for (size_t ii = 0; ii < rows; ++ii)
    for (size_t jj = 0; jj < cols; ++jj)
      if (std::isnan(ret[ii][jj]) || std::isinf(ret[ii][jj]))
        DUNE_THROW(Dune::MathError, "Inversion of triangular matrix failed!");
#endif
  return ret;
} // ... tridiagonal_matrix_inverse(...)

// After each refinement step:
// num_vertices_new = num_vertices_old + num_intersections_old
// num_intersections_new = 2*num_intersections_old + 3*num_faces_old
// num_faces_new = 4*num_faces_old
// Initially, there are 6 vertices, 12 intersections and 8 faces.
template <size_t refinements>
struct OctaederStatistics
{
  static constexpr size_t num_faces()
  {
    return 8 * (1 << 2 * refinements);
  }

  static constexpr size_t num_intersections()
  {
    return 2 * OctaederStatistics<refinements - 1>::num_intersections()
           + 3 * OctaederStatistics<refinements - 1>::num_faces();
  }

  static constexpr size_t num_vertices()
  {
    return OctaederStatistics<refinements - 1>::num_vertices()
           + OctaederStatistics<refinements - 1>::num_intersections();
  }
};

template <>
struct OctaederStatistics<0>
{
  static constexpr size_t num_faces()
  {
    return 8;
  }

  static constexpr size_t num_intersections()
  {
    return 12;
  }

  static constexpr size_t num_vertices()
  {
    return 6;
  }
};

template <class DomainFieldImp,
          size_t domainDim,
          class RangeFieldImp,
          size_t rangeDim,
          size_t rangeDimCols = 1,
          size_t fluxDim = domainDim>
class BasisfunctionsInterface
{
public:
  static const size_t dimDomain = domainDim;
  static const size_t dimRange = rangeDim;
  static const size_t dimRangeCols = rangeDimCols;
  static const size_t dimFlux = fluxDim;
  typedef DomainFieldImp DomainFieldType;
  typedef XT::Common::FieldVector<DomainFieldType, dimDomain> DomainType;
  typedef RangeFieldImp RangeFieldType;
  typedef DynamicMatrix<RangeFieldType> MatrixType;
  typedef typename XT::Functions::RangeTypeSelector<RangeFieldType, dimRange, dimRangeCols>::type RangeType;
  template <class DiscreteFunctionType>
  using VisualizerType = typename std::function<void(const DiscreteFunctionType&, const std::string&, const size_t)>;
  using StringifierType = std::function<std::string(const RangeType&)>;
  typedef typename Dune::QuadratureRule<RangeFieldType, dimDomain> QuadratureType;

  virtual ~BasisfunctionsInterface(){};

  virtual RangeType evaluate(const DomainType& v) const = 0;

  virtual RangeType integrated() const = 0;

  virtual MatrixType mass_matrix() const = 0;

  virtual MatrixType mass_matrix_inverse() const = 0;

  virtual FieldVector<MatrixType, dimFlux> mass_matrix_with_v() const = 0;

  static QuadratureRule<RangeFieldType, 2> barycentre_rule()
  {
    Dune::QuadratureRule<RangeFieldType, 2> ret;
    ret.push_back(Dune::QuadraturePoint<RangeFieldType, 2>({1. / 3., 1. / 3.}, 0.5));
    return ret;
  }

protected:
  std::vector<std::vector<size_t>> create_decomposition(const size_t num_threads, const size_t size) const
  {
    std::vector<std::vector<size_t>> decomposition(num_threads);
    for (size_t ii = 0; ii < num_threads - 1; ++ii) {
      decomposition[ii].reserve(size / num_threads * (ii + 1) - size / num_threads * ii);
      for (size_t jj = size / num_threads * ii; jj < size / num_threads * (ii + 1); ++jj)
        decomposition[ii].push_back(jj);
    }
    decomposition[num_threads - 1].reserve(size - (size / num_threads) * (num_threads - 1));
    for (size_t jj = size / num_threads * (num_threads - 1); jj < size; ++jj)
      decomposition[num_threads - 1].push_back(jj);
    return decomposition;
  }

  virtual void parallel_quadrature(const QuadratureType& quadrature,
                                   MatrixType& matrix,
                                   const size_t v_index,
                                   const bool reflecting = false) const
  {
    size_t num_threads = std::min(XT::Common::threadManager().max_threads(), quadrature.size());
    auto decomposition = create_decomposition(num_threads, quadrature.size());
    std::vector<std::thread> threads(num_threads);
    // Launch a group of threads
    std::vector<MatrixType> local_matrices(num_threads, MatrixType(matrix.N(), matrix.M(), 0.));
    for (size_t ii = 0; ii < num_threads; ++ii)
      threads[ii] = std::thread(&BasisfunctionsInterface::calculate_in_thread,
                                this,
                                std::cref(quadrature),
                                std::ref(local_matrices[ii]),
                                v_index,
                                std::cref(decomposition[ii]),
                                reflecting);
    // Join the threads with the main thread
    for (size_t ii = 0; ii < num_threads; ++ii)
      threads[ii].join();
    // add local matrices
    matrix *= 0.;
    for (size_t ii = 0; ii < num_threads; ++ii)
      matrix += local_matrices[ii];
  } // void parallel_quadrature(...)

  virtual void calculate_in_thread(const QuadratureType& quadrature,
                                   MatrixType& local_matrix,
                                   const size_t v_index,
                                   const std::vector<size_t>& indices,
                                   const bool reflecting) const
  {
    for (const auto& jj : indices) {
      const auto& quad_point = quadrature[jj];
      const auto& v = quad_point.position();
      auto v_reflected = v;
      if (reflecting)
        v_reflected[v_index] *= -1.;
      const auto basis_evaluated = evaluate(v);
      const auto basis_reflected = evaluate(v_reflected);
      const auto& weight = quad_point.weight();
      const auto factor = (reflecting || v_index == size_t(-1)) ? 1. : v[v_index];
      for (size_t nn = 0; nn < local_matrix.N(); ++nn)
        for (size_t mm = 0; mm < local_matrix.M(); ++mm)
          local_matrix[nn][mm] +=
              basis_evaluated[nn] * (reflecting ? basis_reflected[mm] : basis_evaluated[mm]) * factor * weight;
    } // ii
  } // void calculate_in_thread(...)

  RangeType integrated_initializer(const QuadratureType& quadrature) const
  {
    size_t num_threads = std::min(XT::Common::threadManager().max_threads(), quadrature.size());
    auto decomposition = create_decomposition(num_threads, quadrature.size());
    std::vector<std::thread> threads(num_threads);
    std::vector<RangeType> local_vectors(num_threads, RangeType(0.));
    for (size_t ii = 0; ii < num_threads; ++ii)
      threads[ii] = std::thread(&BasisfunctionsInterface::integrated_initializer_thread,
                                this,
                                std::cref(quadrature),
                                std::ref(local_vectors[ii]),
                                std::cref(decomposition[ii]));
    // Join the threads with the main thread
    for (size_t ii = 0; ii < num_threads; ++ii)
      threads[ii].join();
    // add local matrices
    RangeType ret(0.);
    for (size_t ii = 0; ii < num_threads; ++ii)
      ret += local_vectors[ii];
    return ret;
  }

  void integrated_initializer_thread(const QuadratureType& quadrature,
                                     RangeType& local_range,
                                     const std::vector<size_t>& indices) const
  {
    for (const auto& jj : indices) {
      const auto& quad_point = quadrature[jj];
      auto basis_evaluated = evaluate(quad_point.position());
      basis_evaluated *= quad_point.weight();
      local_range += basis_evaluated;
    } // jj
  } // void calculate_in_thread(...)
};

} // namespace Problems
} // namespace Hyperbolic
} // namespace GDT
} // namespace Dune

#endif // DUNE_GDT_HYPERBOLIC_PROBLEMS_MOMENTMODELS_BASISFUNCTIONS_BASE_HH


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  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_SIMD_CONSTANT_DEFINITION_ALLBITS_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_DEFINITION_ALLBITS_HPP_INCLUDED

#include <boost/simd/config.hpp>
#include <boost/simd/detail/brigand.hpp>
#include <boost/simd/detail/dispatch.hpp>
#include <boost/simd/detail/constant_traits.hpp>
#include <boost/simd/constant/definition/mone.hpp>
#include <boost/simd/detail/dispatch/function/make_callable.hpp>
#include <boost/simd/detail/dispatch/hierarchy/functions.hpp>
#include <boost/simd/detail/dispatch/as.hpp>

namespace boost { namespace simd
{
  namespace tag
  {
    struct allbits_ : boost::dispatch::constant_value_<allbits_>
    {
      BOOST_DISPATCH_MAKE_CALLABLE(ext,allbits_,boost::dispatch::constant_value_<allbits_>);
      struct value_map
      {
        template<typename X>
        static auto value(X const& x) -> decltype(mone_::value_map::value(x));

        template<typename X>
        static brigand::single_<0xFFFFFFFFUL> value(boost::dispatch::single_<X> const&);

        template<typename X>
        static brigand::double_<0xFFFFFFFFFFFFFFFFULL> value(boost::dispatch::double_<X> const&);
      };
    };
  }

  namespace ext
  {
    BOOST_DISPATCH_FUNCTION_DECLARATION(tag, allbits_)
  }

  namespace detail
  {
    BOOST_DISPATCH_CALLABLE_DEFINITION(tag::allbits_,allbits);
  }

  template<typename T> BOOST_FORCEINLINE auto Allbits()
  BOOST_NOEXCEPT_DECLTYPE(detail::allbits( boost::dispatch::as_<T>{}))
  {
    return detail::allbits( boost::dispatch::as_<T>{} );
  }
} }

#endif


#include "KSampleRateConverter.h"
#include "../KWav.h"

#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>

//-----------------------------------------------------------------------------

using namespace std;
using namespace boost;

//-----------------------------------------------------------------------------

bool KSampleRateConverter::Convert( const KWav& source,
                                    KWav*& destination,
                                    std::map< std::string, std::string > options,
                                    std::string& errorDescription )
{
  // Get the new sample rate from the options.
  uint32_t newRate = 0;

  if( options.find( "-r" ) == options.end() )
  {
    errorDescription = "Sample rate (-r) option not specified.";
    return false;
  }

  try
  {
    newRate = lexical_cast< uint32_t >( options[ "-r" ].c_str() );
  }
  catch( bad_lexical_cast )
  {
    errorDescription = "Non-numeric sample rate option specified.";
    return false;
  }

  if( newRate == 0 )
  {
    errorDescription = "Sample rate cannot be zero.";
    return false;
  }
  else if( newRate > 96000 )
  {
    errorDescription = "Sample rate cannot exceed 96000.";
    return false;
  }

  // Get the method to use.
  Method method = LINEAR;

  if( options.find( "-m" ) != options.end() )
  {
    string m = options[ "-m" ];
    to_upper< string >( m );

    if( m.length() == 0 )
    {
      errorDescription = "No re-sampling method (-m) specified.";
      return false;
    }

    switch( m[ 0 ] )
    {
      case 'L':
        method = LINEAR;
        break;

      case 'C':
        method = CUBIC;
        break;

      default:
        errorDescription = "Invalid re-sampling method (-m) specified.";
        return false;
    }
  }

  // Already at specified rate?
  if( source.GetSampleRate() == newRate )
  {
    return true;
  }

  // Get the wav data.
  const uint64_t dataSize = source.GetDataSize();
  const int8_t* data = source.GetData();

  // Choose conversion type.
  int8_t* newData = nullptr;
  uint64_t newDataSize = 0;

  switch( method )
  {
    case LINEAR:
      if( source.GetSampleRate() < newRate )
      {
        newData =
          UpsampleLinear( source,
                          newRate,
                          newDataSize );
      }
      break;

    case CUBIC:
      break;

    default:
      errorDescription = "Unknown re-sampling method.";
      return false;
  }

  // Create destination wav object.
  destination =
    new KWav(
      source.GetChannelCount(),
      newRate,
      source.GetBitsPerSample(),
      newData,
      newDataSize );

  return true;
}

//-----------------------------------------------------------------------------

int8_t* KSampleRateConverter::UpsampleLinear( const KWav& input,
                                              const uint32_t newSampleRate,
                                              uint64_t& newDataSize )
{
  // Calculate new buffer size.
  if( input.GetBytesPerSample() == 0 ||
      input.GetChannelCount() == 0 )
  {
    return nullptr;
  }

  newDataSize = newSampleRate / input.GetBytesPerSample() / input.GetChannelCount();

  // Allocate the buffer.
  int8_t* buffer = new int8_t[ newDataSize ];
  fill( buffer, buffer + newDataSize, 0 );

  // Calculate the ratio between sample rates.
  const float ratio = static_cast< float >( input.GetSampleRate() ) / newSampleRate;

  //
  for( uint64_t i = 0; i < input.GetDataSize(); i++ )
  {

  }

  return buffer;
}

//-----------------------------------------------------------------------------


/**TODO:  Add copyright*/

#define BOOST_TEST_MODULE ModelTrainer test suite
#include <boost/test/included/unit_test.hpp>
#include <SmartPeak/ml/ModelTrainerDefaultDevice.h>

#include <SmartPeak/ml/Model.h>
#include <SmartPeak/ml/ModelInterpreterDefaultDevice.h>

using namespace SmartPeak;
using namespace std;

template<typename TensorT>
class ModelTrainerExt : public ModelTrainerDefaultDevice<TensorT>{};

template<typename TensorT>
class DataSimulatorDAGToy : public DataSimulator<TensorT> {
public:
  void simulateTrainingData(Eigen::Tensor<TensorT, 3>& input_data, Eigen::Tensor<TensorT, 3>& loss_output_data, Eigen::Tensor<TensorT, 3>& metric_output_data, Eigen::Tensor<TensorT, 2>& time_steps) override {
    // Make the input data
    input_data.setValues({ {{1, 5, 1, 1}}, {{2, 6, 1, 1}}, {{3, 7, 1, 1}}, {{4, 8, 1, 1}} });

    // Make the output data
    loss_output_data.setValues({ {{0, 1}}, {{0, 1}}, {{0, 1}}, {{0, 1}} });
    metric_output_data.setValues({ {{0, 1}}, {{0, 1}}, {{0, 1}}, {{0, 1}} });

    // Make the simulation time_steps
    time_steps.setConstant(1);
  };
  void simulateValidationData(Eigen::Tensor<TensorT, 3>& input_data, Eigen::Tensor<TensorT, 3>& loss_output_data, Eigen::Tensor<TensorT, 3>& metric_output_data, Eigen::Tensor<TensorT, 2>& time_steps) override {
    // Make the input data
    input_data.setValues({ {{1, 1, 5, 1}}, {{1, 1, 2, 6}}, {{1, 1, 3, 7}}, {{1, 1, 4, 8 }} });

    // Make the output data
    loss_output_data.setValues({ {{1, 0}}, {{1, 0}}, {{1, 0}}, {{1, 0}} });
    metric_output_data.setValues({ {{0, 1}}, {{0, 1}}, {{0, 1}}, {{0, 1}} });

    // Make the simulation time_steps
    time_steps.setConstant(1);
  };
};

template<typename TensorT>
class DataSimulatorDCGToy : public DataSimulator<TensorT> {
public:
  void simulateTrainingData(Eigen::Tensor<TensorT, 3>& input_data, Eigen::Tensor<TensorT, 3>& loss_output_data, Eigen::Tensor<TensorT, 3>& metric_output_data, Eigen::Tensor<TensorT, 2>& time_steps) override {
    // Make the input data
    input_data.setValues(
      { {{8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}, {3, 0, 0}, {2, 0, 0}, {1, 0, 0}},
      {{9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}, {3, 0, 0}, {2, 0, 0}},
      {{10, 0, 0}, {9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}, {3, 0, 0}},
      {{11, 0, 0}, {10, 0, 0}, {9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}},
      {{12, 0, 0}, {11, 0, 0}, {10, 0, 0}, {9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}} }
    );

    // Make the output data
    loss_output_data.setValues(
      { { { 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 },{ 1 },{ 1 } },
      { { 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 },{ 1 } },
      { { 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 } },
      { { 6 },{ 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 } },
      { { 6 },{ 6 },{ 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 } } });
    metric_output_data.setValues(
      { { { 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 },{ 1 },{ 1 } },
      { { 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 },{ 1 } },
      { { 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 } },
      { { 6 },{ 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 } },
      { { 6 },{ 6 },{ 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 } } });

    // Make the simulation time_steps
    time_steps.setValues({
      {1, 1, 1, 1, 1, 1, 1, 1},
      {1, 1, 1, 1, 1, 1, 1, 1},
      {1, 1, 1, 1, 1, 1, 1, 1},
      {1, 1, 1, 1, 1, 1, 1, 1},
      {1, 1, 1, 1, 1, 1, 1, 1} }
    );
  }
  void simulateValidationData(Eigen::Tensor<TensorT, 3>& input_data, Eigen::Tensor<TensorT, 3>& loss_output_data, Eigen::Tensor<TensorT, 3>& metric_output_data, Eigen::Tensor<TensorT, 2>& time_steps) override {
    simulateTrainingData(input_data, loss_output_data, metric_output_data, time_steps);
  }
};

BOOST_AUTO_TEST_SUITE(trainer)

BOOST_AUTO_TEST_CASE(constructor)
{
  ModelTrainerExt<float>* ptr = nullptr;
  ModelTrainerExt<float>* nullPointer = nullptr;
	ptr = new ModelTrainerExt<float>();
  BOOST_CHECK_NE(ptr, nullPointer);
}

BOOST_AUTO_TEST_CASE(destructor)
{
  ModelTrainerExt<float>* ptr = nullptr;
	ptr = new ModelTrainerExt<float>();
  delete ptr;
}

BOOST_AUTO_TEST_CASE(gettersAndSetters)
{
  ModelTrainerExt<float> trainer;

  // Test defaults
  BOOST_CHECK_EQUAL(trainer.getBatchSize(), 1);
  BOOST_CHECK_EQUAL(trainer.getMemorySize(), 1);
  BOOST_CHECK_EQUAL(trainer.getNEpochsTraining(), 0);
  BOOST_CHECK_EQUAL(trainer.getNEpochsValidation(), 0);
  BOOST_CHECK_EQUAL(trainer.getNEpochsEvaluation(), 0);
  BOOST_CHECK_EQUAL(trainer.getVerbosityLevel(), 0);
  BOOST_CHECK_EQUAL(trainer.getNTBPTTSteps(), -1);
  BOOST_CHECK_EQUAL(trainer.getNTETTSteps(), -1);
  BOOST_CHECK_EQUAL(trainer.getLogTraining(), false);
  BOOST_CHECK_EQUAL(trainer.getLogValidation(), false);
  BOOST_CHECK_EQUAL(trainer.getLogEvaluation(), false);
  BOOST_CHECK_EQUAL(trainer.getFindCycles(), true);
  BOOST_CHECK_EQUAL(trainer.getFastInterpreter(), false);
  BOOST_CHECK_EQUAL(trainer.getPreserveOoO(), true);
  BOOST_CHECK_EQUAL(trainer.getLossFunctionHelpers().size(), 0);
  BOOST_CHECK_EQUAL(trainer.getMetricFunctionHelpers().size(), 0);
  BOOST_CHECK_EQUAL(trainer.getInterpretModel(), true);
  BOOST_CHECK_EQUAL(trainer.getResetModel(), true);
  BOOST_CHECK_EQUAL(trainer.getResetInterpreter(), true);

  // Test setters/getters
  trainer.setBatchSize(4);
  trainer.setMemorySize(1);
  trainer.setNEpochsTraining(100);
	trainer.setNEpochsValidation(10);
	trainer.setNEpochsEvaluation(2);
	trainer.setVerbosityLevel(1);
	trainer.setLogging(true, true, true);
	trainer.setNTBPTTSteps(1);
	trainer.setNTETTSteps(2);
  trainer.setFindCycles(false);
  trainer.setFastInterpreter(true);
  trainer.setPreserveOoO(false);
  trainer.setInterpretModel(false);
  trainer.setResetModel(false);
  trainer.setResetInterpreter(false);

  BOOST_CHECK_EQUAL(trainer.getBatchSize(), 4);
  BOOST_CHECK_EQUAL(trainer.getMemorySize(), 1);
  BOOST_CHECK_EQUAL(trainer.getNEpochsTraining(), 100);
	BOOST_CHECK_EQUAL(trainer.getNEpochsValidation(), 10);
	BOOST_CHECK_EQUAL(trainer.getNEpochsEvaluation(), 2);
	BOOST_CHECK_EQUAL(trainer.getVerbosityLevel(), 1);
	BOOST_CHECK_EQUAL(trainer.getNTBPTTSteps(), 1);
	BOOST_CHECK_EQUAL(trainer.getNTETTSteps(), 2);
  BOOST_CHECK_EQUAL(trainer.getLogTraining(), true);
  BOOST_CHECK_EQUAL(trainer.getLogValidation(), true);
  BOOST_CHECK_EQUAL(trainer.getLogEvaluation(), true);
  BOOST_CHECK_EQUAL(trainer.getFindCycles(), false);
  BOOST_CHECK_EQUAL(trainer.getFastInterpreter(), true);
  BOOST_CHECK_EQUAL(trainer.getPreserveOoO(), false);
  BOOST_CHECK_EQUAL(trainer.getInterpretModel(), false);
  BOOST_CHECK_EQUAL(trainer.getResetModel(), false);
  BOOST_CHECK_EQUAL(trainer.getResetInterpreter(), false);

  // Test loss and metric function getters and setters
  std::vector<LossFunctionHelper<float>> loss_function_helpers;
  LossFunctionHelper<float> loss_function_helper1, loss_function_helper2, loss_function_helper3;
  loss_function_helper1.output_nodes_ = { "Output000000000000", "Output00000000001", "Output000000000002" };
  loss_function_helper1.loss_functions_ = { std::make_shared<BCEWithLogitsLossOp<float>>(BCEWithLogitsLossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_ = { std::make_shared<BCEWithLogitsLossGradOp<float>>(BCEWithLogitsLossGradOp<float>(1e-6, 1.0)) };
  loss_function_helpers.push_back(loss_function_helper1);
  loss_function_helper2.output_nodes_ = { "Mu000000000000", "Mu00000000001" };
  loss_function_helper2.loss_functions_ = { std::make_shared<KLDivergenceMuLossOp<float>>(KLDivergenceMuLossOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helper2.loss_function_grads_ = { std::make_shared<KLDivergenceMuLossGradOp<float>>(KLDivergenceMuLossGradOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helpers.push_back(loss_function_helper2);
  loss_function_helper3.output_nodes_ = { "Var000000000000", "Var00000000001" };
  loss_function_helper3.loss_functions_ = { std::make_shared<KLDivergenceLogVarLossOp<float>>(KLDivergenceLogVarLossOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helper3.loss_function_grads_ = { std::make_shared<KLDivergenceLogVarLossGradOp<float>>(KLDivergenceLogVarLossGradOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helpers.push_back(loss_function_helper3);

  std::vector<MetricFunctionHelper<float>> metric_function_helpers;
  MetricFunctionHelper<float> metric_function_helper1;
  metric_function_helper1.output_nodes_ = { "Output000000000000", "Output00000000001", "Output000000000002" };
  metric_function_helper1.metric_functions_ = { std::make_shared<CosineSimilarityOp<float>>(CosineSimilarityOp<float>("Mean")), std::make_shared<CosineSimilarityOp<float>>(CosineSimilarityOp<float>("Var")),
    std::make_shared<PearsonROp<float>>(PearsonROp<float>("Mean")), std::make_shared<PearsonROp<float>>(PearsonROp<float>("Var")) };
  metric_function_helper1.metric_names_ = { "CosineSimilarity-Mean", "CosineSimilarity-Var", "PearsonR-Mean", "PearsonR-Var" };
  metric_function_helpers.push_back(metric_function_helper1);

  trainer.setLossFunctionHelpers(loss_function_helpers);
  trainer.setMetricFunctionHelpers(metric_function_helpers);
  BOOST_CHECK(trainer.getLossOutputNodesLinearized() == std::vector<std::string>({ "Output000000000000", "Output00000000001", "Output000000000002",
    "Mu000000000000", "Mu00000000001", "Var000000000000", "Var00000000001" }));
  BOOST_CHECK(trainer.getMetricOutputNodesLinearized() == std::vector<std::string>({ "Output000000000000", "Output00000000001", "Output000000000002" }));
  BOOST_CHECK(trainer.getMetricNamesLinearized() == std::vector<std::string>({ "CosineSimilarity-Mean", "CosineSimilarity-Var", "PearsonR-Mean", "PearsonR-Var" }));
  BOOST_CHECK_EQUAL(trainer.getNLossFunctions(), 3);
  BOOST_CHECK_EQUAL(trainer.getNMetricFunctions(), 4);
}

BOOST_AUTO_TEST_CASE(checkInputData)
{
  ModelTrainerExt<float> trainer;
  trainer.setBatchSize(4);
  trainer.setMemorySize(1);
  trainer.setNEpochsTraining(100);
	trainer.setNEpochsValidation(100);

  const std::vector<std::string> input_nodes = {"0", "1", "6", "7"};
  Eigen::Tensor<float, 4> input_data(trainer.getBatchSize(), trainer.getMemorySize(), (int)input_nodes.size(), trainer.getNEpochsTraining());

  BOOST_CHECK(trainer.checkInputData(trainer.getNEpochsTraining(),
    input_data, trainer.getBatchSize(), trainer.getMemorySize(), input_nodes));

  BOOST_CHECK(!trainer.checkInputData(90,
    input_data, trainer.getBatchSize(), trainer.getMemorySize(), input_nodes));

  const std::vector<std::string> input_nodes2 = {"0", "1"};
  BOOST_CHECK(!trainer.checkInputData(trainer.getNEpochsTraining(),
    input_data, trainer.getBatchSize(), trainer.getMemorySize(), input_nodes2));

  BOOST_CHECK(!trainer.checkInputData(trainer.getNEpochsTraining(),
    input_data, trainer.getBatchSize(), 3, input_nodes));

  BOOST_CHECK(!trainer.checkInputData(trainer.getNEpochsTraining(),
    input_data, 3, trainer.getMemorySize(), input_nodes));
}

BOOST_AUTO_TEST_CASE(checkOutputData)
{
  ModelTrainerExt<float> trainer;
  trainer.setBatchSize(4);
  trainer.setMemorySize(1);
  trainer.setNEpochsTraining(100);

  const std::vector<std::string> output_nodes = {"4", "5"};
  Eigen::Tensor<float, 4> output_data(trainer.getBatchSize(), trainer.getMemorySize(), (int)output_nodes.size(), trainer.getNEpochsTraining());

  BOOST_CHECK(trainer.checkOutputData(trainer.getNEpochsTraining(),
    output_data, trainer.getBatchSize(), trainer.getMemorySize(), output_nodes));

  BOOST_CHECK(!trainer.checkOutputData(90,
    output_data, trainer.getBatchSize(), trainer.getMemorySize(), output_nodes));

  const std::vector<std::string> output_nodes2 = {"0"};
  BOOST_CHECK(!trainer.checkOutputData(trainer.getNEpochsTraining(),
    output_data, trainer.getBatchSize(), trainer.getMemorySize(), output_nodes2));

  BOOST_CHECK(!trainer.checkOutputData(trainer.getNEpochsTraining(),
    output_data, 3, trainer.getMemorySize(), output_nodes));

	BOOST_CHECK(!trainer.checkOutputData(trainer.getNEpochsTraining(),
		output_data, trainer.getBatchSize(), 0, output_nodes));
}

BOOST_AUTO_TEST_CASE(checkLossFunctions)
{
  ModelTrainerExt<float> model_trainer;
  BOOST_CHECK(!model_trainer.checkLossFunctions());

  std::vector<LossFunctionHelper<float>> loss_function_helpers;
  LossFunctionHelper<float> loss_function_helper1, loss_function_helper2, loss_function_helper3;
  loss_function_helper1.output_nodes_ = { "Output000000000000", "Output00000000001", "Output000000000002" };
  loss_function_helper1.loss_functions_ = { std::make_shared<BCEWithLogitsLossOp<float>>(BCEWithLogitsLossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_ = { std::make_shared<BCEWithLogitsLossGradOp<float>>(BCEWithLogitsLossGradOp<float>(1e-6, 1.0)) };
  loss_function_helpers.push_back(loss_function_helper1);
  loss_function_helper2.output_nodes_ = { "Mu000000000000", "Mu00000000001" };
  loss_function_helper2.loss_functions_ = { std::make_shared<KLDivergenceMuLossOp<float>>(KLDivergenceMuLossOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helper2.loss_function_grads_ = { std::make_shared<KLDivergenceMuLossGradOp<float>>(KLDivergenceMuLossGradOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helpers.push_back(loss_function_helper2);
  loss_function_helper3.output_nodes_ = { "Var000000000000", "Var00000000001" };
  loss_function_helper3.loss_functions_ = { std::make_shared<KLDivergenceLogVarLossOp<float>>(KLDivergenceLogVarLossOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helper3.loss_function_grads_ = { std::make_shared<KLDivergenceLogVarLossGradOp<float>>(KLDivergenceLogVarLossGradOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helpers.push_back(loss_function_helper3);
  model_trainer.setLossFunctionHelpers(loss_function_helpers);
  BOOST_CHECK(model_trainer.checkLossFunctions());

  loss_function_helpers.clear();
  loss_function_helper1.output_nodes_ = { "Output000000000000", "Output00000000001", "Output000000000002" };
  loss_function_helper1.loss_functions_ = { std::make_shared<BCEWithLogitsLossOp<float>>(BCEWithLogitsLossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_.clear();
  loss_function_helpers.push_back(loss_function_helper1);
  loss_function_helper2.output_nodes_ = { "Mu000000000000", "Mu00000000001" };
  loss_function_helper2.loss_functions_ = { std::make_shared<KLDivergenceMuLossOp<float>>(KLDivergenceMuLossOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helper2.loss_function_grads_ = { std::make_shared<KLDivergenceMuLossGradOp<float>>(KLDivergenceMuLossGradOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helpers.push_back(loss_function_helper2);
  loss_function_helper3.output_nodes_ = { "Var000000000000", "Var00000000001" };
  loss_function_helper3.loss_functions_ = { std::make_shared<KLDivergenceLogVarLossOp<float>>(KLDivergenceLogVarLossOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helper3.loss_function_grads_ = { std::make_shared<KLDivergenceLogVarLossGradOp<float>>(KLDivergenceLogVarLossGradOp<float>(1e-6, 0.0, 0.0)) };
  loss_function_helpers.push_back(loss_function_helper3);
  model_trainer.setLossFunctionHelpers(loss_function_helpers);
  BOOST_CHECK(!model_trainer.checkLossFunctions());
}

BOOST_AUTO_TEST_CASE(checkMetricFunctions)
{
  ModelTrainerExt<float> model_trainer;
  BOOST_CHECK(model_trainer.checkMetricFunctions()); // NOTE: changed to not fail

  std::vector<MetricFunctionHelper<float>> metric_function_helpers;
  MetricFunctionHelper<float> metric_function_helper1;
  metric_function_helper1.output_nodes_ = { "Output000000000000", "Output00000000001", "Output000000000002" };
  metric_function_helper1.metric_functions_ = { std::make_shared<CosineSimilarityOp<float>>(CosineSimilarityOp<float>("Mean")), std::make_shared<CosineSimilarityOp<float>>(CosineSimilarityOp<float>("Var")),
    std::make_shared<PearsonROp<float>>(PearsonROp<float>("Mean")), std::make_shared<PearsonROp<float>>(PearsonROp<float>("Var")) };
  metric_function_helper1.metric_names_ = { "CosineSimilarity-Mean", "CosineSimilarity-Var", "PearsonR-Mean", "PearsonR-Var" };
  metric_function_helpers.push_back(metric_function_helper1);
  model_trainer.setMetricFunctionHelpers(metric_function_helpers);
  BOOST_CHECK(model_trainer.checkMetricFunctions());

  metric_function_helpers.clear();
  metric_function_helper1.output_nodes_ = { "Output000000000000", "Output00000000001", "Output000000000002" };
  metric_function_helper1.metric_functions_ = { std::make_shared<CosineSimilarityOp<float>>(CosineSimilarityOp<float>("Mean")), std::make_shared<CosineSimilarityOp<float>>(CosineSimilarityOp<float>("Var")),
    std::make_shared<PearsonROp<float>>(PearsonROp<float>("Mean")), std::make_shared<PearsonROp<float>>(PearsonROp<float>("Var")) };
  metric_function_helper1.metric_names_.clear();
  metric_function_helpers.push_back(metric_function_helper1);
  model_trainer.setMetricFunctionHelpers(metric_function_helpers);
  BOOST_CHECK(!model_trainer.checkMetricFunctions());
}

BOOST_AUTO_TEST_CASE(reduceLROnPlateau)
{
  ModelTrainerDefaultDevice<float> trainer;
  std::vector<float> model_errors1 = { 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
  float lr_new1 = trainer.reduceLROnPlateau(model_errors1, 0.1, 10, 3, 0.1);
  BOOST_CHECK_CLOSE(lr_new1, 1.0, 1e-3);

  std::vector<float> model_errors2 = { 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5 };
  float lr_new2a = trainer.reduceLROnPlateau(model_errors2, 0.1, 11, 3, 0.1);
  BOOST_CHECK_CLOSE(lr_new2a, 1.0, 1e-3); // Too large of `n_epoch_avg` param
  float lr_new2 = trainer.reduceLROnPlateau(model_errors2, 0.1, 10, 3, 0.1);
  BOOST_CHECK_CLOSE(lr_new2, 0.1, 1e-3);
}

template<typename TensorT>
class DAGToyModelTrainer : public ModelTrainerDefaultDevice<TensorT>
{
public:
	Model<TensorT> makeModel()
	{
		// CopyNPasted from Model_DAG_Test
		Node<TensorT> i1, i2, h1, h2, o1, o2, b1, b2;
		Link l1, l2, l3, l4, lb1, lb2, l5, l6, l7, l8, lb3, lb4;
		Weight<TensorT> w1, w2, w3, w4, wb1, wb2, w5, w6, w7, w8, wb3, wb4;
		Model<TensorT> model1;

		// Toy network: 1 hidden layer, fully connected, DAG
		i1 = Node<TensorT>("0", NodeType::input, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		i2 = Node<TensorT>("1", NodeType::input, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		h1 = Node<TensorT>("2", NodeType::hidden, NodeStatus::initialized, std::make_shared<ReLUOp<float>>(ReLUOp<float>()), std::make_shared<ReLUGradOp<float>>(ReLUGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		h2 = Node<TensorT>("3", NodeType::hidden, NodeStatus::initialized, std::make_shared<ReLUOp<float>>(ReLUOp<float>()), std::make_shared<ReLUGradOp<float>>(ReLUGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		o1 = Node<TensorT>("4", NodeType::output, NodeStatus::initialized, std::make_shared<ReLUOp<float>>(ReLUOp<float>()), std::make_shared<ReLUGradOp<float>>(ReLUGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		o2 = Node<TensorT>("5", NodeType::output, NodeStatus::initialized, std::make_shared<ReLUOp<float>>(ReLUOp<float>()), std::make_shared<ReLUGradOp<float>>(ReLUGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		b1 = Node<TensorT>("6", NodeType::bias, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		b2 = Node<TensorT>("7", NodeType::bias, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));

		// weights
		std::shared_ptr<WeightInitOp<TensorT>> weight_init;
		std::shared_ptr<SolverOp<TensorT>> solver;
		// weight_init.reset(new RandWeightInitOp(1.0)); // No random init for testing
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w1 = Weight<TensorT>("0", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w2 = Weight<TensorT>("1", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w3 = Weight<TensorT>("2", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w4 = Weight<TensorT>("3", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		wb1 = Weight<TensorT>("4", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		wb2 = Weight<TensorT>("5", weight_init, solver);
		// input layer + bias
		l1 = Link("0", "0", "2", "0");
		l2 = Link("1", "0", "3", "1");
		l3 = Link("2", "1", "2", "2");
		l4 = Link("3", "1", "3", "3");
		lb1 = Link("4", "6", "2", "4");
		lb2 = Link("5", "6", "3", "5");
		// weights
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w5 = Weight<TensorT>("6", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w6 = Weight<TensorT>("7", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w7 = Weight<TensorT>("8", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		w8 = Weight<TensorT>("9", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		wb3 = Weight<TensorT>("10", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<float>(0.001, 0.9, 0.999, 1e-8));
		wb4 = Weight<TensorT>("11", weight_init, solver);
		// hidden layer + bias
		l5 = Link("6", "2", "4", "6");
		l6 = Link("7", "2", "5", "7");
		l7 = Link("8", "3", "4", "8");
		l8 = Link("9", "3", "5", "9");
		lb3 = Link("10", "7", "4", "10");
		lb4 = Link("11", "7", "5", "11");
		model1.setId(1);
		model1.addNodes({ i1, i2, h1, h2, o1, o2, b1, b2 });
		model1.addWeights({ w1, w2, w3, w4, wb1, wb2, w5, w6, w7, w8, wb3, wb4 });
		model1.addLinks({ l1, l2, l3, l4, lb1, lb2, l5, l6, l7, l8, lb3, lb4 });
    model1.setInputAndOutputNodes();
		return model1;
	}
};

BOOST_AUTO_TEST_CASE(DAGToy1)
{
  // Define the makeModel and trainModel scripts
  DAGToyModelTrainer<float> trainer;

	// Define the model resources
	ModelResources model_resources = { ModelDevice(0, 1) };

  // Test parameters
  trainer.setBatchSize(4);
  trainer.setMemorySize(1);
  trainer.setNEpochsTraining(20);
	trainer.setNEpochsValidation(20);
	trainer.setLogging(false, false);
  const std::vector<std::string> input_nodes = {"0", "1", "6", "7"}; // true inputs + biases
  const std::vector<std::string> output_nodes = {"4", "5"};
  std::vector<LossFunctionHelper<float>> loss_function_helpers;
  LossFunctionHelper<float> loss_function_helper1;
  loss_function_helper1.output_nodes_ = output_nodes;
  loss_function_helper1.loss_functions_ = { std::make_shared<MSELossOp<float>>(MSELossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_ = { std::make_shared<MSELossGradOp<float>>(MSELossGradOp<float>(1e-6, 1.0)) };
  loss_function_helpers.push_back(loss_function_helper1);
  trainer.setLossFunctionHelpers(loss_function_helpers);

  // Make the input data
  Eigen::Tensor<float, 4> input_data(trainer.getBatchSize(), trainer.getMemorySize(), (int)input_nodes.size(), trainer.getNEpochsTraining());
  Eigen::Tensor<float, 3> input_tmp(trainer.getBatchSize(), trainer.getMemorySize(), (int)input_nodes.size());
  input_tmp.setValues({{{1, 5, 1, 1}}, {{2, 6, 1, 1}}, {{3, 7, 1, 1}}, {{4, 8, 1, 1}}});
  for (int batch_iter=0; batch_iter<trainer.getBatchSize(); ++batch_iter)
    for (int memory_iter=0; memory_iter<trainer.getMemorySize(); ++memory_iter)
      for (int nodes_iter=0; nodes_iter<(int)input_nodes.size(); ++nodes_iter)
        for (int epochs_iter=0; epochs_iter<trainer.getNEpochsTraining(); ++epochs_iter)
          input_data(batch_iter, memory_iter, nodes_iter, epochs_iter) = input_tmp(batch_iter, memory_iter, nodes_iter);

  // Make the output data
  Eigen::Tensor<float, 4> output_data(trainer.getBatchSize(), trainer.getMemorySize(), (int)output_nodes.size(), trainer.getNEpochsTraining());
  Eigen::Tensor<float, 2> output_tmp(trainer.getBatchSize(), (int)output_nodes.size());
  output_tmp.setValues({{0, 1}, {0, 1}, {0, 1}, {0, 1}});
  for (int batch_iter=0; batch_iter<trainer.getBatchSize(); ++batch_iter){
		for (int memory_iter = 0; memory_iter<trainer.getMemorySize(); ++memory_iter){
			for (int nodes_iter=0; nodes_iter<(int)output_nodes.size(); ++nodes_iter){
				for (int epochs_iter=0; epochs_iter<trainer.getNEpochsTraining(); ++epochs_iter){
					if (memory_iter == 0)
						output_data(batch_iter, memory_iter, nodes_iter, epochs_iter) = output_tmp(batch_iter, nodes_iter);
					else
						output_data(batch_iter, memory_iter, nodes_iter, epochs_iter) = 0.0;
				}
			}
		}
	}

  // Make the simulation time_steps
	Eigen::Tensor<float, 3> time_steps(trainer.getBatchSize(), trainer.getMemorySize(), trainer.getNEpochsTraining());
	Eigen::Tensor<float, 2> time_steps_tmp(trainer.getBatchSize(), trainer.getMemorySize());
	time_steps_tmp.setValues({
		{ 1 },
		{ 1 },
		{ 1 },
		{ 1 }}
	);
	for (int batch_iter = 0; batch_iter<trainer.getBatchSize(); ++batch_iter)
		for (int memory_iter = 0; memory_iter<trainer.getMemorySize(); ++memory_iter)
			for (int epochs_iter = 0; epochs_iter<trainer.getNEpochsTraining(); ++epochs_iter)
				time_steps(batch_iter, memory_iter, epochs_iter) = time_steps_tmp(batch_iter, memory_iter);

  Model<float> model1 = trainer.makeModel();
  trainer.trainModel(model1, input_data, output_data, time_steps,
    input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

  const Eigen::Tensor<float, 0> total_error = model1.getError().sum();
  BOOST_CHECK(total_error(0) <= 757.0);

	std::vector<float> validation_errors = trainer.validateModel(model1, input_data, output_data, time_steps,
		input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

	const Eigen::Tensor<float, 0> total_error2 = model1.getError().sum();
	BOOST_CHECK(total_error2(0) <= 757.0);
	BOOST_CHECK(validation_errors[0] <= 757.0);

	// TODO evaluateModel
}

BOOST_AUTO_TEST_CASE(DAGToy2)
{
  // Define the makeModel and trainModel scripts
  DAGToyModelTrainer<float> trainer;

  // Define the model resources
  ModelResources model_resources = { ModelDevice(0, 1) };

  // Test parameters
  trainer.setBatchSize(4);
  trainer.setMemorySize(1);
  trainer.setNEpochsTraining(20);
  trainer.setNEpochsValidation(20);
  trainer.setLogging(false, false);
  const std::vector<std::string> input_nodes = { "0", "1", "6", "7" }; // true inputs + biases
  const std::vector<std::string> output_nodes = { "4", "5" };

  std::vector<LossFunctionHelper<float>> loss_function_helpers;
  LossFunctionHelper<float> loss_function_helper1;
  loss_function_helper1.output_nodes_ = output_nodes;
  loss_function_helper1.loss_functions_ = { std::make_shared<MSELossOp<float>>(MSELossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_ = { std::make_shared<MSELossGradOp<float>>(MSELossGradOp<float>(1e-6, 1.0)) };
  loss_function_helpers.push_back(loss_function_helper1);
  trainer.setLossFunctionHelpers(loss_function_helpers);

  std::vector<MetricFunctionHelper<float>> metric_function_helpers;
  MetricFunctionHelper<float> metric_function_helper1;
  metric_function_helper1.output_nodes_ = output_nodes;
  metric_function_helper1.metric_functions_ = { std::make_shared<MAEOp<float>>(MAEOp<float>("Sum")) };
  metric_function_helper1.metric_names_ = { "MAE"};
  metric_function_helpers.push_back(metric_function_helper1);
  trainer.setMetricFunctionHelpers(metric_function_helpers);

  DataSimulatorDAGToy<float> data_simulator;

  Model<float> model1 = trainer.makeModel();
  std::pair<std::vector<float>, std::vector<float>> errors = trainer.trainModel(model1, data_simulator,
    input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

  const Eigen::Tensor<float, 0> total_error = model1.getError().sum();
  BOOST_CHECK_LE(total_error(0), 757.0);
  BOOST_CHECK_LE(errors.first.back(), 757.0);
  BOOST_CHECK_LE(errors.second.back(), 486.0);

  std::pair<std::vector<float>, std::vector<float>> validation_errors = trainer.validateModel(model1, data_simulator,
    input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

  const Eigen::Tensor<float, 0> total_error_validation = model1.getError().sum();
  BOOST_CHECK_LE(total_error_validation(0), 749.853395);
  BOOST_CHECK_LE(validation_errors.first.back(), 749.853395);
  BOOST_CHECK_LE(validation_errors.second.back(), 455.849305);

  // TODO evaluateModel
}

template<typename TensorT>
class DCGToyModelTrainer : public ModelTrainerDefaultDevice<TensorT>
{
public:
	Model<TensorT> makeModel()
	{
		// CopyNPasted from Model_DCG_Test
		Node<TensorT> i1, h1, o1, b1, b2;
		Link l1, l2, l3, lb1, lb2;
		Weight<TensorT> w1, w2, w3, wb1, wb2;
		Model<TensorT> model2;
		// Toy network: 1 hidden layer, fully connected, DCG
		i1 = Node<TensorT>("0", NodeType::input, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		h1 = Node<TensorT>("1", NodeType::hidden, NodeStatus::initialized, std::make_shared<ELUOp<float>>(ELUOp<float>()), std::make_shared<ELUGradOp<float>>(ELUGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		o1 = Node<TensorT>("2", NodeType::output, NodeStatus::initialized, std::make_shared<ELUOp<float>>(ELUOp<float>()), std::make_shared<ELUGradOp<float>>(ELUGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		b1 = Node<TensorT>("3", NodeType::bias, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		b2 = Node<TensorT>("4", NodeType::bias, NodeStatus::activated, std::make_shared<LinearOp<float>>(LinearOp<float>()), std::make_shared<LinearGradOp<float>>(LinearGradOp<float>()), std::make_shared<SumOp<float>>(SumOp<float>()), std::make_shared<SumErrorOp<float>>(SumErrorOp<float>()), std::make_shared<SumWeightGradOp<float>>(SumWeightGradOp<float>()));
		// weights
		std::shared_ptr<WeightInitOp<TensorT>> weight_init;
		std::shared_ptr<SolverOp<TensorT>> solver;
		// weight_init.reset(new RandWeightInitOp(1.0)); // No random init for testing
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<TensorT>(0.01, 0.9, 0.999, 1e-8));
		w1 = Weight<TensorT>("0", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<TensorT>(0.01, 0.9, 0.999, 1e-8));
		w2 = Weight<TensorT>("1", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<TensorT>(0.01, 0.9, 0.999, 1e-8));
		w3 = Weight<TensorT>("2", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<TensorT>(0.01, 0.9, 0.999, 1e-8));
		wb1 = Weight<TensorT>("3", weight_init, solver);
		weight_init = std::make_shared<ConstWeightInitOp<TensorT>>(ConstWeightInitOp<TensorT>(1.0));
		solver.reset(new AdamOp<TensorT>(0.01, 0.9, 0.999, 1e-8));
		wb2 = Weight<TensorT>("4", weight_init, solver);
		weight_init.reset();
		solver.reset();
		// links
		l1 = Link("0", "0", "1", "0");
		l2 = Link("1", "1", "2", "1");
		l3 = Link("2", "1", "1", "2");
		lb1 = Link("3", "3", "1", "3");
		lb2 = Link("4", "4", "2", "4");
		model2.setId(2);
		model2.addNodes({ i1, h1, o1, b1, b2 });
		model2.addWeights({ w1, w2, w3, wb1, wb2 });
		model2.addLinks({ l1, l2, l3, lb1, lb2 });
    model2.setInputAndOutputNodes();
		return model2;
	}
};

BOOST_AUTO_TEST_CASE(DCGToy1)
{
  // Define the makeModel and trainModel scripts
  DCGToyModelTrainer<float> trainer;

	// Define the model resources
	ModelResources model_resources = { ModelDevice(0, 1) };

  // Test parameters
  trainer.setBatchSize(5);
  trainer.setMemorySize(8);
  trainer.setNEpochsTraining(50);
	trainer.setNEpochsValidation(50);
  const std::vector<std::string> input_nodes = {"0", "3", "4"}; // true inputs + biases
  const std::vector<std::string> output_nodes = {"2"};

  std::vector<LossFunctionHelper<float>> loss_function_helpers;
  LossFunctionHelper<float> loss_function_helper1;
  loss_function_helper1.output_nodes_ = output_nodes;
  loss_function_helper1.loss_functions_ = { std::make_shared<MSELossOp<float>>(MSELossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_ = { std::make_shared<MSELossGradOp<float>>(MSELossGradOp<float>(1e-6, 1.0)) };
  loss_function_helpers.push_back(loss_function_helper1);
  trainer.setLossFunctionHelpers(loss_function_helpers);

  // Make the input data
  Eigen::Tensor<float, 4> input_data(trainer.getBatchSize(), trainer.getMemorySize(), (int)input_nodes.size(), trainer.getNEpochsTraining());
  Eigen::Tensor<float, 3> input_tmp(trainer.getBatchSize(), trainer.getMemorySize(), (int)input_nodes.size());
  input_tmp.setValues(
		{ {{8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}, {3, 0, 0}, {2, 0, 0}, {1, 0, 0}},
		{{9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}, {3, 0, 0}, {2, 0, 0}},
		{{10, 0, 0}, {9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}, {3, 0, 0}},
		{{11, 0, 0}, {10, 0, 0}, {9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}, {4, 0, 0}},
		{{12, 0, 0}, {11, 0, 0}, {10, 0, 0}, {9, 0, 0}, {8, 0, 0}, {7, 0, 0}, {6, 0, 0}, {5, 0, 0}} }
  );
  for (int batch_iter=0; batch_iter<trainer.getBatchSize(); ++batch_iter)
    for (int memory_iter=0; memory_iter<trainer.getMemorySize(); ++memory_iter)
      for (int nodes_iter=0; nodes_iter<(int)input_nodes.size(); ++nodes_iter)
        for (int epochs_iter=0; epochs_iter<trainer.getNEpochsTraining(); ++epochs_iter)
          input_data(batch_iter, memory_iter, nodes_iter, epochs_iter) = input_tmp(batch_iter, memory_iter, nodes_iter);

  // Make the output data
  Eigen::Tensor<float, 4> output_data(trainer.getBatchSize(), trainer.getMemorySize(), (int)output_nodes.size(), trainer.getNEpochsTraining());
  Eigen::Tensor<float, 3> output_tmp(trainer.getBatchSize(), trainer.getMemorySize(), (int)output_nodes.size());
  output_tmp.setValues(
		{ { { 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 },{ 1 },{ 1 } },
		{ { 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 },{ 1 } },
		{ { 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 },{ 2 } },
		{ { 6 },{ 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 },{ 2 } },
		{ { 6 },{ 6 },{ 5 },{ 5 },{ 4 },{ 4 },{ 3 },{ 3 } } });
  for (int batch_iter=0; batch_iter<trainer.getBatchSize(); ++batch_iter)
		for (int memory_iter = 0; memory_iter<trainer.getMemorySize(); ++memory_iter)
			for (int nodes_iter=0; nodes_iter<(int)output_nodes.size(); ++nodes_iter)
				for (int epochs_iter=0; epochs_iter<trainer.getNEpochsTraining(); ++epochs_iter)
					output_data(batch_iter, memory_iter, nodes_iter, epochs_iter) = output_tmp(batch_iter, memory_iter, nodes_iter);

  // Make the simulation time_steps
  Eigen::Tensor<float, 3> time_steps(trainer.getBatchSize(), trainer.getMemorySize(), trainer.getNEpochsTraining());
  Eigen::Tensor<float, 2> time_steps_tmp(trainer.getBatchSize(), trainer.getMemorySize());
  time_steps_tmp.setValues({
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1},
    {1, 1, 1, 1, 1, 1, 1, 1}}
  );
  for (int batch_iter=0; batch_iter<trainer.getBatchSize(); ++batch_iter)
    for (int memory_iter=0; memory_iter<trainer.getMemorySize(); ++memory_iter)
      for (int epochs_iter=0; epochs_iter<trainer.getNEpochsTraining(); ++epochs_iter)
        time_steps(batch_iter, memory_iter, epochs_iter) = time_steps_tmp(batch_iter, memory_iter);

  Model<float> model1 = trainer.makeModel();

  trainer.trainModel(model1, input_data, output_data, time_steps,
    input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

  const Eigen::Tensor<float, 0> total_error = model1.getError().sum();
  BOOST_CHECK(total_error(0) <= 1492.6);

	std::vector<float> validation_errors = trainer.validateModel(model1, input_data, output_data, time_steps,
		input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

	const Eigen::Tensor<float, 0> total_error2 = model1.getError().sum();
	BOOST_CHECK(total_error2(0) <= 1492.6);
	BOOST_CHECK(validation_errors[0] <= 1492.6);
	// TODO evaluateModel
}

BOOST_AUTO_TEST_CASE(DCGToy2)
{
  // Define the makeModel and trainModel scripts
  DCGToyModelTrainer<float> trainer;

  // Define the model resources
  ModelResources model_resources = { ModelDevice(0, 1) };

  // Test parameters
  trainer.setBatchSize(5);
  trainer.setMemorySize(8);
  trainer.setNEpochsTraining(50);
  trainer.setNEpochsValidation(50);
  const std::vector<std::string> input_nodes = { "0", "3", "4" }; // true inputs + biases
  const std::vector<std::string> output_nodes = { "2" };

  std::vector<LossFunctionHelper<float>> loss_function_helpers;
  LossFunctionHelper<float> loss_function_helper1;
  loss_function_helper1.output_nodes_ = output_nodes;
  loss_function_helper1.loss_functions_ = { std::make_shared<MSELossOp<float>>(MSELossOp<float>(1e-6, 1.0)) };
  loss_function_helper1.loss_function_grads_ = { std::make_shared<MSELossGradOp<float>>(MSELossGradOp<float>(1e-6, 1.0)) };
  loss_function_helpers.push_back(loss_function_helper1);
  trainer.setLossFunctionHelpers(loss_function_helpers);

  std::vector<MetricFunctionHelper<float>> metric_function_helpers;
  MetricFunctionHelper<float> metric_function_helper1;
  metric_function_helper1.output_nodes_ = output_nodes;
  metric_function_helper1.metric_functions_ = { std::make_shared<MAEOp<float>>(MAEOp<float>("Sum")) };
  metric_function_helper1.metric_names_ = { "MAE" };
  metric_function_helpers.push_back(metric_function_helper1);
  trainer.setMetricFunctionHelpers(metric_function_helpers);

  // Make data simulator
  DataSimulatorDCGToy<float> data_simulator;

  Model<float> model1 = trainer.makeModel();

  std::pair<std::vector<float>, std::vector<float>> errors = trainer.trainModel(model1, data_simulator,
    input_nodes, ModelLogger<float>(), ModelInterpreterDefaultDevice<float>(model_resources));

  const Eigen::Tensor<float, 0> total_error2 = model1.getError().sum();
  BOOST_CHECK(total_error2(0) <= 1492.6);
  BOOST_CHECK(errors.first.back() <= 1492.6);
  BOOST_CHECK(errors.second.back() <= 1492.6);
  // TODO evaluateModel
}

BOOST_AUTO_TEST_SUITE_END()