/*********************************************************************************
*  This file is part of reference implementation of SIGGRAPH Asia 2021 Paper     *
*  `Efficient and Robust Discrete Conformal Equivalence with Boundary`           *
*  v1.0                                                                          *
*                                                                                *
*  The MIT License                                                               *
*                                                                                *
*  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 NON INFRINGEMENT. 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.                                                              *
*                                                                                *
*  Author(s):                                                                    *
*  Marcel Campen, Institute for Computer Science, Osnabrück University, Germany. *
*  Ryan Capouellez, Hanxiao Shen, Leyi Zhu, Daniele Panozzo, Denis Zorin,        *
*  Courant Institute of Mathematical Sciences, New York University, USA          *
*                                          *                                     *
*********************************************************************************/

#ifndef CONFORMAL_IDEAL_DELAUNAY_MAPPING_HH
#define CONFORMAL_IDEAL_DELAUNAY_MAPPING_HH

#include <set>
#include <iomanip>
#include <fstream>
#include <ctime>
#include <Eigen/Sparse>
#include "Angle.hh"
#include "Claussen.hh"
#include "OverlayMesh.hh"

#include <spdlog/spdlog.h>
#include <spdlog/fmt/ostr.h>

using namespace OverlayProblem;

struct DelaunayStats {
// Delaunay
  int n_flips = 0, n_flips_s = 0, n_flips_t = 0, n_flips_q = 0, n_flips_12 = 0, n_nde = -1;
   bool flip_count = false;          // when true: write out stats for different kinds of flips
   std::vector<int> flip_seq;
};

template <typename Scalar>
struct SolveStats {
  int n_solves = 0, n_g = 0, n_checks = 0;
  Scalar cetm_energy = 0;
};

struct StatsParameters{
  bool flip_count = false;      // when true: collect stats on different types of edge flips
  std::string name = "";        // name of the model that's been tested - for logging purpose
  std::string output_dir = "";  // directory name for genearting all stats
  bool error_log = false;       // when true: write out per-newton iterations stats
  bool print_summary = false;   // when true: add final stats of optimization to summary file
  int log_level = 2;            // controlling detail of console logging
};

struct LineSearchParameters {
  double c1 = 1e-4;                  // c1 for armijo condition
  double c2 = 0.9;                   // c2 for curvature condition
  bool energy_samples = false;       // This boolean is only used for generating figure 4 in paper
  bool energy_cond = false;          // when true: use energy decrease as line search stop criterion
  bool do_reduction = false;         // when true: reduce step, if the components of descent direction vary too much
  double descent_dir_max_variation = 1e-10; // threshold for descent direction component max difference to decrease step
  bool do_grad_norm_decrease = true; // when true: require gradient norm to decrease at each iteration
  double bound_norm_thres = 1e-10;   // threshold to drop gradient decrease requirement when step lambda is below this
  double lambda0 = 1.0;              // starting lambda value for the line search, normally 1
  bool reset_lambda = true;          // when true: start with lambda = lambda0 for each newton iteration; if false, start with lambda from the previous
};

struct AlgorithmParameters {
  int MPFR_PREC = 100;           // precision if done in multiprecision
  bool initial_ptolemy = false;  // when true: use ptolemey flips for the first MakeDelaunay  Do we really need this?
  // termination
  double error_eps = 0;          // max angle error tolerance, terminate if below
  double min_lambda = 1e-16;     // terminate if lambda drops below this threshold
  double newton_decr_thres = 0;  // terminate if the newton decrement is above this threshold (it is negative)
  int max_itr = 500;             // upper bound for newton iterations
  bool bypass_overlay = false;       // avoid overlay computation
 };

// Scalar: a floating point type, either double or MPFR
template <typename Scalar>
class ConformalIdealDelaunay
{
public:
  typedef Eigen::Matrix<Scalar, Eigen::Dynamic, 1> VectorX;

  /**
   * Interior angle and its cotangent computed for the whole mesh given decorated per-vertex u values,
   * the angles are computed via rescaled conformal edge lengths.
   *
   * @param m Mesh data structure
   * @param u vector of Scalar size equal to number of vertices of mesh, the per-vertex logarithmic scale factors
   * @param alpha vector of Scalar size equal to number of halfedges of mesh,
   *              ith entry corresponds to the interior angle opposite to
   *              ith halfedge.
   * @param cot_alpha vector of Scalar size equal to number of halfedges of mesh,
   *              e.g. ith entry corresponds to the cotangent of interior angle opposite to
   *              ith halfedge.
   * @return void
   */
  static void ComputeAngles(const Mesh<Scalar>& m, const VectorX& u, VectorX& alpha, VectorX& cot_alpha)
  {

    alpha.setZero(m.n_halfedges());
    cot_alpha.setZero(m.n_halfedges());

    const Scalar cot_infty = Scalar(1e10);

    Scalar pi;
#ifdef WITH_MPFR
    if (std::is_same<Scalar, mpfr::mpreal>::value)
      pi = Scalar(mpfr::const_pi());
    else
      pi = Scalar(M_PI);
#else
      pi = Scalar(M_PI);
#endif

#pragma omp parallel for
    for (int f = 0; f < m.n_faces(); f++)
    {
      int hi = m.h[f];
      int hj = m.n[hi];
      int hk = m.n[hj];
      int i = m.v_rep[m.to[hj]];
      int j = m.v_rep[m.to[hk]];
      int k = m.v_rep[m.to[hi]];
      Scalar ui = u[i];
      Scalar uj = u[j];
      Scalar uk = u[k];
      Scalar uijk_avg = (ui + uj + uk)/3.0; // Scale lengths for numerical stability
      Scalar li = ell(m.l[m.e(hi)], uj, uk, uijk_avg);
      Scalar lj = ell(m.l[m.e(hj)], uk, ui, uijk_avg);
      Scalar lk = ell(m.l[m.e(hk)], ui, uj, uijk_avg);
      // (following "A Cotangent Laplacian for Images as Surfaces")
      Scalar s = (li + lj + lk) / 2.0;
      Scalar Aijk4 = 4.0 * sqrt(std::max<Scalar>(s * (s - li) * (s - lj) * (s - lk), 0.0));
      Scalar Ijk = (-li * li + lj * lj + lk * lk);
      Scalar iJk = (li * li - lj * lj + lk * lk);
      Scalar ijK = (li * li + lj * lj - lk * lk);
      cot_alpha[hi] = Aijk4 == 0.0 ? copysign(cot_infty, Ijk) : (Ijk / Aijk4);
      cot_alpha[hj] = Aijk4 == 0.0 ? copysign(cot_infty, iJk) : (iJk / Aijk4);
      cot_alpha[hk] = Aijk4 == 0.0 ? copysign(cot_infty, ijK) : (ijK / Aijk4);

#define USE_ACOS
#ifdef USE_ACOS
      alpha[hi] = acos(std::min<Scalar>(std::max<Scalar>(Ijk / (2.0 * lj * lk), -1.0), 1.0));
      alpha[hj] = acos(std::min<Scalar>(std::max<Scalar>(iJk / (2.0 * lk * li), -1.0), 1.0));
      alpha[hk] = acos(std::min<Scalar>(std::max<Scalar>(ijK / (2.0 * li * lj), -1.0), 1.0));
#else
      // atan2 is prefered for stability
      alpha[hi] = 0.0, alpha[hj] = 0.0, alpha[hk] = 0.0;
      // li: l12, lj: l23, lk: l31
      Scalar l12 = li, l23 = lj, l31 = lk;
      const Scalar t31 = +l12+l23-l31,
                   t23 = +l12-l23+l31,
                   t12 = -l12+l23+l31;
      // valid triangle
      if( t31 > 0 && t23 > 0 && t12 > 0 ){
        const Scalar l123 = l12+l23+l31;
        const Scalar denom = sqrt(t12*t23*t31*l123);
        alpha[hj] = 2*atan2(t12*t31,denom); // a1 l23
        alpha[hk] = 2*atan2(t23*t12,denom); // a2 l31
        alpha[hi] = 2*atan2(t31*t23,denom); // a3 l12
      }else if( t31 <= 0 ) alpha[hk] = pi;
       else if( t23 <= 0 ) alpha[hj] = pi;
       else if( t12 <= 0 ) alpha[hi] = pi;
       else alpha[hj] = pi;
#endif
    }
  }

  /**
   * Milnor’s Lobachevsky function, see appendix A in http://www.multires.caltech.edu/pubs/ConfEquiv.pdf
   */
  static Scalar Lob(const Scalar angle)
  {
    return 0 <= angle && angle <= M_PI ? claussen(double(2 * angle)) / 2 : 0;
  }

  /**
   * Compute angle sums at each vertex of given mesh according to per-corner angles
   *
   * @param m Mesh data structure
   * @param alpha vector of Scalar size equal to number of halfedges of mesh,
   *              ith entry corresponds to the interior angle opposite to
   *              ith halfedge, e.g. the one computed by function `ComputeAngles`.
   * @return VectorX vector of Scalar size equal to number of vertices of mesh,
   *         each entry is the total angle sum at each vertex.
   */
  static VectorX Theta(const Mesh<Scalar>& m, const VectorX& alpha)
  {
    VectorX t(m.n_ind_vertices());
    t.setZero();
    for (int h = 0; h < m.n_halfedges(); h++)
    {
      t[m.v_rep[m.to[m.n[h]]]] += alpha[h];
    }
    return t;
  }

  /**
   * Compute conformal-equivalence-energy (see https://cims.nyu.edu/gcl/papers/2021-Conformal.pdf section 4)
   * of a given mesh with per-vertex logarithmic scale factors
   *
   * @param m Mesh data structure
   * @param angles vector of Scalar with size equal to number of halfedges, produced by ComputeAngles function
   * @param u vector of Scalar size equal to number of vertices of mesh, the per-vertex logarithmic scale factors
   * @return Scalar Energy of the mesh with given conformal metric
   */
  static Scalar ConformalEquivalenceEnergy(Mesh<Scalar> &m, const VectorX& angles, const VectorX &u)
  {

    auto func_f = [](
      const Scalar l12, const Scalar l23, const Scalar l31,
      const Scalar u1,  const Scalar u2,  const Scalar u3,
      const Scalar a1, const Scalar a2, const Scalar a3
    ){
      // h1->hi, h2->hj, h3->hk
      Scalar s12 = u1 + u2 - 2 * u3;
      Scalar s23 = u2 + u3 - 2 * u1;
      Scalar s31 = u3 + u1 - 2 * u2;
      Scalar lt12 = l12 * exp(1.0 / 6.0 * s12);
      Scalar lt23 = l23 * exp(1.0 / 6.0 * s23);
      Scalar lt31 = l31 * exp(1.0 / 6.0 * s31);
      Scalar lambda12 = 2 * log(l12);
      Scalar lambda23 = 2 * log(l23);
      Scalar lambda31 = 2 * log(l31);
      Scalar lambdat12 = lambda12 + u1 + u2;
      Scalar lambdat23 = lambda23 + u2 + u3;
      Scalar lambdat31 = lambda31 + u3 + u1;
      Scalar T1 = a1 * lambdat23 + a2 * lambdat31 + a3 * lambdat12;
      Scalar T2 = Lob(a1) + Lob(a2) + Lob(a3);
      return 0.5 * T1 + T2;
    };

    Scalar E = 0;

    // first part of the energy on faces
    Scalar total_f = 0.0;
    for (int _f = 0; _f < m.n_faces(); _f++)
    {
      int h1 = m.h[_f], h2 = m.n[h1], h3 = m.n[h2];
      int v1 = m.v_rep[m.to[h3]], v2 = m.v_rep[m.to[h1]], v3 = m.v_rep[m.to[h2]];
      Scalar l12 = m.l[m.e(h1)], l23 = m.l[m.e(h2)], l31 = m.l[m.e(h3)];
      Scalar u2 = u(m.v_rep[m.to[h1]]), u3 = u(m.v_rep[m.to[h2]]), u1 = u(m.v_rep[m.to[h3]]);
      Scalar val_f = func_f(l12, l23, l31, u1, u2, u3, angles[h2], angles[h3], angles[h1]);
      Scalar td_lambda_h1 = 2 * log(m.l[m.e(h1)]) + u[m.v_rep[m.to[h1]]] + u[m.v_rep[m.to[m.opp[h1]]]];
      Scalar td_lambda_h2 = 2 * log(m.l[m.e(h2)]) + u[m.v_rep[m.to[h2]]] + u[m.v_rep[m.to[m.opp[h2]]]];
      Scalar td_lambda_h3 = 2 * log(m.l[m.e(h3)]) + u[m.v_rep[m.to[h3]]] + u[m.v_rep[m.to[m.opp[h3]]]];
      auto e_tri = val_f - (M_PI / 4) * (td_lambda_h1 + td_lambda_h2 + td_lambda_h3);
      E += e_tri;
      total_f += val_f;
    }

    // second part of the energy on vertices
    Scalar Ex = 0;
    for (int _v = 0; _v < m.n_ind_vertices(); _v++)
      Ex += m.Th_hat[_v] * u(_v);

    E = E + 0.5 * Ex;

    return E;
  }

