/*    This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
 *    See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
 *    Author(s):       Clément Maria
 *
 *    Copyright (C) 2014 Inria
 *
 *    Modification(s):
 *      - YYYY/MM Author: Description of the modification
 */

#include <gudhi/graph_simplicial_complex.h>
#include <gudhi/distance_functions.h>
#include <gudhi/Simplex_tree.h>
#include <gudhi/Persistent_cohomology.h>
#include <gudhi/Points_off_io.h>

#include <boost/program_options.hpp>

#include <string>
#include <vector>
#include <limits>  // infinity
#include <utility>  // for pair
#include <map>

// ----------------------------------------------------------------------------
// rips_persistence_step_by_step is an example of each step that is required to
// build a Rips over a Simplex_tree. Please refer to rips_persistence to see
// how to do the same thing with the Rips_complex wrapper for less detailed
// steps.
// ----------------------------------------------------------------------------

// Types definition
using Simplex_tree = Gudhi::Simplex_tree<Gudhi::Simplex_tree_options_fast_persistence>;
using Vertex_handle = Simplex_tree::Vertex_handle;
using Filtration_value = Simplex_tree::Filtration_value;
using Proximity_graph = Gudhi::Proximity_graph<Simplex_tree>;

using Field_Zp = Gudhi::persistent_cohomology::Field_Zp;
using Persistent_cohomology = Gudhi::persistent_cohomology::Persistent_cohomology<Simplex_tree, Field_Zp >;
using Point = std::vector<double>;
using Points_off_reader = Gudhi::Points_off_reader<Point>;

void program_options(int argc, char * argv[]
                     , std::string & off_file_points
                     , std::string & filediag
                     , Filtration_value & threshold
                     , int & dim_max
                     , int & p
                     , Filtration_value & min_persistence);

int main(int argc, char * argv[]) {
  std::string off_file_points;
  std::string filediag;
  Filtration_value threshold;
  int dim_max;
  int p;
  Filtration_value min_persistence;

  program_options(argc, argv, off_file_points, filediag, threshold, dim_max, p, min_persistence);

  // Extract the points from the file filepoints
  Points_off_reader off_reader(off_file_points);

  // Compute the proximity graph of the points
  Proximity_graph prox_graph = Gudhi::compute_proximity_graph<Simplex_tree>(off_reader.get_point_cloud(),
                                                                            threshold,
                                                                            Gudhi::Euclidean_distance());

  // Construct the Rips complex in a Simplex Tree
  Simplex_tree st;
  // insert the proximity graph in the simplex tree
  st.insert_graph(prox_graph);
  // expand the graph until dimension dim_max
  st.expansion(dim_max);

  std::cout << "The complex contains " << st.num_simplices() << " simplices \n";
  std::cout << "   and has dimension " << st.dimension() << " \n";

  // Sort the simplices in the order of the filtration
  st.initialize_filtration();

  // Compute the persistence diagram of the complex
  Persistent_cohomology pcoh(st);
  // initializes the coefficient field for homology
  pcoh.init_coefficients(p);

  pcoh.compute_persistent_cohomology(min_persistence);

  // Output the diagram in filediag
  if (filediag.empty()) {
    pcoh.output_diagram();
  } else {
    std::ofstream out(filediag);
    pcoh.output_diagram(out);
    out.close();
  }

  return 0;
}

void program_options(int argc, char * argv[]
                     , std::string & off_file_points
                     , std::string & filediag
                     , Filtration_value & threshold
                     , int & dim_max
                     , int & p
                     , Filtration_value & min_persistence) {
  namespace po = boost::program_options;
  po::options_description hidden("Hidden options");
  hidden.add_options()
      ("input-file", po::value<std::string>(&off_file_points),
       "Name of an OFF file containing a point set.\n");

  po::options_description visible("Allowed options", 100);
  visible.add_options()
      ("help,h", "produce help message")
      ("output-file,o", po::value<std::string>(&filediag)->default_value(std::string()),
       "Name of file in which the persistence diagram is written. Default print in std::cout")
      ("max-edge-length,r",
       po::value<Filtration_value>(&threshold)->default_value(std::numeric_limits<Filtration_value>::infinity()),
       "Maximal length of an edge for the Rips complex construction.")
      ("cpx-dimension,d", po::value<int>(&dim_max)->default_value(1),
       "Maximal dimension of the Rips complex we want to compute.")
      ("field-charac,p", po::value<int>(&p)->default_value(11),
       "Characteristic p of the coefficient field Z/pZ for computing homology.")
      ("min-persistence,m", po::value<Filtration_value>(&min_persistence),
       "Minimal lifetime of homology feature to be recorded. Default is 0. Enter a negative value to see zero length intervals");

  po::positional_options_description pos;
  pos.add("input-file", 1);

  po::options_description all;
  all.add(visible).add(hidden);

  po::variables_map vm;
  po::store(po::command_line_parser(argc, argv).
            options(all).positional(pos).run(), vm);
  po::notify(vm);

  if (vm.count("help") || !vm.count("input-file")) {
    std::cout << std::endl;
    std::cout << "Compute the persistent homology with coefficient field Z/pZ \n";
    std::cout << "of a Rips complex defined on a set of input points.\n \n";
    std::cout << "The output diagram contains one bar per line, written with the convention: \n";
    std::cout << "   p   dim b d \n";
    std::cout << "where dim is the dimension of the homological feature,\n";
    std::cout << "b and d are respectively the birth and death of the feature and \n";
    std::cout << "p is the characteristic of the field Z/pZ used for homology coefficients." << std::endl << std::endl;

    std::cout << "Usage: " << argv[0] << " [options] input-file" << std::endl << std::endl;
    std::cout << visible << std::endl;
    exit(-1);
  }
}


/*
 * This is part of the fl library, a C++ Bayesian filtering library
 * (https://github.com/filtering-library)
 *
 * Copyright (c) 2015 Max Planck Society,
 * 				 Autonomous Motion Department,
 * 			     Institute for Intelligent Systems
 *
 * This Source Code Form is subject to the terms of the MIT License (MIT).
 * A copy of the license can be found in the LICENSE file distributed with this
 * source code.
 */

/**
 * \file joint_distribution_id.hpp
 * \date Febuary 2015
 * \author Jan Issac (jan.issac@gmail.com)
 */

#pragma once

#include <Eigen/Dense>

#include <fl/util/meta.hpp>
#include <fl/util/traits.hpp>
#include <fl/distribution/interface/moments.hpp>

namespace fl
{

// Forward declaration
template <typename...Distribution> class JointDistribution;

/**
 * \internal
 *
 * Traits of JointDistribution<Distribution...>
 */
template <typename...Distribution>
struct Traits<JointDistribution<Distribution...>>
{
    enum : signed int
    {
        JointSize = JoinSizes<SizeOf<typename Distribution::Variate>::Value...>::Size
    };

    typedef typename FirstTypeIn<
                typename Distribution::Variate...
            >::Type::Scalar Scalar;

    typedef Eigen::Matrix<Scalar, JointSize, 1> Variate;
};

/**
 * \ingroup distributions
 */
template <typename...Distribution>
class JointDistribution
    : public Moments<typename Traits<JointDistribution<Distribution...>>::Variate>
{
public:
    typedef typename Traits<JointDistribution<Distribution...>>::Variate Variate;
    typedef typename Moments<Variate>::SecondMoment SecondMoment;
    typedef std::tuple<Distribution...> MarginalDistributions;

public:
    JointDistribution(Distribution...distributions)
        : distributions_(distributions...)
    { }

    /**
     * \brief Overridable default destructor
     */
    virtual ~JointDistribution() noexcept { }

    virtual Variate mean() const
    {
        Variate mu = Variate(dimension(), 1);

        mean_<sizeof...(Distribution)>(distributions_, mu);

        return mu;
    }

    virtual SecondMoment covariance() const
    {
        SecondMoment cov = SecondMoment::Zero(dimension(), dimension());

        covariance<sizeof...(Distribution)>(distributions_, cov);

        return cov;
    }

    virtual int dimension() const
    {
        return expend_dimension(CreateIndexSequence<sizeof...(Distribution)>());
    }

    MarginalDistributions& distributions()
    {
        return distributions_;
    }

    const MarginalDistributions& distributions() const
    {
        return distributions_;
    }

protected:
    MarginalDistributions distributions_;

private:
    template <int...Indices>
    int expend_dimension(IndexSequence<Indices...>) const
    {
        const auto& dims = { std::get<Indices>(distributions_).dimension()... };

        int joint_dim = 0;
        for (auto dim : dims) { joint_dim += dim; }

        return joint_dim;
    }

    template <int Size, int k = 0>
    void mean_(const MarginalDistributions& distr_tuple,
               Variate& mu,
               int offset = 0) const
    {
        auto&& distribution = std::get<k>(distr_tuple);
        const int dim = distribution.dimension();

        mu.middleRows(offset, dim) = distribution.mean();

        if (Size == k + 1) return;

        mean_<Size, k + (k + 1 < Size ? 1 : 0)>(distr_tuple, mu, offset + dim);
    }

    template <int Size, int k = 0>
    void covariance(const MarginalDistributions& distr,
                    SecondMoment& cov,
                    const int offset = 0) const
    {
        auto& distribution = std::get<k>(distr);
        const int dim = distribution.dimension();

        cov.block(offset, offset, dim, dim) = distribution.covariance();

        if (Size == k + 1) return;

        covariance<Size, k + (k + 1 < Size ? 1 : 0)>(distr, cov, offset + dim);
    }
};

}


#include <vector>
#include <Eigen/Dense>
#include "matplotlibcpp.h"
namespace plt = matplotlibcpp;

int main() {

  const unsigned n = 100;
  Eigen::MatrixXd A(n / 2, n);
  std::vector<std::vector<double>> B;

  for (unsigned i = 0; i < n / 2; ++i) {
    A(i, i) = 1;
    std::vector<double> row(n);
    row[i] = 1;

    if (i < n / 2) {
      A(i, i + n / 2) = 1;
      row[i + n / 2] = 1;
    }
    B.push_back(row);
  }

  for (unsigned i = 0; i < n / 2; ++i) {
    for (unsigned j = 0; j < n; ++j) {
      if (A(i, j) != B[i][j]) {
        std::cout << i << "," << j << " differ!\n";
      }
    }
  }

  plt::figure();
  plt::title("Eigen");
  plt::spy(A);

  plt::figure();
  plt::title("vector");
  plt::spy(B);
  plt::show();
  return 0;
}


#ifndef TDHH_ROUTER_HPP
#define TDHH_ROUTER_HPP

#include <string>
#include "PacketsReader.hpp"
#include "Hyperloglog.hpp"
#include "Heap.hpp"
#include <map>
#include <boost/functional/hash.hpp>
#include <boost/math/special_functions/beta.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include "Utils.hpp"

namespace TDHH {
    using namespace std;
    using namespace hll;

    const int ITERATIONS = 1;

    class Router {

    private:
        DATASET dataset;

    protected:
        string filename;
        PacketsReader pr;

    public:
        explicit Router(const string &filename, DATASET dataset) :
                filename(filename),
                pr(filename, dataset),
                dataset(dataset) {}

        void reset() {
            pr.reset();
        }

        map<int, map<int, vector<double>>> volumeEstimation(vector<int> counters) {
            map<int, map<int, vector<double>>> res;
            std::random_device rd;
            map<int, vector<HyperLogLog> > counter_to_hll_arr;
            for (const int c : counters) {
                auto bits = static_cast<int>(log2(c));
                vector<HyperLogLog> hll_arr;
                for (int I = 0; I < ITERATIONS; I++) {
                    HyperLogLog hll(static_cast<uint8_t>(bits));
                    hll.set_seed(rd());
                    hll_arr.push_back(hll);
                }
                counter_to_hll_arr.insert(pair<int, vector<HyperLogLog>>(c, hll_arr));
            }
            auto pkt = pr.getNextIPPacket();
            int num_pkts = 0;
            while (pkt != nullptr) {
                ++num_pkts;
                const auto &pkt_string = pkt->getReprString();
                for (const int c: counters) {
                    for (auto &hll : counter_to_hll_arr.at(c)) {
                        hll.add(pkt_string.c_str(), static_cast<int>(pkt_string.size()));
                    }
                }
                delete pkt;
                if (num_pkts % 1000000 == 0) {
                    for (const int c: counters) {
                        for (const auto &hll : counter_to_hll_arr.at(c)) {
                            res[c][num_pkts].push_back(hll.estimate());
                        }
                    }
                }
                pkt = pr.getNextIPPacket();
            }
            for (const int c: counters) {
                for (const auto &hll : counter_to_hll_arr.at(c)) {
                    res[c][num_pkts].push_back(hll.estimate());
                }
            }
            return res;
        }

