/**
 * @file linear_svm_test.cpp
 * @author Yashwant Singh Parihar
 *
 * Test mlpackMain() of logistic_regression_main.cpp
 *
 * mlpack is free software; you may redistribute it and/or modify it under the
 * terms of the 3-clause BSD license.  You should have received a copy of the
 * 3-clause BSD license along with mlpack.  If not, see
 * http://www.opensource.org/licenses/BSD-3-Clause for more information.
 */
#include <string>

#define BINDING_TYPE BINDING_TYPE_TEST

static const std::string testName = "LinearSVM";

#include <mlpack/core.hpp>
#include <mlpack/methods/linear_svm/linear_svm_main.cpp>
#include <mlpack/core/util/mlpack_main.hpp>
#include "test_helper.hpp"

#include <boost/test/unit_test.hpp>
#include "../test_tools.hpp"

using namespace mlpack;

struct LinearSVMTestFixture
{
 public:
  LinearSVMTestFixture()
  {
    // Cache in the options for this program.
    CLI::RestoreSettings(testName);
  }

  ~LinearSVMTestFixture()
  {
    // Clear the settings.
    bindings::tests::CleanMemory();
    CLI::ClearSettings();
  }
};

BOOST_FIXTURE_TEST_SUITE(LinearSVMMainTest,
                         LinearSVMTestFixture);

/**
 * Ensure that trainingSet are necessarily passed when training.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNoTrainingData)
{
  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("labels", std::move(trainLabels));

  // Training data is not provided. Should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Checking that that size and dimensionality of prediction is correct.
 */
BOOST_AUTO_TEST_CASE(LinearSVMOutputDimensionTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  arma::mat testData;
  if (!data::Load("iris_test.csv", testData))
    BOOST_FAIL("Cannot load test dataset iris_test.csv!");

  size_t testSize = testData.n_cols;

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("test", std::move(testData));

  // Training the model.
  mlpackMain();

  // Get the output predictions of the test data.
  const arma::Row<size_t>& testLabels =
      CLI::GetParam<arma::Row<size_t>>("predictions");

  // Output predictions size must match the test data set size.
  BOOST_REQUIRE_EQUAL(testLabels.n_rows, 1);
  BOOST_REQUIRE_EQUAL(testLabels.n_cols, testSize);
}

/**
 * Ensuring that the labels size is checked.
 */
