#ifndef ARPACA_ARPACA_HPP_
#define ARPACA_ARPACA_HPP_

#include <algorithm>
#include <stdexcept>
#include <vector>
#include <Eigen/Core>

// ARPACK interface
extern "C" {

// Lanczos method (double precision)
void dsaupd_(int* ido, char* bmat, int* n, char* which, int* nev, double* tol,
             double* resid, int* ncv, double* v, int* ldv, int* iparam,
             int* ipntr, double* workd, double* workl, int* lworkl, int* info);

// Lanczos method (single precision)
void ssaupd_(int* ido, char* bmat, int* n, char* which, int* nev, float* tol,
             float* resid, int* ncv, float* v, int* ldv, int* iparam,
             int* ipntr, float* workd, float* workl, int* lworkl, int* info);

// Eigenvalue problem solver for result of dsaupd_
void dseupd_(int* rvec, char* howmny, int* select, double* d, double* z,
             int* ldz, double* sigma, char* bmat, int* n, char* which, int* nev,
             double* tol, double* resid, int* ncv, double* v, int* ldv,
             int* iparam, int* ipntr, double* workd, double* workl, int* lworkl,
             int* info);

// Eigenvalue problem solver for result of ssaupd_
void sseupd_(int* rvec, char* howmny, int* select, float* d, float* z, int* ldz,
             float* sigma, char* bmat, int* n, char* which, int* nev,
             float* tol, float* resid, int* ncv, float* v, int* ldv,
             int* iparam, int* ipntr, float* workd, float* workl, int* lworkl,
             int* info);

}  // extern "C"

namespace arpaca {

// Type of eigenvalue to solve.
// Arpaca::SymmetricEigenSolver computes part of eigenvalues and eigenvectors.
// EigenvalueType indicates which part is to be solved.
enum EigenvalueType {
  ALGEBRAIC_LARGEST,  // Largest as real numbers
  ALGEBRAIC_SMALLEST,  // Smallest as real numbers
  ALGEBRAIC_BOTH_END,  // Both end as real numbers
  MAGNITUDE_LARGEST,  // Largest as absolute values
  MAGNITUDE_SMALLEST,  // Smallest as absolute values
};

namespace detail {

inline const char* GetNameOfEigenvalueType(EigenvalueType type)
{
  switch (type) {
    case ALGEBRAIC_LARGEST: return "LA";
    case ALGEBRAIC_SMALLEST: return "SA";
    case ALGEBRAIC_BOTH_END: return "BE";
    case MAGNITUDE_LARGEST: return "LM";
    case MAGNITUDE_SMALLEST: return "SM";
  }
  throw std::invalid_argument("Invalid eigenvalue type");
}

inline void GetNameOfEigenvalueType(EigenvalueType type, char* to)
{
  const char* from = GetNameOfEigenvalueType(type);
  std::copy(from, from + 3, to);
}

template<typename Scalar>
void saupd(int* ido, char* bmat, int* n, char* which, int* nev, Scalar* tol,
           Scalar* resid, int* ncv, Scalar* v, int* ldv, int* iparam,
           int* ipntr, Scalar* workd, Scalar* workl, int* lworkl, int* info)
{
  dsaupd_(ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, ipntr,
          workd, workl, lworkl, info);
}

template<>
inline void saupd<float>(int* ido, char* bmat, int* n, char* which, int* nev,
                         float* tol, float* resid, int* ncv, float* v, int* ldv,
                         int* iparam, int* ipntr, float* workd, float* workl,
                         int* lworkl, int* info)
{
  ssaupd_(ido, bmat, n, which, nev, tol, resid, ncv, v, ldv, iparam, ipntr,
          workd, workl, lworkl, info);
}

template<typename Scalar>
void seupd(int* rvec, char* howmny, int* select, Scalar* d, Scalar* z, int* ldz,
           Scalar* sigma, char* bmat, int* n, char* which, int* nev,
           Scalar* tol, Scalar* resid, int* ncv, Scalar* v, int* ldv,
           int* iparam, int* ipntr, Scalar* workd, Scalar* workl, int* lworkl,
           int* info)
{
  dseupd_(rvec, howmny, select, d, z, ldz, sigma, bmat, n, which, nev, tol,
          resid, ncv, v, ldv, iparam, ipntr, workd, workl, lworkl, info);
}

template<>
inline void seupd<float>(int* rvec, char* howmny, int* select, float* d,
                         float* z, int* ldz, float* sigma, char* bmat, int* n,
                         char* which, int* nev, float* tol, float* resid,
                         int* ncv, float* v, int* ldv, int* iparam, int* ipntr,
                         float* workd, float* workl, int* lworkl, int* info)
{
  sseupd_(rvec, howmny, select, d, z, ldz, sigma, bmat, n, which, nev, tol,
          resid, ncv, v, ldv, iparam, ipntr, workd, workl, lworkl, info);
}

}  // namespace detail

// Eigenvalue problem solver for symmetric matrix (especially sparse one).
// It partly computes eigenvalues and eigenvectors of a symmetric matrix A using
// ARPACK, which implements Implicitly Restarted Arnoldi (IRA) method.
template<typename Scalar = double>
class SymmetricEigenSolver {
 public:
  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic> Matrix;
  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> Vector;

  SymmetricEigenSolver()
      : num_lanczos_vectors_(100),
        max_iterations_(100),
        tolerance_(-1)
  {
    SetEigenvalueType(MAGNITUDE_LARGEST);
  }

  // Sets # of Lanczos vectors.
  // From two to four times larger than # of objective eigenvalues is
  // recommended.
  //
  // NOTE: IRA approximates subspace containing the objective eigenvectors by
  // constructing |num| orthogonal basis vectors in each iteration, which is
  // improved for each iteration. Consequently, too small |num| causes slow
  // convergence, while too large |num| requires large size of memory and even
  // make each iteration too slow.
  void SetNumLanczosVectors(int num)
  {
    num_lanczos_vectors_ = num;
  }

  // Sets max # of Lanczos iterations.
  void SetMaxIterations(int num)
  {
    if (num <= 0)
      throw std::invalid_argument("Given # of iterations is not positive");
    max_iterations_ = num;
  }

  // Sets precision tolerance for computation.
  // Negative value means machine precision.
  void SetTolerance(Scalar tolerance)
  {
    tolerance_ = tolerance;
  }

  // Sets the type of eigenvalues to solve.
  // See EigenvalueType for detail.
  void SetEigenvalueType(EigenvalueType type)
  {
    detail::GetNameOfEigenvalueType(type, eigenvalue_type_name_);
  }

  // Solves the eigenvalue problem of a real symmetrix matrix A.
  // The matrix A is not set explicitly. Solve() only needs the operator
  // y = A * x.
  //
  // dimension:       Dimension of space (rows or cols of the matrix A).
  // num_eigenvalues: # of eigenvalues to compute.
  // op:              Functor that computes operation y = A * x. Signature is
  //                  op(x, y), where x and y are slices of Eigen::VectorX{d,f},
  //                  i.e. return values of Eigen::VectorX{d,f}::middleRows().
  template<typename OperatorATimes>
  void Solve(int dimension, int num_eigenvalues, OperatorATimes op)
  {
    if (dimension <= 0)
      throw std::invalid_argument("Given dimension is not positive");
    if (num_eigenvalues <= 0)
      throw std::invalid_argument("Given num_eigenvalues is not positive");

    // Eigenvalues (Lanczos)
    int info = 0;

    num_eigenvalues = std::max(std::min(num_eigenvalues, dimension), 0);

    int num_lanczos_vectors = num_lanczos_vectors_;
    if (num_lanczos_vectors <= 0)
      num_lanczos_vectors = num_eigenvalues * 3;
    else if (num_lanczos_vectors <= num_eigenvalues)
      num_lanczos_vectors = num_eigenvalues + 1;

    num_lanczos_vectors = std::min(num_lanczos_vectors, dimension);

    char bmat[2] = { 'I' };
    int leading_dimension = dimension;

    int iparam[11] = {}, ipntr[11] = {};
    iparam[0] = 1;  // Use effective shift
    iparam[2] = max_iterations_;
    iparam[3] = 1;  // block size
    iparam[6] = 1;  // Solve standard eigenvalue problem

    // Calculation workspace
    int lworkl = (num_lanczos_vectors + 8) * num_lanczos_vectors;
    Vector workd(3 * dimension), workl(lworkl);
    std::vector<Scalar> residue(dimension);
    Matrix lanczos_vectors(dimension, num_lanczos_vectors);

    // Lanczos iterations
    for (int mode = 0; mode != 99; ) {
      detail::saupd(&mode, bmat, &dimension, eigenvalue_type_name_,
                    &num_eigenvalues, &tolerance_, &residue[0],
                    &num_lanczos_vectors, lanczos_vectors.data(),
                    &leading_dimension, iparam, ipntr, workd.data(),
                    workl.data(), &lworkl, &info);

      switch (mode) {
        case -1:
          op(workd.middleRows(ipntr[0] - 1, dimension),
             workd.middleRows(ipntr[1] - 1, dimension));
          break;
        case 1:
          op(workd.middleRows(ipntr[2] - 1, dimension),
             workd.middleRows(ipntr[1] - 1, dimension));
          workd.middleRows(ipntr[2] - 1, dimension) =
              workd.middleRows(ipntr[0] - 1, dimension);
          break;
        case 2:
          workd.middleRows(ipntr[1] - 1, dimension) =
              workd.middleRows(ipntr[0] - 1, dimension);
          break;
      }
    }

    info_ = info;
    num_actual_iterations_ = iparam[2];
    num_converged_eigenvalues_ = iparam[4];

    // Eigenvectors
    int rvec = 1;  // Compute eigenvectors
    char howmany[2] = { 'A' };
    std::vector<int> select(num_lanczos_vectors);
    Scalar sigma;

    eigenvectors_.resize(dimension, num_eigenvalues);
    eigenvalues_.resize(num_eigenvalues);

    detail::seupd(&rvec, howmany, &select[0], eigenvalues_.data(),
                  eigenvectors_.data(), &dimension, &sigma, bmat, &dimension,
                  eigenvalue_type_name_, &num_eigenvalues, &tolerance_,
                  &residue[0], &num_lanczos_vectors, lanczos_vectors.data(),
                  &leading_dimension, iparam, ipntr, workd.data(), workl.data(),
                  &lworkl, &info);
  }