        map<pair<double, double>, vector<map<string, double>>> heavy_hitters(vector<pair<double, double>> params) {
            map<pair<double, double>, vector<map<string, double>>> res;

            map<pair<double, double>, vector<Heap *>> param_to_heap_arr;
            for (const auto &param : params) {
                double eps = param.first;
                double delta = param.second;
                auto chi = static_cast<unsigned int>(ceil(9.0 / (eps * eps) * log2(2.0 / (delta * eps))));
                vector<Heap *> heap_arr;
                for (int I = 0; I < ITERATIONS; I++) {
                    auto *heap = new Heap(chi);
                    heap_arr.push_back(heap);
                }
                param_to_heap_arr.insert(pair<pair<double, double>, vector<Heap *>>(param, heap_arr));
            }

            unsigned int num_pkts = 0;
            auto pkt = pr.getNextIPPacket();
            while (pkt != nullptr) {
                for (const auto &param : params) {
                    for (auto &heap : param_to_heap_arr.at(param)) {
                        heap->Add(pkt->getReprString());
                    }
                }
                delete pkt;
                pkt = pr.getNextIPPacket();
                ++num_pkts;
            }

            for (const auto &param : params) {
                vector<map<string, double>> samples;
                for (auto &heap : param_to_heap_arr.at(param)) {
                    samples.push_back(heap->GetSample());
                }
                res.insert(pair<pair<double, double>, vector<map<string, double>>>(param, samples));
            }

            for (const auto &param : params) {
                for (auto &heap : param_to_heap_arr.at(param)) {
                    delete heap;
                }
            }

            return res;
        }

        int doNothing() {
            auto pkt = pr.getNextIPPacket();
            int num_pkts = 0;
            while (pkt != nullptr) {
                ++num_pkts;
                const auto &pkt_string = pkt->getReprString();
                delete pkt;
                pkt = pr.getNextIPPacket();
            }
            return num_pkts;
        }

        map<pair<double, double>, vector<map<string, double>>>
        frequencyEstimation(vector<pair<double, double>> params) {
            map<pair<double, double>, vector<map<string, double>>> res;
            std::random_device rd;

            map<pair<double, double>, vector<HyperLogLog>> param_to_hll_arr;
            for (const auto &param : params) {
                double eps = param.first;
                double delta = param.second;
                auto counters = static_cast<int>(pow((3 / eps), 2));
                auto bits = static_cast<int>(log2(counters));
                vector<HyperLogLog> hll_arr;
                for (int I = 0; I < ITERATIONS; I++) {
                    HyperLogLog hll(static_cast<uint8_t>(bits));
                    hll.set_seed(rd());
                    hll_arr.push_back(hll);
                }
                param_to_hll_arr.insert(pair<pair<double, double>, vector<HyperLogLog>>(param, hll_arr));
            }

            map<pair<double, double>, vector<Heap *>> param_to_heap_arr;
            for (const auto &param : params) {
                double eps = param.first;
                double delta = param.second;
                auto chi = static_cast<unsigned int>(ceil(3.0 / (eps / 2 * eps / 2) * log2(2.0 / delta / 2)));
                vector<Heap *> heap_arr;
                for (int I = 0; I < ITERATIONS; I++) {
                    auto *heap = new Heap(chi);
                    heap_arr.push_back(heap);
                }
                param_to_heap_arr.insert(pair<pair<double, double>, vector<Heap *>>(param, heap_arr));
            }

            int num_pkts = 0;
            std::clock_t start;
            start = std::clock();
            double duration = (std::clock() - start) / (double) CLOCKS_PER_SEC;
            cout << "pkts:" << num_pkts << " duration:" << duration << "[s]" << endl;
            auto pkt = pr.getNextIPPacket();
            while (pkt != nullptr) {
                ++num_pkts;
                if (num_pkts > stoi(getFrequencyLimit(dataset))) {
                    break;
                }
                const auto &pkt_string = pkt->getReprString();
                for (const auto &param : params) {
                    for (auto &hll : param_to_hll_arr.at(param)) {
                        hll.add(pkt_string.c_str(), static_cast<int>(pkt_string.size()));
                    }
                    for (auto &heap : param_to_heap_arr.at(param)) {
                        heap->Add(pkt->getReprString());
                    }
                }
                delete pkt;
                if (num_pkts % 1000000 == 0) {
                    duration = (std::clock() - start) / (double) CLOCKS_PER_SEC;
                    cout << "pkts:" << num_pkts << " duration:" << duration << "[s]" << endl;
                }
                pkt = pr.getNextIPPacket();
            }
            for (const auto &param : params) {
                double eps = param.first;
                double delta = param.second;
                double chi = ceil(3.0 / (eps / 2 * eps / 2) * log2(2.0 / delta / 2));
                cout << eps << " " << delta << " " << chi << endl;

                vector<double> Ps;
                vector<map<string, double>> samples;

                for (auto &hll : param_to_hll_arr.at(param)) {
                    double d = hll.estimate();
                    cout << "hll.estimate:" << d << endl;
                    Ps.push_back(chi / hll.estimate());
                }
                for (auto &heap : param_to_heap_arr.at(param)) {
                    const auto &s = heap->GetSample();
                    samples.push_back(s);
                }

                for (int k = 0; k < samples.size(); ++k) {
                    auto &sample = samples[k];
                    auto p = Ps[k];
                    cout << "p:" << p << endl;
                    for (const auto &s : sample) {
                        cout << "flow:" << s.first << endl;
                        cout << "sampled_frequency:" << s.second << endl;
                        double estimated_frequency = s.second / p;
                        sample[s.first] = estimated_frequency;
                        cout << "estimated_frequency:" << estimated_frequency << endl;
                    }
                    res[param] = samples;
                }
            }
            cout << "Finished preparing samples" << endl;

            for (const auto &param : params) {
                for (auto &heap : param_to_heap_arr.at(param)) {
                    delete heap;
                }
            }
            cout << "Finished deleting heaps" << endl;
            return res;
        }
    };
}

#endif //TDHH_ROUTER_HPP


/* ---------------------------------------------------------------------
 *
 * Copyright (C) 2006 - 2020 by the deal.II authors
 *
 * This file is part of the deal.II library.
 *
 * The deal.II library is free software; you can use it, redistribute
 * it, and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * The full text of the license can be found in the file LICENSE.md at
 * the top level directory of deal.II.
 *
 * ---------------------------------------------------------------------
 *
 * Author: Ivan Christov, Wolfgang Bangerth, Texas A&M University, 2006
 */

// @sect3{Include files and global variables}

// 关于包含文件的解释，读者应该参考示例程序  step-1  到  step-4  。它们的标准顺序是  base -- lac -- grid  --  dofs -- fe -- numerics  （因为每一类大致都是建立在前面的基础上），然后是一些用于文件输入/输出和字符串流的C++头文件。

#include <deal.II/base/quadrature_lib.h>
#include <deal.II/base/function.h>
#include <deal.II/base/utilities.h>

#include <deal.II/lac/vector.h>
#include <deal.II/lac/full_matrix.h>
#include <deal.II/lac/sparse_matrix.h>
#include <deal.II/lac/dynamic_sparsity_pattern.h>
#include <deal.II/lac/solver_cg.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/affine_constraints.h>

#include <deal.II/grid/tria.h>
#include <deal.II/grid/grid_generator.h>

#include <deal.II/dofs/dof_handler.h>
#include <deal.II/dofs/dof_tools.h>

#include <deal.II/fe/fe_q.h>
#include <deal.II/fe/fe_values.h>

#include <deal.II/numerics/vector_tools.h>
#include <deal.II/numerics/matrix_tools.h>
#include <deal.II/numerics/data_out.h>

#include <fstream>
#include <iostream>

// 最后一步和以前所有的程序一样。

namespace Step25
{
  using namespace dealii;
// @sect3{The SineGordonProblem class template}

// 解决问题的整个算法被封装在这个类中。和以前的例子程序一样，这个类在声明时有一个模板参数，就是空间维度，这样我们就可以在一个、两个或三个空间维度上解决正弦-戈登方程。关于这个问题的独立于维度的类封装的更多信息，读者应该参考  step-3  和  step-4  。

// 与 step-23 和 step-24 相比，在程序的总体结构中没有任何值得注意的地方（当然，在各种函数的内部运作中也有！）。最明显的区别是出现了两个新的函数 compute_nl_term 和 compute_nl_matrix ，计算系统矩阵的非线性贡献和第一个方程的右手边，正如在介绍中讨论的那样。此外，我们还必须有一个向量 solution_update ，它包含在每个牛顿步骤中对解向量的非线性更新。

// 正如介绍中也提到的，我们在这个程序中不存储速度变量，而是质量矩阵乘以速度。这是在 M_x_velocity 变量中完成的（"x "是代表 "次数"）。

// 最后， output_timestep_skip 变量存储了在生成图形输出前每次所需的时间步数。这一点在使用精细网格（因此时间步数较小）时非常重要，在这种情况下，我们会运行大量的时间步数，并创建大量的输出文件，这些文件中的解决方案在后续文件中看起来几乎是一样的。这只会堵塞我们的可视化程序，我们应该避免创建比我们真正感兴趣的更多的输出。因此，如果这个变量被设置为大于1的值 $n$ ，那么只有在每一个 $n$ 的时间步长时才会产生输出。

  template <int dim>
  class SineGordonProblem
  {
  public:
    SineGordonProblem();
    void run();

  private:
    void         make_grid_and_dofs();
    void         assemble_system();
    void         compute_nl_term(const Vector<double> &old_data,
                                 const Vector<double> &new_data,
                                 Vector<double> &      nl_term) const;
    void         compute_nl_matrix(const Vector<double> &old_data,
                                   const Vector<double> &new_data,
                                   SparseMatrix<double> &nl_matrix) const;
    unsigned int solve();
    void         output_results(const unsigned int timestep_number) const;

    Triangulation<dim> triangulation;
    FE_Q<dim>          fe;
    DoFHandler<dim>    dof_handler;

    SparsityPattern      sparsity_pattern;
    SparseMatrix<double> system_matrix;
    SparseMatrix<double> mass_matrix;
    SparseMatrix<double> laplace_matrix;

    const unsigned int n_global_refinements;

    double       time;
    const double final_time, time_step;
    const double theta;

    Vector<double> solution, solution_update, old_solution;
    Vector<double> M_x_velocity;
    Vector<double> system_rhs;

    const unsigned int output_timestep_skip;
  };
// @sect3{Initial conditions}

// 在下面两类中，我们首先实现了本程序介绍中提到的一维、二维和三维的精确解。如果想通过比较数值解和分析解来测试程序的准确性，那么这个时空解可能会有独立的意义（但是请注意，程序使用的是有限域，而这些是无界域的分析解）。例如，这可以用 VectorTools::integrate_difference 函数来完成。再次注意（正如在 step-23 中已经讨论过的），我们如何将时空函数描述为依赖于时间变量的空间函数，该变量可以使用FunctionTime基类的 FunctionTime::set_time() 和 FunctionTime::get_time() 成员函数进行设置和查询。

  template <int dim>
  class ExactSolution : public Function<dim>
  {
  public:
    ExactSolution(const unsigned int n_components = 1, const double time = 0.)
      : Function<dim>(n_components, time)
    {}

