//  (C) Copyright John Maddock 2006.
//  (C) Copyright Jeremy William Murphy 2015.

//  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_MATH_TOOLS_POLYNOMIAL_HPP
#define BOOST_MATH_TOOLS_POLYNOMIAL_HPP

#ifdef _MSC_VER
#pragma once
#endif

#include <boost/assert.hpp>
#include <boost/config.hpp>
#ifdef BOOST_NO_CXX11_LAMBDAS
#include <boost/lambda/lambda.hpp>
#endif
#include <boost/math/tools/rational.hpp>
#include <boost/math/tools/real_cast.hpp>
#include <boost/math/policies/error_handling.hpp>
#include <boost/math/special_functions/binomial.hpp>
#include <boost/core/enable_if.hpp>

#include <vector>
#include <ostream>
#include <algorithm>
#ifndef BOOST_NO_CXX11_HDR_INITIALIZER_LIST
#include <initializer_list>
#endif

namespace boost{ namespace math{ namespace tools{

template <class T>
T chebyshev_coefficient(unsigned n, unsigned m)
{
   BOOST_MATH_STD_USING
   if(m > n)
      return 0;
   if((n & 1) != (m & 1))
      return 0;
   if(n == 0)
      return 1;
   T result = T(n) / 2;
   unsigned r = n - m;
   r /= 2;

   BOOST_ASSERT(n - 2 * r == m);

   if(r & 1)
      result = -result;
   result /= n - r;
   result *= boost::math::binomial_coefficient<T>(n - r, r);
   result *= ldexp(1.0f, m);
   return result;
}

template <class Seq>
Seq polynomial_to_chebyshev(const Seq& s)
{
   // Converts a Polynomial into Chebyshev form:
   typedef typename Seq::value_type value_type;
   typedef typename Seq::difference_type difference_type;
   Seq result(s);
   difference_type order = s.size() - 1;
   difference_type even_order = order & 1 ? order - 1 : order;
   difference_type odd_order = order & 1 ? order : order - 1;

   for(difference_type i = even_order; i >= 0; i -= 2)
   {
      value_type val = s[i];
      for(difference_type k = even_order; k > i; k -= 2)
      {
         val -= result[k] * chebyshev_coefficient<value_type>(static_cast<unsigned>(k), static_cast<unsigned>(i));
      }
      val /= chebyshev_coefficient<value_type>(static_cast<unsigned>(i), static_cast<unsigned>(i));
      result[i] = val;
   }
   result[0] *= 2;

   for(difference_type i = odd_order; i >= 0; i -= 2)
   {
      value_type val = s[i];
      for(difference_type k = odd_order; k > i; k -= 2)
      {
         val -= result[k] * chebyshev_coefficient<value_type>(static_cast<unsigned>(k), static_cast<unsigned>(i));
      }
      val /= chebyshev_coefficient<value_type>(static_cast<unsigned>(i), static_cast<unsigned>(i));
      result[i] = val;
   }
   return result;
}

template <class Seq, class T>
T evaluate_chebyshev(const Seq& a, const T& x)
{
   // Clenshaw's formula:
   typedef typename Seq::difference_type difference_type;
   T yk2 = 0;
   T yk1 = 0;
   T yk = 0;
   for(difference_type i = a.size() - 1; i >= 1; --i)
   {
      yk2 = yk1;
      yk1 = yk;
      yk = 2 * x * yk1 - yk2 + a[i];
   }
   return a[0] / 2 + yk * x - yk1;
}

template <typename T>
class polynomial;

namespace detail {

/**
* Knuth, The Art of Computer Programming: Volume 2, Third edition, 1998
* Chapter 4.6.1, Algorithm D: Division of polynomials over a field.
*
* @tparam  T   Coefficient type, must be not be an integer.
*
* Template-parameter T actually must be a field but we don't currently have that
* subtlety of distinction.
*/
template <typename T, typename N>
BOOST_DEDUCED_TYPENAME disable_if_c<std::numeric_limits<T>::is_integer, void >::type
division_impl(polynomial<T> &q, polynomial<T> &u, const polynomial<T>& v, N n, N k)
{
    q[k] = u[n + k] / v[n];
    for (N j = n + k; j > k;)
    {
        j--;
        u[j] -= q[k] * v[j - k];
    }
}

template <class T, class N>
T integer_power(T t, N n)
{
   switch(n)
   {
   case 0:
      return static_cast<T>(1u);
   case 1:
      return t;
   case 2:
      return t * t;
   case 3:
      return t * t * t;
   }
   T result = integer_power(t, n / 2);
   result *= result;
   if(n & 1)
      result *= t;
   return result;
}

/**
* Knuth, The Art of Computer Programming: Volume 2, Third edition, 1998
* Chapter 4.6.1, Algorithm R: Pseudo-division of polynomials.
*
* @tparam  T   Coefficient type, must be an integer.
*
* Template-parameter T actually must be a unique factorization domain but we
* don't currently have that subtlety of distinction.
*/
template <typename T, typename N>
BOOST_DEDUCED_TYPENAME enable_if_c<std::numeric_limits<T>::is_integer, void >::type
division_impl(polynomial<T> &q, polynomial<T> &u, const polynomial<T>& v, N n, N k)
{
    q[k] = u[n + k] * integer_power(v[n], k);
    for (N j = n + k; j > 0;)
    {
        j--;
        u[j] = v[n] * u[j] - (j < k ? T(0) : u[n + k] * v[j - k]);
    }
}

/**
 * Knuth, The Art of Computer Programming: Volume 2, Third edition, 1998
 * Chapter 4.6.1, Algorithm D and R: Main loop.
 *
 * @param   u   Dividend.
 * @param   v   Divisor.
 */
template <typename T>
std::pair< polynomial<T>, polynomial<T> >
division(polynomial<T> u, const polynomial<T>& v)
{
    BOOST_ASSERT(v.size() <= u.size());
    BOOST_ASSERT(v);
    BOOST_ASSERT(u);

    typedef typename polynomial<T>::size_type N;

    N const m = u.size() - 1, n = v.size() - 1;
    N k = m - n;
    polynomial<T> q;
    q.data().resize(m - n + 1);

    do
    {
        division_impl(q, u, v, n, k);
    }
    while (k-- != 0);
    u.data().resize(n);
    u.normalize(); // Occasionally, the remainder is zeroes.
    return std::make_pair(q, u);
}

//
// These structures are the same as the void specializations of the functors of the same name
// in the std lib from C++14 onwards:
//
struct negate
{
   template <class T>
   T operator()(T const &x) const
   {
      return -x;
   }
};

struct plus
{
   template <class T, class U>
   T operator()(T const &x, U const& y) const
   {
      return x + y;
   }
};

struct minus
{
   template <class T, class U>
   T operator()(T const &x, U const& y) const
   {
      return x - y;
   }
};

} // namespace detail

/**
 * Returns the zero element for multiplication of polynomials.
 */
template <class T>
polynomial<T> zero_element(std::multiplies< polynomial<T> >)
{
    return polynomial<T>();
}

template <class T>
polynomial<T> identity_element(std::multiplies< polynomial<T> >)
{
    return polynomial<T>(T(1));
}

/* Calculates a / b and a % b, returning the pair (quotient, remainder) together
 * because the same amount of computation yields both.
 * This function is not defined for division by zero: user beware.
 */
template <typename T>
std::pair< polynomial<T>, polynomial<T> >
quotient_remainder(const polynomial<T>& dividend, const polynomial<T>& divisor)
{
    BOOST_ASSERT(divisor);
    if (dividend.size() < divisor.size())
        return std::make_pair(polynomial<T>(), dividend);
    return detail::division(dividend, divisor);
}

template <class T>
class polynomial
{
public:
   // typedefs:
   typedef typename std::vector<T>::value_type value_type;
   typedef typename std::vector<T>::size_type size_type;

   // construct:
   polynomial(){}

   template <class U>
   polynomial(const U* data, unsigned order)
      : m_data(data, data + order + 1)
   {
       normalize();
   }

   template <class I>
   polynomial(I first, I last)
   : m_data(first, last)
   {
       normalize();
   }

   template <class U>
   explicit polynomial(const U& point)
   {
       if (point != U(0))
          m_data.push_back(point);
   }

#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   // move:
   polynomial(polynomial&& p) BOOST_NOEXCEPT
      : m_data(std::move(p.m_data)) { }
#endif

   // copy:
   polynomial(const polynomial& p)
      : m_data(p.m_data) { }

   template <class U>
   polynomial(const polynomial<U>& p)
   {
      for(unsigned i = 0; i < p.size(); ++i)
      {
         m_data.push_back(boost::math::tools::real_cast<T>(p[i]));
      }
   }

#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !BOOST_WORKAROUND(BOOST_GCC_VERSION, < 40500)
    polynomial(std::initializer_list<T> l) : polynomial(std::begin(l), std::end(l))
    {
    }

    polynomial&
    operator=(std::initializer_list<T> l)
    {
        m_data.assign(std::begin(l), std::end(l));
        normalize();
        return *this;
    }
#endif

   // access:
   size_type size() const { return m_data.size(); }
   size_type degree() const
   {
       if (size() == 0)
           throw std::logic_error("degree() is undefined for the zero polynomial.");
       return m_data.size() - 1;
   }
   value_type& operator[](size_type i)
   {
      return m_data[i];
   }
   const value_type& operator[](size_type i) const
   {
      return m_data[i];
   }
   T evaluate(T z) const
   {
      return m_data.size() > 0 ? boost::math::tools::evaluate_polynomial(&m_data[0], z, m_data.size()) : 0;
   }
   std::vector<T> chebyshev() const
   {
      return polynomial_to_chebyshev(m_data);
   }

   std::vector<T> const& data() const
   {
       return m_data;
   }

   std::vector<T> & data()
   {
       return m_data;
   }

   // operators:
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
   polynomial& operator =(polynomial&& p) BOOST_NOEXCEPT
   {
       m_data = std::move(p.m_data);
       return *this;
   }
#endif
   polynomial& operator =(const polynomial& p)
   {
       m_data = p.m_data;
       return *this;
   }

   template <class U>
   typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial&>::type operator +=(const U& value)
   {
       addition(value);
       normalize();
       return *this;
   }

   template <class U>
   typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial&>::type operator -=(const U& value)
   {
       subtraction(value);
       normalize();
       return *this;
   }

   template <class U>
   typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial&>::type operator *=(const U& value)
   {
      multiplication(value);
      normalize();
      return *this;
   }

   template <class U>
   typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial&>::type operator /=(const U& value)
   {
       division(value);
       normalize();
       return *this;
   }

   template <class U>
   typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial&>::type operator %=(const U& /*value*/)
   {
       // We can always divide by a scalar, so there is no remainder:
       this->set_zero();
       return *this;
   }

   template <class U>
   polynomial& operator +=(const polynomial<U>& value)
   {
      addition(value);
      normalize();
      return *this;
   }

   template <class U>
   polynomial& operator -=(const polynomial<U>& value)
   {
       subtraction(value);
       normalize();
       return *this;
   }

   template <typename U, typename V>
   void multiply(const polynomial<U>& a, const polynomial<V>& b) {
       if (!a || !b)
       {
           this->set_zero();
           return;
       }
       std::vector<T> prod(a.size() + b.size() - 1, T(0));
       for (unsigned i = 0; i < a.size(); ++i)
           for (unsigned j = 0; j < b.size(); ++j)
               prod[i+j] += a.m_data[i] * b.m_data[j];
       m_data.swap(prod);
   }

   template <class U>
   polynomial& operator *=(const polynomial<U>& value)
   {
      this->multiply(*this, value);
      return *this;
   }

   template <typename U>
   polynomial& operator /=(const polynomial<U>& value)
   {
       *this = quotient_remainder(*this, value).first;
       return *this;
   }

   template <typename U>
   polynomial& operator %=(const polynomial<U>& value)
   {
       *this = quotient_remainder(*this, value).second;
       return *this;
   }

   template <typename U>
   polynomial& operator >>=(U const &n)
   {
       BOOST_ASSERT(n <= m_data.size());
       m_data.erase(m_data.begin(), m_data.begin() + n);
       return *this;
   }