  /**
   * Compute the gradient of conformal-equivalence-energy, which is equal to the per-vertex angle defects
   *
   * @param m Mesh data structure
   * @param angles vector of Scalar with size equal to number of halfedges, produced by ComputeAngles function
   * @param g vector of size equal to number of vertices, the gradient.
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @return void
   */
  static void Gradient(const Mesh<Scalar>& m, const VectorX& angles, VectorX& g, SolveStats<Scalar>& solve_stats)
  {
    solve_stats.n_g++;
    g.setZero(m.n_ind_vertices());
    auto angle_sums = Theta(m, angles);
    for(int i = 0; i < g.rows(); i++)
      g[i] = m.Th_hat[i] - angle_sums(i);
  }

  /**
   * Compute the Hessian of conformal-equivalence-energy, which is the cotangent laplacian.
   *
   * @param m Mesh data structure
   * @param cot_alpha vector of Scalar with size equal to number of halfedges, produced by ComputeAngles function
   * @param H (output), Sparse Matrix with size #v*#v.
   * @return void
   */
  static void Hessian(const Mesh<Scalar>& m, const VectorX& cot_alpha, Eigen::SparseMatrix<Scalar>& H)
  {
    H.resize(m.n_ind_vertices(), m.n_ind_vertices());
    typedef Eigen::Triplet<Scalar> Trip;
    std::vector<Trip> trips;
    trips.clear();
    trips.resize(m.n_halfedges() * 2);
#pragma omp parallel for
    for (int h = 0; h < m.n_halfedges(); h++)
    {
      int v0 = m.v_rep[m.v0(h)];
      int v1 = m.v_rep[m.v1(h)];
      Scalar w = (cot_alpha[h] + cot_alpha[m.opp[h]]) / 2;
      trips[h * 2] = Trip(v0, v1, -w);
      trips[h * 2 + 1] = Trip(v0, v0, w);
    }

    H.setFromTriplets(trips.begin(), trips.end());
  }

  /**
   * Given original edge length and two scale factors defined on two endpoints, compute the rescaled edge lengths.
   *
   * @param l, Scalar, original edge length
   * @param u0, Scalar, first scale factor
   * @param u1, Scalar, second scale factor
   * @param offset, Scalar,  a common factor to be subtracted from the total scale, added for numerical stability.
   * @return Scalar rescaled edge length
   */
  static Scalar ell(Scalar l, Scalar u0, Scalar u1, Scalar offset = 0)
  {
    return l * exp((u0 + u1) / 2 - offset);
  }

  /**
   * Predicate, checking whether the two neighboring triangles of given halfedge in the mesh
   * with given scale factor satisfying delaunay condition after rescaling.
   *
   * @param m, mesh data structure
   * @param u, #v vector, per-vertex scale factors
   * @param e, int, halfedge id
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @return bool, true indicates delaunay condition is violated.
   */
  static bool NonDelaunay(Mesh<Scalar>& m, const VectorX& u, int e, SolveStats<Scalar>& solve_stats)
  {
    if (m.type[m.h0(e)] == 4)
      return false; //virtual diagonal of symmetric trapezoid
    solve_stats.n_checks++;
    int hij = m.h0(e);
    int hjk = m.n[hij];
    int hki = m.n[hjk];
    int hji = m.h1(e);
    int him = m.n[hji];
    int hmj = m.n[him];
    int i = m.v_rep[m.to[hji]];
    int j = m.v_rep[m.to[hij]];
    int k = m.v_rep[m.to[hjk]];
    int n = m.v_rep[m.to[him]];
    Scalar ui = u[i];
    Scalar uj = u[j];
    Scalar uk = u[k];
    Scalar um = u[n];
    Scalar uijk_avg = (ui + uj + uk)/3.0;
    Scalar ujim_avg = (uj + ui + um)/3.0;
    Scalar ljk = ell(m.l[m.e(hjk)], uj, uk, uijk_avg);
    Scalar lki = ell(m.l[m.e(hki)], uk, ui, uijk_avg);
    Scalar lij = ell(m.l[m.e(hij)], ui, uj, uijk_avg);
    Scalar lji = ell(m.l[m.e(hji)], uj, ui, ujim_avg);
    Scalar lmj = ell(m.l[m.e(hmj)], um, uj, ujim_avg);
    Scalar lim = ell(m.l[m.e(him)], ui, um, ujim_avg);

    bool pre_flip_check = (ljk / lki + lki / ljk - (lij / ljk) * (lij / lki)) + (lmj / lim + lim / lmj - (lji / lmj) * (lji / lim)) < 0;

    // additionally check whether delaunay is violated after flip
    // we consider the configuration to 'violate delaunay condition' only if
    // it does not satisfy delaunay check AND post-flip configuration satisfies delaunay condition.
    Scalar umki_avg = (um + uk + ui)/3.0;
    Scalar ukmj_avg = (uk + um + uj)/3.0;
    Scalar _lkm_non_scaled = (m.l[m.e(hjk)] * m.l[m.e(him)] + m.l[m.e(hki)] * m.l[m.e(hmj)]) / m.l[m.e(hij)];
    Scalar _lkm = ell(_lkm_non_scaled , uk, um, ukmj_avg);
    Scalar _lmj = ell(m.l[m.e(hmj)], um, uj, ukmj_avg);
    Scalar _ljk = ell(m.l[m.e(hjk)], uj, uk, ukmj_avg);
    Scalar _lmk = ell(_lkm_non_scaled , um, uk, umki_avg);
    Scalar _lki = ell(m.l[m.e(hki)] , uk, ui, umki_avg);
    Scalar _lim = ell(m.l[m.e(him)] , ui, um, umki_avg);
    bool post_flip_check = (_lki / _lim + _lim / _lki - (_lmk / _lki) * (_lmk / _lim)) + (_ljk / _lmj + _lmj / _ljk - (_lkm / _ljk) * (_lkm / _lmj)) < 0;
    return pre_flip_check && !post_flip_check;
  }

  /**
   * Flip the given halfedge in mesh and update the edge length accordingly.
   *
   * @param m, mesh data structure
   * @param u, #v vector, per-vertex scale factors
   * @param e, int, halfedge id
   * @param delaunay_stats struct collecting info for delaunay flips through out the algorithm
   * @param Ptolemy, bool, when true the edge length is updated via ptolemy formula, otherwise using law of cosine.
   * @return bool, true indicates flip succeeds.
   */
  static bool EdgeFlip(Mesh<Scalar>& m, const VectorX& u, int e, int tag, DelaunayStats& delaunay_stats, bool Ptolemy = true)
  {
    Mesh<Scalar>& mc = m.cmesh();

    int hij = mc.h0(e);
    int hjk = mc.n[hij];
    int hki = mc.n[hjk];
    int hji = mc.h1(e);
    int him = mc.n[hji];
    int hmj = mc.n[him];

    std::vector<char> &type = mc.type;

    std::vector<int> to_flip;
    if (type[hij] > 0) // skip in non-symmetric mode for efficiency
    {
      int types;
      bool reverse = true;
      if (type[hki] <= type[hmj])
      {
        types = type[hki] * 100000 + type[hjk] * 10000 + type[hij] * 1000 + type[hji] * 100 + type[him] * 10 + type[hmj];
        reverse = false;
      }
      else
        types = type[hmj] * 100000 + type[him] * 10000 + type[hji] * 1000 + type[hij] * 100 + type[hjk] * 10 + type[hki];

      if (types == 231123 || types == 231132 || types == 321123)
        return false; // t1t irrelevant
      if (types == 132213 || types == 132231 || types == 312213)
        return false; // t2t irrelevant
      if (types == 341143)
        return false; // q1q irrelevant
      if (types == 342243)
        return false; // q2q irrelevant

      if (types == 111222 || types == 123312)
        delaunay_stats.n_flips_s++;
      if (types == 111123 || types == 111132)
        delaunay_stats.n_flips_t++;
      if (types == 213324 || types == 123314 || types == 111143 || types == 413324 || types == 23314)
        delaunay_stats.n_flips_q++;
      if (types == 111111)
        delaunay_stats.n_flips_12++;
      switch (types)
      {
      case 111222: // (1|2)
        type[hij] = type[hji] = 3;
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 123312: // (t,_,t)
        type[hij] = type[hki];
        type[hji] = type[hmj];
        mc.R[hij] = hji;
        mc.R[hji] = hij;
        break;
      case 111123: // (1,1,t)
        type[hij] = type[hji] = 4;
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 111132: // (1,1,t) mirrored
        type[hij] = type[hji] = 4;
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 222214: // (2,2,t) following (1,1,t) mirrored
        type[hij] = type[hji] = 3;
        to_flip.push_back(6); // to make sure all fake diagonals are top left to bottom right
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 142222: // (2,2,t) following (1,1,t)
        type[hij] = type[hji] = 3;
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 213324: // (t,_,q)
        type[hij] = type[hji] = 2;
        to_flip.push_back(6);
        break;
      case 134412: // (t,_,q) 2nd
        type[hij] = type[hji] = 1;
        if (!reverse)
        {
          mc.R[hji] = hmj;
          mc.R[hmj] = hji;
          mc.R[mc.opp[hji]] = mc.opp[hmj];
          mc.R[mc.opp[hmj]] = mc.opp[hji];
        }
        else
        {
          mc.R[hij] = hki;
          mc.R[hki] = hij;
          mc.R[mc.opp[hij]] = mc.opp[hki];
          mc.R[mc.opp[hki]] = mc.opp[hij];
        }
        break;
      case 123314: // (q,_,t)
        type[hij] = type[hji] = 1;
        to_flip.push_back(6);
        break;
      case 124432: // (q,_,t) 2nd
        type[hij] = type[hji] = 2;
        if (!reverse)
        {
          mc.R[hki] = hij;
          mc.R[hij] = hki;
          mc.R[mc.opp[hki]] = mc.opp[hij];
          mc.R[mc.opp[hij]] = mc.opp[hki];
        }
        else
        {
          mc.R[hmj] = hji;
          mc.R[hji] = hmj;
          mc.R[mc.opp[hmj]] = mc.opp[hji];
          mc.R[mc.opp[hji]] = mc.opp[hmj];
        }
        break;
      case 111143: // (1,1,q)
        type[hij] = type[hji] = 4;
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 222243: // (2,2,q) following (1,1,q)
        type[hij] = type[hji] = 4;
        to_flip.push_back(5);
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 144442: // (1,1,q)+(2,2,q) 3rd
        type[hij] = type[hji] = 3;
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 413324: // (q,_,q)
        type[hij] = type[hji] = 4;
        to_flip.push_back(6);
        to_flip.push_back(1);
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 423314: // (q,_,q) opp
        type[hij] = type[hji] = 4;
        to_flip.push_back(1);
        to_flip.push_back(6);
        mc.R[hij] = hij;
        mc.R[hji] = hji;
        break;
      case 134414: // (q,_,q) 2nd
        type[hij] = type[hji] = 1;
        break;
      case 234424: // (q,_,q) 3rd
        type[hij] = type[hji] = 2;
        if (!reverse)
        {
          mc.R[hji] = mc.n[mc.n[mc.opp[mc.n[mc.n[hji]]]]]; // attention: hji is not yet flipped here, hence twice .n[]
          mc.R[mc.n[mc.n[mc.opp[mc.n[mc.n[hji]]]]]] = hji;
          mc.R[mc.opp[hji]] = mc.opp[mc.R[hji]];
          mc.R[mc.opp[mc.R[hji]]] = mc.opp[hji];
        }
        else
        {
          mc.R[hij] = mc.n[mc.n[mc.opp[mc.n[mc.n[hij]]]]];
          mc.R[mc.n[mc.n[mc.opp[mc.n[mc.n[hij]]]]]] = hij;
          mc.R[mc.opp[hij]] = mc.opp[mc.R[hij]];
          mc.R[mc.opp[mc.R[hij]]] = mc.opp[hij];
        }
        break;
      case 314423: // fake diag switch following (2,2,t) following (1,1,t) mirrored
        break;
      case 324413: // fake diag switch (opp) following (2,2,t) following (1,1,t) mirrored
        break;
      case 111111:
        break;
      case 222222:
        break;
      case 000000:
        type[hij] = type[hji] = 0; // for non-symmetric mode
        break;
      default:
        spdlog::error(" (attempted to flip edge that should never be non-Delaunay (type{})).", types);
        return false;
      }

      if (reverse)
      {
        for (int i = 0; i < to_flip.size(); i++)
          to_flip[i] = 7 - to_flip[i];
      }
    }

    delaunay_stats.n_flips++;
    if (Ptolemy)
    {
      delaunay_stats.flip_seq.push_back(hij);
    }
    else
    {
      delaunay_stats.flip_seq.push_back(-hij-1);
    }
    if (!m.flip_ccw(hij, Ptolemy))
    {
      spdlog::error(" EDGE COULD NOT BE FLIPPED! ");
    }
    if (tag == 1)
    {
      m.flip_ccw(hij, Ptolemy);
      m.flip_ccw(hij, Ptolemy);
      if (Ptolemy)
      {
        delaunay_stats.flip_seq.push_back(hij);
        delaunay_stats.flip_seq.push_back(hij);
      }
      else
      {
        delaunay_stats.flip_seq.push_back(-hij-1);
        delaunay_stats.flip_seq.push_back(-hij-1);
      }
    } // to make it cw on side 2

    for (int i = 0; i < to_flip.size(); i++)
    {
      if (to_flip[i] == 1)
        EdgeFlip(m, u, mc.e(hki), 2, delaunay_stats, Ptolemy);
      if (to_flip[i] == 2)
        EdgeFlip(m, u, mc.e(hjk), 2, delaunay_stats, Ptolemy);
      if (to_flip[i] == 5)
        EdgeFlip(m, u, mc.e(him), 2, delaunay_stats, Ptolemy);
      if (to_flip[i] == 6)
        EdgeFlip(m, u, mc.e(hmj), 2, delaunay_stats, Ptolemy);
    }

    return true;
  }

