/**TODO:  Add copyright*/

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

#include <iostream>

using namespace EvoNet;
using namespace std;

BOOST_AUTO_TEST_SUITE(integrationFunctionTensor)

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

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

BOOST_AUTO_TEST_CASE(operationfunctionSumTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	Eigen::DefaultDevice device;

	SumTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {2}}, {{0}, {4}}, {{0}, {6}}, {{0}, {8}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionSumTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 3> sink_input(batch_size, memory_size, sink_layer_size);
  sink_input.setConstant(0);

  Eigen::DefaultDevice device;

  SumTensorOp<double, Eigen::DefaultDevice> operation;
  operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<double, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {2}}, {{0}, {4}}, {{0}, {6}}, {{0}, {8}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameSumTensorOp)
{
	SumTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "SumTensorOp");
}

/**
 ProdTensorOp Tests
*/
BOOST_AUTO_TEST_CASE(destructorProdTensorOp)
{
	ProdTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
	ptrReLU = new ProdTensorOp<float, Eigen::DefaultDevice>();
	delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionProdTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
  //sink_input.setZero(); // Pre initNode update
	sink_input.setConstant(1);

	Eigen::DefaultDevice device;

	ProdTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	//expected.setValues({ {{0}, {1}}, {{0}, {4}}, {{0}, {9}}, {{0}, {16}} }); // Pre initNode update
  expected.setValues({ {{1}, {1}}, {{1}, {4}}, {{1}, {9}}, {{1}, {16}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionProdTensorOp2)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 2;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
  weights.setZero();
  weights(0, 0) = 2; weights(1, 1) = 2;
  Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
  //sink_input.setZero(); // Pre initNode update
  sink_input.setConstant(1);

  Eigen::DefaultDevice device;

  ProdTensorOp<float, Eigen::DefaultDevice> operation;
  operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{1, 1}, {2, 2}}, {{1, 1}, {4, 4}}, {{1, 1}, {6, 6}}, {{1, 1}, {8,8}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameProdTensorOp)
{
	ProdTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "ProdTensorOp");
}

/**
 ProdSCTensorOp Tests
*/
BOOST_AUTO_TEST_CASE(destructorProdSCTensorOp)
{
  ProdSCTensorOp<float, Eigen::DefaultDevice>* ptrReLU = nullptr;
  ptrReLU = new ProdSCTensorOp<float, Eigen::DefaultDevice>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(operationfunctionProdSCTensorOp)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 2;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
  weights.setZero();
  weights(0, 0) = 2; weights(1, 1) = 2;
  Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
  //sink_input.setZero(); // Pre initNode update
  sink_input.setConstant(1);

  Eigen::DefaultDevice device;

  ProdSCTensorOp<float, Eigen::DefaultDevice> operation;
  operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{1, 1}, {2, 2}}, {{1, 1}, {4, 4}}, {{1, 1}, {6, 6}}, {{1, 1}, {8,8}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameProdSCTensorOp)
{
  ProdSCTensorOp<float, Eigen::DefaultDevice> operation;

  BOOST_CHECK_EQUAL(operation.getName(), "ProdSCTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMaxTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 2}, {0, 0}},
		{{2, 3}, {0, 0}},
		{{3, 4}, {0, 0}},
		{{4, 5}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	Eigen::DefaultDevice device;

	MaxTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {2}}, {{0}, {3}}, {{0}, {4}}, {{0}, {5}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameMaxTensorOp)
{
	MaxTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "MaxTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMinTensorOp)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 2}, {0, 0}},
    {{2, 3}, {0, 0}},
    {{3, 4}, {0, 0}},
    {{4, 5}, {0, 0}} });
  Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
  sink_input.setConstant(2);

  Eigen::DefaultDevice device;

  MinTensorOp<float, Eigen::DefaultDevice> operation;
  operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{2}, {1}}, {{2}, {2}}, {{2}, {2}}, {{2}, {2}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMinTensorOp)
{
  MinTensorOp<float, Eigen::DefaultDevice> operation;

  BOOST_CHECK_EQUAL(operation.getName(), "MinTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMeanTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 2}, {0, 0}},
		{{2, 3}, {0, 0}},
		{{3, 4}, {0, 0}},
		{{4, 5}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	Eigen::DefaultDevice device;

	MeanTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {1.5}}, {{0}, {2.5}}, {{0}, {3.5}}, {{0}, {4.5}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionMeanTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 2}, {0, 0}},
    {{2, 3}, {0, 0}},
    {{3, 4}, {0, 0}},
    {{4, 5}, {0, 0}} });
  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 3> sink_input(batch_size, memory_size, sink_layer_size);
  sink_input.setConstant(0);

  Eigen::DefaultDevice device;

  MeanTensorOp<double, Eigen::DefaultDevice> operation;
  operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<double, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {1.5}}, {{0}, {2.5}}, {{0}, {3.5}}, {{0}, {4.5}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMeanTensorOp)
{
	MeanTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "MeanTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionVarModTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 2}, {0, 0}},
		{{2, 3}, {0, 0}},
		{{3, 4}, {0, 0}},
		{{4, 5}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	Eigen::DefaultDevice device;

	VarModTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {2.5}}, {{0}, {6.5}}, {{0}, {12.5}}, {{0}, {20.5}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionVarModTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 2}, {0, 0}},
    {{2, 3}, {0, 0}},
    {{3, 4}, {0, 0}},
    {{4, 5}, {0, 0}} });
  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 3> sink_input(batch_size, memory_size, sink_layer_size);
  sink_input.setConstant(0);

  Eigen::DefaultDevice device;

  VarModTensorOp<double, Eigen::DefaultDevice> operation;
  operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<double, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {2.5}}, {{0}, {6.5}}, {{0}, {12.5}}, {{0}, {20.5}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameVarModTensorOp)
{
	VarModTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "VarModTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionVarTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 2}, {0, 0}},
		{{2, 3}, {0, 0}},
		{{3, 4}, {0, 0}},
		{{4, 5}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	Eigen::DefaultDevice device;

	VarTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {0.25}}, {{0}, {0.25}}, {{0}, {0.25}}, {{0}, {0.25}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameVarTensorOp)
{
	VarTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "VarTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionCountTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_input(batch_size, memory_size, sink_layer_size);
	sink_input.setConstant(0);

	Eigen::DefaultDevice device;

	CountTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_output.data(), weights.data(), sink_input.data(), batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {2}}, {{0}, {2}}, {{0}, {2}}, {{0}, {2}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_input(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_input(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameCountTensorOp)
{
	CountTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "CountTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionSumErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	Eigen::DefaultDevice device;

	SumErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {4}}, {{0}, {8}}, {{0}, {12}}, {{0}, {16}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameSumErrorTensorOp)
{
	SumErrorTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "SumErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionProdErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	Eigen::DefaultDevice device;

	ProdErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {4}}, {{0}, {16}}, {{0}, {36}}, {{0}, {64}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionProdErrorTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> source_error(batch_size, memory_size, source_layer_size);
  source_error.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
  sink_derivative.setConstant(2);
  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setConstant(0);
  Eigen::Tensor<double, 3> sink_output(batch_size, memory_size, sink_layer_size);
  sink_output.setConstant(1);

  Eigen::DefaultDevice device;

  ProdErrorTensorOp<double, Eigen::DefaultDevice> operation;
  operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<double, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {4}}, {{0}, {16}}, {{0}, {36}}, {{0}, {64}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameProdErrorTensorOp)
{
	ProdErrorTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "ProdErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMaxErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
  source_error.setValues({ {{1, 2}, {0, 0}},
    {{2, 3}, {0, 0}},
    {{3, 4}, {0, 0}},
    {{4, 5}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 2}, {0, 0}},
		{{2, 3}, {0, 0}},
		{{3, 4}, {0, 0}},
		{{4, 5}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setValues({ {{0}, {2}},
		{{0}, {3}},
		{{0}, {4}},
		{{0}, {5}} });

	Eigen::DefaultDevice device;

	MaxErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {4}}, {{0}, {6}}, {{0}, {8}}, {{0}, {10}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameMaxErrorTensorOp)
{
	MaxErrorTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "MaxErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMinErrorTensorOp)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
  source_error.setValues({ {{1, 2}, {0, 0}},
    {{2, 3}, {0, 0}},
    {{3, 4}, {0, 0}},
    {{4, 5}, {0, 0}} });
  Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{1, 2}, {0, 0}},
    {{2, 3}, {0, 0}},
    {{3, 4}, {0, 0}},
    {{4, 5}, {0, 0}} });
  Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
  sink_derivative.setConstant(2);
  Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setConstant(0);
  Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
  sink_output.setValues({ {{0}, {1}},
    {{0}, {2}},
    {{0}, {3}},
    {{0}, {4}} });

  Eigen::DefaultDevice device;

  MinErrorTensorOp<float, Eigen::DefaultDevice> operation;
  operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {2}}, {{0}, {4}}, {{0}, {6}}, {{0}, {8}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMinErrorTensorOp)
{
  MinErrorTensorOp<float, Eigen::DefaultDevice> operation;

  BOOST_CHECK_EQUAL(operation.getName(), "MinErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMeanErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	Eigen::DefaultDevice device;

	MeanErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), 4, //NOTE: used only for testing purposes!
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {1}}, {{0}, {2}}, {{0}, {3}}, {{0}, {4}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionMeanErrorTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> source_error(batch_size, memory_size, source_layer_size);
  source_error.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
  sink_derivative.setConstant(2);
  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setConstant(0);
  Eigen::Tensor<double, 3> sink_output(batch_size, memory_size, sink_layer_size);
  sink_output.setConstant(1);

  Eigen::DefaultDevice device;

  MeanErrorTensorOp<double, Eigen::DefaultDevice> operation;
  operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), 4, //NOTE: used only for testing purposes!
    batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<double, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {1}}, {{0}, {2}}, {{0}, {3}}, {{0}, {4}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionVarModErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	Eigen::DefaultDevice device;

	VarModErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), 4, //NOTE: used only for testing purposes!
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {2}}, {{0}, {4}}, {{0}, {6}}, {{0}, {8}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionVarModErrorTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> source_error(batch_size, memory_size, source_layer_size);
  source_error.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{3, 3}, {0, 0}},
    {{4, 4}, {0, 0}} });
  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
  sink_derivative.setConstant(2);
  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setConstant(0);
  Eigen::Tensor<double, 3> sink_output(batch_size, memory_size, sink_layer_size);
  sink_output.setConstant(1);

  Eigen::DefaultDevice device;

  VarModErrorTensorOp<double, Eigen::DefaultDevice> operation;
  operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), 4, //NOTE: used only for testing purposes!
    batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

  Eigen::Tensor<double, 3> expected(batch_size, memory_size, sink_layer_size);
  expected.setValues({ {{0}, {2}}, {{0}, {4}}, {{0}, {6}}, {{0}, {8}} });

  for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
    for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
      for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
        //std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
        BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
      }
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMeanErrorTensorOp)
{
	MeanErrorTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "MeanErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionVarErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	Eigen::DefaultDevice device;

	VarErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {4}}, {{0}, {8}}, {{0}, {12}}, {{0}, {16}} });

  // TODO: update
	//for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
	//	for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
	//		for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
	//			//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
	//			BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
	//		}
	//	}
	//}
}

