/**TODO:  Add copyright*/

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

#include <iostream>

using namespace SmartPeak;
using namespace std;

BOOST_AUTO_TEST_SUITE(activationFunction)

/**
  ReLUOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorReluOp)
{
  ReLUOp<double>* ptrReLU = nullptr;
  ReLUOp<double>* nullPointerReLU = nullptr;
  BOOST_CHECK_EQUAL(ptrReLU, nullPointerReLU);
}

BOOST_AUTO_TEST_CASE(destructorReluOp)
{
  ReLUOp<double>* ptrReLU = nullptr;
	ptrReLU = new ReLUOp<double>();
  delete ptrReLU;
}

BOOST_AUTO_TEST_CASE(gettersAndSettersReluOp)
{
  // Test defaults
  ReLUOp<double> operation_defaults;
  BOOST_CHECK_CLOSE(operation_defaults.getEps(), 1e-6, 1e-6);
  BOOST_CHECK_CLOSE(operation_defaults.getMin(), -1e9, 1e-6);
  BOOST_CHECK_CLOSE(operation_defaults.getMax(), 1e9, 1e-6);

  // Test setters
  operation_defaults.setEps(10);
  operation_defaults.setMin(20);
  operation_defaults.setMax(30);
  BOOST_CHECK_CLOSE(operation_defaults.getEps(), 10, 1e-6);
  BOOST_CHECK_CLOSE(operation_defaults.getMin(), 20, 1e-6);
  BOOST_CHECK_CLOSE(operation_defaults.getMax(), 30, 1e-6);

  // Test constructor
  ReLUOp<double> operation(10, 20, 30);
  BOOST_CHECK_CLOSE(operation.getEps(), 10, 1e-6);
  BOOST_CHECK_CLOSE(operation.getMin(), 20, 1e-6);
  BOOST_CHECK_CLOSE(operation.getMax(), 30, 1e-6);
}

BOOST_AUTO_TEST_CASE(getNameReLUOp)
{
	ReLUOp<double> operation;

	BOOST_CHECK_EQUAL(operation.getName(), "ReLUOp");
}
BOOST_AUTO_TEST_CASE(copyReluOp)
{
  std::shared_ptr<ActivationOp<double>> relu_ptr_1 = std::make_shared<ReLUOp<double>>();
  std::shared_ptr<ActivationOp<double>> relu_ptr_2;
  relu_ptr_2 = std::shared_ptr<ActivationOp<double>>(relu_ptr_1);
  BOOST_CHECK_EQUAL(relu_ptr_1.get(), relu_ptr_2.get());
  relu_ptr_2 = std::shared_ptr<ActivationOp<double>>(relu_ptr_1.get()->copy());
  BOOST_CHECK_NE(relu_ptr_1.get(), relu_ptr_2.get());
}

/**
  ReLUGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorReluGradOp)
{
  ReLUGradOp<double>* ptrReLUGrad = nullptr;
  ReLUGradOp<double>* nullPointerReLUGrad = nullptr;
  BOOST_CHECK_EQUAL(ptrReLUGrad, nullPointerReLUGrad);
}

BOOST_AUTO_TEST_CASE(destructorReluGradOp)
{
  ReLUGradOp<double>* ptrReLUGrad = nullptr;
	ptrReLUGrad = new ReLUGradOp<double>();
  delete ptrReLUGrad;
}

BOOST_AUTO_TEST_CASE(getNameReLUGradOp)
{
	ReLUGradOp<double> operation;

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

/**
  ELUOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorEluOp)
{
  ELUOp<double>* ptrELU = nullptr;
  ELUOp<double>* nullPointerELU = nullptr;
  BOOST_CHECK_EQUAL(ptrELU, nullPointerELU);
}

BOOST_AUTO_TEST_CASE(destructorEluOp)
{
  ELUOp<double>* ptrELU = nullptr;
	ptrELU = new ELUOp<double>();
  delete ptrELU;
}

BOOST_AUTO_TEST_CASE(gettersAndSettersEluOp)
{
  ELUOp<double> operation;
  operation.setAlpha(1.0);

  BOOST_CHECK_EQUAL(operation.getAlpha(), 1.0);
}

BOOST_AUTO_TEST_CASE(getNameELUOp)
{
	ELUOp<double> operation;

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

/**
  ELUGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorEluGradOp)
{
  ELUGradOp<double>* ptrELU = nullptr;
  ELUGradOp<double>* nullPointerELU = nullptr;
  BOOST_CHECK_EQUAL(ptrELU, nullPointerELU);
}

BOOST_AUTO_TEST_CASE(destructorEluGradOp)
{
  ELUGradOp<double>* ptrELU = nullptr;
	ptrELU = new ELUGradOp<double>();
  delete ptrELU;
}

BOOST_AUTO_TEST_CASE(gettersAndSettersEluGradOp)
{
  ELUGradOp<double> operation;
  operation.setAlpha(1.0);

  BOOST_CHECK_EQUAL(operation.getAlpha(), 1.0);
}

BOOST_AUTO_TEST_CASE(getNameELUGradOp)
{
	ELUGradOp<double> operation;

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

/**
  SigmoidOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorSigmoidOp)
{
  SigmoidOp<double>* ptrSigmoid = nullptr;
  SigmoidOp<double>* nullPointerSigmoid = nullptr;
  BOOST_CHECK_EQUAL(ptrSigmoid, nullPointerSigmoid);
}

BOOST_AUTO_TEST_CASE(destructorSigmoidOp)
{
  SigmoidOp<double>* ptrSigmoid = nullptr;
	ptrSigmoid = new SigmoidOp<double>();
  delete ptrSigmoid;
}

BOOST_AUTO_TEST_CASE(getNameSigmoidOp)
{
	SigmoidOp<double> operation;

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

/**
  SigmoidGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorSigmoidGradOp)
{
  SigmoidGradOp<double>* ptrSigmoidGrad = nullptr;
  SigmoidGradOp<double>* nullPointerSigmoidGrad = nullptr;
  BOOST_CHECK_EQUAL(ptrSigmoidGrad, nullPointerSigmoidGrad);
}

BOOST_AUTO_TEST_CASE(destructorSigmoidGradOp)
{
  SigmoidGradOp<double>* ptrSigmoidGrad = nullptr;
	ptrSigmoidGrad = new SigmoidGradOp<double>();
  delete ptrSigmoidGrad;
}

BOOST_AUTO_TEST_CASE(getNameSigmoidGradOp)
{
	SigmoidGradOp<double> operation;

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

/**
  TanHOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorTanHOp)
{
  TanHOp<double>* ptrTanH = nullptr;
  TanHOp<double>* nullPointerTanH = nullptr;
  BOOST_CHECK_EQUAL(ptrTanH, nullPointerTanH);
}

BOOST_AUTO_TEST_CASE(destructorTanHOp)
{
  TanHOp<double>* ptrTanH = nullptr;
	ptrTanH = new TanHOp<double>();
  delete ptrTanH;
}

BOOST_AUTO_TEST_CASE(getNameTanHOp)
{
	TanHOp<double> operation;

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

/**
  TanHGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorTanHGradOp)
{
  TanHGradOp<double>* ptrTanHGrad = nullptr;
  TanHGradOp<double>* nullPointerTanHGrad = nullptr;
  BOOST_CHECK_EQUAL(ptrTanHGrad, nullPointerTanHGrad);
}

BOOST_AUTO_TEST_CASE(destructorTanHGradOp)
{
  TanHGradOp<double>* ptrTanHGrad = nullptr;
	ptrTanHGrad = new TanHGradOp<double>();
  delete ptrTanHGrad;
}

BOOST_AUTO_TEST_CASE(getNameTanHGradOp)
{
	TanHGradOp<double> operation;

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

/**
  ReTanHOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorReTanHOp)
{
  ReTanHOp<double>* ptrReTanH = nullptr;
  ReTanHOp<double>* nullPointerReTanH = nullptr;
  BOOST_CHECK_EQUAL(ptrReTanH, nullPointerReTanH);
}

BOOST_AUTO_TEST_CASE(destructorReTanHOp)
{
  ReTanHOp<double>* ptrReTanH = nullptr;
	ptrReTanH = new ReTanHOp<double>();
  delete ptrReTanH;
}

BOOST_AUTO_TEST_CASE(getNameReTanHOp)
{
	ReTanHOp<double> operation;

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

/**
  ReTanHGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorReTanHGradOp)
{
  ReTanHGradOp<double>* ptrReTanHGrad = nullptr;
  ReTanHGradOp<double>* nullPointerReTanHGrad = nullptr;
  BOOST_CHECK_EQUAL(ptrReTanHGrad, nullPointerReTanHGrad);
}

BOOST_AUTO_TEST_CASE(destructorReTanHGradOp)
{
  ReTanHGradOp<double>* ptrReTanHGrad = nullptr;
	ptrReTanHGrad = new ReTanHGradOp<double>();
  delete ptrReTanHGrad;
}

BOOST_AUTO_TEST_CASE(getNameReTanHGradOp)
{
	ReTanHGradOp<double> operation;

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

/**
LinearOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLinearOp)
{
	LinearOp<double>* ptrLinear = nullptr;
	LinearOp<double>* nullPointerLinear = nullptr;
	BOOST_CHECK_EQUAL(ptrLinear, nullPointerLinear);
}

BOOST_AUTO_TEST_CASE(destructorLinearOp)
{
	LinearOp<double>* ptrLinear = nullptr;
	ptrLinear = new LinearOp<double>();
	delete ptrLinear;
}

BOOST_AUTO_TEST_CASE(getNameLinearOp)
{
	LinearOp<double> operation;

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

/**
LinearGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLinearGradOp)
{
	LinearGradOp<double>* ptrLinearGrad = nullptr;
	LinearGradOp<double>* nullPointerLinearGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrLinearGrad, nullPointerLinearGrad);
}

BOOST_AUTO_TEST_CASE(destructorLinearGradOp)
{
	LinearGradOp<double>* ptrLinearGrad = nullptr;
	ptrLinearGrad = new LinearGradOp<double>();
	delete ptrLinearGrad;
}

BOOST_AUTO_TEST_CASE(getNameLinearGradOp)
{
	LinearGradOp<double> operation;

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

/**
InverseOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorInverseOp)
{
	InverseOp<double>* ptrInverse = nullptr;
	InverseOp<double>* nullPointerInverse = nullptr;
	BOOST_CHECK_EQUAL(ptrInverse, nullPointerInverse);
}

BOOST_AUTO_TEST_CASE(destructorInverseOp)
{
	InverseOp<double>* ptrInverse = nullptr;
	ptrInverse = new InverseOp<double>();
	delete ptrInverse;
}

BOOST_AUTO_TEST_CASE(getNameInverseOp)
{
	InverseOp<double> operation;

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

/**
InverseGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorInverseGradOp)
{
	InverseGradOp<double>* ptrInverseGrad = nullptr;
	InverseGradOp<double>* nullPointerInverseGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrInverseGrad, nullPointerInverseGrad);
}

BOOST_AUTO_TEST_CASE(destructorInverseGradOp)
{
	InverseGradOp<double>* ptrInverseGrad = nullptr;
	ptrInverseGrad = new InverseGradOp<double>();
	delete ptrInverseGrad;
}

BOOST_AUTO_TEST_CASE(getNameInverseGradOp)
{
	InverseGradOp<double> operation;

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

/**
ExponentialOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorExponentialOp)
{
	ExponentialOp<double>* ptrExponential = nullptr;
	ExponentialOp<double>* nullPointerExponential = nullptr;
	BOOST_CHECK_EQUAL(ptrExponential, nullPointerExponential);
}

BOOST_AUTO_TEST_CASE(destructorExponentialOp)
{
	ExponentialOp<double>* ptrExponential = nullptr;
	ptrExponential = new ExponentialOp<double>();
	delete ptrExponential;
}

BOOST_AUTO_TEST_CASE(getNameExponentialOp)
{
	ExponentialOp<double> operation;

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

/**
ExponentialGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorExponentialGradOp)
{
	ExponentialGradOp<double>* ptrExponentialGrad = nullptr;
	ExponentialGradOp<double>* nullPointerExponentialGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrExponentialGrad, nullPointerExponentialGrad);
}

BOOST_AUTO_TEST_CASE(destructorExponentialGradOp)
{
	ExponentialGradOp<double>* ptrExponentialGrad = nullptr;
	ptrExponentialGrad = new ExponentialGradOp<double>();
	delete ptrExponentialGrad;
}

BOOST_AUTO_TEST_CASE(getNameExponentialGradOp)
{
	ExponentialGradOp<double> operation;

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

/**
LogOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLogOp)
{
	LogOp<double>* ptrLog = nullptr;
	LogOp<double>* nullPointerLog = nullptr;
	BOOST_CHECK_EQUAL(ptrLog, nullPointerLog);
}

BOOST_AUTO_TEST_CASE(destructorLogOp)
{
	LogOp<double>* ptrLog = nullptr;
	ptrLog = new LogOp<double>();
	delete ptrLog;
}

BOOST_AUTO_TEST_CASE(getNameLogOp)
{
	LogOp<double> operation;

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

/**
LogGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLogGradOp)
{
	LogGradOp<double>* ptrLogGrad = nullptr;
	LogGradOp<double>* nullPointerLogGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrLogGrad, nullPointerLogGrad);
}

BOOST_AUTO_TEST_CASE(destructorLogGradOp)
{
	LogGradOp<double>* ptrLogGrad = nullptr;
	ptrLogGrad = new LogGradOp<double>();
	delete ptrLogGrad;
}

BOOST_AUTO_TEST_CASE(getNameLogGradOp)
{
	LogGradOp<double> operation;

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

/**
	PowOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPowOp)
{
	PowOp<double>* ptrPow = nullptr;
	PowOp<double>* nullPointerPow = nullptr;
	BOOST_CHECK_EQUAL(ptrPow, nullPointerPow);
}

BOOST_AUTO_TEST_CASE(destructorPowOp)
{
	PowOp<double>* ptrPow = nullptr;
	ptrPow = new PowOp<double>(2);
	delete ptrPow;
}

BOOST_AUTO_TEST_CASE(getNamePowOp)
{
	PowOp<double> operation(0.5);

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

/**
	PowGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorPowGradOp)
{
	PowGradOp<double>* ptrPowGrad = nullptr;
	PowGradOp<double>* nullPointerPowGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrPowGrad, nullPointerPowGrad);
}

BOOST_AUTO_TEST_CASE(destructorPowGradOp)
{
	PowGradOp<double>* ptrPowGrad = nullptr;
	ptrPowGrad = new PowGradOp<double>(0.5);
	delete ptrPowGrad;
}

BOOST_AUTO_TEST_CASE(getNamePowGradOp)
{
	PowGradOp<double> operation(0.5);

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

/**
	LeakyReLUOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLeakyReLUOp)
{
	LeakyReLUOp<double>* ptrLeakyReLU = nullptr;
	LeakyReLUOp<double>* nullPointerLeakyReLU = nullptr;
	BOOST_CHECK_EQUAL(ptrLeakyReLU, nullPointerLeakyReLU);
}

BOOST_AUTO_TEST_CASE(destructorLeakyReLUOp)
{
	LeakyReLUOp<double>* ptrLeakyReLU = nullptr;
	ptrLeakyReLU = new LeakyReLUOp<double>();
	delete ptrLeakyReLU;
}

BOOST_AUTO_TEST_CASE(gettersAndSettersLeakyReLUOp)
{
	LeakyReLUOp<double> operation;
	operation.setAlpha(1.0);

	BOOST_CHECK_EQUAL(operation.getAlpha(), 1.0);
}

BOOST_AUTO_TEST_CASE(getNameLeakyReLUOp)
{
	LeakyReLUOp<double> operation;

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

/**
	LeakyReLUGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorLeakyReLUGradOp)
{
	LeakyReLUGradOp<double>* ptrLeakyReLU = nullptr;
	LeakyReLUGradOp<double>* nullPointerLeakyReLU = nullptr;
	BOOST_CHECK_EQUAL(ptrLeakyReLU, nullPointerLeakyReLU);
}

BOOST_AUTO_TEST_CASE(destructorLeakyReLUGradOp)
{
	LeakyReLUGradOp<double>* ptrLeakyReLU = nullptr;
	ptrLeakyReLU = new LeakyReLUGradOp<double>();
	delete ptrLeakyReLU;
}

BOOST_AUTO_TEST_CASE(gettersAndSettersLeakyReLUGradOp)
{
	LeakyReLUGradOp<double> operation;
	operation.setAlpha(1.0);

	BOOST_CHECK_EQUAL(operation.getAlpha(), 1.0);
}

BOOST_AUTO_TEST_CASE(getNameLeakyReLUGradOp)
{
	LeakyReLUGradOp<double> operation;

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

/**
SinOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorSinOp)
{
	SinOp<double>* ptrSin = nullptr;
	SinOp<double>* nullPointerSin = nullptr;
	BOOST_CHECK_EQUAL(ptrSin, nullPointerSin);
}

BOOST_AUTO_TEST_CASE(destructorSinOp)
{
	SinOp<double>* ptrSin = nullptr;
	ptrSin = new SinOp<double>();
	delete ptrSin;
}

BOOST_AUTO_TEST_CASE(getNameSinOp)
{
	SinOp<double> operation;

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

/**
SinGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorSinGradOp)
{
	SinGradOp<double>* ptrSinGrad = nullptr;
	SinGradOp<double>* nullPointerSinGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrSinGrad, nullPointerSinGrad);
}

BOOST_AUTO_TEST_CASE(destructorSinGradOp)
{
	SinGradOp<double>* ptrSinGrad = nullptr;
	ptrSinGrad = new SinGradOp<double>();
	delete ptrSinGrad;
}

BOOST_AUTO_TEST_CASE(getNameSinGradOp)
{
	SinGradOp<double> operation;

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

/**
CosOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCosOp)
{
	CosOp<double>* ptrCos = nullptr;
	CosOp<double>* nullPointerCos = nullptr;
	BOOST_CHECK_EQUAL(ptrCos, nullPointerCos);
}

BOOST_AUTO_TEST_CASE(destructorCosOp)
{
	CosOp<double>* ptrCos = nullptr;
	ptrCos = new CosOp<double>();
	delete ptrCos;
}

BOOST_AUTO_TEST_CASE(getNameCosOp)
{
	CosOp<double> operation;

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

/**
CosGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorCosGradOp)
{
	CosGradOp<double>* ptrCosGrad = nullptr;
	CosGradOp<double>* nullPointerCosGrad = nullptr;
	BOOST_CHECK_EQUAL(ptrCosGrad, nullPointerCosGrad);
}

BOOST_AUTO_TEST_CASE(destructorCosGradOp)
{
	CosGradOp<double>* ptrCosGrad = nullptr;
	ptrCosGrad = new CosGradOp<double>();
	delete ptrCosGrad;
}

BOOST_AUTO_TEST_CASE(getNameCosGradOp)
{
	CosGradOp<double> operation;

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

/**
BatchNormOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorBatchNormOp)
{
  BatchNormOp<double>* ptrBatchNorm = nullptr;
  BatchNormOp<double>* nullPointerBatchNorm = nullptr;
  BOOST_CHECK_EQUAL(ptrBatchNorm, nullPointerBatchNorm);
}

BOOST_AUTO_TEST_CASE(destructorBatchNormOp)
{
  BatchNormOp<double>* ptrBatchNorm = nullptr;
  ptrBatchNorm = new BatchNormOp<double>();
  delete ptrBatchNorm;
}

BOOST_AUTO_TEST_CASE(getNameBatchNormOp)
{
  BatchNormOp<double> operation;

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

/**
BatchNormGradOp Tests
*/
BOOST_AUTO_TEST_CASE(constructorBatchNormGradOp)
{
  BatchNormGradOp<double>* ptrBatchNormGrad = nullptr;
  BatchNormGradOp<double>* nullPointerBatchNormGrad = nullptr;
  BOOST_CHECK_EQUAL(ptrBatchNormGrad, nullPointerBatchNormGrad);
}