  /**
   * Repeatedly perform edge flip operations until the rescaled triangles edges satisfying delaunay condition for all.
   *
   * @param m, mesh data structure
   * @param u, #v vector, per-vertex scale factors
   * @param delaunay_stats struct collecting info for delaunay flips through out the algorithm
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @param Ptolemy, bool, when true the edge length is updated via ptolemy formula, otherwise using law of cosine.
   * @return void.
   */
  static void MakeDelaunay(Mesh<Scalar>& m, const VectorX& u, DelaunayStats& delaunay_stats, SolveStats<Scalar>& solve_stats, bool Ptolemy = true)
  {
    Mesh<Scalar>& mc = m.cmesh();
    std::set<int> q;
    for (int i = 0; i < mc.n_halfedges(); i++)
    {
      if (mc.opp[i] < i) // Only consider halfedges with lower index to prevent duplication
        continue;
      int type0 = mc.type[mc.h0(i)];
      int type1 = mc.type[mc.h1(i)];
      if (type0 == 0 || type0 == 1 || type1 == 1 || type0 == 3) //type 22 edges are flipped below; type 44 edges (virtual diagonals) are never flipped.
        q.insert(i);
    }
    while (!q.empty())
    {
      int e = *(q.begin());
      q.erase(q.begin());
      int type0 = mc.type[mc.h0(e)];
      int type1 = mc.type[mc.h1(e)];
      if (!(type0 == 2 && type1 == 2) && !(type0 == 4) && NonDelaunay(mc, u, e, solve_stats))
      {
        int Re = -1;
        if (type0 == 1 && type1 == 1)
          Re = mc.e(mc.R[mc.h0(e)]);
        if (!EdgeFlip(m, u, e, 0, delaunay_stats, Ptolemy))
          continue;
        int hn = mc.n[mc.h0(e)];
        q.insert(mc.e(hn));
        q.insert(mc.e(mc.n[hn]));
        hn = mc.n[mc.h1(e)];
        q.insert(mc.e(hn));
        q.insert(mc.e(mc.n[hn]));
        if (type0 == 1 && type1 == 1) // flip mirror edge on sheet 2
        {
          int e = Re;
          if (Re == -1)
            spdlog::info("Negative index");
          if (!EdgeFlip(m, u, e, 1, delaunay_stats, Ptolemy))
            continue;
          int hn = mc.n[mc.h0(e)];
          q.insert(mc.e(hn));
          q.insert(mc.e(mc.n[hn]));
          hn = mc.n[mc.h1(e)];
          q.insert(mc.e(hn));
          q.insert(mc.e(mc.n[hn]));
        }
        // checkR();
      }
    }
  }

  static VectorX DescentDirection(const Eigen::SparseMatrix<Scalar>& hessian, const VectorX& grad, int fixed_dof, SolveStats<Scalar>& solve_stats)
  {

    static Scalar a = 0.0; // Parameter for interpolating from the Newton direction to steepest descent

    auto grad_dof_fixed = grad;
    auto hessian_dof_fixed = hessian;

    // Set fixed degree of freedom in the gradient and hessian
    grad_dof_fixed[fixed_dof] = 0;
    for (int k = 0; k < hessian_dof_fixed.outerSize(); ++k)
    {
      for (typename Eigen::SparseMatrix<Scalar>::InnerIterator it(hessian_dof_fixed, k); it; ++it)
      {
        if ((it.row() == fixed_dof) || (it.col() == fixed_dof))
        {
          it.valueRef() = 0;
        }
      }
    }
    hessian_dof_fixed.coeffRef(fixed_dof,fixed_dof) = 1;

    // Compute corrected descent direction
    while (true)
    {
      Eigen::SparseMatrix<Scalar> mat;
      if (a == 0)
      {
        mat = hessian_dof_fixed; // Use newton step
      }
      else
      {
        // Create identity
        typedef Eigen::Triplet<Scalar> T;
        std::vector<T> tripletList;
        tripletList.reserve(grad.rows());
        for(int i = 0; i < grad.rows(); ++i)
        {
          tripletList.push_back(T(i,i,1));
        }
        Eigen::SparseMatrix<Scalar> id(grad.rows(), grad.rows());
        id.setFromTriplets(tripletList.begin(), tripletList.end());

        // Create matrix with correction
        mat = hessian_dof_fixed + a*id;
      }

      Eigen::SimplicialLDLT<Eigen::SparseMatrix<Scalar>> solver;
      solver.compute(mat);
      VectorX d = -solver.solve(grad_dof_fixed);
      Scalar newton_decr = d.dot(grad_dof_fixed);
      if (solver.info() == Eigen::Success && newton_decr < 0)
      {
        a *= 0.5; // start from lower a on the next step
        solve_stats.n_solves++;
        return d;
      }
      else if (a == 0)
      {
        a = 1; // We did not try the correction yet, start from arbitrary value 1
        spdlog::info(" Starting correction.");
      }
      else
      {
        a *= 2; // Correction was not enough, increase weight of id
      }
    }
  }

  /**
   * Backtracking line search function, checking the sign of projected gradient.
   * @param m, mesh data structure
   * @param u0, #v vector, per-vertex scale factors
   * @param d0, #v vector, descent direction
   * @param lambda, initial step size, will be updated when exit line-search
   * @param currentg, gradient computed before start doing line-search, will be updated when exit line-search
   * @param bound_norm, when true: require gradient norm to decrease at each iteration
   * @param delaunay_stats struct collecting info for delaunay flips through out the algorithm
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @param alg_params, algorithm parameters, for details check the struct definitions on the top
   * @param ls_params, line search parameters, for details check the struct definitions on the top
   * @param stats_params, statistics parameters, for details check the struct definitions on the top
   * @return VectorX, updated per-vertex scale factor along descent direction.
   */
  static VectorX LineSearchNewtonDecr(Mesh<Scalar>& m, const VectorX& u0, const VectorX& d0, Scalar& lambda, VectorX& currentg, bool& bound_norm, DelaunayStats& delaunay_stats, SolveStats<Scalar>& solve_stats, const AlgorithmParameters& alg_params, const LineSearchParameters& ls_params, const StatsParameters& stats_params){

    Mesh<Scalar> &mc = m.cmesh();
    auto d = d0;
    auto u = u0;
    auto newton_decr = d.dot(currentg);

    // Scale the search direction vector by lambda
    d *= lambda;

    // To avoid nans/infs
    if(ls_params.do_reduction){
      while(d.maxCoeff() - d.minCoeff() > 10)
      {
        d /= 2;
        lambda /=2;
      }
    }

    Scalar init_e = 0.0; VectorX init_g = currentg;
    Scalar l2_g0_sq = currentg.dot(currentg);
    VectorX alpha, cot_alpha;

    // Line search
    u += d;
    MakeDelaunay(m, u, delaunay_stats, solve_stats);
    ComputeAngles(mc, u, alpha, cot_alpha);

    int count = 0;
    Gradient(mc, alpha, currentg, solve_stats); // Current gradient value
    Scalar l2_g_sq = currentg.dot(currentg); // Squared norm of the gradient
    Scalar proj_grad = d.dot(currentg);  // Projected gradient
    while ((proj_grad > 0) || (l2_g_sq > l2_g0_sq && bound_norm))
    {
      // Backtrack one step
      d /= 2;
      lambda /= 2; // record changes in lambda as well
      u -= d;
      MakeDelaunay(m, u, delaunay_stats, solve_stats);
      ComputeAngles(mc, u, alpha, cot_alpha);
      Gradient(mc, alpha, currentg, solve_stats); // update gradient

      // Line search condition to ensure quadratic convergence
      if (   (count == 0)
          && ((l2_g_sq <= l2_g0_sq) || (!bound_norm))
          && (0.5 * (d.dot(currentg) + proj_grad) <= 0.1 * newton_decr))
      {
        u += d; // Use full line step
        lambda *= 2;
        MakeDelaunay(m, u, delaunay_stats, solve_stats);
        ComputeAngles(mc, u, alpha, cot_alpha);
        Gradient(mc, alpha, currentg, solve_stats); // update gradient
        break;
      }

      // Update squared gradient norm and projected gradient
      l2_g_sq = currentg.dot(currentg);
      proj_grad = d.dot(currentg);

      count++;

      // Check if gradient norm is below the threshold to drop the bound
      if ((bound_norm) && (lambda <= ls_params.bound_norm_thres))
      {
        bound_norm = false;
        spdlog::debug("Dropping norm bound.");
      }

      // Check if lambda is below the termination threshold
      if (lambda < alg_params.min_lambda)
        break;
    }
    spdlog::debug("Used lambda {} ", lambda);
    return u;
  }

