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

#include <boost/hana/foldable/mcd.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/minimal/foldable.hpp>
#include <boost/hana/integral.hpp>
using namespace boost::hana;

template <typename mcd>
void test() {
    constexpr auto foldable = detail::minimal::foldable<mcd>;

    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<0>)) == int_<0>);

    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<0>, int_<1>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<1>, int_<0>)) == int_<0>);

    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<0>, int_<1>, int_<2>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<1>, int_<0>, int_<2>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<1>, int_<2>, int_<0>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<2>, int_<1>, int_<0>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<2>, int_<0>, int_<1>)) == int_<0>);

    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<0>, int_<1>, int_<2>, int_<3>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<1>, int_<0>, int_<2>, int_<3>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<1>, int_<2>, int_<0>, int_<3>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<1>, int_<2>, int_<3>, int_<0>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<2>, int_<1>, int_<3>, int_<0>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<2>, int_<3>, int_<1>, int_<0>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<2>, int_<3>, int_<0>, int_<1>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<3>, int_<2>, int_<0>, int_<1>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<3>, int_<0>, int_<2>, int_<1>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<3>, int_<0>, int_<1>, int_<2>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<0>, int_<2>, int_<3>, int_<1>)) == int_<0>);
    BOOST_HANA_CONSTANT_ASSERT(minimum(foldable(int_<0>, int_<3>, int_<1>, int_<2>)) == int_<0>);

    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0})) == int{0});

    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0}, char{1})) == int{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{1}, char{0})) == char{0});

    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0}, char{1}, long{2})) == int{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{1}, char{0}, long{2})) == char{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{1}, char{2}, long{0})) == long{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{2}, char{1}, long{0})) == long{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{2}, char{0}, long{1})) == char{0});

    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0}, char{1}, long{2}, double{3})) == int{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{1}, char{0}, long{2}, double{3})) == char{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{1}, char{2}, long{0}, double{3})) == long{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{1}, char{2}, long{3}, double{0})) == double{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{2}, char{1}, long{3}, double{0})) == double{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{2}, char{3}, long{1}, double{0})) == double{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{2}, char{3}, long{0}, double{1})) == long{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{3}, char{2}, long{0}, double{1})) == long{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{3}, char{0}, long{2}, double{1})) == char{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{3}, char{0}, long{1}, double{2})) == char{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0}, char{2}, long{3}, double{1})) == int{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0}, char{3}, long{1}, double{2})) == int{0});

    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int{0}, char{1}, long_<2>)) == int{0});
    BOOST_HANA_CONSTEXPR_ASSERT(minimum(foldable(int_<0>, char{1}, long{2})) == int_<0>);
}

int main() {
    test<Foldable::mcd>();
    test<Foldable::unpack_mcd>();
}


/**TODO:  Add copyright*/

#define BOOST_TEST_MODULE MetricFunctionTensor test suite
#include <boost/test/included/unit_test.hpp>
#include <EvoNet/ml/MetricFunctionTensor.h>

#include <iostream>

using namespace EvoNet;
using namespace std;

BOOST_AUTO_TEST_SUITE(MetricFunctionTensor1)