BOOST_AUTO_TEST_CASE(destructorBatchNormGradOp)
{
  BatchNormGradOp<double>* ptrBatchNormGrad = nullptr;
  ptrBatchNormGrad = new BatchNormGradOp<double>();
  delete ptrBatchNormGrad;
}

BOOST_AUTO_TEST_CASE(getNameBatchNormGradOp)
{
  BatchNormGradOp<double> operation;

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

BOOST_AUTO_TEST_SUITE_END()


#include "../sdp_solve.hxx"
#include "../set_stream_precision.hxx"
#include "../write_distmatrix.hxx"

#include <boost/filesystem/fstream.hpp>

#include <iomanip>

namespace
{
  void write_psd_block(const boost::filesystem::path &outfile,
                       const El::DistMatrix<El::BigFloat> &block)
  {
    boost::filesystem::ofstream stream;
    if(block.DistRank() == block.Root())
      {
        stream.open(outfile);
      }
    El::Print(block,
              std::to_string(block.Height()) + " "
                + std::to_string(block.Width()),
              "\n", stream);
    if(block.DistRank() == block.Root())
      {
        stream << "\n";
        if(!stream.good())
          {
            throw std::runtime_error("Error when writing to: "
                                     + outfile.string());
          }
      }
  }
}

void save_solution(const SDP_Solver &solver,
                   const SDP_Solver_Terminate_Reason terminate_reason,
                   const std::pair<std::string, Timer> &timer_pair,
                   const boost::filesystem::path &out_directory,
                   const Write_Solution &write_solution,
                   const std::vector<size_t> &block_indices,
                   const Verbosity &verbosity)
{
  // Internally, El::Print() sync's everything to the root core and
  // outputs it from there.  So do not actually open the file on
  // anything but the root node.

  boost::filesystem::ofstream out_stream;
  if(El::mpi::Rank() == 0)
    {
      if(verbosity >= Verbosity::regular)
        {
          std::cout << "Saving solution to      : " << out_directory << '\n';
        }
      const boost::filesystem::path output_path(out_directory / "out.txt");
      out_stream.open(output_path);
      set_stream_precision(out_stream);
      out_stream << "terminateReason = \"" << terminate_reason << "\";\n"
                 << "primalObjective = " << solver.primal_objective << ";\n"
                 << "dualObjective   = " << solver.dual_objective << ";\n"
                 << "dualityGap      = " << solver.duality_gap << ";\n"
                 << "primalError     = " << solver.primal_error() << ";\n"
                 << "dualError       = " << solver.dual_error << ";\n"
                 << std::setw(16) << std::left << timer_pair.first << "= "
                 << timer_pair.second.elapsed_seconds() << ";\n";
      if(!out_stream.good())
        {
          throw std::runtime_error("Error when writing to: "
                                   + output_path.string());
        }
    }
  // y is duplicated among cores, so only need to print out copy on
  // the root node.
  if(write_solution.vector_y && !solver.y.blocks.empty())
    {
      const boost::filesystem::path y_path(out_directory / "y.txt");
      boost::filesystem::ofstream y_stream;
      if(El::mpi::Rank() == 0)
        {
          y_stream.open(y_path);
        }
      El::Print(solver.y.blocks.at(0),
                std::to_string(solver.y.blocks.at(0).Height()) + " "
                  + std::to_string(solver.y.blocks.at(0).Width()),
                "\n", y_stream);
      if(El::mpi::Rank() == 0)
        {
          y_stream << "\n";
          if(!y_stream.good())
            {
              throw std::runtime_error("Error when writing to: "
                                       + y_path.string());
            }
        }
    }

  for(size_t block = 0; block != solver.x.blocks.size(); ++block)
    {
      size_t block_index(block_indices.at(block));
      if(write_solution.vector_x)
        {
          write_distmatrix(solver.x.blocks.at(block),
                           out_directory
                             / ("x_" + std::to_string(block_index) + ".txt"));
        }
      for(size_t psd_block(0); psd_block < 2; ++psd_block)
        {
          std::string suffix(std::to_string(2 * block_index + psd_block)
                             + ".txt");

          if(write_solution.matrix_X
             && solver.X.blocks.at(2 * block + psd_block).Height() != 0)
            {
              write_psd_block(out_directory / ("X_matrix_" + suffix),
                              solver.X.blocks.at(2 * block + psd_block));
            }
          if(write_solution.matrix_Y
             && solver.Y.blocks.at(2 * block + psd_block).Height() != 0)
            {
              write_psd_block(out_directory / ("Y_matrix_" + suffix),
                              solver.Y.blocks.at(2 * block + psd_block));
            }
        }
    }
}


//
// Created by wei on 2/13/18.
//
#include "common/Stream.h"
#include "common/Serializer.h"
#include "common/BinaryFileStream.h"
#include "pcg_solver/solver_configs.h"
#include "pcg_solver/block6x6_pcg_weber.h"
#include "pcg_solver/BinBlockCSR.h"
#include "pcg_solver/BlockPCG6x6.h"
#include "common/sanity_check.h"

#include <boost/filesystem.hpp>

//The check data path for pcg solver
#if defined(_WIN32)
const std::string pcg_data_path = "C:/Users/wei/Documents/Visual Studio 2015/Projects/surfelwarp/data/pcg_test/";
#else
const std::string pcg_data_path = "/home/wei/Documents/programs/surfelwarp/data/pcg_test";
#endif

void surfelwarp::loadCheckData(
        std::vector<float> &A_data,
        std::vector<int> &A_rowptr,
        std::vector<int> &A_colptr,
        std::vector<float> &b,
        std::vector<float> &diag_blks
) {
	std::string pcg_test_file = "pcg_test.dat";

    //Build the path
    using path = boost::filesystem::path;
    path data_dir(pcg_data_path);
	path pcg_test_path = data_dir / pcg_test_file;
	BinaryFileStream input_fstream(pcg_test_path.string().c_str(), BinaryFileStream::Mode::ReadOnly);
	input_fstream.SerializeRead(&A_data);
	input_fstream.SerializeRead(&A_rowptr);
	input_fstream.SerializeRead(&A_colptr);
	input_fstream.SerializeRead(&b);
	input_fstream.SerializeRead(&diag_blks);
}

void surfelwarp::loadCheckData(
        std::vector<float> &x,
        std::vector<float> &inv_diag_blks
) {
    //The name of loaded values
    std::string result_name = "pcg_result.dat";

    //Build the path
    using path = boost::filesystem::path;
    path data_dir(pcg_data_path);

    //Load them
    path pcg_result_path = data_dir / result_name;
	BinaryFileStream input_fstream(pcg_result_path.string().c_str(), BinaryFileStream::Mode::ReadOnly);
	input_fstream.SerializeRead(&x);
	input_fstream.SerializeRead(&inv_diag_blks);
}

void surfelwarp::check6x6DiagBlocksInverse(
        const std::vector<float> &diag_blks,
        const std::vector<float> &inv_diag_blks
) {
    //Upload the input to device
    DeviceArray<float> d_diag_blks, d_diag_inversed;
    d_diag_blks.upload(diag_blks);
    d_diag_inversed.create(d_diag_blks.size());
    const auto num_matrix = diag_blks.size() / 36;

    //Call the kernel
    block6x6_diag_inverse(d_diag_blks, d_diag_inversed, num_matrix);

    //Download and check
    std::vector<float> h_diag_inversed;
    d_diag_inversed.download(h_diag_inversed);

    //The checking code
    float max_relative_err = 0.0f;
    double avg_matrix_value = 0.0;
    for (auto i = 0; i < h_diag_inversed.size(); i++) {
        float h_diag_value = h_diag_inversed[i];
        float check_diag_value = inv_diag_blks[i];
        avg_matrix_value += h_diag_value;
        if(std::abs(check_diag_value) > 2e-3) {
            if (std::abs((check_diag_value - h_diag_value) / h_diag_value) > max_relative_err) {
                max_relative_err = std::abs((check_diag_value - h_diag_value) / h_diag_value);
	            LOG(INFO) << "The host value and checked value are " << h_diag_value << " and " << check_diag_value;
            }
        }
    }
    std::cout << "The maximun relative error is " << max_relative_err << std::endl;
    std::cout << "The average matrix entry value is " << (avg_matrix_value / h_diag_inversed.size()) << std::endl;
}

void surfelwarp::block6x6BuildTripletVector(
        const std::vector<float> &A_data,
        const std::vector<int> &A_rowptr,
        const std::vector<int> &A_colptr,
        const int matrix_size,
        std::vector<Eigen::Triplet<float>> &tripletVec
) {
	//Clear the output vector
    tripletVec.clear();

	//Loop over the bins
	int num_bins = divUp(matrix_size, bin_size);
	for (auto bin_idx = 0; bin_idx < num_bins; bin_idx++) {
		int first_row = bin_idx * bin_size;
		int bin_data_offset = A_rowptr[first_row];
		int bin_colptr_offset = bin_data_offset / 6;
		//Loop over the row in this bin
		for (auto j = 0; j < bin_size; j++) {
			int row_idx = first_row + j;
			int row_data_offset = bin_data_offset + j;
			int row_colptr_offset = bin_colptr_offset + j;
			int row_data_end = A_rowptr[row_idx + bin_size];
			while (row_data_offset < row_data_end) {
				for (auto k = 0; k < 6; k++) {
					float data = A_data[row_data_offset];
					int column_idx = A_colptr[row_colptr_offset] + k;
					if(column_idx >= 0 && std::abs(data) > 0.0f)
						tripletVec.push_back(Eigen::Triplet<float>(row_idx, column_idx, data));
					row_data_offset += bin_size;
				}
				row_colptr_offset += bin_size;
			}
		}
	}
}

void surfelwarp::hostEigenSpMV(
	const std::vector<float>& A_data,
	const std::vector<int>& A_rowptr,
	const std::vector<int>& A_colptr,
	const int matrix_size,
	const std::vector<float>& x,
	std::vector<float>& spmv
) {
	//Transfer to Eigen Vector
	Eigen::VectorXf eigen_x;
	eigen_x.resize(x.size());
	for(auto i = 0; i < x.size(); i++)
	{
		eigen_x(i) = x[i];
	}

	//Perform Sparse MV
	hostEigenSpMV(A_data, A_rowptr, A_colptr, matrix_size, eigen_x, spmv);
}

void surfelwarp::hostEigenSpMV(
	const std::vector<float>& A_data,
	const std::vector<int>& A_rowptr,
	const std::vector<int>& A_colptr,
	const int matrix_size,
	const Eigen::VectorXf & x,
	std::vector<float>& spmv
) {
	//Build the triplet vector
	std::vector<Eigen::Triplet<float>> tripletVec;
	block6x6BuildTripletVector(A_data, A_rowptr, A_colptr, matrix_size, tripletVec);

	//Build the sparse matrix in Eigen
	Eigen::SparseMatrix<float> matrix;
	matrix.resize(matrix_size, matrix_size);
	matrix.setFromTriplets(tripletVec.begin(), tripletVec.end());

	//Do product and store the result
	Eigen::VectorXf product = matrix * x;
	spmv.resize(x.size());
	for (auto i = 0; i < x.size(); i++) {
		spmv[i] = product(i);
	}
}

void surfelwarp::checkBlock6x6PCGSolver(
		const std::vector<float> &diag_blks,
		const std::vector<float> &A_data,
		const std::vector<int> &A_colptr,
		const std::vector<int> &A_rowptr,
		const std::vector<float> &b,
		std::vector<float> &x
) {
	const auto matrix_size = b.size();
	//Prepare the data for device code
	DeviceArray<float> diag_blks_dev, A_data_dev, b_dev, x_dev;
	DeviceArray<int> A_colptr_dev, A_rowptr_dev;
	diag_blks_dev.upload(diag_blks);
	A_data_dev.upload(A_data);
	A_rowptr_dev.upload(A_rowptr);
	A_colptr_dev.upload(A_colptr);
	b_dev.upload(b);
	x_dev.create(matrix_size);

	//Prepare the aux storage
	DeviceArray<float> inv_diag_dev, p, q, r, s, t;
	inv_diag_dev.create(diag_blks_dev.size());
	p.create(matrix_size);
	q.create(matrix_size);
	r.create(matrix_size);
	s.create(matrix_size);
	t.create(matrix_size);

	//Invoke the solver
	DeviceArray<float> valid_x;
	block6x6_pcg_weber(
			diag_blks_dev,
			A_data_dev,
			A_colptr_dev,
			A_rowptr_dev,
			b_dev,
			x_dev,
			inv_diag_dev,
			p, q, r, s, t,
			valid_x
	);

	//Solve it with class version
#if defined(CHECK_CLASS_PCG6x6)
	BlockPCG6x6 solver;
	solver.AllocateBuffer(matrix_size);
	solver.SetSolverInput(diag_blks_dev, A_data_dev, A_colptr_dev, A_rowptr_dev, b_dev);
	//valid_x = solver.Solve();
	valid_x = solver.SolveTextured();
#endif

	//Check the solved x
	assert(valid_x.size() == x.size());
	valid_x.download(x);

	std::vector<float> spmv;
	spmv.resize(x.size());
	for(auto row = 0; row < x.size(); row++) {
		spmv[row] = BinBlockCSR<6>::SparseMV(A_data.data(), A_colptr.data(), A_rowptr.data(), x.data(), row);
	}
}


// Copyright (c) Jeremy Siek 2001
//
// Permission to use, copy, modify, distribute and sell this software
// and its documentation for any purpose is hereby granted without fee,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and this permission notice appear
// in supporting documentation.  Silicon Graphics makes no
// representations about the suitability of this software for any
// purpose.  It is provided "as is" without express or implied warranty.

// NOTE: this final is generated by libs/graph/doc/biconnected_components.w

#ifndef BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP
#define BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP

#include <stack>
#include <boost/limits.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/property_map.hpp>

namespace boost
{
  namespace detail
  {
    template < typename Graph, typename ComponentMap,
      typename DiscoverTimeMap, typename LowPointMap, typename Stack >
      void biconnect(typename graph_traits < Graph >::vertex_descriptor v,
                     typename graph_traits < Graph >::vertex_descriptor u,
                     bool at_top,
                     const Graph & g,
                     ComponentMap comp,
                     std::size_t & c,
                     DiscoverTimeMap d,
                     std::size_t & dfs_time, LowPointMap lowpt, Stack & S)
    {
      typedef typename graph_traits < Graph >::vertex_descriptor vertex_t;
      typedef typename property_traits < DiscoverTimeMap >::value_type D;
      D infinity = std::numeric_limits < D >::max();
        put(d, v, ++dfs_time);
        put(lowpt, v, get(d, v));
      typename graph_traits < Graph >::out_edge_iterator ei, ei_end;
      for (tie(ei, ei_end) = out_edges(v, g); ei != ei_end; ++ei)
      {
        vertex_t w = target(*ei, g);
        if (get(d, w) == infinity)
        {
          S.push(*ei);
          biconnect(w, v, false, g, comp, c, d, dfs_time, lowpt, S);
          put(lowpt, v, std::min(get(lowpt, v), get(lowpt, w)));
          if (get(lowpt, w) >= get(d, v))
          {
            while (d[source(S.top(), g)] >= d[w]) {
              put(comp, S.top(), c);
              S.pop();
            }
            put(comp, S.top(), c);
              S.pop();
            ++c;

          }
        } else if (get(d, w) < get(d, v) && (!at_top && w != u))
        {
          S.push(*ei);
          put(lowpt, v, std::min(get(lowpt, v), get(d, w)));
        }
      }
    }

  }

  template < typename Graph, typename ComponentMap,
    typename DiscoverTimeMap, typename LowPointMap >
    void biconnected_components
    (typename graph_traits < Graph >::vertex_descriptor v,
     typename graph_traits < Graph >::vertex_descriptor u,
     const Graph & g,
     ComponentMap comp,
     std::size_t & num_components,
     DiscoverTimeMap discover_time, LowPointMap lowpt)
  {
    typedef graph_traits < Graph >::vertex_descriptor vertex_t;
    typedef graph_traits < Graph >::edge_descriptor edge_t;
    function_requires < VertexListGraphConcept < Graph > >();
    function_requires < IncidenceGraphConcept < Graph > >();
    function_requires < WritablePropertyMapConcept < ComponentMap,
      edge_t > >();
    function_requires < ReadWritePropertyMapConcept < DiscoverTimeMap,
      vertex_t > >();
    function_requires < ReadWritePropertyMapConcept < LowPointMap,
      vertex_t > >();

    typedef typename property_traits < DiscoverTimeMap >::value_type D;
    num_components = 0;
    std::size_t dfs_time = 0;
    std::stack < edge_t > S;
    typename graph_traits < Graph >::vertex_iterator wi, wi_end;
    std::size_t infinity = std::numeric_limits < std::size_t >::max();
    for (tie(wi, wi_end) = vertices(g); wi != wi_end; ++wi)
      put(discover_time, *wi, infinity);

    for (tie(wi, wi_end) = vertices(g); wi != wi_end; ++wi)
      if (get(discover_time, *wi) == std::numeric_limits < D >::max())
        detail::biconnect(*wi, *wi, true,
                          g, comp, num_components,
                          discover_time, dfs_time, lowpt, S);

  }

}                               // namespace boost

#endif  /* BOOST_GRAPH_BICONNECTED_COMPONENTS_HPP */


/* ========================================================================= */
/* === umf4 ================================================================ */
/* ========================================================================= */

/* ------------------------------------------------------------------------- */
/* UMFPACK Version 4.1 (Apr. 30, 2003), Copyright (c) 2003 by Timothy A.     */
/* Davis.  All Rights Reserved.  See ../README for License.                  */
/* email: davis@cise.ufl.edu    CISE Department, Univ. of Florida.           */
/* web: http://www.cise.ufl.edu/research/sparse/umfpack                      */
/* ------------------------------------------------------------------------- */

/***********************************************************************/
/*         UMFPACK Copyright, License and Availability                 */
/***********************************************************************/
/*
 *
 * UMFPACK Version 4.1 (Apr. 30, 2003),  Copyright (c) 2003 by Timothy A.
 * Davis.  All Rights Reserved.
 *
 * UMFPACK License:
 *
 *   Your use or distribution of UMFPACK or any modified version of
 *   UMFPACK implies that you agree to this License.
 *
 *   THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY
 *   EXPRESSED OR IMPLIED.  ANY USE IS AT YOUR OWN RISK.
 *
 *   Permission is hereby granted to use or copy this program, provided
 *   that the Copyright, this License, and the Availability of the original
 *   version is retained on all copies.  User documentation of any code that
 *   uses UMFPACK or any modified version of UMFPACK code must cite the
 *   Copyright, this License, the Availability note, and "Used by permission."
 *   Permission to modify the code and to distribute modified code is granted,
 *   provided the Copyright, this License, and the Availability note are
 *   retained, and a notice that the code was modified is included.  This
 *   software was developed with support from the National Science Foundation,
 *   and is provided to you free of charge.
 *
 * Availability:
 *
 *   http://www.cise.ufl.edu/research/sparse/umfpack
 *
 */

/* Used by permission. */

/* modified by Kresimir Fresl, 2003              */

/* UMFPACK bindings
 * ublas::compressed_matrix<> or ublas::coordinate_matrix<>
 * ... to use coordinate_matrix<> define COORDINATE
 */

/* Demo program for UMFPACK v4.1.
 *
 * Reads in a triplet-form matrix and right-hand side vector.
 * Input file format:
 *   num_rows_of_A  num_cols_of_A
 *   num_nonzeros_in_A
 *   row col val
 *   row col val
 *   ...
 *   num_nonzeros_in_B
 *   idx val
 *   ...
 *
 * Then calls UMFPACK to analyze, factor, and solve the system.
 *
 * Syntax:
 *
 *	umf4		default "auto" strategy, 1-norm row scaling
 *	umf4 a		default "auto" strategy, 1-norm row scaling
 *	umf4 u		unsymmetric strategy, 1-norm row scaling
 *	umf4 s		symmetric strategy, 1-norm row scaling
 *	umf4 2		2-by-2 strategy, maxnorm row scaling
 *	umf4 A		default "auto" strategy, maxnorm row scaling
 *	umf4 U		unsymmetric strategy, maxnorm row scaling
 *	umf4 S		symmetric strategy, maxnorm row scaling
 *	umf4 T		2-by-2 strategy , maxnorm row scaling
 *      umf4 ?n         ? can be in [aus2AUST], n: no aggressive absorption
 */

#include <iostream>
#include <fstream>
#include <cstdlib>
#include <string>
#include <algorithm>
#include <boost/math/special_functions/fpclassify.hpp>
#include <boost/numeric/bindings/traits/ublas_vector.hpp>
#include <boost/numeric/bindings/traits/ublas_sparse.hpp>
#include <boost/numeric/bindings/umfpack/umfpack.hpp>

using std::max;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::ifstream;
using std::ofstream;
using std::exit;

namespace ublas = boost::numeric::ublas;
namespace umf = boost::numeric::bindings::umfpack;
namespace traits = boost::numeric::bindings::traits;

#ifndef COORDINATE
typedef ublas::compressed_matrix<double, ublas::column_major, 0,
  ublas::unbounded_array<int>, ublas::unbounded_array<double> > cm_t;
#else
typedef ublas::coordinate_matrix<double, ublas::column_major, 0,
  ublas::unbounded_array<int>, ublas::unbounded_array<double> > cm_t;
#endif
typedef ublas::vector<double> v_t;

// wait for 'y'

void wait4y() {
  char yes = 'n';
  while (1) {
    cout << "Continue (y/n) -> ";
    cin >> yes;
    if (yes == 'y') break;
    if (yes == 'n') exit (0);
  }
}

// resid: compute the relative residual, ||Ax-b||/||b||

template <typename M, typename V>
double resid (M const& m, V const& x, V const& b, V& r) {

  int const* Ap = traits::spmatrix_index1_storage (m);
  int const* Ai = traits::spmatrix_index2_storage (m);
  double const* Ax = traits::spmatrix_value_storage (m);

  double rnorm, bnorm, absr, absb;

  int n = traits::vector_size (r);

  r = prod (m, x);

#if 0
  if (transpose) {
    int i;
    for (int j = 0; j < n; j++)
      for (int p = Ap [j]; p < Ap [j+1]; p++) {
        i = Ai [p];
        r [j] += Ax [p] * x [i];
      }
  } else {
    int i;
    for (int j = 0; j < n; j++)
      for (int p = Ap [j]; p < Ap [j+1]; p++) {
        i = Ai [p];
        r [i] += Ax [p] * x [j];
      }
  }
#endif

  for (int i = 0; i < n; i++)
    r [i] -= b [i];

  rnorm = 0.;
  bnorm = 0.;
  for (int i = 0; i < n; i++) {
    if ((boost::math::isnan) (r [i])){
      rnorm = r [i];
      break;
    }
    absr = fabs (r [i]);
    rnorm = max (rnorm, absr);
  }
  for (int i = 0; i < n; i++) {
    if ((boost::math::isnan) (b [i])){
      bnorm = b [i];
      break;
    }
    absb = fabs (b [i]);
    bnorm = max (bnorm, absb);
  }
  if (bnorm == 0)
    bnorm = 1;
  return (rnorm / bnorm);
}

////////////////////////////////////////////////////////////////
// main program

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

  cout << "\n===========================================================\n"
       << "=== UMFPACK v4.1 ==========================================\n"
       << "===========================================================\n"
       << endl;

  // set controls

  umf::control_type<> Control;
  Control [UMFPACK_PRL] = 3;
  Control [UMFPACK_BLOCK_SIZE] = 32;

  if (argc > 1) {
    char *s = argv [1];
    // get the strategy
    if (s [0] == 'u')
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_UNSYMMETRIC;
    else if (s [0] == 'a')
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_AUTO;
    else if (s [0] == 's')
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_SYMMETRIC;
    else if (s [0] == '2')
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_2BY2;
    else if (s [0] == 'U') {
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_UNSYMMETRIC;
      Control [UMFPACK_SCALE] = UMFPACK_SCALE_MAX;
    }
    else if (s [0] == 'A') {
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_AUTO;
      Control [UMFPACK_SCALE] = UMFPACK_SCALE_MAX;
    }
    else if (s [0] == 'S') {
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_SYMMETRIC;
      Control [UMFPACK_SCALE] = UMFPACK_SCALE_MAX;
    }
    else if (s [0] == 'T') {
      Control [UMFPACK_STRATEGY] = UMFPACK_STRATEGY_2BY2;
      Control [UMFPACK_SCALE] = UMFPACK_SCALE_MAX;
    }
    else
      printf ("unrecognized strategy: %s\n", argv [1]);

    if (s [1] == 'n')
      // no aggressive absorption
      Control [UMFPACK_AGGRESSIVE] = 0;
  }
  bool call_wait4y = true;
  string ifs_name;
  if (argc > 2) {
    call_wait4y = false;
    ifs_name = argv [2];
  }

  umf::report_control (Control);

  // open the matrix file
  if (ifs_name.empty()) {
    cout << "input file -> ";
    cin >> ifs_name;
  }
  ifstream f (ifs_name.c_str());
  if (!f) {
    cout << "unable to open file" << endl;
    exit (1);
  }

  // initialize matrix A
  cout << "reading A" << endl;
  int nrow, ncol, nz;
  f >> nrow >> ncol >> nz;
  cm_t A (nrow, ncol, nz);
  int r, c;
  for (int i = 0; i < nz; ++i) {
    f >> r >> c;
    double val;
    f >> val;
    A (r, c) = val;
  }
  Control[UMFPACK_PRL] = (nz > 20) ? 4 : 5;
  cout << "matrix A: ";
  umf::report_matrix (A, Control);

  if (call_wait4y) wait4y();

  // symbolic factorization
  umf::info_type<> Info;
  umf::symbolic_type<> Symbolic;

  int status = umf::symbolic (A, Symbolic, Control, Info);

  cout << "symbolic status:" << endl;
  umf::report_status (Control, status);
  if (status != UMFPACK_OK) {
    umf::report_info (Control, Info);
    cout << "umf::symbolic failed" << endl;
    exit (1);
  }
  umf::report_symbolic (Symbolic, Control);

  if (call_wait4y) wait4y();

  // numeric factorization
  umf::numeric_type<> Numeric;

  status = umf::numeric (A, Symbolic, Numeric, Control, Info);

  cout << "numeric status:" << endl;
  umf::report_status (Control, status);
  if (status < UMFPACK_OK) {
    umf::report_info (Control, Info);
    cout << "umf::numeric failed" << endl;
    exit (1);
  }
  umf::report_numeric (Numeric, Control);

  if (call_wait4y) wait4y();

  // solve Ax=b
  if (nrow == ncol && status == UMFPACK_OK) {

    // create right-hand side vector B
    v_t B (ncol), X (ncol), R (ncol);
    cout << "reading B" << endl;
    int m2, ii;
    f >> m2;
    for (int i = 0; i < m2; ++i) {
      f >> ii;
      f >> B[ii];
    }
    cout << "vector B: ";
    umf::report_vector (B, Control);

    status = umf::solve (A, X, B, Numeric, Control, Info);

    cout << "solve status:" << endl;
    umf::report_status (Control, status);
    umf::report_info (Control, Info);
    if (status < UMFPACK_OK) {
      cout << "umf::solve failed" << endl;
      exit (1);
    }
    cout << "solution vector X: ";
    umf::report_vector (X, Control);

    cout << "relative maxnorm of residual, ||Ax-b||/||b||: "
	 << resid (A, X, B, R) << endl;

  } else {

    cout << "system not solved" << endl;
    umf::report_info (Control, Info);

  }

  cout << endl;

}