  /**
   * Backtracking line search function checking the conformal-equivalence-energy and with armijo condition
   * @param m, mesh data structure
   * @param u0, #v vector, per-vertex scale factors
   * @param d0, #v vector, descent direction
   * @param lambda, initial step size, will be updated when exit line-search
   * @param currentg, gradient computed before start doing line-search, will be updated when exit line-search
   * @param bound_norm, when true: require gradient norm to decrease at each iteration
   * @param delaunay_stats struct collecting info for delaunay flips through out the algorithm
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @param alg_params, algorithm parameters, for details check the struct definitions on the top
   * @param ls_params, line search parameters, for details check the struct definitions on the top
   * @param stats_params, statistics parameters, for details check the struct definitions on the top
   * @return VectorX, updated per-vertex scale factor along descent direction.
   */
  static VectorX LineSearchCETMEnergy(Mesh<Scalar>& m, const VectorX& u0, const VectorX& d0, Scalar& lambda, VectorX& currentg, bool& bound_norm, DelaunayStats& delaunay_stats, SolveStats<Scalar>& solve_stats, const AlgorithmParameters& alg_params, const LineSearchParameters& ls_params, const StatsParameters& stats_params){

    Mesh<Scalar> &mc = m.cmesh();
    auto d = d0;
    auto u = u0;
    auto newton_decr = d.dot(currentg);

    // Scale the search direction vector by lambda
    d *= lambda;

    // To avoid nans/infs
    if(ls_params.do_reduction){
      while(d.maxCoeff() - d.minCoeff() > 10)
      {
        d /= 2;
        lambda /=2;
      }
    }

    Scalar init_e = 0.0; VectorX init_g = currentg;
    Scalar l2_g0_sq = currentg.dot(currentg);
    VectorX alpha, cot_alpha;
    ComputeAngles(mc, u, alpha, cot_alpha);

    if(ls_params.energy_samples){
      DelaunayStats d_stats_placeholder;
      SolveStats<Scalar> s_stats_placeholder;
      SampleEnergyAlongDirection(mc, u, stats_params.output_dir+"/"+"energy_sample.csv", d, 2.0, 200, d_stats_placeholder, s_stats_placeholder, true);
      SampleNewtonDecrement(mc, u, stats_params.output_dir+"/"+"newton_decrement.csv", d, 0.0, 2.0, 200, d_stats_placeholder, s_stats_placeholder);
    }

    // init energy before line search start
    init_e = ConformalEquivalenceEnergy(mc, alpha, u);

    // Line search
    u += d;
    MakeDelaunay(m, u, delaunay_stats, solve_stats);
    ComputeAngles(mc, u, alpha, cot_alpha);

    Gradient(mc, alpha, currentg, solve_stats); // Current gradient value
    Scalar new_e = ConformalEquivalenceEnergy(mc, alpha, u);
    bool armijo_cond = false, curvature_cond = false;
    do{

      armijo_cond = new_e <= (init_e + ls_params.c1 * lambda * init_g.dot(d));
      curvature_cond = currentg.dot(d) >= ls_params.c2 * init_g.dot(d);

      if((bound_norm) && (lambda <= ls_params.bound_norm_thres))
      {
        bound_norm = false;
        spdlog::debug("Dropping norm bound.");
      }
      if (lambda < alg_params.min_lambda)
        break;

      if(new_e < init_e && armijo_cond && curvature_cond)
        break;

      d /= 2;
      lambda /= 2; // record backtrack changes in lambda
      u -= d; // Backtrack
      MakeDelaunay(m, u, delaunay_stats, solve_stats);
      ComputeAngles(mc, u, alpha, cot_alpha);
      Gradient(mc, alpha, currentg, solve_stats);
      new_e = ConformalEquivalenceEnergy(mc, alpha, u);

    }while(true);

    solve_stats.cetm_energy = new_e;

    spdlog::debug("Used lambda {} ", lambda);
    return u;
  }

  /**
   * The top-level conformal-hyperblic-delaunay algorithm
   *
   * @param m Mesh data structure
   * @param u0 vector of Scalar size equal to number of vertices of mesh, the initial values of per-vertex logarithmic scale factors
   * @param pt_fids list of face ids per sample point on the original mesh surface, will be updated through out the whole algorithm
   * @param pt_bcs list of barycentric coordinates of each sample point in the correspoinding face, will be updated through out the whole algorithm
   * @param alg_params, algorithm parameters, for details check the struct definitions on the top
   * @param ls_params, line search parameters, for details check the struct definitions on the top
   * @param stats_params, statistics parameters, for details check the struct definitions on the top
   * @return flip sequence
   */
  static std::tuple<VectorX, std::vector<int>> FindConformalMetric(OverlayMesh<Scalar>& m, const VectorX& u0, std::vector<int>& pt_fids, std::vector<Eigen::Matrix<Scalar, 3, 1>>& pt_bcs, const AlgorithmParameters& alg_params, const LineSearchParameters& ls_params, const StatsParameters& stats_params)
  {
    switch (stats_params.log_level){
      case 0: spdlog::set_level(spdlog::level::trace);    break;
      case 1: spdlog::set_level(spdlog::level::debug);    break;
      case 2: spdlog::set_level(spdlog::level::info);     break;
      case 3: spdlog::set_level(spdlog::level::warn);     break;
      case 4: spdlog::set_level(spdlog::level::err);      break;
      case 5: spdlog::set_level(spdlog::level::critical); break;
      default:
      case 6: spdlog::set_level(spdlog::level::off);      break;
    }
    m.bypass_overlay = alg_params.bypass_overlay;
    Mesh<Scalar>& mc = m.cmesh();
    mc.init_pts(pt_fids, pt_bcs);

    DelaunayStats delaunay_stats;
    SolveStats<Scalar> solve_stats;

    std::clock_t start;
    start = std::clock();

    // Initialize u to the zero vector
    VectorX u = u0;
    VectorX cot_alpha(mc.n_halfedges());
    VectorX alpha(mc.n_halfedges());

    // Degree of freedom to eliminate to make the Hessian positive definite
    // Choose first vertex arbitrarily for the fixed_dof for regular meshes
    int fixed_dof = 0;
    if (mc.R[0] == 0)
    {
      fixed_dof = 0;
    }
    // Set the fixed_dof to the first boundary halfedge for symmetric meshes
    else
    {
      for (int i = 0; i < mc.n_vertices(); ++i)
      {
        if (mc.v_rep[mc.to[mc.R[mc.out[i]]]] == mc.v_rep[i])
        {
          fixed_dof = mc.v_rep[i];
          break;
        }
      }
    }

    bool bound_norm = (ls_params.lambda0 > ls_params.bound_norm_thres); // prevents the grad norm from increasing
    if(bound_norm) spdlog::debug("Using norm bound.");

    double max_curr = 0.0;
    Scalar pi;
#ifdef WITH_MPFR
    if (std::is_same<Scalar, mpfr::mpreal>::value)
        pi = Scalar(mpfr::const_pi());
    else
        pi = Scalar(M_PI);
#else
    pi = Scalar(M_PI);
#endif
    if (stats_params.flip_count){
      // need to also collect max boundary curvature error
      for(int i = 0; i < mc.R.size(); i++){
        if(mc.R[i] == mc.opp[i]){
          int v0 = mc.v_rep[mc.to[i]];
          if(max_curr < std::abs(double(mc.Th_hat[v0])/2-M_PI))
            max_curr = std::abs(double(mc.Th_hat[v0])/2-M_PI);
        }
      }
    }

    Scalar lambda = ls_params.lambda0;

    // Optionally use Euclidean flips instead of Ptolemy flips for the initial MakeDelaunay
    if (!alg_params.initial_ptolemy){
      MakeDelaunay(m, u, delaunay_stats, solve_stats, false);
      spdlog::debug("Finish first delaunay non_ptolemy");
      m.garbage_collection();
      m.bc_original_to_eq(mc.n, mc.to, mc.l);
    }

    // step1 apply per triangle the bc map to unit equilateral triangle
    original_to_equilateral(mc.pts, mc.pt_in_f, mc.n, mc.h, mc.l);
    if (alg_params.initial_ptolemy) {
      MakeDelaunay(m, u, delaunay_stats, solve_stats, true);
      spdlog::debug("Finish first delaunay ptolemy");
    }
    ComputeAngles(mc, u, alpha, cot_alpha);
    std::ofstream mf;
    if(stats_params.error_log){
      mf.open(stats_params.output_dir+"/"+stats_params.name+".csv",std::ios_base::out);
      mf << "itr, max error, min_u, max_u, lambda, newton_dec, do_reduction, cetm_e\n";
    }

    VectorX currentg;
    Gradient(mc, alpha, currentg, solve_stats);
    while (currentg.cwiseAbs().maxCoeff() >= alg_params.error_eps)
    {
      // Compute gradient and descent direction from Hessian (with efficient solver)
      Eigen::SparseMatrix<Scalar> hessian;
      Hessian(mc, cot_alpha, hessian);
      VectorX d = DescentDirection(hessian, currentg, fixed_dof, solve_stats);

      // Terminate if newton decrement sufficiently smalll
      Scalar newton_decr = d.dot(currentg);

      if(stats_params.error_log){
        solve_stats.cetm_energy = ConformalEquivalenceEnergy(mc, alpha, u);
        mf << solve_stats.n_solves << "," << std::setprecision(17) << currentg.cwiseAbs().maxCoeff() << "," <<u.minCoeff() << "," << u.maxCoeff() << "," << lambda << "," << newton_decr << "," << ls_params.do_reduction <<" , "<<solve_stats.cetm_energy<< std::endl;
      }
      // Alternative termination conditons to error threshold
      if (lambda < alg_params.min_lambda)
        break;
      if (solve_stats.n_solves >= alg_params.max_itr)
        break;
      if (newton_decr > alg_params.newton_decr_thres)
        break;

      // Determine initial lambda for line search based on method parameters
      if (ls_params.energy_cond || ls_params.reset_lambda)
      {
        lambda = ls_params.lambda0;
      }
      else
      {
        lambda = std::min<Scalar>(1, 2 * lambda); // adaptive step length
      }

      // reset lambda when it goes above norm bound threshold
      if ((lambda > ls_params.bound_norm_thres) && (!bound_norm))
      {
        bound_norm = true;
        lambda = ls_params.lambda0;
        spdlog::debug("Using norm bound.");
      }
      if(ls_params.energy_cond)
        u = LineSearchCETMEnergy(m, u, d, lambda, currentg, bound_norm, delaunay_stats, solve_stats, alg_params, ls_params, stats_params);
      else
        u = LineSearchNewtonDecr(m, u, d, lambda, currentg, bound_norm, delaunay_stats, solve_stats, alg_params, ls_params, stats_params);

      // Display current iteration information
      if(ls_params.energy_cond)
        spdlog::info("itr({}) lm({}) flips({}) newton_decr({}) max_error({}), cetm_e({}))", solve_stats.n_solves, lambda, delaunay_stats.n_flips, newton_decr, currentg.cwiseAbs().maxCoeff(), solve_stats.cetm_energy);
      else
        spdlog::info("itr({}) lm({}) flips({}) newton_decr({}) max_error({}))", solve_stats.n_solves, lambda, delaunay_stats.n_flips, newton_decr, currentg.cwiseAbs().maxCoeff());

      ComputeAngles(mc, u, alpha, cot_alpha);

    }

    // Output flip stats
    if(stats_params.error_log) mf.close();
    auto total_time = ( std::clock() - start ) / (double) CLOCKS_PER_SEC;
    if(stats_params.flip_count){
      auto fname = stats_params.output_dir+"/flips_stats.csv";
      auto header = "name, flips12, flipsq, flipss,flipst, n_flips, fac, time";
      std::stringstream ss;
      ss << stats_params.name << ", " << delaunay_stats.n_flips_12 << ", " << delaunay_stats.n_flips_q << ", " << delaunay_stats.n_flips_s << ", " << delaunay_stats.n_flips_t << ", " << delaunay_stats.n_flips  << ", " << max_curr/pi <<", "<< total_time;
      std::vector<std::string> content = {ss.str()};
      WriteLog(fname, content, header, true);
    }

    if(stats_params.print_summary){
      auto fname = stats_params.output_dir+"/summary_delaunay.csv";
      auto header = "name, n_flips, max_error, time";
      VectorX currentg;
      Gradient(mc, alpha, currentg, solve_stats);
      std::stringstream ss;
      ss << stats_params.name << ", " <<delaunay_stats.n_flips << "," << currentg.cwiseAbs().maxCoeff()  << ", " << total_time;
      std::vector<std::string> content = {ss.str()};
      WriteLog(fname, content, header, true);
    }

    // map barycentric coordinates from equilateral to scaled triangle
    equilateral_to_scaled(mc.pts, mc.pt_in_f, mc.n, mc.h, mc.to, mc.l, u);

    int cnt = 0;
    for (auto pt : mc.pts)
    {
      pt_fids[cnt] = pt.f_id;
      pt_bcs[cnt] = pt.bc;
      cnt++;
    }

    return std::make_tuple(u, delaunay_stats.flip_seq);

  }

