/**
 * @file
 * @copyright This code is licensed under the 3-clause BSD license.\n
 *            Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.\n
 *            See LICENSE.txt for details.
 */

#include <Core/Log.h>
#include <Utils/Math/IterativeDiagonalizer/DavidsonDiagonalizer.h>
#include <Utils/Math/IterativeDiagonalizer/DiagonalizerSettings.h>
#include <Utils/Math/IterativeDiagonalizer/IndirectPreconditionerEvaluator.h>
#include <Utils/Math/IterativeDiagonalizer/IndirectSigmaVectorEvaluator.h>
#include <Utils/Math/IterativeDiagonalizer/SubspaceCollapser.h>
#include <Utils/Math/IterativeDiagonalizer/SubspaceOrthogonalizer.h>
#include <gmock/gmock.h>
#include <Eigen/Eigenvalues>
#include <Eigen/Sparse>
#include <chrono>
#include <random>

namespace Scine {
namespace Utils {

using namespace testing;

/**
 * @class ADavidsonDiagonalizerTest @file DavidsonDiagonalizerTest.cpp
 * @test
 */
class ADavidsonDiagonalizerTest : public Test {
 public:
  int arbitraryNumberEigenvalue = 4;
  int arbitraryTrialSubspace = 8;
  int arbitraryDimension = 100;
  Eigen::MatrixXd arbitraryDenseMatrix;
  Eigen::SparseMatrix<double> arbitrarySparseMatrix;
  Core::Log log;

 private:
  void SetUp() final {
    Eigen::VectorXd diagonal = Eigen::VectorXd::Ones(arbitraryDimension);
    for (int i = 0; i < arbitraryDimension; ++i) {
      diagonal(i) *= i;
    }
    Eigen::MatrixXd arbitraryMatrix = 0.01 * Eigen::MatrixXd::Random(arbitraryDimension, arbitraryDimension);
    arbitraryDenseMatrix = arbitraryMatrix + arbitraryMatrix.transpose();
    arbitraryDenseMatrix.diagonal() += diagonal;

    arbitrarySparseMatrix.resize(arbitraryDimension, arbitraryDimension);

    Eigen::MatrixXd randomMatrix = Eigen::MatrixXd::Random(arbitraryDimension, arbitraryDimension);
    for (Eigen::Index i = 0; i < arbitraryDimension; ++i) {
      for (Eigen::Index j = i; j < arbitraryDimension; ++j) {
        if (i == j || randomMatrix(i, j) > 0.8) { // Random c [-1,1]
          arbitrarySparseMatrix.coeffRef(i, j) = arbitraryDenseMatrix(i, j);
          arbitrarySparseMatrix.coeffRef(j, i) = arbitraryDenseMatrix(i, j);
        }
      }
    }
  }
};

TEST_F(ADavidsonDiagonalizerTest, DavidsonDiagonalizerCanBeConstructed) {
  OrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);
  NonOrthogonalDavidson davidsonBalanced{arbitraryNumberEigenvalue, arbitraryDimension};
  davidsonBalanced.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);
}

TEST_F(ADavidsonDiagonalizerTest, IndirectPreconditionerEvaluatorCanBeConstructed) {
  Eigen::VectorXd arbitraryDiagonal(5);
  arbitraryDiagonal << 1, 2, 3, 4, 5;
  IndirectPreconditionerEvaluator preconditioner(arbitraryDiagonal);

  // result = -(H - I\lambda)^{-1} * v
  //        = -1/[-5, -4, -3, -2, -1] * v (cwise)
  //        = 1/5, 1/4, 1/3, 1/2, 1
  Eigen::VectorXd expected(5);
  expected << 1. / 5., 1. / 4., 1. / 3., 1. / 2., 1;
  Eigen::VectorXd preconditionedVector = preconditioner.evaluate(Eigen::VectorXd::Ones(5), 6);
  for (int i = 0; i < preconditionedVector.size(); ++i) {
    EXPECT_DOUBLE_EQ(preconditionedVector(i), expected(i));
  }
}

TEST_F(ADavidsonDiagonalizerTest, IndirectSigmaVectorEvaluatorCanBeConstructed) {
  Eigen::MatrixXd arbMatrix(3, 3);
  arbMatrix << 1, 2, 3, 2, 4, 6, 3, 6, 9;
  IndirectSigmaVectorEvaluator<Eigen::MatrixXd> sve(arbMatrix);

  Eigen::MatrixXd guessVectors = Eigen::Vector3d(2, 2, 2).asDiagonal();

  Eigen::MatrixXd result = sve.evaluate(guessVectors);

  ASSERT_EQ(result.rows(), 3);
  ASSERT_EQ(result.cols(), 3);
  for (int row = 0; row < result.rows(); ++row) {
    for (int col = 0; col < result.cols(); ++col) {
      EXPECT_DOUBLE_EQ(result(row, col), arbMatrix(row, col) * 2);
    }
  }

  // collapsed should not have any effect
  sve.collapsed(1);
  result = sve.evaluate(guessVectors);

  ASSERT_EQ(result.rows(), 3);
  ASSERT_EQ(result.cols(), 3);
  for (int row = 0; row < result.rows(); ++row) {
    for (int col = 0; col < result.cols(); ++col) {
      EXPECT_DOUBLE_EQ(result(row, col), arbMatrix(row, col) * 2);
    }
  }

  Eigen::MatrixXd guessVectorsWrongDims = Eigen::Vector2d(2, 2).asDiagonal();
  ASSERT_THROW(sve.evaluate(guessVectorsWrongDims), std::runtime_error);
}

TEST_F(ADavidsonDiagonalizerTest, SparseIndirectSigmaVectorEvaluatorCanBeConstructed) {
  Eigen::MatrixXd arbDenseMatrix(3, 3);
  arbDenseMatrix << 1, 2, 3, 2, 4, 6, 3, 6, 9;
  Eigen::SparseMatrix<double> arbMatrix = arbDenseMatrix.sparseView(0, 0);
  auto sve = std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbMatrix);

  Eigen::MatrixXd guessVectors = Eigen::Vector3d(2, 2, 2).asDiagonal();

  Eigen::MatrixXd result = sve->evaluate(guessVectors);

  ASSERT_EQ(result.rows(), 3);
  ASSERT_EQ(result.cols(), 3);
  for (int row = 0; row < result.rows(); ++row) {
    for (int col = 0; col < result.cols(); ++col) {
      EXPECT_DOUBLE_EQ(result(row, col), arbMatrix.coeffRef(row, col) * 2);
    }
  }

  // collapsed should not have any effect
  sve->collapsed(1);
  result = sve->evaluate(guessVectors);

  ASSERT_EQ(result.rows(), 3);
  ASSERT_EQ(result.cols(), 3);
  for (int row = 0; row < result.rows(); ++row) {
    for (int col = 0; col < result.cols(); ++col) {
      EXPECT_DOUBLE_EQ(result(row, col), arbMatrix.coeffRef(row, col) * 2);
    }
  }
  sve.reset();
  ASSERT_FALSE(sve);
}
TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithStandardMethod) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);

  OrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, 1);

  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  auto result = davidson.solve(log);

  ASSERT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < arbitraryDimension; ++j) {
    ASSERT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithStandardMethodAndManyEigenvaluesWithLargeGuessSpace) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryDimension - 3);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryDimension - 3);

  OrthogonalDavidson davidson{arbitraryDimension - 3, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryDimension - 3);
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));

  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryDimension - 3; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithStandardMethodAndManyEigenvalues) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryNumberEigenvalue);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryNumberEigenvalue);

  OrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);

  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));

  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryNumberEigenvalue; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithSparseMatrix) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitrarySparseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);

  OrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, 1);
  davidson.setSigmaVectorEvaluator(
      std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbitrarySparseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitrarySparseMatrix.diagonal()));
  auto result = davidson.solve(log);

  ASSERT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < 100; ++j) {
    ASSERT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithSparseMatrixAndManyEigenvaluesAndLargeStartingSpace) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitrarySparseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryDimension - 3);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryDimension - 3);

  OrthogonalDavidson davidson{arbitraryDimension - 3, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryDimension - 3);

  davidson.setSigmaVectorEvaluator(
      std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbitrarySparseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitrarySparseMatrix.diagonal()));
  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryDimension - 3; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithSparseMatrixAndManyEigenvalues) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitrarySparseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryNumberEigenvalue);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryNumberEigenvalue);

  OrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);
  davidson.setSigmaVectorEvaluator(
      std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbitrarySparseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitrarySparseMatrix.diagonal()));

  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryNumberEigenvalue; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryNumberEigenvalue; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeMatrixOfSizeOne) {
  Eigen::MatrixXd matrix(1, 1);
  matrix << 13.0;

  OrthogonalDavidson davidson{1, 1};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(matrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(matrix.diagonal()));

  auto result = davidson.solve(log);

  ASSERT_THAT(result.eigenValues(0), Eq(13.0));
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithSimultDiagBalancedMethod) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);

  NonOrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  davidson.settings().modifyString("gep_algo", "simultaneous_diag");

  auto result = davidson.solve(log);

  EXPECT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < arbitraryDimension; ++j) {
    EXPECT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithSimultDiagBalancedMethodAlmostSingular) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);
  Eigen::MatrixXd singularGuess(arbitraryDenseMatrix.rows(), 2);
  Eigen::VectorXd guessVector = Eigen::MatrixXd::Identity(arbitraryDenseMatrix.rows(), 1).col(0);
  singularGuess << guessVector + 1e-12 * Eigen::VectorXd::Random(arbitraryDenseMatrix.rows()),
      guessVector + 1e-12 * Eigen::VectorXd::Random(arbitraryDenseMatrix.rows());

  NonOrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  davidson.settings().modifyInt(initialGuessDimensionOption, 2);
  davidson.setGuess(singularGuess);
  davidson.settings().modifyString("gep_algo", "simultaneous_diag");

  auto result = davidson.solve(log);

  EXPECT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < arbitraryDimension; ++j) {
    EXPECT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithStandardBalancedMethod) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);

  NonOrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  davidson.settings().modifyString("gep_algo", "standard");

  auto result = davidson.solve(log);

  EXPECT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < arbitraryDimension; ++j) {
    EXPECT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithCholeskyBalancedMethod) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);

  NonOrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  davidson.settings().modifyString("gep_algo", "cholesky");

  auto result = davidson.solve(log);

  EXPECT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < arbitraryDimension; ++j) {
    EXPECT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, ThrowsIfNoCorrectGEPAlgorithmChosen) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);

  NonOrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  // "vattelappesca" is no valid gep_algo
  davidson.settings().modifyString("gep_algo", "vattelappesca");

  ASSERT_THROW(davidson.solve(log), InvalidDiagonalizerInputException);
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithBalancedStandardMethodAndManyEigenvaluesWithLargeStartingSpace) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryDimension - 3);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryDimension - 3);

  NonOrthogonalDavidson davidson{arbitraryDimension - 3, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryDimension - 3);
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryDimension - 3; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithBalancedStandardMethodAndManyEigenvalues) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryNumberEigenvalue);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryNumberEigenvalue);

  NonOrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryNumberEigenvalue; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithBalancedMethodSparseMatrix) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitrarySparseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(1);
  auto actualEigenvectors = ref.eigenvectors().leftCols(1);

  NonOrthogonalDavidson davidson{1, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(
      std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbitrarySparseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitrarySparseMatrix.diagonal()));
  auto result = davidson.solve(log);

  ASSERT_THAT(result.eigenValues(0), DoubleNear(actualEigenvalues(0), 1e-5));
  for (int j = 0; j < 1; ++j) {
    ASSERT_THAT(abs(result.eigenVectors(j, 0)), DoubleNear(abs(actualEigenvectors(j, 0)), 1e-5));
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithBalancedMethodSparseMatrixAndManyEigenvaluesAndLargeStartingSpace) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitrarySparseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryDimension - 3);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryDimension - 3);

  NonOrthogonalDavidson davidson{arbitraryDimension - 3, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryDimension - 3);
  davidson.setSigmaVectorEvaluator(
      std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbitrarySparseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitrarySparseMatrix.diagonal()));

  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryDimension - 3; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDiagonalizeWithBalancedMethodSparseMatrixAndManyEigenvalues) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitrarySparseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryNumberEigenvalue);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryNumberEigenvalue);

  NonOrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.setSigmaVectorEvaluator(
      std::make_unique<IndirectSigmaVectorEvaluator<Eigen::SparseMatrix<double>>>(arbitrarySparseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitrarySparseMatrix.diagonal()));
  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryNumberEigenvalue; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, BalancedMethodCanDiagonalizeMatrixOfSizeOne) {
  Eigen::MatrixXd matrix(1, 1);
  matrix << 13.0;

  OrthogonalDavidson davidson{1, 1};
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(matrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(matrix.diagonal()));
  auto result = davidson.solve(log);

  ASSERT_THAT(result.eigenValues(0), Eq(13.0));
}