/**
  AccuracyBCOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorAccuracyBCOp)
{
  AccuracyBCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  AccuracyBCTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorAccuracyBCOp)
{
  AccuracyBCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new AccuracyBCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionAccuracyBCOp)
{
  AccuracyBCTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 0, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 0, 1}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  AccuracyMCMicroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorAccuracyMCMicroOp)
{
  AccuracyMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  AccuracyMCMicroTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorAccuracyMCMicroOp)
{
  AccuracyMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new AccuracyMCMicroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionAccuracyMCMicroOp)
{
  AccuracyMCMicroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.75, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  AccuracyMCMacroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorAccuracyMCMacroOp)
{
  AccuracyMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  AccuracyMCMacroTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorAccuracyMCMacroOp)
{
  AccuracyMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new AccuracyMCMacroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionAccuracyMCMacroOp)
{
  AccuracyMCMacroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  PrecisionBCOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPrecisionBCOp)
{
  PrecisionBCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  PrecisionBCTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorPrecisionBCOp)
{
  PrecisionBCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new PrecisionBCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionPrecisionBCOp)
{
  PrecisionBCTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 0, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 0, 1}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.25, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  PrecisionMCMicroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPrecisionMCMicroOp)
{
  PrecisionMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  PrecisionMCMicroTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorPrecisionMCMicroOp)
{
  PrecisionMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new PrecisionMCMicroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionPrecisionMCMicroOp)
{
  PrecisionMCMicroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  PrecisionMCMacroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPrecisionMCMacroOp)
{
  PrecisionMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  PrecisionMCMacroTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorPrecisionMCMacroOp)
{
  PrecisionMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new PrecisionMCMacroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionPrecisionMCMacroOp)
{
  PrecisionMCMacroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  RecallBCOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorRecallBCOp)
{
  RecallBCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  RecallBCTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorRecallBCOp)
{
  RecallBCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new RecallBCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionRecallBCOp)
{
  RecallBCTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 0, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 0, 1}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  RecallMCMicroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorRecallMCMicroOp)
{
  RecallMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  RecallMCMicroTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorRecallMCMicroOp)
{
  RecallMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new RecallMCMicroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionRecallMCMicroOp)
{
  RecallMCMicroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  RecallMCMacroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorRecallMCMacroOp)
{
  RecallMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  RecallMCMacroTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorRecallMCMacroOp)
{
  RecallMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new RecallMCMacroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionRecallMCMacroOp)
{
  RecallMCMacroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  PredictionBiasOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPredictionBiasOp)
{
  PredictionBiasTensorOp<float, Eigen::DefaultDevice>* ptrPredictionBias = nullptr;
  PredictionBiasTensorOp<float, Eigen::DefaultDevice>* nullPointerPredictionBias = nullptr;
  BOOST_CHECK_EQUAL(ptrPredictionBias, nullPointerPredictionBias);
}

BOOST_AUTO_TEST_CASE(destructorPredictionBiasOp)
{
  PredictionBiasTensorOp<float, Eigen::DefaultDevice>* ptrPredictionBias = nullptr;
  ptrPredictionBias = new PredictionBiasTensorOp<float, Eigen::DefaultDevice>();
  delete ptrPredictionBias;
}

BOOST_AUTO_TEST_CASE(operationfunctionPredictionBiasOp)
{
  PredictionBiasTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  F1ScoreBCOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorF1ScoreBCOp)
{
  F1ScoreBCTensorOp<float, Eigen::DefaultDevice>* ptrF1Score = nullptr;
  F1ScoreBCTensorOp<float, Eigen::DefaultDevice>* nullPointerF1Score = nullptr;
  BOOST_CHECK_EQUAL(ptrF1Score, nullPointerF1Score);
}

BOOST_AUTO_TEST_CASE(destructorF1ScoreBCOp)
{
  F1ScoreBCTensorOp<float, Eigen::DefaultDevice>* ptrF1Score = nullptr;
  ptrF1Score = new F1ScoreBCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrF1Score;
}

BOOST_AUTO_TEST_CASE(operationfunctionF1ScoreBCOp)
{
  F1ScoreBCTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 0, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 0, 1}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0, 1e-4); // FIXME
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  F1ScoreMCMicroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorF1ScoreMCMicroOp)
{
  F1ScoreMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrF1Score = nullptr;
  F1ScoreMCMicroTensorOp<float, Eigen::DefaultDevice>* nullPointerF1Score = nullptr;
  BOOST_CHECK_EQUAL(ptrF1Score, nullPointerF1Score);
}

BOOST_AUTO_TEST_CASE(destructorF1ScoreMCMicroOp)
{
  F1ScoreMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrF1Score = nullptr;
  ptrF1Score = new F1ScoreMCMicroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrF1Score;
}

BOOST_AUTO_TEST_CASE(operationfunctionF1ScoreMCMicroOp)
{
  F1ScoreMCMicroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0, 1e-4); // FIXME
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  F1ScoreMCMacroOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorF1ScoreMCMacroOp)
{
  F1ScoreMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrF1Score = nullptr;
  F1ScoreMCMacroTensorOp<float, Eigen::DefaultDevice>* nullPointerF1Score = nullptr;
  BOOST_CHECK_EQUAL(ptrF1Score, nullPointerF1Score);
}

BOOST_AUTO_TEST_CASE(destructorF1ScoreMCMacroOp)
{
  F1ScoreMCMacroTensorOp<float, Eigen::DefaultDevice>* ptrF1Score = nullptr;
  ptrF1Score = new F1ScoreMCMacroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrF1Score;
}

BOOST_AUTO_TEST_CASE(operationfunctionF1ScoreMCMacroOp)
{
  F1ScoreMCMacroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.66666667, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  AUROCOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorAUROCOp)
{
  AUROCTensorOp<float, Eigen::DefaultDevice>* ptrAUROC = nullptr;
  AUROCTensorOp<float, Eigen::DefaultDevice>* nullPointerAUROC = nullptr;
  BOOST_CHECK_EQUAL(ptrAUROC, nullPointerAUROC);
}

BOOST_AUTO_TEST_CASE(destructorAUROCOp)
{
  AUROCTensorOp<float, Eigen::DefaultDevice>* ptrAUROC = nullptr;
  ptrAUROC = new AUROCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrAUROC;
}

BOOST_AUTO_TEST_CASE(operationfunctionAUROCOp)
{
  AUROCTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  MCCBCTensorOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMCCBCTensorOp)
{
  MCCBCTensorOp<float, Eigen::DefaultDevice>* ptrMCC = nullptr;
  MCCBCTensorOp<float, Eigen::DefaultDevice>* nullPointerMCC = nullptr;
  BOOST_CHECK_EQUAL(ptrMCC, nullPointerMCC);
}

BOOST_AUTO_TEST_CASE(destructorMCCBCTensorOp)
{
  MCCBCTensorOp<float, Eigen::DefaultDevice>* ptrMCC = nullptr;
  ptrMCC = new MCCBCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMCC;
}

BOOST_AUTO_TEST_CASE(operationfunctionMCCBCTensorOp)
{
  MCCBCTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  MCCMCMicroTensorOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMCCMCMicroTensorOp)
{
  MCCMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrMCC = nullptr;
  MCCMCMicroTensorOp<float, Eigen::DefaultDevice>* nullPointerMCC = nullptr;
  BOOST_CHECK_EQUAL(ptrMCC, nullPointerMCC);
}

BOOST_AUTO_TEST_CASE(destructorMCCMCMicroTensorOp)
{
  MCCMCMicroTensorOp<float, Eigen::DefaultDevice>* ptrMCC = nullptr;
  ptrMCC = new MCCMCMicroTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMCC;
}

BOOST_AUTO_TEST_CASE(operationfunctionMCCMCMicroTensorOp)
{
  MCCMCMicroTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  MAEOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMAEOp)
{
  MAETensorOp<float, Eigen::DefaultDevice>* ptrMAE = nullptr;
  MAETensorOp<float, Eigen::DefaultDevice>* nullPointerMAE = nullptr;
  BOOST_CHECK_EQUAL(ptrMAE, nullPointerMAE);
}

BOOST_AUTO_TEST_CASE(destructorMAEOp)
{
  MAETensorOp<float, Eigen::DefaultDevice>* ptrMAE = nullptr;
  ptrMAE = new MAETensorOp<float, Eigen::DefaultDevice>();
  delete ptrMAE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMAEOp)
{
  MAETensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  float error_ptr[] = { 0, 0, 0, 0 };
  Eigen::DefaultDevice device;

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, n_metrics, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 1.5, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  CosineSimilarityOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCosineSimilarityOp)
{
  CosineSimilarityTensorOp<float, Eigen::DefaultDevice>* ptrCosineSimilarity = nullptr;
  CosineSimilarityTensorOp<float, Eigen::DefaultDevice>* nullPointerCosineSimilarity = nullptr;
  BOOST_CHECK_EQUAL(ptrCosineSimilarity, nullPointerCosineSimilarity);
}

BOOST_AUTO_TEST_CASE(destructorCosineSimilarityOp)
{
  CosineSimilarityTensorOp<float, Eigen::DefaultDevice>* ptrCosineSimilarity = nullptr;
  ptrCosineSimilarity = new CosineSimilarityTensorOp<float, Eigen::DefaultDevice>();
  delete ptrCosineSimilarity;
}

BOOST_AUTO_TEST_CASE(operationfunctionCosineSimilarityOp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{0, 1, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  CosineSimilarityTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 0.801783681, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  CosineSimilarityTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 0.40089184, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  CosineSimilarityTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 0.321428537, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

/**
  PearsonROp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPearsonROp)
{
  PearsonRTensorOp<float, Eigen::DefaultDevice>* ptrPearsonR = nullptr;
  PearsonRTensorOp<float, Eigen::DefaultDevice>* nullPointerPearsonR = nullptr;
  BOOST_CHECK_EQUAL(ptrPearsonR, nullPointerPearsonR);
}

BOOST_AUTO_TEST_CASE(destructorPearsonROp)
{
  PearsonRTensorOp<float, Eigen::DefaultDevice>* ptrPearsonR = nullptr;
  ptrPearsonR = new PearsonRTensorOp<float, Eigen::DefaultDevice>();
  delete ptrPearsonR;
}

BOOST_AUTO_TEST_CASE(operationfunctionPearsonROp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{2, 3, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  PearsonRTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 0.197246432, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  PearsonRTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 0.0986232162, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  PearsonRTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 0.913880289, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

/**
  EuclideanDistOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorEuclideanDistOp)
{
  EuclideanDistTensorOp<float, Eigen::DefaultDevice>* ptrEuclideanDist = nullptr;
  EuclideanDistTensorOp<float, Eigen::DefaultDevice>* nullPointerEuclideanDist = nullptr;
  BOOST_CHECK_EQUAL(ptrEuclideanDist, nullPointerEuclideanDist);
}

BOOST_AUTO_TEST_CASE(destructorEuclideanDistOp)
{
  EuclideanDistTensorOp<float, Eigen::DefaultDevice>* ptrEuclideanDist = nullptr;
  ptrEuclideanDist = new EuclideanDistTensorOp<float, Eigen::DefaultDevice>();
  delete ptrEuclideanDist;
}

BOOST_AUTO_TEST_CASE(operationfunctionEuclideanDistOp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{2, 3, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  EuclideanDistTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 7.79583168, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  EuclideanDistTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 3.89791584, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  EuclideanDistTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 1.61250567, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

/**
  ManhattanDistOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorManhattanDistOp)
{
  ManhattanDistTensorOp<float, Eigen::DefaultDevice>* ptrManhattanDist = nullptr;
  ManhattanDistTensorOp<float, Eigen::DefaultDevice>* nullPointerManhattanDist = nullptr;
  BOOST_CHECK_EQUAL(ptrManhattanDist, nullPointerManhattanDist);
}

BOOST_AUTO_TEST_CASE(destructorManhattanDistOp)
{
  ManhattanDistTensorOp<float, Eigen::DefaultDevice>* ptrManhattanDist = nullptr;
  ptrManhattanDist = new ManhattanDistTensorOp<float, Eigen::DefaultDevice>();
  delete ptrManhattanDist;
}

BOOST_AUTO_TEST_CASE(operationfunctionManhattanDistOp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{2, 3, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  ManhattanDistTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 14, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  ManhattanDistTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 7, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  ManhattanDistTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 8, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

/**
  JeffreysAndMatusitaDistOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorJeffreysAndMatusitaDistOp)
{
  JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice>* ptrJeffreysAndMatusitaDist = nullptr;
  JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice>* nullPointerJeffreysAndMatusitaDist = nullptr;
  BOOST_CHECK_EQUAL(ptrJeffreysAndMatusitaDist, nullPointerJeffreysAndMatusitaDist);
}

BOOST_AUTO_TEST_CASE(destructorJeffreysAndMatusitaDistOp)
{
  JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice>* ptrJeffreysAndMatusitaDist = nullptr;
  ptrJeffreysAndMatusitaDist = new JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice>();
  delete ptrJeffreysAndMatusitaDist;
}

BOOST_AUTO_TEST_CASE(operationfunctionJeffreysAndMatusitaDistOp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{2, 3, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 4.7389946, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 2.3694973, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  JeffreysAndMatusitaDistTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 0.478435606, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

/**
  LogarithmicDistOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLogarithmicDistOp)
{
  LogarithmicDistTensorOp<float, Eigen::DefaultDevice>* ptrLogarithmicDist = nullptr;
  LogarithmicDistTensorOp<float, Eigen::DefaultDevice>* nullPointerLogarithmicDist = nullptr;
  BOOST_CHECK_EQUAL(ptrLogarithmicDist, nullPointerLogarithmicDist);
}

BOOST_AUTO_TEST_CASE(destructorLogarithmicDistOp)
{
  LogarithmicDistTensorOp<float, Eigen::DefaultDevice>* ptrLogarithmicDist = nullptr;
  ptrLogarithmicDist = new LogarithmicDistTensorOp<float, Eigen::DefaultDevice>();
  delete ptrLogarithmicDist;
}

BOOST_AUTO_TEST_CASE(operationfunctionLogarithmicDistOp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0, 0, 0}, {1, 0, 0, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{2, 3, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  LogarithmicDistTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 3.58351898, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  LogarithmicDistTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 1.79175949, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  LogarithmicDistTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

/**
  PercentDifferenceOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPercentDifferenceOp)
{
  PercentDifferenceTensorOp<float, Eigen::DefaultDevice>* ptrPercentDifference = nullptr;
  PercentDifferenceTensorOp<float, Eigen::DefaultDevice>* nullPointerPercentDifference = nullptr;
  BOOST_CHECK_EQUAL(ptrPercentDifference, nullPointerPercentDifference);
}

BOOST_AUTO_TEST_CASE(destructorPercentDifferenceOp)
{
  PercentDifferenceTensorOp<float, Eigen::DefaultDevice>* ptrPercentDifference = nullptr;
  ptrPercentDifference = new PercentDifferenceTensorOp<float, Eigen::DefaultDevice>();
  delete ptrPercentDifference;
}

BOOST_AUTO_TEST_CASE(operationfunctionPercentDifferenceOp)
{
  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 4;
  const int n_metrics = 2;
  const int time_step = 0;
  const int metric_index = 1;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 1, 1, 1}, {1, 1, 1, 1}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{3, 2, 1, 0}, {0, 0, 0, 0}},
    {{2, 3, 2, 3}, {0, 0, 0, 0}}
    });

  Eigen::DefaultDevice device;

  PercentDifferenceTensorOp<float, Eigen::DefaultDevice> operation_sum(std::string("Sum"));
  Eigen::Tensor<float, 2> error_sum(n_metrics, memory_size); error_sum.setZero();
  operation_sum(y_pred.data(), y_true.data(), error_sum.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_sum(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 0), 9.99999046, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_sum(1, 1), 0, 1e-4);

  PercentDifferenceTensorOp<float, Eigen::DefaultDevice> operation_mean(std::string("Mean"));
  Eigen::Tensor<float, 2> error_mean(n_metrics, memory_size); error_mean.setZero();
  operation_mean(y_pred.data(), y_true.data(), error_mean.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_mean(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 0), 4.99999523, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_mean(1, 1), 0, 1e-4);

  PercentDifferenceTensorOp<float, Eigen::DefaultDevice> operation_var(std::string("Var"));
  Eigen::Tensor<float, 2> error_var(n_metrics, memory_size); error_var.setZero();
  operation_var(y_pred.data(), y_true.data(), error_var.data(), batch_size, memory_size, layer_size, n_metrics, time_step, metric_index, device);
  BOOST_CHECK_CLOSE(error_var(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 0), 2.0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error_var(1, 1), 0, 1e-4);
}

BOOST_AUTO_TEST_SUITE_END()


/*
 * Copyright (c) 2015-2021 Agalmic Ventures LLC (www.agalmicventures.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <cmath>

#include <boost/test/unit_test.hpp>

#include "Werk/Math/DiscreteEma.hpp"

BOOST_AUTO_TEST_SUITE(DiscreteEmaTest)

BOOST_AUTO_TEST_CASE(TestEmpty)
{
	werk::DiscreteEma ema(0.5);

	BOOST_CHECK_EQUAL(ema.alpha(), 0.5);
	BOOST_CHECK(std::isnan(ema.value()));
}

BOOST_AUTO_TEST_CASE(TestConstant)
{
	werk::DiscreteEma ema(0.5);

	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
}

BOOST_AUTO_TEST_CASE(TestDecreasing)
{
	werk::DiscreteEma ema(0.5);

	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);

	ema.sample(0.0);
	BOOST_CHECK(ema.value() < 10.0);

	double value = ema.value();
	ema.sample(0.0);
	BOOST_CHECK(ema.value() < value);
}

BOOST_AUTO_TEST_CASE(TestIncreasing)
{
	werk::DiscreteEma ema(0.5);

	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(13.0);
	BOOST_CHECK(ema.value() > 10);

	double value = ema.value();
	ema.sample(20.0);
	BOOST_CHECK(ema.value() > value);
}

BOOST_AUTO_TEST_CASE(TestOscillating)
{
	werk::DiscreteEma ema(0.5);

	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(0.0);
	BOOST_CHECK_CLOSE(ema.value(), 5.0, 0.5);
	ema.sample(10.0);
	BOOST_CHECK_CLOSE(ema.value(), 7.5, 0.5);
	ema.sample(0.0);
	BOOST_CHECK_CLOSE(ema.value(), 3.75, 0.5);
}

BOOST_AUTO_TEST_CASE(TestNan)
{
	werk::DiscreteEma ema(0.5);

	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(std::numeric_limits<double>::quiet_NaN());
	BOOST_CHECK(std::isnan(ema.value()));
	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
}

BOOST_AUTO_TEST_CASE(TestZeroAlpha)
{
	werk::DiscreteEma ema(0.0);

	BOOST_CHECK(std::isnan(ema.value()));
	ema.sample(10.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(15.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
	ema.sample(2310.0);
	BOOST_CHECK_EQUAL(ema.value(), 10.0);
}

BOOST_AUTO_TEST_SUITE_END()


#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <numeric>
#include <string>
#include <stdio.h>
#include <time.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <unistd.h>

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/strong_components.hpp>
#include <boost/graph/graphviz.hpp>
#include <boost/program_options.hpp>

#include "datastructs/matrix.h"

#include "polynomials/polynomial.h"
#include "polynomials/non_commutative_polynomial.h"

#include "semirings/commutativeRExp.h"
#include "semirings/float-semiring.h"
#include "semirings/pseudo_linear_set.h"
#include "datastructs/finite_automaton.h"
#include "semirings/lossy-finite-automaton.h"

#ifdef OLD_SEMILINEAR_SET
#include "semirings/semilinSetExp.h"
#else
#include "semirings/semilinear_set.h"
#endif

#include "utils/timer.h"

#include "parser.h"

//#include "newton.h"
#include "newton_generic.h"

template <typename SR>
struct VertexProp {
  std::string name;   // used for graphviz output
  VarId var;         // var and rex combines the equations in the vertex
  Polynomial<SR> rex;
};

int64_t timespecDiff(struct timespec *timeA_p, struct timespec *timeB_p)
{
  return ((timeA_p->tv_sec * 1000000000) + timeA_p->tv_nsec) -
           ((timeB_p->tv_sec * 1000000000) + timeB_p->tv_nsec);
}

// group the equations to SCCs
template <typename SR>
std::vector< std::vector< std::pair< VarId, Polynomial<SR> > > >
group_by_scc(const std::vector< std::pair< VarId, Polynomial<SR> > > &equations,
             bool graphviz_output) {
  // create map of variables to [0..n]. this is used to enumerate important variables in a clean way from 0 to n during graph construction
  std::unordered_map<VarId, int> var_key;

  // build the graph
  boost::adjacency_list<boost::vecS,
                        boost::vecS,
                        boost::bidirectionalS,
                        VertexProp<SR>> graph(equations.size());
  // for (auto e_it = equations.begin(); e_it != equations.end(); ++e_it)
  for (const auto &eq : equations) {
    // if the variable is not yet in the map insert it together with the size of
    // the map. this way we get a unique identifier for the variable counting from 0 to n
    if (var_key.find(eq.first) == var_key.end()) {
      var_key.insert(std::make_pair(eq.first, var_key.size()));
    }
    int a = var_key.find(eq.first)->second; // variable key
    graph[a].var = eq.first; // store VarId to the vertex
    graph[a].name = Var::GetVar(eq.first).string(); // store the name to the vertex
    graph[a].rex = eq.second; // store the regular expression to the vertex

    auto variables = eq.second.get_variables(); // all variables of this rule;
    // for (auto v_it = v.begin(); v_it != v.end(); ++v_it)
    for (const auto &var : variables) {
      if (var_key.find(var) == var_key.end()) // variable is not yet in the map
        var_key.insert(var_key.begin(), std::pair<VarId,int>(var,var_key.size()));
      int b = var_key.find(var)->second; // variable key
      boost::add_edge(a, b, graph);
    }
  }

  if (graphviz_output) {
    // output the created graph to graph.dot
    boost::dynamic_properties dp;
    dp.property("label", boost::get(&VertexProp<SR>::name, graph)); // vertex name is the name of the equation
    dp.property("node_id", get(boost::vertex_index, graph)); // this is needed
    std::ofstream outf("graph.dot");
    boost::write_graphviz_dp(outf, graph, dp);
  }

  // calculate strongly connected components and store them in 'component'
  std::vector<int> component(boost::num_vertices(graph));
  boost::strong_components(graph,&component[0]);

  // group neccessary equations together
  int num_comp = *std::max_element(component.begin(), component.end()) + 1; // find the number of components
  std::vector< std::vector< std::pair< VarId,Polynomial<SR> > > >
    grouped_equations(num_comp);
  // grouped_equations.resize(num_comp);

  // iterate over all vertices (0 to n)
  // collect the necessary variables + equations for every component
  for (std::size_t j = 0; j != component.size(); ++j) {
    //std::cout << j << ", " << graph[j].var << " is in component " << component[j] << std::endl;
    grouped_equations[component[j]].push_back(std::pair<VarId,Polynomial<SR>>(graph[j].var, graph[j].rex));
  }

  return grouped_equations;
}

// apply Newton's method to the given input
template <template <typename> class NewtonType = Newton, typename SR>
ValuationMap<SR> apply_newton(
    const std::vector< std::pair< VarId, Polynomial<SR> > > &equations,
    bool scc, bool iteration_flag, std::size_t iterations, bool graphviz_output) {

  // TODO: sanity checks on the input!

  // generate an instance of the newton solver
  NewtonType<SR> newton;

  // if we use the scc method, group the equations
  // the outer vector contains SCCs starting with a bottom SCC at 0
  std::vector< std::vector< std::pair< VarId,Polynomial<SR> > > > equations2;
  if (scc) {
    auto tmp = group_by_scc(equations, graphviz_output);
    equations2.insert(equations2.begin(), tmp.begin(), tmp.end());
  }
  else if (!scc) {
    equations2.push_back(equations);
  }

  // this holds the solution
  ValuationMap<SR> solution;

  Timer timer;
  timer.Start();

  // the same loop is used for both the scc and the non-scc variant
  // in the non-scc variant, we just run once through the loop
  //for (auto it1 = equations2.begin(); it != equations2.end(); ++it)
  for (std::size_t j = 0; j != equations2.size(); ++j) {
    // use the solutions to get rid of variables in the remaining equations
    // does nothing in the first round
    std::vector<std::pair<VarId, Polynomial<SR>>> tmp1;
    for (auto it = equations2[j].begin(); it != equations2[j].end(); ++it)
    { // it = (VarId, Polynomial[SR])
      auto tmp2 = it->second.partial_eval(solution);
      tmp1.push_back(std::pair<VarId, Polynomial<SR>>(it->first, tmp2));
    }
    // replace old equations with simplified ones
    equations2[j] = tmp1;

    // dynamic iterations
    if (!iteration_flag) {
      // for commutative and idempotent SRs Newton has converged after n+1 iterations, so use this number as default
      iterations = equations2[j].size() + 1;
    }

    // do some real work here
    ValuationMap<SR> result = newton.solve_fixpoint(equations2[j], iterations);

    // copy the results into the solution map
    solution.insert(result.begin(), result.end());
  }

  timer.Stop();
  std::cout << "Solving time:\t" << timer.GetMicroseconds().count()
    << " us" << std::endl
    << "Solving time:\t" << timer.GetMilliseconds().count()
    << " ms" << std::endl;

  return solution;
}

template <typename SR>
std::string result_string(const ValuationMap<SR> &result) {
  std::stringstream ss;
  for (auto &x : result) {
    ss << x.first << " == " << x.second << std::endl;
  }
  return ss.str();
}

template <typename Container>
void PrintEquations(const Container &equations) {
  std::cout << "Equations:" << std::endl;
  for (auto &eq : equations) {
    std::cout << "* " << eq.first << " → " << eq.second << std::endl;
  }
}

std::string currentDateTime() {
    time_t     now = time(0);
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    // Visit http://en.cppreference.com/w/cpp/chrono/c/strftime
    // for more information about date/time format
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);

    return buf;
}

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

  namespace po = boost::program_options;

  po::options_description desc("Allowed options");
  desc.add_options()
    ( "scc", "apply newton method iteratively to strongly connected components of the equation graph" )
    ( "help,h", "print this help message" )
    ( "iterations,i", po::value<int>(), "specify the number of newton iterations. default is optimal number" )
    ( "test", "just for testing purposes ... explicit test defined in main()" )
    ( "file,f", po::value<std::string>(), "input file" )
    ( "file2,f2", po::value<std::string>(), "input file 2" )
    ( "float", "float semiring" )
    ( "rexp", "commutative regular expression semiring" )
    ( "slset", "explicit semilinear sets semiring, no simplification " )
    ( "mlset", "abstraction over semilinear sets" )
    ( "vec-simpl", "vector simplification only (only semilinear and multilinear sets)" )
    ( "lin-simpl", "linear set simplification (only semilinear sets)" )
    ( "free", "free semiring" )
    ( "lossy", "if used with --file: downward closure via Courcelle\nif used with --file and --file2: compare two input grammars for inequality via downward closure\nuse --refine n to fix refinement depth")
    ( "refine", po::value<int>(), "refinement depth for inequality check via downward closure; only available for --lossy --file --file2")
    ( "prefix", po::value<int>(), "prefix semiring with given length")
    ( "graphviz", "create the file graph.dot with the equation graph" )
    ;

  po::variables_map vm;
  po::store(po::parse_command_line(argc, argv, desc), vm);
  po::notify(vm);

  if (vm.count("test")) {
    Newton<SemilinSetExp> newton;
    std::vector<VarId> variables;
    variables.push_back(Var::GetVarId("x"));
    std::cout << "- newton (cnt-SR):" << std::endl;

    std::vector<Polynomial<SemilinSetExp> > polynomials;
    Polynomial<SemilinSetExp> f1 = Polynomial<SemilinSetExp>({
        { SemilinSetExp(Var::GetVarId("a")), Monomial{ {Var::GetVarId("x"),Var::GetVarId("x")} } },
        { SemilinSetExp(Var::GetVarId("c")), Monomial{} } });

    polynomials.push_back(f1);

    Matrix<SemilinSetExp> result = newton.solve_fixpoint(polynomials, variables, 2);
    std::cout << result << std::endl;
    return 0;
  }

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

  int iterations=0;
  if (vm.count("iterations"))
          iterations = vm["iterations"].as<int>();

  // check if we can do something useful
  if (!vm.count("float") &&
      !vm.count("rexp") &&
      !vm.count("slset") &&
      !vm.count("free") &&
      !vm.count("mlset") &&
      !vm.count("prefix") &&
      !vm.count("lossy")
     ) {
      std::cout << "Please supply a supported semiring :)" << std::endl;
      return 0;
  }

  std::vector<std::string> input;
  std::string line;
  if (vm.count("file")) {
    // we are reading the input from the given file
    std::ifstream file;
    file.open(vm["file"].as<std::string>(), std::ifstream::in);
    if (file.fail()) {
      std::cerr << "Could not open input file: " << vm["file"].as<std::string>() << std::endl;
    }
    while (std::getline(file, line)) {
      input.push_back(line);
    }
  } else {
    // we are reading from stdin
    while (std::getline(std::cin, line)) {
      input.push_back(line);
    }
  }

   // join the input into one string
   std::string input_all =
     std::accumulate(input.begin(), input.end(), std::string(""));

  // see to the input of the second file
  std::string input_2_all = "";
  std::vector<std::string> input_2;
  if (vm.count("file2")) {
    // we are reading the input from the given second file
    std::ifstream file;
    file.open(vm["file2"].as<std::string>(), std::ifstream::in);
    if (file.fail()) {
      std::cerr << "Could not open input file: " << vm["file2"].as<std::string>() << std::endl;
    } else {
        while (std::getline(file, line)) {
            input_2.push_back(line);
        }

        input_2_all = std::accumulate(input_2.begin(), input_2.end(), std::string(""));
    }
  }

  Parser p;

  const auto iter_flag = vm.count("iterations");
  const auto graph_flag = vm.count("graphviz");
  const auto scc_flag = vm.count("scc");

  if (vm.count("slset")) {

    auto equations = p.slset_parser(input_all);
    if (equations.empty()) return EXIT_FAILURE;
    //PrintEquations(equations);
    if (!vm.count("vec-simpl") && !vm.count("lin-simpl")) {
      DMSG("A");
      std::cout << result_string(
          apply_newton(equations, scc_flag, iter_flag, iterations, graph_flag)
          ) << std::endl;
    } else if (vm.count("vec-simpl") && !vm.count("lin-simpl")) {
      DMSG("B");
      auto equations2 = MapEquations(equations, [](const SemilinearSet<> &s) {
        return SemilinearSetV{s};
      });
      std::cout << result_string(
          apply_newton(equations2, scc_flag, iter_flag, iterations, graph_flag)
          ) << std::endl;
    } else {
      DMSG("C");
      auto equations2 = MapEquations(equations, [](const SemilinearSet<> &s) {
        return SemilinearSetL{s};
      });
      std::cout << result_string(
          apply_newton(equations2, scc_flag, iter_flag, iterations, graph_flag)
          ) << std::endl;
    }

  } else if (vm.count("mlset")) {

    auto equations = p.slset_parser(input_all);
    if (equations.empty()) return EXIT_FAILURE;
    if (vm.count("vec-simpl")) {
      auto m_equations = SemilinearToPseudoLinearEquations<
        DummyDivider, SparseVecSimplifier>(equations);

      std::cout << result_string(
          apply_newton(m_equations, scc_flag, iter_flag, iterations, graph_flag)
          ) << std::endl;
    } else {
      auto m_equations = SemilinearToPseudoLinearEquations<
        DummyDivider, DummyVecSimplifier>(equations);

      std::cout << result_string(
          apply_newton(m_equations, scc_flag, iter_flag, iterations, graph_flag)
          ) << std::endl;
    }

  } else if (vm.count("rexp")) {

    // parse the input into a list of (Var → Polynomial[SR])
    auto equations = p.rexp_parser(input_all);
    if (equations.empty()) return EXIT_FAILURE;

    //PrintEquations(equations);

    // apply Newton's method to the equations
    auto result = apply_newton(equations, vm.count("scc"),
                               vm.count("iterations"), iterations,
                               vm.count("graphviz"));
    std::cout << result_string(result) << std::endl;

  } else if (vm.count("free")) {

    // parse the input into a list of (Var → Polynomial[SR])
    auto equations = p.free_parser(input_all);
    if (equations.empty()) return EXIT_FAILURE;

    PrintEquations(equations);

  } else if(vm.count("lossy")) {

      auto equations = p.lossy_fa_parser(input_all);
      if (equations.empty()) return EXIT_FAILURE;

      VarId S_1 = equations[0].first;

      if (vm.count("file2")) {
          auto equations_2 = p.lossy_fa_parser(input_2_all);
          VarId S_2 = equations_2[0].first;

          int refinementDepth = 0;
          if(vm.count("refine")) {
              refinementDepth = vm["refine"].as<int>();
          }

          auto witness = LossyFiniteAutomaton::refineCourcelle(equations, S_1, equations_2, S_2, refinementDepth);

          if(witness != LossyFiniteAutomaton::null()) {
              std::cout << "Found witness: " << witness.string() << std::endl;
          }
      } else {
          auto approximation = LossyFiniteAutomaton::downwardClosureCourcelle(equations, S_1);
          std::cout << "dwc:\t" << approximation.string() << std::endl;
          std::cout << "size NFA for DWC:\t" << approximation.size() << std::endl;
          std::cout << "size minimal DFA for DWC:\t" << approximation.minimize().size() << std::endl;
      }
  } else if (vm.count("prefix")) {

    // parse the input into a list of (Var → Polynomial[SR])
    auto equations = p.prefix_parser(input_all, vm["prefix"].as<int>());
    if (equations.empty()) return EXIT_FAILURE;

    //PrintEquations(equations);

    // apply the newton method to the equations
    //auto result = apply_newton<PrefixSemiring>(equations,
    //                                           vm.count("scc"),
    //                                           vm.count("iterations"),
    //                                           iterations,
    //                                           vm.count("graphviz"));
    //std::cout << result_string(result) << std::endl;

  } else if (vm.count("float")) {

    auto equations = p.float_parser(input_all);
    if (equations.empty()) return EXIT_FAILURE;

    //PrintEquations(equations);
      std::cout << result_string(
          apply_newton<NewtonCL>(equations, scc_flag, iter_flag, iterations, graph_flag)
          ) << std::endl;

  }

  return EXIT_SUCCESS;
}


#include <iostream>
using std::cout;
using std::endl;

#include <spatialops/structured/FVStaggered.h>
#include <spatialops/structured/Grid.h>
#include <spatialops/util/TimeLogger.h>

namespace so = SpatialOps;
typedef so::SVolField   CellField;
typedef so::SSurfXField XSideField;
typedef so::SSurfYField YSideField;
typedef so::SSurfZField ZSideField;

typedef so::BasicOpTypes<CellField>::GradX      GradX;
typedef so::BasicOpTypes<CellField>::InterpC2FX InterpX;
typedef so::BasicOpTypes<CellField>::DivX       DivX;

typedef so::BasicOpTypes<CellField>::GradY      GradY;
typedef so::BasicOpTypes<CellField>::InterpC2FY InterpY;
typedef so::BasicOpTypes<CellField>::DivY       DivY;

typedef so::BasicOpTypes<CellField>::GradZ      GradZ;
typedef so::BasicOpTypes<CellField>::InterpC2FZ InterpZ;
typedef so::BasicOpTypes<CellField>::DivZ       DivZ;

//-- boost includes ---//
#include <boost/program_options.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <expression/ExprLib.h>
#include <expression/TimeStepper.h>
#include <expression/BoundaryConditionExpression.h>

//--- local includes ---//
#include "RHSExpr.h"
#include "Flux.h"
#include "MonolithicRHS.h"

namespace po = boost::program_options;
using SpatialOps::IntVec;

void jcs_pause()
{
  std::cout << "Press <ENTER> to continue...";
  std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}

//==============================================================================

void
initialize_mask_points( const so::Grid& grid,
                        std::vector<IntVec>& xminus,
                        std::vector<IntVec>& xplus,
                        std::vector<IntVec>& yminus,
                        std::vector<IntVec>& yplus,
                        std::vector<IntVec>& zminus,
                        std::vector<IntVec>& zplus )
{
  for( int k=0; k<grid.extent(2); ++k ){
    for( int  j=0; j<grid.extent(1); ++j ){
      xminus.push_back( IntVec(0, j, k) );
      xplus .push_back( IntVec(grid.extent(0), j, k) );
    }
  }
  for( int k=0; k<grid.extent(2); ++k ){
    for( int i=0; i<grid.extent(0); ++i ){
      yminus.push_back( IntVec(i,0,k) );
      yplus .push_back( IntVec(i,grid.extent(1), k) );
    }
  }
  for( int j=0; j<grid.extent(1); ++j ){
    for( int i=0; i<grid.extent(0); ++i ){
      zminus.push_back( IntVec(i,j,0) );
      zplus .push_back( IntVec(i,j,grid.extent(2)) );
    }
  }
}

//==============================================================================

void setup_bcs( Expr::ExpressionFactory& segFactory,
                Expr::ExpressionFactory& monoFactory,
                const so::Grid& grid )
{
  std::vector<IntVec> xminusPts, xplusPts, yminusPts, yplusPts, zminusPts, zplusPts;
  initialize_mask_points( grid, xminusPts, xplusPts, yminusPts, yplusPts, zminusPts, zplusPts );

  const so::GhostData ghost(1);
  const so::IntVec hasBC(true,true,true);
  const so::BoundaryCellInfo xInfo = so::BoundaryCellInfo::build<XSideField>(hasBC,hasBC);
  const so::BoundaryCellInfo yInfo = so::BoundaryCellInfo::build<YSideField>(hasBC,hasBC);
  const so::BoundaryCellInfo zInfo = so::BoundaryCellInfo::build<ZSideField>(hasBC,hasBC);
  const so::MemoryWindow xwindow( so::get_window_with_ghost( grid.extent(), ghost, xInfo ) );
  const so::MemoryWindow ywindow( so::get_window_with_ghost( grid.extent(), ghost, yInfo ) );
  const so::MemoryWindow zwindow( so::get_window_with_ghost( grid.extent(), ghost, zInfo ) );

  typedef Expr::ConstantBCOpExpression<CellField,so::XDIR>::Builder XBC;
  typedef Expr::ConstantBCOpExpression<CellField,so::YDIR>::Builder YBC;
  typedef Expr::ConstantBCOpExpression<CellField,so::ZDIR>::Builder ZBC;

  XBC::MaskPtr xminus( new XBC::MaskType( xwindow, xInfo, ghost, xminusPts ) );
  XBC::MaskPtr xplus ( new XBC::MaskType( xwindow, xInfo, ghost, xplusPts  ) );
  YBC::MaskPtr yminus( new YBC::MaskType( ywindow, yInfo, ghost, yminusPts ) );
  YBC::MaskPtr yplus ( new YBC::MaskType( ywindow, yInfo, ghost, yplusPts  ) );
  ZBC::MaskPtr zminus( new ZBC::MaskType( zwindow, zInfo, ghost, zminusPts ) );
  ZBC::MaskPtr zplus ( new ZBC::MaskType( zwindow, zInfo, ghost, zplusPts  ) );

# ifdef ENABLE_CUDA
  // Masks are created on CPU so we need to explicitly transfer them to GPU
  xminus->add_consumer( GPU_INDEX );
  xplus ->add_consumer( GPU_INDEX );
  yminus->add_consumer( GPU_INDEX );
  yplus ->add_consumer( GPU_INDEX );
  zminus->add_consumer( GPU_INDEX );
  zplus ->add_consumer( GPU_INDEX );
# endif

  segFactory.register_expression( new XBC( Expr::Tag("xmbc",Expr::STATE_NONE), xminus, so::DIRICHLET, so::MINUS_SIDE,  1.0 ) );
  segFactory.register_expression( new XBC( Expr::Tag("xpbc",Expr::STATE_NONE), xplus , so::DIRICHLET, so::PLUS_SIDE,  -1.0 ) );
  segFactory.register_expression( new YBC( Expr::Tag("ymbc",Expr::STATE_NONE), yminus, so::DIRICHLET, so::MINUS_SIDE,  1.0 ) );
  segFactory.register_expression( new YBC( Expr::Tag("ypbc",Expr::STATE_NONE), yplus , so::DIRICHLET, so::PLUS_SIDE,  -1.0 ) );
  segFactory.register_expression( new ZBC( Expr::Tag("zmbc",Expr::STATE_NONE), zminus, so::DIRICHLET, so::MINUS_SIDE,  1.0 ) );
  segFactory.register_expression( new ZBC( Expr::Tag("zpbc",Expr::STATE_NONE), zplus , so::DIRICHLET, so::PLUS_SIDE,  -1.0 ) );

  monoFactory.register_expression( new XBC( Expr::Tag("xmbc",Expr::STATE_NONE), xminus, so::DIRICHLET, so::MINUS_SIDE,  1.0 ) );
  monoFactory.register_expression( new XBC( Expr::Tag("xpbc",Expr::STATE_NONE), xplus , so::DIRICHLET, so::PLUS_SIDE,  -1.0 ) );
  monoFactory.register_expression( new YBC( Expr::Tag("ymbc",Expr::STATE_NONE), yminus, so::DIRICHLET, so::MINUS_SIDE,  1.0 ) );
  monoFactory.register_expression( new YBC( Expr::Tag("ypbc",Expr::STATE_NONE), yplus , so::DIRICHLET, so::PLUS_SIDE,  -1.0 ) );
  monoFactory.register_expression( new ZBC( Expr::Tag("zmbc",Expr::STATE_NONE), zminus, so::DIRICHLET, so::MINUS_SIDE,  1.0 ) );
  monoFactory.register_expression( new ZBC( Expr::Tag("zpbc",Expr::STATE_NONE), zplus , so::DIRICHLET, so::PLUS_SIDE,  -1.0 ) );

  const Expr::Tag tempt( "Temperature", Expr::STATE_NONE );

  segFactory.attach_modifier_expression( Expr::Tag("xmbc",Expr::STATE_NONE), tempt );
  segFactory.attach_modifier_expression( Expr::Tag("xpbc",Expr::STATE_NONE), tempt );
  segFactory.attach_modifier_expression( Expr::Tag("ymbc",Expr::STATE_NONE), tempt );
  segFactory.attach_modifier_expression( Expr::Tag("ypbc",Expr::STATE_NONE), tempt );
  segFactory.attach_modifier_expression( Expr::Tag("zmbc",Expr::STATE_NONE), tempt );
  segFactory.attach_modifier_expression( Expr::Tag("zpbc",Expr::STATE_NONE), tempt );

  monoFactory.attach_modifier_expression( Expr::Tag("xmbc",Expr::STATE_NONE), tempt );
  monoFactory.attach_modifier_expression( Expr::Tag("xpbc",Expr::STATE_NONE), tempt );
  monoFactory.attach_modifier_expression( Expr::Tag("ymbc",Expr::STATE_NONE), tempt );
  monoFactory.attach_modifier_expression( Expr::Tag("ypbc",Expr::STATE_NONE), tempt );
  monoFactory.attach_modifier_expression( Expr::Tag("zmbc",Expr::STATE_NONE), tempt );
  monoFactory.attach_modifier_expression( Expr::Tag("zpbc",Expr::STATE_NONE), tempt );
}

//==============================================================================

int main( int iarg, char* carg[] )
{
  double dt, tend;
  IntVec npts;
  so::DoubleVec length;

# ifdef ENABLE_THREADS
  int soThreadCount, exprThreadCount;
# endif

  // parse the command line options input describing the problem
  {
    po::options_description desc("Supported Options");
    desc.add_options()
      ( "help", "print help message" )
      ( "dt",   po::value<double>(&dt  )->default_value(2e-4), "timestep" )
      ( "tend", po::value<double>(&tend)->default_value(0.04 ), "end time" )
#     ifdef ENABLE_THREADS
      ( "tc", po::value<int>(&soThreadCount)->default_value(NTHREADS), "Number of threads for Nebo")
      ( "etc", po::value<int>(&exprThreadCount)->default_value(1), "Number of threads for ExprLib")
#     endif
      ( "nx", po::value<int>(&npts[0])->default_value(32), "nx" )
      ( "ny", po::value<int>(&npts[1])->default_value(32), "ny" )
      ( "nz", po::value<int>(&npts[2])->default_value(32), "nz" )
      ( "Lx", po::value<double>(&length[0])->default_value(1.0),"Length in x")
      ( "Ly", po::value<double>(&length[1])->default_value(1.0),"Length in y")
      ( "Lz", po::value<double>(&length[2])->default_value(1.0),"Length in z");

    po::variables_map args;
    po::store( po::parse_command_line(iarg,carg,desc), args );
    po::notify(args);

    if (args.count("help")) {
      cout << desc << "\n";
      return 1;
    }
  }

# ifdef ENABLE_THREADS
  cout << "ENABLE_THREADS is ON" << endl;
  SpatialOps::set_hard_thread_count( NTHREADS );
  SpatialOps::set_soft_thread_count( soThreadCount );
  Expr::set_hard_thread_count( NTHREADS );
  Expr::set_soft_thread_count( exprThreadCount );
  sleep(1);
# endif

  cout << " [nx,ny,nz] = " << npts << endl
       << " dt = " << dt << ", tend = " << tend << endl
#      ifdef ENABLE_THREADS
       << " SpatialOps NTHREADS (can set at runtime) = " << SpatialOps::get_soft_thread_count()
       << " out of " << SpatialOps::get_hard_thread_count() << endl
       << " ExprLib    NTHREADS (can set at runtime) = "
       << SpatialOps::ThreadPool::get_pool_size() << " out of "
       << SpatialOps::ThreadPool::get_pool_capacity() << endl
#      endif
       << endl;

  // set mesh spacing (uniform, structured mesh)
  const so::DoubleVec spacing = length/npts;
  const so::Grid grid( npts, length );

  // build the spatial operators
  SpatialOps::OperatorDatabase sodb;
  so::build_stencils( grid, sodb );

  Expr::ExpressionFactory segFactory, monoFactory;

  const Expr::Tag tempt( "Temperature", Expr::STATE_NONE );
  const Expr::Tag dct  ( "ThermalCond", Expr::STATE_NONE );

  const Expr::Tag  segrhsTag("segrhs", Expr::STATE_NONE);
  const Expr::Tag monorhsTag("monorhs",Expr::STATE_NONE);

  { //-- segregated approach:
    const Expr::Tag xfluxt( "HeatFluxX", Expr::STATE_NONE );
    const Expr::Tag yfluxt( "HeatFluxY", Expr::STATE_NONE );
    const Expr::Tag zfluxt( "HeatFluxZ", Expr::STATE_NONE );

    segFactory.register_expression( new Flux<GradX,InterpX>::Builder( xfluxt, tempt, dct ) );
    segFactory.register_expression( new Flux<GradY,InterpY>::Builder( yfluxt, tempt, dct ) );
    segFactory.register_expression( new Flux<GradZ,InterpZ>::Builder( zfluxt, tempt, dct ) );

    segFactory.register_expression(
        new RHSExpr<CellField>::Builder( segrhsTag,
                                         npts[0]>1 ? xfluxt : Expr::Tag(),
                                         npts[1]>1 ? yfluxt : Expr::Tag(),
                                         npts[2]>1 ? zfluxt : Expr::Tag() ) );
  }
  { //-- monolithic approach
    monoFactory.register_expression( new MonolithicRHS<CellField>::Builder( monorhsTag, dct, tempt ) );
  }

  //-- build a few coordinate fields
  const so::IntVec hasBC(true,true,true);
  const so::BoundaryCellInfo cellBCInfo = so::BoundaryCellInfo::build<CellField>(hasBC,hasBC);
  const so::GhostData cellGhosts(1);
  const so::MemoryWindow vwindow( so::get_window_with_ghost(npts,cellGhosts,cellBCInfo) );
  CellField xcoord( vwindow, cellBCInfo, cellGhosts, NULL );
  CellField ycoord( vwindow, cellBCInfo, cellGhosts, NULL );
  CellField zcoord( vwindow, cellBCInfo, cellGhosts, NULL );

  grid.set_coord<SpatialOps::XDIR>( xcoord );
  grid.set_coord<SpatialOps::YDIR>( ycoord );
  grid.set_coord<SpatialOps::ZDIR>( zcoord );

# ifdef ENABLE_CUDA
  xcoord.add_device( GPU_INDEX );
  ycoord.add_device( GPU_INDEX );
  zcoord.add_device( GPU_INDEX );
# endif

  segFactory .register_expression( new Expr::ConstantExpr<CellField>::Builder(dct,0.1) );
  monoFactory.register_expression( new Expr::ConstantExpr<CellField>::Builder(dct,0.1) );
  segFactory .register_expression( new Expr::PlaceHolder<CellField>::Builder(tempt) );
  monoFactory.register_expression( new Expr::PlaceHolder<CellField>::Builder(tempt) );

  setup_bcs( segFactory, monoFactory, grid );

  Expr::TimeStepper  segTimeStepper(  segFactory, Expr::FORWARD_EULER, "segregated" );
  Expr::TimeStepper monoTimeStepper( monoFactory, Expr::FORWARD_EULER, "monolithic" );
  Expr::FieldManagerList fml;

  segTimeStepper.request_timings();
  monoTimeStepper.request_timings();

  try{
    const Expr::FieldAllocInfo ainfo( npts, 0, 0, false, false, false );

    segTimeStepper.add_equation<CellField>( "T", segrhsTag, SpatialOps::GhostData(1) );
    segTimeStepper.finalize( fml, sodb, ainfo );

    monoTimeStepper.add_equation<CellField>( "T", monorhsTag, SpatialOps::GhostData(1) );
    monoTimeStepper.finalize( fml, sodb, ainfo );

    std::ofstream  seg("seggraph.dot" );   segTimeStepper.get_tree()->write_tree(seg, true, true );
    std::ofstream mono("monograph.dot");  monoTimeStepper.get_tree()->write_tree(mono,true, true );
  }
  catch( std::exception& err ){
   std::cout << "ERROR allocating/binding fields" << std::endl
       << err.what() << std::endl;
   return -1;
  }

  //-- initialize the fields - temperature and thermal cond.
  {
    using namespace SpatialOps;
    CellField& temp = fml.field_manager<CellField>().field_ref(tempt);
    CellField& dc   = fml.field_manager<CellField>().field_ref(dct);
    temp <<= sin( xcoord ) + cos( ycoord ) + sin( zcoord );
  }

  try{
    SpatialOps::TimeLogger timer;

    timer.start("segTree");
    double t = 0;
    while( t < tend ){
      segTimeStepper.step( dt );
      t += dt;
    }
    timer.stop("segTree");
    cout << "sequential - time taken: " << timer.timer("segTree").elapsed_time() << endl;

    timer.start("monoTree");
    t = 0;
    while( t < tend ){
      monoTimeStepper.step( dt );
      t += dt;
    }
    timer.stop("monoTree");
    cout << "monolithic - time taken: " << timer.timer("monoTree").elapsed_time() << endl << endl;

    return 0;
  }
  catch( std::exception& err ){
    cout << "ERROR!" << endl
         << err.what() << endl;
  }
  return -1;

}


/*
ID: septicmk
LANG: C++
TASK: Kmeans.cpp
*/