  // Gets the computed eigenvectors.
  // Returned matrix contains each eigenvector in each column.
  const Matrix& eigenvectors() const
  {
    return eigenvectors_;
  }

  // Gets the computed eigenvalues.
  const Vector& eigenvalues() const
  {
    return eigenvalues_;
  }

  Matrix MoveEigenvectors()
  {
    Matrix eigenvectors;
    eigenvectors.swap(eigenvectors_);
    return eigenvectors;
  }

  Vector MoveEigenvalues()
  {
    Vector eigenvalues;
    eigenvalues.swap(eigenvalues_);
    return eigenvalues;
  }

  int num_actual_iterations() const
  {
    return num_actual_iterations_;
  }

  int num_converged_eigenvalues() const
  {
    return num_converged_eigenvalues_;
  }

  // Gets information message based on error code of ARPACK.
  const char* GetInfo() const
  {
    switch (info_) {
      case 0: return "Normal exit";
      case 1: return "Not converged";
      case 3: return "num_lanczos_vectors may be too small";
      case -8: return "Error on tridiagonal eigenvalue calculation";
      case -9999: return "Could not build an Arnoldi factorization";
    }
    return "Arpaca internal error";
  }

 private:
  Matrix eigenvectors_;
  Vector eigenvalues_;

  int num_lanczos_vectors_;
  int max_iterations_;
  Scalar tolerance_;

  int info_;
  int num_actual_iterations_;
  int num_converged_eigenvalues_;

  char eigenvalue_type_name_[3];
};

// Default operator for the case the matrix A is explicitly given.
template<typename MatrixType>
class DefaultOperator {
 public:
  explicit DefaultOperator(MatrixType& m)
      : m_(m)
  {}

  template<typename X, typename Y>
  void operator()(X x, Y y) const
  {
    y = m_ * x;
  }

 private:
  MatrixType& m_;
};

template<typename Matrix>
inline DefaultOperator<Matrix> MakeDefaultOperator(Matrix& A)
{
  return DefaultOperator<Matrix>(A);
}

// Solves the eigenvalue problem of a real symmetrix matrix A, where the matrix
// A is explicitly given.
template<typename Matrix>
SymmetricEigenSolver<typename Matrix::Scalar> Solve(
    const Matrix& A,
    int num_eigenvalues,
    EigenvalueType type = MAGNITUDE_LARGEST,
    int num_lanczos_vectors = 0,
    int max_iterations = 10000,
    typename Matrix::Scalar tolerance = -1)
{
  SymmetricEigenSolver<typename Matrix::Scalar> solver;
  solver.SetEigenvalueType(type);
  solver.SetMaxIterations(max_iterations);
  solver.SetNumLanczosVectors(num_lanczos_vectors);
  solver.SetTolerance(tolerance);

  solver.Solve(A.rows(), num_eigenvalues, MakeDefaultOperator(A));

  return solver;
}

}  // namespace arpaca

#endif  // ARPACA_ARPACA_HPP_


/*
	This file is part of solidity.

	solidity is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	solidity is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with solidity.  If not, see <http://www.gnu.org/licenses/>.
*/

#include <test/yulPhaser/TestHelpers.h>

#include <tools/yulPhaser/PairSelections.h>
#include <tools/yulPhaser/SimulationRNG.h>

#include <boost/test/unit_test.hpp>

#include <algorithm>
#include <tuple>
#include <vector>

using namespace std;

namespace solidity::phaser::test
{

BOOST_AUTO_TEST_SUITE(Phaser)
BOOST_AUTO_TEST_SUITE(PairSelectionsTest)
BOOST_AUTO_TEST_SUITE(RandomPairSelectionTest)

BOOST_AUTO_TEST_CASE(materialise_should_return_random_values_with_equal_probabilities)
{
	constexpr int collectionSize = 10;
	constexpr int selectionSize = 100;
	constexpr double relativeTolerance = 0.1;
	constexpr double expectedValue = (collectionSize - 1) / 2.0;
	constexpr double variance = (collectionSize * collectionSize - 1) / 12.0;

	SimulationRNG::reset(1);
	vector<tuple<size_t, size_t>> pairs = RandomPairSelection(selectionSize).materialise(collectionSize);
	vector<size_t> samples;
	for (auto& [first, second]: pairs)
	{
		samples.push_back(first);
		samples.push_back(second);
	}

	BOOST_TEST(abs(mean(samples) - expectedValue) < expectedValue * relativeTolerance);
	BOOST_TEST(abs(meanSquaredError(samples, expectedValue) - variance) < variance * relativeTolerance);
}

BOOST_AUTO_TEST_CASE(materialise_should_return_only_values_that_can_be_used_as_collection_indices)
{
	const size_t collectionSize = 200;

	vector<tuple<size_t, size_t>> pairs = RandomPairSelection(0.5).materialise(collectionSize);

	BOOST_TEST(pairs.size() == 100);
	BOOST_TEST(all_of(pairs.begin(), pairs.end(), [&](auto const& pair){ return get<0>(pair) <= collectionSize; }));
	BOOST_TEST(all_of(pairs.begin(), pairs.end(), [&](auto const& pair){ return get<1>(pair) <= collectionSize; }));
}

BOOST_AUTO_TEST_CASE(materialise_should_never_return_a_pair_of_identical_indices)
{
	vector<tuple<size_t, size_t>> pairs = RandomPairSelection(0.5).materialise(100);

	BOOST_TEST(pairs.size() == 50);
	BOOST_TEST(all_of(pairs.begin(), pairs.end(), [](auto const& pair){ return get<0>(pair) != get<1>(pair); }));
}

BOOST_AUTO_TEST_CASE(materialise_should_return_number_of_pairs_thats_a_fraction_of_collection_size)
{
	BOOST_TEST(RandomPairSelection(0.0).materialise(10).size() == 0);
	BOOST_TEST(RandomPairSelection(0.3).materialise(10).size() == 3);
	BOOST_TEST(RandomPairSelection(0.5).materialise(10).size() == 5);
	BOOST_TEST(RandomPairSelection(0.7).materialise(10).size() == 7);
	BOOST_TEST(RandomPairSelection(1.0).materialise(10).size() == 10);
}

BOOST_AUTO_TEST_CASE(materialise_should_support_number_of_pairs_bigger_than_collection_size)
{
	BOOST_TEST(RandomPairSelection(2.0).materialise(5).size() == 10);
	BOOST_TEST(RandomPairSelection(1.5).materialise(10).size() == 15);
	BOOST_TEST(RandomPairSelection(10.0).materialise(10).size() == 100);
}

BOOST_AUTO_TEST_CASE(materialise_should_round_the_number_of_pairs_to_the_nearest_integer)
{
	BOOST_TEST(RandomPairSelection(0.49).materialise(3).size() == 1);
	BOOST_TEST(RandomPairSelection(0.50).materialise(3).size() == 2);
	BOOST_TEST(RandomPairSelection(0.51).materialise(3).size() == 2);

	BOOST_TEST(RandomPairSelection(1.51).materialise(3).size() == 5);

	BOOST_TEST(RandomPairSelection(0.01).materialise(2).size() == 0);
	BOOST_TEST(RandomPairSelection(0.01).materialise(3).size() == 0);
}

BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_collection_is_empty)
{
	BOOST_TEST(RandomPairSelection(0).materialise(0).empty());
	BOOST_TEST(RandomPairSelection(0.5).materialise(0).empty());
	BOOST_TEST(RandomPairSelection(1.0).materialise(0).empty());
	BOOST_TEST(RandomPairSelection(2.0).materialise(0).empty());
}

BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_collection_has_one_element)
{
	BOOST_TEST(RandomPairSelection(0).materialise(1).empty());
	BOOST_TEST(RandomPairSelection(0.5).materialise(1).empty());
	BOOST_TEST(RandomPairSelection(1.0).materialise(1).empty());
	BOOST_TEST(RandomPairSelection(2.0).materialise(1).empty());
}

BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE(PairMosaicSelectionTest)

using IndexPairs = vector<tuple<size_t, size_t>>;

BOOST_AUTO_TEST_CASE(materialise)
{
	BOOST_TEST(PairMosaicSelection({{1, 1}}, 0.5).materialise(4) == IndexPairs({{1, 1}, {1, 1}}));
	BOOST_TEST(PairMosaicSelection({{1, 1}}, 1.0).materialise(4) == IndexPairs({{1, 1}, {1, 1}, {1, 1}, {1, 1}}));
	BOOST_TEST(PairMosaicSelection({{1, 1}}, 2.0).materialise(4) == IndexPairs({{1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}, {1, 1}}));
	BOOST_TEST(PairMosaicSelection({{1, 1}}, 1.0).materialise(2) == IndexPairs({{1, 1}, {1, 1}}));

	IndexPairs pairs1{{0, 1}, {1, 0}};
	BOOST_TEST(PairMosaicSelection(pairs1, 0.5).materialise(4) == IndexPairs({{0, 1}, {1, 0}}));
	BOOST_TEST(PairMosaicSelection(pairs1, 1.0).materialise(4) == IndexPairs({{0, 1}, {1, 0}, {0, 1}, {1, 0}}));
	BOOST_TEST(PairMosaicSelection(pairs1, 2.0).materialise(4) == IndexPairs({{0, 1}, {1, 0}, {0, 1}, {1, 0}, {0, 1}, {1, 0}, {0, 1}, {1, 0}}));
	BOOST_TEST(PairMosaicSelection(pairs1, 1.0).materialise(2) == IndexPairs({{0, 1}, {1, 0}}));

	IndexPairs pairs2{{3, 2}, {2, 3}, {1, 0}, {1, 1}};
	BOOST_TEST(PairMosaicSelection(pairs2, 0.5).materialise(4) == IndexPairs({{3, 2}, {2, 3}}));
	BOOST_TEST(PairMosaicSelection(pairs2, 1.0).materialise(4) == IndexPairs({{3, 2}, {2, 3}, {1, 0}, {1, 1}}));
	BOOST_TEST(PairMosaicSelection(pairs2, 2.0).materialise(4) == IndexPairs({{3, 2}, {2, 3}, {1, 0}, {1, 1}, {3, 2}, {2, 3}, {1, 0}, {1, 1}}));

	IndexPairs pairs3{{1, 0}, {1, 1}, {1, 0}, {1, 1}};
	BOOST_TEST(PairMosaicSelection(pairs3, 1.0).materialise(2) == IndexPairs({{1, 0}, {1, 1}}));
}