BOOST_AUTO_TEST_CASE(getNameVarErrorTensorOp)
{
	VarErrorTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "VarErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionCountErrorTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> source_error(batch_size, memory_size, source_layer_size);
	source_error.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{3, 3}, {0, 0}},
		{{4, 4}, {0, 0}} });
	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 3> sink_derivative(batch_size, memory_size, sink_layer_size);
	sink_derivative.setConstant(2);
	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setConstant(0);
	Eigen::Tensor<float, 3> sink_output(batch_size, memory_size, sink_layer_size);
	sink_output.setConstant(1);

	Eigen::DefaultDevice device;

	CountErrorTensorOp<float, Eigen::DefaultDevice> operation;
	operation(source_error.data(), source_input.data(), weights.data(), sink_output.data(), sink_error.data(), sink_derivative.data(), sink_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, source_time_step, sink_time_step, device);

	Eigen::Tensor<float, 3> expected(batch_size, memory_size, sink_layer_size);
	expected.setValues({ {{0}, {0}}, {{0}, {0}}, {{0}, {0}}, {{0}, {0}} });

	for (int batch_iter = 0; batch_iter < batch_size; ++batch_iter) {
		for (int memory_iter = 0; memory_iter < memory_size; ++memory_iter) {
			for (int layer_iter = 0; layer_iter < sink_layer_size; ++layer_iter) {
				//std::cout << "Batch iter: " << batch_iter << ", Memory Iter: " << memory_iter << ", Layer Iter: " << memory_iter << "= " << sink_error(batch_iter, memory_iter, layer_iter) << std::endl;
				BOOST_CHECK_CLOSE(sink_error(batch_iter, memory_iter, layer_iter), expected(batch_iter, memory_iter, layer_iter), 1e-4);
			}
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameCountErrorTensorOp)
{
	CountErrorTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "CountErrorTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionSumWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {1}},
    //{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 0}, {1, 0}},
    //{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	SumWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {-5}, {-4} });
  //expected.setValues({ {-4.75}, {-4.75} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionSumWeightGradTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setValues({ {{1}, {1}},
    {{2}, {1}},
    {{3}, {1}},
    //{{3}, {0}},
    {{4}, {0}} });
  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {1, 1}},
    {{2, 2}, {2, 2}},
    {{1, 0}, {1, 0}},
    //{{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}} });

  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 2> weight_error(source_layer_size, sink_layer_size);
  weight_error.setConstant(0);

  Eigen::DefaultDevice device;

  SumWeightGradTensorOp<double, Eigen::DefaultDevice> operation;
  operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, device);

  Eigen::Tensor<double, 2> expected(source_layer_size, sink_layer_size);
  expected.setValues({ {-5}, {-4} });
  //expected.setValues({ {-4.75}, {-4.75} });

  for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
    for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
      //std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
      BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameSumWeightGradTensorOp)
{
	SumWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "SumWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionProdWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	ProdWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {-8}, {-8} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameProdWeightGradTensorOp)
{
	ProdWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "ProdWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMaxWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	MaxWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {-4.75}, {-4.75} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionMaxWeightGradTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setValues({ {{1}, {1}},
    {{2}, {1}},
    {{3}, {0}},
    {{4}, {0}} });
  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {1, 1}},
    {{2, 2}, {2, 2}},
    {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}} });

  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 2> weight_error(source_layer_size, sink_layer_size);
  weight_error.setConstant(0);

  Eigen::DefaultDevice device;

  MaxWeightGradTensorOp<double, Eigen::DefaultDevice> operation;
  operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, device);

  Eigen::Tensor<double, 2> expected(source_layer_size, sink_layer_size);
  expected.setValues({ {-4.75}, {-4.75} });

  for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
    for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
      //std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
      BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMaxWeightGradTensorOp)
{
	MaxWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "MaxWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMinWeightGradTensorOp)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setValues({ {{1}, {1}},
    {{2}, {1}},
    {{3}, {0}},
    {{4}, {0}} });
  Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {1, 1}},
    {{2, 2}, {2, 2}},
    {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}} });
  Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}} });

  Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
  weight_error.setConstant(0);

  Eigen::DefaultDevice device;

  MinWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
  operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, device);

  Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
  expected.setValues({ {-4.75}, {-4.75} });

  for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
    for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
      //std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
      BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
    }
  }
}