   /**
   * Get the Reverse Map of the FindConformalMetric using the Halfedge-Flip Sequence
   * @param m_o OverlayMesh computed from FindConformalMetric
   * @param flip_seq Flip_ccw Sequence used in FindConformalMetric
   * @return Reverse Overlaymesh m_o_rev and vertices-id map between m_o_rev and m_o
   */
  static std::tuple<OverlayMesh<Scalar>, std::vector<int>> GetReverseMap(OverlayMesh<Scalar> & m_o, const std::vector<int> &flip_seq)
  {
    auto mc = m_o.cmesh();
    OverlayMesh<Scalar> m_o_rev(mc);
    bool do_Ptolemy = true;
    // do reverse flips
    for (int ii = flip_seq.size() - 1; ii >= 0; ii--)
    {
      if (do_Ptolemy && flip_seq[ii] < 0)
      {
        do_Ptolemy = false;
        m_o_rev.garbage_collection();
        Eigen::Matrix<Scalar, -1, 1> u_0(m_o_rev.cmesh().out.size());
        u_0.setZero();
        m_o_rev.bc_eq_to_scaled(m_o_rev.cmesh().n, m_o_rev.cmesh().to, m_o_rev.cmesh().l,u_0);
      }
      if (do_Ptolemy)
      {
        m_o_rev.flip_ccw(flip_seq[ii], true);
        m_o_rev.flip_ccw(flip_seq[ii], true);
        m_o_rev.flip_ccw(flip_seq[ii], true);
      }
      else
      {
        m_o_rev.flip_ccw(-flip_seq[ii]-1, false);
        m_o_rev.flip_ccw(-flip_seq[ii]-1, false);
        m_o_rev.flip_ccw(-flip_seq[ii]-1, false);
      }

    }
    if(m_o_rev.bypass_overlay){
      m_o.bypass_overlay = true;
      return std::make_tuple(m_o_rev, std::vector<int>());
    }
    m_o.garbage_collection();
    m_o_rev.garbage_collection();

    if (do_Ptolemy == false)
    {
      m_o_rev.bc_original_to_eq(m_o_rev.cmesh().n, m_o_rev.cmesh().to, m_o_rev.cmesh().l);
    }
    spdlog::debug("#m_o.out: {}, #m_o_rev.out: {}", m_o.out.size(), m_o_rev.out.size());
    spdlog::debug("#m_o.n: {}, #m_o_rev.n: {}", m_o.n.size(), m_o_rev.n.size());

    // get the v_map
    std::vector<int> v_map(m_o.out.size());
    // init the original vertices part with Id
    for (int i = 0; i < mc.out.size(); i++)
    {
      v_map[i] = i;
    }
    // init the segment vertices part with -1
    for (int i = mc.out.size(); i < v_map.size(); i++)
    {
      v_map[i] = -1;
    }

    for (int v_start = 0; v_start < mc.out.size(); v_start++)
    {
      int h_out0 = m_o.out[v_start];
      int h_out0_copy = h_out0;
      int v_end = m_o.find_end_origin(h_out0);

      int h_out0_rev = m_o_rev.out[v_start];
      bool flag = false;
      int while_cnt = 0;
      int caseid = 0;

      while (true)
      {
        if (m_o_rev.find_end_origin(h_out0_rev) == v_end && m_o.dist_to_next_origin(h_out0) == m_o_rev.dist_to_next_origin(h_out0_rev))
        {
          // test first segment vertex
          // case 1, no segment vertex
          if (m_o_rev.to[h_out0_rev] == v_end)
          {
            caseid = 0;
            if (m_o.next_out(h_out0) != h_out0_copy)
            {
              h_out0 = m_o.next_out(h_out0);
              v_end = m_o.find_end_origin(h_out0);
            }
            else
            {
              flag = true;
            }

          }
          else
          {
            int h_first = m_o.n[h_out0];
            int h_first_rev = m_o_rev.n[h_out0_rev];

            if (m_o.find_end_origin(h_first) == m_o_rev.find_end_origin(h_first_rev) && m_o.find_end_origin(m_o.opp[h_first]) == m_o_rev.find_end_origin(m_o_rev.opp[h_first_rev]) && m_o.dist_to_next_origin(h_first) == m_o_rev.dist_to_next_origin(h_first_rev))
            {
              caseid = 1;
              flag = true;
            }
          }
        }

        if (flag) break;

        h_out0_rev = m_o_rev.next_out(h_out0_rev);
        while_cnt++;

        if (while_cnt > 99999)
        {
          spdlog::error("infi loop in finding first match");
          break;
        }
      }

      int h_out = h_out0;
      int h_out_rev = h_out0_rev;

      do
      {
        int h_current = h_out;
        int h_current_rev = h_out_rev;

        while (m_o.vertex_type[m_o.to[h_current]] != ORIGINAL_VERTEX)
        {

          if (m_o_rev.vertex_type[m_o_rev.to[h_current_rev]] == ORIGINAL_VERTEX)
          {
            spdlog::error("out path not matching, case: {}", caseid);
            break;
          }
          int v_current = m_o.to[h_current];
          int v_current_rev = m_o_rev.to[h_current_rev];
          if (v_map[v_current] == -1)
          {
            v_map[v_current] = v_current_rev;
          }
          else if (v_map[v_current] != v_current_rev)
          {
            spdlog::error("the mapping is wrong, case: {}", caseid);
          }
          h_current = m_o.n[m_o.opp[m_o.n[h_current]]];
          h_current_rev = m_o_rev.n[m_o_rev.opp[m_o_rev.n[h_current_rev]]];
        }
        h_out = m_o.next_out(h_out);
        h_out_rev = m_o_rev.next_out(h_out_rev);
      } while (h_out != h_out0);

    }

    return std::make_tuple(m_o_rev, v_map);
  }

  /**
   * Interpolate 3d coordinates to get the OverlayMesh in 3d
   * @param m_o OverlayMesh computed from FindConformalMetric
   * @param flip_seq Flip_ccw Sequence used in FindConformalMetric
   * @param x 3d coordinat of the Original Mesh
   * @return interpolated OverlayMesh Coordinates
   */
  static std::vector<std::vector<Scalar>> Interpolate_3d(OverlayMesh<Scalar> & m_o, const std::vector<int> &flip_seq, const std::vector<std::vector<Scalar>> &x, bool uniform = false)
  {
    std::vector<std::vector<Scalar>> z(3);

    if (uniform)
    {
      for (int j = 0; j < 3; j++)
      {
        z[j] = m_o.interpolate_along_o(x[j]);
      }
      return z;
    }
    auto rev_map = GetReverseMap(m_o, flip_seq);
    if(m_o.bypass_overlay) return std::vector<std::vector<Scalar>>();
    auto m_o_rev = std::get<0>(rev_map);
    auto v_map = std::get<1>(rev_map);

    Eigen::Matrix<Scalar, -1, 1> u_0(m_o_rev.cmesh().out.size());
    u_0.setZero();

    m_o_rev.bc_eq_to_scaled(m_o_rev.cmesh().n, m_o_rev.cmesh().to, m_o_rev.cmesh().l, u_0);

    std::vector<std::vector<Scalar>> z_rev(3);
    for (int j = 0; j < 3; j++)
    {
      z_rev[j] = m_o_rev.interpolate_along_o_bc(m_o_rev.cmesh().opp, m_o_rev.cmesh().to, x[j]);
    }

    for (int j = 0; j < 3; j++)
    {
      z[j].resize(z_rev[j].size());
      for (int i = 0; i < z[j].size(); i++)
      {
        z[j][i] = z_rev[j][v_map[i]];
      }
    }

    return z;
  }

  /**
   * Start at any configuration that all scaled elements in mesh satisfies delaunay condition, evenly evaluate newton-decrement along
   * certain direction for a number of samples the series of sampled values will be written to file `fname`.
   *
   * @param m0 Mesh data structure
   * @param u0 vector of Scalar size equal to number of vertices of mesh, the initial values of per-vertex logarithmic scale factors
   * @param d0 vector of Scalar size eqaul to number of vertices of mesh, a delta vector on u0
   * @param lambda_max Scalar controlling the maximum step size along the direction d0
   * @param n_samples Total number of evenly distributed sample points along d0
   * @param delaunay_stats struct collecting stats for delaunay flips through out the algorithm
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @param rescale Scale (and record) the lambda values by |d0| and the newton decr by 1/|d0|
   * @return void
   */
  static void SampleNewtonDecrement(const Mesh<Scalar>& m0, const VectorX& u0, std::string fname, const VectorX &d, Scalar lambda_min, Scalar lambda_max, int n_sample, DelaunayStats& delaunay_stats, SolveStats<Scalar>& solve_stats, bool rescale=false){

    if (n_sample == 0)
      return;

    std::vector<std::string> newton_dec;
    Scalar step_size = (lambda_max - lambda_min) / n_sample;
    VectorX alpha, cot_alpha;
    auto m = m0;
    auto u = u0;

    spdlog::info("n_sample = {}", n_sample);

    for (int i = 0; i < n_sample; i++)
    {
      m = m0;
      u = u0 + (lambda_min + i * step_size) * d;
      MakeDelaunay(m, u, delaunay_stats, solve_stats);
      ComputeAngles(m, u, alpha, cot_alpha);
      VectorX g;
      Gradient(m, alpha, g, solve_stats);
      std::stringstream ss;
      if (rescale)
      {
          ss << std::setprecision(17) << sqrt(d.dot(d))*(lambda_min + i * step_size)
             << "," << d.dot(g)/sqrt(d.dot(d));
      }
      else
      {
          ss << std::to_string(i) << "," <<std::setprecision(17) << d.dot(g);
      }
      newton_dec.push_back(ss.str());
    }

    WriteLog(fname, newton_dec, "itr, newton_decrement");

  }
  /**
   * @brief Overloaded version of sampleNewtonDecrement function above. Used for python binding. Unlike the above method, this method always computes and uses the Newton descent direction.
   * @param m0 Mesh data structure
   * @param u0 vector of Scalar size equal to number of vertices of mesh, the initial values of per-vertex logarithmic scale factors
   * @param lambda_min Scalar controlling the minimum step size along the direction d0
   * @param lambda_max Scalar controlling the maximum step size along the direction d0
   * @param n_samples Total number of evenly distributed sample points along d0
   * @return void
   */
  static void SampleNewtonDecrementStl(Mesh<Scalar>& m0,
                                       std::vector<Scalar>& u0_vec,
                                       std::string fname,
                                       Scalar lambda_min,
                                       Scalar lambda_max,
                                       int n_sample) {
    VectorX u0(u0_vec.size());
    for(int i = 0; i < u0_vec.size(); i++)
      u0(i) = u0_vec[i];

    // Create placeholder stat structures
    DelaunayStats d_stats_placeholder;
    SolveStats<Scalar> s_stats_placeholder;

    // Degree of freedom to eliminate to make the Hessian positive definite
    // Choose first vertex arbitrarily for the fixed_dof for regular meshes
    int fixed_dof = 0;
    if (m0.R[0] == 0)
    {
      fixed_dof = 0;
    }
    // Set the fixed_dof to the first boundary halfedge for symmetric meshes
    else
    {
      for (int i = 0; i < m0.n_vertices(); ++i)
      {
        if (m0.to[m0.R[m0.out[i]]] == i)
        {
          fixed_dof = i;
          break;
        }
      }
    }

    // Compute angles and cotangents of angles
    VectorX cot_alpha(m0.n_halfedges());
    VectorX alpha(m0.n_halfedges());
    MakeDelaunay(m0, u0, d_stats_placeholder, s_stats_placeholder, true);
    ComputeAngles(m0, u0, alpha, cot_alpha);

    // Compute descent direction from gradient and hessian
    VectorX currentg;
    Gradient(m0, alpha, currentg, s_stats_placeholder);
    Eigen::SparseMatrix<Scalar> hessian;
    Hessian(m0, cot_alpha, hessian);
    VectorX d = DescentDirection(hessian, currentg, fixed_dof, s_stats_placeholder);

    // Sample newton decrement
    SampleNewtonDecrement(m0,
                          u0,
                          fname,
                          d,
                          lambda_min,
                          lambda_max,
                          n_sample,
                          d_stats_placeholder,
                          s_stats_placeholder,
                          true);
  }