BOOST_AUTO_TEST_CASE(materialise_should_round_indices)
{
	IndexPairs pairs{{4, 4}, {3, 3}, {2, 2}, {1, 1}, {0, 0}};
	BOOST_TEST(PairMosaicSelection(pairs, 0.49).materialise(5) == IndexPairs({{4, 4}, {3, 3}}));
	BOOST_TEST(PairMosaicSelection(pairs, 0.50).materialise(5) == IndexPairs({{4, 4}, {3, 3}, {2, 2}}));
	BOOST_TEST(PairMosaicSelection(pairs, 0.51).materialise(5) == IndexPairs({{4, 4}, {3, 3}, {2, 2}}));
}

BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_collection_is_empty)
{
	BOOST_TEST(PairMosaicSelection({{1, 1}}, 1.0).materialise(0).empty());
	BOOST_TEST(PairMosaicSelection({{1, 1}, {3, 3}}, 2.0).materialise(0).empty());
	BOOST_TEST(PairMosaicSelection({{5, 5}, {4, 4}, {3, 3}, {2, 2}}, 0.5).materialise(0).empty());
}

BOOST_AUTO_TEST_CASE(materialise_should_return_no_pairs_if_collection_has_one_element)
{
	IndexPairs pairs{{4, 4}, {3, 3}, {2, 2}, {1, 1}, {0, 0}};
	BOOST_TEST(PairMosaicSelection(pairs, 0.0).materialise(1).empty());
	BOOST_TEST(PairMosaicSelection(pairs, 0.5).materialise(1).empty());
	BOOST_TEST(PairMosaicSelection(pairs, 1.0).materialise(1).empty());
	BOOST_TEST(PairMosaicSelection(pairs, 7.0).materialise(1).empty());
}

BOOST_AUTO_TEST_CASE(materialise_should_clamp_indices_at_collection_size)
{
	IndexPairs pairs{{4, 4}, {3, 3}, {2, 2}, {1, 1}, {0, 0}};
	BOOST_TEST(PairMosaicSelection(pairs, 1.0).materialise(4) == IndexPairs({{3, 3}, {3, 3}, {2, 2}, {1, 1}}));
	BOOST_TEST(PairMosaicSelection(pairs, 2.0).materialise(3) == IndexPairs({{2, 2}, {2, 2}, {2, 2}, {1, 1}, {0, 0}, {2, 2}}));

}

BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_SUITE_END()

}


#include "PacketsReader.hpp"
#include <random>
#include "Hyperloglog.hpp"
#include "Router.hpp"
#include "MathUtils.hpp"

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/filesystem.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/algorithm/string.hpp>

using namespace TDHH;
using namespace std;
using namespace hll;

vector<double>
volume_estimation(vector<int> counters, ofstream &result_stream, const string &dataset_file, DATASET dataset) {
    vector<double> estimations;
    Router router(dataset_file, dataset);
    vector<map<int, double> > result;
    const map<int, map<int, vector<double>>> &m = router.volumeEstimation(counters);
    for (const int &c : counters) {
        const auto &pkts_to_estimation = m.at(c);
        for (const auto &item: pkts_to_estimation) {
            double num_pkts = item.first;
            const auto &pkt_estimations = item.second;
            for (const auto &est: pkt_estimations) {
                result_stream << c << "," << (int) num_pkts << "," << est << endl;
            }
        }
    }
    return estimations;
}

void frequency_estimation(vector<pair<double, double>> params, ofstream &result_stream, const string &dataset_file,
                          const string &real_frequencies_file, DATASET dataset) {
    Router r(dataset_file, dataset);
    const auto &fe = r.frequencyEstimation(params);
    cout << "Got FE results" << endl;
    ifstream is(real_frequencies_file);
    double S;
    is >> S;

    map<pair<double, double>, vector<double>> morethans_per_param;
    map<pair<double, double>, vector<double>> square_sum_per_param;
    for (const auto &param : params) {
        const auto &estimations_per_param = fe.at(param);
        for (int k = 0; k < estimations_per_param.size(); k++) {
            morethans_per_param[param].push_back(0.0);
            square_sum_per_param[param].push_back(0.0);
        }
    }
    cout << "Finished preparing morethans and ss" << endl;

    double number_of_flows = 0;
    while (is.good()) {
        double real_frequency;
        string flow;
        is >> real_frequency;
        is >> flow;
        ++number_of_flows;
        for (const auto &param : params) {
            std::clock_t start;
            start = std::clock();
            double eps = param.first;
            const vector<map<string, double>> &estimations_per_param = fe.at(param);
            for (int k = 0; k < estimations_per_param.size(); k++) {
                const map<string, double> &estimation = estimations_per_param[k];
                double estimated_frequency = 0;
                const auto &iter = estimation.find(flow);
                if (iter != estimation.end()) {
                    estimated_frequency = iter->second;
                }
                double diff = abs(real_frequency - estimated_frequency);
                if (diff > eps * S) {
                    morethans_per_param[param][k] += 1;
                }
                square_sum_per_param[param][k] += pow(diff, 2);
            }
            double duration = (std::clock() - start) / (double) CLOCKS_PER_SEC;
            cout << "took:" << duration << "[s]" << endl;
        }
        if (int(number_of_flows) % 1000 == 0) {
            cout << "Finished reading frequencis of " << number_of_flows << endl;
        }
    }
    cout << "Before reporting results" << endl;

    for (const auto &param : params) {
        double eps = param.first;
        double delta = param.second;
        for (int k = 0; k < morethans_per_param[param].size(); k++) {
            result_stream << eps << "," << delta << "," << morethans_per_param[param][k] << ","
                          << square_sum_per_param[param][k] << "," << number_of_flows << endl;
        }
    }
    cout << "Done." << endl;
}

void heavy_hitter(vector<pair<double, double>> params, double theta, const string &filename, string resDirectory,
                  const string &resfile, DATASET dataset) {
    Router router(filename, dataset);
    map<pair<double, double>, vector<string>> params_to_THH;
    map<pair<double, double>, vector<string>> params_to_miceFlows;
    map<pair<double, double>, vector<string>> params_to_otherFlows;

    for (const auto &param : params) {
        double eps = param.first;
        double delta = param.second;
        ifstream ifsHH(resDirectory + to_string(theta) + string("-") + to_string(eps) + "THH.ser");
        ifstream ifsMice(resDirectory + to_string(theta) + string("-") + to_string(eps) + "mice.ser");
        ifstream ifsOther(resDirectory + to_string(theta) + string("-") + to_string(eps) + "other.ser");
        if (ifsHH.fail() || ifsMice.fail() || ifsOther.fail()) {
            namespace fs = boost::filesystem;
            fs::path someDir(resDirectory);
            fs::directory_iterator end_iter;
            for (fs::directory_iterator dir_iter(someDir); dir_iter != end_iter; ++dir_iter) {
                if (fs::is_regular_file(dir_iter->status())) {
                    string rs(dir_iter->path().c_str());
                    if (boost::starts_with(string(dir_iter->path().c_str()), resfile)) {
                        vector<string> THH;
                        vector<string> miceFlows;
                        vector<string> otherFlows;
                        ifstream is(rs);
                        double S;
                        is >> S;
                        while (is.good()) {
                            double real_frequency;
                            string flow;
                            is >> real_frequency;
                            is >> flow;
                            if (real_frequency >= S * theta) {
                                THH.push_back(flow);
                            } else if (real_frequency < S * (theta - eps)) {
                                miceFlows.push_back(flow);
                            } else {
                                otherFlows.push_back(flow);
                            }
                        }
                        params_to_THH.insert(pair<pair<double, double>, vector<string>>(param, THH));
                        params_to_miceFlows.insert(pair<pair<double, double>, vector<string>>(param, miceFlows));
                        params_to_otherFlows.insert(pair<pair<double, double>, vector<string>>(param, otherFlows));
                    }
                }
            }
            ofstream ofsHH(resDirectory + to_string(theta) + string("-") + to_string(eps) + "THH.ser");
            boost::archive::text_oarchive oa(ofsHH);
            oa << params_to_THH[param];
            ofstream ofsMice(resDirectory + to_string(theta) + string("-") + to_string(eps) + "mice.ser");
            boost::archive::text_oarchive ob(ofsMice);
            ob << params_to_miceFlows[param];
            ofstream ofsOther(resDirectory + to_string(theta) + string("-") + to_string(eps) + "other.ser");
            boost::archive::text_oarchive oc(ofsOther);
            oc << params_to_otherFlows[param];
        } else {
            boost::archive::text_iarchive ia(ifsHH);
            ia >> params_to_THH[param];
            boost::archive::text_iarchive ib(ifsMice);
            ib >> params_to_miceFlows[param];
            boost::archive::text_iarchive ic(ifsOther);
            ic >> params_to_otherFlows[param];
        }
    }
    const auto &samples = router.heavy_hitters(params);
    for (const auto &param : params) {
        vector<double> FPRs;
        vector<double> FNRs;
        double eps = param.first;
        double delta = param.second;
        double chi = ceil(9.0 / (eps * eps) * log2(2.0 / (delta * eps)));
        auto currTHH = params_to_THH.at(param);
        const auto &currMice = params_to_miceFlows.at(param);
        const auto &currOther = params_to_otherFlows.at(param);
        unsigned long universe_size = currTHH.size() + currMice.size() + currOther.size();
        for (const auto &sample : samples.at(param)) {
            vector<string> HH;
            for (const auto &item : sample) {
                string flow = item.first;
                double estimated_frequency = item.second;
                if (item.second >= (theta - eps / 2.0) * chi) {
                    HH.push_back(item.first);
                }
            }
            sort(HH.begin(), HH.end());
            sort(currTHH.begin(), currTHH.end());
            vector<string> difference;
            set_difference(HH.begin(), HH.end(), currTHH.begin(), currTHH.end(), back_inserter(difference));
            double FPR = double(difference.size()) / double(universe_size - currTHH.size());
            FPRs.push_back(FPR);
            vector<string> difference1;
            set_difference(currTHH.begin(), currTHH.end(), HH.begin(), HH.end(), back_inserter(difference1));
            double FNR = double(difference1.size()) / double(currTHH.size());
            FNRs.push_back(FNR);
        }
        cout << eps << "," << delta << "," << theta << ",";
        MEAN(0.0, FPRs, just_mean, false);
        MEAN(0.0, FNRs, just_mean, true);
    }
}