TEST_F(ADavidsonDiagonalizerTest, SubspaceCollapserWorks) {
  Eigen::VectorXd arbitraryDiagonal(5);

  std::vector<int> notConvergedRoots{0, 1, 2, 3, 4};

  SubspaceCollapser collapser;
  collapser.setMaxSubspaceDimension(10);
  collapser.setEigenvaluesToCompute(5);

  // Subspace eigenvectors for 2 iterations. Only 5 roots are looked for.
  // Dimension of Ritz vectors is 30
  Eigen::MatrixXd eigenvectors1 = Eigen::MatrixXd::Random(30, 5);
  Eigen::MatrixXd eigenvectors2 = Eigen::MatrixXd::Random(30, 5);
  EigenContainer eigenpairs1, eigenpairs2;

  SubspaceOrthogonalizer::qrOrthogonalize(eigenvectors1, 5);
  SubspaceOrthogonalizer::qrOrthogonalize(eigenvectors2, 5);

  eigenpairs1.eigenVectors = eigenvectors1;
  eigenpairs1.eigenValues = arbitraryDiagonal;
  eigenpairs2.eigenVectors = eigenvectors2;
  eigenpairs2.eigenValues = arbitraryDiagonal;

  EXPECT_THAT(collapser.collapseNeeded(eigenpairs1, 9, notConvergedRoots), false);

  EXPECT_THAT(collapser.collapseNeeded(eigenpairs2, 10, notConvergedRoots), true);

  Eigen::MatrixXd collapsed = collapser.getCollapsedOrthogonalSubspace();

  EXPECT_EQ(collapsed.cols(), 10);
  EXPECT_EQ(collapsed.rows(), 30);

  for (int i = 0; i < 30; ++i) {
    for (int j = 0; j < 5; ++j) {
      EXPECT_DOUBLE_EQ(collapsed(i, j), eigenvectors2(i, j));
    }
  }

  // Test that iteration n and n-1 are orthogonal
  for (int i = 0; i < 5; ++i) {
    for (int j = 5; j < 10; ++j) {
      EXPECT_LE(collapsed.col(i).transpose() * collapsed.col(j), 1e-15);
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CorrectlyCalculatesNewIterations) {
  EXPECT_EQ(SubspaceCollapser::calculateSubspaceCollapserIterations(1, 0, 100), 80);
  EXPECT_EQ(SubspaceCollapser::calculateSubspaceCollapserIterations(20, 10, 200), 100);
  EXPECT_EQ(SubspaceCollapser::calculateSubspaceCollapserIterations(100, 0, 1000), 500);
  EXPECT_EQ(SubspaceCollapser::calculateSubspaceCollapserIterations(100, 0, 100), 100);
}

TEST_F(ADavidsonDiagonalizerTest, NonOrthogonalSubspaceCollapserWorks) {
  Eigen::VectorXd arbitraryDiagonal(5);

  std::vector<int> notConvergedRoots{0, 1, 2, 3, 4};

  SubspaceCollapser collapser;
  collapser.setMaxSubspaceDimension(10);
  collapser.setEigenvaluesToCompute(5);

  // Subspace eigenvectors for 2 iterations. Only 5 roots are looked for.
  // Dimension of Ritz vectors is 30
  Eigen::MatrixXd eigenvectors1 = Eigen::MatrixXd::Random(30, 5);
  Eigen::MatrixXd eigenvectors2 = Eigen::MatrixXd::Random(30, 5);
  EigenContainer eigenpairs1, eigenpairs2;

  SubspaceOrthogonalizer::qrOrthogonalize(eigenvectors1, 5);
  SubspaceOrthogonalizer::qrOrthogonalize(eigenvectors2, 5);

  eigenpairs1.eigenVectors = eigenvectors1;
  eigenpairs1.eigenValues = arbitraryDiagonal;
  eigenpairs2.eigenVectors = eigenvectors2;
  eigenpairs2.eigenValues = arbitraryDiagonal;

  EXPECT_THAT(collapser.collapseNeeded(eigenpairs1, 9, notConvergedRoots), false);

  EXPECT_THAT(collapser.collapseNeeded(eigenpairs2, 10, notConvergedRoots), true);

  Eigen::MatrixXd collapsed = collapser.getCollapsedNonOrthogonalSubspace();

  EXPECT_EQ(collapsed.cols(), 10);
  EXPECT_EQ(collapsed.rows(), 30);

  for (int i = 0; i < 30; ++i) {
    for (int j = 0; j < 5; ++j) {
      EXPECT_THAT(collapsed(i, j), DoubleNear(eigenvectors2(i, j), 1e-10));
    }
  }

  // Test that iteration n and n-1 are orthogonal
  for (int i = 0; i < 5; ++i) {
    for (int j = 5; j < 10; ++j) {
      EXPECT_LE(collapsed.col(i).transpose() * collapsed.col(j), 1e-15);
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, CanDoDavidsonWithSubsetCollapse) {
  Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ref(arbitraryDenseMatrix);
  auto actualEigenvalues = ref.eigenvalues().head(arbitraryNumberEigenvalue);
  auto actualEigenvectors = ref.eigenvectors().leftCols(arbitraryNumberEigenvalue);

  OrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);
  davidson.setSigmaVectorEvaluator(std::make_unique<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix));
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  davidson.settings().modifyInt(subspaceCollapseDimensionOption, 10);

  auto result = davidson.solve(log);

  for (int i = 0; i < arbitraryNumberEigenvalue; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(actualEigenvalues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(actualEigenvectors(j, i)), 1e-5));
    }
  }
  auto result2 = davidson.getEigenPairs();
  for (int i = 0; i < arbitraryNumberEigenvalue; ++i) {
    ASSERT_THAT(result.eigenValues(i), DoubleNear(result2.eigenValues(i), 1e-5));
    for (int j = 0; j < arbitraryDimension; ++j) {
      ASSERT_THAT(abs(result.eigenVectors(j, i)), DoubleNear(abs(result2.eigenVectors(j, i)), 1e-5));
    }
  }
}

TEST_F(ADavidsonDiagonalizerTest, IterativeDiagonalizerUtilitiesFunctions) {
  OrthogonalDavidson davidson{arbitraryNumberEigenvalue, arbitraryDimension};
  davidson.settings().modifyInt(initialGuessDimensionOption, arbitraryTrialSubspace);
  davidson.setPreconditionerEvaluator(std::make_unique<IndirectPreconditionerEvaluator>(arbitraryDenseMatrix.diagonal()));
  davidson.settings().modifyInt(subspaceCollapseDimensionOption, 10);
  ASSERT_THROW(davidson.solve(log), InvalidSigmaVectorEvaluator);

  auto sve = std::make_shared<IndirectSigmaVectorEvaluator<Eigen::MatrixXd>>(arbitraryDenseMatrix);
  davidson.setSigmaVectorEvaluator(sve);

  auto& sve2 = davidson.getSigmaVectorEvaluator();
  ASSERT_EQ(sve.get(), &sve2);

  davidson.settings().modifyInt(SettingsNames::maxDavidsonIterations, 0);
  ASSERT_THROW(davidson.solve(log), DiagonalizerNotConvergedException);

  InvalidSigmaVectorEvaluator testException;
  DiagonalizerNotConvergedException testException2;

  ASSERT_EQ(testException.what(),
            "Empty sigma vector evaluator or preconditioner for Davidson iterative diagonalizer.");
  ASSERT_EQ(testException2.what(), "Davidson could not converge.");

  const auto& setting = davidson.settings();
  ASSERT_EQ(setting.getInt(subspaceCollapseDimensionOption), davidson.settings().getInt(subspaceCollapseDimensionOption));

  ASSERT_THROW(davidson.setGuess(Eigen::MatrixXd(Eigen::MatrixXd::Identity(200, 200))), std::runtime_error);
}

TEST_F(ADavidsonDiagonalizerTest, TestDavidsonSettingsFunctions) {
  auto testException = InvalidDiagonalizerInputException("This is a test exception");
  ASSERT_EQ(std::string(testException.what()), "Input error: This is a test exception");
  auto settings = DiagonalizerSettings(100, 1000);
  settings.modifyInt(numberOfRootsOption, -1);
  ASSERT_THROW(settings.check(1000), InvalidDiagonalizerInputException);
  ASSERT_THROW(DiagonalizerSettings(1000, 100), InvalidDiagonalizerInputException);
  auto settings2 = DiagonalizerSettings(100, 1000);
  settings2.modifyInt(initialGuessDimensionOption, 2000);
  ASSERT_THROW(settings2.check(1000), InvalidDiagonalizerInputException);
}

} // namespace Utils
} // namespace Scine


/* ----------------------------------------------------------------------------

 * GTSAM Copyright 2010, Georgia Tech Research Corporation,
 * Atlanta, Georgia 30332-0415
 * All Rights Reserved
 * Authors: Frank Dellaert, et al. (see THANKS for the full author list)

 * See LICENSE for the license information

 * -------------------------------------------------------------------------- */

/**
 * @file testGaussianJunctionTreeB.cpp
 * @date Jul 8, 2010
 * @author nikai
 */

#include <tests/smallExample.h>
#include <gtsam/sam/BearingRangeFactor.h>
#include <gtsam/slam/BetweenFactor.h>
#include <gtsam/slam/PriorFactor.h>
#include <gtsam/geometry/Pose2.h>
#include <gtsam/nonlinear/NonlinearFactorGraph.h>
#include <gtsam/nonlinear/Values.h>
#include <gtsam/linear/GaussianBayesNet.h>
#include <gtsam/linear/GaussianConditional.h>
#include <gtsam/linear/GaussianFactor.h>
#include <gtsam/linear/GaussianFactorGraph.h>
#include <gtsam/linear/GaussianEliminationTree.h>
#include <gtsam/linear/GaussianJunctionTree.h>
#include <gtsam/linear/HessianFactor.h>
#include <gtsam/linear/JacobianFactor.h>
#include <gtsam/linear/NoiseModel.h>
#include <gtsam/linear/VectorValues.h>
#include <gtsam/symbolic/SymbolicEliminationTree.h>
#include <gtsam/inference/BayesTree.h>
#include <gtsam/inference/ClusterTree.h>
#include <gtsam/inference/Ordering.h>
#include <gtsam/inference/Symbol.h>
#include <gtsam/base/Matrix.h>
#include <gtsam/base/Testable.h>