    virtual double value(const Point<dim> &p,
                         const unsigned int /*component*/ = 0) const override
    {
      const double t = this->get_time();

      switch (dim)
        {
          case 1:
            {
              const double m  = 0.5;
              const double c1 = 0.;
              const double c2 = 0.;
              return -4. * std::atan(m / std::sqrt(1. - m * m) *
                                     std::sin(std::sqrt(1. - m * m) * t + c2) /
                                     std::cosh(m * p[0] + c1));
            }

          case 2:
            {
              const double theta  = numbers::PI / 4.;
              const double lambda = 1.;
              const double a0     = 1.;
              const double s      = 1.;
              const double arg    = p[0] * std::cos(theta) +
                                 std::sin(theta) * (p[1] * std::cosh(lambda) +
                                                    t * std::sinh(lambda));
              return 4. * std::atan(a0 * std::exp(s * arg));
            }

          case 3:
            {
              const double theta = numbers::PI / 4;
              const double phi   = numbers::PI / 4;
              const double tau   = 1.;
              const double c0    = 1.;
              const double s     = 1.;
              const double arg   = p[0] * std::cos(theta) +
                                 p[1] * std::sin(theta) * std::cos(phi) +
                                 std::sin(theta) * std::sin(phi) *
                                   (p[2] * std::cosh(tau) + t * std::sinh(tau));
              return 4. * std::atan(c0 * std::exp(s * arg));
            }

          default:
            Assert(false, ExcNotImplemented());
            return -1e8;
        }
    }
  };

// 在本节的第二部分，我们提供初始条件。我们很懒惰（也很谨慎），不想第二次实现与上面相同的函数。相反，如果我们被查询到初始条件，我们会创建一个对象 ExactSolution ，将其设置为正确的时间，并让它计算当时的精确解的任何值。

  template <int dim>
  class InitialValues : public Function<dim>
  {
  public:
    InitialValues(const unsigned int n_components = 1, const double time = 0.)
      : Function<dim>(n_components, time)
    {}

    virtual double value(const Point<dim> & p,
                         const unsigned int component = 0) const override
    {
      return ExactSolution<dim>(1, this->get_time()).value(p, component);
    }
  };
// @sect3{Implementation of the SineGordonProblem class}

// 让我们继续讨论主类的实现，因为它实现了介绍中概述的算法。

//  @sect4{SineGordonProblem::SineGordonProblem}

// 这是 SineGordonProblem 类的构造函数。它指定了所需的有限元的多项式程度，关联了一个 DoFHandler to the triangulation 对象（就像在示例程序 step-3 和 step-4 中一样），初始化了当前或初始时间、最终时间、时间步长，以及时间步长方案的 $\theta$ 值。由于我们在这里计算的解是时间周期性的，所以开始时间的实际值并不重要，我们选择它是为了让我们在一个有趣的时间开始。

// 请注意，如果我们选择显式欧拉时间步进方案（ $\theta = 0$ ），那么我们必须选择一个时间步进 $k \le h$ ，否则该方案不稳定，解中可能出现振荡。Crank-Nicolson方案（ $\theta = \frac{1}{2}$ ）和隐式Euler方案（ $\theta=1$ ）不存在这个缺陷，因为它们是无条件稳定的。然而，即使如此，时间步长也应选择在 $h$ 的数量级上，以获得一个好的解决方案。由于我们知道我们的网格是由矩形的均匀细分而来，我们可以很容易地计算出这个时间步长；如果我们有一个不同的域， step-24 中的技术使用 GridTools::minimal_cell_diameter 也是可以的。

  template <int dim>
  SineGordonProblem<dim>::SineGordonProblem()
    : fe(1)
    , dof_handler(triangulation)
    , n_global_refinements(6)
    , time(-5.4414)
    , final_time(2.7207)
    , time_step(10 * 1. / std::pow(2., 1. * n_global_refinements))
    , theta(0.5)
    , output_timestep_skip(1)
  {}
// @sect4{SineGordonProblem::make_grid_and_dofs}

// 这个函数创建了一个 dim 维度的矩形网格，并对其进行了多次细化。同时，一旦自由度被集合起来， SineGordonProblem 类的所有矩阵和向量成员都被初始化为相应的大小。像 step-24 一样，我们使用 MatrixCreator 函数来生成质量矩阵 $M$ 和拉普拉斯矩阵 $A$ ，并在程序的剩余时间里将它们存储在适当的变量中。

  template <int dim>
  void SineGordonProblem<dim>::make_grid_and_dofs()
  {
    GridGenerator::hyper_cube(triangulation, -10, 10);
    triangulation.refine_global(n_global_refinements);

    std::cout << "   Number of active cells: " << triangulation.n_active_cells()
              << std::endl
              << "   Total number of cells: " << triangulation.n_cells()
              << std::endl;

    dof_handler.distribute_dofs(fe);

    std::cout << "   Number of degrees of freedom: " << dof_handler.n_dofs()
              << std::endl;

    DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
    DoFTools::make_sparsity_pattern(dof_handler, dsp);
    sparsity_pattern.copy_from(dsp);

    system_matrix.reinit(sparsity_pattern);
    mass_matrix.reinit(sparsity_pattern);
    laplace_matrix.reinit(sparsity_pattern);

    MatrixCreator::create_mass_matrix(dof_handler,
                                      QGauss<dim>(fe.degree + 1),
                                      mass_matrix);
    MatrixCreator::create_laplace_matrix(dof_handler,
                                         QGauss<dim>(fe.degree + 1),
                                         laplace_matrix);

    solution.reinit(dof_handler.n_dofs());
    solution_update.reinit(dof_handler.n_dofs());
    old_solution.reinit(dof_handler.n_dofs());
    M_x_velocity.reinit(dof_handler.n_dofs());
    system_rhs.reinit(dof_handler.n_dofs());
  }
// @sect4{SineGordonProblem::assemble_system}

// 这个函数为牛顿方法的每次迭代组装系统矩阵和右手向量。关于系统矩阵和右手边的明确公式，读者应该参考导论。

// 请注意，在每个时间步长中，我们必须把对矩阵和右手边的各种贡献加起来。与 step-23 和 step-24 相比，这需要集合更多的项，因为它们取决于前一个时间步骤或前一个非线性步骤的解。我们使用函数 compute_nl_matrix 和 compute_nl_term 来做到这一点，而本函数提供了顶层逻辑。

  template <int dim>
  void SineGordonProblem<dim>::assemble_system()
  {

// 首先我们组装雅各布矩阵 $F'_h(U^{n,l})$  ，其中 $U^{n,l}$ 为方便起见被储存在向量 solution 中。

    system_matrix.copy_from(mass_matrix);
    system_matrix.add(std::pow(time_step * theta, 2), laplace_matrix);

    SparseMatrix<double> tmp_matrix(sparsity_pattern);
    compute_nl_matrix(old_solution, solution, tmp_matrix);
    system_matrix.add(std::pow(time_step * theta, 2), tmp_matrix);

// 接下来我们计算右手边的向量。这只是介绍中对 $-F_h(U^{n,l})$ 的描述所暗示的矩阵-向量的组合。

    system_rhs = 0.;

    Vector<double> tmp_vector(solution.size());

    mass_matrix.vmult(system_rhs, solution);
    laplace_matrix.vmult(tmp_vector, solution);
    system_rhs.add(std::pow(time_step * theta, 2), tmp_vector);

    mass_matrix.vmult(tmp_vector, old_solution);
    system_rhs.add(-1.0, tmp_vector);
    laplace_matrix.vmult(tmp_vector, old_solution);
    system_rhs.add(std::pow(time_step, 2) * theta * (1 - theta), tmp_vector);

    system_rhs.add(-time_step, M_x_velocity);

    compute_nl_term(old_solution, solution, tmp_vector);
    system_rhs.add(std::pow(time_step, 2) * theta, tmp_vector);

    system_rhs *= -1.;
  }
// @sect4{SineGordonProblem::compute_nl_term}

// 这个函数计算向量 $S(\cdot,\cdot)$ ，它出现在分裂公式的两个方程的非线性项中。这个函数不仅简化了这个项的重复计算，而且也是我们在时间步长为隐式时使用的非线性迭代求解器的基本组成部分（即 $\theta\ne 0$  ）。此外，我们必须允许该函数接收一个 "旧 "和一个 "新 "的解决方案作为输入。这些可能不是存储在 old_solution and solution 中的问题的实际解决方案，而只是我们线性化的两个函数。为了这个函数的目的，让我们在下面这个类的文档中分别调用前两个参数  $w_{\mathrm{old}}$  和  $w_{\mathrm{new}}$  。

// 作为一个旁注，也许值得研究一下什么阶次的正交公式最适合这种类型的积分。由于 $\sin(\cdot)$ 不是一个多项式，可能没有正交公式可以准确地积分这些项。通常只需确保右手边的积分达到与离散化方案相同的精度即可，但通过选择更精确的正交公式，也许可以改善渐近收敛声明中的常数。

  template <int dim>
  void SineGordonProblem<dim>::compute_nl_term(const Vector<double> &old_data,
                                               const Vector<double> &new_data,
                                               Vector<double> &nl_term) const
  {
    nl_term = 0;
    const QGauss<dim> quadrature_formula(fe.degree + 1);
    FEValues<dim>     fe_values(fe,
                            quadrature_formula,
                            update_values | update_JxW_values |
                              update_quadrature_points);

    const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
    const unsigned int n_q_points    = quadrature_formula.size();

    Vector<double>                       local_nl_term(dofs_per_cell);
    std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
    std::vector<double>                  old_data_values(n_q_points);
    std::vector<double>                  new_data_values(n_q_points);

    for (const auto &cell : dof_handler.active_cell_iterators())
      {
        local_nl_term = 0;

// 一旦我们将 FEValues 实例化重新初始化到当前单元格，我们就利用 get_function_values 例程来获取 "旧 "数据（大概在 $t=t_{n-1}$ ）和 "新 "数据（大概在 $t=t_n$ ）在所选正交公式节点的值。

        fe_values.reinit(cell);
        fe_values.get_function_values(old_data, old_data_values);
        fe_values.get_function_values(new_data, new_data_values);

// 现在，我们可以用所需的正交公式来评估  $\int_K \sin\left[\theta w_{\mathrm{new}} + (1-\theta) w_{\mathrm{old}}\right] \,\varphi_j\,\mathrm{d}x$  。

        for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
          for (unsigned int i = 0; i < dofs_per_cell; ++i)
            local_nl_term(i) +=
              (std::sin(theta * new_data_values[q_point] +
                        (1 - theta) * old_data_values[q_point]) *
               fe_values.shape_value(i, q_point) * fe_values.JxW(q_point));

// 我们通过将各单元的积分对全局积分的贡献相加来得出结论。

        cell->get_dof_indices(local_dof_indices);

        for (unsigned int i = 0; i < dofs_per_cell; ++i)
          nl_term(local_dof_indices[i]) += local_nl_term(i);
      }
  }
// @sect4{SineGordonProblem::compute_nl_matrix}

// 这是处理非线性方案的第二个函数。它计算矩阵  $N(\cdot,\cdot)$  ，它出现在  $F(\cdot)$  的雅各布项的非线性项中。正如 compute_nl_term 一样，我们必须让这个函数接收一个 "旧 "和一个 "新 "的解决方案作为输入，我们再次将其分别称为 $w_{\mathrm{old}}$ 和 $w_{\mathrm{new}}$ ，如下。