/**
 * @file
 *
 * Includes all external headers.
 */
#pragma once

#include <algorithm>
#include <numeric>
#include <limits>
#include <utility>
#include <functional>
#include <vector>
#include <memory>
#include <string>
#include <sstream>
#include <iomanip>
#include <initializer_list>
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cstdint>
#include <cstddef>
#include <cmath>
#include <unistd.h>
#include <getopt.h>
#include <dlfcn.h>

#include <eigen3/Eigen/Dense>
#include <eigen3/Eigen/Sparse>
#include <eigen3/unsupported/Eigen/KroneckerProduct>

/* undefine __SSE2__ as workaround for compile errors for device target due
 * to use of SSE2 intrinsics in Boost headers */
#define OLD_SSE2 __SSE2__
#undef __SSE2__
#include <boost/math/distributions.hpp>
#include <boost/math/special_functions.hpp>
#define __SSE2__ OLD_SSE2

#ifdef _OPENMP
#include <omp.h>
#endif


// Author(s): Wieger Wesselink
// Copyright: see the accompanying file COPYING or copy at
// https://svn.win.tue.nl/trac/MCRL2/browser/trunk/COPYING
//
// 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)
//
/// \file stategraph_test.cpp
/// \brief Add your file description here.

// #define MCRL2_PBES_STATEGRAPH_CHECK_GUARDS