#include "Kmeans.h"
#include "../Core/Core.h"
#include <boost/serialization/vector.hpp>
#include <boost/serialization/queue.hpp>
#include <boost/serialization/export.hpp>
#include <vector>
#include <queue>
#include <fstream>

BOOST_CLASS_EXPORT(kmeans::Kmeans)
namespace kmeans{

    void Matrix::init (size_t nrow, size_t ncol){
        this->nrow = nrow;
        this->ncol = ncol;
        data.resize(nrow*ncol);
        std::fill(data.begin(), data.end(), 0.0f);
    }

    void Matrix::add (std::vector<double> record, size_t i){
        for(size_t j = 0; j < record.size(); ++j){
            data[ i*ncol + j ] += record[j];
        }
    }

    std::vector<double> Matrix::get_value(size_t i){
        std::vector<double> ret;
        for (size_t j = 0; j < ncol; ++j){
            ret.push_back(data[ i*ncol + j ]);
        }
        return ret;
    }

    Kmeans* Kmeans::operator +(Kmeans &rhs){
        Kmeans *pret = new Kmeans();
        pret->mat.init(mat.nrow, mat.ncol);
        for(size_t i = 0; i< mat.data.size() ; ++i)
            pret->mat.data[i] = mat.data[i] + rhs.mat.data[i];
        return pret;
    }