  /**
   * Start at any configuration that all scaled elements in mesh satisfies delaunay condition, evenly evaluate conformal-equivalence-energy along
   * certain direction for a number of samples the series of sampled values will be written to file `fname`.
   *
   * @param m0 Mesh data structure
   * @param u0 vector of Scalar size equal to number of vertices of mesh, the initial values of per-vertex logarithmic scale factors
   * @param d0 vector of Scalar size eqaul to number of vertices of mesh, a delta vector on u0
   * @param lambda_max Scalar controlling the maximum step size along the direction d0
   * @param n_samples Total number of evenly distributed sample points along d0
   * @param delaunay_stats struct collecting stats for delaunay flips through out the algorithm
   * @param solve_stats struct collecting info for solvings through out the algorithm
   * @return void
   */
  static void SampleEnergyAlongDirection(const Mesh<Scalar>& m0, const VectorX& u0, std::string fname, const VectorX &d, Scalar lambda_max, int n_sample, DelaunayStats& delaunay_stats, SolveStats<Scalar>& solve_stats, bool subtract_avg=false){

    if (n_sample == 0) return;

    VectorX alpha, cot_alpha;
    Scalar step_size = lambda_max / n_sample;
    Scalar avg_e = 0.0;
    auto m = m0; auto u = u0;
    Eigen::Matrix<Scalar, Eigen::Dynamic, 1> E; E.setZero(n_sample);
    for (int i = 0; i < n_sample; i++){
      m = m0;
      u = u0 + i * step_size * d;
      MakeDelaunay(m, u, delaunay_stats, solve_stats);
      ComputeAngles(m, u, alpha, cot_alpha);
      E[i] = ConformalEquivalenceEnergy(m, alpha, u);
    }
    if(subtract_avg) avg_e = E.sum() / n_sample;

    std::vector<std::string> e_samples;
    for(int i = 0; i < E.size(); i++){
      std::stringstream ss;
      ss << std::to_string(i) << "," << std::setprecision(17) << E[i]-avg_e;
      e_samples.push_back(ss.str());
    }

    std::fstream nf(fname,std::ios::in | std::ios::out);
    WriteLog(fname, e_samples, "itr, e-avg_e");

  }

  /**
   * Given the prescribed per-vertex angle sum, modify the angle sum at first vertex,
   * to make sure Gauss-Bonnet is respected up to numerical error.
   * @param m Mesh data structure
   * @return void
   */
  static void GaussBonnetCorrection(Mesh<Scalar>& m)
  {

    Scalar pi;
#ifdef WITH_MPFR
    if (std::is_same<Scalar, mpfr::mpreal>::value)
      pi = Scalar(mpfr::const_pi());
    else
      pi = Scalar(M_PI);
#else
      pi = Scalar(M_PI);
#endif
    int double_genus = 2 - (m.n_vertices() - m.n_edges() + m.n_faces());
    Scalar targetsum = pi * (2 * m.n_vertices() - 2 * (2 - double_genus));
    double th_hat_sum = 0.0;
    for(auto t: m.Th_hat)
      th_hat_sum += t;
    m.Th_hat[0] -= (th_hat_sum - targetsum);
  }

  /**
   * Logging function, to write list of strings to given file, possibly with header if the file is empty.
   * @param fname, the filename to write log to.
   * @param v, vector of strings to be written to file.
   * @param header, will be written as first line to the file if it's empty.
   * @param append, toggle between append (true) and out mode (false).
   * @return void
   */
  static void WriteLog(std::string fname, std::vector<std::string>& v, std::string header="", bool append=false){
    std::fstream mf, nf; nf.open(fname, std::ios_base::in);
    if(append)
      mf.open(fname, std::ios_base::app);
    else
      mf.open(fname, std::ios_base::out);

    if(!(append && nf.peek() != std::ifstream::traits_type::eof()))
      mf << header << "\n";
    for(int i = 0; i < v.size(); i++){
      mf << v[i] << "\n";
    }
    mf.close();
  }

};
#endif


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2012 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2012 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

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

#ifndef BOOST_GEOMETRY_EXTENSIONS_NSPHERE_ALGORITHMS_WITHIN_HPP
#define BOOST_GEOMETRY_EXTENSIONS_NSPHERE_ALGORITHMS_WITHIN_HPP

#include <boost/geometry/algorithms/distance.hpp>
#include <boost/geometry/algorithms/make.hpp>
#include <boost/geometry/algorithms/within.hpp>
#include <boost/geometry/strategies/distance.hpp>

#include <boost/geometry/multi/core/tags.hpp>

#include <boost/geometry/extensions/nsphere/core/access.hpp>
#include <boost/geometry/extensions/nsphere/core/radius.hpp>
#include <boost/geometry/extensions/nsphere/core/tags.hpp>
#include <boost/geometry/extensions/nsphere/algorithms/assign.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace within
{

//-------------------------------------------------------------------------------------------------------
// Implementation for n-spheres. Supports circles or spheres, in 2 or 3 dimensions, in Euclidian system
// Circle center might be of other point-type as geometry
// Todo: implement as strategy
//-------------------------------------------------------------------------------------------------------
template<typename P, typename C>
inline bool point_in_circle(P const& p, C const& c)
{
    namespace services = strategy::distance::services;

    assert_dimension<C, 2>();

    typedef typename point_type<C>::type point_type;
    typedef typename services::default_strategy
        <
            point_tag, P, point_type
        >::type strategy_type;
    typedef typename services::return_type<strategy_type>::type return_type;

    strategy_type strategy;

    P const center = geometry::make<P>(get<0>(c), get<1>(c));
    return_type const r = geometry::distance(p, center, strategy);
    return_type const rad = services::result_from_distance
        <
            strategy_type
        >::apply(strategy, get_radius<0>(c));

    return r < rad;
}
/// 2D version
template<typename T, typename C>
inline bool point_in_circle(T const& c1, T const& c2, C const& c)
{
    typedef typename point_type<C>::type point_type;

    point_type p = geometry::make<point_type>(c1, c2);
    return point_in_circle(p, c);
}

template<typename B, typename C>
inline bool box_in_circle(B const& b, C const& c)
{
    typedef typename point_type<B>::type point_type;

    // Currently only implemented for 2d geometries
    assert_dimension<point_type, 2>();
    assert_dimension<C, 2>();

    // Box: all four points must lie within circle

    // Check points lower-left and upper-right, then lower-right and upper-left
    return point_in_circle(get<min_corner, 0>(b), get<min_corner, 1>(b), c)
        && point_in_circle(get<max_corner, 0>(b), get<max_corner, 1>(b), c)
        && point_in_circle(get<min_corner, 0>(b), get<max_corner, 1>(b), c)
        && point_in_circle(get<max_corner, 0>(b), get<min_corner, 1>(b), c);
}

// Generic "range-in-circle", true if all points within circle
template<typename R, typename C>
inline bool range_in_circle(R const& range, C const& c)
{
    assert_dimension<R, 2>();
    assert_dimension<C, 2>();

    for (typename boost::range_iterator<R const>::type it = boost::begin(range);
         it != boost::end(range); ++it)
    {
        if (! point_in_circle(*it, c))
        {
            return false;
        }
    }

    return true;
}

template<typename Y, typename C>
inline bool polygon_in_circle(Y const& poly, C const& c)
{
    return range_in_circle(exterior_ring(poly), c);
}

template<typename I, typename C>
inline bool multi_polygon_in_circle(I const& m, C const& c)
{
    for (typename I::const_iterator i = m.begin(); i != m.end(); i++)
    {
        if (! polygon_in_circle(*i, c))
        {
            return false;
        }
    }
    return true;
}

}} // namespace detail::within
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename P, typename Circle>
struct within<P, Circle, point_tag, nsphere_tag>
{
    template <typename Strategy>
    static inline bool apply(P const& p, Circle const& c, Strategy const&)
    {
        return detail::within::point_in_circle(p, c);
    }
};

template <typename Box, typename Circle>
struct within<Box, Circle, box_tag, nsphere_tag>
{
    template <typename Strategy>
    static inline bool apply(Box const& b, Circle const& c, Strategy const&)
    {
        return detail::within::box_in_circle(b, c);
    }
};

template <typename Linestring, typename Circle>
struct within<Linestring, Circle, linestring_tag, nsphere_tag>
{
    template <typename Strategy>
    static inline bool apply(Linestring const& ln, Circle const& c, Strategy const&)
    {
        return detail::within::range_in_circle(ln, c);
    }
};

template <typename Ring, typename Circle>
struct within<Ring, Circle, ring_tag, nsphere_tag>
{
    template <typename Strategy>
    static inline bool apply(Ring const& r, Circle const& c, Strategy const&)
    {
        return detail::within::range_in_circle(r, c);
    }
};

template <typename Polygon, typename Circle>
struct within<Polygon, Circle, polygon_tag, nsphere_tag>
{
    template <typename Strategy>
    static inline bool apply(Polygon const& poly, Circle const& c, Strategy const&)
    {
        return detail::within::polygon_in_circle(poly, c);
    }
};