#include <iostream>
#include <utility>
#include <set>
#include <boost/test/minimal.hpp>
#include "mcrl2/pbes/detail/guard_traverser.h"
#include "mcrl2/pbes/find.h"
#include "mcrl2/pbes/significant_variables.h"
#include "mcrl2/pbes/txt2pbes.h"
#include "mcrl2/pbes/detail/stategraph_local_reset_variables.h"
#include "mcrl2/pbes/detail/stategraph_global_reset_variables.h"
#include "mcrl2/utilities/detail/split.h"

using namespace mcrl2;
using namespace pbes_system;

// prints the names of the variables (sorted alphabetically)
std::string print_set(const std::set<data::variable>& variables)
{
  std::ostringstream out;
  std::set<std::string> s;
  for (const data::variable& v: variables)
  {
    s.insert(std::string(v.name()));
  }
  out << "{";
  for (auto i = s.begin(); i != s.end(); ++i)
  {
    if (i != s.begin())
    {
      out << ", ";
    }
    out << *i;
  }
  out << "}";
  return out.str();
}

propositional_variable_instantiation propvar(const std::string& name)
{
  return propositional_variable_instantiation(core::identifier_string(name), data::data_expression_list());
}

void check_result(const std::string& expression, const std::string& result, const std::string& expected_result, const std::string& title)
{
  if (result != expected_result)
  {
    std::cout << "--- failure in " << title << " ---" << std::endl;
    std::cout << "expression      = " << expression << std::endl;
    std::cout << "result          = " << result << std::endl;
    std::cout << "expected result = " << expected_result << std::endl;
    BOOST_CHECK(result == expected_result);
  }
}

