/**TODO:  Add copyright*/

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

#include <iostream>

using namespace SmartPeak;
using namespace std;

BOOST_AUTO_TEST_SUITE(lossFunctionTensor)

/**
  ManhattanDistanceLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorManhattanDistanceLossOp)
{
  ManhattanDistanceLossTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ManhattanDistanceLossTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

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

BOOST_AUTO_TEST_CASE(operationfunctionManhattanDistanceLossOp)
{
  ManhattanDistanceLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 1, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 1, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  ManhattanDistanceLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorManhattanDistanceLossGradOp)
{
  ManhattanDistanceLossGradTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ManhattanDistanceLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

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

BOOST_AUTO_TEST_CASE(operationfunctionManhattanDistanceLossGradOp)
{
  ManhattanDistanceLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), 0.0, 1e-4); //-nan
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -1.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), 1.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
}

/**
  L2NormLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorL2NormOp)
{
  L2NormLossTensorOp<float, Eigen::DefaultDevice>* ptrL2Norm = nullptr;
  L2NormLossTensorOp<float, Eigen::DefaultDevice>* nullPointerL2Norm = nullptr;
  BOOST_CHECK_EQUAL(ptrL2Norm, nullPointerL2Norm);
}

BOOST_AUTO_TEST_CASE(destructorL2NormOp)
{
  L2NormLossTensorOp<float, Eigen::DefaultDevice>* ptrL2Norm = nullptr;
  ptrL2Norm = new L2NormLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrL2Norm;
}

BOOST_AUTO_TEST_CASE(operationfunctionL2NormOp)
{
  L2NormLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0.5, 1e-4); //TODO
  BOOST_CHECK_CLOSE(error(1, 0), -2.5, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  L2NormLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorL2NormGradOp)
{
  L2NormLossGradTensorOp<float, Eigen::DefaultDevice>* ptrL2Norm = nullptr;
  L2NormLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerL2Norm = nullptr;
  BOOST_CHECK_EQUAL(ptrL2Norm, nullPointerL2Norm);
}

BOOST_AUTO_TEST_CASE(destructorL2NormGradOp)
{
  L2NormLossGradTensorOp<float, Eigen::DefaultDevice>* ptrL2Norm = nullptr;
  ptrL2Norm = new L2NormLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrL2Norm;
}

BOOST_AUTO_TEST_CASE(operationfunctionL2NormGradOp)
{
  L2NormLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

/**
  CrossEntropyOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCrossEntropyOp)
{
  BCELossTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropy = nullptr;
  BCELossTensorOp<float, Eigen::DefaultDevice>* nullPointerCrossEntropy = nullptr;
  BOOST_CHECK_EQUAL(ptrCrossEntropy, nullPointerCrossEntropy);
}

BOOST_AUTO_TEST_CASE(destructorCrossEntropyOp)
{
  BCELossTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropy = nullptr;
  ptrCrossEntropy = new BCELossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrCrossEntropy;
}

BOOST_AUTO_TEST_CASE(operationfunctionCrossEntropyOp)
{
  BCELossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0}, {1, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{.1, .9}, {0, 0}},
    {{.9, .1}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 4.60517025, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.21072109, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  CrossEntropyGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCrossEntropyGradOp)
{
  BCELossGradTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropy = nullptr;
  BCELossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerCrossEntropy = nullptr;
  BOOST_CHECK_EQUAL(ptrCrossEntropy, nullPointerCrossEntropy);
}

BOOST_AUTO_TEST_CASE(destructorCrossEntropyGradOp)
{
  BCELossGradTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropy = nullptr;
  ptrCrossEntropy = new BCELossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrCrossEntropy;
}

BOOST_AUTO_TEST_CASE(operationfunctionCrossEntropyGradOp)
{
  BCELossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), 10.0000, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), 1.11111116, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -10.0000, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), -1.11111116, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
}

/**
  NegativeLogLikelihoodLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorNegativeLogLikelihoodOp)
{
  NegativeLogLikelihoodLossTensorOp<float, Eigen::DefaultDevice>* ptrNegativeLogLikelihood = nullptr;
  NegativeLogLikelihoodLossTensorOp<float, Eigen::DefaultDevice>* nullPointerNegativeLogLikelihood = nullptr;
  BOOST_CHECK_EQUAL(ptrNegativeLogLikelihood, nullPointerNegativeLogLikelihood);
}

BOOST_AUTO_TEST_CASE(destructorNegativeLogLikelihoodOp)
{
  NegativeLogLikelihoodLossTensorOp<float, Eigen::DefaultDevice>* ptrNegativeLogLikelihood = nullptr;
  ptrNegativeLogLikelihood = new NegativeLogLikelihoodLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrNegativeLogLikelihood;
}

BOOST_AUTO_TEST_CASE(operationfunctionNegativeLogLikelihoodOp)
{
  NegativeLogLikelihoodLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0}, {1, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{.1, .9}, {0, 0}},
    {{.9, .1}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 1.15129256, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.0526802726, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  NegativeLogLikelihoodLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorNegativeLogLikelihoodGradOp)
{
  NegativeLogLikelihoodLossGradTensorOp<float, Eigen::DefaultDevice>* ptrNegativeLogLikelihood = nullptr;
  NegativeLogLikelihoodLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerNegativeLogLikelihood = nullptr;
  BOOST_CHECK_EQUAL(ptrNegativeLogLikelihood, nullPointerNegativeLogLikelihood);
}

BOOST_AUTO_TEST_CASE(destructorNegativeLogLikelihoodGradOp)
{
  NegativeLogLikelihoodLossGradTensorOp<float, Eigen::DefaultDevice>* ptrNegativeLogLikelihood = nullptr;
  ptrNegativeLogLikelihood = new NegativeLogLikelihoodLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrNegativeLogLikelihood;
}

BOOST_AUTO_TEST_CASE(operationfunctionNegativeLogLikelihoodGradOp)
{
  NegativeLogLikelihoodLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), -5.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -0.555555582, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
}

/**
  MSELossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMSEOp)
{
  MSELossTensorOp<float, Eigen::DefaultDevice>* ptrMSE = nullptr;
  MSELossTensorOp<float, Eigen::DefaultDevice>* nullPointerMSE = nullptr;
  BOOST_CHECK_EQUAL(ptrMSE, nullPointerMSE);
}

BOOST_AUTO_TEST_CASE(destructorMSEOp)
{
  MSELossTensorOp<float, Eigen::DefaultDevice>* ptrMSE = nullptr;
  ptrMSE = new MSELossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMSE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMSEOp)
{
  MSELossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0.25, 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);
}

/**
  MSELossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMSEGradOp)
{
  MSELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMSE = nullptr;
  MSELossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMSE = nullptr;
  BOOST_CHECK_EQUAL(ptrMSE, nullPointerMSE);
}

BOOST_AUTO_TEST_CASE(destructorMSEGradOp)
{
  MSELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMSE = nullptr;
  ptrMSE = new MSELossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMSE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMSEGradOp)
{
  MSELossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

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

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

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

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0.5, 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);
}

/**
  MAELossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMAEGradOp)
{
  MAELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMAE = nullptr;
  MAELossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMAE = nullptr;
  BOOST_CHECK_EQUAL(ptrMAE, nullPointerMAE);
}

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

BOOST_AUTO_TEST_CASE(operationfunctionMAEGradOp)
{
  MAELossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

/**
  MRSELossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMRSEOp)
{
  MRSELossTensorOp<float, Eigen::DefaultDevice>* ptrMRSE = nullptr;
  MRSELossTensorOp<float, Eigen::DefaultDevice>* nullPointerMRSE = nullptr;
  BOOST_CHECK_EQUAL(ptrMRSE, nullPointerMRSE);
}

BOOST_AUTO_TEST_CASE(destructorMRSEOp)
{
  MRSELossTensorOp<float, Eigen::DefaultDevice>* ptrMRSE = nullptr;
  ptrMRSE = new MRSELossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMRSE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMRSEOp)
{
  MRSELossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 1.5, 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);
}

/**
  MRSELossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMRSEGradOp)
{
  MRSELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMRSE = nullptr;
  MRSELossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMRSE = nullptr;
  BOOST_CHECK_EQUAL(ptrMRSE, nullPointerMRSE);
}

BOOST_AUTO_TEST_CASE(destructorMRSEGradOp)
{
  MRSELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMRSE = nullptr;
  ptrMRSE = new MRSELossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMRSE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMRSEGradOp)
{
  MRSELossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), -499999.969, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -499999.969, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -707106.688, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), -707106.688, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
}

/**
  MLELossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMLEOp)
{
  MLELossTensorOp<float, Eigen::DefaultDevice>* ptrMLE = nullptr;
  MLELossTensorOp<float, Eigen::DefaultDevice>* nullPointerMLE = nullptr;
  BOOST_CHECK_EQUAL(ptrMLE, nullPointerMLE);
}

BOOST_AUTO_TEST_CASE(destructorMLEOp)
{
  MLELossTensorOp<float, Eigen::DefaultDevice>* ptrMLE = nullptr;
  ptrMLE = new MLELossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMLE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMLEOp)
{
  MLELossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0.346573591, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.346573591, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  MLELossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMLEGradOp)
{
  MLELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMLE = nullptr;
  MLELossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMLE = nullptr;
  BOOST_CHECK_EQUAL(ptrMLE, nullPointerMLE);
}

BOOST_AUTO_TEST_CASE(destructorMLEGradOp)
{
  MLELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMLE = nullptr;
  ptrMLE = new MLELossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMLE;
}

BOOST_AUTO_TEST_CASE(operationfunctionMLEGradOp)
{
  MLELossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

/**
  KLDivergenceMuLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorKLDivergenceMuOp)
{
  KLDivergenceMuLossTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceMu = nullptr;
  KLDivergenceMuLossTensorOp<float, Eigen::DefaultDevice>* nullPointerKLDivergenceMu = nullptr;
  BOOST_CHECK_EQUAL(ptrKLDivergenceMu, nullPointerKLDivergenceMu);
}

BOOST_AUTO_TEST_CASE(destructorKLDivergenceMuOp)
{
  KLDivergenceMuLossTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceMu = nullptr;
  ptrKLDivergenceMu = new KLDivergenceMuLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrKLDivergenceMu;
}

BOOST_AUTO_TEST_CASE(operationfunctionKLDivergenceMuOp)
{
	// Without capacity
  KLDivergenceMuLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 3, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);

	// With capacity
	KLDivergenceMuLossTensorOp<float, Eigen::DefaultDevice> operationC(1e-3, 1, 5);

	float errorC_ptr[] = { 0, 0, 0, 0 };

	operationC(y_pred.data(), y_true.data(), errorC_ptr, batch_size, memory_size, layer_size, time_step, device);
	Eigen::TensorMap<Eigen::Tensor<float, 2>> errorC(errorC_ptr, batch_size, memory_size);
	BOOST_CHECK_CLOSE(errorC(0, 0), -5, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0), -2, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1), 0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1), 0, 1e-4);
}

/**
  KLDivergenceMuLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorKLDivergenceMuGradOp)
{
  KLDivergenceMuLossGradTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceMu = nullptr;
  KLDivergenceMuLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerKLDivergenceMu = nullptr;
  BOOST_CHECK_EQUAL(ptrKLDivergenceMu, nullPointerKLDivergenceMu);
}

BOOST_AUTO_TEST_CASE(destructorKLDivergenceMuGradOp)
{
  KLDivergenceMuLossGradTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceMu = nullptr;
  ptrKLDivergenceMu = new KLDivergenceMuLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrKLDivergenceMu;
}

BOOST_AUTO_TEST_CASE(operationfunctionKLDivergenceMuGradOp)
{
	// Without capacity
  KLDivergenceMuLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), -2.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -4.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -2.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), -4.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);

	// With capacity
	KLDivergenceMuLossGradTensorOp<float, Eigen::DefaultDevice> operationC(1e-4, 1, 5);

	float errorC_ptr[] = { 0, 0, 0, 0, 0, 0, 0, 0 };

	operationC(y_pred.data(), y_true.data(), errorC_ptr, batch_size, memory_size, layer_size, time_step, device);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> errorC(errorC_ptr, batch_size, memory_size, layer_size);
	BOOST_CHECK_CLOSE(errorC(0, 0, 0), 3.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1, 0), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0, 0), 1.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1, 0), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 0, 1), 3.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1, 1), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0, 1), 1.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1, 1), 0.0, 1e-4);
}

/**
  KLDivergenceLogVarLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorKLDivergenceLogVarOp)
{
  KLDivergenceLogVarLossTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceLogVar = nullptr;
  KLDivergenceLogVarLossTensorOp<float, Eigen::DefaultDevice>* nullPointerKLDivergenceLogVar = nullptr;
  BOOST_CHECK_EQUAL(ptrKLDivergenceLogVar, nullPointerKLDivergenceLogVar);
}

BOOST_AUTO_TEST_CASE(destructorKLDivergenceLogVarOp)
{
  KLDivergenceLogVarLossTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceLogVar = nullptr;
  ptrKLDivergenceLogVar = new KLDivergenceLogVarLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrKLDivergenceLogVar;
}

BOOST_AUTO_TEST_CASE(operationfunctionKLDivergenceLogVarOp2)
{
	// Without capacity
  KLDivergenceLogVarLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 1.29744244, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 2.43656349, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);

	// With capacity
	KLDivergenceLogVarLossTensorOp<float, Eigen::DefaultDevice> operationC(1e-3, 1, 5);

	float errorC_ptr[] = { 0, 0, 0, 0 };

	operationC(y_pred.data(), y_true.data(), errorC_ptr, batch_size, memory_size, layer_size, time_step, device);
	Eigen::TensorMap<Eigen::Tensor<float, 2>> errorC(errorC_ptr, batch_size, memory_size);
	BOOST_CHECK_CLOSE(errorC(0, 0), -3.70255756, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0), -2.56343651, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1), 0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1), 0, 1e-4);
}

/**
  KLDivergenceLogVarLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorKLDivergenceLogVarGradOp)
{
  KLDivergenceLogVarLossGradTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceLogVar = nullptr;
  KLDivergenceLogVarLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerKLDivergenceLogVar = nullptr;
  BOOST_CHECK_EQUAL(ptrKLDivergenceLogVar, nullPointerKLDivergenceLogVar);
}

BOOST_AUTO_TEST_CASE(destructorKLDivergenceLogVarGradOp)
{
  KLDivergenceLogVarLossGradTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceLogVar = nullptr;
  ptrKLDivergenceLogVar = new KLDivergenceLogVarLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrKLDivergenceLogVar;
}

BOOST_AUTO_TEST_CASE(operationfunctionKLDivergenceLogVarGradOp)
{
	// Without capacity
  KLDivergenceLogVarLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), -1.14872122, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -2.21828175, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -1.14872122, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), -2.21828175, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);

	// With capacity
	KLDivergenceLogVarLossGradTensorOp<float, Eigen::DefaultDevice> operationC(1e-4, 1, 5);

	float errorC_ptr[] = { 0, 0, 0, 0, 0, 0, 0, 0 };

	operationC(y_pred.data(), y_true.data(), errorC_ptr, batch_size, memory_size, layer_size, time_step, device);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> errorC(errorC_ptr, batch_size, memory_size, layer_size);
	BOOST_CHECK_CLOSE(errorC(0, 0, 0), 3.85127878, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1, 0), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0, 0), 2.78171825, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1, 0), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 0, 1), 3.85127878, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1, 1), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0, 1), 2.78171825, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1, 1), 0.0, 1e-4);
}

/**
BCEWithLogitsLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorBCEWithLogitsOp)
{
  BCEWithLogitsLossTensorOp<float, Eigen::DefaultDevice>* ptrBCEWithLogits = nullptr;
  BCEWithLogitsLossTensorOp<float, Eigen::DefaultDevice>* nullPointerBCEWithLogits = nullptr;
  BOOST_CHECK_EQUAL(ptrBCEWithLogits, nullPointerBCEWithLogits);
}

BOOST_AUTO_TEST_CASE(destructorBCEWithLogitsOp)
{
  BCEWithLogitsLossTensorOp<float, Eigen::DefaultDevice>* ptrBCEWithLogits = nullptr;
  ptrBCEWithLogits = new BCEWithLogitsLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrBCEWithLogits;
}

BOOST_AUTO_TEST_CASE(operationfunctionBCEWithLogitsOp)
{
  BCEWithLogitsLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 0},{0, 1}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 2}, {0, 0}},
    {{1, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 2.44018984, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 1.44018972, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
BCEWithLogitsLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorBCEWithLogitsGradOp)
{
  BCEWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>* ptrBCEWithLogits = nullptr;
  BCEWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerBCEWithLogits = nullptr;
  BOOST_CHECK_EQUAL(ptrBCEWithLogits, nullPointerBCEWithLogits);
}

BOOST_AUTO_TEST_CASE(destructorBCEWithLogitsGradOp)
{
  BCEWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>* ptrBCEWithLogits = nullptr;
  ptrBCEWithLogits = new BCEWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrBCEWithLogits;
}

BOOST_AUTO_TEST_CASE(operationfunctionBCEWithLogitsGradOp)
{
  BCEWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), 0.268941402, 1e-4); //0.268941432
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -0.731058598, 1e-4); //-0.731058598
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -0.880797088, 1e-4); //-0.880797088
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), 0.119202971, 1e-4); //0.119202919
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
}

/**
  CrossEntropyWithLogitsLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCrossEntropyWithLogitsOp)
{
  CrossEntropyWithLogitsLossTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropyWithLogits = nullptr;
  CrossEntropyWithLogitsLossTensorOp<float, Eigen::DefaultDevice>* nullPointerCrossEntropyWithLogits = nullptr;
  BOOST_CHECK_EQUAL(ptrCrossEntropyWithLogits, nullPointerCrossEntropyWithLogits);
}

BOOST_AUTO_TEST_CASE(destructorCrossEntropyWithLogitsOp)
{
  CrossEntropyWithLogitsLossTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropyWithLogits = nullptr;
  ptrCrossEntropyWithLogits = new CrossEntropyWithLogitsLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrCrossEntropyWithLogits;
}

BOOST_AUTO_TEST_CASE(operationfunctionCrossEntropyWithLogitsOp1)
{
  CrossEntropyWithLogitsLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    //{1, 0},{0, 1}
    {1, 0}, {1, 0}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    //{{1, 2}, {0, 0}},
    //{{1, 2}, {0, 0}}
    { {0, 2.19722}, {0, 0}},
    {{2.19722, 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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  //BOOST_CHECK_CLOSE(error(0, 0), 0.656630814, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0), 0.156630829, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0), 1.15129054, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.0526805036, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  CrossEntropyWithLogitsLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCrossEntropyWithLogitsGradOp)
{
  CrossEntropyWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropyWithLogits = nullptr;
  CrossEntropyWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerCrossEntropyWithLogits = nullptr;
  BOOST_CHECK_EQUAL(ptrCrossEntropyWithLogits, nullPointerCrossEntropyWithLogits);
}

BOOST_AUTO_TEST_CASE(destructorCrossEntropyWithLogitsGradOp)
{
  CrossEntropyWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>* ptrCrossEntropyWithLogits = nullptr;
  ptrCrossEntropyWithLogits = new CrossEntropyWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrCrossEntropyWithLogits;
}

BOOST_AUTO_TEST_CASE(operationfunctionCrossEntropyWithLogitsGradOp1)
{
  CrossEntropyWithLogitsLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  //BOOST_CHECK_CLOSE(error(0, 0, 0), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0, 0), -0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 0, 1), -1.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0, 1), -0.5, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
  // Option 1
  BOOST_CHECK_CLOSE(error(0, 0, 0), 0.5, 1e-4); // NegLogLiklihoodGrad = -4.99994993
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -0.598610044, 1e-4); // NegLogLiklihoodGrad = -0.555554926
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -1.09861004, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
  //// Option 2
  //BOOST_CHECK_CLOSE(error(0, 0, 0), -4.9999299, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0, 0), -0.555555224, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 0, 1), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 0, 1), 0.0, 1e-4);
  //BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);
}

/**
  MSERangeUBLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMSERangeUBOp)
{
  MSERangeUBLossTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeUB = nullptr;
  MSERangeUBLossTensorOp<float, Eigen::DefaultDevice>* nullPointerMSERangeUB = nullptr;
  BOOST_CHECK_EQUAL(ptrMSERangeUB, nullPointerMSERangeUB);
}

BOOST_AUTO_TEST_CASE(destructorMSERangeUBOp)
{
  MSERangeUBLossTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeUB = nullptr;
  ptrMSERangeUB = new MSERangeUBLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMSERangeUB;
}

BOOST_AUTO_TEST_CASE(operationfunctionMSERangeUBOp)
{
  MSERangeUBLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 3}, {0, 0}},
    {{0, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0.25, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  MSERangeUBLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMSERangeUBGradOp)
{
  MSERangeUBLossGradTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeUB = nullptr;
  MSERangeUBLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMSERangeUB = nullptr;
  BOOST_CHECK_EQUAL(ptrMSERangeUB, nullPointerMSERangeUB);
}

BOOST_AUTO_TEST_CASE(destructorMSERangeUBGradOp)
{
  MSERangeUBLossGradTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeUB = nullptr;
  ptrMSERangeUB = new MSERangeUBLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMSERangeUB;
}

BOOST_AUTO_TEST_CASE(operationfunctionMSERangeUBGradOp)
{
  MSERangeUBLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

/**
  MSERangeLBLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMSERangeLBOp)
{
  MSERangeLBLossTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeLB = nullptr;
  MSERangeLBLossTensorOp<float, Eigen::DefaultDevice>* nullPointerMSERangeLB = nullptr;
  BOOST_CHECK_EQUAL(ptrMSERangeLB, nullPointerMSERangeLB);
}

BOOST_AUTO_TEST_CASE(destructorMSERangeLBOp)
{
  MSERangeLBLossTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeLB = nullptr;
  ptrMSERangeLB = new MSERangeLBLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMSERangeLB;
}

BOOST_AUTO_TEST_CASE(operationfunctionMSERangeLBOp)
{
  MSERangeLBLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 3}, {0, 0}},
    {{0, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, 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);
}

/**
  MSERangeLBLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMSERangeLBGradOp)
{
  MSERangeLBLossGradTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeLB = nullptr;
  MSERangeLBLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMSERangeLB = nullptr;
  BOOST_CHECK_EQUAL(ptrMSERangeLB, nullPointerMSERangeLB);
}

BOOST_AUTO_TEST_CASE(destructorMSERangeLBGradOp)
{
  MSERangeLBLossGradTensorOp<float, Eigen::DefaultDevice>* ptrMSERangeLB = nullptr;
  ptrMSERangeLB = new MSERangeLBLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMSERangeLB;
}

BOOST_AUTO_TEST_CASE(operationfunctionMSERangeLBGradOp)
{
  MSERangeLBLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

/**
  KLDivergenceCatLossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorKLDivergenceCatOp)
{
  KLDivergenceCatLossTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceCat = nullptr;
  KLDivergenceCatLossTensorOp<float, Eigen::DefaultDevice>* nullPointerKLDivergenceCat = nullptr;
  BOOST_CHECK_EQUAL(ptrKLDivergenceCat, nullPointerKLDivergenceCat);
}

BOOST_AUTO_TEST_CASE(destructorKLDivergenceCatOp)
{
  KLDivergenceCatLossTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceCat = nullptr;
  ptrKLDivergenceCat = new KLDivergenceCatLossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrKLDivergenceCat;
}

BOOST_AUTO_TEST_CASE(operationfunctionKLDivergenceCatOp)
{
	// Without capacity
  KLDivergenceCatLossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 6.12971067, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 30.2493725, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);

	// With capacity
	KLDivergenceCatLossTensorOp<float, Eigen::DefaultDevice> operationC(1e-3, 1, 5);

	float errorC_ptr[] = { 0, 0, 0, 0 };

	operationC(y_pred.data(), y_true.data(), errorC_ptr, batch_size, memory_size, layer_size, time_step, device);
	Eigen::TensorMap<Eigen::Tensor<float, 2>> errorC(errorC_ptr, batch_size, memory_size);
	BOOST_CHECK_CLOSE(errorC(0, 0), 5.43656349, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0), 29.5562248, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1), 0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1), 0, 1e-4);
}

/**
  KLDivergenceCatLossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorKLDivergenceCatGradOp)
{
  KLDivergenceCatLossGradTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceCat = nullptr;
  KLDivergenceCatLossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerKLDivergenceCat = nullptr;
  BOOST_CHECK_EQUAL(ptrKLDivergenceCat, nullPointerKLDivergenceCat);
}

BOOST_AUTO_TEST_CASE(destructorKLDivergenceCatGradOp)
{
  KLDivergenceCatLossGradTensorOp<float, Eigen::DefaultDevice>* ptrKLDivergenceCat = nullptr;
  ptrKLDivergenceCat = new KLDivergenceCatLossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrKLDivergenceCat;
}

BOOST_AUTO_TEST_CASE(operationfunctionKLDivergenceCatGradOp)
{
	// No capacity
  KLDivergenceCatLossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

  operation(y_pred.data(), y_true.data(), error_ptr, batch_size, memory_size, layer_size, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 3>> error(error_ptr, batch_size, memory_size, layer_size);
  BOOST_CHECK_CLOSE(error(0, 0, 0), -5.43656349, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 0), -22.1671677, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 0), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 0, 1), -5.43656349, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1, 1), 0.0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0, 1), -22.1671677, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1, 1), 0.0, 1e-4);

	// With capacity
	KLDivergenceCatLossGradTensorOp<float, Eigen::DefaultDevice> operationC(1e-4, 1, 5);

	float errorC_ptr[] = { 0, 0, 0, 0, 0, 0, 0, 0 };

	operationC(y_pred.data(), y_true.data(), errorC_ptr, batch_size, memory_size, layer_size, time_step, device);
	Eigen::TensorMap<Eigen::Tensor<float, 3>> errorC(errorC_ptr, batch_size, memory_size, layer_size);
	BOOST_CHECK_CLOSE(errorC(0, 0, 0), -4.74341631, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1, 0), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0, 0), -21.47402, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1, 0), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 0, 1), -4.74341631, 1e-4);
	BOOST_CHECK_CLOSE(errorC(0, 1, 1), 0.0, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 0, 1), -21.47402, 1e-4);
	BOOST_CHECK_CLOSE(errorC(1, 1, 1), 0.0, 1e-4);
}

/**
  MAPELossOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMAPELossOp)
{
  MAPELossTensorOp<float, Eigen::DefaultDevice>* ptrMAPELoss = nullptr;
  MAPELossTensorOp<float, Eigen::DefaultDevice>* nullPointerMAPELoss = nullptr;
  BOOST_CHECK_EQUAL(ptrMAPELoss, nullPointerMAPELoss);
}

BOOST_AUTO_TEST_CASE(destructorMAPELossOp)
{
  MAPELossTensorOp<float, Eigen::DefaultDevice>* ptrMAPELoss = nullptr;
  ptrMAPELoss = new MAPELossTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMAPELoss;
}

BOOST_AUTO_TEST_CASE(operationfunctionMAPELossOp)
{
  MAPELossTensorOp<float, Eigen::DefaultDevice> operation;

  const int memory_size = 2;
  const int batch_size = 2;
  const int layer_size = 2;
  const int time_step = 0;
  Eigen::Tensor<float, 2> y_true(batch_size, layer_size);
  y_true.setValues({
    {1, 2}, {1, 2}
    });
  Eigen::Tensor<float, 3> y_pred(batch_size, memory_size, layer_size);
  y_pred.setValues({
    {{1, 1}, {0, 0}},
    {{2, 2}, {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, time_step, device);
  Eigen::TensorMap<Eigen::Tensor<float, 2>> error(error_ptr, batch_size, memory_size);
  BOOST_CHECK_CLOSE(error(0, 0), 0.249999881, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 0), 0.499999523, 1e-4);
  BOOST_CHECK_CLOSE(error(0, 1), 0, 1e-4);
  BOOST_CHECK_CLOSE(error(1, 1), 0, 1e-4);
}

/**
  MAPELossGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorMAPELossGradOp)
{
  MAPELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMAPELoss = nullptr;
  MAPELossGradTensorOp<float, Eigen::DefaultDevice>* nullPointerMAPELoss = nullptr;
  BOOST_CHECK_EQUAL(ptrMAPELoss, nullPointerMAPELoss);
}

BOOST_AUTO_TEST_CASE(destructorMAPELossGradOp)
{
  MAPELossGradTensorOp<float, Eigen::DefaultDevice>* ptrMAPELoss = nullptr;
  ptrMAPELoss = new MAPELossGradTensorOp<float, Eigen::DefaultDevice>();
  delete ptrMAPELoss;
}

BOOST_AUTO_TEST_CASE(operationfunctionMAPELossGradOp)
{
  MAPELossGradTensorOp<float, Eigen::DefaultDevice> operation;

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

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

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

BOOST_AUTO_TEST_SUITE_END()


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * 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 "qec.hpp"

#include <cmath>
#include <iostream>

#include <boost/program_options.hpp>

#include <xtensor/xio.hpp>
#include <xtensor/xmath.hpp>
#include <xtensor-blas/xlinalg.hpp>

#include <cli/reversible_stores.hpp>
#include <reversible/utils/matrix_utils.hpp>

namespace cirkit
{

using boost::program_options::value;

qec_command::qec_command( const environment::ptr& env )
  : cirkit_command( env, "Quantum equivalence checking" )
{
  opts.add_options()
    ( "qid,q",     value( &qids )->composing(), "value of a quantum circuit" )
    ( "rid,r",     value( &rids )->composing(), "value of a reversible circuit" )
    ( "ancilla,a",                              "add ancilla (to the end) if necessary" )
    ( "progress,p",                             "show progress" )
    ( "quiet",                                  "do not print result" )
    ;
}

command::rules_t qec_command::validity_rules() const
{
  return {
    {[this]() { return qids.size() + rids.size() == 2u; }, "two circuits must be selected"},
    {
      [this]() {
        const auto& circuits = env->store<circuit>();
        for ( auto id : qids )
        {
          if ( id >= circuits.size() ) return false;
        }
        for ( auto id : rids )
        {
          if ( id >= circuits.size() ) return false;
        }

        return true;
      },
      "invalid circuit id"
    }
  };
}

bool qec_command::execute()
{
  const auto& circuits = env->store<circuit>();

  std::vector<std::pair<xt::xarray<complex_t>, unsigned>> matrices;

  for ( auto id : qids )
  {
    matrices.push_back( {matrix_from_clifford_t_circuit( circuits[id], is_set( "progress" ) ), circuits[id].lines()} );
  }
  for ( auto id : rids )
  {
    matrices.push_back( {matrix_from_reversible_circuit( circuits[id] ), circuits[id].lines()} );
  }

  assert( matrices.size() == 2u );

  /* add ancillas if necessary */
  if ( matrices[0u].second != matrices[1u].second )
  {
    if ( is_set( "ancilla" ) )
    {
      const auto d0 = matrices[0u].second;
      const auto d1 = matrices[1u].second;

      if ( d0 < d1 )
      {
        /* make first matrix larger */
        matrices[0u].first = xt::linalg::kron( identity( 1 << ( d1 - d0 ) ), matrices[0u].first );
      }
      else
      {
        /* make first matrix larger */
        matrices[1u].first = xt::linalg::kron( identity( 1 << ( d0 - d1 ) ), matrices[1u].first );
      }
    }
    else
    {
      std::cout << "[e] matrices have different dimensions, use ancilla option to adjust." << std::endl;
      return true;
    }
  }

  result = complex_allclose( matrices[0u].first, matrices[1u].first );
  if ( is_set( "quiet" ) ) return true;

  if ( result )
  {
    std::cout << "[i] circuits are \033[1;32mequivalent\033[0m" << std::endl;
  }
  else
  {
    std::cout << "[i] circuits are \033[1;31mnot equivalent\033[0m" << std::endl;
  }

  qids.clear();
  rids.clear();

  return true;
}