#include <CppUnitLite/TestHarness.h>

#include <memory>
#include <boost/tuple/tuple.hpp>
#include <boost/assign/std/vector.hpp>

#include <cmath>
#include <list>
#include <utility>
#include <vector>

using namespace boost::assign;

#include <iostream>

using namespace std;
using namespace gtsam;
using namespace example;

using symbol_shorthand::X;
using symbol_shorthand::L;

/* ************************************************************************* *
 Bayes tree for smoother with "nested dissection" ordering:
 C1     x5 x6 x4
 C2      x3 x2 : x4
 C3        x1 : x2
 C4      x7 : x6
 */
TEST( GaussianJunctionTreeB, constructor2 ) {
  // create a graph
  NonlinearFactorGraph nlfg;
  Values values;
  boost::tie(nlfg, values) = createNonlinearSmoother(7);
  SymbolicFactorGraph::shared_ptr symbolic = nlfg.symbolic();

  // linearize
  GaussianFactorGraph::shared_ptr fg = nlfg.linearize(values);

  Ordering ordering;
  ordering += X(1), X(3), X(5), X(7), X(2), X(6), X(4);

  // create an ordering
  GaussianEliminationTree etree(*fg, ordering);
  SymbolicEliminationTree stree(*symbolic, ordering);
  GaussianJunctionTree actual(etree);

  Ordering o324;
  o324 += X(3), X(2), X(4);
  Ordering o56;
  o56 += X(5), X(6);
  Ordering o7;
  o7 += X(7);
  Ordering o1;
  o1 += X(1);

  // Can no longer test these:
//  Ordering sep1;
//  Ordering sep2; sep2 += X(4);
//  Ordering sep3; sep3 += X(6);
//  Ordering sep4; sep4 += X(2);

  GaussianJunctionTree::sharedNode x324 = actual.roots().front();
  LONGS_EQUAL(2, x324->children.size());
  GaussianJunctionTree::sharedNode x1 = x324->children.front();
  GaussianJunctionTree::sharedNode x56 = x324->children.back();
  if (x1->children.size() > 0)
    x1.swap(x56); // makes it work with different tie-breakers

  LONGS_EQUAL(0, x1->children.size());
  LONGS_EQUAL(1, x56->children.size());
  GaussianJunctionTree::sharedNode x7 = x56->children[0];
  LONGS_EQUAL(0, x7->children.size());

  EXPECT(assert_equal(o324, x324->orderedFrontalKeys));
  EXPECT_LONGS_EQUAL(5, x324->factors.size());
  EXPECT_LONGS_EQUAL(9, x324->problemSize_);

  EXPECT(assert_equal(o56, x56->orderedFrontalKeys));
  EXPECT_LONGS_EQUAL(4, x56->factors.size());
  EXPECT_LONGS_EQUAL(9, x56->problemSize_);

  EXPECT(assert_equal(o7, x7->orderedFrontalKeys));
  EXPECT_LONGS_EQUAL(2, x7->factors.size());
  EXPECT_LONGS_EQUAL(4, x7->problemSize_);

  EXPECT(assert_equal(o1, x1->orderedFrontalKeys));
  EXPECT_LONGS_EQUAL(2, x1->factors.size());
  EXPECT_LONGS_EQUAL(4, x1->problemSize_);
}

///* ************************************************************************* */
//TEST( GaussianJunctionTreeB, optimizeMultiFrontal )
//{
//  // create a graph
//  GaussianFactorGraph fg;
//  Ordering ordering;
//  boost::tie(fg,ordering) = createSmoother(7);
//
//  // optimize the graph
//  GaussianJunctionTree tree(fg);
//  VectorValues actual = tree.optimize(&EliminateQR);
//
//  // verify
//  VectorValues expected(vector<size_t>(7,2)); // expected solution
//  Vector v = Vector2(0., 0.);
//  for (int i=1; i<=7; i++)
//    expected[ordering[X(i)]] = v;
//  EXPECT(assert_equal(expected,actual));
//}
//
///* ************************************************************************* */
//TEST( GaussianJunctionTreeB, optimizeMultiFrontal2)
//{
//  // create a graph
//  example::Graph nlfg = createNonlinearFactorGraph();
//  Values noisy = createNoisyValues();
//  Ordering ordering; ordering += X(1),X(2),L(1);
//  GaussianFactorGraph fg = *nlfg.linearize(noisy, ordering);
//
//  // optimize the graph
//  GaussianJunctionTree tree(fg);
//  VectorValues actual = tree.optimize(&EliminateQR);
//
//  // verify
//  VectorValues expected = createCorrectDelta(ordering); // expected solution
//  EXPECT(assert_equal(expected,actual));
//}
//
///* ************************************************************************* */
//TEST(GaussianJunctionTreeB, slamlike) {
//  Values init;
//  NonlinearFactorGraph newfactors;
//  NonlinearFactorGraph fullgraph;
//  SharedDiagonal odoNoise = noiseModel::Diagonal::Sigmas((Vector(3) << 0.1, 0.1, M_PI/100.0));
//  SharedDiagonal brNoise = noiseModel::Diagonal::Sigmas((Vector(2) << M_PI/100.0, 0.1));
//
//  size_t i = 0;
//
//  newfactors = NonlinearFactorGraph();
//  newfactors.add(PriorFactor<Pose2>(X(0), Pose2(0.0, 0.0, 0.0), odoNoise));
//  init.insert(X(0), Pose2(0.01, 0.01, 0.01));
//  fullgraph.push_back(newfactors);
//
//  for( ; i<5; ++i) {
//    newfactors = NonlinearFactorGraph();
//    newfactors.add(BetweenFactor<Pose2>(X(i), X(i+1), Pose2(1.0, 0.0, 0.0), odoNoise));
//    init.insert(X(i+1), Pose2(double(i+1)+0.1, -0.1, 0.01));
//    fullgraph.push_back(newfactors);
//  }
//
//  newfactors = NonlinearFactorGraph();
//  newfactors.add(BetweenFactor<Pose2>(X(i), X(i+1), Pose2(1.0, 0.0, 0.0), odoNoise));
//  newfactors.add(BearingRangeFactor<Pose2,Point2>(X(i), L(0), Rot2::fromAngle(M_PI/4.0), 5.0, brNoise));
//  newfactors.add(BearingRangeFactor<Pose2,Point2>(X(i), L(1), Rot2::fromAngle(-M_PI/4.0), 5.0, brNoise));
//  init.insert(X(i+1), Pose2(1.01, 0.01, 0.01));
//  init.insert(L(0), Point2(5.0/sqrt(2.0), 5.0/sqrt(2.0)));
//  init.insert(L(1), Point2(5.0/sqrt(2.0), -5.0/sqrt(2.0)));
//  fullgraph.push_back(newfactors);
//  ++ i;
//
//  for( ; i<5; ++i) {
//    newfactors = NonlinearFactorGraph();
//    newfactors.add(BetweenFactor<Pose2>(X(i), X(i+1), Pose2(1.0, 0.0, 0.0), odoNoise));
//    init.insert(X(i+1), Pose2(double(i+1)+0.1, -0.1, 0.01));
//    fullgraph.push_back(newfactors);
//  }
//
//  newfactors = NonlinearFactorGraph();
//  newfactors.add(BetweenFactor<Pose2>(X(i), X(i+1), Pose2(1.0, 0.0, 0.0), odoNoise));
//  newfactors.add(BearingRangeFactor<Pose2,Point2>(X(i), L(0), Rot2::fromAngle(M_PI/4.0 + M_PI/16.0), 4.5, brNoise));
//  newfactors.add(BearingRangeFactor<Pose2,Point2>(X(i), L(1), Rot2::fromAngle(-M_PI/4.0 + M_PI/16.0), 4.5, brNoise));
//  init.insert(X(i+1), Pose2(6.9, 0.1, 0.01));
//  fullgraph.push_back(newfactors);
//  ++ i;
//
//  // Compare solutions
//  Ordering ordering = *fullgraph.orderingCOLAMD(init);
//  GaussianFactorGraph linearized = *fullgraph.linearize(init, ordering);
//
//  GaussianJunctionTree gjt(linearized);
//  VectorValues deltaactual = gjt.optimize(&EliminateQR);
//  Values actual = init.retract(deltaactual, ordering);
//
//  GaussianBayesNet gbn = *GaussianSequentialSolver(linearized).eliminate();
//  VectorValues delta = optimize(gbn);
//  Values expected = init.retract(delta, ordering);
//
//  EXPECT(assert_equal(expected, actual));
//}
//
///* ************************************************************************* */
//TEST(GaussianJunctionTreeB, simpleMarginal) {
//
//  typedef BayesTree<GaussianConditional> GaussianBayesTree;
//
//  // Create a simple graph
//  NonlinearFactorGraph fg;
//  fg.add(PriorFactor<Pose2>(X(0), Pose2(), noiseModel::Isotropic::Sigma(3, 10.0)));
//  fg.add(BetweenFactor<Pose2>(X(0), X(1), Pose2(1.0, 0.0, 0.0), noiseModel::Diagonal::Sigmas(Vector3(10.0, 1.0, 1.0))));
//
//  Values init;
//  init.insert(X(0), Pose2());
//  init.insert(X(1), Pose2(1.0, 0.0, 0.0));
//
//  Ordering ordering;
//  ordering += X(1), X(0);
//
//  GaussianFactorGraph gfg = *fg.linearize(init, ordering);
//
//  // Compute marginals with both sequential and multifrontal
//  Matrix expected = GaussianSequentialSolver(gfg).marginalCovariance(1);
//
//  Matrix actual1 = GaussianMultifrontalSolver(gfg).marginalCovariance(1);
//
//  // Compute marginal directly from marginal factor
//  GaussianFactor::shared_ptr marginalFactor = GaussianMultifrontalSolver(gfg).marginalFactor(1);
//  JacobianFactor::shared_ptr marginalJacobian = std::dynamic_pointer_cast<JacobianFactor>(marginalFactor);
//  Matrix actual2 = inverse(marginalJacobian->getA(marginalJacobian->begin()).transpose() * marginalJacobian->getA(marginalJacobian->begin()));
//
//  // Compute marginal directly from BayesTree
//  GaussianBayesTree gbt;
//  gbt.insert(GaussianJunctionTree(gfg).eliminate(EliminateCholesky));
//  marginalFactor = gbt.marginalFactor(1, EliminateCholesky);
//  marginalJacobian = std::dynamic_pointer_cast<JacobianFactor>(marginalFactor);
//  Matrix actual3 = inverse(marginalJacobian->getA(marginalJacobian->begin()).transpose() * marginalJacobian->getA(marginalJacobian->begin()));
//
//  EXPECT(assert_equal(expected, actual1));
//  EXPECT(assert_equal(expected, actual2));
//  EXPECT(assert_equal(expected, actual3));
//}

/* ************************************************************************* */
int main() {
  TestResult tr;
  return TestRegistry::runAllTests(tr);
}
/* ************************************************************************* */


#include "Adder6D.h"

#include "factory.hpp"
#include <boost/numeric/ublas/assignment.hpp>
#include <boost/format.hpp>