void test_significant_variables(const pbes_expression& x, const std::string& expected_result)
{
  std::set<data::variable> v = significant_variables(x);
  std::string result = print_set(v);
  check_result(pbes_system::pp(x), result, expected_result, "significant_variables");
}

void test_significant_variables()
{
  std::string text =
    "pbes                                                    \n"
    "   mu X0(b: Bool) = val(b);                             \n"
    "   mu X1 = true;                                        \n"
    "   mu X2(b: Bool) = val(b) && forall b: Bool. val(b);   \n"
    "   mu X3(b: Bool) = val(b) && forall c: Bool. val(c);   \n"
    "init X1;                                                \n"
    ;

  bool normalize = false;
  pbes p = txt2pbes(text, normalize);
  const std::vector<pbes_equation>& eqn = p.equations();
  test_significant_variables(eqn[0].formula(), "{b}");
  test_significant_variables(eqn[1].formula(), "{}");
  test_significant_variables(eqn[2].formula(), "{b}");
  test_significant_variables(eqn[2].formula(), "{b}");
}

// find propositional variable instantiation with the given name
propositional_variable_instantiation find_propvar(const std::string& name, const pbes_expression& x)
{
  for (const propositional_variable_instantiation& v: find_propositional_variable_instantiations(x))
  {
    if (v.name() == core::identifier_string(name))
    {
      return v;
    }
  }
  throw mcrl2::runtime_error("propvar not found!");
  return propositional_variable_instantiation();
}