command::log_opt_t qec_command::log() const
{
  return log_map_t( {
      {"result", result}
    } );
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End:


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
Copyright (C) 2005, 2007 StatPro Italia srl
Copyright (C) 2015 CompatibL

This file is part of QuantLib, a free-software/open-source library
for financial quantitative analysts and developers - http://quantlib.org/

QuantLib is free software: you can redistribute it and/or modify it
under the terms of the QuantLib license.  You should have received a
copy of the license along with this program; if not, please email
<quantlib-dev@lists.sf.net>. The license is also available online at
<http://quantlib.org/license.shtml>.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

// Based on swaptionvolatilitymatrix.cpp project from Quantlib.

#ifndef cl_adjoint_default_probability_curve_impl_hpp
#define cl_adjoint_default_probability_curve_impl_hpp
#pragma once

#include "adjointtestutilities.hpp"
#include "adjointtestbase.hpp"
#include <boost/make_shared.hpp>
#include "adjointdefaultprobabilitycurvetest.hpp"
#include "utilities.hpp"
#include <ql/termstructures/credit/piecewisedefaultcurve.hpp>
#include <ql/termstructures/credit/defaultprobabilityhelpers.hpp>
#include <ql/termstructures/credit/flathazardrate.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/instruments/creditdefaultswap.hpp>
#include <ql/pricingengines/credit/midpointcdsengine.hpp>
#include <ql/math/interpolations/linearinterpolation.hpp>
#include <ql/math/interpolations/backwardflatinterpolation.hpp>
#include <ql/math/interpolations/loginterpolation.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/utilities/dataformatters.hpp>
#include <iomanip>
#include <ql/time/calendars/target.hpp>
#include <ql/time/daycounters/actual360.hpp>
#include <ql/time/daycounters/thirty360.hpp>

using namespace QuantLib;
using namespace boost::unit_test_framework;

#define OUTPUT_FOLDER_NAME "AdjointSwaptionVolatilityMatrix"

namespace
{
    struct CommonVars
    {
        CommonVars() :
        dayCounter_(Actual360())
        , calendar_(TARGET())
        , today_(Settings::instance().evaluationDate())
        , startDate_(today_)
        , endDate_(startDate_)
        , notional_(1.0)
        , frequency_(Quarterly)
        , convention_(Following)
        , rule_(DateGeneration::TwentiethIMM)
        , settlementDays_(1)
        , recoveryRate_(0.4)
        , fixedRate_(0.05)
        {
            discountCurve_.linkTo(boost::shared_ptr<YieldTermStructure>(
                new FlatForward(today_, 0.06, Actual360())));
        }

        DayCounter dayCounter_;
        Calendar calendar_;

        Date today_;
        Date startDate_;
        Date endDate_;

        Real notional_;
        Frequency frequency_;
        BusinessDayConvention convention_;
        DateGeneration::Rule rule_;
        Integer settlementDays_;
        Real recoveryRate_;
        Real fixedRate_;
        RelinkableHandle<YieldTermStructure> discountCurve_;

    };

    template <class T, class I>
    struct FairRate : CommonVars
    {
        FairRate() :
        CommonVars()
        {
        }

        template <class T, class I>
        Real computeFairRateFromSpread(Real quote, Integer n)
        {
            settlementDays_ = 1;
            std::vector<boost::shared_ptr<DefaultProbabilityHelper> > helpers = {
                boost::make_shared<SpreadCdsHelper>(quote, Period(n, Months),
                settlementDays_, calendar_,
                frequency_, convention_, rule_,
                dayCounter_, recoveryRate_,
                discountCurve_) };

            RelinkableHandle<DefaultProbabilityTermStructure> piecewiseCurve;
            piecewiseCurve.linkTo(
                boost::make_shared<PiecewiseDefaultCurve<T, I>>(
                today_, helpers,
                Thirty360()));

            // ensure apple-to-apple comparison
            SavedSettings backup;
            Settings::instance().includeTodaysCashFlows() = true;

            Date protectionStart = today_ + settlementDays_;
            startDate_ = calendar_.adjust(protectionStart, convention_);
            endDate_ = today_ + n * Months;

            Schedule schedule(startDate_, endDate_, Period(frequency_), calendar_,
                              convention_, Unadjusted, rule_, false);

            CreditDefaultSwap cds(Protection::Buyer, notional_, quote,
                                  schedule, convention_, dayCounter_,
                                  true, true, protectionStart);
            cds.setPricingEngine(boost::shared_ptr<PricingEngine>(
                new MidPointCdsEngine(piecewiseCurve, recoveryRate_,
                discountCurve_)));

            return cds.fairSpread();
        }

        template <class T, class I>
        Real computeFairRateFromUpfront(Real quote, Integer n)
        {
            settlementDays_ = 0;
            std::vector<boost::shared_ptr<DefaultProbabilityHelper> > helpers = {
                boost::make_shared<UpfrontCdsHelper>(quote, fixedRate_, Period(n, Months),
                settlementDays_, calendar_,
                frequency_, convention_, rule_,
                dayCounter_, recoveryRate_,
                discountCurve_) };

            RelinkableHandle<DefaultProbabilityTermStructure> piecewiseCurve;
            piecewiseCurve.linkTo(
                boost::make_shared<PiecewiseDefaultCurve<T, I>>(today_, helpers,
                Thirty360()));

            // ensure apple-to-apple comparison
            SavedSettings backup;
            Settings::instance().includeTodaysCashFlows() = true;

            Date protectionStart = today_ + settlementDays_;
            startDate_ = calendar_.adjust(protectionStart, convention_);
            endDate_ = today_ + n * Months;

            Schedule schedule(startDate_, endDate_, Period(frequency_), calendar_,
                              convention_, Unadjusted, rule_, false);

            CreditDefaultSwap cds(Protection::Buyer, notional_, quote, fixedRate_,
                                  schedule, convention_, dayCounter_,
                                  true, true, protectionStart);
            cds.setPricingEngine(boost::shared_ptr<PricingEngine>(
                new MidPointCdsEngine(piecewiseCurve, recoveryRate_,
                discountCurve_, true)));

            return cds.fairUpfront();
        }
    };

    struct TestFlatHazardRate
        : public cl::AdjointTest<TestFlatHazardRate>
    {
        TestFlatHazardRate(Size size)
            : size_(size)
            , hazardRate_(1, 0.0100)
            , t_(size)
            , computedProbability_(size)
            , var_()
        {
            for (Size i = 0; i < size_; i++)
            {
                var_.endDate_ = var_.calendar_.advance(var_.endDate_, 1, Months);
                t_[i] = var_.dayCounter_.yearFraction(var_.startDate_, var_.endDate_);
            }
        }

        Size indepVarNumber() { return 1; }

        Size depVarNumber() { return size_; }

        void computeProbability()
        {
            Handle<Quote> hazardRateQuote = Handle<Quote>(
                boost::make_shared<SimpleQuote>(hazardRate_[0]));
            FlatHazardRate flatHazardRate(var_.today_, hazardRateQuote, var_.dayCounter_);

            for (Size i = 0; i < size_; i++)
            {
                computedProbability_[i] = flatHazardRate.defaultProbability(t_[i]);
            }
        }

        // Calculates derivatives using finite difference method.
        void calcAnalytical()
        {
            analyticalResults_.resize(size_, 0);
            for (Size i = 0; i < size_; i++)
            {
                analyticalResults_[i] = t_[i] * std::exp(-hazardRate_[0] * t_[i]);
            }
        }

        double relativeTol() const { return 1e-10; }

        double absTol() const { return 1e-10; }

        Size size_;
        std::vector<cl::tape_double> hazardRate_;
        std::vector<Time> t_;
        std::vector<cl::tape_double> computedProbability_;

        CommonVars var_;
    };

    struct TestFlatHazardRateTime
        : public cl::AdjointTest<TestFlatHazardRateTime>
    {
        static const CalcMethod default_method = other;

        TestFlatHazardRateTime(Size size)
            : size_(size)
            , hazardRate_(1, 0.0100)
            , t_(size)
            , doubleT_(size)
            , computedProbability_(size)
            , var_()
        {
            for (Size i = 0; i < size_; i++)
            {
                var_.endDate_ = var_.calendar_.advance(var_.endDate_, 1, Months);
                t_[i] = var_.dayCounter_.yearFraction(var_.startDate_, var_.endDate_);
                doubleT_[i] = double(var_.dayCounter_.yearFraction(var_.startDate_, var_.endDate_));
            }
        }

        Size indepVarNumber() { return size_; }

        Size depVarNumber() { return size_; }

        void computeProbability()
        {
            Handle<Quote> hazardRateQuote = Handle<Quote>(
                boost::make_shared<SimpleQuote>(hazardRate_[0]));
            FlatHazardRate flatHazardRate(var_.today_, hazardRateQuote, var_.dayCounter_);

            for (Size i = 0; i < size_; i++)
            {
                computedProbability_[i] = flatHazardRate.defaultProbability(t_[i]);
            }
        }

        // Calculates derivatives using finite difference method.

        void calcAnalytical()
        {
            double h = 1e-4;  // shift for finite diff. method
            analyticalResults_.resize(size_*size_, 0);

            Handle<Quote> hazardRateQuote = Handle<Quote>(
                boost::make_shared<SimpleQuote>(0.0100));
            FlatHazardRate flatHazardRate(var_.today_, hazardRateQuote, var_.dayCounter_);

            for (Size i = 0; i < size_; i++)
            {
                Real rightValue = flatHazardRate.defaultProbability(t_[i] + h);
                Real leftValue = flatHazardRate.defaultProbability(t_[i] - h);
                analyticalResults_[i*size_ + i] = (rightValue - leftValue) / (2 * h);
            }
        }

        double relativeTol() const { return 1e-4; }

        double absTol() const { return 1e-4; }

        Size size_;
        std::vector<cl::tape_double> hazardRate_;
        std::vector<cl::tape_double> t_;
        std::vector<double> doubleT_;
        std::vector<cl::tape_double> computedProbability_;

        CommonVars var_;
    };

    template <class T, class I>
    struct TestBootstrapFromSpread
        : public cl::AdjointTest<TestBootstrapFromSpread<T,I>>
        {
        static const CalcMethod default_method = other;

        TestBootstrapFromSpread(Size size)
            : size_(size)
            , quote_(size)
            , doubleQuote_(size)
            , computedRate_(size)
            , var_()
        {
            for (Size i = 0; i < size_; i++)
            {
                doubleQuote_[i] = (i + 1)*0.001;
                quote_[i] = (i + 1)*0.001;
            }
        }

            Size indepVarNumber() { return size_; }

            Size depVarNumber() { return size_; }

            void computeFairRate()
            {
                for (Size i = 0; i < size_; i++)
                    computedRate_[i] = var_.computeFairRateFromSpread<T, I>(quote_[i], i+1);
            }

            // Calculates derivatives using finite difference method.
            void calcAnalytical()
            {
                double h = 1e-4;  // shift for finite diff. method
                analyticalResults_.resize(size_*size_, 0);
                for (Size i = 0; i < size_; i++)
                {
                    Real rightValue = var_.computeFairRateFromSpread<T, I>(quote_[i] + h, i + 1);
                    Real leftValue = var_.computeFairRateFromSpread<T, I>(quote_[i] - h, i + 1);
                    analyticalResults_[i*size_ + i] = (rightValue - leftValue) / (2 * h);
                }
            }

            double relativeTol() const { return 1e-4; }

            double absTol() const { return 1e-4; }

            Size size_;
            std::vector<cl::tape_double> quote_;
            std::vector<double> doubleQuote_;
            std::vector<cl::tape_double> computedRate_;

            FairRate<T, I> var_;
        };

    template <class T, class I>
    struct TestBootstrapFromUpfront
        : public cl::AdjointTest<TestBootstrapFromUpfront<T, I>>
    {
        static const CalcMethod default_method = other;

        TestBootstrapFromUpfront(Size size)
            : size_(size)
            , quote_(size)
            , doubleQuote_(size)
            , computedRate_(size)
            , var_()
        {
            for (Size i = 0; i < size_; i++)
            {
                doubleQuote_[i] = (i + 1)*0.001;
                quote_[i] = (i + 1)*0.001;
            }
        }

        Size indepVarNumber() { return size_; }

        Size depVarNumber() { return size_; }

        void computeFairRate()
        {
            for (Size i = 0; i < size_; i++)
                computedRate_[i] = var_.computeFairRateFromUpfront<T, I>(quote_[i], i + 2);
        }

        // Calculates derivatives using finite difference method.
        void calcAnalytical()
        {
            double h = 1e-4;  // shift for finite diff. method
            analyticalResults_.resize(size_*size_, 0);
            for (Size i = 0; i < size_; i++)
            {
                Real rightValue = var_.computeFairRateFromUpfront<T, I>(quote_[i] + h, i + 2);
                Real leftValue = var_.computeFairRateFromUpfront<T, I>(quote_[i] - h, i + 2);
                analyticalResults_[i*size_ + i] = (rightValue - leftValue) / (2 * h);
            }
        }

        double relativeTol() const { return 1e-4; }

        double absTol() const { return 1e-4; }

        Size size_;
        std::vector<cl::tape_double> quote_;
        std::vector<double> doubleQuote_;
        std::vector<cl::tape_double> computedRate_;

        FairRate<T, I> var_;
    };
}
#endif


#ifndef STAN_MATH_PRIM_PROB_FRECHET_RNG_HPP
#define STAN_MATH_PRIM_PROB_FRECHET_RNG_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/err.hpp>
#include <stan/math/prim/fun/max_size.hpp>
#include <boost/random/weibull_distribution.hpp>
#include <boost/random/variate_generator.hpp>

namespace stan {
namespace math {
/** \ingroup prob_dists
 * Return a pseudorandom Frechet variate for the given shape
 * and scale parameters using the specified random number generator.
 *
 * alpha and sigma can each be a scalar or a one-dimensional container. Any
 * non-scalar inputs must be the same size.
 *
 * @tparam T_shape type of shape parameter
 * @tparam T_scale type of scale parameter
 * @tparam RNG type of random number generator
 * @param alpha (Sequence of) positive shape parameter(s)
 * @param sigma (Sequence of) positive scale parameter(s)
 * @param rng random number generator
 * @return (Sequence of) Frechet random variate(s)
 * @throw std::domain_error if alpha is nonpositive or sigma is nonpositive
 * @throw std::invalid_argument if non-scalar arguments are of different
 * sizes
 */
template <typename T_shape, typename T_scale, class RNG>
inline typename VectorBuilder<true, double, T_shape, T_scale>::type frechet_rng(
    const T_shape& alpha, const T_scale& sigma, RNG& rng) {
  using boost::variate_generator;
  using boost::random::weibull_distribution;
  using T_alpha_ref = ref_type_t<T_shape>;
  using T_sigma_ref = ref_type_t<T_scale>;
  static const char* function = "frechet_rng";
  check_consistent_sizes(function, "Shape parameter", alpha, "Scale Parameter",
                         sigma);
  T_alpha_ref alpha_ref = alpha;
  T_sigma_ref sigma_ref = sigma;
  check_positive_finite(function, "Shape parameter", alpha_ref);
  check_positive_finite(function, "Scale parameter", sigma_ref);

  scalar_seq_view<T_alpha_ref> alpha_vec(alpha_ref);
  scalar_seq_view<T_sigma_ref> sigma_vec(sigma_ref);
  size_t N = max_size(alpha, sigma);
  VectorBuilder<true, double, T_shape, T_scale> output(N);

  for (size_t n = 0; n < N; ++n) {
    variate_generator<RNG&, weibull_distribution<> > weibull_rng(
        rng, weibull_distribution<>(alpha_vec[n], 1.0 / sigma_vec[n]));
    output[n] = 1 / weibull_rng();
  }

  return output.data();
}

}  // namespace math
}  // namespace stan
#endif