    void Kmeans::beginDataScan(std::vector<double> records, size_t feat_dim){
        std::priority_queue<int> q;
        std::vector<int> weight;
        size_t n_records = records.size();
        size_t n_cluster = mat.nrow;
        for (size_t i = 0; i < n_records; ++ i){
            int x = core::Random(10007);
            weight.push_back(x);
            if (q.size() < n_cluster){
                q.push(i);
            }else if (x < weight[q.top()]){
                q.pop();
                q.push(i);
            }
        }
        while(!q.empty()){
            std::vector<double> record;
            record.clear();
            size_t i = q.top();
            for(size_t j = 0; j < feat_dim ; ++j){
                record.push_back(records[i*feat_dim+j]);
            }
            mat.add(record, n_cluster - q.size());
            q.pop();
        }
    }

    void Kmeans::endDataScan(){
        for(size_t i = 0; i < mat.nrow; ++i){
            for(size_t j = 0; j < mat.ncol; ++j){
                mat.data[i*mat.ncol+j] /= mat.data[i*mat.ncol + mat.ncol-1];
            }
            mat.data[i*mat.ncol + mat.ncol-1] = 1;
        }

        //for(size_t i = 0; i < nrow; ++i){
        //for(size_t j = 0; j < ncol; ++j){
        //std::cout << data[i*ncol+j] << " ";
        //}
        //std::puts("");
        //}
    }