  template <int dim>
  void SineGordonProblem<dim>::compute_nl_matrix(
    const Vector<double> &old_data,
    const Vector<double> &new_data,
    SparseMatrix<double> &nl_matrix) const
  {
    QGauss<dim>   quadrature_formula(fe.degree + 1);
    FEValues<dim> fe_values(fe,
                            quadrature_formula,
                            update_values | update_JxW_values |
                              update_quadrature_points);

    const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
    const unsigned int n_q_points    = quadrature_formula.size();

    FullMatrix<double> local_nl_matrix(dofs_per_cell, dofs_per_cell);
    std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
    std::vector<double>                  old_data_values(n_q_points);
    std::vector<double>                  new_data_values(n_q_points);

    for (const auto &cell : dof_handler.active_cell_iterators())
      {
        local_nl_matrix = 0;

// 同样，首先我们将我们的 FEValues 实例化重新初始化为当前单元。

        fe_values.reinit(cell);
        fe_values.get_function_values(old_data, old_data_values);
        fe_values.get_function_values(new_data, new_data_values);

// 然后，我们用所需的正交公式评估 $\int_K \cos\left[\theta w_{\mathrm{new}} + (1-\theta) w_{\mathrm{old}}\right]\, \varphi_i\, \varphi_j\,\mathrm{d}x$ 。

        for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
          for (unsigned int i = 0; i < dofs_per_cell; ++i)
            for (unsigned int j = 0; j < dofs_per_cell; ++j)
              local_nl_matrix(i, j) +=
                (std::cos(theta * new_data_values[q_point] +
                          (1 - theta) * old_data_values[q_point]) *
                 fe_values.shape_value(i, q_point) *
                 fe_values.shape_value(j, q_point) * fe_values.JxW(q_point));

// 最后，我们将各单元的积分对全局积分的贡献相加。

        cell->get_dof_indices(local_dof_indices);

        for (unsigned int i = 0; i < dofs_per_cell; ++i)
          for (unsigned int j = 0; j < dofs_per_cell; ++j)
            nl_matrix.add(local_dof_indices[i],
                          local_dof_indices[j],
                          local_nl_matrix(i, j));
      }
  }

//  @sect4{SineGordonProblem::solve}

// 正如在介绍中所讨论的，这个函数在线性方程组上使用CG迭代求解器，该方程组是由牛顿方法的每个迭代的有限元空间离散化产生的，用于分割公式中的（非线性）第一个方程。该系统的解实际上是 $\delta U^{n,l}$ ，所以它被存储在 solution_update and used to update solution 的 run 函数中。

// 注意，我们在求解前将解的更新值重新设置为零。这是没有必要的：迭代求解器可以从任何一点开始并收敛到正确的解。如果对线性系统的解有一个很好的估计，那么从这个向量开始可能是值得的，但是作为一个一般的观察，起点并不是很重要：它必须是一个非常非常好的猜测，以减少超过几个迭代的次数。事实证明，对于这个问题，使用之前的非线性更新作为起点实际上会损害收敛性并增加所需的迭代次数，所以我们简单地将其设置为零。

// 该函数返回收敛到一个解决方案所需的迭代次数。这个数字以后将被用来在屏幕上生成输出，显示每次非线性迭代需要多少次迭代。

  template <int dim>
  unsigned int SineGordonProblem<dim>::solve()
  {
    SolverControl            solver_control(1000, 1e-12 * system_rhs.l2_norm());
    SolverCG<Vector<double>> cg(solver_control);

    PreconditionSSOR<SparseMatrix<double>> preconditioner;
    preconditioner.initialize(system_matrix, 1.2);

    cg.solve(system_matrix, solution_update, system_rhs, preconditioner);

    return solver_control.last_step();
  }
// @sect4{SineGordonProblem::output_results}

// 这个函数将结果输出到一个文件。它与  step-23  和  step-24  中的相应函数基本相同。

  template <int dim>
  void SineGordonProblem<dim>::output_results(
    const unsigned int timestep_number) const
  {
    DataOut<dim> data_out;

    data_out.attach_dof_handler(dof_handler);
    data_out.add_data_vector(solution, "u");
    data_out.build_patches();

    const std::string filename =
      "solution-" + Utilities::int_to_string(timestep_number, 3) + ".vtu";
    DataOutBase::VtkFlags vtk_flags;
    vtk_flags.compression_level =
      DataOutBase::VtkFlags::ZlibCompressionLevel::best_speed;
    data_out.set_flags(vtk_flags);
    std::ofstream output(filename);
    data_out.write_vtu(output);
  }
// @sect4{SineGordonProblem::run}

// 这个函数对一切都有最高级别的控制：它运行（外部）时间步长循环，（内部）非线性求解器循环，并在每个时间步长后输出解。

  template <int dim>
  void SineGordonProblem<dim>::run()
  {
    make_grid_and_dofs();

// 为了确认初始条件，我们必须使用函数  $u_0(x)$  来计算  $U^0$  。为此，下面我们将创建一个 InitialValues 类型的对象；注意，当我们创建这个对象（它来自 Function 类）时，我们将其内部的时间变量设置为 $t_0$ ，以表明初始条件是在 $t=t_0$ 处评估的空间和时间的函数。

// 然后我们通过使用 VectorTools::project 将 $u_0(x)$ 投影到网格上，产生 $U^0$ 。我们必须使用与 step-21 相同的悬挂节点约束结构： VectorTools::project 函数需要一个悬挂节点约束对象，但为了使用它，我们首先需要关闭它。

    {
      AffineConstraints<double> constraints;
      constraints.close();
      VectorTools::project(dof_handler,
                           constraints,
                           QGauss<dim>(fe.degree + 1),
                           InitialValues<dim>(1, time),
                           solution);
    }

// 为了完整起见，我们像其他时间步长一样，将第2个时间步长输出到一个文件。

    output_results(0);

// 现在我们进行时间步进：在每个时间步进中，我们解决与问题的有限元离散化相对应的矩阵方程，然后根据我们在介绍中讨论的时间步进公式推进我们的解决方案。

    unsigned int timestep_number = 1;
    for (time += time_step; time <= final_time;
         time += time_step, ++timestep_number)
      {
        old_solution = solution;

        std::cout << std::endl
                  << "Time step #" << timestep_number << "; "
                  << "advancing to t = " << time << "." << std::endl;

// 在每个时间步长的开始，我们必须通过牛顿方法求解拆分公式中的非线性方程---即先求解 $\delta U^{n,l}$ ，然后再计算 $U^{n,l+1}$ ，如此反复。这种非线性迭代的停止标准是： $\|F_h(U^{n,l})\|_2 \le 10^{-6} \|F_h(U^{n,0})\|_2$  。因此，我们需要记录第一次迭代中残差的规范。

// 在每次迭代结束时，我们向控制台输出我们花了多少次线性求解器的迭代。当下面的循环完成后，我们有（一个近似的） $U^n$  。

        double initial_rhs_norm = 0.;
        bool   first_iteration  = true;
        do
          {
            assemble_system();

            if (first_iteration == true)
              initial_rhs_norm = system_rhs.l2_norm();

            const unsigned int n_iterations = solve();

            solution += solution_update;

            if (first_iteration == true)
              std::cout << "    " << n_iterations;
            else
              std::cout << '+' << n_iterations;
            first_iteration = false;
          }
        while (system_rhs.l2_norm() > 1e-6 * initial_rhs_norm);

        std::cout << " CG iterations per nonlinear step." << std::endl;

// 在得到问题的第一个方程 $t=t_n$ 的解后，我们必须更新辅助速度变量  $V^n$  。然而，我们不计算和存储 $V^n$ ，因为它不是我们在问题中直接使用的数量。因此，为了简单起见，我们直接更新 $MV^n$ 。

        Vector<double> tmp_vector(solution.size());
        laplace_matrix.vmult(tmp_vector, solution);
        M_x_velocity.add(-time_step * theta, tmp_vector);

        laplace_matrix.vmult(tmp_vector, old_solution);
        M_x_velocity.add(-time_step * (1 - theta), tmp_vector);

        compute_nl_term(old_solution, solution, tmp_vector);
        M_x_velocity.add(-time_step, tmp_vector);

// 很多时候，特别是对于细网格，我们必须选择相当小的时间步长，以使方案稳定。因此，有很多时间步长，在解的过程中 "没有什么有趣的事情发生"。为了提高整体效率--特别是加快程序速度和节省磁盘空间--我们每隔 output_timestep_skip 个时间步数才输出解。

        if (timestep_number % output_timestep_skip == 0)
          output_results(timestep_number);
      }
  }
} // namespace Step25
// @sect3{The main function}

// 这是该程序的主函数。它创建一个顶层类的对象并调用其主函数。如果在执行 SineGordonProblem 类的运行方法时抛出了异常，我们会在这里捕获并报告它们。关于异常的更多信息，读者应该参考  step-6  。

int main()
{
  try
    {
      using namespace Step25;

      SineGordonProblem<1> sg_problem;
      sg_problem.run();
    }
  catch (std::exception &exc)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Exception on processing: " << std::endl
                << exc.what() << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;

      return 1;
    }
  catch (...)
    {
      std::cerr << std::endl
                << std::endl
                << "----------------------------------------------------"
                << std::endl;
      std::cerr << "Unknown exception!" << std::endl
                << "Aborting!" << std::endl
                << "----------------------------------------------------"
                << std::endl;
      return 1;
    }

  return 0;
}


//
// MIT License
//
// Copyright (c) Deif Lou
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//

#include <Eigen/Sparse>

#include "cubicsplineinterpolator1D.h"