void test_guard(const std::string& pbesspec, const std::string& X, const std::string& expected_result)
{
  bool normalize = false;
  pbes p = txt2pbes(pbesspec, normalize);
  pbes_expression x1 = p.equations().front().formula();
  propositional_variable_instantiation X1 = find_propvar(X, x1);
  simplify_data_rewriter<data::rewriter> R(p.data());

  detail::guard_traverser f(p.data());
  f.apply(x1);
  BOOST_CHECK(f.expression_stack.back().check_guards(x1, R));

//  pbes_expression g = detail::guard(X1, x1);
//  std::string result = pbes_system::pp(g);
//  check_result(X, result, expected_result, "");
}

void test_guard()
{
  std::string text =
    "pbes                          \n"
    "   mu X1 = X1 || X2;          \n"
    "   mu X2 = true;              \n"
    "init X2;                      \n"
    ;
  test_guard(text, "X1", "true");

  text =
    "pbes                                     \n"
    "   mu X1(b: Bool) = X1(true) && val(b);  \n"
    "   mu X2 = true;                         \n"
    "init X2;                                 \n"
    ;
  test_guard(text, "X1", "b");

  text =
    "pbes                                     \n"
    "   mu X1(b: Bool) = X1(true) || val(b);  \n"
    "   mu X2 = true;                         \n"
    "init X2;                                 \n"
    ;
  test_guard(text, "X1", "!val(b)");

  text =
    "pbes                                             \n"
    "   mu X1(b: Bool) = X1(true) || (val(b) && X2);  \n"
    "   mu X2 = true;                                 \n"
    "init X2;                                         \n"
    ;
  test_guard(text, "X1", "true");

  text =
    "pbes                                                       \n"
    "   mu X1(b: Bool) = (forall c: Bool. val(c)) || X1(true);  \n"
    "   mu X2 = true;                                           \n"
    "init X2;                                                   \n"
    ;
  test_guard(text, "X1", "!(forall c: Bool. val(c))");

  text =
    "pbes                                                       \n"
    "   mu X1(b: Bool) = (forall c: Bool. val(c)) && X1(true);  \n"
    "   mu X2 = true;                                           \n"
    "init X2;                                                   \n"
    ;
  test_guard(text, "X1", "forall c: Bool. val(c)");

  text =
    "pbes                                                 \n"
    "   mu X1(b: Bool) = forall c: Bool. val(c) || X1(c); \n"
    "   mu X2 = true;                                     \n"
    "init X2;                                             \n"
    ;
  test_guard(text, "X1", "!val(c)");

  text =
    "pbes                                                             \n"
    "   mu X1(b: Bool) = val(b) || (forall c: Bool. val(c) || X1(c)); \n"
    "   mu X2 = true;                                                 \n"
    "init X2;                                                         \n"
    ;
  test_guard(text, "X1", "!val(b) && !val(c)");

  text =
    "pbes                                                                     \n"
    "   mu X1(b: Bool) = (val(b) && X2) || (forall c: Bool. val(c) || X1(c)); \n"
    "   mu X2 = true;                                                         \n"
    "init X2;                                                                 \n"
    ;
  test_guard(text, "X1", "!val(c)");

  text =
    "pbes                                                                     \n"
    "   mu X1(b: Bool) = (val(b) || X2) || (forall c: Bool. val(c) || X1(c)); \n"
    "   mu X2 = true;                                                         \n"
    "init X2;                                                                 \n"
    ;
  test_guard(text, "X1", "!val(c)");

  text =
    "pbes                                                                      \n"
    "   mu X1(b: Bool) = (!val(b) || X2) || (forall c: Bool. val(c) || X1(c)); \n"
    "   mu X2 = true;                                                          \n"
    "init X2;                                                                  \n"
    ;
  test_guard(text, "X1", "!val(c)");

  text =
    "pbes                                                                             \n"
    "   mu X0(b, c, d, e: Bool) = (val(b) || X1) && (val(c) || X2) && (val(d) || X3); \n"
    "   mu X1 = true;                                                                 \n"
    "   mu X2 = true;                                                                 \n"
    "   mu X3 = true;                                                                 \n"
    "init X2;                                                                         \n"
    ;
  test_guard(text, "X1", "!val(b)");

  text =
    "pbes                                                   \n"
    "   mu X0(b, c, d, e: Bool) = (val(b) || val(e) && X1); \n"
    "   mu X1 = true;                                       \n"
    "init X1;                                               \n"
    ;
  test_guard(text, "X1", "!val(b) && val(e)");

  text =
    "pbes                                                                     \n"
    "   mu X0(b, c, d, e: Bool) = (val(b) || val(e) && X1) && (val(c) || X2); \n"
    "   mu X1 = true;                                                         \n"
    "   mu X2 = true;                                                         \n"
    "init X1;                                                                 \n"
    ;
  test_guard(text, "X1", "!val(b) && val(e)");

  text =
    "pbes nu X1(s,d: Nat) = (!val((s == 1)) || X1(2, d)) &&\n"
    "                       (!val((s == 2)) || X2(3, d)) &&\n"
    "                       (!val((s == 3)) || X3(4, d)) &&\n"
    "                       (!val((s == 4)) || (forall e: Nat. val(!(e < 10)) || X4(5, 2 * e))) &&\n"
    "                       (!val((s == 5)) || val(d mod 2 == 0) && X5(6, d)) &&\n"
    "                       (!val((s == 6)) || X6(7, d)) &&\n"
    "                       (!val((s == 7)) || X7(1, d));\n"
    "     nu X2(s,d: Nat) = true;\n"
    "     nu X3(s,d: Nat) = true;\n"
    "     nu X4(s,d: Nat) = true;\n"
    "     nu X5(s,d: Nat) = true;\n"
    "     nu X6(s,d: Nat) = true;\n"
    "     nu X7(s,d: Nat) = true;\n"
    "init X1(0, 0);\n"
    ;
  test_guard(text, "X1", "s == 1");
  test_guard(text, "X2", "s == 2");
  test_guard(text, "X3", "s == 3");
  test_guard(text, "X4", "val(s == 4) && !val(!(e < 10))");
  test_guard(text, "X5", "val(s == 5) && val(d mod 2 == 0)");
  test_guard(text, "X6", "s == 6");
  test_guard(text, "X7", "s == 7");
}