   template <typename U>
   polynomial& operator <<=(U const &n)
   {
       m_data.insert(m_data.begin(), n, static_cast<T>(0));
       normalize();
       return *this;
   }

   // Convenient and efficient query for zero.
   bool is_zero() const
   {
       return m_data.empty();
   }

   // Conversion to bool.
#ifdef BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS
   typedef bool (polynomial::*unmentionable_type)() const;

   BOOST_FORCEINLINE operator unmentionable_type() const
   {
       return is_zero() ? false : &polynomial::is_zero;
   }
#else
   BOOST_FORCEINLINE explicit operator bool() const
   {
       return !m_data.empty();
   }
#endif

   // Fast way to set a polynomial to zero.
   void set_zero()
   {
       m_data.clear();
   }

    /** Remove zero coefficients 'from the top', that is for which there are no
    *        non-zero coefficients of higher degree. */
   void normalize()
   {
#ifndef BOOST_NO_CXX11_LAMBDAS
      m_data.erase(std::find_if(m_data.rbegin(), m_data.rend(), [](const T& x)->bool { return x != 0; }).base(), m_data.end());
#else
       using namespace boost::lambda;
       m_data.erase(std::find_if(m_data.rbegin(), m_data.rend(), _1 != T(0)).base(), m_data.end());
#endif
   }

private:
    template <class U, class R>
    polynomial& addition(const U& value, R op)
    {
        if(m_data.size() == 0)
            m_data.resize(1, 0);
        m_data[0] = op(m_data[0], value);
        return *this;
    }

    template <class U>
    polynomial& addition(const U& value)
    {
        return addition(value, detail::plus());
    }

    template <class U>
    polynomial& subtraction(const U& value)
    {
        return addition(value, detail::minus());
    }

    template <class U, class R>
    polynomial& addition(const polynomial<U>& value, R op)
    {
        if (m_data.size() < value.size())
            m_data.resize(value.size(), 0);
        for(size_type i = 0; i < value.size(); ++i)
            m_data[i] = op(m_data[i], value[i]);
        return *this;
    }

    template <class U>
    polynomial& addition(const polynomial<U>& value)
    {
        return addition(value, detail::plus());
    }

    template <class U>
    polynomial& subtraction(const polynomial<U>& value)
    {
        return addition(value, detail::minus());
    }

    template <class U>
    polynomial& multiplication(const U& value)
    {
#ifndef BOOST_NO_CXX11_LAMBDAS
       std::transform(m_data.begin(), m_data.end(), m_data.begin(), [&](const T& x)->T { return x * value; });
#else
        using namespace boost::lambda;
        std::transform(m_data.begin(), m_data.end(), m_data.begin(), ret<T>(_1 * value));
#endif
        return *this;
    }

    template <class U>
    polynomial& division(const U& value)
    {
#ifndef BOOST_NO_CXX11_LAMBDAS
       std::transform(m_data.begin(), m_data.end(), m_data.begin(), [&](const T& x)->T { return x / value; });
#else
        using namespace boost::lambda;
        std::transform(m_data.begin(), m_data.end(), m_data.begin(), ret<T>(_1 / value));
#endif
        return *this;
    }

    std::vector<T> m_data;
};

template <class T>
inline polynomial<T> operator + (const polynomial<T>& a, const polynomial<T>& b)
{
   polynomial<T> result(a);
   result += b;
   return result;
}
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
template <class T>
inline polynomial<T> operator + (polynomial<T>&& a, const polynomial<T>& b)
{
   a += b;
   return a;
}
template <class T>
inline polynomial<T> operator + (const polynomial<T>& a, polynomial<T>&& b)
{
   b += a;
   return b;
}
template <class T>
inline polynomial<T> operator + (polynomial<T>&& a, polynomial<T>&& b)
{
   a += b;
   return a;
}
#endif

template <class T>
inline polynomial<T> operator - (const polynomial<T>& a, const polynomial<T>& b)
{
   polynomial<T> result(a);
   result -= b;
   return result;
}
#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
template <class T>
inline polynomial<T> operator - (polynomial<T>&& a, const polynomial<T>& b)
{
   a -= b;
   return a;
}
template <class T>
inline polynomial<T> operator - (const polynomial<T>& a, polynomial<T>&& b)
{
   b -= a;
   return -b;
}
template <class T>
inline polynomial<T> operator - (polynomial<T>&& a, polynomial<T>&& b)
{
   a -= b;
   return a;
}
#endif

template <class T>
inline polynomial<T> operator * (const polynomial<T>& a, const polynomial<T>& b)
{
   polynomial<T> result;
   result.multiply(a, b);
   return result;
}

template <class T>
inline polynomial<T> operator / (const polynomial<T>& a, const polynomial<T>& b)
{
   return quotient_remainder(a, b).first;
}

template <class T>
inline polynomial<T> operator % (const polynomial<T>& a, const polynomial<T>& b)
{
   return quotient_remainder(a, b).second;
}

template <class T, class U>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator + (polynomial<T> a, const U& b)
{
   a += b;
   return a;
}

template <class T, class U>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator - (polynomial<T> a, const U& b)
{
   a -= b;
   return a;
}

template <class T, class U>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator * (polynomial<T> a, const U& b)
{
   a *= b;
   return a;
}

template <class T, class U>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator / (polynomial<T> a, const U& b)
{
   a /= b;
   return a;
}

template <class T, class U>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator % (const polynomial<T>&, const U&)
{
   // Since we can always divide by a scalar, result is always an empty polynomial:
   return polynomial<T>();
}

template <class U, class T>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator + (const U& a, polynomial<T> b)
{
   b += a;
   return b;
}

template <class U, class T>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator - (const U& a, polynomial<T> b)
{
   b -= a;
   return -b;
}

template <class U, class T>
inline typename boost::enable_if_c<boost::is_constructible<T, U>::value, polynomial<T> >::type operator * (const U& a, polynomial<T> b)
{
   b *= a;
   return b;
}

template <class T>
bool operator == (const polynomial<T> &a, const polynomial<T> &b)
{
    return a.data() == b.data();
}

template <class T>
bool operator != (const polynomial<T> &a, const polynomial<T> &b)
{
    return a.data() != b.data();
}

template <typename T, typename U>
polynomial<T> operator >> (polynomial<T> a, const U& b)
{
    a >>= b;
    return a;
}

template <typename T, typename U>
polynomial<T> operator << (polynomial<T> a, const U& b)
{
    a <<= b;
    return a;
}

// Unary minus (negate).
template <class T>
polynomial<T> operator - (polynomial<T> a)
{
    std::transform(a.data().begin(), a.data().end(), a.data().begin(), detail::negate());
    return a;
}

template <class T>
bool odd(polynomial<T> const &a)
{
    return a.size() > 0 && a[0] != static_cast<T>(0);
}

template <class T>
bool even(polynomial<T> const &a)
{
    return !odd(a);
}

template <class T>
polynomial<T> pow(polynomial<T> base, int exp)
{
    if (exp < 0)
        return policies::raise_domain_error(
                "boost::math::tools::pow<%1%>",
                "Negative powers are not supported for polynomials.",
                base, policies::policy<>());
        // if the policy is ignore_error or errno_on_error, raise_domain_error
        // will return std::numeric_limits<polynomial<T>>::quiet_NaN(), which
        // defaults to polynomial<T>(), which is the zero polynomial
    polynomial<T> result(T(1));
    if (exp & 1)
        result = base;
    /* "Exponentiation by squaring" */
    while (exp >>= 1)
    {
        base *= base;
        if (exp & 1)
            result *= base;
    }
    return result;
}

template <class charT, class traits, class T>
inline std::basic_ostream<charT, traits>& operator << (std::basic_ostream<charT, traits>& os, const polynomial<T>& poly)
{
   os << "{ ";
   for(unsigned i = 0; i < poly.size(); ++i)
   {
      if(i) os << ", ";
      os << poly[i];
   }
   os << " }";
   return os;
}

} // namespace tools
} // namespace math
} // namespace boost

//
// Polynomial specific overload of gcd algorithm:
//
#include <boost/math/tools/polynomial_gcd.hpp>

#endif // BOOST_MATH_TOOLS_POLYNOMIAL_HPP


/*    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):       Vincent Rouvreau
 *
 *    Copyright (C) 2017 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/Points_off_io.h>

#include <CGAL/Epick_d.h>
#include <CGAL/Min_sphere_of_spheres_d.h>
#include <CGAL/Min_sphere_of_points_d_traits_d.h>

#include <boost/program_options.hpp>

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

// -------------------------------------------------------------------------------
// cech_complex_cgal_mini_sphere_3d is an example of each step that is required to
// build a Cech over a Simplex_tree. Please refer to cech_persistence to see
// how to do the same thing with the Cech_complex wrapper for less detailed
// steps.
// -------------------------------------------------------------------------------

// Types definition
using Simplex_tree = Gudhi::Simplex_tree<>;
using Vertex_handle = Simplex_tree::Vertex_handle;
using Simplex_handle = Simplex_tree::Simplex_handle;
using Filtration_value = Simplex_tree::Filtration_value;
using Siblings = Simplex_tree::Siblings;
using Graph_t = boost::adjacency_list<boost::vecS, boost::vecS, boost::directedS,
                                      boost::property<Gudhi::vertex_filtration_t, Filtration_value>,
                                      boost::property<Gudhi::edge_filtration_t, Filtration_value> >;
using Edge_t = std::pair<Vertex_handle, Vertex_handle>;

using Kernel = CGAL::Epick_d<CGAL::Dimension_tag<3> >;
using Point = Kernel::Point_d;
using Traits = CGAL::Min_sphere_of_points_d_traits_d<Kernel, Filtration_value, 3>;
using Min_sphere = CGAL::Min_sphere_of_spheres_d<Traits>;

using Points_off_reader = Gudhi::Points_off_reader<Point>;

class Cech_blocker {
 public:
  bool operator()(Simplex_handle sh) {
    std::vector<Point> points;
#if DEBUG_TRACES
    std::clog << "Cech_blocker on [";
#endif  // DEBUG_TRACES
    for (auto vertex : simplex_tree_.simplex_vertex_range(sh)) {
      points.push_back(point_cloud_[vertex]);
#if DEBUG_TRACES
      std::clog << vertex << ", ";
#endif  // DEBUG_TRACES
    }
    Min_sphere ms(points.begin(), points.end());
    Filtration_value radius = ms.radius();
#if DEBUG_TRACES
    std::clog << "] - radius = " << radius << " - returns " << (radius > threshold_) << std::endl;
#endif  // DEBUG_TRACES
    simplex_tree_.assign_filtration(sh, radius);
    return (radius > threshold_);
  }
  Cech_blocker(Simplex_tree& simplex_tree, Filtration_value threshold, const std::vector<Point>& point_cloud)
      : simplex_tree_(simplex_tree), threshold_(threshold), point_cloud_(point_cloud) {}

 private:
  Simplex_tree simplex_tree_;
  Filtration_value threshold_;
  std::vector<Point> point_cloud_;
};

template <typename InputPointRange>
Graph_t compute_proximity_graph(InputPointRange& points, Filtration_value threshold);

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

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

  program_options(argc, argv, off_file_points, threshold, dim_max);

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

  // Compute the proximity graph of the points
  Graph_t prox_graph = compute_proximity_graph(off_reader.get_point_cloud(), threshold);

  // Min_sphere sph1(off_reader.get_point_cloud()[0], off_reader.get_point_cloud()[1], off_reader.get_point_cloud()[2]);
  // 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_with_blockers(dim_max, Cech_blocker(st, threshold, off_reader.get_point_cloud()));

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

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

#if DEBUG_TRACES
  std::clog << "********************************************************************\n";
  // Display the Simplex_tree - Can not be done in the middle of 2 inserts
  std::clog << "* The complex contains " << st.num_simplices() << " simplices - dimension=" << st.dimension() << "\n";
  std::clog << "* Iterator on Simplices in the filtration, with [filtration value]:\n";
  for (auto f_simplex : st.filtration_simplex_range()) {
    std::clog << "   "
              << "[" << st.filtration(f_simplex) << "] ";
    for (auto vertex : st.simplex_vertex_range(f_simplex)) {
      std::clog << static_cast<int>(vertex) << " ";
    }
    std::clog << std::endl;
  }
#endif  // DEBUG_TRACES
  return 0;
}

void program_options(int argc, char* argv[], std::string& off_file_points, Filtration_value& threshold, int& dim_max) {
  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 3d point set.\n");

  po::options_description visible("Allowed options", 100);
  visible.add_options()("help,h", "produce help message")(
      "max-edge-length,r",
      po::value<Filtration_value>(&threshold)->default_value(std::numeric_limits<Filtration_value>::infinity()),
      "Maximal length of an edge for the Cech complex construction.")(
      "cpx-dimension,d", po::value<int>(&dim_max)->default_value(1),
      "Maximal dimension of the Cech complex we want to compute.");

  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::clog << std::endl;
    std::clog << "Construct a Cech complex defined on a set of input points.\n \n";

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

/** Output the proximity graph of the points.
 *
 * If points contains n elements, the proximity graph is the graph
 * with n vertices, and an edge [u,v] iff the distance function between
 * points u and v is smaller than threshold.
 *
 * The type PointCloud furnishes .begin() and .end() methods, that return
 * iterators with value_type Point.
 */