BOOST_AUTO_TEST_CASE(LinearSVMCheckLabelsSizeTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("vc2_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset vc2_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));

  // Labels with incorrect size. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Checking two options of specifying labels (extra row in train matrix and
 * extra parameter) and ensuring that predictions are the same.
 */
BOOST_AUTO_TEST_CASE(LinearSVMLabelsRepresentationTest)
{
  arma::mat trainData1({{1.0, 2.0, 3.0}, {1.0, 4.0, 9.0}, {0, 1, 1}});
  arma::mat testData({{4.0, 5.0}, {1.0, 6.0}});

  SetInputParam("training", std::move(trainData1));
  SetInputParam("test", testData);

  // The first solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the output.
  const arma::Row<size_t> testLabels1 =
      std::move(CLI::GetParam<arma::Row<size_t>>("predictions"));

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  // Now train by providing labels as extra parameter.
  arma::mat trainData2({{1.0, 2.0, 3.0}, {1.0, 4.0, 9.0}});
  arma::Row<size_t> trainLabels({0, 1, 1});

  SetInputParam("training", std::move(trainData2));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("test", std::move(testData));

  // The second solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // get the output
  const arma::Row<size_t>& testLabels2 =
      CLI::GetParam<arma::Row<size_t>>("predictions");

  // Both solutions should be equal.
  CheckMatrices(testLabels1, testLabels2);
}

/**
 * Ensure that saved model can be used again.
 */
BOOST_AUTO_TEST_CASE(LinearSVMModelReuseTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  arma::mat testData;
  if (!data::Load("iris_test.csv", testData))
    BOOST_FAIL("Cannot load test dataset iris_test.csv!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("test", testData);

  // First solution
  mlpackMain();

  // Get the output model obtained from training.
  LinearSVMModel* model =
      CLI::GetParam<LinearSVMModel*>("output_model");
  // Get the output.
  const arma::Row<size_t>& testLabels1 =
      std::move(CLI::GetParam<arma::Row<size_t>>("predictions"));

  // Reset the data passed.
  CLI::GetSingleton().Parameters()["training"].wasPassed = false;
  CLI::GetSingleton().Parameters()["labels"].wasPassed = false;
  CLI::GetSingleton().Parameters()["test"].wasPassed = false;

  SetInputParam("input_model", model);
  SetInputParam("test", std::move(testData));

  // Second solution.
  mlpackMain();

  // Get the output.
  const arma::Row<size_t>& testLabels2 =
      CLI::GetParam<arma::Row<size_t>>("predictions");

  // Both solutions should be equal.
  CheckMatrices(testLabels1, testLabels2);
}

/**
 * Checking for dimensionality of the test data set.
 */
BOOST_AUTO_TEST_CASE(LinearSVMCheckDimOfTestData)
{
  // Dimensionality of trainingSet is trainData.n_rows - 1 because labels are
  // not provided.
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::mat testData;
  if (!data::Load("iris_test.csv", testData))
    BOOST_FAIL("Cannot load test dataset iris_test.csv!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("test", std::move(testData));

  // Dimensionality of test data is wrong. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that test data dimensionality is checked when model is loaded.
 */
BOOST_AUTO_TEST_CASE(LinearSVMCheckDimOfTestData2)
{
  // Dimensionality of trainingSet is trainData.n_rows - 1 because labels are
  // not provided.
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::mat testData;
  if (!data::Load("iris_test.csv", testData))
    BOOST_FAIL("Cannot load test dataset iris_test.csv!");

  SetInputParam("training", std::move(trainData));

  // Training the model.
  mlpackMain();

  // Get the output model obtained from training.
  LinearSVMModel* model =
      CLI::GetParam<LinearSVMModel*>("output_model");

  // Reset the data passed.
  CLI::GetSingleton().Parameters()["training"].wasPassed = false;
  CLI::GetSingleton().Parameters()["labels"].wasPassed = false;

  SetInputParam("input_model", model);
  SetInputParam("test", std::move(testData));

  // Test data dimensionality is wrong. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that max iteration for optimizers is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeMaxIterationTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("max_iterations", int(-1));

  // Maximum iterations is negative. It should a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that lambda for optimizers is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeLambdaTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("lambda", double(-0.01));

  // Lambda is negative. It should a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that number of classes for optimizers is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeNumberOfClassesTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("num_classes", int(-1));

  // Number of classes is negative. It should a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that tolerance is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeToleranceTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("tolerance", double(-0.01));

  // Tolerance is negative. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that delta is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeDeltaTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("delta", double(-0.01));

  // Delta is negative. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that epochs is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeEpochsTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("epochs", int(-1));

  // Epochs is negative. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that number classes must not be zero.
 */
BOOST_AUTO_TEST_CASE(LinearSVMZeroNumberOfClassesTest)
{
  arma::mat trainData = "2 0 0;"
                        "0 0 0;"
                        "0 2 1;"
                        "1 0 2;"
                        "0 1 0";

  arma::Row<size_t> trainLabels = "0 0 0";

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));

  // Number of classes for optimizer is zero.
  // It should throw a invalid_argument error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::invalid_argument);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that Optimizer must be correct.
 */
BOOST_AUTO_TEST_CASE(LinearSVMOptimizerTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("hello"));

  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring changing Maximum number of iterations changes the output model.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffMaxIterationsTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("max_iterations", int(1));

  // First solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("max_iterations", int(100));

  // Second solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that lambda has some effects on the output.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffLambdaTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("lambda", double(0.001));

  // First solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("lambda", double(1000));

  // Second solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that delta has some effects on the output.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffDeltaTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("delta", double(1.0));

  // First solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("delta", double(1000));

  // Second solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that no_intercept has some effects on the output.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffInterceptTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);

  // First solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("no_intercept", bool(true));

  // Second solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that no_intercept has some effects on the output
 * when the optimizer is 'psgd'.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffInterceptTestWithPsgd)
{
  arma::mat trainData = "2 0 0;"
                        "0 0 0;"
                        "0 2 1;"
                        "1 0 2;"
                        "0 1 0";

  arma::Row<size_t> trainLabels = "1 0 1";

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("optimizer", std::string("psgd"));

  // First solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("no_intercept", bool(true));

  // Second solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that step size for optimizer is non negative.
 */
BOOST_AUTO_TEST_CASE(LinearSVMNonNegativeStepSizeTest)
{
  arma::mat trainData;
  if (!data::Load("iris.csv", trainData))
    BOOST_FAIL("Cannot load test dataset iris.csv!");

  arma::Row<size_t> trainLabels;
  if (!data::Load("iris_labels.txt", trainLabels))
    BOOST_FAIL("Cannot load test dataset iris_labels.txt!");

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("step_size", double(-0.01));

  // Step size for optimizer is negative. It should throw a runtime error.
  Log::Fatal.ignoreInput = true;
  BOOST_REQUIRE_THROW(mlpackMain(), std::runtime_error);
  Log::Fatal.ignoreInput = false;
}

/**
 * Ensuring that epochs has some effects on the output.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffEpochsTest)
{
  arma::mat trainData = "2 0 0;"
                        "0 0 0;"
                        "0 2 1;"
                        "1 0 2;"
                        "0 1 0";

  arma::Row<size_t> trainLabels = "1 0 1";

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("epochs", int(5));

  // First solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("epochs", int(10));

  // Second solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that Step size has some effects on the output.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffStepSizeTest)
{
  arma::mat trainData = "2 0 0;"
                        "0 0 0;"
                        "0 2 1;"
                        "1 0 2;"
                        "0 1 0";

  arma::Row<size_t> trainLabels = "1 0 1";

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("num_classes", int(2));
  SetInputParam("step_size", double(0.02));

  // First solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("num_classes", int(2));
  SetInputParam("step_size", double(1.02));

  // Second solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that tolerance has some effects on the output.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffToleranceTest)
{
  arma::mat trainData = "2 0 0;"
                        "0 0 0;"
                        "0 2 1;"
                        "1 0 2;"
                        "0 1 0";

  arma::Row<size_t> trainLabels = "1 0 1";

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("num_classes", int(2));
  SetInputParam("tolerance", double(1e-1));

  // First solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("psgd"));
  SetInputParam("num_classes", int(2));
  SetInputParam("tolerance", double(1e-10));

  // Second solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

/**
 * Ensuring that lbfgs optimizer converges to a different result than psgd.
 */
BOOST_AUTO_TEST_CASE(LinearSVMDiffOptimizerTest)
{
  arma::mat trainData = "2 0 0;"
                        "0 0 0;"
                        "0 2 1;"
                        "1 0 2;"
                        "0 1 0";

  arma::Row<size_t> trainLabels = "1 0 1";

  SetInputParam("training", trainData);
  SetInputParam("labels", trainLabels);
  SetInputParam("optimizer", std::string("lbfgs"));

  // First solution.
  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after first training.
  const arma::mat parameters1 = std::move(
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters());

  // Reset the settings.
  bindings::tests::CleanMemory();
  CLI::ClearSettings();
  CLI::RestoreSettings(testName);

  SetInputParam("training", std::move(trainData));
  SetInputParam("labels", std::move(trainLabels));
  SetInputParam("optimizer", std::string("psgd"));

  // Second solution.
  #ifdef HAS_OPENMP
  omp_set_num_threads(1);
  #endif

  mlpack::math::FixedRandomSeed();
  mlpackMain();

  // Get the parameters of the output model obtained after second training.
  const arma::mat& parameters2 =
      CLI::GetParam<LinearSVMModel*>("output_model")->svm.Parameters();

  // Both solutions should be not equal.
  CheckMatricesNotEqual(parameters1, parameters2);
}

BOOST_AUTO_TEST_SUITE_END();


#define NO_IMPORT_ARRAY

#include <complex>
#include <stdint.h>
#include <stdio.h>
#include <vector>
#include <string>
extern "C" {
  #include <cblas.h>
  // Additional prototypes for Fortran LAPACK routines.
  // sposv: solve Ax = b for A positive definite.
  void sposv_(const char* uplo, int* n, int* nrhs, float* a, int* lda,
              float* b, int* ldb, int* info );
}

#include <boost/python.hpp>

#include <pybindings.h>
#include "so3g_numpy.h"
#include "numpy_assist.h"
#include "Ranges.h"

// TODO: Generalize to double precision too.
// This implements Jon's noise model for ACT. It takes in
// * ft[ndet,nfreq]        the fourier transform of the time-ordered data
// * bins[nbin,{from,to}]  the start and end of each bin
// * iD[nbin,ndet]         the inverse uncorrelated variance for each detector per bin
// * iV[nbin,ndet,nvec]    matrix representing the scaled eivenvectors per bin
void nmat_detvecs_apply(const bp::object & ft, const bp::object & bins, const bp::object & iD, const bp::object & iV, float s, float norm) {
	// Should pass in this too
	BufferWrapper<float>               ft_buf  ("ft",   ft,   false, std::vector<int>{-1,-1});
	BufferWrapper<int32_t>             bins_buf("bins", bins, false, std::vector<int>{-1, 2});
	int ndet = ft_buf->shape[0], nmode = ft_buf->shape[1], nbin = bins_buf->shape[0];
	BufferWrapper<float>               iD_buf  ("iD",   iD,   false, std::vector<int>{nbin,ndet});
	BufferWrapper<float>               iV_buf  ("iV",   iV,   false, std::vector<int>{nbin,ndet,-1});
	int nvec = iV_buf->shape[2];
	if (ft_buf->strides[1] != ft_buf->itemsize || ft_buf->strides[0] != ft_buf->itemsize*nmode)
		throw buffer_exception("ft must be C-contiguous along last axis");
	if (bins_buf->strides[1] != bins_buf->itemsize || bins_buf->strides[0] != bins_buf->itemsize*2)
		throw buffer_exception("bins must be C-contiguous along last axis");
	if (iD_buf->strides[1] != iD_buf->itemsize || iD_buf->strides[0] != iD_buf->itemsize*ndet)
		throw buffer_exception("iD must be C-contiguous along last axis");
	if (iV_buf->strides[2] != iV_buf->itemsize || iV_buf->strides[1] != iV_buf->itemsize*nvec || iV_buf->strides[0] != iV_buf->itemsize*nvec*ndet)
		throw buffer_exception("iV must be C-contiguous along last axis");
	// Internally we work with a real view of ft, with twice as many elements to compensate
	//int nmode = 2*nfreq;
	float   * ft_   = (float*)   ft_buf->buf;
	int32_t * bins_ = (int32_t*) bins_buf->buf;
	float   * iD_   = (float*)   iD_buf->buf;
	float   * iV_   = (float*)   iV_buf->buf;

	// Ok, actually do the work
	for(int bi = 0; bi < nbin; bi++) {
		int b1 = min(2*bins_[2*bi+0],nmode-1);
		int b2 = min(2*bins_[2*bi+1],nmode);
		int nm = b2-b1;
		float * biD = iD_ + bi*ndet;
		float * biV = iV_ + bi*ndet*nvec;

		// what I want to do
		// ft    = ftod[:,b[0]:b[1]]
		// iD    = self.iD[bi]/norm
		// iV    = self.iV[bi]/norm**0.5
		// ft[:] = iD[:,None]*ft + self.s*iV.dot(iV.T.dot(ft))
		// So first do iV.T [nvec,ndet] dot ft [ndet,nm] -> Q [nvec,nm]
		float * Q = new float[nvec*nm];
		cblas_sgemm(CblasRowMajor, CblasTrans, CblasNoTrans, nvec, nm, ndet, 1.0f, biV, nvec, ft_+b1, nmode, 0.0f, Q, nm);
		// Handle the uncorrelated part
		//#pragma omp parallel for
		for(int di = 0; di < ndet; di++)
			for(int i = b1; i < b2; i++)
				ft_[di*nmode+i] *= biD[di]/norm;
		// Do ft += s*iV[ndet,nvec] dot Q [nvec,nm]
		cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, ndet, nm, nvec, s/norm, biV, nvec, Q, nm, 1.0f, ft_+b1, nmode);
		delete [] Q;
	}
}

// Support of maximum-liklihood sample cut handling. This got a bit long, so it should
// probably be moved into its own file.

// Forward declarations of helper functions
int get_dtype(const bp::object &);
int pcut_full_measure_helper(const vector<RangesInt32> &);
template <typename T> void pcut_full_tod2vals_helper(const vector<RangesInt32> &, T *, int, int, T *);
template <typename T> void pcut_full_vals2tod_helper(const vector<RangesInt32> &, T *, int, int, T *);
int pcut_poly_measure_helper(const vector<RangesInt32> &, int, int nmax);
template <typename T> void pcut_poly_tod2vals_helper(const vector<RangesInt32> &, int, int, T *, int, int, T *);
template <typename T> void pcut_poly_vals2tod_helper(const vector<RangesInt32> &, int, int, T *, int, int, T *);
template <typename T> void pcut_clear_helper(const vector<RangesInt32> &, T *, int, int);

// The main cuts processing function. In Maximum-likelihood map-making cuts are handled
// as special degrees of freedom, and are part of the pointing matrix. This function
// provides the operations corresponding to the cuts part of the pointing matrix P and
// P'. In particular, operation == "insert" corresponds to the model-to-data matrix P,
// while operation == "extract" corresponds to the data-to-model matrix P'. Additionally,
// we provide operation == "measure", which doesn't touch any of the arrays, and just returns
// how long the vals argument should be.
//
// Note that "extract" does not in general estimate the coefficients of the model. It just does
// P'd, while the coefficients would (in the absence of any sky signal etc) be (P'P)'P'd.
//
// Arguments:
//  range_matrix: the .ranges member of a python RangesMatrix
//  operation: "measure", "insert" or "extract"
//  model: The type of model used
//   * "full": One degree of freedom per sample
//   * "poly": Model is a legendre polynomial in each range.
//        Order of poly determined by params["resolution"] (samples per order) and params["nmax"] (max order)
//  tod:  numpy array with shape [ndet,nsamp] of dtype float32 or float64
//  vals: numpy array with shape [:] of same dtype as tod. Holds the model degrees of freedom.
//
// TODO: To be able to process cuts in parallel, we need a lookup table for where in vals each
// cut range starts. This will be fast enough to build on the fly. Would pass this as an extra
// argument to the helper functions.

int process_cuts(const bp::object & range_matrix, const std::string & operation, const std::string & model, const bp::dict & params, const bp::object & tod, const bp::object & vals) {
	auto ranges = extract_ranges<int32_t>(range_matrix);
	// Decoding these up here lets us avoid some duplication later
	int resolution, nmax;
	if     (model == "full") {}
	else if(model == "poly") {
		resolution = bp::extract<int>(params.get("resolution"));
		nmax       = bp::extract<int>(params.get("nmax"));
	} else throw general_exception("process_cuts model can only be 'full' or 'poly'");

	if(operation == "measure") {
		if     (model == "full") return pcut_full_measure_helper(ranges);
		else if(model == "poly") return pcut_poly_measure_helper(ranges, resolution, nmax);
	} else {
		int dtype = get_dtype(tod);
		if(dtype == NPY_FLOAT) {
			BufferWrapper<float> tod_buf  ("tod",  tod,  false, std::vector<int>{-1,-1});
			BufferWrapper<float> vals_buf ("vals", vals, false, std::vector<int>{-1});
			int ndet = tod_buf->shape[0], nsamp = tod_buf->shape[1];
			if(operation == "insert") {
				if     (model == "full")
					pcut_full_vals2tod_helper(ranges, (float*)tod_buf->buf, ndet, nsamp, (float*) vals_buf->buf);
				else if(model == "poly")
					pcut_poly_vals2tod_helper(ranges, resolution, nmax, (float*)tod_buf->buf, ndet, nsamp, (float*) vals_buf->buf);
			} else if(operation == "extract") {
				if     (model == "full")
					pcut_full_tod2vals_helper(ranges, (float*)tod_buf->buf, ndet, nsamp, (float*) vals_buf->buf);
				else if(model == "poly")
					pcut_poly_tod2vals_helper(ranges, resolution, nmax, (float*)tod_buf->buf, ndet, nsamp, (float*) vals_buf->buf);
			} else if(operation == "clear") {
				pcut_clear_helper(ranges, (float*)tod_buf->buf, ndet, nsamp);
			} else throw general_exception("process_cuts operation can only be 'measure', 'insert' or 'extract'");
		} else if(dtype == NPY_DOUBLE) {
			BufferWrapper<double> tod_buf  ("tod",  tod,  false, std::vector<int>{-1,-1});
			BufferWrapper<double> vals_buf ("vals", vals, false, std::vector<int>{-1});
			int ndet = tod_buf->shape[0], nsamp = tod_buf->shape[1];
			if(operation == "insert") {
				if     (model == "full")
					pcut_full_vals2tod_helper(ranges, (double*)tod_buf->buf, ndet, nsamp, (double*) vals_buf->buf);
				else if(model == "poly")
					pcut_poly_vals2tod_helper(ranges, resolution, nmax, (double*)tod_buf->buf, ndet, nsamp, (double*) vals_buf->buf);
			} else if(operation == "extract") {
				if     (model == "full")
					pcut_full_tod2vals_helper(ranges, (double*)tod_buf->buf, ndet, nsamp, (double*) vals_buf->buf);
				else if(model == "poly")
					pcut_poly_tod2vals_helper(ranges, resolution, nmax, (double*)tod_buf->buf, ndet, nsamp, (double*) vals_buf->buf);
			} else if(operation == "clear") {
				pcut_clear_helper(ranges, (float*)tod_buf->buf, ndet, nsamp);
			} else throw general_exception("process_cuts operation can only be 'measure', 'insert' or 'extract'");
		} else throw general_exception("process_cuts only supports float32 and float64");
	}
	return 0;
}

// Helpers for the cuts

int get_dtype(const bp::object & arr) {
	PyObject *ob = PyArray_FromAny(arr.ptr(), NULL, 0, 0, 0, NULL);
	if (ob == NULL) throw exception();
	PyArrayObject * a = reinterpret_cast<PyArrayObject*>(ob);
	int res = PyArray_TYPE(a);
	Py_DECREF(a);
	return PyArray_TYPE(a);
}

// This is all from Jon's work for ACT.
int get_npoly(int gapsize, int resolution, int nmax) {
	if(nmax < 1) nmax = 1;
	if     (gapsize <=  1) return min<int>(1, nmax);
	else if(gapsize <=  3) return min<int>(2, nmax);
	else if(gapsize <=  6) return min<int>(3, nmax);
	else if(gapsize <= 20) return min<int>(4, nmax);
	else return min<int>(5 + gapsize/resolution, nmax);
}

// Full cut treatment, with one degree of freedom for each sample in the cut
int pcut_full_measure_helper(const vector<RangesInt32> & rangemat) {
	int n = 0;
	for(int di = 0; di < rangemat.size(); di++)
		for (auto const &r: rangemat[di].segments)
			n += r.second-r.first;
	return n;
}
template <typename T>
void pcut_full_tod2vals_helper(const vector<RangesInt32> & rangemat, T * tod, int ndet, int nsamp, T * vals) {
	int i = 0;
	for(int di = 0; di < rangemat.size(); di++)
		for (auto const &r: rangemat[di].segments)
			for(int j = r.first; j < r.second; j++, i++)
				vals[i] = tod[di*nsamp+j];
}
template <typename T>
void pcut_full_vals2tod_helper(const vector<RangesInt32> & rangemat, T * tod, int ndet, int nsamp, T * vals) {
	int i = 0;
	for(int di = 0; di < rangemat.size(); di++)
		for (auto const &r: rangemat[di].segments)
			for(int j = r.first; j < r.second; j++, i++)
				tod[di*nsamp+j] = vals[i];
}

// Polynomial cut treatment
int pcut_poly_measure_helper(const vector<RangesInt32> & rangemat, int resolution, int nmax) {
	int n = 0;
	for(int di = 0; di < rangemat.size(); di++)
		for (auto const &r: rangemat[di].segments)
			n += get_npoly(r.second-r.first, resolution, nmax);
	return n;
}
template <typename T>
void pcut_poly_tod2vals_helper(const vector<RangesInt32> & rangemat, int resolution, int nmax, T * tod, int ndet, int nsamp, T * vals) {
	int i = 0;
	for(int di = 0; di < rangemat.size(); di++) {
		for (auto const &r: rangemat[di].segments) {
			int np = get_npoly(r.second-r.first, resolution, nmax);
			if(np <= 1) {
				for(int s = r.first; s < r.second; s++)
					vals[i] += tod[di*nsamp+s];
				i++;
			} else {
				for(int p = 0; p < np; p++) vals[i+p] = 0;
				for(int s = r.first; s < r.second; s++) {
					T x = -1 + 2*(s-r.first)/T(r.second-r.first-1);
					T t = tod[di*nsamp+s];
					vals[i] += t;
					if(np > 1) vals[i+1] += t*x;
					if(np > 2) {
						T Pa = x, Pb = 1, Pc = 0;
						for(int p = 2; p < np; p++) {
							Pc = Pb; Pb = Pa; Pa = ((2*p-1)*x*Pb-(p-1)*Pc)/p;
							vals[i+p] += t*Pa;
						}
					}
					i += np;
				}
			}
		}
	}
}
template <typename T>
void pcut_poly_vals2tod_helper(const vector<RangesInt32> & rangemat, int resolution, int nmax, T * tod, int ndet, int nsamp, T * vals) {
	int i = 0;
	for(int di = 0; di < rangemat.size(); di++) {
		for (auto const &r: rangemat[di].segments) {
			int np = get_npoly(r.second-r.first, resolution, nmax);
			if(np <= 1) {
				for(int s = r.first; s < r.second; s++)
					tod[di*nsamp+s] = vals[i];
				i++;
			} else {
				for(int s = r.first; s < r.second; s++) {
					T x = -1 + 2*(s-r.first)/T(r.second-r.first-1);
					T t = vals[i];
					if(np > 1) t += x*vals[i+1];
					if(np > 2) {
						T Pa = x, Pb = 1, Pc = 0;
						for(int p = 2; p < np; p++) {
							Pc = Pb; Pb = Pa; Pa = ((2*p-1)*x*Pb-(p-1)*Pc)/p;
							t += Pa*vals[i+p];
						}
					}
					tod[di*nsamp+s] = t;
					i += np;
				}
			}
		}
	}
}
template <typename T>
void pcut_clear_helper(const vector<RangesInt32> & rangemat, T * tod, int ndet, int nsamp) {
	#pragma omp parallel for
	for(int di = 0; di < rangemat.size(); di++)
		for (auto const &r: rangemat[di].segments)
			for(int s = r.first; s < r.second; s++)
				tod[di*nsamp+s] = 0;
}

// get_gap_fill_poly_single
//
// Single detector processor for get_gap_fill_poly.  Fit polynomials
// to each stretch of data in "gaps".  Requires square matrix a and
// vector b to be preallocated for ncoeff.
//
// The arguments (inplace, extract) behave like this:
//
//   (true,  valid):   The data are replaced with the model, and the
//                     replaced samples are copied into *extract.
//   (false, valid):   The data are left unchanged and the model
//                     values are placed in *extract.
//   (true,  nullptr): The data are replaced with the model, and
//                     the original data samples are discarded.
//   (false, nullptr): The data is left unchanged and the model
//                     is discarded (probably not what you want...).
//
// When extract is non-null, it needs to be the right size... this
// should be checked by get_gap_fill_poly.

void get_gap_fill_poly_single(const RangesInt32 &gaps, float *data,
                             float *a, float *b,
                             int buffer, int ncoeff,
                             bool inplace, float *extract)
{
        // Generate Ranges corresponding to samples near the edges of
        // intervals we want to fill.
        RangesInt32 rsegs = gaps.buffered((int32_t)buffer);
        rsegs.intersect(gaps.complement());

        // We are guaranteed that there is one or two rseg intervals
        // between each of our gaps, and zero or one rseg intervals
        // before the first gap and after the second gap.

        // Index of the rseg we're working with.
        int model_i = 0;

        for (auto const &gap: gaps.segments) {
                // std::cout << "GAP\n";
                int contrib_samps = 0;
                int contrib_segs = 0;
                memset(a, 0, ncoeff*ncoeff*sizeof(*a));
                memset(b, 0, ncoeff*sizeof(*b));
                float x0 = gap.first;

                for (; model_i < rsegs.segments.size(); model_i++) {
                        // Advance until just before this gap.
                        auto const &ival = rsegs.segments[model_i];
                        if (ival.second + 1 < gap.first)
                                continue;
                        // Include this interval in the fit.
                        for (int i=ival.first; i<ival.second; i++) {
                                float xx = 1.;
                                float yxx = data[i];
                                float x = i - x0;
                                for (int j=0; j<ncoeff; j++) {
                                        b[j] += yxx;
                                        yxx *= x;
                                        a[j] += xx;
                                        xx *= x;
                                }
                                // Now down...
                                for (int k=2; k<ncoeff+1; k++) {
                                        a[k*ncoeff - 1] += xx;
                                        xx *= x;
                                }
                        }
                        contrib_samps += ival.second - ival.first;
                        contrib_segs += 1;
                        // If this was the right side interval, bail
                        // now (so we can possibly re-use this
                        // interval for next gap).
                        if (ival.first > gap.first)
                                break;
                }

                // Restrict order based on number of contributing samples.
                int n_keep = std::min(contrib_samps / 10 + 1, ncoeff);
                if (contrib_samps > 0) {
                        // Fill in a's interior.
                        for (int r=1; r<ncoeff; r++)
                                for (int c=0; c<ncoeff-1; c++)
                                        a[r*ncoeff + c] = a[r*ncoeff + c - ncoeff + 1];

                        // Re-organize a if the order has changed...
                        if (n_keep < ncoeff) {
                                for (int r=1; r<n_keep; r++)
                                        for (int c=0; c<n_keep; c++)
                                                a[r*n_keep+c] = a[r*ncoeff+c];
                        }

                        // Solve the system...
                        int one = 1;
                        int err = 0;
                        sposv_("Upper", &n_keep, &one, a, &n_keep, b, &n_keep, &err);
                }
                float *write_to = nullptr;
                float *save_data = nullptr;
                if (inplace) {
			// Copy original data to extract, write model to data.
			save_data = extract;
			write_to = data + gap.first;
                } else {
			// Write results to extract, do not touch data.
			write_to = extract;
                }

                if (save_data != nullptr) {
			for (int i=gap.first; i<gap.second; i++, save_data++)
				*save_data = data[i];
                }
                if (write_to != nullptr) {
			for (int i=gap.first; i<gap.second; i++, write_to++) {
				float xx = 1.;
				*write_to = 0.;
				for (int j=0; j<n_keep; j++) {
					*write_to += xx * b[j];
					xx *= (i - gap.first);
				}
			}
                }
                if (extract != nullptr)
			extract += (gap.second - gap.first);
        }
}

void get_gap_fill_poly(const bp::object ranges,
		       const bp::object tod,
		       int buffer,
		       int order,
		       bool inplace,
		       const bp::object ex)
{
        // As a test, copy data from rangemat into segment.
	auto rangemat = extract_ranges<int32_t>(ranges);
        int ndet = rangemat.size();

        BufferWrapper<float> tod_buf  ("tod",  tod,  false, std::vector<int>{ndet,-1});
        int nsamp = tod_buf->shape[1];

        int ncoeff = order + 1; // Let us not speak of order again.
        float *a = (float*)malloc(ncoeff*(ncoeff+1)*sizeof(*a));
        float *b = a + ncoeff*ncoeff;

        float *ex_data = nullptr;
        std::vector<int> ex_offsets;

        if (ex.ptr() != Py_None) {
		// Compute offsets of each detector into ex.
		int n = 0;
		for (auto const &r: rangemat) {
			ex_offsets.push_back(n);
			for (auto const &r: r.segments)
				n += (r.second - r.first);
		}
		BufferWrapper<float> ex_buf("ex", ex, false, std::vector<int>{n});
		ex_data = (float*)ex_buf->buf;
        }

        for (int di=0; di < rangemat.size(); di++) {
                float* data = (float*)((char*)tod_buf->buf + di*tod_buf->strides[0]);
                float* _ex = ex_data;
                if (_ex != nullptr)
			_ex += ex_offsets[di];
                get_gap_fill_poly_single(rangemat[di], data, a, b, buffer, ncoeff,
					 inplace, _ex);
        }

        free(a);
}

PYBINDINGS("so3g")
{
	bp::def("nmat_detvecs_apply", nmat_detvecs_apply);
	bp::def("process_cuts",  process_cuts);
	bp::def("get_gap_fill_poly",  get_gap_fill_poly,
                "get_gap_fill_poly(ranges, signal, buffer, order, extract)\n"
                "\n"
                "Do polynomial gap-filling.\n"
                "\n"
                "Args:\n"
                "  ranges: RangesMatrix with shape (ndet, nsamp)\n"
                "  signal: data array with shape (ndet, nsamp)\n"
                "  buffer: integer stating max number of samples to use on each end\n"
                "  order: order of polynomial to use (1 means linear)\n"
		"  inplace: whether to overwrite data array with the model\n"
		"  extract: array to write the original data samples (inplace)\n"
		"    or the model (!inplace) into.\n");
}


// Copyright Louis Dionne 2013-2016
// 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/assert.hpp>
#include <boost/hana/config.hpp>
#include <boost/hana/functional/flip.hpp>
namespace hana = boost::hana;

BOOST_HANA_CONSTEXPR_LAMBDA auto minus = [](int x, int y, int z = 0) {
    return x - y - z;
};

int main() {
    BOOST_HANA_CONSTEXPR_CHECK(minus(3, 0) == 3 - 0);
    BOOST_HANA_CONSTEXPR_CHECK(hana::flip(minus)(3, 0) == 0 - 3);

    BOOST_HANA_CONSTEXPR_CHECK(minus(3, 0, 1) == 3 - 0 - 1);
    BOOST_HANA_CONSTEXPR_CHECK(hana::flip(minus)(3, 0, 1) == 0 - 3 - 1);
}


//****************************************************************************
// (c) 2008, 2009 by the openOR Team
//****************************************************************************
// The contents of this file are available under the GPL v2.0 license
// or under the openOR comercial license. see
//   /Doc/openOR_free_license.txt or
//   /Doc/openOR_comercial_license.txt
// for Details.
//****************************************************************************
//!
//! @file
//! @ingroup openOR_core_math

#ifndef openOR_core_Math_vector_types_hpp
#define openOR_core_Math_vector_types_hpp

#include <boost/numeric/ublas/fwd.hpp>

namespace openOR {
   namespace Math {

      //----------------------------------------------------------------------------
      // convenience typedefs
      //----------------------------------------------------------------------------

      typedef boost::numeric::ublas::bounded_vector<int, 2>          Vector2i;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<unsigned int, 2> Vector2ui;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<float, 2>        Vector2f;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<double, 2>       Vector2d;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<long long, 2>    Vector2ll;    //!< @ingroup openOR_core_math

      typedef boost::numeric::ublas::bounded_vector<int, 3>          Vector3i;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<unsigned int, 3> Vector3ui;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<float, 3>        Vector3f;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<double, 3>       Vector3d;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<long long, 3>    Vector3ll;    //!< @ingroup openOR_core_math

      typedef boost::numeric::ublas::bounded_vector<int, 4>          Vector4i;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<unsigned int, 4> Vector4ui;    //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<float, 4>        Vector4f;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<double, 4>       Vector4d;     //!< @ingroup openOR_core_math
      typedef boost::numeric::ublas::bounded_vector<long long, 4>    Vector4ll;    //!< @ingroup openOR_core_math

      typedef boost::numeric::ublas::bounded_vector<int, 6>          Vector6i;     //!< @ingroup openOR_core_math

   }
}

#endif


#include <iostream>
#include <omp.h>

// program options
#include <boost/program_options.hpp>
namespace po = boost::program_options;

// json parser
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
namespace pt = boost::property_tree;

#include "ProgressBar.hpp"
#include "Quaca.h"

// parameters that are parsed from the command line
std::string parameter_file;
std::string output_file;
int num_threads = 1;

// reads the input file and number of threads from the command line
// uses boost program options
void read_command_line(int argc, char *argv[]) {
  /* Read command line options */
  try {
    // List all options and their description
    po::options_description desc("Allowed options");
    desc.add_options()("help,h", "Help screen")(
        "file", po::value<std::string>(&(parameter_file)), "Input File")(
        "threads", po::value<int>(&(num_threads))->default_value(-1),
        "Number of parallel threads");

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

    // if the help option is given, show the flag description
    if (vm.count("help")) {
      std::cout << desc << std::endl;
      exit(0);
    }

  } catch (std::exception &e) {
    std::cerr << "error: " << e.what() << std::endl;
  }

  /* set according output file */
  output_file =
      parameter_file.substr(0, parameter_file.find_last_of('.')) +
      ".csv";
}

int main(int argc, char *argv[]) {
  // get command line options
  read_command_line(argc, argv);

  // read parameters (No Looper class used for omega, since it is no attribute of the class)

  pt::ptree root;
  pt::read_json(parameter_file, root);

  double start;              // starting value
  double end;                // end value
  int number_of_steps;       // number of steps
  std::string scale;         // scale type

  start = root.get<double>("Looper.start");
  end = root.get<double>("Looper.end");
  number_of_steps = root.get<double>("Looper.steps");
  scale = root.get<std::string>("Looper.scale");

  // Calculate steps
  std::vector<double> steps; // array containing the steps
  if (scale == "linear") {
    double spacing = (end - start) / ((double)number_of_steps - 1);
    for (int i = 0; i < number_of_steps; i++) {
      steps.push_back(start + i * spacing);
    }
  } else if (scale == "log") {
    double spacing = pow(end / start, 1. / ((double)number_of_steps - 1.0));
    for (int i = 0; i < number_of_steps; i++) {
      steps.push_back(start * pow(spacing, i));
    }

  } else {
    std::cerr << "Unknown scale: " << scale << std::endl;
    exit(-1);
  }

  // array to store all the computed values of the loop
  std::vector<double> decay_data;
  decay_data.resize(number_of_steps);

  // define progressbar
  ProgressBar progbar(number_of_steps, 70);

  // Check whether the number of threads have been set by the --threads flag
  if (num_threads == -1) {
    // If the --threads flag has not been set, set the number of flags to
    // the maximal value
    num_threads = omp_get_max_threads();
  }

  // Create a parallel region given threads given by the --threads flag
  // we have to create the parallel region already here to ensure,
  // that any thread creates their own instance of quantum_friction
  std::cout << "Starting parallel region with " << num_threads
            << " threads." << std::endl;
  if (num_threads > omp_get_max_threads()) {
    std::cout
        << "There are not enough avaiable threads. Maximal avaiable threads: "
        << omp_get_max_threads() << std::endl;
    std::cout << "Aborting calculation" << std::endl;
    exit(0);
  }
#pragma omp parallel num_threads(num_threads)
  {

    // define needed quantities
    auto polarizability = std::make_shared<Polarizability>(parameter_file);
    double omega_a = polarizability->get_omega_a();
    double alpha_zero = polarizability->get_alpha_zero();
    // Parallelize the for-loop of the given looper
#pragma omp critical
    progbar.display();

#pragma omp for schedule(dynamic)
    for (int i = 0; i < number_of_steps; i++) {

	    // Calculate decay rate
	    // define different alphas
	    std::complex<double> I(0e0, 1e0);
	    cx_mat::fixed<3, 3> alphaI;
	    cx_mat::fixed<3, 3> alphaR;
	    cx_mat::fixed<3, 3> inv_alpha;
	    cx_mat::fixed<3, 3> inv_alpha_dag;

	    // calculate alpha
	    polarizability->calculate_tensor(steps[i], alphaI, IM);
	    polarizability->calculate_tensor(steps[i], alphaR, RE);

	    inv_alpha     = inv(alphaR + I * alphaI);
	    inv_alpha_dag = inv(alphaR - I * alphaI);

	    decay_data[i] = alpha_zero*omega_a*omega_a*real(trace(inv_alpha*alphaI*inv_alpha_dag))/steps[i];

#pragma omp critical
	    ++progbar;
#pragma omp critical
	    progbar.display();
    }
  }

  // define output file
  std::ofstream file;
  file.open(output_file);

  // write the data to the file
  double step;
  for (int i = 0; i < number_of_steps; i++) {
    step = steps[i];
    file << step << "," << decay_data[i] << "\n";
  }

  // close file
  file.close();

  // close progress bar
  progbar.done();

  return 0;
}


//--------------------------------------------------------------
// Standard cpp library
//--------------------------------------------------------------
#include <random>
#include <fstream>
//--------------------------------------------------------------
// Boost library
//--------------------------------------------------------------
#include <boost/program_options.hpp>
//--------------------------------------------------------------
// User Defined library
//--------------------------------------------------------------
#include "Network/Network.hpp"
#include "Generate/Generate.hpp"
#include "Timing/Timing.hpp"
//--------------------------------------------------------------

int main(int argc, char const *argv[]){
    //--------------------------------------------------------------
    // Command line arugments using boost options
    //--------------------------
    std::string filename;
    size_t training_size, generated_size, batch_size, epoch;
    bool isEpoch;
    long double precision;
    //--------------------------
    boost::program_options::options_description description("Allowed options:");
    //--------------------------
    description.add_options()
    ("help,h", "Display this help message")
    ("filename,s", boost::program_options::value<std::string>(&filename)->default_value("test_results"), "Name of the file saved")
    ("training_size,t", boost::program_options::value<size_t>(&training_size)->default_value(100), "How many different points to train. Accepts an integer x > 0")
    ("generated_size,g", boost::program_options::value<size_t>(&generated_size)->default_value(10000), "How many points generated. Accepts an integer x >= 100")
    ("batch_size,b", boost::program_options::value<size_t>(&batch_size)->default_value(20), "Batch the generated data to train. Limitations: - Must be less then the generated_size - Must be less the 1000 this a libtorch limitation")
    ("precision,p", boost::program_options::value<long double>(&precision)->default_value(2.5E-1L), "Determine when to stop the training. This uses a validation set")
    ("use_epoch,u", boost::program_options::value<bool>(&isEpoch)->default_value(false), "false: validation precision or true: Train with an epoch iteration")
    ("epoch,e", boost::program_options::value<size_t>(&epoch)->default_value(20), "How many iterations to train");
    //--------------------------
    boost::program_options::variables_map vm;
    boost::program_options::store(boost::program_options::command_line_parser(argc, argv).options(description).run(), vm);
    boost::program_options::notify(vm);
    //--------------------------
    // Add protection to the values
    //-----------
    if (vm.count("help")){
        std::cout << description;
    }// end if (vm.count("help"))
    //-----------
    if (vm.count("filename")){
        filename = vm["filename"].as<std::string>() + std::string(".csv");
    }// end if (vm.count("filename"))
    //-----------
    if (vm["training_size"].as<size_t>() < 0){
        throw std::out_of_range("Must be at least postive");
    }// end if (vm.count("training_size") < 0)
    //-----------
    if (vm["generated_size"].as<size_t>() < 100){
        throw std::out_of_range("Must be at least 100 (x >= 100)");
    }// end if (vm.count("generated_size") < 100)
    //-----------
    if (vm["batch_size"].as<size_t>() < 0 and vm["batch_size"].as<size_t>() > generated_size and vm["batch_size"].as<size_t>() > 1000){
        throw std::out_of_range("Must be at least postive or less then generated size or less then 1000 (x <= 1000)");
    }// end  if (vm.count("batch_size") < 0 and vm.count("batch_size") > static_cast<int>(generated_size) and vm.count("batch_size") > 1000)
    //-----------
    if (vm["epoch"].as<size_t>() < 0){
        throw std::out_of_range("Must be at least postive");
    }// end if (vm.count("epoch") < 0)
    //-----------
    if (vm["precision"].as<long double>() < 0){
        throw std::out_of_range("Must be at least postive");
    }// end if (vm.count("precision") < 0)
    //--------------------------
    if(isEpoch){
        std::cout   << "filename: " << filename << "\n"
                    << "training_size: " << training_size << "\n"
                    << "generated_size: " << generated_size << "\n"
                    << "batch_size: " << batch_size << "\n"
                    << "epoch: " << epoch << std::endl;
    }// end if(isEpoch)
    else{
        std::cout   << "filename: " << filename << "\n"
                    << "training_size: " << training_size << "\n"
                    << "generated_size: " << generated_size << "\n"
                    << "batch_size: " << batch_size << "\n"
                    << "precision: " << precision << std::endl;
    }// end else
    //--------------------------------------------------------------
    // Creating a random number generator
    //--------------------------
    std::random_device dev;
    std::mt19937 rng(dev()), center_rng(dev());
    std::uniform_real_distribution<double> random_radius(1,10);
    std::uniform_int_distribution<> random_centers(-10,10);
    //--------------------------------------------------------------
    // Initiate Torch seed, device type
    //--------------------------
    torch::manual_seed(17);
    //--------------------------
    torch::DeviceType device_type;
    //--------------------------
    if (torch::cuda::is_available()) {
        std::cout << "CUDA available! Training on GPU." << std::endl;
        device_type = torch::kCUDA;
        torch::cuda::manual_seed(7);
    }// end if (torch::cuda::is_available())
    else {
        std::cout << "Training on CPU." << std::endl;
        device_type = torch::kCPU;
    }// end else
    //--------------------------
    torch::Device device(device_type);
    //--------------------------------------------------------------
    // Initialize the network
    //--------------------------
    Net model(batch_size);
    model.to(device);
    // LSTMNet model(device);
    // model.to(device);
    //--------------------------
    torch::optim::SGD optimizer(model.parameters(), torch::optim::SGDOptions(1E-1L).momentum(0.95).nesterov(true));
    //--------------------------
    NetworkHandling<Net> handler(std::move(model), device);
    //--------------------------------------------------------------
    // Timing main
    //--------------------------
    Timing _timer(__FUNCTION__);
    //--------------------------------------------------------------
    // Training loop
    //--------------------------
    for (size_t i = 0; i < training_size; ++i){
        //--------------------------------------------------------------
        // Generate data
        //--------------------------
        Generate _generate(random_radius(rng), generated_size, {random_centers(center_rng), random_centers(center_rng)});
        //------------
        std::cout   << "Training data radius: " << _generate.get_radius()
                    << " at center: (" << std::get<0>(_generate.get_center()) << ","
                    << std::get<1>(_generate.get_center()) << ")" << std::endl;
        //--------------------------------------------------------------
        // Generate your data set. At this point you can add transforms to you data set, e.g. stack your
        // batches into a single tensor.
        // auto data_set = DataLoader(std::move(std::get<0>(data)), std::move(std::get<1>(data))).map(torch::data::transforms::Normalize<>(0.5, 0.25)).map(torch::data::transforms::Stack<>());
        //--------------------------
        auto data_set = DataLoader( Normalize::normalization(std::get<0>(_generate.get_data())),
                                    Normalize::normalization(std::get<1>(_generate.get_data()))).map(torch::data::transforms::Stack<>());
        //--------------------------
        // Generate a data loader.
        //--------------------------
        auto data_loader = torch::data::make_data_loader<torch::data::samplers::RandomSampler>( std::move(data_set),
                                                                                torch::data::DataLoaderOptions(batch_size));
        //--------------------------------------------------------------
        // Time the loop
        //--------------------------
        Timing _loop_timer("Main for loop");
        //--------------------------------------------------------------
        // Train the network
        //--------------------------
        if (isEpoch){
            //--------------------------
            auto loss = handler.train(std::move(data_loader), optimizer, epoch);
            //--------------------------
        }// end if (isEpoch)
        else{
            //--------------------------------------------------------------
            // Generate your validation data set. At this point you can add transforms to you data set, e.g. stack your
            // batches into a single tensor.
            //--------------------------
            auto validation_data_set = DataLoader(  Normalize::normalization(std::get<0>(_generate.get_validation())),
                                                    Normalize::normalization(std::get<1>(_generate.get_validation())))
                                                        .map(torch::data::transforms::Stack<>());
            //--------------------------
            // Generate a data loader.
            //--------------------------
            auto validation_data_loader = torch::data::make_data_loader<torch::data::samplers::SequentialSampler>(std::move(validation_data_set),
                                                                                torch::data::DataLoaderOptions(batch_size));
            //--------------------------
            auto loss = handler.train(std::move(data_loader), std::move(validation_data_loader), optimizer, precision);
            //--------------------------
        }// end else
        //--------------------------
        printf("\n-----------------Done:[%zu]-----------------\n", i);
        //--------------------------
    }// end (size_t i = 0; i < training_size; ++i)
    //--------------------------------------------------------------
    // Generate test data
    //--------------------------
    Generate test_generate(random_radius(rng), 3*batch_size, {random_centers(center_rng), random_centers(center_rng)});
    //--------------------------
    std::cout   << "\n" << "Test Data"
                << "\n" << "test data radius: " << test_generate.get_radius()
                << " at center: (" << std::get<0>(test_generate.get_center()) << ","
                << std::get<1>(test_generate.get_center()) << ")" << std::endl;
    //--------------------------------------------------------------
    // Normalize the data
    //--------------------------
    Normalize test_input_normal(std::get<0>(test_generate.get_data()));
    Normalize test_target_normal(std::get<1>(test_generate.get_data()));
    //--------------------------
    // Generate your data set. At this point you can add transforms to you data set, e.g. stack your
    // batches into a single tensor.
    //--------------------------
    auto test_data_set = DataLoader(test_input_normal.normalization(),
                                    test_target_normal.normalization()).map(torch::data::transforms::Stack<>());
    //--------------------------
    // Generate a data loader.
    //--------------------------
    auto test_data_loader = torch::data::make_data_loader<torch::data::samplers::SequentialSampler>( std::move(test_data_set),
                                                                                torch::data::DataLoaderOptions(batch_size));
    //--------------------------
    // Test the data
    //--------------------------
    auto test = handler.test(std::move(test_data_loader));
    //--------------------------------------------------------------
    // Print table settup
    //--------------------------
    fort::char_table table;
    //--------------------------
    // Change border style
    //--------------------------
    table.set_border_style(FT_BASIC2_STYLE);
    //--------------------------
    // Set color
    //--------------------------
    table.row(0).set_cell_content_fg_color(fort::color::light_blue);
    //--------------------------
    // Set center alignment for the all columns
    //--------------------------
    table.column(0).set_cell_text_align(fort::text_align::center);
    table.column(1).set_cell_text_align(fort::text_align::center);
    table.column(2).set_cell_text_align(fort::text_align::center);
    table.column(3).set_cell_text_align(fort::text_align::center);
    table.column(4).set_cell_text_align(fort::text_align::center);
    //--------------------------
    table   << fort::header
            << "Target" << "Output" << "Target Original" << "Output original" << "Loss" << fort::endr;
    //--------------------------------------------------------------
    // File pointer
    //--------------------------
    std::fstream fout;
    //--------------------------
    // opens an existing csv file or creates a new file.
    //--------------------------
    fout.open(filename, std::ios::out | std::ios::app);
    //--------------------------
    fout    << "Radius: " << test_generate.get_radius()
            << " at center: (" << std::get<0>(test_generate.get_center()) << "."
            << std::get<1>(test_generate.get_center()) << ")" << "\n"
            << "target" << ","
            << "output" << ","
            << "target original" << ","
            << "output original" << ","
            << "loss" << "\n";
    //--------------------------
    fout.flush();
    //--------------------------
    for (const auto& [_test_target, _test_output, _loss] : test){
        //--------------------------
        table   << test_target_normal.unnormalization(_test_target)
                << test_target_normal.unnormalization(_test_output)
                << _test_target
                << _test_output
                << _loss << fort::endr;
        //--------------------------
        auto _output_temp = _test_target.cpu();
        auto _output = _output_temp.accessor<float, 2>();
        auto _target_temp = _test_output.cpu();
        auto _target = _target_temp.accessor<float, 2>();
        //--------------------------
        for (int64_t i = 0; i < _test_target.size(0); ++i){
            //--------------------------
            for (int64_t j = 0; j < _test_target.size(1); ++j){
                //--------------------------
                fout    << test_target_normal.unnormalization_nonTensor(_output[i][j]) << ","
                        << test_target_normal.unnormalization_nonTensor(_target[i][j]) << ","
                        << _output[i][j] << ","
                        << _target[i][j] << ","
                        << _loss << "\n";
                //--------------------------
                fout.flush();
                //--------------------------
            }// end for (size_t i = 0; i < _test_target.size(0); ++i)
            //--------------------------
        }// end for (size_t i = 0; i < _test_target.size(1); ++i)
        //--------------------------
    }// end for (const auto& [_test_target, _test_output, _loss] : test)
    //--------------------------
    fout.close();
    //--------------------------------------------------------------
    std::cout << "\n" << table.to_string() << std::endl;
    //--------------------------
    std::cout << "test data Saved" << std::endl;
    //--------------------------------------------------------------
    return 0;
    //--------------------------
}// end int main(int argc, char const *argv[])


/*
 * Author: Johannes M Dieterich
 */

#ifndef HELPERFUNCTIONS_HPP
#define	HELPERFUNCTIONS_HPP

#include <armadillo>
#include <memory>
#include "Grid.hpp"
using namespace std;
using namespace arma;

class CutoffFunctions {

public:
    template<class GridComputer, template<class> class GridType>
    static void WGCVacuumCutoff(unique_ptr<GridType<GridComputer> >& density, const double rhoV, const double rhoStep);

    static inline double vacuumCutoff(const double density, const double rhoV, const double rhoStep);

    template<class GridComputer, template<class> class GridType>
    static void WGCVacuumCutoffDeriv(unique_ptr<GridType<GridComputer> >& density, const double rhoV, const double rhoStep);

    static inline double vacuumCutoffDeriv(const double density, const double rhoV, const double rhoStep);
};

class MathFunctions {

public:
    static double lindhardResponse(const double eta, const double lambda, const double mu);

    static double derivativeLindhardResponse(const double eta, const double mu);
};

class MemoryFunctions {
public:
    static unique_ptr<cube, void (*) (cube*)> allocateScratch(const size_t rows, const size_t cols, const size_t slices);
    static unique_ptr<fcube, void (*) (fcube*)> allocateScratchFloat(const size_t rows, const size_t cols, const size_t slices);
    static unique_ptr<cx_cube, void (*) (cx_cube*)> allocateReciprocalScratch(const size_t rows, const size_t cols, const size_t slices);
    static unique_ptr<vec, void (*) (vec*)> allocateScratch(const size_t elements);
    static cube* allocateScratchCube(const size_t rows, const size_t cols, const size_t slices);
    static fcube* allocateScratchCubeFloat(const size_t rows, const size_t cols, const size_t slices);
    static cx_cube* allocateReciprocalScratchCube(const size_t rows, const size_t cols, const size_t slices);
};

#endif	/* HELPERFUNCTIONS_HPP */


/** Interface.hpp
  *
  * This interface defines a transparent mapping for Dense Eigen types
  * that exposes only the Scalar template parameter so that template
  * specialization can be performed in Cython.
  *
  * This requires a C++11 compliant compiler.
  */
#ifndef EIGEN_PYTHON_HPP
#define EIGEN_PYTHON_HPP
#include <Eigen/Dense>

template <typename BaseType>
class Map : public Eigen::Map<BaseType>
{
    public:
        typedef Eigen::Map<BaseType> Base;
        typedef typename Base::Scalar Scalar;

        /** default constructor
          *
          * This sets the pointer to NULL, so must NOT be accessed before initialized!
          * For auto-generated Cython code, this is not an issue
          */
        Map() : Base(NULL,
            BaseType::RowsAtCompileTime == Eigen::Dynamic ? 0 : BaseType::RowsAtCompileTime,
            BaseType::ColsAtCompileTime == Eigen::Dynamic ? 0 : BaseType::ColsAtCompileTime) {
        }

        /** Vector constructor
          *
          * \param data pointer to the data array
          * \param size the number of elements in the array
          */
        Map(Scalar * data, Eigen::Index size) : Base(data,
            BaseType::RowsAtCompileTime == Eigen::Dynamic ? size : BaseType::RowsAtCompileTime,
            BaseType::ColsAtCompileTime == Eigen::Dynamic ? 1 : BaseType::ColsAtCompileTime) {
        }

        /** Matrix constructor
          *
          * \param data pointer to the data array
          * \param rows the number of rows (M) in the matrix
          * \param cols the number of cols (N) in the matrix
          */
        Map(Scalar * data, Eigen::Index rows, Eigen::Index cols) : Base(data,
            BaseType::RowsAtCompileTime == Eigen::Dynamic ? rows : BaseType::RowsAtCompileTime,
            BaseType::ColsAtCompileTime == Eigen::Dynamic ? cols : BaseType::ColsAtCompileTime) {
        }

        /** Assignment (initialization) operator
          *
          * \param other the initialized Map to replace this Map with
          */
        Map<BaseType>& operator=(const Map<BaseType>& other) {
            new (this) Base(other.m_data,
                BaseType::RowsAtCompileTime == Eigen::Dynamic ? other.m_rows.value() : BaseType::RowsAtCompileTime,
                BaseType::ColsAtCompileTime == Eigen::Dynamic ? other.m_cols.value() : BaseType::ColsAtCompileTime);
            return *this;
        }

        /** Copy constructor
          * \param other the other Eigen object
          */
        template <typename OtherDerived>
        Map<BaseType>(const Eigen::MatrixBase<OtherDerived>& other) : Base(other) {}

        /** General assignment operator
          * \param other the other Eigen object
          */
        template <typename OtherDerived>
        Map<BaseType>& operator=(const Eigen::MatrixBase<OtherDerived>& other) {
            this->Base::operator=(other);
            return *this;
        }
};

template <typename Scalar>
using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;

template <typename Scalar>
using MatrixMap = Map<Matrix<Scalar> >;

template <typename Scalar>
using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;

template <typename Scalar>
using VectorMap = Map<Vector<Scalar> >;

template <typename Scalar>
using Array = Eigen::Array<Scalar, Eigen::Dynamic, Eigen::Dynamic>;

#endif // EIGEN_PYTHON_HPP


// Copyright 2018-2021 Jan Niklas Hasse <jhasse@bixense.com>
// For conditions of distribution and use, see copyright notice in LICENSE.txt
/// Contains jngl::Vec2 class
/// @file
#pragma once

#if !defined(__has_include) || __has_include("boost/version.hpp")
#include <boost/version.hpp>
#endif
#include <iosfwd>

namespace jngl {

/// Two-dimensional vector
///
/// JNGL doesn't provide any linear algebra functions but relies on the excelent
/// [Boost.QVM](https://www.boost.org/doc/libs/release/libs/qvm/index.html):
///
/// \code
/// #include <boost/qvm/vec_operations.hpp>
///
/// jngl::Vec2 v(3, 4);
/// double length = boost::qvm::mag(v);
/// assert(length == 5);
/// \endcode
///
/// If the header `boost/qvm/vec_traits.hpp` is available, common math operators will also be
/// brought into the global namespace:
///
/// \code
/// jngl::Vec2 v(3, 4);
/// v *= 2.0;
/// assert(std::lround(v.x) == 6);
/// \endcode
class Vec2 {
public:
	/// Null vector
	Vec2();

	/// Creates a vector {x, y}
	Vec2(double x, double y);

	/// x component
	double x = 0;

	/// y component
	double y = 0;

	/// Helper function to use with cereal or Boost.Serialization
	template <class Archive> void serialize(Archive& ar, const unsigned int) {
		ar(x, y);
	}
};

} // namespace jngl

/// Prints the vector like this: [x=…, y=…]
std::ostream& operator<<(std::ostream&, const jngl::Vec2&);

#if BOOST_VERSION >= 106200 || __has_include("boost/qvm/vec_traits.hpp")
#include <boost/qvm/vec_traits.hpp>

namespace boost::qvm {
template <> struct vec_traits<jngl::Vec2> {
	static int const dim = 2;
	using scalar_type = double;

	template <int I> static inline scalar_type& write_element(jngl::Vec2& v) {
		return (&v.x)[I];
	}
	template <int I> static inline scalar_type read_element(const jngl::Vec2& v) {
		return (&v.x)[I];
	}

	static inline scalar_type& write_element_idx(int i, jngl::Vec2& v) {
		return (&v.x)[i];
	}
	static inline scalar_type read_element_idx(int i, jngl::Vec2 const& v) {
		return (&v.x)[i];
	}
};
} // namespace boost::qvm

#include <boost/qvm/vec_operations.hpp>

using boost::qvm::operator+=;
using boost::qvm::operator*=;
using boost::qvm::operator/=;
using boost::qvm::operator-=;
using boost::qvm::operator==;
using boost::qvm::operator!=;
using boost::qvm::operator+;
using boost::qvm::operator-;
using boost::qvm::operator/;
using boost::qvm::operator*;
#endif


//          Copyright Rein Halbersma 2014-2021.
// 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)

#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS
#define BOOST_MPL_LIMIT_VECTOR_SIZE 50

#include <set/compatible/flat_set.hpp>  // op<=>
#include <set/compatible/set.hpp>       // op<=>
#include <set/exhaustive.hpp>           // empty_set, full_set, all_values, all_cardinality_sets,
                                        // all_singleton_arrays, all_singleton_ilists, all_singleton_sets
#include <set/primitives.hpp>           // constructor, mem_const_reference, mem_const_iterator, mem_front, mem_back,
                                        // mem_empty, mem_size, mem_max_size, mem_insert, mem_erase, mem_clear,
                                        // op_equal, op_compare_three_way, fn_iterator, fn_size, fn_ssize, fn_empty
#include <xstd/bit_set.hpp>             // bit_set
#include <boost/container/flat_set.hpp> // flat_set
#include <boost/mpl/vector.hpp>         // vector
#include <boost/test/unit_test.hpp>     // BOOST_AUTO_TEST_SUITE, BOOST_AUTO_TEST_SUITE_END, BOOST_AUTO_TEST_CASE_TEMPLATE
#include <cstdint>                      // uint8_t, uint16_t, uint32_t, uint64_t
#include <set>                          // bitset

BOOST_AUTO_TEST_SUITE(Linear)

using namespace xstd;

using int_set_types = boost::mpl::vector
<       std::set<int>
,       boost::container::flat_set<int>
,       bit_set<  0, uint8_t>
,       bit_set<  1, uint8_t>
,       bit_set<  7, uint8_t>
,       bit_set<  8, uint8_t>
,       bit_set<  9, uint8_t>
,       bit_set< 15, uint8_t>
,       bit_set< 16, uint8_t>
,       bit_set< 17, uint8_t>
,       bit_set< 24, uint8_t>
,       bit_set<  0, uint16_t>
,       bit_set<  1, uint16_t>
,       bit_set< 15, uint16_t>
,       bit_set< 16, uint16_t>
,       bit_set< 17, uint16_t>
,       bit_set< 31, uint16_t>
,       bit_set< 32, uint16_t>
,       bit_set< 33, uint16_t>
,       bit_set< 48, uint16_t>
,       bit_set<  0, uint32_t>
,       bit_set<  1, uint32_t>
,       bit_set< 31, uint32_t>
,       bit_set< 32, uint32_t>
,       bit_set< 33, uint32_t>
,       bit_set< 63, uint32_t>
,       bit_set< 64, uint32_t>
,       bit_set< 65, uint32_t>
#if defined(__GNUG__) || defined(_MSC_VER) && defined(WIN64)
,       bit_set<  0, uint64_t>
,       bit_set<  1, uint64_t>
,       bit_set< 63, uint64_t>
,       bit_set< 64, uint64_t>
,       bit_set< 65, uint64_t>
#endif
#if defined(__GNUG__)
,       bit_set<  0, __uint128_t>
,       bit_set<  1, __uint128_t>
,       bit_set<127, __uint128_t>
,       bit_set<128, __uint128_t>
,       bit_set<129, __uint128_t>
#endif
>;

BOOST_AUTO_TEST_CASE_TEMPLATE(IntSet, T, int_set_types)
{
        all_singleton_arrays<T>([](auto const& a1) {
                constructor<T>()(a1.begin(), a1.end());
        });
        all_singleton_ilists<T>([](auto ilist1) {
                constructor<T>()(ilist1);
        });

        all_cardinality_sets<T>(mem_const_reference());
        all_singleton_sets<T>(mem_const_reference());

        all_cardinality_sets<T>([](auto& is) {
                mem_const_iterator()(is);
        });
        all_cardinality_sets<T>([](auto const& is) {
                mem_const_iterator()(is);
        });
        all_singleton_sets<T>([](auto& is1) {
                mem_const_iterator()(is1);
        });
        all_singleton_sets<T>([](auto const& is1) {
                mem_const_iterator()(is1);
        });

        all_cardinality_sets<T>(mem_front());
        all_singleton_sets<T>(mem_front());

        all_cardinality_sets<T>(mem_back());
        all_singleton_sets<T>(mem_back());

        all_cardinality_sets<T>(mem_empty());
        all_cardinality_sets<T>(mem_size());
        all_cardinality_sets<T>(mem_max_size());
        all_cardinality_sets<T>(mem_capacity());

        all_values<T>([](auto const& t) {
                empty_set<T>([=](auto& is0) {
                        mem_emplace()(is0, t);
                });
                empty_set<T>([=](auto& is0) {
                        mem_insert()(is0, t);
                });
        });
        all_values<T>([](auto const& t) {
                empty_set<T>([=](auto& is0) {
                        mem_emplace_hint()(is0, is0.end(), t);
                });
                empty_set<T>([=](auto& is0) {
                        mem_insert()(is0, is0.end(), t);
                });
        });
        all_singleton_arrays<T>([](auto const& a1) {
                empty_set<T>([&](auto& is0) {
                        mem_insert()(is0, a1.begin(), a1.end());
                });
        });
        all_singleton_ilists<T>([](auto ilist1) {
                empty_set<T>([&](auto& is0) {
                        mem_insert()(is0, ilist1);
                });
        });

        full_set<T>([](auto& isN) {
                all_values<T>([&](auto const& k) {
                        mem_erase()(isN, k);
                });
        });

        // boost::container::flat_set<int>::erase invalidates iterators
        if constexpr (!std::is_same_v<T, boost::container::flat_set<int>>) {
                full_set<T>([](auto& isN) {
                        for (auto first = isN.begin(), last = isN.end(); first != last; /* expression inside loop */) {
                                mem_erase()(isN, first++);
                        }
                });
                full_set<T>([](auto& isN) {
                        mem_erase()(isN, isN.begin(), isN.end());
                });
        }

        all_cardinality_sets<T>(mem_clear());
        all_singleton_sets<T>(mem_clear());

        all_cardinality_sets<T>(op_equal_to());
        all_singleton_sets<T>(op_equal_to());

        all_cardinality_sets<T>(op_compare_three_way());
        all_singleton_sets<T>(op_compare_three_way());

        all_cardinality_sets<T>([](auto& is) {
                fn_iterator()(is);
        });
        all_cardinality_sets<T>([](auto const& is) {
                fn_iterator()(is);
        });

        all_cardinality_sets<T>(fn_size());
        all_cardinality_sets<T>(fn_ssize());
        all_cardinality_sets<T>(fn_empty());
}

BOOST_AUTO_TEST_SUITE_END()


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//

#include <boost/test/unit_test.hpp>
#include "armnnTfParser/ITfParser.hpp"
#include "ParserPrototxtFixture.hpp"

BOOST_AUTO_TEST_SUITE(TensorflowParser)

struct AdditionFixture : public armnnUtils::ParserPrototxtFixture<armnnTfParser::ITfParser>
{
    AdditionFixture()
    {
        m_Prototext = "node { \n"
            "    name: \"graphInput\" \n"
            "    op: \"Placeholder\" \n"
            "    attr { \n"
            "      key: \"dtype\" \n"
            "      value { \n"
            "        type: DT_FLOAT \n"
            "      } \n"
            "    } \n"
            "    attr { \n"
            "      key: \"shape\" \n"
            "      value { \n"
            "        shape { \n"
            "        } \n"
            "      } \n"
            "    } \n"
            "  } \n"
            "  node { \n"
            "    name: \"softmax1\" \n"
            "    op: \"Softmax\" \n"
            "    input: \"graphInput\" \n"
            "    attr { \n"
            "      key: \"T\" \n"
            "      value { \n"
            "        type: DT_FLOAT \n"
            "      } \n"
            "    } \n"
            "  }\n"
            "  node {\n"
            "    name: \"softmax2\"\n"
            "    op : \"Softmax\"\n"
            "    input: \"graphInput\"\n"
            "    attr { \n"
            "      key: \"T\" \n"
            "      value { \n"
            "        type: DT_FLOAT \n"
            "      } \n"
            "    } \n"
            "  }\n"
            "  node {\n"
            "    name: \"addition\"\n"
            "    op : \"Add\"\n"
            "    input: \"softmax1\"\n"
            "    input: \"softmax2\"\n"
            "    attr { \n"
            "      key: \"T\" \n"
            "      value { \n"
            "        type: DT_FLOAT \n"
            "      } \n"
            "    } \n"
            "  }\n";

        SetupSingleInputSingleOutput({ 1, 7 }, "graphInput", "addition");
    }
};

BOOST_FIXTURE_TEST_CASE(ParseAddition, AdditionFixture)
{
    RunTest<2>({ 0, 0, 10000, 0, 0, 0, 0 }, { 0, 0, 2, 0, 0, 0, 0 });
}

BOOST_AUTO_TEST_SUITE_END()


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

  @copyright 2016 NumScale SAS
  @copyright 2016 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_SIMD_FUNCTION_SHUFFLE_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_SHUFFLE_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/hierarchy/simd.hpp>

namespace boost { namespace simd { namespace ext
{
  namespace bd = boost::dispatch;
  namespace bs = boost::simd;
  template < int N1, int N2,  int N3, int N4, typename T>
  BOOST_FORCEINLINE T shuffle(const T & a) BOOST_NOEXCEPT
  {
    return {a[N1],a[N2],a[N3],a[N4]};
  }
  template < int N1, int N2, int N3, int N4, typename T>
  BOOST_FORCEINLINE T shuffle(const T & a0, const T & a1) BOOST_NOEXCEPT
  {
    static const int N = 4;
    return { N1 <  N ? a0[N1] : a1[N1-N]
        ,    N2 <  N ? a0[N2] : a1[N2-N]
        ,    N3 <  N ? a0[N3] : a1[N3-N]
        ,    N4 <  N ? a0[N4] : a1[N4-N]
        };
  }
  template < int N1, int N2, typename T>
  BOOST_FORCEINLINE T shuffle(const T & a) BOOST_NOEXCEPT
  {
    return {a[N1],a[N2]};
  }
  template < int N1, int N2, typename T>
  BOOST_FORCEINLINE T shuffle(const T & a0, const T & a1) BOOST_NOEXCEPT
  {
    static const int N = 2;
    return { N1 <  N ? a0[N1] : a1[N1-N]
        ,    N2 <  N ? a0[N2] : a1[N2-N]
        };
  }

} } }

#endif


// -*- C++ -*-
//
//

#include <sstream>
#include <boost/python.hpp>
#include "mccomponents/kernels/sample/AbstractSvQ.h"
#include "mccomponents/kernels/sample/SQ/GridSvQ.h"

namespace wrap_mccomponents {

  using namespace mccomponents::sample;

  double fxyz_get( const fxyz & fxyz, const double &x, const double &y, const double &z)
  {
    return fxyz(x, y, z);
  }

  fxyz * new_fxyz(double xbegin, double xend, double xstep,
                  double ybegin, double yend, double ystep,
                  double zbegin, double zend, double zstep,
                  std::vector<double> & v )
  {
    return new fxyz( xbegin, xend, xstep, ybegin, yend, ystep, zbegin, zend, zstep, v.begin() );
  }

  void wrap_GridSvQ()
  {
    using namespace boost::python;

    typedef GridSvQ w_t;

    class_<w_t, bases<mccomponents::sample::AbstractSvQ>, boost::noncopyable >
      ("GridSvQ",
       init<const fxyz &>()
       [with_custodian_and_ward<1, 2> () ]
       )
      ;

    class_<fxyz>
      ("fxyz", no_init)
      .def( "__call__", fxyz_get)
      ;

    def( "new_fxyz", new_fxyz,
         with_custodian_and_ward_postcall<0, 10,
         return_value_policy< manage_new_object > > () )
      ;
  }
}

// End of file