BOOST_AUTO_TEST_CASE(operationfunctionMinWeightGradTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setValues({ {{1}, {1}},
    {{2}, {1}},
    {{3}, {0}},
    {{4}, {0}} });
  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {1, 1}},
    {{2, 2}, {2, 2}},
    {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}} });

  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 2> weight_error(source_layer_size, sink_layer_size);
  weight_error.setConstant(0);

  Eigen::DefaultDevice device;

  MinWeightGradTensorOp<double, Eigen::DefaultDevice> operation;
  operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, device);

  Eigen::Tensor<double, 2> expected(source_layer_size, sink_layer_size);
  expected.setValues({ {-4.75}, {-4.75} });

  for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
    for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
      //std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
      BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMinWeightGradTensorOp)
{
  MinWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

  BOOST_CHECK_EQUAL(operation.getName(), "MinWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionMeanWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	MeanWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {-2.375}, {-2.375} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionMeanWeightGradTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setValues({ {{1}, {1}},
    {{2}, {1}},
    {{3}, {0}},
    {{4}, {0}} });
  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {1, 1}},
    {{2, 2}, {2, 2}},
    {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}} });

  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 2> weight_error(source_layer_size, sink_layer_size);
  weight_error.setConstant(0);

  Eigen::DefaultDevice device;

  MeanWeightGradTensorOp<double, Eigen::DefaultDevice> operation;
  operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, device);

  Eigen::Tensor<double, 2> expected(source_layer_size, sink_layer_size);
  expected.setValues({ {-2.375}, {-2.375} });

  for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
    for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
      //std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
      BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameMeanWeightGradTensorOp)
{
	MeanWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "MeanWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionVarModWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	VarModWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {-4.75}, {-4.75} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
		}
	}
}