    Kmeans* Kmeans::processRecord(std::vector<double> records, size_t feat_dim){
        Kmeans* pret = new Kmeans();
        //std::cout << this->test << std::endl;
        pret->mat.init(this->mat.nrow, this->mat.ncol);
        size_t n_records = records.size()/feat_dim;
        for (size_t i = 0 ; i < n_records ; i++){
            std::vector<double> record;
            record.clear();
            for(size_t j = 0; j < feat_dim ; j ++){
                record.push_back(records[i*feat_dim+j]);
            }
            double dis = (1L<<16)-1;
            int who = -1;
            //std::cout<<"bp"<<std::endl;
            for(size_t k = 0; k < mat.nrow; k++){
                std::vector<double> centroid = mat.get_value(k);
                double tmp = 0;
                for (size_t u = 0; u < record.size(); ++u){
                    tmp += (centroid[u] - record[u])*(centroid[u] - record[u]);
                }

                if (tmp < dis){
                    who = k;
                    dis = tmp;
                }
            }
            record.push_back(1);
            pret->mat.add(record, who);
        }
        return pret;
    }

    bool Kmeans::isConverged(Kmeans* rhs, size_t feat_dim, double eps){
        double diff = 0;
        for(size_t i = 0; i < mat.nrow; i ++){
            double tmp = 0;
            for(size_t j = 0;  j < feat_dim; j++){
                tmp += (mat.data[i*mat.ncol+j] - rhs->mat.data[i*mat.ncol+j]) * (mat.data[i*mat.ncol+j] - rhs->mat.data[i*mat.ncol+j]);
            }
            tmp = sqrt(tmp);
            diff += tmp;
        }
        if (diff <= eps) return true;
        else return false;

    }