template <typename InputPointRange>
Graph_t compute_proximity_graph(InputPointRange& points, Filtration_value threshold) {
  std::vector<Edge_t> edges;
  std::vector<Filtration_value> edges_fil;

  Kernel k;
  Vertex_handle idx_u, idx_v;
  Filtration_value fil;
  idx_u = 0;
  for (auto it_u = points.begin(); it_u != points.end(); ++it_u) {
    idx_v = idx_u + 1;
    for (auto it_v = it_u + 1; it_v != points.end(); ++it_v, ++idx_v) {
      fil = k.squared_distance_d_object()(*it_u, *it_v);
      // For Cech Complex, threshold is a radius (distance /2)
      fil = std::sqrt(fil) / 2.;
      if (fil <= threshold) {
        edges.emplace_back(idx_u, idx_v);
        edges_fil.push_back(fil);
      }
    }
    ++idx_u;
  }

  Graph_t skel_graph(edges.begin(), edges.end(), edges_fil.begin(),
                     idx_u);  // number of points labeled from 0 to idx_u-1

  auto vertex_prop = boost::get(Gudhi::vertex_filtration_t(), skel_graph);

  boost::graph_traits<Graph_t>::vertex_iterator vi, vi_end;
  for (std::tie(vi, vi_end) = boost::vertices(skel_graph); vi != vi_end; ++vi) {
    boost::put(vertex_prop, *vi, 0.);
  }

  return skel_graph;
}


/*
 *  shortest_path.cpp
 *  consensusmap
 *
 *  Created by yonghui on 11/9/07.
 *  Copyright 2007 __MyCompanyName__. All rights reserved.
 *
 */

#include "shortest_path.h"
#include <limits>
#include <iostream>
#include <stdio.h>
#include <cmath>
#include <map>
#include <cstdlib>
#include <boost/graph/strong_components.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/adjacency_list.hpp>
using namespace boost;