BOOST_AUTO_TEST_CASE(operationfunctionVarModWeightGradTensorOpDouble)
{
  const int batch_size = 4;
  const int memory_size = 2;
  const int source_layer_size = 2;
  const int sink_layer_size = 1;
  const int source_time_step = 0;
  const int sink_time_step = 1;

  Eigen::Tensor<double, 3> sink_error(batch_size, memory_size, sink_layer_size);
  sink_error.setValues({ {{1}, {1}},
    {{2}, {1}},
    {{3}, {0}},
    {{4}, {0}} });
  Eigen::Tensor<double, 3> source_output(batch_size, memory_size, source_layer_size);
  source_output.setValues({ {{1, 1}, {1, 1}},
    {{2, 2}, {2, 2}},
    {{1, 1}, {0, 0}},
    {{2, 2}, {0, 0}} });
  Eigen::Tensor<double, 3> source_input(batch_size, memory_size, source_layer_size);
  source_input.setValues({ {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}},
    {{2, 2}, {0, 0}},
    {{4, 4}, {0, 0}} });

  Eigen::Tensor<double, 2> weights(source_layer_size, sink_layer_size);
  weights.setConstant(1);
  Eigen::Tensor<double, 2> weight_error(source_layer_size, sink_layer_size);
  weight_error.setConstant(0);

  Eigen::DefaultDevice device;

  VarModWeightGradTensorOp<double, Eigen::DefaultDevice> operation;
  operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
    batch_size, memory_size, source_layer_size, sink_layer_size, device);

  Eigen::Tensor<double, 2> expected(source_layer_size, sink_layer_size);
  expected.setValues({ {-4.75}, {-4.75} });

  for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
    for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
      //std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
      BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
    }
  }
}