    void Kmeans::finish(std::vector<double> records, size_t feat_dim){
        std::ofstream file;
        file.open("./ans.txt");
        std::vector<float> record;
        size_t n_records = records.size()/feat_dim;
        for(size_t i = 0; i < n_records; ++i){
            std::vector<double> record;
            record.clear();
            for(size_t j = 0; j < feat_dim; ++ j){
                record.push_back(records[i*feat_dim+j]);
            }
            double dis = (1<<16)-1;
            int who = -1;
            for(size_t k = 0; k < this->mat.nrow; k++){
                std::vector<double> centroid = this->mat.get_value(k);
                double tmp = 0;
                for (size_t u = 0; u < record.size(); ++u){
                    tmp += (centroid[u] - record[u])*(centroid[u] - record[u]);
                }

                if (tmp < dis){
                    who = k;
                    dis = tmp;
                }
            }
            file << who << std::endl;

        }

    }

}


/**
 * @file wheel_controller.hpp
 * @author Fujii Naomichi
 * @copyright (c) 2021 Fujii Naomichi
 * SPDX-License-Identifier: MIT
 */

#pragma once

#include <Eigen/Core>
#include "filter/gravity_filter.hpp"
#include "filter/velocity_filter.hpp"
#include "filter/acceleration_limitter.hpp"
#include "filter/hpf.hpp"