void test_local_stategraph()
{
  std::string text;
  bool normalize = false;
  pbes p;

  text =
    "sort D = struct d1 | d2;\n"
    "\n"
    "pbes nu X(s: Nat, d: D)   = forall v: D. forall e1: D. val(!(e1 == v)) || val(!(s == 1)) || Y(2, e1, v);\n"
    "     nu Y(s: Nat, d,v: D) = val(!(s == 2)) || Y(3, d, v);\n"
    "\n"
    "init X(1, d1);\n"
   ;
  p = txt2pbes(text, normalize);
  // This fails because no suitable must graph is found
  // pbes_system::detail::local_reset_variables_algorithm(p).run();

  text =
    "pbes\n"
    "nu X(c:Pos, d:Nat) = val(c == 1) => Y(1,d+1) && (val(c == 1) => X(1,d+2));\n"
    "nu Y(c:Pos, d:Nat) = (val(c == 1) => Y(c,d+1)) && (val(d > 0));\n"
    "init X(1,0);\n"
    ;
  p = txt2pbes(text, normalize);
  pbesstategraph_options options;
  options.use_global_variant = false;
  pbes_system::detail::local_reset_variables_algorithm(p, options).run();
}

// Test cases provided by Tim Willemse, 28-06-2013
void test_cfp()
{
  std::string text =
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(0,j+1,n+1,m+1)) &&                                                  \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i)}                                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(0,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(0,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,n,j,m) );                                                         \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i)}, {(Y, i)}                                                                    \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(0,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(0,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,j,n,m) );                                                         \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i)}, {(Y, i)}                                                                    \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(i,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(0,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,j,n,m) );                                                         \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i)}                                                                      \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(i,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(0,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,j,n,m) ) &&                                                       \n"
    "(val(i == 1) => Y(0,j,n,m) );                                                         \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i)}                                                                      \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(i,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(0,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,j,n,m) ) &&                                                       \n"
    "(val(i == 1) => Y(0,j,n,m) );                                                         \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i)}                                                                      \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(i,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(j,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => X(i,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,j,n,m) ) &&                                                       \n"
    "(val(i == 1) => Y(0,j,n,m) );                                                         \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => Y(i,j,n+1,m+1)) &&                                                    \n"
    "(val(i == 0) => X(i+1,j+1,n+1,m+1));                                                  \n"
    "                                                                                \n"
    "nu Y(i,j,n,m:Nat) =                                                                   \n"
    "(val(i == 1) => X(j,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Z(i,j,n,m) ) &&                                                       \n"
    "(val(i == 0) => Y(1,j,n,m) ) &&                                                       \n"
    "(val(i == 1) => Y(0,j,n,m) );                                                         \n"
    "                                                                                \n"
    "nu Z(i,j,n,m:Nat) =                                                                   \n"
    "(val(i== 0) => X(i,j,n,m)) &&                                                         \n"
    "(val(i== 1) => Z(i,j,n,m));                                                           \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0,0);                                                                           \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => Y(i,j,n+1)) &&                                                        \n"
    "(val(i == 0) => X(i+1,j+1,n+1));                                                      \n"
    "                                                                                \n"
    "nu Y(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => X(j,j,n) )                                                            \n"
    ";                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0);                                                                             \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(Y, i)}                                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => Y(i,j,n+1)) &&                                                        \n"
    "(val(i == 0) => X(i+1,j+1,n+1));                                                      \n"
    "                                                                                \n"
    "nu Y(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => X(i,j,n) ) &&                                                         \n"
    "(val(i == 1) => Z(i,j,n) )                                                            \n"
    ";                                                                                \n"
    "                                                                                \n"
    "nu Z(i,j,n:Nat) =                                                                     \n"
    "(val(j == 1) => X(i,j,n)) &&                                                          \n"
    "(val(i == 1) => Z(0,j,n))                                                             \n"
    ";                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0);                                                                             \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i), (Z, i)}                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => Y(i,j,n+1)) &&                                                        \n"
    "(val(i == 0) => X(i+1,j+1,n+1));                                                      \n"
    "                                                                                \n"
    "nu Y(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => X(i,j,n) ) &&                                                         \n"
    "(val(i == 1) => Z(i,j,n) )                                                            \n"
    ";                                                                                \n"
    "                                                                                \n"
    "nu Z(i,j,n:Nat) =                                                                     \n"
    "(val(j == 1) => X(j,i,n)) &&                                                          \n"
    "(val(i == 1) => Z(0,j,n))                                                             \n"
    ";                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0);                                                                             \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => Y(i,j,n+1)) &&                                                        \n"
    "(val(i == 0) => X(i+1,i+1,n+1));                                                      \n"
    "                                                                                \n"
    "nu Y(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => X(i,j,n) ) &&                                                         \n"
    "(val(i == 1) => Z(i,j,n) )                                                            \n"
    ";                                                                                \n"
    "                                                                                \n"
    "nu Z(i,j,n:Nat) =                                                                     \n"
    "(val(j == 1) => X(i,j,n)) &&                                                          \n"
    "(val(i == 1) => Z(0,j,n))                                                             \n"
    ";                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0);                                                                             \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i), (Z, i)}                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => Y(i,j,n+1)) &&                                                        \n"
    "(val(i == 0) => X(1,1,n+1));                                                          \n"
    "                                                                                \n"
    "nu Y(i,j,n:Nat) =                                                                     \n"
    "(val(i == 1) => X(i,j,n) ) &&                                                         \n"
    "(val(i == 1) => Z(i,j,n) )                                                            \n"
    ";                                                                                \n"
    "                                                                                \n"
    "nu Z(i,j,n:Nat) =                                                                     \n"
    "(val(j == 1) => X(i,j,n)) &&                                                          \n"
    "(val(i == 1) => Z(0,j,n))                                                             \n"
    ";                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0);                                                                             \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i), (Z, i)}                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,j,n:Nat) =                                                                     \n"
    "(val(i == 0) => Y(i,j,n+1));                                                          \n"
    "                                                                                \n"
    "nu Y(i,j,n:Nat) =                                                                     \n"
    "(val(i==0) => Z(i,j,n+1));                                                            \n"
    "                                                                                \n"
    "nu Z(i,j,n:Nat) =                                                                     \n"
    "(val(i==0) => Z(1,j,n)) &&                                                            \n"
    "(val(i==1) => (X(i,j,n) || Y(i,j,n)));                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0,0);                                                                             \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i), (Z, i)}, {(X, j), (Y, j), (Z, j)}                                    \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) =                                                                       \n"
    "(val(i == 0) => Y(i,n+1));                                                            \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) =                                                                       \n"
    "(val(i==0) => Z(i,n+1));                                                              \n"
    "                                                                                \n"
    "nu Z(i,n:Nat) =                                                                       \n"
    "(val(i==0) => X(i,n));                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, i), (Z, i)}                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(n:Nat) = Y(n+1);                                                                 \n"
    "nu Y(n:Nat) = Z(n+1);                                                                 \n"
    "nu Z(n:Nat) =  X(n);                                                                  \n"
    "                                                                                \n"
    "init X(0);                                                                            \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) =                                                                       \n"
    "(val(i == 0) => Y(i,n+1));                                                            \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) =                                                                       \n"
    "(val(i==0) => Z(i,0,n+1)) &&                                                          \n"
    "(val(i==0) => X(i,n+1));                                                              \n"
    "                                                                                \n"
    "nu Z(i,j,n:Nat) =                                                                     \n"
    "(val(i==0 && n == 1) => Z(1,j,n)) &&                                                  \n"
    "(val(i==1) => X(n,j));                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(Z, i)}                                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) =                                                                       \n"
    "(val(i == 0) => Y(i,n));                                                              \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) =                                                                       \n"
    "(val(i==0) => Z(i,0)) &&                                                              \n"
    "(val(i==0) => X(i,2));                                                                \n"
    "                                                                                \n"
    "nu Z(i,n:Nat) =                                                                       \n"
    "(val(i==0 && n == 1) => Z(1,n)) &&                                                    \n"
    "(val(i==1) => X(n,i));                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, n), (Y, n), (Z, i)}                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) =                                                                       \n"
    "(val(i == 0) => Y(i,n));                                                              \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) =                                                                       \n"
    "(val(i==0) => X(i,0)) &&                                                              \n"
    "(val(i==1) => X(n,2));                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) =                                                                       \n"
    "(val(i == 0) => Y(i,n+1));                                                            \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) =                                                                       \n"
    "(val(i==0) => X(i,0)) &&                                                              \n"
    "(val(i==1) => X(n,2));                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(n,i:Nat) = Y(n+1,0);                                                             \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) = X(0,n);                                                               \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, i), (Y, n)}                                                                      \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) = forall j:Nat.(val(i == 0) => Y(i,n+j));                               \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) = (val(i==1) => X(n,2));                                                \n"
    "                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(Y, i)}                                                                              \n"
    "----------                                                                            \n"
    "%                                                                                \n"
    "% The below PBES should only have (Y,i) as a control flow parameter                   \n"
    "%                                                                                \n"
    "                                                                                \n"
    "pbes                                                                                \n"
    "nu X(n:Nat) = forall j:Nat. Y(0,n+j);                                                 \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) = (val(i==1) => X(2));                                                  \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0);                                                                                \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(Y, i)}                                                                              \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "nu X(n:Nat) = forall j:Nat. Y(0,j);                                                   \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) = (val(i==1) => X(2));                                                  \n"
    "                                                                                \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0);                                                                                \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{(X, n)}, {(Y, i)}                                                                    \n"
    "----------                                                                            \n"
    "pbes                                                                                \n"
    "                                                                                \n"
    "nu X(i,j:Nat) =                                                                       \n"
    "(val(i==0) => X(0,i)) &&                                                              \n"
    "(val(j==1) => X(j,1));                                                                \n"
    "                                                                                \n"
    "init X(0,0);                                                                          \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    "----------                                                                            \n"
    "%This solution assumes strict separation between CFPs and DPs, if this assumption is  \n"
    "%dropped, the following is the expected solution                                      \n"
    "%{(X, n), (Y, n)}                                                                     \n"
    "pbes                                                                                \n"
    "nu X(i,n:Nat) =                                                                       \n"
    "(val(i == 0) => Y(i,n));                                                              \n"
    "                                                                                \n"
    "nu Y(i,n:Nat) =                                                                       \n"
    "(val(i==0) => X(i,0)) &&                                                              \n"
    "(val(i==1) => X(n+1,2));                                                              \n"
    "                                                                                \n"
    "init                                                                                \n"
    "X(0,0);                                                                               \n"
    "                                                                                \n"
    "expected_result                                                                       \n"
    "{}                                                                                \n"
    ;

  std::vector<std::string> test_cases = utilities::detail::split_text(text, "----------");
  for (const std::string& s: test_cases)
  {
    std::vector<std::string> keywords;
    keywords.push_back("pbes");
    keywords.push_back("expected_result");
    std::map<std::string, std::string> test_case = utilities::detail::split_text_using_keywords(s, keywords);
    pbes p = txt2pbes(test_case["pbes"], false);
    std::string expected_result = boost::trim_copy(test_case["expected_result"]);
    if (!expected_result.empty())
    {
      expected_result = boost::trim_copy(expected_result.substr(15));
    }
    std::vector<std::string> lines = utilities::detail::nonempty_lines(expected_result);
    std::set<std::string> lineset(lines.begin(), lines.end());
    std::string expected = utilities::string_join(lineset, ", ");
    pbesstategraph_options options;
    pbes_system::detail::stategraph_algorithm algorithm(p, options);
    algorithm.run();
    //std::string result = utilities::string_join(print_connected_components(algorithm.connected_components(), algorithm), ", ");
    //
    //if (result != expected)
    //{
    //  BOOST_CHECK(result == expected);
    //  std::cout << "--- Control flow test failed ---" << std::endl;
    //  std::cout << test_case["pbes"] << std::endl;
    //  std::cout << "result               = " << result   << std::endl;
    //  std::cout << "expected result      = " << expected << std::endl;
    //}
  }
}