namespace ibp {
namespace misc {

CubicSplineInterpolator1D::CubicSplineInterpolator1D() :
    BaseSplineInterpolator1D(),
    mFloorBoundaryConditions(BoundaryConditions_Natural),
    mCeilBoundaryConditions(BoundaryConditions_Natural),
    mFloorBoundaryConditionsValue(0.),
    mCeilBoundaryConditionsValue(0.),
    mIsDirty(true)
{
}

Interpolator1D * CubicSplineInterpolator1D::clone() const
{
    CubicSplineInterpolator1D * si = new CubicSplineInterpolator1D();
    if (!si)
        return 0;
    si->mKnots = mKnots;
    si->mFloorExtrapolationMode = mFloorExtrapolationMode;
    si->mCeilExtrapolationMode = mCeilExtrapolationMode;
    si->mFloorExtrapolationValue = mFloorExtrapolationValue;
    si->mCeilExtrapolationValue = mCeilExtrapolationValue;
    si->mFloorBoundaryConditions = mFloorBoundaryConditions;
    si->mCeilBoundaryConditions = mCeilBoundaryConditions;
    si->mFloorBoundaryConditionsValue = mFloorBoundaryConditionsValue;
    si->mCeilBoundaryConditionsValue = mCeilBoundaryConditionsValue;
    si->mCoefficients = mCoefficients;
    si->mIsDirty = mIsDirty;
    return si;
}

double CubicSplineInterpolator1D::F(double x)
{
    if (mIsDirty)
        calculateCoefficients();
    const int piece = pieceForValue(x);
    const double w = x - mKnots[piece].x();
    return ((mCoefficients[piece].a * w + mCoefficients[piece].b) * w +
            mCoefficients[piece].c) * w + mCoefficients[piece].d;
}

double CubicSplineInterpolator1D::floorExtrapolate(double x)
{
    if (mFloorExtrapolationMode == ExtrapolationMode_Constant)
        return mFloorExtrapolationValue;
    else if (mFloorExtrapolationMode == ExtrapolationMode_FollowTangent && mKnots.size() > 1)
    {
        if (mIsDirty)
            calculateCoefficients();
        return mCoefficients.first().c * (x - mKnots[0].x()) + mKnots[0].y();
    }
    else if (mFloorExtrapolationMode == ExtrapolationMode_Repeat && mKnots.size() > 1)
        x = fmod(x - mKnots.first().x(), mKnots.last().x() - mKnots.first().x()) + mKnots.last().x();
    else if (mFloorExtrapolationMode == ExtrapolationMode_Mirror && mKnots.size() > 1)
    {
        const double a = (mKnots.last().x() - mKnots.first().x()) * 2.;
        const double b = (x - mKnots.first().x()) / a;
        x = fabs(b - floor(b + .5)) * a + mKnots.first().x();
    }
    else
        return mKnots.first().y();
    return F(x);
}

double CubicSplineInterpolator1D::ceilExtrapolate(double x)
{
    if (mCeilExtrapolationMode == ExtrapolationMode_Constant)
        return mCeilExtrapolationValue;
    else if (mCeilExtrapolationMode == ExtrapolationMode_FollowTangent && mKnots.size() > 1)
    {
        if (mIsDirty)
            calculateCoefficients();
        const double lkp = mKnots.last().x() - mKnots[mKnots.size() - 2].x();
        return ((3. * mCoefficients.last().a * lkp + 2. * mCoefficients.last().b) * lkp + mCoefficients.last().c) *
               (x - mKnots.last().x()) + mKnots.last().y();
    }
    else if (mCeilExtrapolationMode == ExtrapolationMode_Repeat && mKnots.size() > 1)
        x = fmod(x - mKnots.first().x(), mKnots.last().x() - mKnots.first().x()) + mKnots.first().x();
    else if (mCeilExtrapolationMode == ExtrapolationMode_Mirror && mKnots.size() > 1)
    {
        const double a = (mKnots.last().x() - mKnots.first().x()) * 2.;
        const double b = (x - mKnots.first().x()) / a;
        x = fabs(b - floor(b + .5)) * a + mKnots.first().x();
    }
    else
        return mKnots.last().y();
    return F(x);
}

void CubicSplineInterpolator1D::calculateCoefficients()
{
    if (mKnots.size() < 1) return;

    register int kSize = mKnots.size();
    bool isPeriodic = mFloorBoundaryConditions == BoundaryConditions_Periodic &&
                      mCeilBoundaryConditions == BoundaryConditions_Periodic;

    mCoefficients.clear();
    mCoefficients.resize(kSize > 1 ? kSize - 1 : 1);

    if (kSize == 1)
    {
        // ------------------------------------------
        // constant, the final value is extrapolated
        // from the unique knot
        // ------------------------------------------
        mCoefficients[0].a = mCoefficients[0].b = mCoefficients[0].c = 0.;
        mCoefficients[0].d = mKnots[0].y();
    }
    else if (kSize == 2)
    {
        // ------------------------------------------
        // linear interpolation
        // ------------------------------------------
        mCoefficients[0].a = mCoefficients[0].b = 0.;
        mCoefficients[0].c = (mKnots[1].y() - mKnots[0].y()) / (mKnots[1].x() - mKnots[0].x());
        mCoefficients[0].d = mKnots[0].y();
    }
    else
    {
        // ------------------------------------------
        // cubic interpolation
        // ------------------------------------------
        std::vector<Eigen::Triplet<double> > tripletList;
        Eigen::SparseMatrix<double> AMatrix;
        Eigen::VectorXd bVector;
        Eigen::VectorXd xVector;
        register float dx0, dx1, dx2, dy0, dy1;

        // ------------------------------------------
        // if is periodic, we need to solve a
        // (ksize - 1) x (ksize - 1) system; otherwise,
        // the system is (ksize - 2) x (ksize - 2)
        // ------------------------------------------
        if (isPeriodic)
        {
            tripletList.reserve((kSize - 1) * 3);
            bVector = Eigen::VectorXd(kSize - 1);

            // ------------------------------------------
            // fill the A matrix and the b vector
            // ------------------------------------------
            for (int i = 1; i < kSize - 2; i++)
            {
                // distances between knots
                dx0 = mKnots[i].x() - mKnots[i - 1].x();
                dx1 = mKnots[i + 1].x() - mKnots[i].x();
                dx2 = dx0 + dx1;
                dy0 = mKnots[i].y() - mKnots[i - 1].y();
                dy1 = mKnots[i + 1].y() - mKnots[i].y();
                // diagonal
                tripletList.push_back(Eigen::Triplet<double>(i, i, 2. * dx2));
                // upper diagonal
                tripletList.push_back(Eigen::Triplet<double>(i, i + 1, dx1));
                // lower diagonal
                tripletList.push_back(Eigen::Triplet<double>(i + 1, i, dx1));
                // b vector
                bVector(i) = 6. * (dy1 / dx1 - dy0 / dx0);
            }

            // ------------------------------------------
            // fill the (0, 0), (0, 1), (0, n - 1),
            // (1, 0), (n - 1, 0), (n - 1, n - 1) entries
            // of A and the 0 and n - 1 entries of b
            // ------------------------------------------
            dx0 = mKnots[kSize - 1].x() - mKnots[kSize - 2].x();
            dx1 = mKnots[1].x() - mKnots[0].x();
            dx2 = dx0 + dx1;
            dy0 = mKnots[0].y() - mKnots[kSize - 2].y();
            dy1 = mKnots[1].y() - mKnots[0].y();
            tripletList.push_back(Eigen::Triplet<double>(0, 0, 2. * dx2));
            tripletList.push_back(Eigen::Triplet<double>(0, 1, dx1));
            tripletList.push_back(Eigen::Triplet<double>(0, kSize - 2, dx0));
            tripletList.push_back(Eigen::Triplet<double>(1, 0, dx1));
            bVector(0) = 6. * (dy1 / dx1 - dy0 / dx0);
            dx0 = mKnots[kSize - 2].x() - mKnots[kSize - 3].x();
            dx1 = mKnots[kSize - 1].x() - mKnots[kSize - 2].x();
            dx2 = dx0 + dx1;
            dy0 = mKnots[kSize - 2].y() - mKnots[kSize - 3].y();
            dy1 = mKnots[0].y() - mKnots[kSize - 2].y();
            tripletList.push_back(Eigen::Triplet<double>(kSize - 2, 0, dx1));
            tripletList.push_back(Eigen::Triplet<double>(kSize - 2, kSize - 2, 2. * dx2));
            bVector(kSize - 2) = 6. * (dy1 / dx1 - dy0 / dx0);

            // ------------------------------------------
            // solve system and copy solution to vector x
            // ------------------------------------------
            AMatrix = Eigen::SparseMatrix<double>(kSize - 1, kSize - 1);
            AMatrix.setFromTriplets(tripletList.begin(), tripletList.end());
            Eigen::SparseLU<Eigen::SparseMatrix<double> > solver(AMatrix);
            xVector = solver.solve(bVector);
        }
        else
        {
            tripletList.reserve((kSize - 2) * 3 - 2);
            bVector = Eigen::VectorXd(kSize - 2);
            xVector = Eigen::VectorXd(kSize);

            // ------------------------------------------
            // fill the A matrix and the b vector
            // ------------------------------------------
            for (int i = 0; i < kSize - 2; i++)
            {
                // distances between knots
                dx0 = mKnots[i + 1].x() - mKnots[i].x();
                dx1 = mKnots[i + 2].x() - mKnots[i + 1].x();
                dx2 = dx0 + dx1;
                dy0 = mKnots[i + 1].y() - mKnots[i].y();
                dy1 = mKnots[i + 2].y() - mKnots[i + 1].y();
                // diagonal
                tripletList.push_back(Eigen::Triplet<double>(i, i, 2. * dx2));
                if (i < kSize - 3)
                {
                    // upper diagonal
                    tripletList.push_back(Eigen::Triplet<double>(i, i + 1, dx1));
                    // lower diagonal
                    tripletList.push_back(Eigen::Triplet<double>(i + 1, i, dx1));
                }
                // b vector
                bVector(i) = 6. * (dy1 / dx1 - dy0 / dx0);
            }

            // ------------------------------------------
            // modify A matrix and b vector subject to
            // the end point constraints
            // ------------------------------------------
            dx0 = mKnots[1].x() - mKnots[0].x();
            dx1 = mKnots[kSize - 1].x() - mKnots[kSize - 2].x();
            dy0 = mKnots[1].y() - mKnots[0].y();
            dy1 = mKnots[kSize - 1].y() - mKnots[kSize - 2].y();
            if (mFloorBoundaryConditions == BoundaryConditions_Fixed1stDerivatives)
            {
                bVector(0) -= 3. * (dy0 / dx0 - mFloorBoundaryConditionsValue);
                tripletList.at(0) = Eigen::Triplet<double>(0, 0, tripletList.at(0).value() - dx0 / 2.);
            }
            else if (mFloorBoundaryConditions == BoundaryConditions_Fixed2ndDerivatives)
                bVector(0) -= dx0 * mFloorBoundaryConditionsValue;
            else if (mFloorBoundaryConditions == BoundaryConditions_Copy)
                tripletList.at(0) = Eigen::Triplet<double>(0, 0, tripletList.at(0).value() + dx0);
            else if (mFloorBoundaryConditions == BoundaryConditions_Extrapolate && kSize > 3)
            {
                dx2 = dx0 * dx0 / (mKnots[2].x() - mKnots[1].x());
                tripletList.at(0) = Eigen::Triplet<double>(0, 0, tripletList.at(0).value() + dx0 + dx2);
                tripletList.at(1) = Eigen::Triplet<double>(0, 1, tripletList.at(1).value() - dx2);
            }
            // EndPointConstraints_Natural, EndPointConstraints_Extrapolate && kSize == 3
            else
                xVector(0) = 0.;

            if (mCeilBoundaryConditions == BoundaryConditions_Fixed1stDerivatives)
            {
                bVector(kSize - 3) -= 3. * (mCeilBoundaryConditionsValue - dy1 / dx1);
                tripletList.at(tripletList.size() - 1) = Eigen::Triplet<double>(kSize - 3, kSize - 3,
                                                         tripletList.at(tripletList.size() - 1).value() - dx1 / 2.);
            }
            else if (mCeilBoundaryConditions == BoundaryConditions_Fixed2ndDerivatives)
                bVector(kSize - 3) -= dx1 * mCeilBoundaryConditionsValue;
            else if (mCeilBoundaryConditions == BoundaryConditions_Copy)
                tripletList.at(tripletList.size() - 1) = Eigen::Triplet<double>(kSize - 3, kSize - 3,
                                                         tripletList.at(tripletList.size() - 1).value() + dx1);
            else if (mCeilBoundaryConditions == BoundaryConditions_Extrapolate && kSize > 3)
            {
                dx2 = dx1 * dx1 / (mKnots[kSize - 2].x() - mKnots[kSize - 3].x());
                tripletList.at(tripletList.size() - 1) = Eigen::Triplet<double>(kSize - 3, kSize - 3,
                                                         tripletList.at(tripletList.size() - 1).value() + dx1 + dx2);
                tripletList.at(tripletList.size() - 2) = Eigen::Triplet<double>(kSize - 3, kSize - 4,
                                                         tripletList.at(tripletList.size() - 2).value() - dx2);
            }
            // EndPointConstraints_Natural, EndPointConstraints_Extrapolate && kSize == 3
            else
                xVector(kSize - 1) = 0.;

            // ------------------------------------------
            // solve system and copy solution to vector x
            // ------------------------------------------
            AMatrix = Eigen::SparseMatrix<double>(kSize - 2, kSize - 2);
            AMatrix.setFromTriplets(tripletList.begin(), tripletList.end());
            Eigen::SparseLU<Eigen::SparseMatrix<double> > solver(AMatrix);
            Eigen::VectorXd xVector2 = solver.solve(bVector);
            for (int i = 0; i < kSize - 2; i++)
                xVector(i + 1) = xVector2(i);

            // ------------------------------------------
            // fix the values of the second derivatives
            // at the first and last knots based on
            // the computed second derivatives at the
            // interiot knots
            // ------------------------------------------
            if (mFloorBoundaryConditions == BoundaryConditions_Fixed1stDerivatives)
                xVector(0) = (3. / dx0) * (dy0 / dx0 - mFloorBoundaryConditionsValue) - (xVector(1) / 2.);
            else if (mFloorBoundaryConditions == BoundaryConditions_Fixed2ndDerivatives)
                xVector(0) = mFloorBoundaryConditionsValue;
            else if (mFloorBoundaryConditions == BoundaryConditions_Copy)
                xVector(0) = xVector(1);
            else if (mFloorBoundaryConditions == BoundaryConditions_Extrapolate && kSize > 3)
                xVector(0) = xVector(1) - dx0 * (xVector(2) - xVector(1)) / (mKnots[2].x() - mKnots[1].x());

            if (mCeilBoundaryConditions == BoundaryConditions_Fixed1stDerivatives)
                xVector(kSize - 1) = (3. / dx1) * (mCeilBoundaryConditionsValue - dy1 / dx1) -
                        (xVector(kSize - 2) / 2.);
            else if (mCeilBoundaryConditions == BoundaryConditions_Fixed2ndDerivatives)
                xVector(kSize - 1) = mCeilBoundaryConditionsValue;
            else if (mCeilBoundaryConditions == BoundaryConditions_Copy)
                xVector(kSize - 1) = xVector(kSize - 2);
            else if (mCeilBoundaryConditions == BoundaryConditions_Extrapolate && kSize > 3)
                xVector(kSize - 1) = xVector(kSize - 2) + dx1 * (xVector(kSize - 2) - xVector(kSize - 3)) /
                                     (mKnots[kSize - 2].x() - mKnots[kSize - 3].x());
        }

        // ------------------------------------------
        // compute polynomial coefficients
        // ------------------------------------------
        for (int i = 0; i < kSize - 1; i++)
        {
            // distances between knots
            dx1 = mKnots[i + 1].x() - mKnots[i].x();
            dy1 = mKnots[i + 1].y() - mKnots[i].y();
            // compute coefficients
            dx2 = isPeriodic && i == kSize - 2 ? xVector(0) : xVector(i + 1);
            mCoefficients[i].a = (dx2 - xVector(i)) / (6. * dx1);
            mCoefficients[i].b = xVector(i) / 2.;
            mCoefficients[i].c = dy1 / dx1 - dx1 * (2. * xVector(i) + dx2) / 6.;
            mCoefficients[i].d = mKnots[i].y();
        }
    }

    mIsDirty = false;
}

bool CubicSplineInterpolator1D::setKnots(const Interpolator1DKnots &k)
{
    bool b = BaseSplineInterpolator1D::setKnots(k);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::setKnot(int i, const Interpolator1DKnot &k)
{
    bool b = BaseSplineInterpolator1D::setKnot(i, k);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::setKnot(int i, double nx, double ny)
{
    bool b = BaseSplineInterpolator1D::setKnot(i, nx, ny);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::setKnot(double x, const Interpolator1DKnot &k)
{
    bool b = BaseSplineInterpolator1D::setKnot(x, k);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::setKnot(double x, double nx, double ny)
{
    bool b = BaseSplineInterpolator1D::setKnot(x, nx, ny);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::addKnot(const Interpolator1DKnot &k, bool replace, int * index)
{
    bool b = BaseSplineInterpolator1D::addKnot(k, replace, index);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::addKnot(double nx, double ny, bool replace, int * index)
{
    bool b = BaseSplineInterpolator1D::addKnot(nx, ny, replace, index);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::removeKnot(double x)
{
    bool b = BaseSplineInterpolator1D::removeKnot(x);
    if (b) mIsDirty = true;
    return b;
}

bool CubicSplineInterpolator1D::removeKnot(int i)
{
    bool b = BaseSplineInterpolator1D::removeKnot(i);
    if (b) mIsDirty = true;
    return b;
}

CubicSplineInterpolator1D::BoundaryConditions CubicSplineInterpolator1D::floorBoundaryConditions() const
{
    return mFloorBoundaryConditions;
}

CubicSplineInterpolator1D::BoundaryConditions CubicSplineInterpolator1D::ceilBoundaryConditions() const
{
    return mCeilBoundaryConditions;
}

double CubicSplineInterpolator1D::floorBoundaryConditionsValue() const
{
    return mFloorBoundaryConditionsValue;
}

double CubicSplineInterpolator1D::ceilBoundaryConditionsValue() const
{
    return mCeilBoundaryConditionsValue;
}

void CubicSplineInterpolator1D::setBoundaryConditions(BoundaryConditions f, BoundaryConditions c,
                                                     double fv, double cv)
{
    mFloorBoundaryConditions = f;
    mCeilBoundaryConditions = c;
    mFloorBoundaryConditionsValue = fv;
    mCeilBoundaryConditionsValue = cv;
    mIsDirty = true;
}

}}


#include <boost/python/module.hpp>
#include <boost/python/def.hpp>

namespace {

  long
  factorial(int n)
  {
    long fact = 1;
    for(int i=2;i<=n;i++) {
      fact *= i;
    }
    return fact;
  }

  double
  power(double x, int n)
  {
    double pow = x;
    for(int i=1;i<n;i++) {
      pow *= x;
    }
    return pow;
  }

  double
  sin(double x, int n_terms)
  {
    double result = x;
    int sign = -1;
    int pow = 3;
    for(int i=1;i<n_terms;i++) {
      result += power(x,pow)/(sign*factorial(pow));
      sign *= -1;
      pow += 2;
    }
    return result;
  }

  double
  run_c_plus_plus(int n, int n_terms)
  {
    double result = 0;
    while (n--) {
      for(int i=0;i<180;i++) {
        result += sin(i * 3.14159265359/180, n_terms);
      }
    }
    return result;
  }

} // namespace anonymous

BOOST_PYTHON_MODULE(boost_python_hybrid_times_ext)
{
  using namespace boost::python;
  def("run_c_plus_plus", run_c_plus_plus);
}


#include <algorithm>
#include <fstream>
#include <functional>
#include <iostream>
#include <iterator>
#include <numeric>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>

#include <boost/range/counting_range.hpp>

using Location = std::string;
using LocationID = unsigned;
using Distance = unsigned;

struct Route {
	LocationID id1, id2;
	Distance distance;
};

struct RouteEntry {
	Location location_A, location_B;
	Distance distance;
};

auto& operator>>(std::istream& in, RouteEntry& entry) {
	return in >> entry.location_A >> entry.location_B >> entry.distance;
}

template<typename T>
auto factorial(T n) {
	const auto range = boost::counting_range<T>(2, (n + 1));
	return std::accumulate(range.begin(), range.end(), T{1}, std::multiplies{});
}

template<typename T>
auto triangle(T n) {
	return n * (n - 1) / 2;
}

template<typename T>
auto triangle_index(T a, T b) {
	const auto& [min, max] = std::minmax(a, b);
	return min + triangle(max);
}

int main() {

	const auto filename = std::string{"locations.txt"};
	auto file = std::fstream{filename};

	if(file.is_open()) {

		auto locations = std::set<LocationID>{};

		const auto add_location = [&locations] (const auto& location) {

			static auto location_id = LocationID{};
			static auto geo_map = std::unordered_map<Location, LocationID>{};

			const auto pos = geo_map.try_emplace(location, 0);

			auto& current_id = pos.first->second;

			const auto is_new_location = pos.second;

			if(is_new_location) {
				// the ids start from 0, hence the postfix increment
				current_id = location_id++;
				locations.insert(current_id);
			}

			return current_id;
		};

		auto routes = std::vector<Route>{};

		RouteEntry entry;

		while(file >> entry) {
			routes.push_back({
				add_location(entry.location_A),
				add_location(entry.location_B),
				entry.distance
			});
		}

		// ========================================
		// find shortest path through all locations
		// ========================================

		// will be used for various calculations
		const auto size = locations.size();

		// 1) creates a lookup table for distances of all routes
		// 2) doing triangle instead of (size*size) halves the memory footprint,
		// but at the expense of doing additional calculation for each access
		auto chart = std::vector<Distance>(triangle(size));

		// log the distance for any given route between two locations
		for(const auto& route : routes) {
			const auto& index = triangle_index(route.id1, route.id2);
			chart[index] = route.distance;
		}

		auto itinerary = std::vector<LocationID>{locations.begin(), locations.end()};
		auto min_distance = Distance{std::numeric_limits<Distance>::max()};

		// only a subset of all possible permutations of routes is relevant for us,
		// so we create an upper limit to count down from when looping through permutations
		auto limit = (factorial(size) / size) * (size - 1);

		do {
			const auto begin1 = itinerary.begin();
			const auto end1	  = std::prev(itinerary.end());
			const auto begin2 = std::next(begin1);
			const auto acc	  = Distance{};
			const auto op1	  = std::plus{};
			const auto op2	  = [&chart] (auto a, auto b) { return chart[triangle_index(a, b)]; };

			// inner_product resolves to acc = op1(acc, op2(begin1, begin2)) in a loop
			const auto tmp_distance = std::inner_product(begin1, end1, begin2, acc, op1, op2);

			min_distance = std::min(min_distance, tmp_distance);

		} while(((limit--) > 0) && std::next_permutation(itinerary.begin(), itinerary.end()));

		std::cout << min_distance << std::endl;

	} else {
		std::cerr << "Error! Could not open \"" << filename << "\"!" << std::endl;
	}

	return 0;
}


#ifndef PROJECTION_HPP
#define PROJECTION_HPP

/**
 * \file projection.hpp
 * \brief Utilities for conversion from coordinates in an image to
 * geographical coordinates.
 * \author Le Bars, Yoann
 * \version 1.0
 * \date 2013/06/27
 * \date 2013/06/28
 * \date 2014/03/06
 */

#include <boost/concept_check.hpp>
#include <cassert>
#include <vector>
#include <eigen3/Eigen/Dense>

/// \brief Namespace for projection computations.
namespace Projection {
  /// \brief Class for 2D points.
  class Point2D {
    public:
      /**
       * \brief Construct a point with two double.
       * \param _x Abscissa.
       * \param _y Ordinate.
       */
      Point2D (double _x = 0., double _y = 0.): x_ (_x), y_ (_y) {}

      /**
       * \brief Copy constructor.
       * \param p Point2D to be copied.
       */
      Point2D (const Point2D &p): x_ (p.x_), y_ (p.y_) {}

      /// \brief Destructor.
      ~Point2D () {}

      /// \brief Access to abscissa, modification is not possible.
      double x () const {return x_;}

      /// \brief Access to abscissa, modification is possible.
      double &x () {return x_;}

      /// \brief Access to ordinate, modification is not possible.
      double y () const {return y_;}

      /// \brief Access to ordinate, modification is possible.
      double &y () {return y_;}

      /**
       * \brief Copy operator.
       * \param p Point2D to be copied.
       * \return A reference to current point.
       */
      Point2D &operator = (const Point2D &p) {
        x_ = p.x_;
        y_ = p.y_;
        return *this;
      }

    private:
      /// \brief Abscissa.
      double x_;

      /// \brief Ordinate;
      double y_;
  };

  /// \brief Type for projection coefficients.
  typedef Eigen::Matrix<double, 3, 2> Coefficients;

  /**
   * \brief Compute the projection coefficients.
   * \param r1 Vector containing reference points in image coordinates.
   * \param r2 Vector containing reference points in geographical coordinates.
   * \return Vector containing projection coefficients.
   *
   * Information on coefficients can be found at:
   * <http://en.wikipedia.org/wiki/World_file>
   */
  inline Coefficients computeCoefficients (const std::vector<Point2D> &r1,
                                           const std::vector<Point2D> &r2) {
    assert(r1.size() == 3);
    assert(r2.size() == 3);
    /* Matrix to compute coefficients. */
    Eigen::Matrix3d a;
    a << r1[0].x(), r1[0].y(), 1.,
         r1[1].x(), r1[1].y(), 1.,
         r1[2].x(), r1[2].y(), 1.;
    /* Matrix of points in geographical coordinates. */
    Coefficients b;
    b << r2[0].x(), r2[0].y(),
         r2[1].x(), r2[1].y(),
         r2[2].x(), r2[2].y();
//     return a.fullPivLu().solve(b);
    return a.jacobiSvd(Eigen::ComputeThinU | Eigen::ComputeThinV).solve(b);
  }
}

#endif  // #ifndef PROJECTION_HPP


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

// This is a sample program for education purposes only.
// It attempts to show the various basic mathematical
// operations that can be performed on both ciphertexts
// and plaintexts.

#include <iostream>
#include <boost/lexical_cast.hpp>
#include <helib/helib.h>
#include <time.h>

int main(int argc, char* argv[])
{
  /*  Example of BGV scheme  */

  if( argc != 3 && argc != 4 )
  {std::cout << "not enough args: need p and m" << std::endl;
		exit(-1);
  }
  // Plaintext prime modulus
  unsigned long p = boost::lexical_cast<long>(argv[1]);
  // Cyclotomic polynomial - defines phi(m)
  unsigned long m = boost::lexical_cast<long>(argv[2]);
  // Hensel lifting (default = 1)
  unsigned long r = 1;
  // Number of bits of the modulus chain
  unsigned long bits = argc == 3 ? 300 : boost::lexical_cast<long>(argv[3]);
  // Number of columns of Key-Switching matrix (default = 2 or 3)
  unsigned long c = 2;

	clock_t start = clock();
  // Initialize context
  // This object will hold information about the algebra created from the
  // previously set parameters
  helib::Context context(m, p, r);
  std::cout << "Initialising context object..." << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;
  // Modify the context, adding primes to the modulus chain
  // This defines the ciphertext space
  buildModChain(context, bits, c);
	std::cout << "Building modulus chain..." << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;

  // Print the context
  context.zMStar.printout();
  std::cout << std::endl;

  // Print the security level
  std::cout << "Security: " << context.securityLevel() << std::endl;

  //FIXME: really should make this a separate progrm but too lazy for that
  if( argc == 3)
    exit(0);

  // Secret key management
  std::cout << "Creating secret key..." << std::endl;
  // Create a secret key associated with the context
  helib::SecKey secret_key(context);
  // Generate the secret key
  secret_key.GenSecKey();
  // Compute key-switching matrices that we need
  helib::addSome1DMatrices(secret_key);
  std::cout << "Generating key-switching matrices..." << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;

  // Public key management
  // Set the secret key (upcast: SecKey is a subclass of PubKey)
  const helib::PubKey& public_key = secret_key;

  // Get the EncryptedArray of the context
  const helib::EncryptedArray& ea = *(context.ea);

  // Get the number of slot (phi(m))
  long nslots = ea.size();
  std::cout << "Number of slots: " << nslots << std::endl;

  // Create a vector of long with nslots elements
  helib::Ptxt<helib::BGV> ptxt(context);
  // Set it with numbers 0..nslots - 1
  // ptxt = [0] [1] [2] ... [nslots-2] [nslots-1]
  for (int i = 0; i < ptxt.size(); ++i) {
    ptxt[i] = i;
  }

  // Create a ciphertext object
	start = clock();
  helib::Ctxt ctxt(public_key);
  // Encrypt the plaintext using the public_key
  public_key.Encrypt(ctxt, ptxt);
	std::cout << "Done encrypting: " << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;

  // ********** Operations **********
  // Ciphertext and plaintext operations are performed
  // "entry-wise".

  // Square the ciphertext
  // [0] [1] [2] [3] [4] ... [nslots-1]
  // -> [0] [1] [4] [9] [16] ... [(nslots-1)*(nslots-1)]
	start = clock();
	ctxt.multiplyBy(ctxt);
  std::cout << "Power: " << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;

  // Raise the copy to the exponent 2
  // Note: 0 is a special case because 0^n = 0 for any power n
  //ctxt.power(2);

  // Subtract it from itself (result should be 0)
	start = clock();
  ctxt += ctxt;
	std::cout << "Subtract: " << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;

  // Create a plaintext for decryption
  start = clock();
  helib::Ptxt<helib::BGV> plaintext_result(context);
  // Decrypt the modified ciphertext
  secret_key.Decrypt(plaintext_result, ctxt);
	std::cout << "Decryt: " << (double)(clock()-start)/CLOCKS_PER_SEC << std::endl;

  // Print the decrypted plaintext
  std::cout << "Decrypted Result: " << plaintext_result << std::endl;

  return 0;
}


/**
 * $Id$
 *
 * Copyright (C)
 * 2015 - $Date$
 *     Martin Wolf <ndhist@martin-wolf.org>
 *
 * This file is distributed under the BSD 2-Clause Open Source License
 * (See LICENSE file).
 *
 */
#ifndef NDHIST_STATS_EXPECTATION_HPP_INCLUDED
#define NDHIST_STATS_EXPECTATION_HPP_INCLUDED 1

#include <cmath>

#include <boost/python.hpp>

#include <boost/numpy/ndarray.hpp>
#include <boost/numpy/iterators/multi_flat_iterator.hpp>

#include <ndhist/detail/bin_iter_value_type_traits.hpp>
#include <ndhist/ndhist.hpp>

namespace ndhist {
namespace stats {

namespace detail {

/**
 * Calculates the n'th order expectation value along the given axis of the given
 * ndhist object weighted by the sum of weights in each bin.
 * It generates a projection along the given axis and then calculates the n'th
 * order expectation value.
 * In statistics the n'th order expectation is defined as the expectation of
 * x^n, i.e. ``E[x^n]``, where x is the bin center axis value in this case.
 */
template <typename AxisValueType, typename WeightValueType>
double
calc_axis_expectation_impl(
    ndhist const & h
  , intptr_t const n
  , intptr_t const axis
)
{
    // Project the given histogram to the given axis (if nd > 1).
    ndhist const proj = (h.get_nd() == 1 ? h : h.project(bp::object(axis)));

    // Iterate over the bins (which are along the given axis) and exclude
    // possible under- and overflow bins.
    Axis const & theaxis = *proj.get_axes()[0];
    intptr_t nbins = theaxis.get_n_bins();
    if(theaxis.has_underflow_bin()) --nbins;
    if(theaxis.has_overflow_bin()) --nbins;
    bn::ndarray proj_bincenters_arr = theaxis.get_bincenters_ndarray();
    bn::ndarray proj_bc_arr = proj.bc_.construct_ndarray(proj.bc_.get_dtype(), 0, /*owner=*/NULL, /*set_owndata_flag=*/false);
    typedef bn::iterators::multi_flat_iterator<2>::impl<
                bn::iterators::single_value<AxisValueType>
              , ::ndhist::detail::bin_iter_value_type_traits<WeightValueType>
            >
            multi_iter_t;
    multi_iter_t iter(
        proj_bincenters_arr
      , proj_bc_arr
      , bn::detail::iter_operand::flags::READONLY::value
      , bn::detail::iter_operand::flags::READONLY::value
    );

    // Skip the underflow bin.
    if(theaxis.has_underflow_bin()) ++iter;

    double expectation = 0;
    double sow_sum = 0;
    while(nbins > 0)
    {
        typename multi_iter_t::multi_references_type multi_value = *iter;
        typename multi_iter_t::value_ref_type_0 axis_bincenter_value = multi_value.value_0;
        typename multi_iter_t::value_ref_type_1 bin                  = multi_value.value_1;

        sow_sum += *bin.sow_;
        expectation += *bin.sow_ * (n == 1 ? axis_bincenter_value
                                 : (n == 2 ? axis_bincenter_value*axis_bincenter_value
                                 : (n == 3 ? axis_bincenter_value*axis_bincenter_value*axis_bincenter_value
                                 : std::pow(axis_bincenter_value, n))));

        ++iter;
        --nbins;
    }
    expectation /= sow_sum;
    return expectation;
}

}// namespace detail

namespace py {

/**
 * @brief Calculates the n'th order expectation value along the given axis of
 *     the given ndhist object weighted by the sum of weights in each bin.
 *     It generates a projection along the given axis and then calculates the
 *     n'th order expectation value.
 *     In statistics the n'th order expectation value is defined as the
 *     expectation of x^n, i.e. ``E[x^n]``, where x is the bin center axis
 *     value in this case.
 *     If None is given as axis, the expectation value for all axes of the
 *     ndhist object will be calculated and returned as a tuple. But if the
 *     dimensionality of the ndhist object is 1, a scalar value is returned.
 *
 * @note This function is only defined for ndhist objects with POD axis values
 *     AND POD weight values.
 */
boost::python::object
expectation(
    ndhist const & h
  , intptr_t const n = 1
  , boost::python::object const & axis = boost::python::object()
);

}// namespace py
}// namespace stats
}// namespace ndhist

#endif // !NDHIST_STATS_EXPECTATION_HPP_INCLUDED


//
// Created by Ragesh on 5/17/18.
//

#ifndef PROJECT_RAY_TRACE_ITERATOR_HPP
#define PROJECT_RAY_TRACE_ITERATOR_HPP

/**
 * This is the header file which defines the prototype of class and functions for performing
 * ray tracing operation on laser beam rays. The main class is a iterator which is derived from
 * iterator_facade class which a part of the iterator module in the boost library.
 */

// Boost libraries
#include <boost/iterator/iterator_facade.hpp>

// C libraries
#include <cassert>
#include <cmath>
#include <cstdlib>
#include <cstdio>

// C++ libraries
#include <limits>
#include <stdexcept>
#include <utility>

namespace NS_occupancy_grid {

template<typename T>
inline int signum(T val) {
  /// A template based implementation signum function.
  /// T is the template type
  /**
   * \param val : the input to the signum function
   *
   */
  return ((T(0) < val) - (val < T(0)));
}

template<typename real_t, typename int_t>
class ray_trace_iterator : public boost::iterator_facade<ray_trace_iterator<real_t, int_t>, std::pair<int_t, int_t>,
                           boost::forward_traversal_tag, std::pair<int_t, int_t>>
{

private:

typedef typename boost::iterator_facade<ray_trace_iterator<real_t, int_t>, std::pair<int_t, int_t>,
boost::forward_traversal_tag, std::pair<int_t, int_t>> super_t;
// Input arguments
real_t px_, py_, dx_, dy_, origin_x, origin_y, cell_size_x, cell_size_y;

// Intermediate variables for faster computation

/// integral steps (direction) (-1, 0, 1) in both x and y
int_t dir_x, dir_y;
/// distance to the nearest grid line
real_t ex_, ey_;
/// Maximum time to collision (from one grid line to next)
real_t Tx_, Ty_;

// State of iterator
/// Grid index
int_t i_, j_;
/// time to collision to next grid line
real_t tx_, ty_;

public:

// constructor
ray_trace_iterator(real_t px, real_t py, real_t dx, real_t dy, real_t origin_x_, real_t origin_y_,
    real_t cell_size_x_, real_t cell_size_y_) :
px_{px}, py_{py},
dx_{dx}, dy_{dy},
origin_x{origin_x_}, origin_y{origin_y_},
cell_size_x{cell_size_x_}, cell_size_y{cell_size_y_} {
/**
 * The constructor for the ray trace iterator.
 *
 * \param px : the start point x coordinate
 * \param py : the start point y coordinate
 * \param dx : the increment along x axis
 * \param dy : the increment along y axis
 * \param origin_x_ : the x coordinate of the map bottom left corner
 * \param origin_y_ : the y coordinate of the map bottom left corner
 * \param cell_size_x_ : the size of the cell along x
 * \param cell_size_y_ : the size of the cell along y
 *
 */

// shift the coordinates to zero the origin
px = px - origin_x_;
py = py - origin_y_;

// grid cell containing (px,py)
i_ = static_cast<int_t>(std::floor(px / cell_size_x_));
j_ = static_cast<int_t>(std::floor(py / cell_size_y_));

dir_x = signum(dx);
dir_y = signum(dy);

// whether the grid line we are going to hit is floor() or ceil()
// depends on the direction in which the ray is moving
// using the fact that ceil() = floor() + 1
int_t floor_or_ceil_x = (dir_x > 0) ? 1 : 0;
int_t floor_or_ceil_y = (dir_y > 0) ? 1 : 0;

// uncomment lines below for debugging
//printf("\n Cell: (%i, %i), dx dy: (%f,%f) \n", i_, j_, dx, dy);

// distance to the nearest grid line
ex_ = std::fabs((i_ + floor_or_ceil_x) * cell_size_x_ - px);
ey_ = std::fabs((j_ + floor_or_ceil_y) * cell_size_y_ - py);

// (max) time to collision from one grid line to another
Tx_ = (dx == 0) ? std::numeric_limits<real_t>::infinity() : cell_size_x_ / std::fabs(dx);
Ty_ = (dy == 0) ? std::numeric_limits<real_t>::infinity() : cell_size_y_ / std::fabs(dy);

// time to collision from this position
tx_ = (dx == 0) ? std::numeric_limits<real_t>::infinity() : ex_ / fabs(dx);
ty_ = (dy == 0) ? std::numeric_limits<real_t>::infinity() : ey_ / fabs(dy);

if (!((tx_ >= 0) && (ty_ >= 0))) {
printf("t:(%f, %f), direction:(%f, %f), position:(%f, %f), cell:(%d, %d), cell size:(%f, %f)\n",
tx_, ty_, dx, dy, px, py, i_, j_, cell_size_x, cell_size_y);
throw std::logic_error("tx < 0 or ty < 0");
}

// time is always positive
assert(tx_ >= 0);
assert(ty_ >= 0);

}

typename super_t::reference dereference() const {
  return std::make_pair(i_, j_);
}

bool equal(ray_trace_iterator it) const {
  /// function to check if the objects are equal
  return ((it.i_ == i_) && (it.j_ == j_) &&
      (it.tx_ == tx_) && (it.ty_ == ty_) &&
      (it.Tx_ == Tx_) && (it.Ty_ == Ty_) &&
      (it.dir_x == dir_x) && (it.dir_y == dir_y));
}

/// overload the == operator
//      friend bool operator== (const ray_trace_iterator<real_t, int_t>& it1,
//                              const ray_trace_iterator<real_t, int_t>& it2);
friend bool operator==(const ray_trace_iterator<real_t, int_t> &it1, const ray_trace_iterator<real_t, int_t> &it2) {
  return ((it1.i_ == it2.i_) && (it1.j_ == it2.j_) &&
      (it1.tx_ == it2.tx_) && (it1.ty_ == it2.ty_) &&
      (it1.Tx_ == it2.Tx_) && (it1.Ty_ == it2.Ty_) &&
      (it1.dir_x == it2.dir_x) && (it1.dir_y == it2.dir_y));
}

void increment();

std::pair<real_t, real_t> real_position() const;

};

template<typename real_t, typename int_t>
void ray_trace_iterator<real_t, int_t>::increment() {
  if (tx_ < ty_) {
    i_ += dir_x;
    ty_ = ty_ - tx_;
    tx_ = Tx_;
  } else {
    j_ += dir_y;
    tx_ = tx_ - ty_;
    ty_ = Ty_;
  }
}

template<typename real_t, typename int_t>
std::pair<real_t, real_t> ray_trace_iterator<real_t, int_t>::real_position() const {

  // whether the grid line we are going to hit is floor() or ceil()
  // depends on the direction ray is moving
  int_t floor_or_ceil_x = (dir_x > 0) ? 1 : 0;
  int_t floor_or_ceil_y = (dir_y > 0) ? 1 : 0;

  real_t ex = (dx_ == 0) ? ex_ // error is same as starting point
                         : tx_ * fabs(dx_);
  real_t ey = (dy_ == 0) ? ey_
                         : ty_ * fabs(dy_);

  real_t px = (i_ + floor_or_ceil_x) * cell_size_x - ex * dir_x;
  real_t py = (j_ + floor_or_ceil_y) * cell_size_y - ey * dir_y;

  // shift coordinates
  px = px + origin_x;
  py = py + origin_y;

  return std::make_pair(px, py);
}

}

#endif //PROJECT_RAY_TRACE_ITERATOR_HPP


//=======================================================================
// Copyright 2007 Aaron Windsor
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//=======================================================================
#include <iostream>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/property_map.hpp>
#include <boost/ref.hpp>
#include <vector>

#include <boost/graph/planar_canonical_ordering.hpp>
#include <boost/graph/boyer_myrvold_planar_test.hpp>

using namespace boost;

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

  typedef adjacency_list
    < vecS,
      vecS,
      undirectedS,
      property<vertex_index_t, int>,
      property<edge_index_t, int>
    >
    graph;

  // Create a maximal planar graph on 6 vertices
  graph g(6);

  add_edge(0,1,g);
  add_edge(1,2,g);
  add_edge(2,3,g);
  add_edge(3,4,g);
  add_edge(4,5,g);
  add_edge(5,0,g);

  add_edge(0,2,g);
  add_edge(0,3,g);
  add_edge(0,4,g);

  add_edge(1,3,g);
  add_edge(1,4,g);
  add_edge(1,5,g);

  // Initialize the interior edge index
  property_map<graph, edge_index_t>::type e_index = get(edge_index, g);
  graph_traits<graph>::edges_size_type edge_count = 0;
  graph_traits<graph>::edge_iterator ei, ei_end;
  for(tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
    put(e_index, *ei, edge_count++);

  // Test for planarity - we know it is planar, we just want to
  // compute the planar embedding as a side-effect
  typedef std::vector< graph_traits<graph>::edge_descriptor > vec_t;
  std::vector<vec_t> embedding(num_vertices(g));
  if (boyer_myrvold_planarity_test(boyer_myrvold_params::graph = g,
                                   boyer_myrvold_params::embedding =
                                       &embedding[0]
                                   )
      )
    std::cout << "Input graph is planar" << std::endl;
  else
    std::cout << "Input graph is not planar" << std::endl;

  typedef std::vector<graph_traits<graph>::vertex_descriptor>
    ordering_storage_t;

  ordering_storage_t ordering;
  planar_canonical_ordering(g, &embedding[0], std::back_inserter(ordering));

  ordering_storage_t::iterator oi, oi_end;
  oi_end = ordering.end();
  std::cout << "The planar canonical ordering is: ";
  for(oi = ordering.begin(); oi != oi_end; ++oi)
    std::cout << *oi << " ";
  std::cout << std::endl;

  return 0;
}


// Copyright (C) 2006-2009 Dmitry Bufistov and Andrey Parfenov

// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <cassert>
#include <ctime>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/howard_cycle_ratio.hpp>

/**
 * @author Dmitry Bufistov
 * @author Andrey Parfenov
 */

using namespace boost;
typedef adjacency_list<
    listS, listS, directedS,
    property<vertex_index_t, int>,
    property<
        edge_weight_t, double, property<edge_weight2_t, double>
    >
> grap_real_t;

template <typename TG>
void gen_rand_graph(TG &g, size_t nV, size_t nE)
{
    g.clear();
    mt19937 rng;
    rng.seed(uint32_t(time(0)));
    boost::generate_random_graph(g, nV, nE, rng, true, true);
    boost::uniform_real<> ur(-1,10);
    boost::variate_generator<boost::mt19937&, boost::uniform_real<> >   ew1rg(rng, ur);
    randomize_property<edge_weight_t>(g, ew1rg);
    boost::uniform_int<size_t> uint(1,5);
    boost::variate_generator<boost::mt19937&, boost::uniform_int<size_t> >      ew2rg(rng, uint);
    randomize_property<edge_weight2_t>(g, ew2rg);
}

int main(int argc, char* argv[])
{
    using std::cout;
    using std::endl;
    const double epsilon = 0.0000001;
    double min_cr, max_cr; ///Minimum and maximum cycle ratio
    typedef std::vector<graph_traits<grap_real_t>::edge_descriptor> ccReal_t;
    ccReal_t cc; ///critical cycle

    grap_real_t tgr;
    property_map<grap_real_t, vertex_index_t>::type vim = get(vertex_index, tgr);
    property_map<grap_real_t, edge_weight_t>::type ew1 = get(edge_weight, tgr);
    property_map<grap_real_t, edge_weight2_t>::type ew2 = get(edge_weight2, tgr);

    gen_rand_graph(tgr, 1000, 30000);
    cout << "Vertices number: " << num_vertices(tgr) << endl;
    cout << "Edges number: " << num_edges(tgr) << endl;
    int i = 0;
    graph_traits<grap_real_t>::vertex_iterator vi, vi_end;
    for (boost::tie(vi, vi_end) = vertices(tgr); vi != vi_end; vi++) {
        vim[*vi] = i++; ///Initialize vertex index property
    }
    max_cr = maximum_cycle_ratio(tgr, vim, ew1, ew2);
    cout << "Maximum cycle ratio is " << max_cr << endl;
    min_cr = minimum_cycle_ratio(tgr, vim, ew1, ew2, &cc);
    cout << "Minimum cycle ratio is " << min_cr << endl;
    std::pair<double, double> cr(.0,.0);
    cout << "Critical cycle:\n";
    for (ccReal_t::iterator itr = cc.begin(); itr != cc.end(); ++itr)
    {
        cr.first += ew1[*itr];
        cr.second += ew2[*itr];
        std::cout << "(" << vim[source(*itr, tgr)] << "," <<
            vim[target(*itr, tgr)] << ") ";
    }
    cout << endl;
    assert(std::abs(cr.first / cr.second - min_cr) < epsilon);
    return EXIT_SUCCESS;
}


/*

Copyright (c) 2013  Ghassen Hamrouni

Abstract:

This module provides a weighted undirected graph G (possibly with loops)

Author:

Ghassen Hamrouni <ghamrouni.iptech@gmail.com> 23-07-2013

Revision History:

*/

#ifndef R_1_PUBLIC_GRAPH_H_
#define R_1_PUBLIC_GRAPH_H_

#include <Eigen/Dense>
#include "MinCutClusterAnalyzer.hpp"
#include "ClusterCollection.hpp"

#include <random>
#include <fstream>
#include <string>
#include <set>
#include <unordered_set>

namespace R1 {

	//
	// Graph: A weighted undirected graph G (possibly with loops)
	//
	class Graph {

	public:

		virtual ~Graph() {}

		Graph() {}

		Graph(int NVertex) : n_vertex(NVertex), n_edges(0) {
			adjacency_matrix = Eigen::MatrixXf(NVertex, NVertex);
			adjacency_matrix.setZero();
		}

		void setSize(int NVertex) {
			n_vertex = (NVertex);
			n_edges  = (0);
			adjacency_matrix = Eigen::MatrixXf(NVertex, NVertex);
			adjacency_matrix.setZero();
		}

		void connect(int u, int v) {
			if (u >= n_vertex) {
				return;
			}

			if (v >= n_vertex) {
				return;
			}

			adjacency_matrix(u, v) = 1.0;
			adjacency_matrix(v, u) = 1.0;

			n_edges = n_edges + 1;
		}

		void connect(int u, int v, float weight) {
			if (u >= n_vertex) {
				return;
			}

			if (v >= n_vertex) {
				return;
			}

			adjacency_matrix(u, v) = weight;
			adjacency_matrix(v, u) = weight;

			n_edges = n_edges + 1;
		}

		float degree(int v) {
			float n = 0.0f;

			if (v >= n_vertex) {
				return 0;
			}

			for (int u = 0; u < n_vertex; u++) {
				n += adjacency_matrix(u, v);
			}

			return n;
		}

		bool is_connected(int u, int v) {
			if (u >= n_vertex) {
				return false;
			}

			if (v >= n_vertex) {
				return false;
			}

			return (adjacency_matrix(u, v) >= 1.0);
		}

		// A graph is said to be nontrivial if it contains at least
		// one edge.
		bool is_trivial() {
			return n_edges == 0;
		}

		void generateRandomSpanningTree()
		{
			std::random_device rd;
			std::mt19937 gen(rd());
			std::uniform_int_distribution<> vdist(0, n_vertex - 1);
			int current_vertex = vdist(gen);

			std::vector<int> P;
			std::vector<int> V;

			for (int i = 0; i < n_vertex; i++)
			{
				if (i != current_vertex)
					P.push_back(i);
			}

			V.push_back(current_vertex);

			while (!P.empty())
			{
				std::uniform_int_distribution<> distP(0, P.size() - 1);

				int j_index = distP(gen);
				int j = P[j_index];

				std::swap(P[j_index], P.back());
				P.pop_back();

				std::uniform_int_distribution<> distV(0, V.size() - 1);

				int u_index = distV(gen);
				int u = V[u_index];

				connect(u, j);

				V.push_back(j);
			}
		}

		void generateUniformRandomSpanningTree()
		{
			std::random_device rd;
			std::mt19937 gen(rd());
			std::uniform_int_distribution<> vdist(0, n_vertex - 1);

			int current_vertex = vdist(gen);
			std::vector<int> V;

			int n_edge = n_vertex - 1;

			for (int i = 0; i < n_vertex; i++)
			{
				V.push_back(0);
			}

			V[current_vertex] = 1;

			while (n_edge)
			{
				int j = vdist(gen);

				if (V[j] == 0)
				{
					V[j] = 1;
					connect(current_vertex, j);
					n_edge--;
				}

				current_vertex = j;
			}
		}

		void generateRandomGraph(int n_random_edges)
		{
			std::random_device rd;
			std::mt19937 gen(rd());
			std::uniform_int_distribution<> vdist(0, n_vertex - 1);

			while (n_random_edges > 0)
			{
				int i = vdist(gen);
				int j = vdist(gen);

				connect(i, j);

				n_random_edges--;
			}
		}

		ClusterCollection cluster(double alpha = 0.5)
		{
			return MinCutClusterAnalyzer::cluster(adjacency_matrix, alpha);
		}

		std::vector<ClusterCollection> hierarchicalCluster(double alpha = 0.5, double decay = 0.5)
		{
			std::vector<ClusterCollection> clusters;
			int prev_cluster_nb = 0;

			for (int i = 0; ; i++)
			{
				ClusterCollection cl = MinCutClusterAnalyzer::cluster(adjacency_matrix, alpha);

				if (cl.size != prev_cluster_nb)
				{
					prev_cluster_nb = cl.size;
					clusters.push_back(cl);
				}

				if (cl.size == 1) // One cluster
					break;

				if (alpha <= 0.0)
					break;

				alpha *= decay;
			}

			return clusters;
		}

	protected:
		Eigen::MatrixXf adjacency_matrix;

		int		n_vertex;
		int		n_edges;

	};
}
#endif