template <typename M, typename C>
struct within<M, C, multi_polygon_tag, nsphere_tag>
{
    template <typename Strategy>
    static inline bool apply(M const& m, C const& c, Strategy const&)
    {
        return detail::within::multi_polygon_in_circle(m, c);
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_EXTENSIONS_NSPHERE_ALGORITHMS_WITHIN_HPP


// Copyright (c) 2015-2019 Daniel Cooke
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

#ifndef random_select_hpp
#define random_select_hpp

#include <random>
#include <iterator>
#include <cstddef>

#include <boost/random/uniform_int_distribution.hpp>

namespace octopus {

template <typename ForwardIt, typename RandomGenerator>
ForwardIt random_select(ForwardIt first, ForwardIt last, RandomGenerator& g)
{
    if (first == last) return first;
    const auto max = static_cast<std::size_t>(std::distance(first, last));
    if (max == 1) return first;
    boost::random::uniform_int_distribution<std::size_t> dist {0, max - 1};
    std::advance(first, dist(g));
    return first;
}

template <typename ForwardIt>
ForwardIt random_select(ForwardIt first, ForwardIt last)
{
    static thread_local std::mt19937 generator {42};
    return random_select(first, last, generator);
}

template <typename Range>
decltype(auto) random_select(const Range& values)
{
    assert(!values.empty());
    return *random_select(std::cbegin(values), std::cend(values));
}

} // namespace octopus

#endif


/**
 * Copyright Soramitsu Co., Ltd. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

#include "interfaces/common_objects/amount.hpp"

#include <sstream>
#include <type_traits>
#include <utility>

#include <gtest/gtest.h>
#include <boost/multiprecision/cpp_int.hpp>

using namespace shared_model::interface;

struct AmountTest : public ::testing::Test {
  using int_type = std::decay_t<decltype(std::declval<Amount>().intValue())>;

  /// Check int value, precision and string representation of a valid amount.
  void checkValid(const Amount &tested,
                  int_type ref_int_val,
                  types::PrecisionType ref_precision,
                  std::string ref_str) {
    EXPECT_EQ(tested.intValue(), ref_int_val);
    EXPECT_EQ(tested.precision(), ref_precision);
    EXPECT_EQ(tested.toStringRepr(), ref_str);
  }

  /// Check int value, precision and string representation of an invalid amount.
  void checkInvalid(const Amount &tested) {
    EXPECT_EQ(tested.intValue(), 0);
    EXPECT_EQ(tested.precision(), 0);
    EXPECT_EQ(tested.toStringRepr(), "NaN");
  }
};

TEST_F(AmountTest, Basic) {
  checkValid(Amount{"0"}, 0, 0, "0");
  checkValid(Amount{"0.1"}, 1, 1, "0.1");
  checkValid(Amount{"1234"}, 1234, 0, "1234");
  checkValid(Amount{"23.45"}, 2345, 2, "23.45");
}

TEST_F(AmountTest, Strange) {
  checkValid(Amount{"000.000"}, 0, 3, "0.000");
  checkValid(Amount{"000.001"}, 1, 3, "0.001");
  checkValid(Amount{"0000001"}, 1, 0, "1");
  checkValid(Amount{"1.00000"}, 100000, 5, "1.00000");
}

TEST_F(AmountTest, Invalid) {
  checkInvalid(Amount{"-100"});
  checkInvalid(Amount{"-1.23"});
  checkInvalid(Amount{"0xFF"});
  checkInvalid(Amount{"12.34.56"});
  checkInvalid(Amount{".3456"});
  checkInvalid(Amount{".12.34"});
  checkInvalid(Amount{"0A"});
  checkInvalid(Amount{"1."});
  checkInvalid(Amount{"."});
}


/* boost random/shuffle_order.hpp header file
 *
 * Copyright Jens Maurer 2000-2001
 * Copyright Steven Watanabe 2010
 * 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)
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id$
 *
 */

#ifndef BOOST_RANDOM_SHUFFLE_ORDER_HPP
#define BOOST_RANDOM_SHUFFLE_ORDER_HPP

#include <iostream>
#include <algorithm>     // std::copy
#include <cassert>
#include <boost/config.hpp>
#include <boost/limits.hpp>
#include <boost/static_assert.hpp>
#include <boost/cstdint.hpp>
#include <boost/random/detail/operators.hpp>
#include <boost/random/detail/seed.hpp>
#include <boost/random/detail/signed_unsigned_tools.hpp>
#include <boost/random/linear_congruential.hpp>

#include <boost/random/detail/disable_warnings.hpp>

namespace boost {
namespace random {

/**
 * Instatiations of class template @c shuffle_order_engine model a
 * \pseudo_random_number_generator. It mixes the output
 * of some (usually \linear_congruential_engine)
 * \uniform_random_number_generator to get better statistical properties.
 * The algorithm is described in
 *
 *  @blockquote
 *  "Improving a poor random number generator", Carter Bays
 *  and S.D. Durham, ACM Transactions on Mathematical Software,
 *  Vol 2, No. 1, March 1976, pp. 59-64.
 *  http://doi.acm.org/10.1145/355666.355670
 *  @endblockquote
 *
 * The output of the base generator is buffered in an array of
 * length k. Every output X(n) has a second role: It gives an
 * index into the array where X(n+1) will be retrieved. Used
 * array elements are replaced with fresh output from the base
 * generator.
 *
 * Template parameters are the base generator and the array
 * length k, which should be around 100.
 */
template<class UniformRandomNumberGenerator, std::size_t k>
class shuffle_order_engine
{
public:
    typedef UniformRandomNumberGenerator base_type;
    typedef typename base_type::result_type result_type;

    BOOST_STATIC_CONSTANT(bool, has_fixed_range = false);
    BOOST_STATIC_CONSTANT(std::size_t, buffer_size = k);
    BOOST_STATIC_CONSTANT(std::size_t, table_size = k);

    BOOST_STATIC_ASSERT(std::numeric_limits<result_type>::is_integer);
    BOOST_STATIC_ASSERT(k > 0);

    /**
     * Constructs a @c shuffle_order_engine by invoking the
     * default constructor of the base generator.
     *
     * Complexity: Exactly k+1 invocations of the base generator.
     */
    shuffle_order_engine() : _rng() { init(); }
    /**
     * Constructs a @c shuffle_output_engine by invoking the one-argument
     * constructor of the base generator with the parameter seed.
     *
     * Complexity: Exactly k+1 invocations of the base generator.
     */
    BOOST_RANDOM_DETAIL_ARITHMETIC_CONSTRUCTOR(shuffle_order_engine,
                                               result_type, s)
    { _rng.seed(s); init(); }
    BOOST_RANDOM_DETAIL_SEED_SEQ_CONSTRUCTOR(shuffle_order_engine, SeedSeq, seq)
    { _rng.seed(seq); init(); }
    /**
     * Constructs a @c shuffle_output_engine by using a copy
     * of the provided generator.
     *
     * Precondition: The template argument UniformRandomNumberGenerator
     * shall denote a CopyConstructible type.
     *
     * Complexity: Exactly k+1 invocations of the base generator.
     */
    explicit shuffle_order_engine(const base_type & rng) : _rng(rng) { init(); }

#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
    explicit shuffle_order_engine(base_type&& rng) : _rng(rng) { init(); }
#endif

    template<class It> shuffle_order_engine(It& first, It last)
      : _rng(first, last) { init(); }
    void seed() { _rng.seed(); init(); }
    /**
     * Invokes the one-argument seed method of the base generator
     * with the parameter seed and re-initializes the internal buffer array.
     *
     * Complexity: Exactly k+1 invocations of the base generator.
     */
    BOOST_RANDOM_DETAIL_ARITHMETIC_SEED(shuffle_order_engine,
        result_type, seed_arg)
    { _rng.seed(seed_arg); init(); }
    /**
     * Invokes the one-argument seed method of the base generator
     * with the parameter seq and re-initializes the internal buffer array.
     *
     * Complexity: Exactly k+1 invocations of the base generator.
     */
    BOOST_RANDOM_DETAIL_SEED_SEQ_SEED(shuffle_order_engine, SeedSeq, seq)
    { _rng.seed(seq); init(); }
    template<class It> void seed(It& first, It last)
    { _rng.seed(first, last); init(); }

    const base_type& base() const { return _rng; }

    result_type operator()() {
        // calculating the range every time may seem wasteful.  However, this
        // makes the information locally available for the optimizer.
        typedef typename make_unsigned<result_type>::type base_unsigned;
        const base_unsigned brange =
            detail::subtract<result_type>()((max)(), (min)());
        const base_unsigned off =
            detail::subtract<result_type>()(y, (min)());

        base_unsigned j;
        if(k == 1) {
            j = 0;
        } else if(brange < (std::numeric_limits<base_unsigned>::max)() / k) {
            // try to do it in the native type if we know that it won't
            // overflow
            j = k * off / (brange + 1);
        } else if(brange < (std::numeric_limits<uintmax_t>::max)() / k) {
            // Otherwise try to use uint64_t
            j = static_cast<base_unsigned>(
                static_cast<uintmax_t>(off) * k /
                (static_cast<uintmax_t>(brange) + 1));
        } else {
            boost::uintmax_t divisor =
                static_cast<boost::uintmax_t>(brange) + 1;
            j = static_cast<base_unsigned>(detail::muldiv(off, k, divisor));
        }
        // assert(0 <= j && j < k);
        y = v[j];
        v[j] = _rng();
        return y;
    }

    /** Advances the generator by z steps. */
    void discard(boost::uintmax_t z)
    {
        for(boost::uintmax_t j = 0; j < z; ++j) {
            (*this)();
        }
    }

    /** Fills a range with pseudo-random values. */
    template<class Iter>
    void generate(Iter first, Iter last)
    { detail::generate_from_int(*this, first, last); }

    /** Returns the smallest value that the generator can produce. */
    static result_type min BOOST_PREVENT_MACRO_SUBSTITUTION ()
    { return (base_type::min)(); }
    /** Returns the largest value that the generator can produce. */
    static result_type max BOOST_PREVENT_MACRO_SUBSTITUTION ()
    { return (base_type::max)(); }

    /** Writes a @c shuffle_order_engine to a @c std::ostream. */
    BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, shuffle_order_engine, s)
    {
        os << s._rng;
        for(std::size_t i = 0; i < k; ++i)
            os << ' ' << s.v[i];
        os << ' ' << s.y;
        return os;
    }

    /** Reads a @c shuffle_order_engine from a @c std::istream. */
    BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, shuffle_order_engine, s)
    {
        is >> s._rng;
        for(std::size_t i = 0; i < k; ++i)
            is >> std::ws >> s.v[i];
        is >> std::ws >> s.y;
        return is;
    }

    /** Returns true if the two generators will produce identical sequences. */
    BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(shuffle_order_engine, x, y)
    { return x._rng == y._rng && x.y == y.y && std::equal(x.v, x.v+k, y.v); }
    /** Returns true if the two generators will produce different sequences. */
    BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(shuffle_order_engine)

private:

    /// \cond show_private

    void init()
    {
        // we cannot use std::generate, because it uses pass-by-value for _rng
        for(result_type * p = v; p != v+k; ++p)
            *p = _rng();
        y = _rng();
    }

    /// \endcond

    base_type _rng;
    result_type v[k];
    result_type y;
};

#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION
//  A definition is required even for integral static constants
template<class URNG, std::size_t k>
const bool shuffle_order_engine<URNG, k>::has_fixed_range;
template<class URNG, std::size_t k>
const std::size_t shuffle_order_engine<URNG, k>::table_size;
template<class URNG, std::size_t k>
const std::size_t shuffle_order_engine<URNG, k>::buffer_size;
#endif

/**
 * According to Harry Erwin (private e-mail), the specialization
 * @c kreutzer1986 was suggested in:
 *
 * @blockquote
 * "System Simulation: Programming Styles and Languages (International
 * Computer Science Series)", Wolfgang Kreutzer, Addison-Wesley, December 1986.
 * @endblockquote
 */
typedef shuffle_order_engine<
    linear_congruential_engine<uint32_t, 1366, 150889, 714025>,
    97> kreutzer1986;

/**
 * The specialization @c knuth_b is specified by the C++ standard.
 * It is described in
 *
 * @blockquote
 * "The Art of Computer Programming, Second Edition, Volume 2,
 * Seminumerical Algorithms", Donald Knuth, Addison-Wesley, 1981.
 * @endblockquote
 */
typedef shuffle_order_engine<minstd_rand0, 256> knuth_b;

} // namespace random

using random::kreutzer1986;

} // namespace boost

#include <boost/random/detail/enable_warnings.hpp>

#endif // BOOST_RANDOM_SHUFFLE_OUTPUT_HPP


// Author(s): Wieger Wesselink
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
/// \file find_test.cpp
/// \brief Test for find functions.