REGISTER_SYSTEM(Adder6D);
using namespace std;

string Adder6D::getDocs() {
	return string(
		"System that simply adds two vectors together\n\n"
		);
}

Adder6D::Adder6D(void) :
	_inputs(MAX_INPUTS, pysim::vector::Zero(6)),
	out(pysim::vector::Zero(6))
{
	int i = 0;
	for (auto iter = _inputs.begin(); iter != _inputs.end(); ++iter) {
		pysim::vector* p = &(*iter);
		std::string name = str(boost::format("input_%1%") % i++);
		inputs.add(name.c_str(), p, "");
	}

	OUTPUT(out, "Result of the addition")
}

void Adder6D::doStep(double time) {
	out << 0, 0, 0, 0, 0, 0;
	for (auto iter = _inputs.begin(); iter != _inputs.end(); ++iter) {
		out += *iter;
	}
}


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

#include <boost/hana/equal.hpp>
#include <boost/hana/mult.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/zip_with.hpp>
namespace hana = boost::hana;

static_assert(
    hana::zip_with(hana::mult, hana::make_tuple(1, 2, 3, 4), hana::make_tuple(5, 6, 7, 8))
    ==
    hana::make_tuple(5, 12, 21, 32)
, "");

int main() { }


//==================================================================================================
/*!

  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/function/scalar/ifnot_minus.hpp>
#include <scalar_test.hpp>
#include <boost/simd/detail/dispatch/meta/as_integer.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>
#include <boost/simd/logical.hpp>
STF_CASE_TPL (" ifnot_minus real",  STF_IEEE_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::ifnot_minus;
  using bs::logical;
  using r_t = decltype(ifnot_minus(T(),T(),T()));

  // return type conformity test
  STF_TYPE_IS( r_t, T );

  // specific values tests
  STF_EQUAL(ifnot_minus(T(0),T(1),T(2)), T(-1));
  STF_EQUAL(ifnot_minus(T(1),T(1),T(2)), T(1));
  STF_EQUAL(ifnot_minus(T(1),bs::Inf<T>(),bs::Inf<T>()), bs::Inf<r_t>());
  STF_IEEE_EQUAL(ifnot_minus(T(0),bs::Minf<T>(),bs::Minf<T>()), bs::Nan<r_t>());
  STF_IEEE_EQUAL(ifnot_minus(T(0),bs::Nan<T>(),bs::Nan<T>()), bs::Nan<r_t>());
  STF_EQUAL(ifnot_minus(T(0),bs::Zero<T>(),bs::One<T>()), bs::Mone<r_t>());
} // end of test for floating_

STF_CASE_TPL (" ifnot_minus integer",  STF_INTEGRAL_TYPES)
{
  namespace bs = boost::simd;
  namespace bd = boost::dispatch;
  using bs::ifnot_minus;
  using bs::logical;
  using r_t = decltype(ifnot_minus(T(),T(),T()));

  // return type conformity test
  STF_TYPE_IS( r_t, T );

  // specific values tests
  STF_EQUAL(ifnot_minus(logical<T>(T(-1)),T(1),T(2)), T(1));
  STF_EQUAL(ifnot_minus(logical<T>(T(0)),T(2),T(1)), T(1));
  STF_EQUAL(ifnot_minus(logical<T>(bs::Zero<T>()),bs::Zero<T>(),bs::Zero<T>()), bs::Zero<r_t>());
  STF_EQUAL(ifnot_minus(T(-1),T(1),T(2)), T(1));
  STF_EQUAL(ifnot_minus(T(0),T(2),T(1)), T(1));
  STF_EQUAL(ifnot_minus(bs::Zero<T>(),bs::Zero<T>(),bs::Zero<T>()), bs::Zero<r_t>());
} // end of test for integer_


#include <iostream>                    // std::flush
#include <fstream>                     // std::ofstream
#include <string>                      // std::string
#include <tuple>                       // std::tuple
#include <vector>                      // std::vector
// For External Library
#include <torch/torch.h>               // torch
#include <boost/program_options.hpp>   // boost::program_options
// For Original Header
#include "loss.hpp"                    // Loss
#include "networks.hpp"                // ConvolutionalAutoEncoder
#include "dataloader.hpp"              // DataLoader::ImageFolderWithPaths
#include "visualizer.hpp"              // visualizer::graph

// Define Namespace
namespace po = boost::program_options;

// -------------------
// Validation Function
// -------------------
void valid(po::variables_map &vm, DataLoader::ImageFolderPairWithPaths &valid_dataloader, torch::Device &device, Loss &criterion, ConvolutionalAutoEncoder &model, const size_t epoch, visualizer::graph &writer){

    // (0) Initialization and Declaration
    size_t iteration;
    float ave_loss, total_loss;
    std::ofstream ofs;
    std::tuple<torch::Tensor, torch::Tensor, std::vector<std::string>, std::vector<std::string>> mini_batch;
    torch::Tensor loss, imageI, imageO, output;

    // (1) Tensor Forward per Mini Batch
    model->eval();
    iteration = 0;
    total_loss = 0.0;
    while (valid_dataloader(mini_batch)){
        imageI = std::get<0>(mini_batch).to(device);
        imageO = std::get<1>(mini_batch).to(device);
        output = model->forward(imageI);
        loss = criterion(output, imageO);
        total_loss += loss.item<float>();
        iteration++;
    }

    // (2) Calculate Average Loss
    ave_loss = total_loss / (float)iteration;

    // (3.1) Record Loss (Log)
    ofs.open("checkpoints/" + vm["dataset"].as<std::string>() + "/log/valid.txt", std::ios::app);
    ofs << "epoch:" << epoch << '/' << vm["epochs"].as<size_t>() << ' ' << std::flush;
    ofs << "rec:" << ave_loss << std::endl;
    ofs.close();

    // (3.2) Record Loss (Graph)
    writer.plot(/*base=*/epoch, /*value=*/{ave_loss});

    // End Processing
    return;

}


#ifndef _MLES_QLEARNING_HPP_
#define _MLES_QLEARNING_HPP_

#include <Eigen/Dense>
#include <map>
#include <string>
#include "Training.hpp"

namespace mles
{
    class QEnv
    {
        public:
            QEnv();
            virtual ~QEnv();
            virtual void toTrain();
            virtual void toTest();
            virtual int getNumStates();
            virtual int getNumActions();
            virtual int getAction();
            virtual int reset();
            virtual bool ready();
            virtual void onEpoch(int epoch);
            virtual bool step(int action, int& nextState, double& reward);
            virtual void onDone();
    };

    class QLearning
    {
        private:
            QEnv *env;
            Eigen::MatrixXd Q;
            double explorationFactor;
            double discountFactor;
            double exploitationFactor;
        public:
            QLearning(QEnv* env);
            virtual ~QLearning();

            void setDiscountFactor(double factor);
            void setExplorationFactor(double factor);
            void setExploitationFactor(double factor);

            void train(const TrainingSettings& settings);
            void run();

            bool load(const std::string& fileName);
            bool save(const std::string& fileName);

    };
}

#endif


//  Copyright John Maddock 2017.
//  Use, modification and distribution are subject to 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)
//
// Basic sanity check that header <boost/math/tools/config.hpp>
// #includes all the files that it needs to.
//
#include <boost/math/common_factor_rt.hpp>
//
// Note this header includes no other headers, this is
// important if this test is to be meaningful:
//
#include "test_compile_result.hpp"

inline void check_result_imp(std::pair<unsigned long, unsigned long*>, std::pair<unsigned long, unsigned long*>) {}

void compile_and_link_test()
{
   int i(3), j(4);
   check_result<int>(boost::math::gcd(i, j));
   check_result<int>(boost::math::lcm(i, j));

   unsigned long arr[] = { 45, 56, 99, 101 };
   check_result<std::pair<unsigned long, unsigned long*> >(boost::math::gcd_range(&arr[0], &arr[0] + 4));

}


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

  @copyright 2015 NumScale SAS
  @copyright 2015 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_SQRS_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SCALAR_FUNCTION_SQRS_HPP_INCLUDED

#include <boost/simd/constant/sqrtvalmax.hpp>
#include <boost/simd/constant/valmax.hpp>
#include <boost/simd/function/scalar/abss.hpp>
#include <boost/simd/function/scalar/sqr.hpp>
#include <boost/dispatch/function/overload.hpp>
#include <boost/config.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  BOOST_DISPATCH_OVERLOAD ( sqrs_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_< bd::int_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      return abss(a0) > Sqrtvalmax<A0>() ? Valmax<A0>() : sqr(a0);
    }
  };

  BOOST_DISPATCH_OVERLOAD ( sqrs_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_< bd::uint_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      return a0 > Sqrtvalmax<A0>() ? Valmax<A0>() : sqr(a0);
    }
  };

  BOOST_DISPATCH_OVERLOAD ( sqrs_
                          , (typename A0)
                          , bd::cpu_
                          , bd::scalar_< bd::floating_<A0> >
                          )
  {
    BOOST_FORCEINLINE A0 operator() ( A0 a0) const BOOST_NOEXCEPT
    {
      return sqr(a0);
    }
  };
} } }

#endif


#include <gtest/gtest.h>
#include <KnowledgeBase.h>
#include <MetaFactory.h>
#include <Transformations.h>

#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
namespace test {

 namespace fs = boost::filesystem;
using namespace ::id::attribute;
using namespace ::id::type;

using std::to_string;

using TransPtr    = Transformation::TransPtr;
using Events      = Transformer::Events;
using EventTypes  = Transformer::EventTypes;

struct TransformationTestSuite : public ::testing::Test{
  MetaFactory& f  = MetaFactory::instance();
  MetaEvent    in, out;
  EventType    inT, outT;

  TransformationTestSuite()  {
    MetaAttribute s(Time::value());
    s.unit() = Second();
    in.add(s);
    out.add(s);
    KnowledgeBase::clear();
    KnowledgeBase::registerTransformation(cast);
    KnowledgeBase::registerTransformation(rescale);
  }