namespace consensus_map {
    CG::~CG(){
        if (g_ != NULL) {
            assert(n_vertex_ != 0);
            for (int ii = 0; ii < n_vertex_; ii++) {
                delete[] g_[ii].edges;
            }
            delete[] g_;
            g_ = NULL;
            n_vertex_ = 0;
        }
    } // end of CG::~CG()
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    void CG::solve_lp(double epsilon){
        bool no_delete = valid_solution();
        if (no_delete) { // if there is no need to delete nodes
            cout << "CG::solve_lp() no need to delete nodes" << endl;
            return;
        }
        cout << "problem size: " << n_vertex_ << endl;
        double max_weight = 0;
        for (int ii = 0; ii < n_vertex_; ii++) {
            if (g_[ii].weight > max_weight) {
                max_weight = g_[ii].weight;
            }
        }
        // find the initial solution, which is the longest path in terms of the weights assigned
        for (int ii = 0; ii < n_vertex_; ii++) {
            g_[ii].dual_val = 1 + max_weight - g_[ii].weight;
        }
        double dummy1;
        vector<int> initial_path;
        find_shortest_path(dummy1, initial_path);
        for (vector<int>::iterator iter1 = initial_path.begin(); iter1 != initial_path.end(); ++iter1) {
            g_[*iter1].total_flow = 1;
        }

        double lambda = compute_lambda();
        double alpha = (4 / (lambda * epsilon)) * log(2 * n_vertex_ / epsilon);
        double delta = epsilon / (4 * alpha);

        double crt_left = 0.0;
        double crt_lambda = lambda;
        int iteration = -1;

        while (true) {
            iteration++;
            compute_dual_vals(alpha);

            double middle = 0; // middle corresponds to \vec{z}^tA\vec{y}
            for (int ii = 0; ii < n_vertex_; ii++) {
                middle = middle + g_[ii].dual_val * g_[ii].total_flow;
            }

            double right = 0; // right corresponds to \lambda\vec{z}^t\vec{b}
            for (int ii = 0; ii < n_vertex_; ii++) {
                right = right + crt_lambda * g_[ii].dual_val * g_[ii].weight;
            }

            vector<int> opt_path;
            double left; // left corresponds to \vec{z}^tA\vec{y}
            find_shortest_path(left, opt_path);
            crt_left = left;

            if (iteration % 1000 == 0) {
                cout << "iteration " << iteration
                     << "  left:" << left
                     << "  middle:" << middle
                     << "  right:" << right
                     << "  sol:" << 1 / crt_lambda
                     << endl;
            }

            // check if the following condition is met or not
            // \vec{z}^tA\vec{y} - C(\vec{z}) \le \epsilon(\vec{z}^tA\vec{y} + \lambda\vec{z}^t\vec{b})
            if ((middle - left) < epsilon * (middle + right) ) {
                cout << "converged!" << endl;
                cout << "iteration " << iteration
                     << "  left:" << left
                     << "  middle:" << middle
                     << "  right:" << right
                     << "  sol:" << 1 / crt_lambda
                     << endl;
                break;
            } else {
                update_solution(delta, opt_path);
                crt_lambda = compute_lambda();
                if (crt_lambda < 0.5 * lambda) {
                    lambda = crt_lambda;
                    alpha = 4 / (lambda * epsilon) * log(2 * n_vertex_ / epsilon);
                    delta = epsilon / (4 * alpha);
                }
            }
        }

        // compute the normalized dual solution
        for (int ii = 0; ii < n_vertex_; ii++) {
            g_[ii].nor_dual_val = g_[ii].dual_val / crt_left;
            if (g_[ii].nor_dual_val > 1) {
                g_[ii].nor_dual_val = 1;
            }
        }
        // as the last step, round the fractional solution to integral solutions
        round_lp();
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void CG::dump(){
        for (int ii = 0; ii < n_vertex_; ii++) {
            printf("weight: %.3f, dual_val: %.3f, total_flow: %.3f, nor_dual_val:%.3f, n_edges:%d, to_delete:%d",
                   g_[ii].weight,
                   g_[ii].dual_val,
                   g_[ii].total_flow,
                   g_[ii].nor_dual_val,
                   g_[ii].num_edges,
                   g_[ii].to_delete);
            for (int jj = 0; jj < g_[ii].num_edges; jj++) {
                cout << ";" << g_[ii].edges[jj].to_id << " " << g_[ii].edges[jj].special;
            }
            cout << endl;
        }
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    V_Satellite* CG::construct_sub_graph(const vector<int>& vertices) const{
        map<int,int> old_new;
        int sub_size = vertices.size();
        for (int ii = 0; ii < sub_size; ii++) {
            old_new[vertices[ii]] = ii;
        }
        V_Satellite* sub_g = new V_Satellite[sub_size];
        vector<vector<E_Satellite> > edges;
        edges.resize(sub_size);
        for (int ii = 0; ii < sub_size; ii++){
            int node_id = vertices[ii];
            for (int jj = 0; jj < g_[node_id].num_edges; jj++) {
                int to_id = g_[node_id].edges[jj].to_id;
                if (old_new.find(to_id) != old_new.end()) {
                    int new_to_id = old_new[to_id];
                    bool is_special = g_[node_id].edges[jj].special;
                    E_Satellite new_edge = {new_to_id, is_special};
                    edges[ii].push_back(new_edge);
                }
            }
        }
        for (int ii = 0; ii < sub_size; ii++) {
            sub_g[ii] = g_[vertices[ii]];
            sub_g[ii].dual_val = 0.0;
            sub_g[ii].total_flow = 0.0;
            sub_g[ii].nor_dual_val = 0.0;
            sub_g[ii].to_delete = false;
            sub_g[ii].num_edges = edges[ii].size();
            sub_g[ii].edges = new E_Satellite[edges[ii].size()];
            for (int jj = 0; jj < edges[ii].size(); jj++) {
                sub_g[ii].edges[jj] = edges[ii][jj];
            }
        }

        return sub_g;
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void CG::compute_dual_vals(double alpha) {
        // compute on the log-scale first to avoid overflow
        for (int ii = 0; ii < n_vertex_; ii++) {
            double dual_val = log(1 / g_[ii].weight) + (alpha * g_[ii].total_flow / g_[ii].weight);
            g_[ii].dual_val = dual_val;
        }
        double max_dual = 0;
        for (int ii = 0; ii < n_vertex_; ii++) {
            if (g_[ii].dual_val > max_dual) {
                max_dual = g_[ii].dual_val;
            }
        }
        for (int ii = 0; ii < n_vertex_; ii++) {
            g_[ii].dual_val = g_[ii].dual_val - max_dual + 50;
            g_[ii].dual_val = exp(g_[ii].dual_val);
        }
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    double CG::compute_lambda() {
        double max_lambda = 0;
        for (int ii = 0; ii < n_vertex_; ii++) {
            double lambda = g_[ii].total_flow / g_[ii].weight;
            if (lambda > max_lambda) {
                max_lambda = lambda;
            }
        }
        return max_lambda;
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    void CG::update_solution(double delta, vector<int> & path) {
        for (int ii = 0; ii < n_vertex_; ii++) {
            g_[ii].total_flow = (1 - delta) * g_[ii].total_flow;
        }
        for (vector<int>::iterator iter1 = path.begin(); iter1 != path.end(); ++iter1) {
            g_[*iter1].total_flow = g_[*iter1].total_flow + delta;
        }
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    void CG::find_shortest_path(double& crt_cost, vector<int>& opt_path){
        vector<int> crt_shortest_path;
        double cost = numeric_limits<double>::max();

        DJ_node* node_status = new DJ_node[n_vertex_];
        // run dijastra's algorithm starting from every node
        for (int ii = 0; ii < n_vertex_; ii++) {
            // initialize the node_status array
            for (int jj = 0; jj < n_vertex_; jj++) {
                node_status[jj].parent = -1;
                node_status[jj].visited = false;
                node_status[jj].reachable = false;
                node_status[jj].distance = numeric_limits<double>::max();
            }
            // special vertices
            vector<int> sinks;
            // initialize the first node;
            node_status[ii].visited = true;
            node_status[ii].reachable = true;
            node_status[ii].distance = g_[ii].dual_val;
            for (int jj = 0; jj < g_[ii].num_edges; jj++) {
                int dest_id = g_[ii].edges[jj].to_id;
                if (g_[ii].edges[jj].special) {
                    sinks.push_back(dest_id);
                } else {
                    node_status[dest_id].distance = node_status[ii].distance + g_[dest_id].dual_val;
                    node_status[dest_id].parent = ii;
                    node_status[dest_id].reachable = true;
                }
            }

            while (true) {
                // find the closest node that still has not been visited
                double min_dist = numeric_limits<double>::max();
                int dest_id = -1;
                for (int kk = 0; kk < n_vertex_; kk++) {
                    if ((not node_status[kk].visited) and
                        (node_status[kk].reachable) and
                        (node_status[kk].distance < min_dist)) {
                        min_dist = node_status[kk].distance;
			dest_id = kk;
                    }
                }
                if (dest_id != -1) {
                    node_status[dest_id].visited = true;
                    for (int ll = 0; ll < g_[dest_id].num_edges; ll++) {
                        int ll_id = g_[dest_id].edges[ll].to_id;
                        if (not node_status[ll_id].visited) {
                            if (node_status[ll_id].distance > (node_status[dest_id].distance + g_[ll_id].dual_val)) {
                                node_status[ll_id].distance = node_status[dest_id].distance + g_[ll_id].dual_val;
                                node_status[ll_id].parent = dest_id;
                                node_status[ll_id].reachable = true;
                            }
                        }
                    }
                } else {
                    break;
                }
            }

            double min_dist1 = numeric_limits<double>::max();
            int closest_dest = -1;
            for (vector<int>::iterator sink = sinks.begin();
                 sink != sinks.end();
                 ++sink) {
                if (node_status[*sink].distance <= min_dist1) {
                    min_dist1 = node_status[*sink].distance;
                    closest_dest = *sink;
                }
            }
            assert(closest_dest != -1);
            if (min_dist1 < cost) {
                cost = min_dist1;
                crt_shortest_path.clear();
                crt_shortest_path.push_back(closest_dest);
                int parent = node_status[closest_dest].parent;
                while (parent > -1) {
                    crt_shortest_path.push_back(parent);
                    parent = node_status[parent].parent;
                }
            }

        } // end of for (dijastra to find shortest path from each node)

        crt_cost = cost;
        assert(cost < numeric_limits<double>::max());
        delete[] node_status;
        opt_path = crt_shortest_path;
        return;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void CG::initialize_lp_round() {
        for (int ii = 0; ii < n_vertex_; ii++) {
            g_[ii].to_delete = false;
        }
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void CG::round_lp_randomized(vector<int> nodes){
        // step0 decide if the sub-problem is solved already

        map<int, int> ori_2_new_map;
        for (int ii = 0; ii < nodes.size(); ii++) {
            ori_2_new_map[nodes[ii]] = ii;
        }

        double total_probs = 0.0; // care should be taken when dealing with nodes that have been deleted already
        vector<double> probs;
        probs.resize(nodes.size());
        for (int ii = 0; ii < nodes.size(); ii++) {
            if (g_[nodes[ii]].to_delete) {
                probs[ii] = 0.0;
            } else {
                probs[ii] = g_[nodes[ii]].nor_dual_val;
                total_probs = total_probs + probs[ii];
            }
        }

        bool exist_none_special_edges = exist_no_special_edge(nodes);

        if (not exist_none_special_edges) {
            return;
        }

        // step1: sample one node to delete
        // random_double is a number between 0 and total_probs
        double random_double = ((rand() + 0.5) / (double(RAND_MAX) + 1)) * total_probs;
        cout << "dbg print:random_double" << random_double / total_probs << endl;
        double accumulated_probs = 0.0;
        int delete_index;
        for (int ii = 0; ii < nodes.size(); ii++) {
            delete_index = ii;
            if (probs[ii] == 0.0) continue;
            accumulated_probs = accumulated_probs + probs[ii];
            if (accumulated_probs > random_double) break;
        }

        // step2: delete the vertex
        g_[nodes[delete_index]].to_delete = true;

        // step 3: construct a graph. Find out the strongly connected components
        typedef boost::adjacency_list<vecS,vecS,directedS> Graph;
        Graph G(nodes.size());
        for (int ii = 0; ii < nodes.size(); ii++) {
            if (not g_[nodes[ii]].to_delete) {
                for (int jj = 0; jj < g_[nodes[ii]].num_edges; jj++) {
                    int to_id = g_[nodes[ii]].edges[jj].to_id;
                    if ((ori_2_new_map.find(to_id) != ori_2_new_map.end()) and
                        (not g_[to_id].to_delete)) {
                        add_edge(ii, ori_2_new_map[to_id], G);
                    }
                }
            }
        }
        vector<int> components(nodes.size(),-1);
        int num = strong_components(G,&components[0]);

        // step4: recurse
        vector<vector<int> > sccs;
        sccs.resize(num);
        for (int ii = 0; ii < nodes.size(); ii++){
            assert(components[ii] < num);
            assert(components[ii] >= 0);
            sccs[components[ii]].push_back(nodes[ii]);
        }
        for (int ii = 0; ii < num; ii++) {
            round_lp_randomized(sccs[ii]);
        }

    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    void CG::round_lp_greedy(vector<int> nodes){
        // step0 decide if the sub-problem is solved already

        map<int, int> ori_2_new_map;
        for (int ii = 0; ii < nodes.size(); ii++) {
            ori_2_new_map[nodes[ii]] = ii;
        }

        double total_probs = 0.0; // care should be taken when dealing with nodes that have been deleted already
        vector<double> probs;
        probs.resize(nodes.size());
        for (int ii = 0; ii < nodes.size(); ii++) {
            if (g_[nodes[ii]].to_delete) {
                probs[ii] = 0.0;
            } else {
                probs[ii] = g_[nodes[ii]].nor_dual_val;
                total_probs = total_probs + probs[ii];
            }
        }

        bool exist_none_special_edges = exist_no_special_edge(nodes);

        if (not exist_none_special_edges) {
            return;
        }

        // step1: sample one node to delete
        // use a greedy approach to decide which node to delete
        int delete_index = -1;
        double max_prob = 0.0;
        for (int ii = 0; ii < nodes.size(); ii++) {
            if (probs[ii] > max_prob) {
                delete_index = ii;
                max_prob = probs[ii];
            }
        }
        assert(delete_index != -1);

        // step2: delete the vertex
        g_[nodes[delete_index]].to_delete = true;

        // step 3: construct a graph. Find out the strongly connected components
        typedef boost::adjacency_list<vecS,vecS,directedS> Graph;
        Graph G(nodes.size());
        for (int ii = 0; ii < nodes.size(); ii++) {
            if (not g_[nodes[ii]].to_delete) {
                for (int jj = 0; jj < g_[nodes[ii]].num_edges; jj++) {
                    int to_id = g_[nodes[ii]].edges[jj].to_id;
                    if ((ori_2_new_map.find(to_id) != ori_2_new_map.end()) and
                        (not g_[to_id].to_delete)) {
                        add_edge(ii, ori_2_new_map[to_id], G);
                    }
                }
            }
        }
        vector<int> components(nodes.size(),-1);
        int num = strong_components(G,&components[0]);

        // step4: recurse
        vector<vector<int> > sccs;
        sccs.resize(num);
        for (int ii = 0; ii < nodes.size(); ii++){
            assert(components[ii] < num);
            assert(components[ii] >= 0);
            sccs[components[ii]].push_back(nodes[ii]);
        }
        for (int ii = 0; ii < num; ii++) {
            round_lp_greedy(sccs[ii]);
        }

    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    bool CG::exist_no_special_edge(const vector<int>& nodes){
        map<int, int> ori_2_new_map;
        for (int ii = 0; ii < nodes.size(); ii++) {
            ori_2_new_map[nodes[ii]] = ii;
        }
        bool exist_none_special_edges = false;
        for (int ii = 0; ii < nodes.size(); ii++) {
            if (not g_[nodes[ii]].to_delete) {
                for (int jj = 0; jj < g_[nodes[ii]].num_edges; jj++) {
                    int to_id = g_[nodes[ii]].edges[jj].to_id;
                    if ((ori_2_new_map.find(to_id) != ori_2_new_map.end()) and
                        (not g_[to_id].to_delete)) {
                        if (not g_[nodes[ii]].edges[jj].special) {
                            exist_none_special_edges = true;
                        }
                    }
                }
            }
        }
        return exist_none_special_edges;
    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    bool CG::valid_solution(){
        typedef boost::adjacency_list<vecS, vecS, directedS> Graph;
        Graph G(n_vertex_);
        for (int ii = 0; ii < n_vertex_; ii++) {
            if (not g_[ii].to_delete) {
                for (int jj = 0; jj < g_[ii].num_edges; jj++) {
                    int to_id = g_[ii].edges[jj].to_id;
                    if (not g_[to_id].to_delete) {
                        add_edge(ii, to_id, G);
                    }
                }
            }
        }
        vector<int> components(n_vertex_, -1);
        int num = strong_components(G, &components[0]);

        // check each connected component being a simple component
        vector<vector<int> > sccs;
        sccs.resize(num);
        for (int ii = 0; ii < n_vertex_; ii++){
            assert(components[ii] < num);
            assert(components[ii] >= 0);
            sccs[components[ii]].push_back(ii);
        }
        for (int ii = 0; ii < num; ii++) {
            bool none_special_edges = exist_no_special_edge(sccs[ii]);
            if (none_special_edges) {
                return false;
            }
        }
        return true;
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    void CG::minimize_sol(){
        bool exist_redundent = true;
        int count = 0;
        while (exist_redundent) {
            exist_redundent = false;
            for (int ii = 0; ii < n_vertex_; ii++) {
                if (g_[ii].to_delete) {
                    // test to put back the node
                    g_[ii].to_delete = false;
                    bool valid = valid_solution();
                    if (valid) {
                        exist_redundent = true;
                        count++;
                    } else {
                        g_[ii].to_delete = true;
                    }
                }
            }
        }
        cout << "removed " << count << " redundent nodes" << endl;
    };
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    void CG::round_lp() {
        vector<int> entire_set;
        for (int ii = 0; ii < n_vertex_; ii++) {
            entire_set.push_back(ii);
        }
        initialize_lp_round();
        round_lp_greedy(entire_set);
        minimize_sol();
        vector<int> greedy_sol;
        for (int ii = 0; ii < n_vertex_; ii++) {
            if (g_[ii].to_delete) {
                greedy_sol.push_back(ii);
            }
        }
        initialize_lp_round();
        round_lp_randomized(entire_set);
        minimize_sol();
        vector<int> randomized_sol;
        for (int ii = 0; ii < n_vertex_; ii++) {
            if (g_[ii].to_delete) {
                randomized_sol.push_back(ii);
            }
        }

        cout << "randomized solution size:" << randomized_sol.size() << endl;
        cout << "greedy solution size:" << greedy_sol.size() << endl;
        initialize_lp_round();
        if (greedy_sol.size() < randomized_sol.size()) {
            for (int ii = 0; ii < greedy_sol.size(); ii++) {
                g_[greedy_sol[ii]].to_delete = true;
            }
        } else {
            for (int ii = 0; ii < randomized_sol.size(); ii++) {
                g_[randomized_sol[ii]].to_delete = true;
            }
        }

    }
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}


/* ================================================================
 *
 * PyCA Project
 *
 * Copyright (c) J. Samuel Preston, Linh K. Ha, Sarang C. Joshi. All
 * rights reserved.  See Copyright.txt or for details.
 *
 * This software is distributed WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the above copyright notice for more information.
 *
 * ================================================================ */

#include <FluidKernelFFTBase.h>

#include <FOpers.h>
#include <CudaUtils.h>
#include <boost_mem.h>

// MSVC lacks a few constants and math functions
#ifdef _MSC_VER
#define M_PI 3.141592653589793
#endif

namespace PyCA {

// ================================================================
// FFTLookupTable3D
// ================================================================

template<class T>
void
FFTLookupTable3D<T>::
InitTable(){

    //
    // Test that MAX_FFT_TABLE_LENGTH is big enough
    //
    if(mSize.x > MAX_FFT_TABLE_LENGTH ||
       mSize.y > MAX_FFT_TABLE_LENGTH ||
       mSize.z > MAX_FFT_TABLE_LENGTH)
    {
       throw PyCAException(__FILE__,__LINE__,"Error, not enough static memory allocated!");
    }

    //
    // precompute some values
    //
    double sX = 2.0 * M_PI / mSize.x;
    double sY = 2.0 * M_PI / mSize.y;
    double sZ = 2.0 * M_PI / mSize.z;

    double deltaXSq = mSp.x * mSp.x;
    double deltaYSq = mSp.y * mSp.y;
    double deltaZSq = mSp.z * mSp.z;

    //
    // fill in luts
    //
    for (size_t x = 0; x < (size_t)mSize.x; ++x) {
        mCosWX[x] = (2.0 * cos(sX * static_cast<float>(x)) - 2.0) / deltaXSq;
        mSinWX[x] = sin(sX * static_cast<float>(x)) / mSp.x;
    }

    for (size_t y = 0; y < (size_t)mSize.y; ++y) {
        mCosWY[y] = (2.0 * cos(sY * static_cast<float>(y)) - 2.0) / deltaYSq;
        mSinWY[y] = sin(sY * static_cast<float>(y)) / mSp.y;
    }

    for (size_t z = 0; z < (size_t)mSize.z; ++z) {
        mCosWZ[z] = (2.0 * cos(sZ * static_cast<float>(z)) - 2.0) / deltaZSq;
        mSinWZ[z] = sin(sZ * static_cast<float>(z)) / mSp.z;
    }
}

template<class T>
void
FFTLookupTable3D<T>::
Allocate(){
    mCosWX = new T [MAX_FFT_TABLE_LENGTH];
    mCosWY = new T [MAX_FFT_TABLE_LENGTH];
    mCosWZ = new T [MAX_FFT_TABLE_LENGTH];

    mSinWX = new T [MAX_FFT_TABLE_LENGTH];
    mSinWY = new T [MAX_FFT_TABLE_LENGTH];
    mSinWZ = new T [MAX_FFT_TABLE_LENGTH];
}

template<class T>
void
FFTLookupTable3D<T>::
setSize(const Vec3Di& size,
	const Vec3Df& spacing)
{
    mSize = size;
    mSp   = spacing;
    InitTable();
}

template<class T>
void
FFTLookupTable3D<T>::
Clear(){
    delete []mCosWX;
    delete []mCosWY;
    delete []mCosWZ;

    delete []mSinWX;
    delete []mSinWY;
    delete []mSinWZ;
}

// ================================================================
// FluidKernelFFTBase
// ================================================================

template<int mode, MemoryType mType, class T>
FluidKernelFFTBase<mode, mType, T>::
FluidKernelFFTBase()
  :mAlpha(1.0), mBeta(0.0), mGamma(0.0), mLPow(1.0),
   mDivergenceFree(false),
   mHasFFTPlan(false),
   mSize(0,0,0), mSpacing(0,0,0),
   mComplexSize(0),
   mAllocateSize(0),
   mFFTArrayX(NULL),
   mFFTArrayY(NULL),
   mFFTArrayZ(NULL)
{

}

template<int mode, MemoryType mType, class T>
FluidKernelFFTBase<mode, mType, T>::
~FluidKernelFFTBase()
{
}

template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
setGrid(const GridInfo& grid)
{
    this->setSize(grid);
}

template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::setSize(const GridInfo& g)
{

    Vec3Di newSize   = g.size();
    Vec3Df newSpacing= g.spacing();

    bool changeSize    = (mSize    != newSize);
    bool changeSpacing = (mSpacing != newSpacing);

    // TEST
    if(mType != MEM_HOST){
	CudaUtils::CheckCUDAError(__FILE__, __LINE__);
    }
    // END TEST

    if (changeSize) {
        mSize = newSize;

        mComplexSize   = mSize;
        mComplexSize.x = mSize.x/2+1;

        size_t n = mComplexSize.prod();
        if ( n > mAllocateSize)
            Alloc(n);
    }

    if (changeSpacing) {
        mSpacing  = newSpacing;
    }

    // recompute the lookup table
    if (changeSize || changeSpacing)
        mLookupTable.setSize(mSize, mSpacing);

    // TEST
    if(mType != MEM_HOST){
	CudaUtils::CheckCUDAError(__FILE__, __LINE__);
    }
    // END TEST

    // create FFT plan
    if (changeSize) {
        if (mHasFFTPlan)
            DestroyFFTPlan();
        CreateFFTPlan();
    }
}

template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
Alloc(size_t n){
    mFFTArray = CreateSharedArray<mType, ComplexT<T> >(3*n);
    mFFTArrayX = &(mFFTArray.get()[0]);
    mFFTArrayY = &(mFFTArray.get()[n]);
    mFFTArrayZ = &(mFFTArray.get()[2*n]);
    mAllocateSize = n;
}

template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
ApplyImpl(T* dataX, T* dataY, T* dataZ, bool inverseOp, StreamT stream)
{
    //1. Compute FFT of the data, store result in mFFTArray buffers
    // convert the input from real to frequency(complex image)
    toFrequencyDomain(dataX, dataY, dataZ, stream);

    //2. Solve system
    if (mLPow == floor(mLPow)) {
        size_t nPow = (size_t) mLPow;
        for (size_t i=0; i < nPow; ++i)
            frequencyDomainApply(inverseOp, stream);
    }

    //3. convert the output back to time domain
    toSpaceDomain(dataX, dataY, dataZ, stream);

};

template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
Apply(Field3D& out, const Field3D& in,
      bool inverseOp, StreamT stream)
{
    MK_CHECK_REFSIZE_2(mSize, out, in);
    //size_t nElems = mSize.prod();
    //FieldOpers<mode>::MulC(out, in, 1.f / nElems, stream);
    Opers::Copy(out, in, NULL);
    ApplyImpl(out.x, out.y, out.z, inverseOp, stream);
}

template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
Apply(Field3D& out, bool inverseOp, StreamT stream){
    MK_CHECK_REFSIZE_1(mSize, out);
    //size_t nElems = mSize.prod();
    //FieldOpers<mode>::MulC_I(out, 1.f / nElems, stream);
    ApplyImpl(out.x, out.y, out.z, inverseOp, stream);
}

//--------------------------------------------------------------------------------
/**
 * f = Lv
 *
 * v field is overwritten in this operation
 */
template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
applyOperator(Field3D& f, const Field3D& v, StreamT stream){
    Apply(f, v, false, stream);
}

/**
 * v = Kf
 *
 * f field is overwritten in this operation
 */
template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
applyInverseOperator(Field3D& v, const Field3D& f, StreamT stream){
    Apply(v, f, true, stream);
}

/**
 * f = Lv
 *
 * v field is overwritten in this operation (holds f).
 */
template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
applyOperator(Field3D& f, StreamT stream){
    Apply(f, false, stream);
}

/**
 * v = Kf
 *
 * f field is overwritten in this operation (holds v).
 */
template<int mode, MemoryType mType, class T>
void
FluidKernelFFTBase<mode, mType, T>::
applyInverseOperator(Field3D& v, StreamT stream){
    Apply(v, true, stream);
}

// template instantiations
template class FluidKernelFFTBase<EXEC_CPU, MEM_HOST, float>;
#ifdef CUDA_ENABLED
template class FluidKernelFFTBase<EXEC_GPU, MEM_DEVICE, float>;
#endif

}; // end namespace PyCA


#include <algorithm>
#include <queue>
#include <boost/math/constants/constants.hpp>
#include <Eigen/Sparse>
#include <Euclid/Geometry/TriMeshGeometry.h>
#include <Euclid/Math/Numeric.h>
#include <Euclid/Topology/MeshTopology.h>

namespace Euclid
{

namespace _impl
{

template<typename Mesh, typename VertexCurvatureMap>
bool check_gauss_bonnet(const Mesh& mesh, VertexCurvatureMap vcm)
{
    using Scalar =
        typename boost::property_traits<VertexCurvatureMap>::value_type;
    Scalar sum = 0;
    for (auto v : vertices(mesh)) {
        sum += get(vcm, v);
    }
    auto chi = euler_characteristic(mesh);
    return eq_almost(sum, boost::math::constants::two_pi<Scalar>() * chi);
}

template<typename Mesh, typename VertexRadiusMap, typename EdgeWeightMap>
decltype(auto) elen_from_circle_packing_metric(
    const Mesh& mesh,
    typename boost::graph_traits<Mesh>::edge_descriptor e,
    VertexRadiusMap vrm,
    EdgeWeightMap ewm)
{
    auto h = halfedge(e, mesh);
    auto s = source(h, mesh);
    auto t = target(h, mesh);
    auto rs = get(vrm, s);
    auto rt = get(vrm, t);
    auto cosw = get(ewm, e);
    auto l2 = rs * rs + rt * rt + 2 * rs * rt * cosw;
    l2 = std::max(l2, static_cast<decltype(l2)>(0));
    return std::sqrt(l2);
}

template<typename T>
T cos12(T l1, T l2, T l3)
{
    auto cos = (l1 * l1 + l2 * l2 - l3 * l3) / (2 * l1 * l2);
    cos = std::min(cos, static_cast<T>(1));
    cos = std::max(cos, static_cast<T>(-1));
    return cos;
}

template<typename Mesh, typename T, typename EIMap>
T corner_angle(const Mesh& mesh,
               typename boost::graph_traits<Mesh>::halfedge_descriptor h1,
               const std::vector<T>& lengths,
               EIMap eimap)
{
    auto h2 = next(h1, mesh);
    auto h3 = prev(h1, mesh);
    auto e1 = edge(h1, mesh);
    auto e2 = edge(h2, mesh);
    auto e3 = edge(h3, mesh);
    auto l1 = lengths[get(eimap, e1)];
    auto l2 = lengths[get(eimap, e2)];
    auto l3 = lengths[get(eimap, e3)];
    auto c = _impl::cos12(l1, l2, l3);
    return std::acos(c);
}

template<typename Mesh, typename T, typename EIMap>
T gaussian(const Mesh& mesh,
           typename boost::graph_traits<Mesh>::vertex_descriptor v,
           const std::vector<T>& lengths,
           EIMap eimap)
{
    T k;
    if (CGAL::is_border(v, mesh)) {
        k = boost::math::constants::pi<T>();
    }
    else {
        k = boost::math::constants::two_pi<T>();
    }
    for (auto h : halfedges_around_target(v, mesh)) {
        if (!CGAL::is_border(h, mesh)) {
            k -= corner_angle(mesh, h, lengths, eimap);
        }
    }
    return k;
}

template<typename Mesh,
         typename VertexRadiusMap,
         typename EdgeIndexMap,
         typename T>
void solve_power_circle(const Mesh& mesh,
                        typename boost::graph_traits<Mesh>::face_descriptor f,
                        VertexRadiusMap vrm,
                        EdgeIndexMap eim,
                        const std::vector<T>& lengths,
                        std::vector<T>& weights)
{
    // construct local coordinates for a triangle
    auto h = *(halfedges_around_face(halfedge(f, mesh), mesh).first);
    auto v = target(h, mesh);
    T l1 = lengths[get(eim, edge(h, mesh))];
    T x1 = 0;
    T y1 = 0;
    T r1 = get(vrm, v);

    h = next(h, mesh);
    v = target(h, mesh);
    T l2 = lengths[get(eim, edge(h, mesh))];
    T x2 = l2;
    T y2 = 0;
    T r2 = get(vrm, v);

    h = next(h, mesh);
    v = target(h, mesh);
    T l3 = lengths[get(eim, edge(h, mesh))];
    T cos = cos12(l1, l2, l3);
    T sin = std::sqrt(1 - cos * cos);
    T x3 = cos * l1;
    T y3 = sin * l1;
    T r3 = get(vrm, v);

    // solve the power circle in local coordinates
    auto x1_2 = x1 * x1;
    auto y1_2 = y1 * y1;
    auto r1_2 = r1 * r1;
    auto x2_2 = x2 * x2;
    auto y2_2 = y2 * y2;
    auto r2_2 = r2 * r2;
    auto x3_2 = x3 * x3;
    auto y3_2 = y3 * y3;
    auto r3_2 = r3 * r3;
    auto a = -x1_2 + x2_2 - y1_2 + y2_2 + r1_2 - r2_2;
    auto b = -x1_2 + x3_2 - y1_2 + y3_2 + r1_2 - r3_2;
    auto c = 2 * ((y1 - y2) * (x3 - x1) - (y1 - y3) * (x2 - x1));

    auto y = ((x2 - x1) * b - (x3 - x1) * a) / c;
    auto x = (2 * (y1 - y3) * y + b) / (2 * (x3 - x1));

    // calculate the height from the center to each edge
    auto d1 = std::sqrt((x1 - x) * (x1 - x) + (y1 - y) * (y1 - y));
    auto d2 = std::sqrt((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y));
    auto d3 = std::sqrt((x3 - x) * (x3 - x) + (y3 - y) * (y3 - y));

    auto cos1 = cos12(d3, l1, d1);
    auto sin1 = std::sqrt(1 - cos1 * cos1);
    auto height1 = d3 * sin1;
    h = next(h, mesh);
    weights[get(eim, edge(h, mesh))] += height1;

    auto cos2 = cos12(d1, l2, d2);
    auto sin2 = std::sqrt(1 - cos2 * cos2);
    auto height2 = d1 * sin2;
    h = next(h, mesh);
    weights[get(eim, edge(h, mesh))] += height2;

    auto cos3 = cos12(d2, l3, d3);
    auto sin3 = std::sqrt(1 - cos3 * cos3);
    auto height3 = d2 * sin3;
    h = next(h, mesh);
    weights[get(eim, edge(h, mesh))] += height3;
}

template<typename Mesh,
         typename VertexRadiusMap,
         typename EdgeWeightMap,
         typename VertexIndexMap,
         typename EdgeIndexMap,
         typename T>
void hessian(const Mesh& mesh,
             VertexRadiusMap vrm,
             EdgeWeightMap ewm,
             VertexIndexMap vim,
             EdgeIndexMap eim,
             const std::vector<T>& lengths,
             Eigen::SparseMatrix<T>& H)
{
    using Triplet = Eigen::Triplet<T>;
    using Triplets = std::vector<Triplet>;

    std::vector<T> weights(num_edges(mesh), 0);
    for (auto f : faces(mesh)) {
        solve_power_circle(mesh, f, vrm, eim, lengths, weights);
    }

    Triplets triplets;
    triplets.reserve(num_halfedges(mesh) + num_vertices(mesh));
    for (auto v : vertices(mesh)) {
        T sum = 0;
        auto vi_idx = get(vim, v);
        for (auto h : halfedges_around_target(v, mesh)) {
            auto vj_idx = get(vim, source(h, mesh));
            auto eidx = get(eim, edge(h, mesh));
            auto w = weights[eidx] / lengths[eidx];
            sum += w;
            triplets.emplace_back(vi_idx, vj_idx, -w);
        }
        triplets.emplace_back(vi_idx, vi_idx, sum);
    }

    H.resize(num_vertices(mesh), num_vertices(mesh));
    H.setFromTriplets(triplets.begin(), triplets.end());
    H.makeCompressed();
}

template<typename Mesh,
         typename VertexRadiusMap,
         typename EdgeWeightMap,
         typename SEM,
         typename SVM,
         typename VertexUVMap,
         typename VertexParameterizedMap>
void embed_circle_packing_metric(const Mesh& mesh,
                                 VertexRadiusMap vrm,
                                 EdgeWeightMap ewm,
                                 CGAL::Seam_mesh<Mesh, SEM, SVM>& seam_mesh,
                                 VertexUVMap uvm,
                                 VertexParameterizedMap vpm)
{
    using Seam_mesh = CGAL::Seam_mesh<Mesh, SEM, SVM>;
    using fd = typename boost::graph_traits<Seam_mesh>::face_descriptor;
    using Point_2 = typename boost::property_traits<VertexUVMap>::value_type;
    using Point_3 = typename CGAL::Kernel_traits<Point_2>::Kernel::Point_3;
    using FT = typename CGAL::Kernel_traits<Point_2>::Kernel::FT;

    // mesh and seam_mesh have the same number of faces
    // seam_mesh does not have face_index, use mesh's
    auto fimap = get(boost::face_index, mesh);
    std::vector<bool> visited(num_faces(seam_mesh), false);
    std::queue<fd> queue;

    // flatten the root face
    auto froot = *(faces(seam_mesh).first);
    auto hroot = halfedge(froot, seam_mesh);
    auto vroot = target(hroot, seam_mesh);
    auto l0 = _impl::elen_from_circle_packing_metric(
        mesh, edge(hroot.tmhd, mesh), vrm, ewm);
    put(uvm, vroot, Point_2(0, 0));
    put(vpm, vroot, true);
    hroot = next(hroot, seam_mesh);
    vroot = target(hroot, seam_mesh);
    auto l1 = _impl::elen_from_circle_packing_metric(
        mesh, edge(hroot.tmhd, mesh), vrm, ewm);
    put(uvm, vroot, Point_2(l1, 0));
    put(vpm, vroot, true);
    hroot = next(hroot, seam_mesh);
    vroot = target(hroot, seam_mesh);
    auto l2 = _impl::elen_from_circle_packing_metric(
        mesh, edge(hroot.tmhd, mesh), vrm, ewm);
    auto cos0 = _impl::cos12(l0, l1, l2);
    auto sin0 = std::sqrt(1 - cos0 * cos0);
    put(uvm, vroot, Point_2(cos0 * l0, sin0 * l0));
    put(vpm, vroot, true);
    visited[get(fimap, face(hroot.tmhd, mesh))] = true;
    queue.push(froot);
    size_t nv = 3;
    size_t nf = 1;

    // propogate the flattening
    while (!queue.empty()) {
        auto f = queue.front();
        queue.pop();
        for (auto h :
             halfedges_around_face(halfedge(f, seam_mesh), seam_mesh)) {
            auto hoppo = opposite(h, seam_mesh);
            if (!CGAL::is_border(hoppo, seam_mesh)) {
                auto foppo_idx = get(fimap, face(hoppo.tmhd, mesh));
                if (visited[foppo_idx]) {
                    continue;
                }
                visited[foppo_idx] = true;
                queue.push(face(hoppo, seam_mesh));
                ++nf;

                auto vk = target(next(hoppo, seam_mesh), seam_mesh);
                if (get(vpm, vk)) {
                    continue;
                }
                put(vpm, vk, true);
                ++nv;

                // use the intersection point of two circles as the next
                // flattened vertex's uv-coordinate
                auto vs = source(hoppo, seam_mesh);
                auto vt = target(hoppo, seam_mesh);
                auto ps = get(uvm, vs);
                auto pt = get(uvm, vt);
                auto ls = _impl::elen_from_circle_packing_metric(
                    mesh, edge(prev(hoppo.tmhd, mesh), mesh), vrm, ewm);
                auto lt = _impl::elen_from_circle_packing_metric(
                    mesh, edge(next(hoppo.tmhd, mesh), mesh), vrm, ewm);
                auto d = std::sqrt((pt - ps).squared_length());
                auto a = (ls * ls - lt * lt + d * d) / (2 * d);
                auto h =
                    std::sqrt(std::max(ls * ls - a * a, static_cast<FT>(0)));
                auto pm = ps + a * (pt - ps) / d;
                auto u = pm.x() + h * (pt.y() - ps.y()) / d;
                auto v = pm.y() - h * (pt.x() - ps.x()) / d;
                assert(!std::isnan(u));
                assert(!std::isnan(v));

                // check orientation
                Point_3 pi(ps.x(), ps.y(), 0);
                Point_3 pj(pt.x(), pt.y(), 0);
                Point_3 pk(u, v, 0);
                if (CGAL::cross_product(pj - pi, pk - pi).z() > 0) {
                    put(uvm, vk, Point_2(u, v));
                }
                else {
                    u = pm.x() - h * (pt.y() - ps.y()) / d;
                    v = pm.y() + h * (pt.x() - ps.x()) / d;
                    put(uvm, vk, Point_2(u, v));
                }
            }
        }
    }
    assert(nv == num_vertices(seam_mesh));
    assert(nf == num_faces(seam_mesh));
}

} // namespace _impl

template<typename Mesh, typename VertexRadiusMap, typename EdgeWeightMap>
void circle_packing_metric(Mesh& mesh, VertexRadiusMap vrm, EdgeWeightMap ewm)
{
    using Scalar = typename boost::property_traits<VertexRadiusMap>::value_type;
    auto eimap = get(boost::edge_index, mesh);

    // compute edge lengths
    std::vector<Scalar> lens;
    lens.reserve(num_edges(mesh));
    for (auto e : edges(mesh)) {
        auto l = edge_length(e, mesh);
        lens.push_back(l);
    }

    // compute vertex radii
    for (auto v : vertices(mesh)) {
        Scalar radius;
        bool initialized = false;
        for (auto h : halfedges_around_target(v, mesh)) {
            if (!CGAL::is_border(h, mesh)) {
                auto e1 = get(eimap, edge(h, mesh));
                auto e2 = get(eimap, edge(next(h, mesh), mesh));
                auto e3 = get(eimap, edge(prev(h, mesh), mesh));
                auto r = (lens[e1] + lens[e2] - lens[e3]) / 2;
                if (!initialized) {
                    radius = r;
                    initialized = true;
                }
                else {
                    radius = std::min(radius, r);
                }
            }
        }
        put(vrm, v, radius);
    }

    // compute edge weights
    for (auto e : edges(mesh)) {
        auto h = halfedge(e, mesh);
        auto s = source(h, mesh);
        auto t = target(h, mesh);
        auto rs = get(vrm, s);
        auto rt = get(vrm, t);
        auto l = lens[get(eimap, e)];
        // don't use cos12 since it's okay if cosw > 1
        auto cosw = (l * l - rs * rs - rt * rt) / (2 * rs * rt);
        put(ewm, e, cosw);
    }
}

template<typename Mesh,
         typename VertexRadiusMap,
         typename EdgeWeightMap,
         typename SEM,
         typename SVM,
         typename VertexUVMap>
void embed_circle_packing_metric(const Mesh& mesh,
                                 VertexRadiusMap vrm,
                                 EdgeWeightMap ewm,
                                 CGAL::Seam_mesh<Mesh, SEM, SVM>& seam_mesh,
                                 VertexUVMap uvm)
{
    using Seam_mesh = CGAL::Seam_mesh<Mesh, SEM, SVM>;
    using Vertex = typename boost::graph_traits<Seam_mesh>::vertex_descriptor;
    using VPM = std::map<Vertex, bool>;

    VPM vpm;
    _impl::embed_circle_packing_metric(
        mesh,
        vrm,
        ewm,
        seam_mesh,
        uvm,
        boost::associative_property_map<VPM>(vpm));
}

template<typename Mesh,
         typename VertexRadiusMap,
         typename EdgeWeightMap,
         typename VertexCurvatureMap>
RicciFlowSolverStatus ricci_flow(Mesh& mesh,
                                 VertexRadiusMap vrm,
                                 EdgeWeightMap ewm,
                                 VertexCurvatureMap vcm,
                                 const RicciFlowSolverSettings& settings)
{
    std::cout << "==========Ricci flow==========" << std::endl
              << "solver type: ";
    if (settings.type == RicciFlowSolverType::GradientDescent) {
        std::cout << "gradient descent" << std::endl;
    }
    else {
        std::cout << "Newton" << std::endl;
    }
    std::cout << "step: " << settings.step << std::endl
              << "eps: " << settings.eps << std::endl
              << "max iters: " << settings.max_iters << std::endl
              << "==============================" << std::endl;

    if (!_impl::check_gauss_bonnet(mesh, vcm)) {
        std::cerr << "Error: target curvatures are not admissible."
                  << std::endl;
        return RicciFlowSolverStatus::InvalidInput;
    }

    using Scalar = typename boost::property_traits<VertexRadiusMap>::value_type;
    using SpMat = Eigen::SparseMatrix<Scalar>;
    using Vec = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
    auto vimap = get(boost::vertex_index, mesh);
    auto eimap = get(boost::edge_index, mesh);
    std::vector<Scalar> lengths(num_edges(mesh));
    auto type = settings.type;

    Eigen::SimplicialLDLT<SpMat> solver;
    if (settings.type == RicciFlowSolverType::Newton) {
        // prefactor the solver using graph Laplacian
        auto L = graph_laplacian_matrix(mesh);
        solver.analyzePattern(L);
    }

    for (auto iter = 0; iter < settings.max_iters; ++iter) {
        // update edge lengths from current vertex radii and edge weights
        for (auto e : edges(mesh)) {
            lengths[get(eimap, e)] =
                _impl::elen_from_circle_packing_metric(mesh, e, vrm, ewm);
        }

        // compute the gradients based on solver type
        Scalar kdiff_max = 0;
        Vec grads = Vec::Zero(num_vertices(mesh));
        if (type == RicciFlowSolverType::Newton) {
            // FIXME: Newton is not working
            assert(type == RicciFlowSolverType::GradientDescent);
            // SpMat H;
            // _impl::hessian(mesh, vrm, ewm, vimap, eimap, lengths, H);
            // solver.factorize(H);
            // Vec b(num_vertices(mesh));
            // for (auto v : vertices(mesh)) {
            //     auto kdiff =
            //         get(vcm, v) - _impl::gaussian(mesh, v, lengths, eimap);
            //     b(get(vimap, v)) = kdiff;
            //     kdiff_max = std::max(std::abs(kdiff), kdiff_max);
            // }
            // grads = solver.solve(b);
        }
        else { // GradientDescent
            for (auto v : vertices(mesh)) {
                auto kdiff =
                    get(vcm, v) - _impl::gaussian(mesh, v, lengths, eimap);
                grads(get(vimap, v)) = kdiff * settings.step;
                kdiff_max = std::max(std::abs(kdiff), kdiff_max);
            }
        }

        // optimize the Ricci energy
        for (auto v : vertices(mesh)) {
            auto u = std::log(get(vrm, v)); // convert radius to scaling factor
            u += grads(get(vimap, v));      // Ricci flow
            put(vrm, v, std::exp(u));       // convert back to vertex radius
        }

        // stopping criterion
        if (settings.verbose) {
            std::cout << "iter: " << iter << ", error: " << kdiff_max
                      << std::endl;
        }
        if (kdiff_max < settings.eps) {
            std::cout << "Optimization done." << std::endl;
            return RicciFlowSolverStatus::Optimal;
        }
    }
    std::cout << "Max iterations reached." << std::endl;
    return RicciFlowSolverStatus::Suboptimal;
}

template<typename Mesh, typename SEM, typename SVM>
Ricci_flow_parameterizer3<Mesh, SEM, SVM>::Ricci_flow_parameterizer3(
    const Mesh& mesh)
    : _underlying_mesh(mesh), _vrpm(_vrm), _ewpm(_ewm), _vcpm(_vcm)
{
    circle_packing_metric(_underlying_mesh, _vrpm, _ewpm);
    for (auto v : vertices(mesh)) {
        put(_vcpm, v, static_cast<Scalar>(0));
    }
}

template<typename Mesh, typename SEM, typename SVM>
void Ricci_flow_parameterizer3<Mesh, SEM, SVM>::set_solver_settings(
    const RicciFlowSolverSettings& settings)
{
    _settings = settings;
}

template<typename Mesh, typename SEM, typename SVM>
void Ricci_flow_parameterizer3<Mesh, SEM, SVM>::add_cone(
    typename boost::graph_traits<Mesh>::vertex_descriptor v,
    Scalar k)
{
    put(_vcpm, v, k * boost::math::constants::pi<Scalar>());
}

template<typename Mesh, typename SEM, typename SVM>
template<typename VertexUVMap,
         typename VertexIndexMap,
         typename VertexParameterizedMap>
CGAL::Surface_mesh_parameterization::Error_code
Ricci_flow_parameterizer3<Mesh, SEM, SVM>::parameterize(
    TriangleMesh& mesh,
    halfedge_descriptor,
    VertexUVMap uvmap,
    VertexIndexMap,
    VertexParameterizedMap vpmap)
{
    auto status = ricci_flow(_underlying_mesh, _vrpm, _ewpm, _vcpm, _settings);
    _impl::embed_circle_packing_metric(
        _underlying_mesh, _vrpm, _ewpm, mesh, uvmap, vpmap);
    if (status == RicciFlowSolverStatus::InvalidInput) {
        return CGAL::Surface_mesh_parameterization::ERROR_WRONG_PARAMETER;
    }
    else {
        return CGAL::Surface_mesh_parameterization::OK;
    }
}

} // namespace Euclid


#include "mtf/SSM//HomographyEstimator.h"
#include "mtf/Utilities/miscUtils.h"
#include "opencv2/core/core_c.h"
#include "opencv2/calib3d/calib3d.hpp"
#include <boost/random/uniform_int_distribution.hpp>

_MTF_BEGIN_NAMESPACE

HomographyEstimator::HomographyEstimator(int _modelPoints, bool _use_boost_rng)
	: SSMEstimator(_modelPoints, cvSize(3, 3), 1, _use_boost_rng) {
	assert(_modelPoints >= 4);
	checkPartialSubsets = false;
}

int HomographyEstimator::runKernel(const CvMat* m1, const CvMat* m2, CvMat* H) {
	int i, count = m1->rows * m1->cols;
	const CvPoint2D64f* M = (const CvPoint2D64f*)m1->data.ptr;
	const CvPoint2D64f* m = (const CvPoint2D64f*)m2->data.ptr;

	double LtL[9][9], W[9][1], V[9][9];
	CvMat _LtL = cvMat(9, 9, CV_64F, LtL);
	CvMat matW = cvMat(9, 1, CV_64F, W);
	CvMat matV = cvMat(9, 9, CV_64F, V);
	CvMat _H0 = cvMat(3, 3, CV_64F, V[8]);
	CvMat _Htemp = cvMat(3, 3, CV_64F, V[7]);
	CvPoint2D64f cM = { 0, 0 }, cm = { 0, 0 }, sM = { 0, 0 }, sm = { 0, 0 };

	for(i = 0; i < count; i++) {
		cm.x += m[i].x;
		cm.y += m[i].y;
		cM.x += M[i].x;
		cM.y += M[i].y;
	}

	cm.x /= count;
	cm.y /= count;
	cM.x /= count;
	cM.y /= count;

	for(i = 0; i < count; i++) {
		sm.x += fabs(m[i].x - cm.x);
		sm.y += fabs(m[i].y - cm.y);
		sM.x += fabs(M[i].x - cM.x);
		sM.y += fabs(M[i].y - cM.y);
	}

	if(fabs(sm.x) < DBL_EPSILON || fabs(sm.y) < DBL_EPSILON ||
		fabs(sM.x) < DBL_EPSILON || fabs(sM.y) < DBL_EPSILON)
		return 0;
	sm.x = count / sm.x;
	sm.y = count / sm.y;
	sM.x = count / sM.x;
	sM.y = count / sM.y;

	double invHnorm[9] = { 1. / sm.x, 0, cm.x, 0, 1. / sm.y, cm.y, 0, 0, 1 };
	double Hnorm2[9] = { sM.x, 0, -cM.x * sM.x, 0, sM.y, -cM.y * sM.y, 0, 0, 1 };
	CvMat _invHnorm = cvMat(3, 3, CV_64FC1, invHnorm);
	CvMat _Hnorm2 = cvMat(3, 3, CV_64FC1, Hnorm2);

	cvZero(&_LtL);
	for(i = 0; i < count; i++) {
		double x = (m[i].x - cm.x) * sm.x, y = (m[i].y - cm.y) * sm.y;
		double X = (M[i].x - cM.x) * sM.x, Y = (M[i].y - cM.y) * sM.y;
		double Lx[] = { X, Y, 1, 0, 0, 0, -x * X, -x * Y, -x };
		double Ly[] = { 0, 0, 0, X, Y, 1, -y * X, -y * Y, -y };
		int j, k;
		for(j = 0; j < 9; j++)
			for(k = j; k < 9; k++)
				LtL[j][k] += Lx[j] * Lx[k] + Ly[j] * Ly[k];
	}
	cvCompleteSymm(&_LtL);

	//cvSVD( &_LtL, &matW, 0, &matV, CV_SVD_MODIFY_A + CV_SVD_V_T );
	cvEigenVV(&_LtL, &matV, &matW);
	cvMatMul(&_invHnorm, &_H0, &_Htemp);
	cvMatMul(&_Htemp, &_Hnorm2, &_H0);
	cvConvertScale(&_H0, H, 1. / _H0.data.db[8]);

	return 1;
}

void HomographyEstimator::computeReprojError(const CvMat* m1, const CvMat* m2,
	const CvMat* model, CvMat* _err) {
	int i, count = m1->rows * m1->cols;
	const CvPoint2D64f* M = (const CvPoint2D64f*)m1->data.ptr;
	const CvPoint2D64f* m = (const CvPoint2D64f*)m2->data.ptr;
	const double* H = model->data.db;
	float* err = _err->data.fl;

	for(i = 0; i < count; i++) {
		double ww = 1. / (H[6] * M[i].x + H[7] * M[i].y + 1.);
		double dx = (H[0] * M[i].x + H[1] * M[i].y + H[2]) * ww - m[i].x;
		double dy = (H[3] * M[i].x + H[4] * M[i].y + H[5]) * ww - m[i].y;
		err[i] = (float)(dx * dx + dy * dy);
	}
}

bool HomographyEstimator::refine(const CvMat* m1, const CvMat* m2,
	CvMat* model, int maxIters) {
	LevMarq solver(8, 0, cvTermCriteria(CV_TERMCRIT_ITER + CV_TERMCRIT_EPS, maxIters, DBL_EPSILON));
	int i, j, k, count = m1->rows * m1->cols;
	const CvPoint2D64f* M = (const CvPoint2D64f*)m1->data.ptr;
	const CvPoint2D64f* m = (const CvPoint2D64f*)m2->data.ptr;
	CvMat modelPart = cvMat(solver.param->rows, solver.param->cols, model->type, model->data.ptr);
	cvCopy(&modelPart, solver.param);

	for(;;) {
		const CvMat* _param = 0;
		CvMat *_JtJ = 0, *_JtErr = 0;
		double* _errNorm = 0;

		if(!solver.updateAlt(_param, _JtJ, _JtErr, _errNorm))
			break;

		for(i = 0; i < count; i++) {
			const double* h = _param->data.db;
			double Mx = M[i].x, My = M[i].y;
			double ww = h[6] * Mx + h[7] * My + 1.;
			ww = fabs(ww) > DBL_EPSILON ? 1. / ww : 0;
			double _xi = (h[0] * Mx + h[1] * My + h[2]) * ww;
			double _yi = (h[3] * Mx + h[4] * My + h[5]) * ww;
			double err[] = { _xi - m[i].x, _yi - m[i].y };
			if(_JtJ || _JtErr) {
				double J[][8] = {
					{ Mx * ww, My * ww, ww, 0, 0, 0, -Mx*ww * _xi, -My*ww * _xi },
					{ 0, 0, 0, Mx * ww, My * ww, ww, -Mx*ww * _yi, -My*ww * _yi }
				};

				for(j = 0; j < 8; j++) {
					for(k = j; k < 8; k++)
						_JtJ->data.db[j * 8 + k] += J[0][j] * J[0][k] + J[1][j] * J[1][k];
					_JtErr->data.db[j] += J[0][j] * err[0] + J[1][j] * err[1];
				}
			}
			if(_errNorm)
				*_errNorm += err[0] * err[0] + err[1] * err[1];
		}
	}

	cvCopy(solver.param, &modelPart);
	return true;
}

cv::Mat estimateHomography(cv::InputArray _points1, cv::InputArray _points2,
	cv::OutputArray _mask, const SSMEstimatorParams &params){
	cv::Mat points1 = _points1.getMat(), points2 = _points2.getMat();
	int n_pts = points1.checkVector(2);
	CV_Assert(n_pts >= 0 && points2.checkVector(2) == n_pts &&
		points1.type() == points2.type());

	cv::Mat H(3, 3, CV_64F);
	CvMat _pt1 = points1, _pt2 = points2;
	CvMat matH = H, c_mask, *p_mask = 0;
	if(_mask.needed()){
		_mask.create(n_pts, 1, CV_8U, -1, true);
		p_mask = &(c_mask = _mask.getMat());
	}
	bool ok = estimateHomography(&_pt1, &_pt2, &matH, p_mask, params) > 0;
	if(!ok)
		H = cv::Scalar(0);
	return H;
}

int estimateHomography(const CvMat* objectPoints, const CvMat* imagePoints,
	CvMat* __H, CvMat* mask, const SSMEstimatorParams &params){
	bool result = false;
	cv::Ptr<CvMat> m, M, tempMask;

	double H[9];
	CvMat matH = cvMat(3, 3, CV_64FC1, H);

	CV_Assert(CV_IS_MAT(imagePoints) && CV_IS_MAT(objectPoints));

	int n_pts = MAX(imagePoints->cols, imagePoints->rows);
	CV_Assert(n_pts >= params.n_model_pts);

	m = cvCreateMat(1, n_pts, CV_64FC2);
	cvConvertPointsHomogeneous(imagePoints, m);

	M = cvCreateMat(1, n_pts, CV_64FC2);
	cvConvertPointsHomogeneous(objectPoints, M);

	if(mask){
		CV_Assert(CV_IS_MASK_ARR(mask) && CV_IS_MAT_CONT(mask->type) &&
			(mask->rows == 1 || mask->cols == 1) &&
			mask->rows*mask->cols == n_pts);
	}
	if(mask || n_pts > params.n_model_pts)
		tempMask = cvCreateMat(1, n_pts, CV_8U);
	if(!tempMask.empty())
		cvSet(tempMask, cvScalarAll(1.));

	HomographyEstimator estimator(params.n_model_pts, params.use_boost_rng);
	int method = n_pts == params.n_model_pts ? 0 : params.method_cv;
	if(method == CV_LMEDS)
		result = estimator.runLMeDS(M, m, &matH, tempMask, params.confidence,
		params.max_iters, params.max_subset_attempts);
	else if(method == CV_RANSAC)
		result = estimator.runRANSAC(M, m, &matH, tempMask, params.ransac_reproj_thresh,
		params.confidence, params.max_iters, params.max_subset_attempts);
	else
		result = estimator.runKernel(M, m, &matH) > 0;

	if(result && n_pts > params.n_model_pts){
		utils::icvCompressPoints((CvPoint2D64f*)M->data.ptr, tempMask->data.ptr, 1, n_pts);
		n_pts = utils::icvCompressPoints((CvPoint2D64f*)m->data.ptr, tempMask->data.ptr, 1, n_pts);
		M->cols = m->cols = n_pts;
		if(method == CV_RANSAC)
			estimator.runKernel(M, m, &matH);
		if(params.refine){
			estimator.refine(M, m, &matH, params.lm_max_iters);
		}
	}

	if(result)
		cvConvert(&matH, __H);

	if(mask && tempMask){
		if(CV_ARE_SIZES_EQ(mask, tempMask))
			cvCopy(tempMask, mask);
		else
			cvTranspose(tempMask, mask);
	}

	return (int)result;
}

//int	cvFindHomography(const PtsT &objectPoints, const PtsT &imagePoints,
//	ProjWarpT & __H, EstimatorMethod method, double ransacReprojThreshold,
//	VectorXb &mask){
//	int count = objectPoints.cols();
//	assert(count >= 4);
//	assert(mask.size() == count);

//	const double confidence = 0.995;
//	const int maxIters = 2000;
//	const double defaultRANSACReprojThreshold = 3;
//	bool result = false;

//	HomPtsT m, M;
//	ProjWarpT matH;
//	if(ransacReprojThreshold <= 0)
//		ransacReprojThreshold = defaultRANSACReprojThreshold;

//	homogenize(imagePoints, m);
//	homogenize(objectPoints, M);
//
//	mask.fill(1);

//	AffineEstimator estimator(4);
//	if(count == 4)
//		method = EstimatorMethod::LSTSQR;

//	if(method == EstimatorMethod::LMEDS)
//		result = estimator.runLMeDS(M, m, matH, mask, confidence, maxIters);
//	else if(method == EstimatorMethod::RANSAC)
//		result = estimator.runRANSAC(M, m, matH, mask, ransacReprojThreshold, confidence, maxIters);
//	else
//		result = estimator.runKernel(M, m, matH) > 0;

//	if(result && count > 4){
//		compressPoints(m, mask, 1, count);
//		count = compressPoints(M, mask, 1, count);
//		if(method == EstimatorMethod::RANSAC)
//			estimator.runKernel(M, m, matH);
//		estimator.refine(M, m, matH, 10);
//	}

//	if(result)
//		__H = matH;

//	return result;
//}

_MTF_END_NAMESPACE


#ifndef TRIUMF_NMR_NUCLEI_HPP
#define TRIUMF_NMR_NUCLEI_HPP

#include <boost/math/constants/constants.hpp>

#include <triumf/nmr/utilities.hpp>

// TRIUMF: Canada's particle accelerator centre
namespace triumf {

// nuclear magnetic resonance (NMR)
namespace nmr {

// NMR probe nuclei and their properties
namespace nuclei {

// niobium-93
template <typename T = double> struct niobium_93 {
  // radioactive half-life (s)
  static inline constexpr T half_life() {
    return std::numeric_limits<T>::infinity();
  }
  // radioactive lifetime (s)
  static inline constexpr T lifetime() {
    return half_life() / boost::math::constants::ln_two<T>();
  }
  // spin quantum number
  static inline constexpr T spin() { return 9.0 / 2.0; }
  // magnetic dipole moment (nm)
  static inline constexpr T magnetic_dipole_moment() { return 6.163; }
  // electric dipole moment (b)
  static inline constexpr T electric_quadrupole_moment() { return -0.32; }
  // gyromagnetic ratio (s^-1 T^-1)
  static inline constexpr T gyromagnetic_ratio() {
    return triumf::nmr::utilities::calculate_gamma<T>(magnetic_dipole_moment(),
                                                      spin());
  }
  // gyromagnetic ratio (MHz / T)
  static inline constexpr T gyromagnetic_ratio_in_MHz_T() {
    return gyromagnetic_ratio() / 1e6 / boost::math::constants::two_pi<T>();
  }
};

} // namespace nuclei

} // namespace nmr

} // namespace triumf

#endif // TRIUMF_NMR_NUCLEI_HPP


#include <cctbx/boost_python/flex_fwd.h>
#include <boost/python/enum.hpp>
#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/dict.hpp>
#include <boost/python/list.hpp>
#include <scitbx/array_family/flex_types.h>
#include <scitbx/array_family/shared.h>
#include <scitbx/math/mean_and_variance.h>
#include <scitbx/array_family/boost_python/shared_wrapper.h>
#include <cctbx/examples/merging/xscale_prototype_core.h>
#include <cctbx/examples/merging/postrefine_base.h>
#include <Eigen/Sparse>
#include <boost/python/return_internal_reference.hpp>
#include <boost/math/tools/precision.hpp>

using namespace boost::python;
namespace cctbx{ namespace merging {

double d_product (double const&A, double const&B, double const&dA, double const&dB){
  return dA * B + A * dB;
}

static const double logmax = boost::math::tools::log_max_value<double>()/2.;
static const double logmin = boost::math::tools::log_min_value<double>()/2.;

static boost::python::tuple
task6c_f_g(intensity_data::vecd const& x,
           const int& N_I, const int& N_G,
           const intensity_data & FSIM,
           bool compute_curv) {
  double f = 0.;
  const double* Iptr = x.begin(); // indices into the array of parameters
  const double* Gptr = Iptr + N_I;
  const double* Bptr = Gptr + N_G;

  intensity_data::vecd g(x.size(),0);
  intensity_data::vecd c(x.size(),0);
  double* Iptrg = g.begin(); // indices into the array of derivatives
  double* Gptrg = Iptrg + N_I;
  double* Bptrg = Gptrg + N_G;
  double* Iptrc = c.begin(); // indices into the array of curvatures
  double* Gptrc = Iptrc + N_I;
  double* Bptrc = Gptrc + N_G;

  for (size_t i = 0; i<FSIM.raw_obs.size(); ++i){
      double weight = 1./FSIM.exp_var[i];
      double Gitem  = Gptr[ FSIM.frame[i] ];
      double Bargument = -2.* Bptr[ FSIM.frame[i] ] * FSIM.stol_sq[i];

      if (logmax < Bargument){
        throw SCITBX_ERROR("exp argument greater than logmax");
      }
      if (logmin > Bargument){
        throw SCITBX_ERROR("exp argument less than logmin");
      }

      double Bitem  = std::exp(Bargument); // :=exp(beta)
      double Iitem  = Iptr[ FSIM.miller[i] ];

      double residual = - FSIM.raw_obs[i] +
                        Gitem *
                        Bitem *
                        Iitem;

      f += weight * residual * residual;

      Gptrg[ FSIM.frame[i] ] += weight * residual * Bitem * Iitem;
      Iptrg[ FSIM.miller[i] ]+= weight * residual * Bitem * Gitem;

      double d_Bitem_d_B =    Bitem * ( -2. * FSIM.stol_sq[i] );
      double d_residual_d_B = Gitem * Iitem * d_Bitem_d_B;

      Bptrg[ FSIM.frame[i] ] += weight * residual * d_residual_d_B;

      if (compute_curv) {
        Gptrc[ FSIM.frame[i] ] +=  weight * Bitem * Bitem * Iitem * Iitem;
        Iptrc[ FSIM.miller[i] ]+=  weight * Bitem * Bitem * Gitem * Gitem;
        Bptrc[ FSIM.frame[i] ] +=  weight * d_residual_d_B * d_residual_d_B;
      }
  }

  return (boost::python::make_tuple(f/2., g, c));
}

void f_g_error(std::string const& message){
  throw SCITBX_ERROR(message);
}

namespace boost_python { namespace {

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

    def("task6c_f_g", &cctbx::merging::task6c_f_g);
    def("f_g_error", &cctbx::merging::f_g_error);

    typedef return_value_policy<return_by_value> rbv;
    typedef default_call_policies dcp;

    typedef cctbx::merging::intensity_data cmid;
    class_<cmid>("intensity_data", init<>())
      .add_property("frame",
                    make_getter(&cmid::frame, rbv()),
                    make_setter(&cmid::frame, dcp()))
      .add_property("miller",
                    make_getter(&cmid::miller, rbv()),
                    make_setter(&cmid::miller, dcp()))
      .add_property("raw_obs",
                    make_getter(&cmid::raw_obs, rbv()),
                    make_setter(&cmid::raw_obs, dcp()))
      .add_property("exp_var",
                    make_getter(&cmid::exp_var, rbv()),
                    make_setter(&cmid::exp_var, dcp()))
      .add_property("stol_sq",
                    make_getter(&cmid::stol_sq, rbv()),
                    make_setter(&cmid::stol_sq, dcp()))
      .add_property("origHKL",
                    make_getter(&cmid::origHKL, rbv()),
                    make_setter(&cmid::origHKL, dcp()))
      .def("estimate_G", &cmid::estimate_G,(arg("Nframes"),arg("inv_d_sq_max")=0.,arg("inv_d_sq_min")=0.))
      .def("estimate_I", &cmid::estimate_I,(arg("Gframes"),arg("inv_d_sq_max")=0.,arg("inv_d_sq_min")=0.))
      .def("reset_mem", &cmid::reset_mem)
    ;

    enum_<ParameterFlags>("ParameterFlags")
      .value("PartialityDeff", PartialityDeff)
      .value("PartialityEtaDeff", PartialityEtaDeff)
      .value("Bfactor", Bfactor)
      .value("Deff", Deff)
      .value("Eta", Eta)
      .value("Rxy", Rxy)
    ;

    typedef scaling_common_functions scf;
    class_<scf >(
      "scaling_common_functions", no_init)
      .def("set_cpp_data",&scf::set_cpp_data,
        (arg("fsim")))
      .def("set_parameter_flags", &scf::set_parameter_flags)
      .def("set_wavelength", &scf::set_wavelength)
      .def("set_domain_size", &scf::set_domain_size)
      .def("set_Astar_matrix", &scf::set_Astar_matrix)
      .def("get_rh_rs_ratio", &scf::get_rh_rs_ratio)
    ;

    typedef scitbx::example::non_linear_ls_eigen_wrapper nllsew;
    typedef xscale6e wt6e;
    class_<wt6e,
           bases<nllsew, scf  > >(
      "xscale6e", no_init)
      .def(init<int>(arg("n_parameters")))
      .def("access_cpp_build_up_directly_eigen_eqn",&wt6e::access_cpp_build_up_directly_eigen_eqn,
        (arg("objective_only"),arg("current_values")))
      .def("reset_mem", &wt6e::reset_mem)
    ;

    typedef postrefine_base prb;
    class_<prb,
           bases<wt6e  > >(
      "postrefine_base", no_init)
      .def(init<int>(arg("n_parameters")))
      .def("access_cpp_build_up_directly_eigen_eqn",&prb::access_cpp_build_up_directly_eigen_eqn,
        (arg("objective_only"),arg("current_values")))
    ;

  }

}}
}} // namespace xfel::boost_python::<anonymous>

BOOST_PYTHON_MODULE(cctbx_large_scale_merging_ext)
{
  cctbx::merging::boost_python::large_scale_merging_init_module();

}