int main(int argc, char **argv) {
    if (argc < 3) {
        throw invalid_argument(
                "Please provide test (VE, FE, HH) and dataset (CAIDA, UCLA, UCLA_FULL, UNIV1, UINV2, TEST).");
    }
    const TEST t = testEnum(argv[1]);
    const DATASET d = datasetEnum(argv[2]);
    const string results_path = "../results/";
    const string results_suffix = ".raw_res";
    const string datasets_path = "../datasets_files/";
    const string datasets_suffix = ".csv";

    const string test = testName(t);
    const string dataset = datasetName(d);
    string DATASET = dataset;
    boost::to_upper(DATASET);

    const string dataset_file = datasets_path + DATASET + "/" + dataset + datasets_suffix;
    const string result_file = results_path + test + "_" + dataset + results_suffix;
    const string real_frequencies_file =
            datasets_path + DATASET + "/" + dataset + "_flows_count-" + getFrequencyLimit(d) + datasets_suffix;

    bool addCSVHeader = false;
    ifstream check_result_stream;
    check_result_stream.open(result_file, fstream::in | fstream::out | fstream::app);
    if (!check_result_stream) {
        throw invalid_argument("Could not open " + result_file + " for reading.");
    }
    if (check_result_stream.peek() == EOF) {
        addCSVHeader = true;
    }
    check_result_stream.close();

    ofstream result_stream;
    result_stream.open(result_file, fstream::in | fstream::out | fstream::app);
    if (!result_stream) {
        throw invalid_argument("Could not open " + result_file + " for writing.");
    }

    switch (t) {
        case TEST::VE: {
            if (addCSVHeader) {
                result_stream << "counters,packets,estimation" << endl;
            }
            vector<int> counters = {128, 512, 1024};
            volume_estimation(counters, result_stream, dataset_file, d);
        }
            break;
        case TEST::FE: {
            if (addCSVHeader) {
                result_stream << "eps,delta,more_than,square_sum,number_of_flows" << endl;
            }
            vector<pair<double, double>> params;
            double epss[] = {0.1, 0.05, 0.01, 0.005};
            for (auto eps : epss) {
                for (int delta_pow = -2; delta_pow > -6; --delta_pow) {
                    double delta = pow(2, delta_pow);
                    params.emplace_back(eps, delta);
                }
            }
            frequency_estimation(params, result_stream, dataset_file, real_frequencies_file, d);
        }
            break;
        case TEST::HH: {
            if (addCSVHeader) {
                result_stream << "eps,delta,theta,FPR,FPR_upper_ci,FPR_lower_ci,FNR,FNR_upper_ci,FNR_lower_ci" << endl;
            }
            double theta = 0.01;
            vector<pair<double, double>> params;
            double epss[] = {0.005};
            for (auto eps : epss) {
                for (int delta_pow = -2; delta_pow > -6; --delta_pow) {
                    double delta = pow(2, delta_pow);
                    params.emplace_back(eps, delta);
                }
            }
            heavy_hitter(params, theta, dataset_file, "../datasets_files/" + DATASET + "/", result_file, d);
        }
            break;
    }

    result_stream.close();
    return 0;
}


// disable Testing Macros from G-test
#define MATLAB

#include <iostream>
#include <string>
#include <Eigen/Dense>
#include "mex.h"
#include "../include/cppoptlib/problem.h"
#include "../include/cppoptlib/meta.h"
#include "../include/cppoptlib/solver/gradientdescentsolver.h"
#include "../include/cppoptlib/solver/conjugatedgradientdescentsolver.h"
#include "../include/cppoptlib/solver/newtondescentsolver.h"
#include "../include/cppoptlib/solver/bfgssolver.h"
#include "../include/cppoptlib/solver/lbfgssolver.h"
#include "../include/cppoptlib/solver/lbfgsbsolver.h"
#include "../include/cppoptlib/solver/cmaessolver.h"
#include "../include/cppoptlib/solver/neldermeadsolver.h"

using namespace cppoptlib;

char *nameObjectiveFunction;
char *nameGradientFunction;  bool hasGradient = false;
char *nameHessianFunction;   bool hasHessian  = false;

size_t in_rows;
size_t in_cols;

char error_msg[200];

template<typename T>
class MATLABobjective : public Problem<T> {
 public:
  T value(const Vector<T> &x) {
    mxArray * objective_ans, *objective_param[1];
    objective_param[0] = mxCreateDoubleMatrix(x.rows(), x.cols(), mxREAL);
    const T *constVariablePtr = &x(0);
    memcpy(mxGetPr(objective_param[0]), constVariablePtr, mxGetM(objective_param[0]) * mxGetN(objective_param[0]) * sizeof(*constVariablePtr));
    mexCallMATLAB(1, &objective_ans, 1, objective_param, nameObjectiveFunction) ;
    return mxGetScalar(objective_ans);
  }

  void gradient(const Vector<T> &x, Vector<T> &grad) {
    if (hasGradient) {
      mxArray * objective_ans, *objective_param[1];
      objective_param[0] = mxCreateDoubleMatrix(x.rows(), x.cols(), mxREAL);
      const double *constVariablePtr = &x(0);
      memcpy(mxGetPr(objective_param[0]), constVariablePtr, mxGetM(objective_param[0]) * mxGetN(objective_param[0]) * sizeof(*constVariablePtr));
      mexCallMATLAB(1, &objective_ans, 1, objective_param, nameGradientFunction) ;
      size_t r = mxGetM(objective_ans);
      size_t c = mxGetN(objective_ans);
      if ((in_rows != r) || (in_cols != c)) {
        sprintf(error_msg, "Wrong format of gradient! The correct format is %zu x %zu, but %zu x %zu was given", in_rows, in_cols, r, c);
        mexErrMsgIdAndTxt("MATLAB:cppoptlib", error_msg);
      }

      grad = Eigen::Map<Eigen::VectorXd>(mxGetPr(objective_ans), mxGetM(objective_ans) );

    } else {
      this->finiteGradient(x, grad);
    }
  }

  void hessian(const Vector<T> &x, Matrix<T> & hessian) {
    if (hasHessian) {

      mxArray * objective_ans, *objective_param[1];
      objective_param[0] = mxCreateDoubleMatrix(x.rows(), x.cols(), mxREAL);
      const double *constVariablePtr = &x(0);
      memcpy(mxGetPr(objective_param[0]), constVariablePtr, mxGetM(objective_param[0]) * mxGetN(objective_param[0]) * sizeof(*constVariablePtr));
      mexCallMATLAB(1, &objective_ans, 1, objective_param, nameHessianFunction) ;
      size_t r = mxGetM(objective_ans);
      size_t c = mxGetN(objective_ans);
      if ((in_rows != r) || (in_rows != c) || (c != r)) {
        sprintf(error_msg, "Wrong format of hessian! The correct format is %zu x %zu, but %zu x %zu was given", in_rows, in_rows, r, c);
        mexErrMsgIdAndTxt("MATLAB:cppoptlib", error_msg);
      }

      hessian = Eigen::Map<Eigen::MatrixXd>(mxGetPr(objective_ans), mxGetM(objective_ans) , mxGetN(objective_ans));

    } else {
      this->finiteHessian(x, hessian);
    }
  }

};