  void registerTypes() {
    inT = (EventType)in;
    outT = (EventType)out;
    KnowledgeBase::registerEventType(inT);
  }
};

TEST_F(TransformationTestSuite, scaleTransformBasicTest) {
  MetaAttribute& inA  = *in.attribute(Time::value());
  MetaAttribute& outA = *out.attribute(Time::value());
  inA.scale() = MetaScale(Scale<std::milli>());
  inA.value() = MetaValue({{{1234, 0}}});
  outA.value() = MetaValue({{{1.234, 0.0001}}});
  registerTypes();
  auto transList=KnowledgeBase::findTransforms(outT);
  ASSERT_GT(transList.size(), 0U) << "No Transform found!";
  size_t i=0;
  for(const CompositeTransformation& t : transList) {
    fs::path file = fs::current_path()/"doc"/("basicScale"+to_string(i++)+".dot");
    fs::ofstream out(file);
    out << t;
  }
  ASSERT_LE(transList.size(), 1U);
  TransPtr scaleT = transList.front().create(AbstractPolicy());
  ASSERT_NE(scaleT, nullptr);
  EXPECT_TRUE(scaleT->check(in)) << "MetaValue is not supported by ScaleTransform";
  Events result = (*scaleT)(in);
  ASSERT_GE(result.size(), 1U) << "No Events generated!";
  MetaAttribute* resA = result.front().attribute(Time::value());
  ASSERT_NE(resA, nullptr);
  EXPECT_EQ(resA->value().get(0,0).value(), outA.value().get(0,0).value()) <<  resA->value() << " != " << outA.value();
  EXPECT_LE(resA->value().get(0,0).uncertainty(), outA.value().get(0,0).uncertainty()) <<  resA->value() << " != " << outA.value();
}

TEST_F(TransformationTestSuite, typeTransformBasicTest) {
  MetaAttribute& inA  = *in.attribute(Time::value());
  MetaAttribute& outA = *out.attribute(Time::value());
  inA.value() = MetaValue({{{1234.5, 0}}}, Float::value());
  outA.value() = MetaValue({{{1234, 1}}}, UInt32::value());
  registerTypes();
  auto transList = KnowledgeBase::findTransforms(outT);
  ASSERT_GT(transList.size(), 0U) << "No Transform found!";
  size_t i=0;
  for(const CompositeTransformation& t : transList) {
    fs::path file = fs::current_path()/"doc"/("basicType"+to_string(i++)+".dot");
    fs::ofstream out(file);
    out << t;
  }

  ASSERT_LE(transList.size(), 1U);
  TransPtr typeT = transList.front().create(AbstractPolicy());
  ASSERT_NE(typeT, nullptr);
  EXPECT_TRUE(typeT->check(in)) << "MetaValue is not supported by TypeTransform";
  Events result = (*typeT)(in);
  ASSERT_GE(result.size(), 1U) << "No Events generated!";
  MetaAttribute* resA = result.front().attribute(Time::value());
  ASSERT_NE(resA, nullptr);
  EXPECT_EQ(resA->value().get(0,0).value(), outA.value().get(0,0).value()) <<  resA->value() << " != " << outA.value();
  EXPECT_EQ(resA->value().get(0,0).uncertainty(), outA.value().get(0,0).uncertainty()) <<  resA->value() << " != " << outA.value();
}

TEST_F(TransformationTestSuite, castedRescaleTest) {
  MetaAttribute& inA  = *in.attribute(Time::value());
  MetaAttribute& outA = *out.attribute(Time::value());
  inA.scale() = MetaScale(Scale<std::milli>());
  inA.value() = MetaValue({{{1234.5, 0}}}, Float::value());
  outA.value() = MetaValue({{{1, 1}}}, UInt32::value());
  registerTypes();
  auto transList = KnowledgeBase::findTransforms(outT);
  EXPECT_GE(transList.size(), 1U);
  size_t i=0;
  for(const CompositeTransformation& t : transList) {
    fs::path file = fs::current_path()/"doc"/("scaleType"+to_string(i++)+".dot");
    fs::ofstream out(file);
    out << t;
    TransPtr typeT = t.create(AbstractPolicy());
    ASSERT_NE(typeT, nullptr);
    EXPECT_TRUE(typeT->check(in)) << "MetaValue is not supported by TypeTransform";
    Events result = (*typeT)(in);
    ASSERT_GE(result.size(), 1U) << "No Events generated!";
    MetaAttribute* resA = result.front().attribute(Time::value());
    ASSERT_NE(resA, nullptr);
    EXPECT_EQ(resA->value().rows(), 1U);
    EXPECT_EQ(resA->value().cols(), 1U);
    EXPECT_EQ(resA->value().get(0,0).value(), outA.value().get(0,0).value()) <<  resA->value() << " != " << outA.value();
    EXPECT_EQ(resA->value().get(0,0).uncertainty(), outA.value().get(0,0).uncertainty()) <<  resA->value() << " != " << outA.value();
  }
}

}


#define BOOST_TEST_MODULE cypher_suites
#include <boost/test/unit_test.hpp>

#include <fc/crypto/public_key.hpp>
#include <fc/crypto/private_key.hpp>
#include <fc/crypto/signature.hpp>
#include <fc/utility.hpp>

using namespace fc::crypto;
using namespace fc;