BOOST_AUTO_TEST_CASE(getNameVarModWeightGradTensorOp)
{
	VarModWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "VarModWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionVarWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	VarWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {-4.75}, {-4.75} });

  // TODO: update
	//for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
	//	for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
	//		//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
	//		BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
	//	}
	//}
}

BOOST_AUTO_TEST_CASE(getNameVarWeightGradTensorOp)
{
	VarWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "VarWeightGradTensorOp");
}

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

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

BOOST_AUTO_TEST_CASE(operationfunctionCountWeightGradTensorOp)
{
	const int batch_size = 4;
	const int memory_size = 2;
	const int source_layer_size = 2;
	const int sink_layer_size = 1;
	const int source_time_step = 0;
	const int sink_time_step = 1;

	Eigen::Tensor<float, 3> sink_error(batch_size, memory_size, sink_layer_size);
	sink_error.setValues({ {{1}, {1}},
		{{2}, {1}},
		{{3}, {0}},
		{{4}, {0}} });
	Eigen::Tensor<float, 3> source_output(batch_size, memory_size, source_layer_size);
	source_output.setValues({ {{1, 1}, {1, 1}},
		{{2, 2}, {2, 2}},
		{{1, 1}, {0, 0}},
		{{2, 2}, {0, 0}} });
	Eigen::Tensor<float, 3> source_input(batch_size, memory_size, source_layer_size);
	source_input.setValues({ {{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}},
		{{2, 2}, {0, 0}},
		{{4, 4}, {0, 0}} });

	Eigen::Tensor<float, 2> weights(source_layer_size, sink_layer_size);
	weights.setConstant(1);
	Eigen::Tensor<float, 2> weight_error(source_layer_size, sink_layer_size);
	weight_error.setConstant(0);

	Eigen::DefaultDevice device;

	CountWeightGradTensorOp<float, Eigen::DefaultDevice> operation;
	operation(sink_error.data(), source_output.data(), weights.data(), source_input.data(), weight_error.data(), source_layer_size,
		batch_size, memory_size, source_layer_size, sink_layer_size, device);

	Eigen::Tensor<float, 2> expected(source_layer_size, sink_layer_size);
	expected.setValues({ {0}, {0} });

	for (int source_iter = 0; source_iter < source_layer_size; ++source_iter) {
		for (int sink_iter = 0; sink_iter < sink_layer_size; ++sink_iter) {
			//std::cout << "[Weight Error] Source iter: " << source_iter << ", Sink Iter: " << sink_iter << " = " << weight_error(source_iter, sink_iter) << std::endl;
			BOOST_CHECK_CLOSE(weight_error(source_iter, sink_iter), expected(source_iter, sink_iter), 1e-4);
		}
	}
}

BOOST_AUTO_TEST_CASE(getNameCountWeightGradTensorOp)
{
	CountWeightGradTensorOp<float, Eigen::DefaultDevice> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "CountWeightGradTensorOp");
}

BOOST_AUTO_TEST_SUITE_END()


//=======================================================================
// Copyright (c)
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
#include "test_utils/sample_graph.hpp"
#include "test_utils/logger.hpp"

#include "paal/local_search/facility_location/facility_location.hpp"
#include "paal/local_search/custom_components.hpp"
#include "paal/utils/functors.hpp"

#include <boost/test/unit_test.hpp>

using namespace paal::local_search;