/**
 * 車輪制御を行う
 */
class WheelController {
public:
    /**
     * @brief 初期化を行う
     */
    static void initialize(void) {
        stopControl();
    }

    /**
     * @brief モーター制御を開始する
     */
    static void startControl(void);

    /**
     * @brief モーター制御を終了し、モーターを脱力する
     */
    static void stopControl(void);

    /**
     * @brief 指令値を更新する
     * @param new_parameters new_parameters 共有メモリーのParametersが更新されたときにtrueを指定する
     * @param sensor_only trueのときセンサーデータの更新のみ行う
     */
    static void update(bool new_parameters, bool sensor_only);

    /**
     * @brief 重力フィルタへアクセスする
     * @return 重力フィルタ
     */
    static const GravityFilter& gravityFilter(void) {
        return _gravity_filter;
    }

    /**
     * @brief 速度フィルタへアクセスする
     * @return 速度フィルタ
     */
    static const VelocityFilter& velocityFilter(void) {
        return _velocity_filter;
    }

    /**
     * @brief 車体加速度の推定値を取得する
     * @return 車体加速度 X, Y, Z [m/s^2]
     */
    static const Eigen::Vector3f& bodyAcceleration(void) {
        return _gravity_filter.acceleration();
    }

    /**
     * @brief 車体速度の推定値を取得する
     * @return 車体速度 X [m/s], Y [m/s], ω [rad/s]
     */
    static Eigen::Vector3f bodyVelocity(void) {
        return _velocity_filter.bodyVelocity();
    }