int test_main(int, char**)
{
  log::mcrl2_logger::set_reporting_level(log::debug, "stategraph");
  test_guard();
  test_significant_variables();
  test_local_stategraph();
  test_cfp(); // This does not longer work since the computation of components has been changed.

  return 0;
}


/*
 * Copyright 2009-2020 The VOTCA Development Team (http://www.votca.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 *
 *     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.
 *
 */
#define BOOST_TEST_MAIN

#define BOOST_TEST_MODULE aobasis_test

// Standard includes
#include <fstream>

// Third party includes
#include <boost/test/unit_test.hpp>

// Local VOTCA includes
#include "votca/xtp/aobasis.h"
#include "votca/xtp/aomatrix.h"
#include "votca/xtp/convergenceacc.h"
#include "votca/xtp/orbitals.h"

using namespace votca::xtp;
using namespace votca;

BOOST_AUTO_TEST_SUITE(aobasis_test)

BOOST_AUTO_TEST_CASE(FillNormBasis_test) {

  Orbitals orbitals;
  orbitals.QMAtoms().LoadFromFile(std::string(XTP_TEST_DATA_FOLDER) +
                                  "/aobasis/Al.xyz");
  BasisSet basis;
  basis.Load(std::string(XTP_TEST_DATA_FOLDER) + "/aobasis/notnormalized.xml");
  AOBasis aobasis;
  aobasis.Fill(basis, orbitals.QMAtoms());

  const AOShell& shell = aobasis.getShell(0);
  std::vector<double> ref_results = {0.1831079647, 0.9155398233};
  Index i = 0;
  bool check_norm = true;
  for (const AOGaussianPrimitive& gaussian : shell) {
    if (std::abs(ref_results[i] - gaussian.getContraction()) > 1e-7) {
      check_norm = false;
      break;
    }
    i++;
  }

  i = 0;
  if (!check_norm) {
    for (const AOGaussianPrimitive& gaussian : shell) {
      std::cout << "Ref:" << ref_results[i]
                << " result:" << gaussian.getContraction() << std::endl;
      i++;
    }
  }
  BOOST_CHECK_EQUAL(check_norm, 1);
}

BOOST_AUTO_TEST_SUITE_END()


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
/*!
 * \file
**/
#ifndef NT2_COMPLEX_FUNCTIONS_C_REAL_HPP_INCLUDED
#define NT2_COMPLEX_FUNCTIONS_C_REAL_HPP_INCLUDED
#include <nt2/include/functor.hpp>
#include <boost/dispatch/include/functor.hpp>

#include <nt2/sdk/complex/complex.hpp>

/*!
 * \ingroup nt2_complex
 * \defgroup nt2_complex_c_real c_real
 *
 * \par Description
 * return the c_real imaginary part of a complex
 * but as a value of imaginary type
 *
 * \par Header file
 *
 * \code
 * #include <nt2/include/functions/c_real.hpp>
 * \endcode
 *
 * \synopsis
 *
 * \code
 * namespace boost::simd
 * {
 *   template <class A0>
 *     meta::call<tag::c_real_(A0)>::type
 *     c_real(const A0 & a0);
 * }
 * \endcode
 *
 * \param a0 the first parameter of c_real
 *
 * \return an imaginary value
 *
 * \par Notes
 * In SIMD mode, this function acts elementwise on the input vectors' elements
 * \par
 *
**/

namespace nt2
{
  namespace tag
  {
    /*!
     * \brief Define the tag c_real_ of functor c_real
     *        in namespace nt2::tag for toolbox complex
    **/
    struct c_real_ : ext::elementwise_<c_real_> { typedef ext::elementwise_<c_real_> parent; };
  }

  BOOST_DISPATCH_FUNCTION_IMPLEMENTATION(tag::c_real_, c_real, 1)
}

#endif