#define BOOST_TEST_MODULE find_test
#include "mcrl2/bes/find.h"
#include "mcrl2/bes/parse.h"
#include <algorithm>
#include <boost/test/included/unit_test_framework.hpp>
#include <iterator>
#include <set>
#include <vector>

using namespace mcrl2;
using namespace mcrl2::bes;

template <typename Container, typename OutputIterator>
void my_find_variables(Container const& container, OutputIterator o)
{
  bes::detail::make_find_boolean_variables_traverser<bes::boolean_expression_traverser>(o).apply(container);
}

struct my_compare_variable
{
  const boolean_variable& m_variable;

  my_compare_variable(const boolean_variable& t)
    : m_variable(t)
  {}

  /// \brief Function call operator
  /// \param t A term
  /// \return The function result
  template <typename Variable>
  bool operator()(const Variable& t) const
  {
    return m_variable == t;
  }

};

template <typename Container>
std::set<boolean_variable> my_find_variables(Container const& container)
{
  std::set<boolean_variable> result;
  my_find_variables(container, std::inserter(result, result.end()));
  return result;
}

void test_my_search()
{
  std::string bes1 =
    "pbes              \n"
    "                  \n"
    "nu X1 = X2 && X1; \n"
    "mu X2 = X1 || X2; \n"
    "                  \n"
    "init X1;          \n"
    ;
  boolean_equation_system b;
  std::stringstream from(bes1);
  from >> b;

  std::set<boolean_variable> v;

  BOOST_CHECK(search_boolean_variable(b, boolean_variable("X1")));
  BOOST_CHECK(search_boolean_variable(b, boolean_variable("X2")));
  BOOST_CHECK(!search_boolean_variable(b, boolean_variable("X3")));

  boolean_equation eq = b.equations().front();
  BOOST_CHECK(search_boolean_variable(eq, boolean_variable("X1")));
  BOOST_CHECK(search_boolean_variable(eq, boolean_variable("X2")));
  BOOST_CHECK(!search_boolean_variable(eq, boolean_variable("X3")));

  boolean_expression x = eq.formula();
  BOOST_CHECK(search_boolean_variable(x, boolean_variable("X1")));
  BOOST_CHECK(search_boolean_variable(x, boolean_variable("X2")));
  BOOST_CHECK(!search_boolean_variable(x, boolean_variable("X3")));

}

void test_my_find()
{
  std::string bes1 =
    "pbes              \n"
    "                  \n"
    "nu X1 = X2 && X1; \n"
    "mu X2 = X1 || X2; \n"
    "                  \n"
    "init X1;          \n"
    ;
  boolean_equation_system b;
  std::stringstream from(bes1);
  from >> b;

  std::set<boolean_variable> v;

  //--- find_boolean_variables ---//
  v = my_find_variables(b);
  BOOST_CHECK(v.size() == 2);
  BOOST_CHECK(v.find(boolean_variable("X1")) != v.end());
  BOOST_CHECK(v.find(boolean_variable("X2")) != v.end());

  boolean_equation eq = b.equations().front();
  v = my_find_variables(eq);
  BOOST_CHECK(v.size() == 2);
  BOOST_CHECK(v.find(boolean_variable("X1")) != v.end());
  BOOST_CHECK(v.find(boolean_variable("X2")) != v.end());

  boolean_expression x = eq.formula();
  v = my_find_variables(x);
  BOOST_CHECK(v.size() == 2);
  BOOST_CHECK(v.find(boolean_variable("X1")) != v.end());
  BOOST_CHECK(v.find(boolean_variable("X2")) != v.end());

}

void test_find()
{
  std::string bes1 =
    "pbes              \n"
    "                  \n"
    "nu X1 = X2 && X1; \n"
    "mu X2 = X1 || X2; \n"
    "                  \n"
    "init X1;          \n"
    ;
  boolean_equation_system b;
  std::stringstream from(bes1);
  from >> b;

  std::set<boolean_variable> v;

  //--- find_boolean_variables ---//
  v = find_boolean_variables(b);
  BOOST_CHECK(v.size() == 2);
  BOOST_CHECK(v.find(boolean_variable("X1")) != v.end());
  BOOST_CHECK(v.find(boolean_variable("X2")) != v.end());

  boolean_equation eq = b.equations().front();
  v = find_boolean_variables(eq);
  BOOST_CHECK(v.size() == 2);
  BOOST_CHECK(v.find(boolean_variable("X1")) != v.end());
  BOOST_CHECK(v.find(boolean_variable("X2")) != v.end());

  boolean_expression x = eq.formula();
  v = find_boolean_variables(x);
  BOOST_CHECK(v.size() == 2);
  BOOST_CHECK(v.find(boolean_variable("X1")) != v.end());
  BOOST_CHECK(v.find(boolean_variable("X2")) != v.end());

}

void test_bnd_occ()
{
  std::string bes1 =
    "pbes              \n"
    "                  \n"
    "nu X1 = X2 && X1; \n"
    "mu X2 = X1 || X2; \n"
    "                  \n"
    "init X1;          \n"
    ;
  boolean_equation_system b;
  std::stringstream from(bes1);
  from >> b;

  std::set<boolean_variable> bnd = b.binding_variables();
  BOOST_CHECK(bnd.size() == 2);
  BOOST_CHECK(bnd.find(boolean_variable("X1")) != bnd.end());
  BOOST_CHECK(bnd.find(boolean_variable("X2")) != bnd.end());

  std::set<boolean_variable> occ = b.occurring_variables();
  BOOST_CHECK(occ.size() == 2);
  BOOST_CHECK(occ.find(boolean_variable("X1")) != occ.end());
  BOOST_CHECK(occ.find(boolean_variable("X2")) != occ.end());

}

BOOST_AUTO_TEST_CASE(test_main)
{
  test_my_find();
  test_find();
  test_my_search();
  test_bnd_occ();
}


// (C) Copyright Jeremy Siek 2000-2004.
// 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 <boost/config.hpp>
#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>
#include <boost/iterator/counting_iterator.hpp>
#include <boost/iterator/indirect_iterator.hpp>
#include <boost/cstdlib.hpp>

int main(int, char*[])
{
  // Example of using counting_iterator
  std::cout << "counting from 0 to 4:" << std::endl;
  boost::counting_iterator<int> first(0), last(4);
  std::copy(first, last, std::ostream_iterator<int>(std::cout, " "));
  std::cout << std::endl;

  // Example of using counting iterator to create an array of pointers.
  int N = 7;
  std::vector<int> numbers;
  typedef std::vector<int>::iterator n_iter;
  // Fill "numbers" array with [0,N)
  std::copy(
      boost::counting_iterator<int>(0)
      , boost::counting_iterator<int>(N)
      , std::back_inserter(numbers));

  std::vector<std::vector<int>::iterator> pointers;

  // Use counting iterator to fill in the array of pointers.
  // causes an ICE with MSVC6
  std::copy(boost::make_counting_iterator(numbers.begin()),
            boost::make_counting_iterator(numbers.end()),
            std::back_inserter(pointers));

  // Use indirect iterator to print out numbers by accessing
  // them through the array of pointers.
  std::cout << "indirectly printing out the numbers from 0 to "
            << N << std::endl;
  std::copy(boost::make_indirect_iterator(pointers.begin()),
            boost::make_indirect_iterator(pointers.end()),
            std::ostream_iterator<int>(std::cout, " "));
  std::cout << std::endl;

  return boost::exit_success;
}


/*    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):       Mathieu Carrière
 *
 *    Copyright (C) 2017 Inria
 *
 *    Modification(s):
 *      - YYYY/MM Author: Description of the modification
 */

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE "graph_induced_complex"

#include <boost/test/unit_test.hpp>

#include <limits>
#include <string>
#include <vector>

#include <gudhi/GIC.h>
#include <gudhi/distance_functions.h>
#include <gudhi/reader_utils.h>

BOOST_AUTO_TEST_CASE(check_nerve) {
  using Point = std::vector<float>;
  Gudhi::cover_complex::Cover_complex<Point> N;
  N.set_type("Nerve");
  std::string cloud_file_name("data/cloud");
  N.read_point_cloud(cloud_file_name);
  N.set_color_from_coordinate();
  std::string graph_file_name("data/graph");
  N.set_graph_from_file(graph_file_name);
  std::string cover_file_name("data/cover");
  N.set_cover_from_file(cover_file_name);
  N.find_simplices();
  Gudhi::Simplex_tree<> stree;
  N.create_complex(stree);

  BOOST_CHECK(stree.num_vertices() == 3);
  BOOST_CHECK((stree.num_simplices() - stree.num_vertices()) == 0);
  BOOST_CHECK(stree.dimension() == 0);
}

BOOST_AUTO_TEST_CASE(check_GIC) {
  using Point = std::vector<float>;
  Gudhi::cover_complex::Cover_complex<Point> GIC;
  GIC.set_type("GIC");
  std::string cloud_file_name("data/cloud");
  GIC.read_point_cloud(cloud_file_name);
  GIC.set_color_from_coordinate();
  std::string graph_file_name("data/graph");
  GIC.set_graph_from_file(graph_file_name);
  std::string cover_file_name("data/cover");
  GIC.set_cover_from_file(cover_file_name);
  GIC.find_simplices();
  Gudhi::Simplex_tree<> stree;
  GIC.create_complex(stree);

  BOOST_CHECK(stree.num_vertices() == 3);
  BOOST_CHECK((stree.num_simplices() - stree.num_vertices()) == 4);
  BOOST_CHECK(stree.dimension() == 2);
}

BOOST_AUTO_TEST_CASE(check_voronoiGIC) {
  using Point = std::vector<float>;
  Gudhi::cover_complex::Cover_complex<Point> GIC;
  GIC.set_type("GIC");
  std::string cloud_file_name("data/cloud");
  GIC.read_point_cloud(cloud_file_name);
  GIC.set_color_from_coordinate();
  std::string graph_file_name("data/graph");
  GIC.set_graph_from_file(graph_file_name);
  GIC.set_cover_from_Voronoi(Gudhi::Euclidean_distance(), 2);
  GIC.find_simplices();
  Gudhi::Simplex_tree<> stree;
  GIC.create_complex(stree);

  BOOST_CHECK(stree.num_vertices() == 2);
  BOOST_CHECK((stree.num_simplices() - stree.num_vertices()) == 1);
  BOOST_CHECK(stree.dimension() == 1);
}


///////////////////////////////////////////////////////////////////////////////
// statistics::model::algorithm::include.hpp                                 //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_MODEL_ALGORITHM_INCLUDE_HPP_ER_2009
#define BOOST_STATISTICS_MODEL_ALGORITHM_INCLUDE_HPP_ER_2009

#include <boost/statistics/model/algorithm/log_likelihood.hpp>
#include <boost/statistics/model/algorithm/log_likelihoods.hpp>
#include <boost/statistics/model/algorithm/log_posteriors.hpp>
#include <boost/statistics/model/algorithm/log_posteriors2.hpp>

#endif


//////////////////////////////////////////////////////////////////////////////
// distribution::common::reference::include.hpp                             //
//                                                                          //
//  (C) Copyright 2009 Erwann Rogard                                        //
//  Use, modification and distribution are subject to the                   //
//  Boost Software License, Version 1.0. (See accompanying file             //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)        //
//////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DISTRIBUTION_COMMON_DISTRIBUTIONS_REFERENCE_INCLUDE_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_COMMON_DISTRIBUTIONS_REFERENCE_INCLUDE_HPP_ER_2009

#include <boost/statistics/detail/distribution_common/distributions/reference/cdf.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/derivative_log_unnormalized_pdf.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/description.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/log_unnormalized_pdf.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/normalizing_constant.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/mean.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/pdf.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/os.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/random.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/variance.hpp>
#include <boost/statistics/detail/distribution_common/distributions/reference/wrapper.hpp>

#endif