    /**
     * @brief 目標車体加速度を取得する
     * @return 目標車体加速度 X [m/s^2], Y [m/s^2], ω [rad/s^2], C [m/s^2]
     */
    static Eigen::Vector4f referenceAcceleration(void) {
        return _ref_body_accel;
    }

private:
    /**
     * 制御情報をクリアする
     */
    static void initializeState(void);

    /**
     * レジスタを初期化する
     */
    static void initializeRegisters(void);

    /**
     * @brief モーターの出力[W]を制限する電流制限値を計算する
     * @param velocity 車輪速度 [m/s]
     * @return 電流制限値 [A]
     */
    static float limitPower(float velocity);

    /// IMUの加速度から重力を分離するフィルタ
    static GravityFilter _gravity_filter;

    /// IMUとエンコーダから車体速度を求めるカルマンフィルタ
    static VelocityFilter _velocity_filter;

    /// 誤差の不完全微分を行うHPF
    static Hpf1stOrder5 _error_hpf[4];

    /// 車体加速度の指令値
    static Eigen::Vector4f _ref_body_accel;

    /// 電流制御の指令値 [A]
    static Eigen::Vector4f _ref_wheel_current;

    /// モーターの発生させた回生エネルギー (負の値をとる)
    static Eigen::Vector4f _regeneration_energy;
};


#ifndef Fitting_detail_ConstantTemperatureLinearRegression_hpp
#define Fitting_detail_ConstantTemperatureLinearRegression_hpp

/** @file ConstantTemperatureLinearRegression.hpp
  * @brief
  * @author C.D. Clark III
  * @date 07/07/17
  */

#include <Eigen/Dense>
#include "../../Utils/LinearRegression.hpp"

namespace libArrhenius {

/** @class ArrheniusFit<Real,ConstantTemperatureLinearRegression>
  * @brief Implements the standard constant-temperature linear regression method.
  * @author C.D. Clark III
  */
template<typename Real>
class ArrheniusFit<Real,ConstantTemperatureLinearRegression> : public ArrheniusFitBase<Real>
{
  public:
    ArrheniusFit() {};
    virtual ~ArrheniusFit() {};

    typedef typename ArrheniusFitBase<Real>::Return Return;

    Return
    exec() const
    {
      Return ret;
      ArrheniusIntegral<Real> integrator;
      std::vector<Real*> const &t = this->t;
      std::vector<Real*> const &T = this->T;
      std::vector<size_t> const &N = this->N;

      // construct effective exposure parameters for each profile
      BOOST_LOG_TRIVIAL(trace) << "Determining peak temperature and exposure durations.";
      Eigen::Matrix<Real,Eigen::Dynamic,1> logteff(N.size()),invTeff(N.size());
      for(size_t i = 0; i < N.size(); i++)
      {
        Real Tmax = *std::max_element( T[i], T[i]+N[i] );
        Real Tmin = *std::min_element( T[i], T[i]+N[i] );
        invTeff[i] = 1 / Tmax;

        // get the time above 50% temp rise
        logteff[i] = 0;
        for(size_t j = 1; j < N[i]; j++)
        {
          if( (T[i][j] + T[i][j-1])/2 > (Tmax+Tmin)/2 )
            logteff[i] += t[i][j] - t[i][j-1];
        }
        logteff[i] = log(logteff[i]);

        BOOST_LOG_TRIVIAL(trace) << "peak T: "<< 1/invTeff[i];
        BOOST_LOG_TRIVIAL(trace) << "tau: "<< exp(logteff[i]);
      }

      BOOST_LOG_TRIVIAL(trace) << "Performing linear regression.";
      // now perform linear regression with effective parameters
      auto linreg = RUC::LinearRegression( invTeff, logteff );

      ret.A = exp(-linreg[0]);
      ret.Ea = linreg[1]*Constants::MKS::R;

      return ret;

    }

  protected:
};

}

#endif // include protector


//
//  cal_fe_int_el.hpp
//  hybrid_fem_bie
//
//  Created by Max on 3/21/18.
//
//

#ifndef cal_fe_int_el_hpp
#define cal_fe_int_el_hpp

#include <stdio.h>
#include <Eigen/Eigen>
#include "Getplastic.hpp"
using namespace Eigen;

void cal_fe_int_el(double E, double nu, std::vector<Eigen::MatrixXd> &B_mat, double &detJ,VectorXd &u_n_local, VectorXd &fe_int_el,
                   MatrixXd &strain_n, MatrixXd &stress_n,double sxx_initial,
                   double syy_initial, double sxy_initial, double cohes, double blkfric);

#endif /* cal_fe_int_el_hpp */