BOOST_AUTO_TEST_CASE(FacilityLocationTest) {

    typedef sample_graphs_metrics SGM;
    auto gm = SGM::get_graph_metric_small();
    std::vector<int> fcosts{ 7, 8 };
    auto cost = paal::utils::make_array_to_functor(fcosts);

    typedef paal::data_structures::voronoi<decltype(gm)> VorType;
    typedef paal::data_structures::facility_location_solution<decltype(cost),
                                                              VorType> Sol;
    typedef typename VorType::GeneratorsSet FSet;
    VorType voronoi(FSet{}, FSet{ SGM::A, SGM::B, SGM::C, SGM::D, SGM::E }, gm);
    Sol sol(std::move(voronoi), FSet{ SGM::A, SGM::B }, cost);
    default_remove_fl_components rem;
    default_add_fl_components add;
    default_swap_fl_components swap;
    paal::utils::always_true nop;
    // this search can and in one or 2 rounds depending on
    // implementation of unordered_set in facility_location_solution
    // if the first facility to add is A, then the search will be finished
    // in  one round. The 2 round are needed otherwise
    stop_condition_count_limit oneRoundSearch(2);

    ON_LOG(auto const &ch = sol.get_chosen_facilities());
    LOGLN("Solution before the first search");
    LOG_COPY_RANGE_DEL(ch, ",");
    LOGLN("");

    BOOST_CHECK(facility_location_local_search(
        sol, first_improving_strategy{}, nop,
        oneRoundSearch, rem, add, swap));

    LOGLN("Solution after the first search");
    LOG_COPY_RANGE_DEL(ch, ",");
    LOGLN("");
    BOOST_CHECK(!facility_location_first_improving(sol, rem, add, swap));
    //best improving strategy
    BOOST_CHECK(!facility_location_local_search(sol,
                best_improving_strategy{}, paal::utils::always_true{},
                paal::utils::always_false{}, rem, add, swap));
}


// Copyright 2015-2020 The ALMA Project Developers
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the License for the specific language governing
// permissions and limitations under the License.

/// @file
/// Test the functionality of the MSD_calculators.

#include <iostream>
#include <functional>
#include <boost/filesystem.hpp>
#include <boost/mpi.hpp>
#include "gtest/gtest.h"
#include <utilities.hpp>
#include <vasp_io.hpp>
#include <qpoint_grid.hpp>
#include <processes.hpp>
#include <bulk_hdf5.hpp>
#include <analytic1d.hpp>

TEST(analytic1D_MSD_case, bulk_test) {
    auto basedir = boost::filesystem::path(TEST_RESOURCE_DIR);
    auto si_dir = basedir / boost::filesystem::path("Si");
    auto hdf5_path = si_dir / boost::filesystem::path("Si_threeph.h5");
    boost::mpi::communicator world;
    auto my_id = world.rank();

    // obtain phonon data from HDF5 file
    auto hdf5_data = alma::load_bulk_hdf5(hdf5_path.string().c_str(), world);
    auto description = std::get<0>(hdf5_data);
    auto poscar = std::move(std::get<1>(hdf5_data));
    auto syms = std::move(std::get<2>(hdf5_data));
    auto grid = std::move(std::get<3>(hdf5_data));
    auto processes = std::move(std::get<4>(hdf5_data));

    double T = 300.0;

    Eigen::ArrayXXd w(alma::calc_w0_threeph(*grid, *processes, T, world));

    // create transport direction
    Eigen::Vector3d u100(1.0, 0.0, 0.0);

    // general variables
    int Nt;
    Eigen::VectorXd MSD;
    Eigen::VectorXd MSD_ref;
    Eigen::VectorXd MSD_ratio;

    if (my_id == 0) { // analytic1D code is not parallellised; avoid
                      // running duplicates.
        Nt = 7;

        alma::analytic1D::MSD_calculator_RealTime MSDcalc(
            poscar.get(), grid.get(), &w, T);
        MSDcalc.setDirection(u100);
        MSDcalc.setLogGrid(1e-12, 1e-6, Nt);
        MSDcalc.normaliseOutput(false);

        MSD = MSDcalc.getMSD();
        MSD_ref.resize(Nt);
        MSD_ref << 2.258270594e-18, 1.539701019e-16, 7.205645714e-15,
            1.351483594e-13, 1.753307494e-12, 1.811198571e-11, 1.81698549e-10;
        ;
        MSD_ratio = MSD.array() / MSD_ref.array();

        for (int nt = 0; nt < Nt; nt++) {
            EXPECT_TRUE(alma::almost_equal(MSD_ratio(nt), 1.0, 1e-4));
        }
    }
}

int main(int argc, char** argv) {
    boost::mpi::environment env;

    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}


//
//  AABB.hpp
//  PinkTopaz
//
//  Created by Andrew Fox on 3/13/16.
//
//

#ifndef AABB_hpp
#define AABB_hpp

#include <glm/glm.hpp>
#include <glm/gtx/component_wise.hpp>
#include <glm/gtx/string_cast.hpp>
#include <vector>
#include <sstream>
#include <array>
#include <boost/functional/hash.hpp>
#include "CerealGLM.hpp"
#include <spdlog/fmt/ostr.h>

// An axis-aligned bounding box.
template<typename TYPE>
struct _AABB
{
    // The point at the exact center of the bounding box.
    TYPE center;