BOOST_AUTO_TEST_SUITE(cypher_suites)
BOOST_AUTO_TEST_CASE(test_k1) try {
   auto private_key_string = std::string("5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3");
   auto expected_public_key = std::string("DFC6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV");
   auto test_private_key = private_key(private_key_string);
   auto test_public_key = test_private_key.get_public_key();

   BOOST_CHECK_EQUAL(private_key_string, std::string(test_private_key));
   BOOST_CHECK_EQUAL(expected_public_key, std::string(test_public_key));
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_CASE(test_r1) try {
   auto private_key_string = std::string("PVT_R1_iyQmnyPEGvFd8uffnk152WC2WryBjgTrg22fXQryuGL9mU6qW");
   auto expected_public_key = std::string("PUB_R1_6EPHFSKVYHBjQgxVGQPrwCxTg7BbZ69H9i4gztN9deKTEXYne4");
   auto test_private_key = private_key(private_key_string);
   auto test_public_key = test_private_key.get_public_key();

   BOOST_CHECK_EQUAL(private_key_string, std::string(test_private_key));
   BOOST_CHECK_EQUAL(expected_public_key, std::string(test_public_key));
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_CASE(test_k1_recovery) try {
   auto payload = "Test Cases";
   auto digest = sha256::hash(payload, const_strlen(payload));
   auto key = private_key::generate<ecc::private_key_shim>();
   auto pub = key.get_public_key();
   auto sig = key.sign(digest);

   auto recovered_pub = public_key(sig, digest);
   std::cout << recovered_pub << std::endl;

   BOOST_CHECK_EQUAL(std::string(recovered_pub), std::string(pub));
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_CASE(test_r1_recovery) try {
   auto payload = "Test Cases";
   auto digest = sha256::hash(payload, const_strlen(payload));
   auto key = private_key::generate<r1::private_key_shim>();
   auto pub = key.get_public_key();
   auto sig = key.sign(digest);

   auto recovered_pub = public_key(sig, digest);
   std::cout << recovered_pub << std::endl;

   BOOST_CHECK_EQUAL(std::string(recovered_pub), std::string(pub));
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_CASE(test_k1_recyle) try {
   auto key = private_key::generate<ecc::private_key_shim>();
   auto pub = key.get_public_key();
   auto pub_str = std::string(pub);
   auto recycled_pub = public_key(pub_str);

   std::cout << pub << " -> " << recycled_pub << std::endl;

   BOOST_CHECK_EQUAL(std::string(pub), std::string(recycled_pub));
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_CASE(test_r1_recyle) try {
   auto key = private_key::generate<r1::private_key_shim>();
   auto pub = key.get_public_key();
   auto pub_str = std::string(pub);
   auto recycled_pub = public_key(pub_str);

   std::cout << pub << " -> " << recycled_pub << std::endl;

   BOOST_CHECK_EQUAL(std::string(pub), std::string(recycled_pub));
} FC_LOG_AND_RETHROW();

BOOST_AUTO_TEST_SUITE_END()


#include "detail/random.hpp"
#include "observation.hpp"
#include "sampling/simple.hpp"

#include <boost/foreach.hpp>
#include <boost/random.hpp>
#include <boost/shared_ptr.hpp>
#include <cmath>
#include <ctime>
#include <Eigen/Cholesky>
#include <Eigen/Dense>
#include <limits>

extern "C" {
#include "../third-party/gen_beta.h"
}

#include "detail/random.hpp"

namespace biggles { namespace sampling
{

using detail::random_generator;

int uniform_int(int first, int last)
{
    BOOST_ASSERT(last > first);
    boost::uniform_int<> dist(first, last-1); // <- Oh, FFS boost::random. Really?
    boost::variate_generator<boost::mt19937&, boost::uniform_int<> > uni(detail::random_generator, dist);
    return uni();
}

float uniform_real(float first, float last)
{
    BOOST_ASSERT(last > first);
    boost::uniform_real<float> dist(first, last);
    boost::variate_generator<boost::mt19937&, boost::uniform_real<float> > uni(detail::random_generator, dist);
    return uni();
}

float exponential(float lambda)
{
    return -log(uniform_real()) / lambda;
}

int poisson(float lambda)
{
    // Knuth's algrotihsm
    float L(exp(-lambda)), p(1.f);
    int k(0);
    do {
        ++k;
        p *= uniform_real();
    } while(p > L);
    return k-1;
}

/*
bool observation_from_light_cone(const observation_collection& observations,
                                        const observation& start,
                                        float speed_of_light,
                                        time_stamp first_time_stamp,
                                        time_stamp last_time_stamp,
                                        observation& output_obs,
                                        float& output_log_prob)
{
    BOOST_ASSERT(first_time_stamp <= last_time_stamp);

    // early-out if given a zero-sized time range
    if(first_time_stamp == last_time_stamp)
        return false;

    // find candidate observations
    observation_collection candidates;
    std::set<time_stamp> time_stamp_with_cand;
    observations_within_light_cone(observations,
                                   start,
                                   speed_of_light,
                                   first_time_stamp, last_time_stamp,
                                   time_stamp_with_cand,
                                   std::inserter(candidates, candidates.begin()));

    // did we find any?
    if(candidates.empty())
    {
        // no, return failure
        return false;
    }

    // which time stamp do we choose?
    std::set<time_stamp>::const_iterator ts_iter = time_stamp_with_cand.begin();
    std::advance(ts_iter, uniform_int(0, time_stamp_with_cand.size()));
    float time_stamp_log_prob = -logf(static_cast<float>(time_stamp_with_cand.size()));

    // what candidates are available to us?
    observation_collection::const_range refined_range(candidates.at_time_stamp(*ts_iter));

    // does that leave us with any to choose from?
    BOOST_ASSERT((refined_range.first != refined_range.second));

    // _finally_ let's sample an observation
    float refined_log_prob(1.f);
    output_obs = *from_range(refined_range.first, refined_range.second, refined_log_prob);
    BOOST_ASSERT(refined_log_prob <= 0.f);

    // update the overall log prob
    output_log_prob = time_stamp_log_prob + refined_log_prob;

    return true;
}
*/

float log_prob_of_sampling_from_light_cone(const observation& o,
                                           const observation_collection& observations,
                                           const observation& start,
                                           float speed_of_light,
                                           time_stamp first_time_stamp,
                                           time_stamp last_time_stamp)
{
    BOOST_ASSERT(first_time_stamp <= last_time_stamp);
    BOOST_ASSERT(observations.find(o) != observations.end());

    // early-out if given a zero-sized time range
    if(first_time_stamp == last_time_stamp)
        return -std::numeric_limits<float>::max();

    // early-out if observation is outside of time cone
    if(!observation_is_within_light_cone(o, start, speed_of_light, first_time_stamp, last_time_stamp))
        return -std::numeric_limits<float>::max();

    // the observation *is* somewhere in the light cone

    // find candidate observations
    observation_collection candidates;
    std::set<time_stamp> time_stamp_with_cand;
    observations_within_light_cone(observations,
                                   start,
                                   speed_of_light,
                                   first_time_stamp, last_time_stamp,
                                   time_stamp_with_cand,
                                   std::inserter(candidates, candidates.begin()));
    BOOST_ASSERT(candidates.find(o) != candidates.end());

    // log prob of having sampled the time stamp
    float time_stamp_log_prob = -logf(time_stamp_with_cand.size());

    // the log prob. of sampling the observation
    size_t n_candidates_at_t(candidates.count_at_time_stamp(t(o)));
    BOOST_ASSERT(n_candidates_at_t > 0);
    float refined_log_prob = -logf(static_cast<float>(n_candidates_at_t));

    return time_stamp_log_prob + refined_log_prob;
}

bool observation_from_collection(const observation_collection& oc, observation& output_obs, float& output_log_prob)
{
    throw std::logic_error("this function is no longer in use");
    // early out if we cannot sample
    if(oc.empty())
        return false;

    std::set<time_stamp> ts_with_obs;

    for (observation_collection::const_iterator iter = oc.begin(); iter != oc.end(); ++iter)
        ts_with_obs.insert(t(*iter));

    // choose a time stamp to sample an observation from
    std::set<time_stamp>::iterator b = ts_with_obs.begin();
    std::advance(b, uniform_int(0, ts_with_obs.size()));
    time_stamp ts = *b;
    float log_ts_prob = -logf(ts_with_obs.size());
    /*
    time_stamp ts(uniform_int(oc.first_time_stamp(), oc.last_time_stamp()));
    float log_ts_prob = -logf(oc.last_time_stamp() - oc.first_time_stamp());
    */

    // get the observations at this time stamp
    observation_collection::const_range candidate_range(oc.at_time_stamp(ts));

    // found no observations?
    if(candidate_range.first == candidate_range.second)
        return false;

    // sample an observation
    float log_obs_prob(1.f);
    output_obs = *from_range(candidate_range.first, candidate_range.second, log_obs_prob);
    BOOST_ASSERT(log_obs_prob <= 0.f);

    // update log probability
    output_log_prob = log_ts_prob + log_obs_prob;

    return true;
}

float log_prob_of_sampling_observation(const observation& o,
                                       const observation_collection& oc)
{
    // early out if we cannot sample
    if(oc.empty())
        return -std::numeric_limits<float>::max();

    // early-out if observation is outside of temporal bounds
    if((t(o) < oc.first_time_stamp()) || (t(o) >= oc.last_time_stamp()))
        return -std::numeric_limits<float>::max();

    std::set<time_stamp> ts_with_obs;
    for (observation_collection::const_iterator iter = oc.begin(); iter != oc.end(); ++iter)
        ts_with_obs.insert(t(*iter));

    // log prob of choosing the time stamp
    float log_ts_prob = -logf(ts_with_obs.size());

    // get the observations at the observation's time stamp
    observation_collection::const_range candidate_range(oc.at_time_stamp(t(o)));

    // found no observations?
    if(candidate_range.first == candidate_range.second)
        return -std::numeric_limits<float>::max();

    // how likely was it we sampled this observation?
    size_t n_at_ts(oc.count_at_time_stamp(t(o)));
    BOOST_ASSERT(n_at_ts > 0);
    float log_obs_prob = -logf(static_cast<float>(n_at_ts));
    BOOST_ASSERT(log_obs_prob <= 0.f);

    // return log probability
    return log_ts_prob + log_obs_prob;
}

// sampling functions
static double rand_double();
//static float sample_normal(); // zero-mean, unit variance
inline float sample_exponential(float lambda);

template<typename Real, int Dim>
Eigen::Matrix<Real, Dim, 1> sample_multivariate_gaussian(const Eigen::Matrix<Real, Dim, 1>& mean,
                                                         const Eigen::Matrix<Real, Dim, Dim>& covariance);

// return a random double on the interval [0,1].
static double rand_double()
{
    static boost::uniform_real<double> dist(0.f, 1.f);
    static boost::variate_generator<boost::mt19937&, boost::uniform_real<double> > uni(random_generator, dist);
    return uni();
}

float sample_normal()
{
    static boost::normal_distribution<float> dist(0.f, 1.f);
    static boost::variate_generator<boost::mt19937&, boost::normal_distribution<float> > norm(random_generator, dist);
    return norm();
}

// sample X ~ Exponential(lambda)
inline float sample_exponential(float lambda)
{
    typedef boost::exponential_distribution<float> dist_t;
    dist_t dist(lambda);
    boost::variate_generator<boost::mt19937&, dist_t > sample(random_generator, dist);
    return sample();
}

// sample X ~ Gamma(k, theta)
float sample_gamma(unsigned int k, float theta)
{
    float sum(0.f);
    for(unsigned int i=0; i<k; ++i)
        sum += sample_exponential(1.f / theta);
    return sum;
}

// sample X ~ Gamma(k, theta)
float sample_gamma0(unsigned int k, float theta)
{
    float sum(std::numeric_limits<float>::min());
    for(unsigned int i=0; i<k; ++i)
        sum += sample_exponential(1.f / theta);
    return sum;
}

// sample X ~ Beta(alpha, beta)
float sample_beta(float alpha, float beta)
{
    gen_beta_param gen;
    gen_beta_initialize(&gen, alpha, beta);
    return gen_beta(&gen, rand_double);
}

float sample_truncated_beta(float alpha, float beta, float lo_limit, float hi_limit) {
    gen_beta_param gen;
    gen_beta_initialize(&gen, alpha, beta);
    float sample = -1;
    while (sample < lo_limit or sample > hi_limit)
        sample = gen_beta(&gen, rand_double);
    return sample;
}

Eigen::Matrix2f sample_inverse_wishart(const Eigen::Matrix2f& Phi, size_t s)
{
    return sample_wishart(Phi.inverse(), s).inverse();
}

Eigen::Matrix2f sample_wishart(const Eigen::Matrix2f& Phi, size_t s)
{
    BOOST_ASSERT(s > 1);

    Eigen::Matrix2f sample(Eigen::Matrix2f::Zero());

    // this simply makes use of the definition of the Wishart distribution.
    for(size_t i=0; i<s; ++i)
    {
        Eigen::Vector2f norm(sample_multivariate_gaussian<float,2>(Eigen::Vector2f::Zero(), Phi));
        sample += norm * norm.transpose();
    }

    return sample;
}

//float t_sample_normal() { return sample_normal(); }

} }  // biggles::sampling


/* data.cc
   Jeremy Barnes, 30 September 2009
   Copyright (c) 2009 Jeremy Barnes.  All rights reserved.

   File to read data for the competition.
*/

#include "data.h"
#include "jml/utils/parse_context.h"
#include "jml/stats/distribution_ops.h"
#include "jml/stats/distribution_simd.h"
#include "jml/utils/vector_utils.h"
#include "jml/utils/pair_utils.h"
#include "jml/algebra/lapack.h"
#include "jml/arch/timers.h"
#include "jml/arch/threads.h"
#include "decomposition.h"
#include "jml/utils/worker_task.h"
#include "jml/utils/guard.h"
#include <boost/bind.hpp>
#include "jml/stats/rmse.h"
#include "jml/stats/auc.h"
#include "jml/utils/filter_streams.h"

using namespace std;
using namespace ML;

/*****************************************************************************/
/* DIFFICULTY                                                                */
/*****************************************************************************/

std::string print(const Difficulty_Category & cat)
{
    switch (cat) {
    case DIF_UNKNOWN:    return "UNKNOWN";
    case DIF_AUTOMATIC:  return "AUTO";
    case DIF_POSSIBLE:   return "POSS";
    case DIF_IMPOSSIBLE: return "IMP";
    default: return format("Difficulty_Category(%d)", cat);
    }
}

std::ostream & operator << (std::ostream & stream, Difficulty_Category cat)
{
    return stream << print(cat);
}

Difficulty::
Difficulty()
    : category(DIF_UNKNOWN), difficulty(0.0)
{
}

Difficulty::
Difficulty(const ML::distribution<float> & model_outputs,
           float label, Target target)
{
    // If label is zero, then the example was unlabeled
    if ((target == RMSE && label == -1.5)
        || (target == AUC && label == 0.0)) {
        difficulty = 0.0;
        category = DIF_UNKNOWN;
        return;
    }

    if (target == AUC) {
        int ncorr = (model_outputs * label >= 0.0f).count();
        int nincorr = (model_outputs.size() - ncorr);

        if (ncorr == 0) {
            category = DIF_IMPOSSIBLE;
            difficulty = model_outputs.mean() * target;
        }
        else if (nincorr == 0) {
            category = DIF_AUTOMATIC;
            difficulty = model_outputs.mean() * target;
        }
        else {
            category = DIF_POSSIBLE;
            difficulty = model_outputs.mean() * target;
        }
    } else {
        int ncorr = (abs(model_outputs - label) < 0.5).count();
        int nincorr = (model_outputs.size() - ncorr);

        if (ncorr == 0) {
            category = DIF_IMPOSSIBLE;
            difficulty = abs(model_outputs.mean() - target);
        }
        else if (nincorr == 0) {
            category = DIF_AUTOMATIC;
            difficulty = abs(model_outputs.mean() - target);
        }
        else {
            category = DIF_POSSIBLE;
            difficulty = abs(model_outputs.mean() - target);
        }

    }
}

/*****************************************************************************/
/* MODEL_OUTPUT                                                              */
/*****************************************************************************/

double
Model_Output::
calc_rmse(const distribution<float> & targets) const
{
    return ML::calc_rmse(*this, targets);
}

double
Model_Output::
calc_rmse(const distribution<float> & targets,
          const distribution<float> & weights) const
{
    return ML::calc_rmse(*this, targets, weights);
}

double
Model_Output::
calc_auc(const distribution<float> & targets) const
{
    if ((targets == 0).all()) return 1;
    if ((targets == 0).any()) {
        cerr << "targets = " << targets << endl;
        cerr << "problem with calc_auc" << endl;
    }
    return ML::calc_auc(*this, targets, -1.0, 1.0);
}

double
Model_Output::
calc_auc(const distribution<float> & targets,
         const distribution<float> & weights) const
{
    return ML::calc_auc(*this, targets, weights, -1.0, 1.0);
}

double
Model_Output::
calc_score(const distribution<float> & targets,
           Target target) const
{
    if (target == AUC) return calc_auc(targets);
    else if (target == RMSE) return calc_rmse(targets);
    else throw Exception("unknown target");
}

double
Model_Output::
calc_score(const distribution<float> & targets,
           const distribution<float> & weights,
           Target target) const
{
    if (target == AUC) return calc_auc(targets, weights);
    else if (target == RMSE) return calc_rmse(targets, weights);
    else throw Exception("unknown target");
}

/*****************************************************************************/
/* DATA                                                                      */
/*****************************************************************************/

void
Data::
load(const std::string & filename, Target target, bool clear_first)
{
    if (clear_first) clear();
    else if (target != this->target)
        throw Exception("Data::load(): target loaded on top isn't same");

    this->target = target;

    filter_istream stream(filename);
    Parse_Context c(filename, stream);

    // First row: header.
    c.expect_literal("RowID,Target");

    int m = 0;
    while (!c.match_eol()) {
        c.expect_literal(',');
        string model_name = c.expect_text(",\n\r");

        if (clear_first)
            model_names.push_back(model_name);
        else if (model_name != model_names.at(m++))
            throw Exception("model names don't match");
    }

    if (!clear_first && model_names.size() != m)
        throw Exception("wrong number of models");

    int nm = model_names.size();

    // Create the data structures
    //cerr << model_names.size() << " models... ";

    if (clear_first) {
        models.resize(model_names.size());
    }

    int num_rows = 0;
    for (; c; ++num_rows) {
        int id = c.expect_int();
        example_ids.push_back(id);
        c.expect_literal(',');

        distribution<float> model_values(nm);

        float target_val = c.expect_int();

        if (target == RMSE) {
            // Convert into range (-1, 1)
            target_val = (target_val - 3000.0) / 2000.0;
        }

        targets.push_back(target_val);

        for (unsigned i = 0;  i < nm;  ++i) {
            c.expect_literal(',');
            int score = c.expect_int();
            float val = (score - 3000)/ 2000.0;
            model_values[i] = val;
        }

        boost::shared_ptr<Example> example
            (new Example(model_values, target_val, target));
        examples.push_back(example);

        c.skip_whitespace();
        c.expect_eol();
    }

    //cerr << num_rows << " rows... ";
    calc_scores();
}

void
Data::
clear()
{
    targets.clear();
    model_names.clear();
    example_ids.clear();
    models.clear();
    model_ranking.clear();
    examples.clear();
    decomposition = 0;
}

void
Data::
swap(Data & other)
{
    std::swap(target, other.target);
    targets.swap(other.targets);
    model_names.swap(other.model_names);
    example_ids.swap(other.example_ids);
    models.swap(other.models);
    model_ranking.swap(other.model_ranking);
    examples.swap(other.examples);
    std::swap(decomposition, other.decomposition);
}

void
Data::
calc_scores()
{
    vector<pair<float, int> > model_scores;

    Model_Output outputs[16];
    for (unsigned i = 0;  i < 16;  ++i)
        outputs[i].resize(nx());

    for (unsigned i = 0;  i < nm();  i += 16) {
        int ii = std::min<int>(i + 16, nm());

        for (unsigned x = 0;  x < nx();  ++x) {
            const distribution<float> & m = examples[x]->models;

            for (unsigned j = i;  j < ii;  ++j)
                outputs[j - i][x] = m[j];
        }

        for (unsigned j = i;  j < ii;  ++j) {
            if (target == RMSE)
                models[j].score = outputs[j - i].calc_rmse(targets);
            else models[j].score = outputs[j - i].calc_auc(targets);

            model_scores.push_back(make_pair(models[j].score, j));
        }
    }

    sort_on_first_ascending(model_scores);

    for (unsigned i = 0;  i < nm();  ++i)
        models[model_scores[i].second].rank = i;

    model_ranking.clear();
    model_ranking.insert(model_ranking.end(),
                         second_extractor(model_scores.begin()),
                         second_extractor(model_scores.end()));
}

void
Data::
hold_out(Data & remove_to, float proportion,
         int random_seed)
{
    distribution<float> example_weights(nx());
    distribution<float> remove_to_example_weights;

    hold_out(remove_to, proportion, example_weights,
             remove_to_example_weights, random_seed);
}

void
Data::
hold_out(Data & remove_to, float proportion,
         distribution<float> & example_weights,
         distribution<float> & remove_to_example_weights,
         int random_seed)
{
    static Lock lock;
    Guard guard(lock);

    srand(random_seed);

    vector<int> to_remove;
    for (unsigned i = 0;  i < nx();  ++i) {
        to_remove.push_back(i);
    }

    std::random_shuffle(to_remove.begin(), to_remove.end());

    if (proportion < 0 || proportion > 1.0)
        throw Exception("bad proportion");

    to_remove.erase(to_remove.begin() + proportion * to_remove.size(),
                    to_remove.end());

    vector<bool> remove_me(nx(), false);
    for (unsigned i = 0;  i < to_remove.size();  ++i)
        remove_me[to_remove[i]] = true;

    hold_out(remove_to, remove_me, example_weights,
             remove_to_example_weights);
}

void
Data::
hold_out(Data & remove_to, const vector<bool> & to_remove)
{
    distribution<float> example_weights(nx());
    distribution<float> remove_to_example_weights;

    hold_out(remove_to, to_remove, example_weights,
             remove_to_example_weights);
}

void
Data::
hold_out(Data & remove_to, const vector<bool> & remove_me,
         distribution<float> & example_weights,
         distribution<float> & remove_to_example_weights)
{
    remove_to.clear();

    if (remove_me.size() != nx())
        throw Exception("remove_me size was wrong");

    int num_to_remove = 0;
    for (unsigned i = 0;  i < remove_me.size();  ++i)
        if (remove_me[i]) ++num_to_remove;

    Data new_me;

    new_me.target = remove_to.target = target;
    new_me.model_names = remove_to.model_names = model_names;
    new_me.decomposition = remove_to.decomposition = decomposition;

    new_me.models.resize(model_names.size());
    remove_to.models.resize(model_names.size());

    distribution<float> new_example_weights;
    new_example_weights.reserve(nx() - num_to_remove);

    remove_to_example_weights.clear();
    remove_to_example_weights.reserve(num_to_remove);

    for (unsigned i = 0;  i < nx();  ++i) {
        Data & add_to = remove_me[i] ? remove_to : new_me;
        distribution<float> & weights
            = remove_me[i] ? remove_to_example_weights : new_example_weights;
        add_to.targets.push_back(targets[i]);
        add_to.example_ids.push_back(example_ids[i]);
        add_to.examples.push_back(examples[i]);
        weights.push_back(example_weights[i]);
    }

    new_me.calc_scores();
    remove_to.calc_scores();

    swap(new_me);
    example_weights.swap(new_example_weights);
}

struct Decompose_Job {

    vector<boost::shared_ptr<Data::Example> > & examples;
    const Decomposition & decomposition;
    int first;
    int last;

    Decompose_Job(vector<boost::shared_ptr<Data::Example> > & examples,
                  const Decomposition & decomposition,
                  int first, int last)
        : examples(examples),
          decomposition(decomposition), first(first), last(last)
    {
    }

    void operator () ()
    {
        for (unsigned x = first;  x < last;  ++x)
            examples[x]->decomposed
                = decomposition.decompose(examples[x]->models);
    }
};

void
Data::
apply_decomposition(const Decomposition & decomposition)
{
    this->decomposition = &decomposition;

    static Worker_Task & worker = Worker_Task::instance(num_threads() - 1);

    // Now, submit it as jobs to the worker task to be done multithreaded
    int group;
    {
        int parent = -1;  // no parent group
        group = worker.get_group(NO_JOB, "dump user results task", parent);

        // Make sure the group gets unlocked once we've populated
        // everything
        Call_Guard guard(boost::bind(&Worker_Task::unlock_group,
                                     boost::ref(worker),
                                     group));

        for (unsigned i = 0;  i < nx();  i += 100) {
            int last = std::min<int>(nx(), i + 100);

            Decompose_Job job(examples,
                              decomposition,
                              i, last);

            worker.add(job, "decompose_job", group);
        }
    }

    // Add this thread to the thread pool until we're ready
    worker.run_until_finished(group);
}

distribution<float>
Data::
apply_decomposition(const distribution<float> & example) const
{
    if (!decomposition) return distribution<float>();
    return decomposition->decompose(example);
}

#if 0
void
Data::
stats()
{
    target_stats.resize(nx());
    target_difficulty.resize(nx());

    distribution<float> model_vals(nm());

    double total_mean_neg = 0.0, total_mean_pos = 0.0;
    double num_neg = 0.0, num_pos = 0.0;
    double minval = INFINITY, maxval = -INFINITY;

    int nimpossible = 0, nautomatic = 0, npossible = 0, nunknown = 0;

    for (unsigned i = 0;  i < nx();  ++i) {
        for (unsigned j = 0;  j < nm();  ++j)
            model_vals[j] = models[j][i];
        target_stats[i] = Target_Stats(model_vals.begin(), model_vals.end());
        target_difficulty[i] = Difficulty(model_vals, targets[i], target);

        if (targets[i] < 0.0) {
            total_mean_neg += model_vals.mean();
            num_neg += 1.0;
        }
        else if (targets[i] > 0.0) {
            total_mean_pos += model_vals.mean();
            num_pos += 1.0;
        }

        minval = std::min<double>(minval, model_vals.min());
        maxval = std::max<double>(maxval, model_vals.max());

        switch (target_difficulty[i].category) {
        case DIF_IMPOSSIBLE: ++nimpossible;  break;
        case DIF_AUTOMATIC: ++nautomatic;  break;
        case DIF_POSSIBLE: ++npossible;  break;
        default: ++nunknown;
        }
    }

#if 0
    cerr << "mean_neg = " << total_mean_neg / num_neg
         << " mean_pos = " << total_mean_pos / num_pos
         << " min = " << minval << " max = " << maxval
         << endl;

    cerr << "auto " << nautomatic << " imp " << nimpossible << " poss "
         << npossible << endl;
#endif
}
#endif

size_t
Data::
decomposition_size() const
{
    return (decomposition ? decomposition->size() : 0);
}


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

  @copyright 2012-2015 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_MEIGHT_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_MEIGHT_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-constant

    Generate the constant meight.

    @return The Meight constant for the proper type
  **/
  template<typename T> T Meight();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant
      Generate the  constant meight.

      @return The Meight constant for the proper type
    **/
    const boost::dispatch::functor<tag::meight_> meight = {};
  }
} }
#endif

#include <boost/simd/constant/definition/meight.hpp>
#include <boost/simd/arch/common/scalar/constant/constant_value.hpp>
#include <boost/simd/arch/common/simd/constant/constant_value.hpp>

#endif


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2013   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
//==============================================================================
#ifndef NT2_LINALG_FUNCTIONS_LAPACK_MLINSOLVE_HPP_INCLUDED
#define NT2_LINALG_FUNCTIONS_LAPACK_MLINSOLVE_HPP_INCLUDED

#include <nt2/linalg/functions/mlinsolve.hpp>
#include <nt2/include/functions/clinsolve.hpp>
#include <nt2/include/functions/gemsv.hpp>
#include <nt2/include/functions/mcsne.hpp>
#include <nt2/include/functions/pomsv.hpp>
#include <nt2/include/functions/of_size.hpp>
#include <nt2/linalg/options.hpp>
#include <nt2/sdk/meta/settings_of.hpp>
#include <nt2/include/functions/tie.hpp>
#include <nt2/sdk/meta/as_real.hpp>
#include <boost/proto/traits.hpp>
#include <nt2/core/container/table/table.hpp>
#include <boost/dispatch/meta/terminal_of.hpp>
#include <boost/core/ignore_unused.hpp>

namespace nt2 { namespace ext
{
  //============================================================================
  // LINSOLVE
  //============================================================================
  BOOST_DISPATCH_IMPLEMENT  ( mlinsolve_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((ast_<A0, nt2::container::domain>)) // A
                              ((ast_<A1, nt2::container::domain>)) // B
                              ((ast_<A2, nt2::container::domain>)) // X
                            )
  {
    typedef void  result_type;;
    typedef typename A0::value_type ctype_t;
    typedef typename nt2::meta::as_real<ctype_t>::type   type_t;
    typedef typename meta::option<typename A0::settings_type,nt2::tag::shape_>::type shape;
    typedef nt2::memory::container<tag::table_, ctype_t, nt2::settings(nt2::_2D,shape)> desired_semantic1;
    typedef nt2::memory::container<tag::table_, ctype_t, nt2::settings(nt2::_2D)> desired_semantic;
    typedef nt2::container::table<ctype_t,shape>  matrix_type;

    BOOST_FORCEINLINE result_type operator()( A0 const& a0, A1 const& a1, A2& a2 ) const
    {
      const type_t x = type_t(0);
      eval(a0,a1,a2,x,shape());
    }
    //==========================================================================
    /// INTERNAL ONLY - Shape analysis

    /// INTERNAL ONLY - Rectangular shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2& a2, double const, nt2::rectangular_ const&) const
    {
      nt2_la_int m   = boost::fusion::at_c<0>( a0.extent() );
      nt2_la_int n   = boost::fusion::at_c<1>( a0.extent() );
      nt2_la_int nb   = boost::fusion::at_c<1>( a1.extent() );

      if (m>n)
      {
      NT2_AS_TERMINAL_IN(desired_semantic1,a,a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);

      a2 = nt2::mcsne(a,b);
      }
      else
      {
      // Copy of matrix a is costly and should be avoided
      matrix_type entry(a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      a2.resize(nt2::of_size(m,nb));
      nt2_la_int iter = nt2::gemsv(boost::proto::value(entry)
                       ,boost::proto::value(b),boost::proto::value(a2) );
      boost::ignore_unused(iter);
      }

    }

    /// INTERNAL ONLY - Positive definite shape
    BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2& a2, double const, nt2::positive_definite_ const&) const
    {
      nt2_la_int m   = boost::fusion::at_c<0>( a0.extent() );
      nt2_la_int n   = boost::fusion::at_c<1>( a1.extent() );

      matrix_type entry(a0);
      NT2_AS_TERMINAL_IN(desired_semantic,b,a1);
      a2.resize(nt2::of_size(m,n));
      nt2_la_int iter = nt2::pomsv( boost::proto::value(entry)
                                  , boost::proto::value(b),a2);
      boost::ignore_unused(iter);
    }

    /// INTERNAL ONLY - Default case
    template<typename T, typename sh> BOOST_FORCEINLINE
    void eval ( A0 const& a0, A1 const& a1 , A2& a2, T const, sh const&) const
    {
      nt2::clinsolve(a0,a1,nt2::tie(a2));
    }

  };

} }

#endif


#ifndef SEM_SIMILARITY
#define SEM_SIMILARITY

#include <string>
#include <set>
#include <vector>
#include <map>
#include <regex>
#include <algorithm>
#include <boost/random/uniform_real.hpp>
#include <boost/range/algorithm/remove_if.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <numeric>
#include <random>
// #include "./matrix_mult.cuh"
// #include <cuda_runtime.h>
#include <iostream>
#include <codecvt>
#include <fstream>

enum ScoreType {SEM_F1,SEM_Precision,SEM_Recall};

typedef struct
{
    float* weightVectors;
    float* ngramVectors;

} SemanticVectors;

SemanticVectors* loadVectors(std::string vectorsAddress,std::string wordsAddress,std::string hash2IndexAddress,std::string ngramsVectorAddress);

class SemanticSimilarity
{
private:
    /* data */
    float* weightVectors;
    float* oovVector;
    float* ngramVectors;
    int ngram_size;
    int maximumWordCount;
    int vectorSize;
    int total_tokens;
    std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter;
    boost::uniform_real<float> random_gen;
    std::mt19937 gen;
    std::map<std::wstring,int> stringToVectorIndexMap;
    std::map<uint32_t,int> ngarmsHash2Index;
    std::vector<std::pair<int,int>> sentencesStartEndIndex;
    std::set<std::wstring> stopwords;
    float* extractDocumentMatrix(std::vector<std::wstring>& sentences);
    float getF1SimScore(float* result,std::pair<int,int> sentence);
    float getRecallSimScore(float* result,std::pair<int,int> sentence);
    float getPrecisionSimScore(float* result,std::pair<int,int> sentence);
    void similarityMultiply(float* matrix,float* out,int input_size,int vector_size);
    uint32_t hash(std::wstring str);
public:
    SemanticSimilarity(SemanticVectors* vectors,std::string wordsAddress,std::string stopwordsAddress,std::string hash2IndexAddress,int max_words_per_sentence = 512);
    std::map<std::wstring,int> getWordsIndex(){return this->stringToVectorIndexMap;}
    float* makeVector(std::wstring oov);
    float* getVectors(){return this->weightVectors;}
    std::set<int> getMostSimilarSentencesToDocUsingSemantics(std::vector<std::wstring>& sentences,int numberOfMaskedSentences,ScoreType type,bool ignoreNext = true);
};

#endif


/*
 Copyright (C) 2020 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include "toplevelfixture.hpp"
#include <boost/make_shared.hpp>
#include <boost/test/unit_test.hpp>
#include <ql/currencies/america.hpp>
#include <ql/math/matrix.hpp>
#include <ql/quotes/simplequote.hpp>
#include <ql/termstructures/volatility/equityfx/blackvariancesurface.hpp>
#include <ql/termstructures/yield/flatforward.hpp>
#include <ql/time/calendars/unitedstates.hpp>
#include <ql/time/daycounters/actualactual.hpp>
#include <qle/indexes/equityindex.hpp>
#include <qle/termstructures/equityblackvolsurfaceproxy.hpp>

using namespace boost::unit_test_framework;
using namespace QuantLib;
using namespace QuantExt;
using namespace std;

BOOST_FIXTURE_TEST_SUITE(QuantExtTestSuite, qle::test::TopLevelFixture)

BOOST_AUTO_TEST_SUITE(EquityBlackVolSurfaceProxy)

BOOST_AUTO_TEST_CASE(testEquityBlackVolSurfaceProxy) {

    BOOST_TEST_MESSAGE("Testing QuantExt::EquityBlackVolSurfaceProxy...");

    // take an index and one of it's underlyings, proxy the underlyings vol surface off the
    // index vol surface, check that the forward ATM vols are the same

    Date today = Date(1, Jan, 2020);
    DayCounter dc = ActualActual();

    Settings::instance().evaluationDate() = today;

    vector<Date> dates;
    dates.push_back(Date(3, Feb, 2020));
    dates.push_back(Date(2, Mar, 2020));
    dates.push_back(Date(1, Apr, 2020));
    dates.push_back(Date(4, Jan, 2021));

    vector<Real> strikes;
    strikes.push_back(500);
    strikes.push_back(1000);
    strikes.push_back(1500);

    Matrix vols = Matrix(3, 4);
    vols[0][0] = 0.12;
    vols[1][0] = 0.10;
    vols[2][0] = 0.13;
    vols[0][1] = 0.22;
    vols[1][1] = 0.20;
    vols[2][1] = 0.23;
    vols[0][2] = 0.32;
    vols[1][2] = 0.30;
    vols[2][2] = 0.33;
    vols[0][3] = 0.42;
    vols[1][3] = 0.40;
    vols[2][3] = 0.43;

    // spots for the index and underlying
    Handle<Quote> indexSpot = Handle<Quote>(boost::shared_ptr<Quote>(new SimpleQuote(1000)));
    Handle<Quote> underlyingSpot = Handle<Quote>(boost::shared_ptr<Quote>(new SimpleQuote(150)));

    // forecast and dividend yields for the index
    Handle<YieldTermStructure> indexForecast = Handle<YieldTermStructure>(
        boost::make_shared<FlatForward>(today, Handle<Quote>(boost::make_shared<SimpleQuote>(0.03)), dc));
    Handle<YieldTermStructure> indexDividend = Handle<YieldTermStructure>(
        boost::make_shared<FlatForward>(today, Handle<Quote>(boost::make_shared<SimpleQuote>(0.02)), dc));

    // forecast and dividend yields for the underlying
    Handle<YieldTermStructure> underlyingForecast = Handle<YieldTermStructure>(
        boost::make_shared<FlatForward>(today, Handle<Quote>(boost::make_shared<SimpleQuote>(0.02)), dc));
    Handle<YieldTermStructure> underlyingDividend = Handle<YieldTermStructure>(
        boost::make_shared<FlatForward>(today, Handle<Quote>(boost::make_shared<SimpleQuote>(0.01)), dc));

    // set up EquityIndexes for the index and underlying
    boost::shared_ptr<EquityIndex> index = boost::make_shared<EquityIndex>("Index", UnitedStates(), USDCurrency(),
                                                                           indexSpot, indexForecast, indexDividend);
    boost::shared_ptr<EquityIndex> underlying = boost::make_shared<EquityIndex>(
        "Underlying", UnitedStates(), USDCurrency(), underlyingSpot, underlyingForecast, underlyingDividend);

    // set up a vol surface for the index
    boost::shared_ptr<BlackVolTermStructure> indexVolSurface =
        boost::make_shared<BlackVarianceSurface>(today, UnitedStates(), dates, strikes, vols, dc);

    // set up a vol surface for the underlying, to be proxied from the index surface
    boost::shared_ptr<EquityBlackVolatilitySurfaceProxy> underlyingVolSurface =
        boost::make_shared<EquityBlackVolatilitySurfaceProxy>(indexVolSurface, underlying, index);

    // Check the ATM forward vols
    for (auto d : dates) {
        // underlying forward
        Real underlyingF = underlying->fixing(d);
        // vol from proxy surface
        Real underlyingVol = underlyingVolSurface->blackVol(d, underlyingF);

        // index forward
        Real indexF = index->fixing(d);
        // vol from index surface
        Real indexVol = indexVolSurface->blackVol(d, indexF);

        BOOST_CHECK_CLOSE(underlyingVol, indexVol, 0.001);
    }
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


// Copyright 2015-2018 Hans Dembinski
//
// 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)

//[ guide_histogram_reduction

#include <boost/histogram.hpp>
#include <cassert>

int main() {
  using namespace boost::histogram;

  // make a 2d histogram
  auto h = make_histogram(axis::regular<>(4, 0.0, 4.0), axis::regular<>(4, -1.0, 1.0));

  h(0, -0.9);
  h(1, 0.9);
  h(2, 0.1);
  h(3, 0.1);

  // shrink the first axis to remove the highest bin
  // rebin the second axis by merging pairs of adjacent bins
  auto h2 = algorithm::reduce(h, algorithm::shrink(0, 0.0, 3.0), algorithm::rebin(1, 2));

  // reduce does not remove counts if the histogram has underflow/overflow bins
  assert(algorithm::sum(h) == 4 && algorithm::sum(h2) == 4);

  assert(h2.axis(0).size() == 3);
  assert(h2.axis(0).bin(0).lower() == 0.0);
  assert(h2.axis(0).bin(2).upper() == 3.0);
  assert(h2.axis(1).size() == 2);
  assert(h2.axis(1).bin(0).lower() == -1.0);
  assert(h2.axis(1).bin(1).upper() == 1.0);
}

//]


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   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
//==============================================================================
#define NT2_UNIT_MODULE "nt2::deltaf function"
#include <nt2/table.hpp>
#include <nt2/include/functions/deltaf.hpp>
#include <nt2/table.hpp>
#include <nt2/include/functions/size.hpp>
#include <nt2/include/functions/cons.hpp>
#include <nt2/include/functions/zeros.hpp>

#include <nt2/sdk/unit/module.hpp>
#include <nt2/sdk/unit/tests/basic.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <nt2/sdk/unit/tests/type_expr.hpp>
#include <nt2/sdk/unit/tests/exceptions.hpp>
#include <boost/dispatch/meta/nth_hierarchy.hpp>
#include <nt2/sdk/meta/type_id.hpp>

NT2_TEST_CASE( untyped )
{
  using boost::mpl::_;
  using nt2::meta::value_type_;

  nt2::table<float> ref =  nt2::zeros(3, 3);
  ref(2, 3) = 1.0;
  NT2_DISPLAY(ref);
  nt2::table<int> p(nt2::of_size(1, 2)); p(1) = p(2) = 3;
  nt2::table<float> x0 = nt2::deltaf(2, 3, nt2::of_size(3, 3), nt2::meta::as_<float>());
  nt2::table<float> x1 = nt2::deltaf(2, 3, 3, nt2::meta::as_<float>());
  NT2_DISPLAY(x0);
  NT2_TEST_EQUAL(ref, nt2::deltaf(2, 3, 3, nt2::meta::as_<float>()));
  NT2_TEST_EQUAL(ref, nt2::deltaf(2, 3, 3, 3, nt2::meta::as_<float>()));
  NT2_TEST_EQUAL(ref, nt2::deltaf(2, 3, p, nt2::meta::as_<float>()));
}