void mexFunction(int outLen, mxArray *outArr[], int inLen, const mxArray *inArr[]) {

  MATLABobjective<double> f;

  // check parameters
  if (inLen < 2) {
    mexErrMsgIdAndTxt("MATLAB:cppoptlib", "this function need at leat two parameters");
  }

  if (mxGetClassID(inArr[0]) != mxDOUBLE_CLASS) {
    mexErrMsgIdAndTxt("MATLAB:cppoptlib", "the first arguments needs to be an array of type double");
  }

  // check starting point
  // ----------------------------------------------------------
  in_rows = mxGetM(inArr[0]);
  in_cols = mxGetN(inArr[0]);
  if (in_cols > 1 || in_rows == 0) {
    sprintf(error_msg, "The first argument has to be the inital guess x0 (format: n x 1), but the input format is %zu x %zu", in_rows, in_cols);
    mexErrMsgIdAndTxt("MATLAB:cppoptlib", error_msg);
  }
  Eigen::Map<Eigen::VectorXd> solution = Eigen::Map<Eigen::VectorXd>(mxGetPr(inArr[0]), mxGetM(inArr[0]) * mxGetN(inArr[0]));

  Vector<double> x = solution.eval();
  // check objective function
  // ----------------------------------------------------------
  if (mxGetClassID(inArr[1]) != mxFUNCTION_CLASS) {
    mexErrMsgIdAndTxt("MATLAB:cppoptlib", "the second arguments has to be the handle of the function (@objective)");
  }

  // get name of objective
  // ----------------------------------------------------------
  mxArray *objective_ans, *objective_param[1];
  objective_param[0] = const_cast<mxArray *>( inArr[1] );
  mexCallMATLAB(1, &objective_ans, 1, objective_param, "char") ;
  nameObjectiveFunction =   mxArrayToString(objective_ans);

  // parse remaining arguments
  // ----------------------------------------------------------
  enum solver_type {GRADIENTDESCENT, NEWTON, BFGS, LBFGS, LBFGSB, CONJUGATEDGRADIENTDESCENT, CMAES, NELDERMEAD};
  solver_type selected_solver = BFGS;

  if (inLen > 2) {
    // there are some parameters
    if ((inLen % 2) != 0) {
      mexErrMsgIdAndTxt("MATLAB:cppoptlib", "optional arguments have to be passed by 'key','value'.");
    }
    for (int arg = 2; arg < inLen; arg += 2) {
      if (!mxIsChar( inArr[arg])) {
        mexErrMsgIdAndTxt("MATLAB:cppoptlib", "optional argument keys have to be strings");
      }
      char *key_str = mxArrayToString(inArr[arg]);

      if (strcmp(key_str, "gradient") == 0) {
        if (mxGetClassID(inArr[arg + 1]) != mxFUNCTION_CLASS) {
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", "the argument following 'gradient' has to be a function handle (@gradient)");
        }
        objective_param[0] = const_cast<mxArray *>( inArr[arg + 1] );
        mexCallMATLAB(1, &objective_ans, 1, objective_param, "char") ;
        nameGradientFunction =   mxArrayToString(objective_ans);
        hasGradient = true;
      }
      if (strcmp(key_str, "hessian") == 0) {
        if (mxGetClassID(inArr[arg + 1]) != mxFUNCTION_CLASS) {
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", "the argument following 'hessian' has to be a function handle (@hessian)");
        }
        objective_param[0] = const_cast<mxArray *>( inArr[arg + 1] );
        mexCallMATLAB(1, &objective_ans, 1, objective_param, "char") ;
        nameHessianFunction =   mxArrayToString(objective_ans);
        hasHessian = true;
      }
      if (strcmp(key_str, "solver") == 0) {
        if (!mxIsChar( inArr[arg + 1])) {
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", "solver name has to be a string");
        }
        char *solver_str = mxArrayToString(inArr[arg + 1]);
        if (strcmp(solver_str, "gradientdescent") == 0) {
          selected_solver = GRADIENTDESCENT;
        } else if (strcmp(solver_str, "cg") == 0) {
          selected_solver = CONJUGATEDGRADIENTDESCENT;
        } else if (strcmp(solver_str, "bfgs") == 0) {
          selected_solver = BFGS;
        } else if (strcmp(solver_str, "l-bfgs") == 0) {
          selected_solver = LBFGS;
        } else if (strcmp(solver_str, "l-bfgs-b") == 0) {
          selected_solver = LBFGSB;
        } else if (strcmp(solver_str, "newton") == 0) {
          selected_solver = NEWTON;
        } else if (strcmp(solver_str, "cmaes") == 0) {
          selected_solver = CMAES;
        } else if (strcmp(solver_str, "neldermead") == 0) {
          selected_solver = NELDERMEAD;
        } else {
          sprintf(error_msg, "unknown solver %s", solver_str);
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", error_msg);
        }
      }
      if (strcmp(key_str, "lb") == 0) {
        if (mxGetClassID(inArr[arg + 1]) != mxDOUBLE_CLASS) {
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", "the argument following 'lb' has to be an array of type double");
        }
        size_t lbr = mxGetM(inArr[arg + 1]);
        size_t lbc = mxGetN(inArr[arg + 1]);

        if ((in_cols != lbc) || (in_rows != lbr)) {
          sprintf(error_msg, "expected lowerBound argument format is (format: %zu x 1), but the input format is %zu x %zu", in_rows, lbr, lbc);
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", error_msg);
        }

        Eigen::Map<Eigen::VectorXd> tmp = Eigen::Map<Eigen::VectorXd>(mxGetPr(inArr[arg + 1]), mxGetM(inArr[arg + 1]) * mxGetN(inArr[arg + 1]));
        Vector<double> t = tmp;
        f.setLowerBound(t);

      }
      if (strcmp(key_str, "ub") == 0) {
        if (mxGetClassID(inArr[arg + 1]) != mxDOUBLE_CLASS) {
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", "the argument following 'lb' has to be an array of type double");
        }
        size_t lbr = mxGetM(inArr[arg + 1]);
        size_t lbc = mxGetN(inArr[arg + 1]);

        if ((in_cols != lbc) || (in_rows != lbr)) {
          sprintf(error_msg, "expected lowerBound argument format is (format: %zu x 1), but the input format is %zu x %zu", in_rows, lbr, lbc);
          mexErrMsgIdAndTxt("MATLAB:cppoptlib", error_msg);
        }

        Eigen::Map<Eigen::VectorXd> tmp = Eigen::Map<Eigen::VectorXd>(mxGetPr(inArr[arg + 1]), mxGetM(inArr[arg + 1]) * mxGetN(inArr[arg + 1]));
        Vector<double> t = tmp;
        f.setUpperBound(t);

      }
    }
  }

  // solve
  // ----------------------------------------------------------

  switch (selected_solver) {
  case LBFGS: {
    LbfgsSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case LBFGSB: {
    LbfgsbSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case BFGS: {
    BfgsSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case GRADIENTDESCENT: {
    GradientDescentSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case CONJUGATEDGRADIENTDESCENT: {
    ConjugatedGradientDescentSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case NEWTON: {
    NewtonDescentSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case NELDERMEAD: {
    NelderMeadSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  case CMAES: {
    CMAesSolver<double> solver;
    solver.minimize(f, x);
  }
  break;
  default:
    mexErrMsgIdAndTxt("MATLAB:cppoptlib:not_implemented", "Your select solver has currently no matlab binding. Oops.");
    break;
  }

  // prepare solution
  outArr[0] = mxCreateDoubleScalar(f(x));
  if (outLen > 1) {
    outArr[1] = mxCreateDoubleMatrix(x.rows(), x.cols(), mxREAL);
    double *constVariablePtr = &x(0);
    memcpy(mxGetPr(outArr[1]), constVariablePtr, mxGetM(outArr[1]) * mxGetN(outArr[1]) * sizeof(*constVariablePtr));
  }

}


#include "teca_config.h"
#include "teca_cf_reader.h"
#include "teca_normalize_coordinates.h"
#include "teca_l2_norm.h"
#include "teca_vorticity.h"
#include "teca_derived_quantity.h"
#include "teca_derived_quantity_numerics.h"
#include "teca_tc_candidates.h"
#include "teca_tc_trajectory.h"
#include "teca_array_collection.h"
#include "teca_variant_array.h"
#include "teca_metadata.h"
#include "teca_table_reader.h"
#include "teca_table_sort.h"
#include "teca_table_calendar.h"
#include "teca_table_writer.h"
#include "teca_mpi_manager.h"
#include "teca_coordinate_util.h"
#include "teca_calcalcs.h"
#include "teca_app_util.h"

#include <vector>
#include <string>
#include <iostream>
#include <boost/program_options.hpp>

using namespace std;
using namespace teca_derived_quantity_numerics;

using boost::program_options::value;

// --------------------------------------------------------------------------
int main(int argc, char **argv)
{
    // initialize mpi
    teca_mpi_manager mpi_man(argc, argv);

    // initialize command line options description
    // set up some common options to simplify use for most
    // common scenarios
    int help_width = 100;
    options_description basic_opt_defs(
        "Basic usage:\n\n"
        "The following options are the most commonly used. Information\n"
        "on advanced options can be displayed using --advanced_help\n\n"
        "Basic command line options", help_width, help_width - 4
        );
    basic_opt_defs.add_options()
        ("candidate_file", value<string>()->default_value("candidates.bin"), "\nfile path to read the storm candidates from\n")
        ("max_daily_distance", value<double>()->default_value(1600), "\nmax distance in km that a storm can travel in one day\n")
        ("min_wind_speed", value<double>()->default_value(17.0), "\nminimum peak wind speed to be considered a tropical storm\n")
        ("min_wind_duration", value<double>()->default_value(2.0), "\nnumber of, not necessarily consecutive, days min wind speed sustained\n")
        ("track_file", value<string>()->default_value("tracks.bin"), "\nfile path to"
            " write storm tracks to. The extension determines the file format. May be"
            " one of `.nc`, `.csv`, or `.bin`\n")
        ("help", "\ndisplays documentation for application specific command line options\n")
        ("advanced_help", "\ndisplays documentation for algorithm specific command line options\n")
        ("full_help", "\ndisplays both basic and advanced documentation together\n")
        ;

    // add all options from each pipeline stage for more advanced use
    options_description advanced_opt_defs(
        "Advanced usage:\n\n"
        "The following list contains the full set options giving one full\n"
        "control over all runtime modifiable parameters. The basic options\n"
        "(see" "--help) map to these, and will override them if both are\n"
        "specified.\n\n"
        "tropical storms trajectory pipeline:\n\n"
        "   (candidate reader)--(sort)--(tracks)--(track writer)\n\n"
        "Advanced command line options", help_width, help_width - 4
        );

    // create the pipeline stages here, they contain the
    // documentation and parse command line.
    // objects report all of their properties directly
    // set default options here so that command line options override
    // them. while we are at it connect the pipeline
    p_teca_table_reader candidate_reader = teca_table_reader::New();
    candidate_reader->get_properties_description("candidate_reader", advanced_opt_defs);

    p_teca_table_sort sort = teca_table_sort::New();
    sort->set_input_connection(candidate_reader->get_output_port());
    sort->set_index_column("storm_id");
    sort->get_properties_description("sort", advanced_opt_defs);

    p_teca_tc_trajectory tracks = teca_tc_trajectory::New();
    tracks->set_input_connection(sort->get_output_port());
    tracks->set_max_daily_distance(1600.0);
    tracks->set_min_wind_speed(17.0);
    tracks->set_min_wind_duration(2.0);
    tracks->get_properties_description("tracks", advanced_opt_defs);

    p_teca_table_calendar calendar = teca_table_calendar::New();
    calendar->set_input_connection(tracks->get_output_port());
    calendar->get_properties_description("calendar", advanced_opt_defs);

    p_teca_table_writer track_writer = teca_table_writer::New();
    track_writer->set_input_connection(calendar->get_output_port());
    track_writer->set_output_format_auto();
    track_writer->get_properties_description("track_writer", advanced_opt_defs);

    // package basic and advanced options for display
    options_description all_opt_defs(help_width, help_width - 4);
    all_opt_defs.add(basic_opt_defs).add(advanced_opt_defs);

    // parse the command line
    int ierr = 0;
    variables_map opt_vals;
    if ((ierr = teca_app_util::process_command_line_help(
        mpi_man.get_comm_rank(), argc, argv, basic_opt_defs,
        advanced_opt_defs, all_opt_defs, opt_vals)))
    {
        if (ierr == 1)
            return 0;
        return -1;
    }

    // pass command line arguments into the pipeline objects
    // advanced options are processed first, so that the basic
    // options will override them
    candidate_reader->set_properties("candidate_reader", opt_vals);
    sort->set_properties("sort", opt_vals);
    tracks->set_properties("tracks", opt_vals);
    calendar->set_properties("calendar", opt_vals);
    track_writer->set_properties("track_writer", opt_vals);

    // now pass in the basic options, these are processed
    // last so that they will take precedence
    candidate_reader->set_file_name(
        opt_vals["candidate_file"].as<string>());

    if (!opt_vals["max_daily_distance"].defaulted())
        tracks->set_max_daily_distance(
            opt_vals["max_daily_distance"].as<double>());

    if (!opt_vals["min_wind_speed"].defaulted())
        tracks->set_min_wind_speed(
            opt_vals["min_wind_speed"].as<double>());

    if (!opt_vals["min_wind_duration"].defaulted())
        tracks->set_min_wind_duration(
            opt_vals["min_wind_duration"].as<double>());

    track_writer->set_file_name(
        opt_vals["track_file"].as<string>());

    // some minimal check for missing options
    if (candidate_reader->get_file_name().empty())
    {
        if (mpi_man.get_comm_rank() == 0)
        {
            TECA_FATAL_ERROR("missing file name for candidate reader. "
                "See --help for a list of command line options.")
        }
        return -1;
    }

    // run the pipeline
    track_writer->update();

    return 0;
}


#include <pcl/PolygonMesh.h>
#include <pcl/common/common.h>
#include <pcl/point_cloud.h>
#include <pcl/filters/crop_hull.h>
#include <pcl/io/pcd_io.h>
#include <pcl/visualization/cloud_viewer.h>
#include <pcl/filters/crop_box.h>
#include <pcl/surface/convex_hull.h>

#include "PCUtils.h"

#include "crop2pcd.h"

#include <boost/property_tree/json_parser.hpp> // .json parse
#include <boost/property_tree/exceptions.hpp>

#include <unistd.h>

void printUsage(){
std::cout << "Usage:" << std::endl << " geocrop cloud.pcd crop1.crop crop2.crop ...  [origin.json] [-D dir] -[v]" << std::endl << std::endl;
std::cout << "Crop point clouds clouds with geographical coordinates" << std::endl << std::endl;
std::cout << "First parameter must be the '.pcd' cloud to be cropped." << std::endl;
std::cout << "'.json' files are optional, but are required if no origin is given in the .crop files. They must have a node called 'origem' with at latitude and longitude specified" << std::endl;
std::cout << "Options:" << std::endl;
std::cout << "-D	specify cropped cloud save directory " << std::endl;
std::cout << "-v 	optional visualization interface" << std::endl;

}

//TODO
// gerar arquivos de saída com nomes padronizados (parsear timestamp do arquivo origem e juntar nomes nas saídas)

static void extrude(const pcl::PointCloud<pcl::PointXYZ> &cloud_in, const pcl::PointCloud<pcl::PointXYZ> &crop_zone, pcl::PointCloud<pcl::PointXYZ> &crop_zone3D)
{
	pcl::PointXYZ min, max;
	pcl::getMinMax3D(cloud_in, min, max);
	crop_zone3D = crop_zone; // Copy ?

	for (int i = 0; i < crop_zone.size(); ++i)
	{
		pcl::PointXYZ newpt(crop_zone.points[i].x, crop_zone.points[i].y, max.z + 10);
		crop_zone3D.points[i].z = min.z - 10;
		crop_zone3D.push_back(newpt);
	}
}

static void crop(const pcl::PointCloud<pcl::PointXYZ>::ConstPtr &cloud_in, const pcl::PointCloud<pcl::PointXYZ>::ConstPtr &crop_zone, pcl::PointCloud<pcl::PointXYZ>::Ptr cropped_cloud)
{
	pcl::PointCloud<pcl::PointXYZ>::Ptr crop_zone3D(new pcl::PointCloud<pcl::PointXYZ>);

	extrude(*cloud_in, *crop_zone, *crop_zone3D);

	pcl::ConvexHull<pcl::PointXYZ> convexhull;
	std::vector<pcl::Vertices> polygons;
	pcl::PolygonMesh mesh;
	convexhull.setInputCloud(crop_zone3D);
	convexhull.setDimension(3);

	convexhull.reconstruct(polygons);
	convexhull.reconstruct(mesh);

	pcl::CropHull<pcl::PointXYZ> cropper;
	cropper.setInputCloud(cloud_in);
	cropper.setHullCloud(crop_zone3D);
	cropper.setHullIndices(polygons);
	cropper.setDim(2);
	cropper.setCropOutside(true);
	cropper.filter(*cropped_cloud);

	// Cropbox funciona muito bem
	//   pcl::CropBox<pcl::PointXYZ> crop_box;
	//
	//   Eigen::Vector4f min,max;
	//   pcl::getMinMax3D(*crop_zone,min,max);
	//
	//   crop_box.setMin(min);
	//   crop_box.setMax(max);
	//   crop_box.setInputCloud(cloud_in);
	//   crop_box.filter(*cloud_out);
}

int main(int argc, char **argv)
{
	if (argc < 3)
	{
		printUsage();
		exit(-1);
	}

	char opt;
	bool hasVisualization = false;
	std::string custom_dir = "./";

	while ((opt = getopt(argc, argv, "vD:h")) != -1)
	{

			switch(opt){
					case 'v':
							hasVisualization = true;
							break;
					case 'D':
							custom_dir = std::string(optarg) + "/";
//							std::cout << "custom dir : " << custom_dir << std::endl;
							break;
					case '?':
							std::cerr << "Specify the requried directory!" << std::endl;
							exit(-1);
							break;

					case 'h':
					printUsage();
					exit(0);
					break;

			}

	}

	pcl::PointCloud<pcl::PointXYZ>::Ptr cloud_raw(new pcl::PointCloud<pcl::PointXYZ>);

	bool hasJsonOrigin = false;
	bool hasPCD = false;
	std::string raw_prefix;

	for(int i = optind;i<argc;++i){
		std::string filename = argv[i];
		if (filename.find(".pcd") != std::string::npos) {// Check if .crop file
		hasPCD = true;
	std::cout << "Loading RAW point cloud..." << std::endl;
	if (pcl::io::loadPCDFile(argv[optind], *cloud_raw) == -1)
	{
		std::cerr << "Error loading raw cloud.Exiting." << std::endl;
		exit(-1);
	}
	std::cout << "RAW point cloud loaded!" << std::endl;
	std::string rawfilename = argv[optind];
	size_t final_raw = rawfilename.find_last_of("."); //Substring extraction
	size_t init_raw = rawfilename.find_last_of("/");
	raw_prefix = rawfilename.substr(init_raw + 1, final_raw - init_raw - 1);
	break; // Looks only for a single .pcd
		}
	}

	if(!hasPCD){
		std::cerr << "No .pcd file given! Exiting.";
		exit(-1);
	}

	std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr> crop_clouds;

	int cloud_i = 0;
	NavSatFix Origin = {0}; // Set to zero to indicate abscence of data

	boost::property_tree::ptree pt;
	// look for json origin
	for (int i = optind; i < argc; ++i)
	{
		std::string filename = argv[i];
		if (filename.find(".json") != std::string::npos)
		{
			// parse .json, check origin
			hasJsonOrigin = true;
			std::cout << "Loading .json metadata: " << filename << std::endl;
			try{
			boost::property_tree::read_json(filename, pt); // May throw
			} catch(boost::property_tree::ptree_error e) {
				std::cerr << "error loading .json: " << e.what() << std::endl;
				hasJsonOrigin = false;
			}

			try{
			std::string value;
			value = pt.get<std::string>("origem"); // may throw.
			sscanf(value.c_str(), "%lf,%lf,%*lf", &Origin.latitude, &Origin.longitude);
			}
			 catch (boost::property_tree::ptree_error e) {
				std::cerr << "Warning: " << e.what() << std::endl;
				hasJsonOrigin = false;
			}
			// std::cout << "Json Origin: " << value << std::endl;

		}
	}

	std::vector<std::string> outputfilenames;

	for (int i = optind; i < argc; ++i)
	{
		std::string filename = argv[i];
		if (filename.find(".crop") != std::string::npos) // Check if .crop file
		{
			// parse .crops, check origin
			pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
			pcl::PointCloud<pcl::PointXYZ>::Ptr cropped_cloud(new pcl::PointCloud<pcl::PointXYZ>);
			int r = readCropfile(filename, cloud, Origin);
			if (r == 1 && hasJsonOrigin == false)
			{ // Some .crop has no origin
				std::cerr << "Warning : '" << filename << "' has NO Origin and no '.json' origin was found. Ignoring crop." << std::endl;
				// exit(-1);
				continue;
			}

			if ( r == -1){
				exit(-1);
			}

			// Generate the name of output file
			size_t final = filename.find_last_of(".");
			size_t init = filename.find_last_of("/");
			std::string newfilename;
			newfilename = filename.substr(init + 1, final - init - 1);

			std::cout << "Cropping '" << newfilename << "' ..." << std::endl;
			crop(cloud_raw, cloud, cropped_cloud); // Crop happens here
			crop_clouds.push_back(cropped_cloud);
			if(cropped_cloud->size() != 0){
			pcl::io::savePCDFileBinary(custom_dir + raw_prefix + "_" + newfilename + ".pcd", *cropped_cloud); // TODO adotar padrão aqui
			std::cout << "Saved: " << newfilename << ".pcd" << std::endl;
			} else {
				std::cout << "Warning: Cropped zone '" << newfilename << "' has no points!" << std::endl;
			}

		}
	}

	if (!hasVisualization)
		return 0;

	std::cout << "Opening crop viewer..." << std::endl;

	pcl::visualization::PCLVisualizer::Ptr viewer(new pcl::visualization::PCLVisualizer);
	viewer->addPointCloud(cloud_raw, "raw");
	viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 1, "raw");

	int count = 0;

	for (std::vector<pcl::PointCloud<pcl::PointXYZ>::Ptr>::iterator it = crop_clouds.begin(); it != crop_clouds.end(); ++it)
	{
		std::string crop_name = "crop" + std::to_string(count++);
		pcl::visualization::PointCloudColorHandlerRandom<pcl::PointXYZ> color(*it);
		viewer->addPointCloud(*it, color, crop_name);
		viewer->setPointCloudRenderingProperties(pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2, crop_name);
	}

	while (!viewer->wasStopped())
	{
		viewer->spin();
	}

	return 0;
}


#include <armadillo>
#include <cstdint>

#include "struct.h"
#include <h5cpp/core>
	// generated file must be sandwiched between core and io
	// to satisfy template dependencies in <h5cpp/io>
	#include "generated.h"
#include <h5cpp/io>
#include <cstddef>
#include "utils.hpp"

int main(){
	arma::mat M = arma::zeros(5,6);

	arma::mat matrix = arma::zeros(3,4); for(int i=0; i<matrix.n_elem; i++ ) matrix[i] = i;
	std::vector<sn::example::Record> vector = h5::utils::get_test_data<sn::example::Record>(40);
	sn::example::Record& record = vector[3];
	// set to use the latest file format version to able to use large size attributes
	h5::fd_t fd = h5::create("001.h5", H5F_ACC_TRUNC, h5::default_fcpl,
						h5::libver_bounds({H5F_LIBVER_V18, H5F_LIBVER_V18}) );
	h5::ds_t ds = h5::write(fd,"directory/dataset", M);
	{
		/*
		(gr_t | ds_t | dt_t = fd["/root/some/path"]) = some object;
		ds_t ds = ... ;

		gr["data set"] = some object;
		ds["attribute"] = some attribute;
		h5::ds_t obj = fd["/some/path"]["attribute"];
		//h5::gr_t obj = fd["/some/path"];*/
	}

	{
		ds["att_01"] = 42 ;
		ds["att_02"] = {1.,2.,3.,4.};
		ds["att_03"] = {'1','2','3','4'};
		ds["att_04"] = {"alpha", "beta","gamma","..."};

		ds["att_05"] = "const char[N]";
		ds["att_06"] = u8"const char[N]áééé";
		ds["att_07"] = std::string( "std::string");

		ds["att_08"] = record; // pod/compound datatype
		ds["att_09"] = vector; // vector of pod/compound type
		ds["att_10"] = matrix; // linear algebra object
	}

	/* supported types:
	 * T := integral | std::string | const char[] | POD struct*
	 * accept := T | std::vector<T> | linalg
	 * * pod struct requires h5cpp compiler or manual labour
	 */
	{ // create + write
		h5::awrite(ds,"att_21", 42 );
		h5::awrite(ds,"att_22", {1.,3.,4.,5.} );
		h5::awrite(ds,"att_23", {'1','3','4','5'} );
		h5::awrite(ds,"att_24", {"alpha", "beta","gamma","..."} );

		h5::awrite(ds,"att_25", "const char[N]");
		h5::awrite(ds,"att_26", u8"const char[N]áééé");
		h5::awrite(ds,"att_27", std::string( "std::string") );

		h5::awrite(ds,"att_28", record ); // pod/compound datatype
		h5::awrite(ds,"att_29", vector ); // vector of pod/compound type
		h5::awrite(ds,"att_30", matrix ); // linear algebra object
	}
	{//directory
		h5::gr_t gr{H5Gopen(fd,"/directory", H5P_DEFAULT)};
		h5::awrite(gr,"att_21", 42 );
		h5::awrite(gr,"att_22", {1.,3.,4.,5.} );
		h5::awrite(gr,"att_23", {'1','3','4','5'} );
		h5::awrite(gr,"att_24", {"alpha", "beta","gamma","..."} );

		h5::awrite(gr,"att_25", "const char[N]");
		h5::awrite(gr,"att_26", u8"const char[N]áééé");
		h5::awrite(gr,"att_27", std::string( "std::string") );

		h5::awrite(gr,"att_28", record ); // pod/compound datatype
		h5::awrite(gr,"att_29", vector ); // vector of pod/compound type
		h5::awrite(gr,"att_30", matrix ); // linear algebra object
	}

	{ // open + write -> attribute size must not change
		arma::mat att_01 = arma::ones(3,4);
		h5::awrite(ds,"att_01", att_01 );
		//TODO: attribute with different dimension
		// 1.) remove previous attribute
		// 2.) create new attribute
		// 3.) write attribute
		h5::awrite(ds,"att_02", {1.,2.,3.,4.,5.,6.} );
		// trigger runtime error and then handle it
		h5::mute();
		try{
			h5::awrite(ds,"att_02", {"one","two","..."} );
		} catch ( const h5::error::io::attribute::any& err ){
			std::cerr <<"INTENTIONAL ERROR: " <<err.what() << std::endl;
		}
		h5::unmute();
	}
	{ // reading back attribute is always single shot, no partial IO
		int a = h5::aread<int>(ds,"att_01");
		arma::mat att_10 = h5::aread<arma::mat>(ds,"att_10");
		std::cerr << att_10 <<"\n";
	}
	{ // reading back attribute is always single shot, no partial IO
		// vector of ints
		std::cout << "att_01 : "; int att_01 = h5::aread<int>(ds,"att_01"); std::cout << att_01 << "\n";
		// vector of doubles
		std::cout << "att_02 : "; auto att_02 = h5::aread<std::vector<double>>(ds,"att_02");
		std::cout << att_02 << "\n";
		// vector of ints
		std::cout << "att_03 : "; auto att_03 = h5::aread<std::vector<int>>(ds,"att_03");
		std::cout << att_03 << "\n";
		// vector of strings, NOTE: charaters or initializer lists are not yet supported
		std::cout << "att_04 : "; auto att_04 = h5::aread<std::vector<std::string>>(ds,"att_04");
		std::cout<< att_04 <<"\n";
		// POD type rank 0
		std::cout << "att_08 : "; sn::example::Record att_08 = h5::aread<sn::example::Record>(ds,"att_08");
		std::cout<< att_08.idx <<"\n";
		// POD type rank 1
		std::cout << "att_09 : "; auto att_09 = h5::aread<std::vector<sn::example::Record>>(ds,"att_09");
		for(auto v: att_09) std::cout << v.idx <<","; std::cout <<std::endl;
		// linear algebra object
		std::cout << "att_10 : "; auto att_10 = h5::aread<arma::mat>(ds,"att_10"); std::cout << att_10 << "\n";
	}
}


//---------------------------------------------------------------------------//
//!
//! \file   tstBoostIntegrator.cpp
//! \author Luke Kersting
//! \brief  Gauss-Kronrod quadrature integrator unit tests.
//!
//---------------------------------------------------------------------------//

// Std Lib Includes
#include <iostream>
#include <limits>

// Boost Includes
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/math/tools/precision.hpp>
#include <boost/numeric/odeint.hpp>

// FRENSIE Includes
#include "Utility_UnitTestHarnessWithMain.hpp"

//---------------------------------------------------------------------------//
// Testing Types
//---------------------------------------------------------------------------//

typedef boost::multiprecision::cpp_dec_float_50 long_float;

typedef std::vector<double> state_type;

struct X2Functor
{
  void operator()( const state_type& x, state_type& y, const double t ) const
  {
    if( t >= 0.0 && t <= 1.0 )
      y[0] = t*t;
    else
      y[0] = 0.0;
  }

  static double getIntegratedValue()
  {
    return 1.0/3.0;
  }

  static double getLowerIntegratedValue()
  {
    return 1.0/24.0;
  }

  static double getUpperIntegratedValue()
  {
    return 7.0/24.0;
  }
};

struct X3Functor
{
  void operator()( const state_type& x, state_type& y, const double t ) const
  {
    if( t >= 0.0 && t <= 1.0 )
      y[0] = t*t*t;
    else
      y[0] = 0.0;
  }

  static double getIntegratedValue()
  {
    return 0.25;
  }

  static double getLowerIntegratedValue()
  {
    return 1.0/64.0;
  }

  static double getUpperIntegratedValue()
  {
    return 15.0/64.0;
  }
};

namespace Utility{

TYPE_NAME_TRAITS_QUICK_DECL( boost::multiprecision::cpp_dec_float_50 );
TYPE_NAME_TRAITS_QUICK_DECL( X2Functor );
TYPE_NAME_TRAITS_QUICK_DECL( X3Functor );

} // end Utility namespace

typedef std::tuple<X2Functor,X3Functor> TestFunctors;

typedef std::vector< double > state_type;

//---------------------------------------------------------------------------//
// Testing Functions
//---------------------------------------------------------------------------//

// Observer, prints time and state when called (during integration)
void my_observer( const state_type& x, const double t )
{    std::cout  << t << "   " << x[0] << std::endl;   }

//---------------------------------------------------------------------------//
// Tests
//---------------------------------------------------------------------------//
// Check that functions can be integrated over [0,1]
FRENSIE_UNIT_TEST_TEMPLATE( BoostIntegrator,
                            integrate,
                            TestFunctors )
{
  FETCH_TEMPLATE_PARAM( 0, Functor );

  state_type x0(1);
  x0[0] = 0.0;
  double t0 = 0.0;
  double t1 = 1.0;
  double dt = 0.01;

  Functor functor_instance;

  int steps = boost::numeric::odeint::integrate(
    functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;
}

//---------------------------------------------------------------------------//
// Check that functions can be integrated over [0,1]
FRENSIE_UNIT_TEST_TEMPLATE( BoostIntegrator,
                            integrate_const,
                            TestFunctors )
{
  FETCH_TEMPLATE_PARAM( 0, Functor );

  state_type x0(1);
  x0[0] = 0.0;
  double t0 = 0.0;
  double t1 = 1.0;
  double dt = 0.01;

  Functor functor_instance;

  int steps = boost::numeric::odeint::integrate_const(
    boost::numeric::odeint::runge_kutta4<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  dt = 0.00001;
  steps = boost::numeric::odeint::integrate_const(
    boost::numeric::odeint::euler<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 0.5 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  dt = 0.01;
  steps = boost::numeric::odeint::integrate_const(
    boost::numeric::odeint::runge_kutta_cash_karp54<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  steps = boost::numeric::odeint::integrate_const(
    boost::numeric::odeint::runge_kutta_dopri5<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  steps = boost::numeric::odeint::integrate_const(
    boost::numeric::odeint::runge_kutta_fehlberg78<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  dt = 0.00001;
  steps = boost::numeric::odeint::integrate_const(
    boost::numeric::odeint::modified_midpoint<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 0.01 );
  std::cout << "steps = " << steps << std::endl;
}

//---------------------------------------------------------------------------//
// Check that functions can be integrated over [0,1]
FRENSIE_UNIT_TEST_TEMPLATE( BoostIntegrator,
                            integrate_n_steps,
                            TestFunctors )
{
  FETCH_TEMPLATE_PARAM( 0, Functor );

  state_type x0(1);
  x0[0] = 0.0;
  double t0 = 0.0;
  double t1 = 1.0;
  double dt = 0.1;
  int n = 10;

  Functor functor_instance;

  int end_time = boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::runge_kutta4<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "end time = " << end_time << std::endl;

  x0[0] = 0.0;
  end_time = boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::euler<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 0.5 );
  std::cout << "end time = " << end_time << std::endl;

  x0[0] = 0.0;
  end_time = boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::runge_kutta_cash_karp54<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "end time = " << end_time << std::endl;

  x0[0] = 0.0;
  end_time = boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::runge_kutta_dopri5<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "end time = " << end_time << std::endl;

  x0[0] = 0.0;
  end_time = boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::runge_kutta_fehlberg78<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "end time = " << end_time << std::endl;

  x0[0] = 0.0;
  end_time = boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::modified_midpoint<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 0.01 );
  std::cout << "end time = " << end_time << std::endl;
/*
  x0[0] = 0.0;
  boost::numeric::odeint::integrate_n_steps(
    boost::numeric::odeint::rosenbrock4<state_type>(), functor_instance, x0, t0, dt, n );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );*/
}

//---------------------------------------------------------------------------//
// Check that functions can be integrated over [0,1]
FRENSIE_UNIT_TEST_TEMPLATE( BoostIntegrator,
                            integrate_adaptive,
                            TestFunctors )
{
  FETCH_TEMPLATE_PARAM( 0, Functor );

  state_type x0(1);
  x0[0] = 0.0;
  double t0 = 0.0;
  double t1 = 1.0;
  double dt = 0.1;

  Functor functor_instance;

  int steps =  boost::numeric::odeint::integrate_adaptive(
    boost::numeric::odeint::runge_kutta4<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  steps = boost::numeric::odeint::integrate_adaptive(
    boost::numeric::odeint::euler<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 0.5 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  auto stepper = make_controlled( 1.0e-16 , 1.0e-16 , boost::numeric::odeint::runge_kutta_cash_karp54< state_type >() );
  steps = boost::numeric::odeint::integrate_adaptive(
    stepper, functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  boost::numeric::odeint::controlled_runge_kutta< boost::numeric::odeint::runge_kutta_dopri5< state_type > > rkd5;
  steps = boost::numeric::odeint::integrate_adaptive(
    rkd5, functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  boost::numeric::odeint::controlled_runge_kutta< boost::numeric::odeint::runge_kutta_fehlberg78< state_type > > rkf78;
  steps = boost::numeric::odeint::integrate_adaptive(
    rkf78, functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;

  x0[0] = 0.0;
  steps = boost::numeric::odeint::integrate_adaptive(
    boost::numeric::odeint::modified_midpoint<state_type>(), functor_instance, x0, t0, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 0.01 );
  std::cout << "steps = " << steps << std::endl;

  boost::numeric::odeint::bulirsch_stoer< state_type > bs;
  x0[0] = 0.0;
  steps = boost::numeric::odeint::integrate_adaptive(
    bs, functor_instance, x0, t0, t1/2, dt );

  steps = boost::numeric::odeint::integrate_adaptive(
    bs, functor_instance, x0, t1/2, t1, dt );

  FRENSIE_CHECK_FLOATING_EQUALITY( Functor::getIntegratedValue(), x0[0], 1e-10 );
  std::cout << "steps = " << steps << std::endl;
}

//---------------------------------------------------------------------------//
// end tstGaussKronrodIntegrator.cpp
//---------------------------------------------------------------------------//


/*
 [auto_generated]
 boost/numeric/odeint/iterator/const_step_ode_iterator.hpp

 [begin_description]
 Iterator for iterating through the solution of an ODE with constant step size.
 [end_description]

 Copyright 2009-2011 Karsten Ahnert
 Copyright 2009-2011 Mario Mulansky

 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)
 */

#ifndef BOOST_NUMERIC_ODEINT_ITERATOR_CONST_STEP_ODE_ITERATOR_HPP_INCLUDED
#define BOOST_NUMERIC_ODEINT_ITERATOR_CONST_STEP_ODE_ITERATOR_HPP_INCLUDED

#include <boost/numeric/odeint/util/stepper_traits.hpp>
#include <boost/numeric/odeint/stepper/stepper_categories.hpp>

namespace boost {
namespace numeric {
namespace odeint {

    template< class Stepper , class System ,
              class StepperTag = typename base_tag< typename traits::stepper_category< Stepper >::type >::type >
    class const_step_iterator;

    template< class Stepper , class System >
    const_step_iterator< Stepper , System > make_const_step_iterator_begin(
        Stepper stepper ,
        System system ,
        typename traits::state_type< Stepper >::type &x ,
        typename traits::time_type< Stepper >::type t ,
        typename traits::time_type< Stepper >::type t_end ,
        typename traits::time_type< Stepper >::type dt )
    {
        return const_step_iterator< Stepper , System >( stepper , system , x , t , t_end , dt );
    }

    template< class Stepper , class System >
    const_step_iterator< Stepper , System > make_const_step_iterator_end(
        Stepper stepper ,
        System system ,
        typename traits::state_type< Stepper >::type &x )
    {
        return const_step_iterator< Stepper , System >( stepper , system , x );
    }

    template< class Stepper , class System >
    std::pair< const_step_iterator< Stepper , System > , const_step_iterator< Stepper , System > >
    make_const_step_range(
        Stepper stepper ,
        System system ,
        typename traits::state_type< Stepper >::type &x ,
        typename traits::time_type< Stepper >::type t_start ,
        typename traits::time_type< Stepper >::type t_end ,
        typename traits::time_type< Stepper >::type dt )
    {
        return std::make_pair(
            const_step_iterator< Stepper , System >( stepper , system , x , t_start , t_end , dt ) ,
            const_step_iterator< Stepper , System >( stepper , system , x )
            );
    }

    /**
     * \fn make_const_step_iterator_begin( Stepper stepper , System system , typename Stepper::state_type &x , typename Stepper::time_type t , typename Stepper::time_type t_end , typename Stepper::time_type dt )
     *
     * \brief Factory function for const_step_iterator. Constructs a begin iterator.
     *
     * \param stepper The stepper to use during the iteration.
     * \param system The system function (ODE) to solve.
     * \param x The initial state. const_step_iterator stores a reference of s and changes its value during the iteration.
     * \param t The initial time.
     * \param t_end The end time, at which the iteration should stop.
     * \param dt The initial time step.
     * \returns The const step iterator.
     */

    /**
     * \fn make_const_step_iterator_end( Stepper stepper , System system , typename Stepper::state_type &x )
     * \brief Factory function for const_step_iterator. Constructs a end iterator.
     *
     * \param stepper The stepper to use during the iteration.
     * \param system The system function (ODE) to solve.
     * \param x The initial state. const_step_iterator stores a reference of s and changes its value during the iteration.
     * \returns The const_step_iterator.
     */

    /**
     * \fn make_const_step_range( Stepper stepper , System system , typename Stepper::state_type &x , typename Stepper::time_type t_start , typename Stepper::time_type t_end , typename Stepper::time_type dt )
     *
     * \brief Factory function to construct a single pass range of const step iterators. A range is here a pair
     * of const_step_iterator.
     *
     * \param stepper The stepper to use during the iteration.
     * \param system The system function (ODE) to solve.
     * \param x The initial state. const_step_iterator store a reference of s and changes its value during the iteration.
     * \param t The initial time.
     * \param t_end The end time, at which the iteration should stop.
     * \param dt The initial time step.
     * \returns The const step range.
     */

} // namespace odeint
} // namespace numeric
} // namespace boost

#include <boost/numeric/odeint/iterator/impl/const_step_iterator_impl.hpp>
#include <boost/numeric/odeint/iterator/impl/const_step_iterator_dense_output_impl.hpp>

#endif // BOOST_NUMERIC_ODEINT_ITERATOR_CONST_STEP_ODE_ITERATOR_HPP_INCLUDED