    // The corners of the box are given given as the Cartesian Product of
    // {-a.x, -a.y, -a.z} and {+a.x, +a.y, +a.z} where a = center + extent.
    // This means that the length of the edge of the box along the X axis is
    // extent.x * 2, and ditto for the other two axii.
    TYPE extent;

    bool operator==(const _AABB<TYPE> &other) const
    {
        return (center == other.center) && (extent == other.extent);
    }

    bool operator!=(const _AABB<TYPE> &other) const
    {
        bool theSame = ((*this) == other);
        return !theSame;
    }

    bool operator<(const _AABB<TYPE> &right) const
    {
        return center.x < right.center.x; // This is arbitrary, but consistent.
    }

    inline TYPE mins() const
    {
        return center - extent;
    }

    inline TYPE maxs() const
    {
        return center + extent;
    }

    inline _AABB<TYPE> inset(TYPE inset) const
    {
        return _AABB<TYPE>{center, extent - inset};
    }

    auto octants() const
    {
        const glm::vec3 subExtent = extent * 0.50f;
        const std::array<_AABB<TYPE>, 8> r = {{
            { center + glm::vec3(-subExtent.x, -subExtent.y, -subExtent.z), subExtent},
            { center + glm::vec3(-subExtent.x, -subExtent.y, +subExtent.z), subExtent},
            { center + glm::vec3(-subExtent.x, +subExtent.y, -subExtent.z), subExtent},
            { center + glm::vec3(-subExtent.x, +subExtent.y, +subExtent.z), subExtent},
            { center + glm::vec3(+subExtent.x, -subExtent.y, -subExtent.z), subExtent},
            { center + glm::vec3(+subExtent.x, -subExtent.y, +subExtent.z), subExtent},
            { center + glm::vec3(+subExtent.x, +subExtent.y, -subExtent.z), subExtent},
            { center + glm::vec3(+subExtent.x, +subExtent.y, +subExtent.z), subExtent}
        }};
        return r;
    }

    inline _AABB<TYPE> intersect(const _AABB<TYPE> &thatBox) const
    {
        const TYPE thisMin = mins();
        const TYPE thatMin = thatBox.mins();
        const TYPE thisMax = maxs();
        const TYPE thatMax = thatBox.maxs();
        const TYPE min = TYPE(std::max(thisMin.x, thatMin.x),
                              std::max(thisMin.y, thatMin.y),
                              std::max(thisMin.z, thatMin.z));
        const TYPE max = TYPE(std::min(thisMax.x, thatMax.x),
                              std::min(thisMax.y, thatMax.y),
                              std::min(thisMax.z, thatMax.z));
        const TYPE center = (max + min) * 0.5f;
        const TYPE extent = (max - min) * 0.5f;
        return {center, extent};
    }

    inline _AABB<TYPE> unionBox(const _AABB<TYPE> &thatBox) const
    {
        const TYPE thisMin = mins();
        const TYPE thatMin = thatBox.mins();
        const TYPE thisMax = maxs();
        const TYPE thatMax = thatBox.maxs();
        const TYPE min = TYPE(std::min(thisMin.x, thatMin.x),
                              std::min(thisMin.y, thatMin.y),
                              std::min(thisMin.z, thatMin.z));
        const TYPE max = TYPE(std::max(thisMax.x, thatMax.x),
                              std::max(thisMax.y, thatMax.y),
                              std::max(thisMax.z, thatMax.z));
        const TYPE center = (max + min) * 0.5f;
        const TYPE extent = (max - min) * 0.5f;
        return {center, extent};
    }

    // Return the string representation of this bounding box.
    std::string to_string() const
    {
        std::ostringstream ss;
        ss << "{"
           << glm::to_string(mins())
           << " x "
           << glm::to_string(maxs())
           << "}";
        return ss.str();
    }

    // Permits logging with spdlog.
    template<typename OStream>
    friend OStream& operator<<(OStream &os, const _AABB<TYPE> &box)
    {
        os << "{"
           << glm::to_string(box.mins())
           << " x "
           << glm::to_string(box.maxs())
           << "}";
        return os;
    }

    // Permits serialization with cereal.
    template<typename Archive>
    void serialize(Archive &archive)
    {
        archive(CEREAL_NVP(center), CEREAL_NVP(extent));
    }
};

using AABB = _AABB<glm::vec3>;

namespace std {
    template <> struct hash<glm::vec3>
    {
        size_t operator()(const glm::vec3 &point) const
        {
            size_t seed = 0;
            boost::hash_combine(seed, point.x);
            boost::hash_combine(seed, point.y);
            boost::hash_combine(seed, point.z);
            return seed;
        }
    };
}

namespace std {
    template <> struct hash<AABB>
    {
        size_t operator()(const AABB &box) const
        {
            size_t seed = 0;
            std::hash<glm::vec3> vecHasher;
            boost::hash_combine(seed, vecHasher(box.center));
            boost::hash_combine(seed, vecHasher(box.extent));
            return seed;
        }
    };
}

template<typename PointType> inline bool
isPointInsideBox(const PointType &point,
                 const PointType &mins,
                 const PointType &maxs)
{
    return point.x >= mins.x && point.y >= mins.y && point.z >= mins.z &&
           point.x < maxs.x && point.y < maxs.y && point.z < maxs.z;
}

inline bool isPointInsideBox(const glm::vec3 &point, const AABB &box)
{
    return isPointInsideBox(point, box.mins(), box.maxs());
}

inline bool doBoxesIntersect(const AABB &a, const AABB &b)
{
    const glm::vec3 a_max = a.maxs();
    const glm::vec3 b_max = b.maxs();

    const glm::vec3 a_min = a.mins();
    const glm::vec3 b_min = b.mins();

    return (a_max.x >= b_min.x) &&
           (a_min.x <= b_max.x) &&
           (a_max.y >= b_min.y) &&
           (a_min.y <= b_max.y) &&
           (a_max.z >= b_min.z) &&
           (a_min.z <= b_max.z);
}

#endif /* AABB_hpp */


#pragma once
#include <libunittest/all.hpp>
#include <densitas/all.hpp>
#define ARMA_DONT_USE_CXX11
#include <armadillo>
using namespace unittest::assertions;

typedef arma::vec vector_t;

typedef arma::mat matrix_t;

inline
vector_t mkcol(std::vector<double> data)
{
    vector_t col(data.size());
    std::copy(data.begin(), data.end(), col.begin());
    return col;
}

inline
arma::rowvec mkrow(std::vector<double> data)
{
    arma::rowvec row(data.size());
    std::copy(data.begin(), data.end(), row.begin());
    return row;
}

struct mock_model {

    vector_t prediction;
    vector_t train_y;
    matrix_t train_X;

    mock_model()
        : prediction(), train_y(), train_X()
    {}

    std::unique_ptr<mock_model> clone() const
    {
        std::unique_ptr<mock_model> m(new mock_model);
        m->prediction = prediction;
        m->train_y = train_y;
        m->train_X = train_X;
        return std::move(m);
    }

    void train(matrix_t& X, vector_t& y)
    {
        train_y = y;
        train_X = X;
    }

    vector_t predict_proba(matrix_t&) const
    {
        return prediction;
    }

};

const int no = densitas::model_adapter::no<mock_model>();

const int yes = densitas::model_adapter::yes<mock_model>();

const double dno = static_cast<double>(no);

const double dyes = static_cast<double>(yes);

namespace densitas {
namespace matrix_adapter {

template<>
inline
std::size_t n_rows(const matrix_t& matrix)
{
    return matrix.n_rows;
}

template<>
inline
std::size_t n_columns(const matrix_t& matrix)
{
    return matrix.n_cols;
}

} // matrix_adapter

namespace vector_adapter {

template<>
inline
std::size_t n_elements(const vector_t& vector)
{
    return vector.n_elem;
}

} // vector_adapter
} // densitas

namespace unittest {
namespace assertions {

template<typename... Args>
void
assert_equal_containers(const matrix_t& expected,
                        const matrix_t& actual,
                        const Args&... message)
{
    assert_equal(expected.n_rows, actual.n_rows, "n_rows don't match! ", message...);
    assert_equal(expected.n_cols, actual.n_cols, "n_cols don't match! ", message...);
    for (std::size_t i=0; i<expected.n_rows; ++i) {
        if (!unittest::core::is_containers_equal(arma::rowvec(expected.row(i)), arma::rowvec(actual.row(i)))) {
            const std::string text = "matrices are not equal";
            unittest::fail(UNITTEST_FUNC, text, message...);
        }
    }
}

template<typename... Args>
void
assert_approx_equal_containers(const matrix_t& expected,
                               const matrix_t& actual,
                               const double eps,
                               const Args&... message)
{
    assert_equal(expected.n_rows, actual.n_rows, "n_rows don't match! ", message...);
    assert_equal(expected.n_cols, actual.n_cols, "n_cols don't match! ", message...);
    for (std::size_t i=0; i<expected.n_rows; ++i) {
        if (!unittest::core::is_containers_approx_equal(arma::rowvec(expected.row(i)), arma::rowvec(actual.row(i)), eps)) {
            const std::string text = "matrices are not approx. equal";
            unittest::fail(UNITTEST_FUNC, text, message...);
        }
    }
}

} // unittest
} // assertions