/* boost random/geometric_distribution.hpp header file
 *
 * Copyright Jens Maurer 2000-2001
 * Copyright Steven Watanabe 2011
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 * See http://www.boost.org for most recent version including documentation.
 *
 * $Id$
 *
 * Revision history
 *  2001-02-18  moved to individual header files
 */

#ifndef BOOST_RANDOM_GEOMETRIC_DISTRIBUTION_HPP
#define BOOST_RANDOM_GEOMETRIC_DISTRIBUTION_HPP

#include <boost/config/no_tr1/cmath.hpp>          // std::log
#include <iosfwd>
#include <ios>
#include <boost/assert.hpp>
#include <boost/random/detail/config.hpp>
#include <boost/random/detail/operators.hpp>
#include <boost/random/uniform_01.hpp>

namespace boost {
namespace random {

/**
 * An instantiation of the class template @c geometric_distribution models
 * a \random_distribution.  The distribution produces positive
 * integers which are the number of bernoulli trials
 * with probability @c p required to get one that fails.
 *
 * For the geometric distribution, \f$p(i) = p(1-p)^{i}\f$.
 *
 * @xmlwarning
 * This distribution has been updated to match the C++ standard.
 * Its behavior has changed from the original
 * boost::geometric_distribution.  A backwards compatible
 * wrapper is provided in namespace boost.
 * @endxmlwarning
 */
template<class IntType = int, class RealType = double>
class geometric_distribution
{
public:
    typedef RealType input_type;
    typedef IntType result_type;

    class param_type
    {
    public:

        typedef geometric_distribution distribution_type;

        /** Constructs the parameters with p. */
        explicit param_type(RealType p_arg = RealType(0.5))
          : _p(p_arg)
        {
            BOOST_ASSERT(RealType(0) < _p && _p < RealType(1));
        }

        /** Returns the p parameter of the distribution. */
        RealType p() const { return _p; }

        /** Writes the parameters to a std::ostream. */
        BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, param_type, parm)
        {
            os << parm._p;
            return os;
        }

        /** Reads the parameters from a std::istream. */
        BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, param_type, parm)
        {
            double p_in;
            if(is >> p_in) {
                if(p_in > RealType(0) && p_in < RealType(1)) {
                    parm._p = p_in;
                } else {
                    is.setstate(std::ios_base::failbit);
                }
            }
            return is;
        }

        /** Returns true if the two sets of parameters are equal. */
        BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(param_type, lhs, rhs)
        { return lhs._p == rhs._p; }

        /** Returns true if the two sets of parameters are different. */
        BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(param_type)

    private:
        RealType _p;
    };

    /**
     * Contructs a new geometric_distribution with the paramter @c p.
     *
     * Requires: 0 < p < 1
     */
    explicit geometric_distribution(const RealType& p_arg = RealType(0.5))
      : _p(p_arg)
    {
        BOOST_ASSERT(RealType(0) < _p && _p < RealType(1));
        init();
    }

    /** Constructs a new geometric_distribution from its parameters. */
    explicit geometric_distribution(const param_type& parm)
      : _p(parm.p())
    {
        init();
    }

    // compiler-generated copy ctor and assignment operator are fine

    /** Returns: the distribution parameter @c p  */
    RealType p() const { return _p; }

    /** Returns the smallest value that the distribution can produce. */
    IntType min BOOST_PREVENT_MACRO_SUBSTITUTION () const { return IntType(0); }

    /** Returns the largest value that the distribution can produce. */
    IntType max BOOST_PREVENT_MACRO_SUBSTITUTION () const
    { return (std::numeric_limits<IntType>::max)(); }

    /** Returns the parameters of the distribution. */
    param_type param() const { return param_type(_p); }

    /** Sets the parameters of the distribution. */
    void param(const param_type& parm)
    {
        _p = parm.p();
        init();
    }

    /**
     * Effects: Subsequent uses of the distribution do not depend
     * on values produced by any engine prior to invoking reset.
     */
    void reset() { }

    /**
     * Returns a random variate distributed according to the
     * geometric_distribution.
     */
    template<class Engine>
    result_type operator()(Engine& eng) const
    {
        using std::log;
        using std::floor;
        RealType x = RealType(1) - boost::uniform_01<RealType>()(eng);
        return IntType(floor(log(x) / _log_1mp));
    }

    /**
     * Returns a random variate distributed according to the
     * geometric distribution with parameters specified by param.
     */
    template<class Engine>
    result_type operator()(Engine& eng, const param_type& parm) const
    { return geometric_distribution(parm)(eng); }

    /** Writes the distribution to a @c std::ostream. */
    BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, geometric_distribution, gd)
    {
        os << gd._p;
        return os;
    }

    /** Reads the distribution from a @c std::istream. */
    BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, geometric_distribution, gd)
    {
        param_type parm;
        if(is >> parm) {
            gd.param(parm);
        }
        return is;
    }

    /**
     * Returns true if the two distributions will produce identical
     * sequences of values given equal generators.
     */
    BOOST_RANDOM_DETAIL_EQUALITY_OPERATOR(geometric_distribution, lhs, rhs)
    { return lhs._p == rhs._p; }

    /**
     * Returns true if the two distributions may produce different
     * sequences of values given equal generators.
     */
    BOOST_RANDOM_DETAIL_INEQUALITY_OPERATOR(geometric_distribution)

private:

    /// \cond show_private

    void init()
    {
        using std::log;
        _log_1mp = log(1 - _p);
    }

    RealType _p;
    RealType _log_1mp;

    /// \endcond
};

} // namespace random

/// \cond show_deprecated

/**
 * Provided for backwards compatibility.  This class is
 * deprecated.  It provides the old behavior of geometric_distribution
 * with \f$p(i) = (1-p) p^{i-1}\f$.
 */
template<class IntType = int, class RealType = double>
class geometric_distribution
{
public:
    typedef RealType input_type;
    typedef IntType result_type;

    explicit geometric_distribution(RealType p_arg = RealType(0.5))
      : _impl(1 - p_arg) {}

    RealType p() const { return 1 - _impl.p(); }

    void reset() {}

    template<class Engine>
    IntType operator()(Engine& eng) const { return _impl(eng) + IntType(1); }

    BOOST_RANDOM_DETAIL_OSTREAM_OPERATOR(os, geometric_distribution, gd)
    {
        os << gd.p();
        return os;
    }

    BOOST_RANDOM_DETAIL_ISTREAM_OPERATOR(is, geometric_distribution, gd)
    {
        RealType val;
        if(is >> val) {
            typename impl_type::param_type impl_param(1 - val);
            gd._impl.param(impl_param);
        }
        return is;
    }

private:
    typedef random::geometric_distribution<IntType, RealType> impl_type;
    impl_type _impl;
};

/// \endcond

} // namespace boost

#endif // BOOST_RANDOM_GEOMETRIC_DISTRIBUTION_HPP


#ifndef BART_SRC_CONVERGENCE_MOMENTS_SINGLE_MOMENT_L1_NORM_H_
#define BART_SRC_CONVERGENCE_MOMENTS_SINGLE_MOMENT_L1_NORM_H_

#include "convergence/convergence_checker.hpp"

#include <deal.II/lac/vector.h>

//! Convergence checkers for problem vector moments
namespace bart::convergence::moments {

/*! \brief Checks for convergence between flux moments using the percentage
 * change in the L1 norm, compared to the current iteration (\f$i\f$):
 *
 * \f[
 *
 * \Delta_i = \frac{|\phi_i - \phi_{i-1}|_{1}}{|\phi_{i}|_{1}}
 *
 * \f]
 *
 * Convergence is achieved if \f$\Delta_i \leq \Delta_{\text{max}}\f$.
 * */

class ConvergenceCheckerL1Norm : public ConvergenceChecker<dealii::Vector<double>> {
 public:
  using Vector = dealii::Vector<double>;
  /*! \brief Default constructor, setting max delta to \f$10^{-6}\f$. */
  explicit ConvergenceCheckerL1Norm(const double max_delta = 1e-6);
  auto SetMaxDelta(const double& to_set) -> void override;
  auto IsConverged(const Vector& current_iteration, const Vector& previous_iteration) -> bool override;
};

} // namespace bart::convergence::moments

#endif // BART_SRC_CONVERGENCE_MOMENTS_SINGLE_MOMENT_L1_NORM_H_


#ifndef FIELD_MATH_H_
#define FIELD_MATH_H_

#include <Eigen/Core>
#include <Eigen/Dense>
#include <algorithm>
//#include <glm/glm.hpp>
#include <vector>
using namespace Eigen;

struct DEdge
{
    DEdge()
    : x(0), y(0)
    {}
    DEdge(int _x, int _y) {
        if (_x > _y)
            x = _y, y = _x;
        else
            x = _x, y = _y;
    }
    bool operator<(const DEdge& e) const {
        return (x < e.x) || (x == e.x && y < e.y);
    }
    bool operator==(const DEdge& e) const {
        return x == e.x && y == e.y;
    }
    bool operator!=(const DEdge& e) const {
        return x != e.x || y != e.y;
    }
    int x, y;
};

inline int get_parents(std::vector<std::pair<int, int>>& parents, int j) {
    if (j == parents[j].first) return j;
    int k = get_parents(parents, parents[j].first);
    parents[j].second = (parents[j].second + parents[parents[j].first].second) % 4;
    parents[j].first = k;
    return k;
}

inline int get_parents_orient(std::vector<std::pair<int, int>>& parents, int j) {
    if (j == parents[j].first) return parents[j].second;
    return (parents[j].second + get_parents_orient(parents, parents[j].first)) % 4;
}

inline double fast_acos(double x) {
    double negate = double(x < 0.0f);
    x = std::abs(x);
    double ret = -0.0187293f;
    ret *= x;
    ret = ret + 0.0742610f;
    ret *= x;
    ret = ret - 0.2121144f;
    ret *= x;
    ret = ret + 1.5707288f;
    ret = ret * std::sqrt(1.0f - x);
    ret = ret - 2.0f * negate * ret;
    return negate * (double)M_PI + ret;
}

inline double signum(double value) { return std::copysign((double)1, value); }

/// Always-positive modulo function (assumes b > 0)
inline int modulo(int a, int b) {
    int r = a % b;
    return (r < 0) ? r + b : r;
}

inline Vector3d rotate90_by(const Vector3d &q, const Vector3d &n, int amount) {
    return ((amount & 1) ? (n.cross(q)) : q) * (amount < 2 ? 1.0f : -1.0f);
}

inline Vector2i rshift90(Vector2i shift, int amount) {
    if (amount & 1) shift = Vector2i(-shift.y(), shift.x());
    if (amount >= 2) shift = -shift;
    return shift;
}

inline std::pair<int, int> compat_orientation_extrinsic_index_4(const Vector3d &q0,
                                                                const Vector3d &n0,
                                                                const Vector3d &q1,
                                                                const Vector3d &n1) {
    const Vector3d A[2] = {q0, n0.cross(q0)};
    const Vector3d B[2] = {q1, n1.cross(q1)};

    double best_score = -std::numeric_limits<double>::infinity();
    int best_a = 0, best_b = 0;

    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            double score = std::abs(A[i].dot(B[j]));
            if (score > best_score) {
                best_a = i;
                best_b = j;
                best_score = score;
            }
        }
    }

    if (A[best_a].dot(B[best_b]) < 0) best_b += 2;

    return std::make_pair(best_a, best_b);
}

inline std::pair<Vector3d, Vector3d> compat_orientation_extrinsic_4(const Vector3d &q0,
                                                                    const Vector3d &n0,
                                                                    const Vector3d &q1,
                                                                    const Vector3d &n1) {
    const Vector3d A[2] = {q0, n0.cross(q0)};
    const Vector3d B[2] = {q1, n1.cross(q1)};

    double best_score = -std::numeric_limits<double>::infinity();
    int best_a = 0, best_b = 0;

    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 2; ++j) {
            double score = std::abs(A[i].dot(B[j]));
            if (score > best_score + 1e-6) {
                best_a = i;
                best_b = j;
                best_score = score;
            }
        }
    }

    const double dp = A[best_a].dot(B[best_b]);
    return std::make_pair(A[best_a], B[best_b] * signum(dp));
}

inline Vector3d middle_point(const Vector3d &p0, const Vector3d &n0, const Vector3d &p1,
                             const Vector3d &n1) {
    /* How was this derived?
     *
     * Minimize \|x-p0\|^2 + \|x-p1\|^2, where
     * dot(n0, x) == dot(n0, p0)
     * dot(n1, x) == dot(n1, p1)
     *
     * -> Lagrange multipliers, set derivative = 0
     *  Use first 3 equalities to write x in terms of
     *  lambda_1 and lambda_2. Substitute that into the last
     *  two equations and solve for the lambdas. Finally,
     *  add a small epsilon term to avoid issues when n1=n2.
     */
    double n0p0 = n0.dot(p0), n0p1 = n0.dot(p1), n1p0 = n1.dot(p0), n1p1 = n1.dot(p1),
           n0n1 = n0.dot(n1), denom = 1.0f / (1.0f - n0n1 * n0n1 + 1e-4f),
           lambda_0 = 2.0f * (n0p1 - n0p0 - n0n1 * (n1p0 - n1p1)) * denom,
           lambda_1 = 2.0f * (n1p0 - n1p1 - n0n1 * (n0p1 - n0p0)) * denom;

    return 0.5f * (p0 + p1) - 0.25f * (n0 * lambda_0 + n1 * lambda_1);
}

inline Vector3d position_floor_4(const Vector3d &o, const Vector3d &q, const Vector3d &n,
                                 const Vector3d &p, double scale_x, double scale_y,
                                 double inv_scale_x, double inv_scale_y) {
    Vector3d t = n.cross(q);
    Vector3d d = p - o;
    return o + q * std::floor(q.dot(d) * inv_scale_x) * scale_x +
           t * std::floor(t.dot(d) * inv_scale_y) * scale_y;
}

inline std::pair<Vector3d, Vector3d> compat_position_extrinsic_4(
    const Vector3d &p0, const Vector3d &n0, const Vector3d &q0, const Vector3d &o0,
    const Vector3d &p1, const Vector3d &n1, const Vector3d &q1, const Vector3d &o1, double scale_x,
    double scale_y, double inv_scale_x, double inv_scale_y, double scale_x_1, double scale_y_1,
    double inv_scale_x_1, double inv_scale_y_1) {
    Vector3d t0 = n0.cross(q0), t1 = n1.cross(q1);
    Vector3d middle = middle_point(p0, n0, p1, n1);
    Vector3d o0p =
        position_floor_4(o0, q0, n0, middle, scale_x, scale_y, inv_scale_x, inv_scale_y);
    Vector3d o1p =
        position_floor_4(o1, q1, n1, middle, scale_x_1, scale_y_1, inv_scale_x_1, inv_scale_y_1);

    double best_cost = std::numeric_limits<double>::infinity();
    int best_i = -1, best_j = -1;

    for (int i = 0; i < 4; ++i) {
        Vector3d o0t = o0p + (q0 * (i & 1) * scale_x + t0 * ((i & 2) >> 1) * scale_y);
        for (int j = 0; j < 4; ++j) {
            Vector3d o1t = o1p + (q1 * (j & 1) * scale_x_1 + t1 * ((j & 2) >> 1) * scale_y_1);
            double cost = (o0t - o1t).squaredNorm();

            if (cost < best_cost) {
                best_i = i;
                best_j = j;
                best_cost = cost;
            }
        }
    }

    return std::make_pair(
        o0p + (q0 * (best_i & 1) * scale_x + t0 * ((best_i & 2) >> 1) * scale_y),
        o1p + (q1 * (best_j & 1) * scale_x_1 + t1 * ((best_j & 2) >> 1) * scale_y_1));
}

inline Vector3d position_round_4(const Vector3d &o, const Vector3d &q, const Vector3d &n,
                                 const Vector3d &p, double scale_x, double scale_y,
                                 double inv_scale_x, double inv_scale_y) {
    Vector3d t = n.cross(q);
    Vector3d d = p - o;
    return o + q * std::round(q.dot(d) * inv_scale_x) * scale_x +
           t * std::round(t.dot(d) * inv_scale_y) * scale_y;
}

inline Vector2i position_floor_index_4(const Vector3d &o, const Vector3d &q, const Vector3d &n,
                                       const Vector3d &p, double /* unused */, double /* unused */,
                                       double inv_scale_x, double inv_scale_y) {
    Vector3d t = n.cross(q);
    Vector3d d = p - o;
    return Vector2i((int)std::floor(q.dot(d) * inv_scale_x),
                    (int)std::floor(t.dot(d) * inv_scale_y));
}

inline std::pair<Vector2i, Vector2i> compat_position_extrinsic_index_4(
    const Vector3d &p0, const Vector3d &n0, const Vector3d &q0, const Vector3d &o0,
    const Vector3d &p1, const Vector3d &n1, const Vector3d &q1, const Vector3d &o1, double scale_x,
    double scale_y, double inv_scale_x, double inv_scale_y, double scale_x_1, double scale_y_1,
    double inv_scale_x_1, double inv_scale_y_1, double *error) {
    Vector3d t0 = n0.cross(q0), t1 = n1.cross(q1);
    Vector3d middle = middle_point(p0, n0, p1, n1);
    Vector2i o0p =
        position_floor_index_4(o0, q0, n0, middle, scale_x, scale_y, inv_scale_x, inv_scale_y);
    Vector2i o1p = position_floor_index_4(o1, q1, n1, middle, scale_x_1, scale_y_1, inv_scale_x_1,
                                          inv_scale_y_1);

    double best_cost = std::numeric_limits<double>::infinity();
    int best_i = -1, best_j = -1;

    for (int i = 0; i < 4; ++i) {
        Vector3d o0t =
            o0 + (q0 * ((i & 1) + o0p[0]) * scale_x + t0 * (((i & 2) >> 1) + o0p[1]) * scale_y);
        for (int j = 0; j < 4; ++j) {
            Vector3d o1t = o1 + (q1 * ((j & 1) + o1p[0]) * scale_x_1 +
                                 t1 * (((j & 2) >> 1) + o1p[1]) * scale_y_1);
            double cost = (o0t - o1t).squaredNorm();

            if (cost < best_cost) {
                best_i = i;
                best_j = j;
                best_cost = cost;
            }
        }
    }
    if (error) *error = best_cost;

    return std::make_pair(Vector2i((best_i & 1) + o0p[0], ((best_i & 2) >> 1) + o0p[1]),
                          Vector2i((best_j & 1) + o1p[0], ((best_j & 2) >> 1) + o1p[1]));
}

inline void coordinate_system(const Vector3d &a, Vector3d &b, Vector3d &c) {
    if (std::abs(a.x()) > std::abs(a.y())) {
        double invLen = 1.0f / std::sqrt(a.x() * a.x() + a.z() * a.z());
        c = Vector3d(a.z() * invLen, 0.0f, -a.x() * invLen);
    } else {
        double invLen = 1.0f / std::sqrt(a.y() * a.y() + a.z() * a.z());
        c = Vector3d(0.0f, a.z() * invLen, -a.y() * invLen);
    }
    b = c.cross(a);
}

inline Vector3d rotate_vector_into_plane(Vector3d q, const Vector3d &source_normal,
                                         const Vector3d &target_normal) {
    const double cosTheta = source_normal.dot(target_normal);
    if (cosTheta < 0.9999f) {
        if (cosTheta < -0.9999f) return -q;
        Vector3d axis = source_normal.cross(target_normal);
        q = q * cosTheta + axis.cross(q) +
            axis * (axis.dot(q) * (1.0 - cosTheta) / axis.dot(axis));
    }
    return q;
}

inline Vector3d Travel(Vector3d p, const Vector3d &dir, double &len, int &f, VectorXi &E2E,
                       MatrixXd &V, MatrixXi &F, MatrixXd &NF,
                       std::vector<MatrixXd> &triangle_space, double *tx = 0, double *ty = 0) {
    Vector3d N = NF.col(f);
    Vector3d pt = (dir - dir.dot(N) * N).normalized();
    int prev_id = -1;
    int count = 0;
    while (len > 0) {
        count += 1;
        Vector3d t1 = V.col(F(1, f)) - V.col(F(0, f));
        Vector3d t2 = V.col(F(2, f)) - V.col(F(0, f));
        Vector3d N = NF.col(f);
        //		printf("point dis: %f\n", (p - V.col(F(1, f))).dot(N));
        int edge_id = f * 3;
        double max_len = 1e30;
        bool found = false;
        int next_id, next_f;
        Vector3d next_q;
        Matrix3d m, n;
        m.col(0) = t1;
        m.col(1) = t2;
        m.col(2) = N;
        n = m.inverse();
        MatrixXd &T = triangle_space[f];
        VectorXd coord = T * Vector3d(p - V.col(F(0, f)));
        VectorXd dirs = (T * pt);

        double lens[3];
        lens[0] = -coord.y() / dirs.y();
        lens[1] = (1 - coord.x() - coord.y()) / (dirs.x() + dirs.y());
        lens[2] = -coord.x() / dirs.x();
        for (int fid = 0; fid < 3; ++fid) {
            if (fid + edge_id == prev_id) continue;

            if (lens[fid] >= 0 && lens[fid] < max_len) {
                max_len = lens[fid];
                next_id = E2E[edge_id + fid];
                next_f = next_id;
                if (next_f != -1) next_f /= 3;
                found = true;
            }
        }
        if (!found) {
            printf("error...\n");
            exit(0);
        }
        //		printf("status: %f %f %d\n", len, max_len, f);
        if (max_len >= len) {
            if (tx && ty) {
                *tx = coord.x() + dirs.x() * len;
                *ty = coord.y() + dirs.y() * len;
            }
            p = p + len * pt;
            len = 0;
            return p;
        }
        p = V.col(F(0, f)) + t1 * (coord.x() + dirs.x() * max_len) +
            t2 * (coord.y() + dirs.y() * max_len);
        len -= max_len;
        if (next_f == -1) {
            if (tx && ty) {
                *tx = coord.x() + dirs.x() * max_len;
                *ty = coord.y() + dirs.y() * max_len;
            }
            return p;
        }
        pt = rotate_vector_into_plane(pt, NF.col(f), NF.col(next_f));
        f = next_f;
        prev_id = next_id;
    }
    return p;
}
inline Vector3d TravelField(Vector3d p, Vector3d &pt, double &len, int &f, VectorXi &E2E,
                            MatrixXd &V, MatrixXi &F, MatrixXd &NF, MatrixXd &QF, MatrixXd &QV,
                            MatrixXd &NV, std::vector<MatrixXd> &triangle_space, double *tx = 0,
                            double *ty = 0, Vector3d *dir_unfold = 0) {
    Vector3d N = NF.col(f);
    pt = (pt - pt.dot(N) * N).normalized();
    int prev_id = -1;
    int count = 0;
    std::vector<Vector3d> Ns;

    auto FaceQFromVertices = [&](int f, double tx, double ty) {
        const Vector3d &n = NF.col(f);
        const Vector3d &q_1 = QV.col(F(0, f)), &q_2 = QV.col(F(1, f)), &q_3 = QV.col(F(2, f));
        const Vector3d &n_1 = NV.col(F(0, f)), &n_2 = NV.col(F(1, f)), &n_3 = NV.col(F(2, f));
        Vector3d q_1n = rotate_vector_into_plane(q_1, n_1, n);
        Vector3d q_2n = rotate_vector_into_plane(q_2, n_2, n);
        Vector3d q_3n = rotate_vector_into_plane(q_3, n_3, n);
        auto orient = compat_orientation_extrinsic_4(q_1n, n, q_2n, n);
        Vector3d q = (orient.first * tx + orient.second * ty).normalized();
        orient = compat_orientation_extrinsic_4(q, n, q_3n, n);
        q = (orient.first * (tx + ty) + orient.second * (1 - tx - ty)).normalized();
        return q;
    };

    auto BestQFromGivenQ = [&](const Vector3d &n, const Vector3d &q, const Vector3d &given_q) {
        Vector3d q_1 = n.cross(q);
        double t1 = q.dot(given_q);
        double t2 = q_1.dot(given_q);
        if (fabs(t1) > fabs(t2)) {
            if (t1 > 0.0)
                return Vector3d(q);
            else
                return Vector3d(-q);
        } else {
            if (t2 > 0.0)
                return Vector3d(q_1);
            else
                return Vector3d(-q_1);
        }
    };

    while (len > 0) {
        count += 1;
        Vector3d t1 = V.col(F(1, f)) - V.col(F(0, f));
        Vector3d t2 = V.col(F(2, f)) - V.col(F(0, f));
        Vector3d N = NF.col(f);
        Ns.push_back(N);
        //		printf("point dis: %f\n", (p - V.col(F(1, f))).dot(N));
        int edge_id = f * 3;
        double max_len = 1e30;
        bool found = false;
        int next_id, next_f;
        Vector3d next_q;
        Matrix3d m, n;
        m.col(0) = t1;
        m.col(1) = t2;
        m.col(2) = N;
        n = m.inverse();
        MatrixXd &T = triangle_space[f];
        VectorXd coord = T * Vector3d(p - V.col(F(0, f)));
        VectorXd dirs = (T * pt);
        double lens[3];
        lens[0] = -coord.y() / dirs.y();
        lens[1] = (1 - coord.x() - coord.y()) / (dirs.x() + dirs.y());
        lens[2] = -coord.x() / dirs.x();
        for (int fid = 0; fid < 3; ++fid) {
            if (fid + edge_id == prev_id) continue;

            if (lens[fid] >= 0 && lens[fid] < max_len) {
                max_len = lens[fid];
                next_id = E2E[edge_id + fid];
                next_f = next_id;
                if (next_f != -1) next_f /= 3;
                found = true;
            }
        }
        double w1 = (coord.x() + dirs.x() * max_len);
        double w2 = (coord.y() + dirs.y() * max_len);
        if (w1 < 0) w1 = 0.0f;
        if (w2 < 0) w2 = 0.0f;
        if (w1 + w2 > 1) {
            double w = w1 + w2;
            w1 /= w;
            w2 /= w;
        }

        if (!found) {
            printf("error...\n");
            exit(0);
        }
        //		printf("status: %f %f %d\n", len, max_len, f);
        if (max_len >= len) {
            if (tx && ty) {
                *tx = w1;
                *ty = w2;
            }
            Vector3d ideal_q = FaceQFromVertices(f, *tx, *ty);
            *dir_unfold = BestQFromGivenQ(NF.col(f), ideal_q, *dir_unfold);
            for (int i = Ns.size() - 1; i > 0; --i) {
                *dir_unfold = rotate_vector_into_plane(*dir_unfold, Ns[i], Ns[i - 1]);
            }
            p = p + len * pt;
            len = 0;
            return p;
        }
        p = V.col(F(0, f)) + t1 * w1 + t2 * w2;
        len -= max_len;
        if (next_f == -1) {
            if (tx && ty) {
                *tx = w1;
                *ty = w2;
            }
            Vector3d ideal_q = FaceQFromVertices(f, *tx, *ty);
            *dir_unfold = BestQFromGivenQ(NF.col(f), ideal_q, *dir_unfold);
            for (int i = Ns.size() - 1; i > 0; --i) {
                *dir_unfold = rotate_vector_into_plane(*dir_unfold, Ns[i], Ns[i - 1]);
            }
            return p;
        }
        pt = rotate_vector_into_plane(pt, NF.col(f), NF.col(next_f));
        //		pt = BestQFromGivenQ(NF.col(next_f), QF.col(next_f), pt);
        if (dir_unfold) {
            *dir_unfold = BestQFromGivenQ(NF.col(next_f), QF.col(next_f), *dir_unfold);
        }
        f = next_f;
        prev_id = next_id;
    }

    return p;
}

#endif


// Build with:
// emcc --bind -I${EMSCRIPTEN}/system/include -I${QUANTLIB} -I${BOOST} -O3 -s MODULARIZE=1 -s "EXTRA_EXPORTED_RUNTIME_METHODS=['addOnPostRun']" -s EXPORT_NAME=QuantLib -s TOTAL_MEMORY=64MB -o quantlib-embind.js quantlib-embind.cpp ${QUANTLIB}/ql/.libs/libQuantLib.a

// https://www.quantlib.org/slides/dima-ql-intro-1.pdf

#include <math.h>
#include <iostream>
#include <malloc.h>
#include <ql/quantlib.hpp>
#include <emscripten/bind.h>
#include <boost/foreach.hpp>

using namespace std;
using namespace QuantLib;
using namespace emscripten;

namespace
{

    val emval_test_mallinfo()
    {
        const auto &i = mallinfo();
        unsigned long t =
            std::chrono::system_clock::now().time_since_epoch() /
            std::chrono::milliseconds(1);
        val rv(val::object());
        rv.set("arena", val(i.arena));
        rv.set("ordblks", val(i.ordblks));
        rv.set("smblks", val(i.smblks));
        rv.set("hblks", val(i.hblks));
        rv.set("hblkhd", val(i.hblkhd));
        rv.set("usmblks", val(i.usmblks));
        rv.set("fsmblks", val(i.fsmblks));
        rv.set("uordblks", val(i.uordblks));
        rv.set("fordblks", val(i.fordblks));
        rv.set("keepcost", val(i.keepcost));
        rv.set("time", val(t));
        return rv;
    }

    void setValuationDate(Date &date)
    {
        Settings::instance().evaluationDate() = date;
    }

    string timeUnitToString(Period &p)
    {
        stringstream stream;
        stream << p;
        return stream.str();
    }

    double swapNpv(VanillaSwap &swap)
    {
        return swap.NPV();
    }

    string calendarToISOString(Date &d)
    {
        stringstream stream;
        stream << d.year() << "-" << setfill('0') << setw(2) << (int)d.month() << "-" << setfill('0') << setw(2) << d.dayOfMonth();
        return stream.str();
    }

    string calendarToString(Date &d)
    {
        stringstream stream;
        stream << d;
        return stream.str();
    }

    string interestRateToString(InterestRate &r)
    {
        stringstream stream;
        stream.precision(4);
        stream << r;
        return stream.str();
    }

    Date calendarAdvance(Calendar &cal, Date &d, Integer n, TimeUnit unit, BusinessDayConvention c, bool endOfMonth)
    {
        return cal.advance(d, n, unit, c, endOfMonth);
    }

    Date *dateFromISOString(string s)
    {
        int y, m, d;
        sscanf(s.c_str(), "%d-%d-%d", &y, &m, &d);
        return new Date(d, (Month)m, y);
    }

    void swapSetPricingEngine(VanillaSwap &swap, Handle<YieldTermStructure> &discountingTermStructure)
    {
        boost::shared_ptr<PricingEngine> swapEngine(new DiscountingSwapEngine(discountingTermStructure));
        swap.setPricingEngine(swapEngine);
    }

    Handle<YieldTermStructure> *createLogLinearYieldTermStructure(vector<Date> &dates, vector<DiscountFactor> &discountFactor, DayCounter &dayCounter)
    {
        // auto curve = new InterpolatedDiscountCurve<LogLinear>(dates, discountFactor, dayCounter);
        // InterpolatedDiscountCurve<LogLinear> curve(InterpolatedDiscountCurve<LogLinear>(dates, discountFactor, dayCounter));
        // auto yts = boost::make_shared<InterpolatedDiscountCurve<LogLinear>>(*curve);
        boost::shared_ptr<YieldTermStructure> yts(new InterpolatedDiscountCurve<LogLinear>(dates, discountFactor, dayCounter));
        return new Handle<YieldTermStructure>(yts);
    }

    VanillaSwap *createVanillaSwap(VanillaSwap::Type type, Real nominal, const Schedule &fixedSchedule, Rate fixedRate, DayCounter &fixedDayCount,
                                   const Schedule &floatSchedule, IborIndex &iborIndex, Spread spread, DayCounter &floatingDayCount)
    {
        boost::shared_ptr<IborIndex> iborIndexPtr = boost::make_shared<IborIndex>(iborIndex);
        VanillaSwap *res = new VanillaSwap(type, nominal, fixedSchedule, fixedRate, fixedDayCount, floatSchedule, iborIndexPtr, spread, floatingDayCount);
        return res;
    }

    Handle<Quote> *createQuoteHandle(Rate rate)
    {
        boost::shared_ptr<SimpleQuote> ptrSimpleQuote = boost::make_shared<SimpleQuote>(rate);
        boost::shared_ptr<Quote> ptrQuote(ptrSimpleQuote);
        return new Handle<Quote>(ptrQuote);
    }

    Real handleQuoteValue(Handle<Quote> &quote)
    {
        return ((ext::shared_ptr<Quote> &)quote.currentLink())->value();
    }

    OISRateHelper *createOISRateHelper(Natural settlementDays, Period &tenor, Handle<Quote> &fixedRate, OvernightIndex &overnightIndex
                                       /*, Handle<YieldTermStructure> &discountingCurve, bool telescopicValueDates, Natural paymentLag,
                                   BusinessDayConvention paymentConvention, Frequency paymentFrequency, Calendar &paymentCalendar, Period &forwardStart,
                                   Spread overnightSpread */
    )
    {
        ext::shared_ptr<OvernightIndex> ptrOvernightIndex(&overnightIndex);
        return new OISRateHelper(settlementDays, tenor, fixedRate, ptrOvernightIndex /*, discountingCurve,
                                  telescopicValueDates, paymentLag, paymentConvention, paymentFrequency, paymentCalendar, forwardStart, overnightSpread*/
        );
    }

    DatedOISRateHelper *createDatedOISRateHelper(Date &startDate, Date &endDate, Handle<Quote> &fixedRate, OvernightIndex &overnightIndex)
    {
        ext::shared_ptr<OvernightIndex> ptrOvernightIndex(&overnightIndex);
        return new DatedOISRateHelper(startDate, endDate, fixedRate, ptrOvernightIndex);
    }

    FuturesRateHelper *createFuturesRateHelper(Handle<Quote> &price, Date &iborStartDate, Natural lengthInMonths, Calendar &calendar, BusinessDayConvention convention, bool endOfMonth, DayCounter &dayCounter)
    {
        return new FuturesRateHelper(price, iborStartDate, lengthInMonths, calendar, convention, endOfMonth, dayCounter);
    }

    SwapRateHelper *createSwapRateHelper(Handle<Quote> &rate, Period &tenor, Calendar &calendar, Frequency fixedFrequency,
                                         BusinessDayConvention fixedConvention, DayCounter &fixedDayCount, IborIndex &iborIndex)
    {
        boost::shared_ptr<IborIndex> ptrIborIndex = boost::make_shared<IborIndex>(iborIndex);
        // ext::shared_ptr<IborIndex> ptrIborIndex(&iborIndex);
        return new SwapRateHelper(rate, tenor, calendar, fixedFrequency, fixedConvention, fixedDayCount, ptrIborIndex);
    }

    Date *immNextDate(const Date &date, bool mainCycle)
    {
        return new Date(IMM::nextDate(date, mainCycle).serialNumber());
    }

    PiecewiseYieldCurve<Discount, Linear> *createPiecewiseYieldCurveDiscountLinear(Date &referenceDate, vector<RateHelper *> &instruments, DayCounter &dayCounter)
    {
        vector<boost::shared_ptr<RateHelper>> ptrs;
        for (int i = 0; i < instruments.size(); i++)
        {
            // boost::shared_ptr<RateHelper> ptr = boost::make_shared<RateHelper>(instruments[i]);
            // ext::shared_ptr<RateHelper> ptr(&instruments[i]);
            boost::shared_ptr<RateHelper> ptr(instruments[i]);
            ptrs.push_back(ptr);
        }
        return new PiecewiseYieldCurve<Discount, Linear>(referenceDate, ptrs, dayCounter);
    }

    void handle_eptr(std::exception_ptr eptr) // passing by value is ok
    {
        try
        {
            if (eptr)
            {
                std::rethrow_exception(eptr);
            }
        }
        catch (const std::exception &e)
        {
            std::cout << "Caught exception \"" << e.what() << "\"\n";
        }
    }

    InterestRate *yieldTermStructureZeroRate(YieldTermStructure &yieldTermStructure, Date &d, DayCounter &resultDayCounter, Compounding comp, Frequency freq, bool extrapolate)
    {
        // try
        // {
        // std::cout << "Uno" << std::endl;
        // auto dates = yieldTermStructure.jumpDates();
        // std::cout << "Due" << std::endl;
        // for (int i=0; i<dates.size(); i++)
        // {
        //     std::cout << dates[i] << std::endl;
        // }
        // std::cout << "Tre" << std::endl;
        auto r = yieldTermStructure.zeroRate(d, resultDayCounter, comp, freq, extrapolate);
        // std::cout << "Quattro" << std::endl;
        return new InterestRate(r.rate(), r.dayCounter(), r.compounding(), r.frequency()); // Copies from stack to heap memory
        // }
        // catch (...)
        // {
        //     std::exception_ptr eptr = std::current_exception();
        //     handle_eptr(eptr);
        //     // std::cout << eptr->what() << std::endl;
        // }
        // return NULL;
    }

    DiscountFactor yieldTermStructureDiscount(YieldTermStructure &yieldTermStructure, Date &d, bool extrapolate)
    {
        return yieldTermStructure.discount(d, extrapolate);
    }

    InterestRate *yieldTermStructureForwardRate(YieldTermStructure &yieldTermStructure, Date &d1, Date &d2,
                                                DayCounter &resultDayCounter, Compounding comp, Frequency freq, bool extrapolate)
    {
        auto r = yieldTermStructure.forwardRate(d1, d2, resultDayCounter, comp, freq, extrapolate);
        return new InterestRate(r.rate(), r.dayCounter(), r.compounding(), r.frequency()); // Copies from stack to heap memory
    }

    EMSCRIPTEN_BINDINGS(quantlib)
    {
        emscripten::constant<string>("version", QL_VERSION);
        enum_<BusinessDayConvention>("BusinessDayConvention")
            .value("Following", Following)
            .value("ModifiedFollowing", ModifiedFollowing)
            .value("Preceding", Preceding)
            .value("ModifiedPreceding", ModifiedPreceding)
            .value("Unadjusted", Unadjusted)
            .value("HalfMonthModifiedFollowing", HalfMonthModifiedFollowing)
            .value("Nearest", Nearest);
        enum_<Month>("Month")
            .value("January", January)
            .value("February", February)
            .value("March", March)
            .value("April", April)
            .value("May", May)
            .value("June", June)
            .value("July", July)
            .value("August", August)
            .value("September", September)
            .value("October", October)
            .value("November", November)
            .value("December", December)
            .value("Jan", Jan)
            .value("Feb", Feb)
            .value("Mar", Mar)
            .value("Apr", Apr)
            .value("May", May)
            .value("Jun", Jun)
            .value("Jul", Jul)
            .value("Aug", Aug)
            .value("Sep", Sep)
            .value("October", Oct)
            .value("Nov", Nov)
            .value("Dec", Dec);
        enum_<TimeUnit>("TimeUnit")
            .value("D", Days)
            .value("W", Weeks)
            .value("M", Months)
            .value("Y", Years)
            .value("Days", Days)
            .value("Weeks", Weeks)
            .value("Months", Months)
            .value("Years", Years)
            .value("Hours", Hours)
            .value("Minutes", Minutes)
            .value("Seconds", Seconds)
            .value("Milliseconds", Milliseconds)
            .value("Microseconds", Microseconds);
        enum_<DateGeneration::Rule>("DateGenerationRule")
            .value("Backward", DateGeneration::Backward)
            .value("Forward", DateGeneration::Forward)
            .value("Zero", DateGeneration::Zero)
            .value("ThirdWednesday", DateGeneration::ThirdWednesday)
            .value("Twentieth", DateGeneration::Twentieth)
            .value("TwentiethIMM", DateGeneration::TwentiethIMM)
            .value("OldCDS", DateGeneration::OldCDS)
            .value("CDS", DateGeneration::CDS)
            .value("CDS2015", DateGeneration::CDS2015);
        enum_<Thirty360::Convention>("Thirty360Convention")
            .value("USA", Thirty360::USA)
            .value("BondBasis", Thirty360::BondBasis)
            .value("European", Thirty360::European)
            .value("EurobondBasis", Thirty360::EurobondBasis)
            .value("Italian", Thirty360::Italian)
            .value("German", Thirty360::German);
        enum_<ActualActual::Convention>("ActualActualConvention")
            .value("ISMA", ActualActual::ISMA)
            .value("Bond", ActualActual::Bond)
            .value("ISDA", ActualActual::ISDA)
            .value("Historical", ActualActual::Historical)
            .value("Actual365", ActualActual::Actual365)
            .value("AFB", ActualActual::AFB)
            .value("Euro", ActualActual::Euro);
        enum_<VanillaSwap::Type>("VanillaSwapType")
            .value("Payer", VanillaSwap::Payer)
            .value("Receiver", VanillaSwap::Receiver);
        enum_<Weekday>("Weekday")
            .value("Sunday", Sunday)
            .value("Monday", Monday)
            .value("Tuesday", Tuesday)
            .value("Wednesday", Wednesday)
            .value("Thursday", Thursday)
            .value("Friday", Friday)
            .value("Saturday", Saturday)
            .value("Sun", Sun)
            .value("Mon", Mon)
            .value("Tue", Tue)
            .value("Wed", Wed)
            .value("Thu", Thu)
            .value("Fri", Fri)
            .value("Sat", Sat);
        enum_<Pillar::Choice>("PillarChoice")
            .value("MaturityDate", Pillar::Choice::MaturityDate)
            .value("LastRelevantDate", Pillar::Choice::LastRelevantDate)
            .value("CustomDate", Pillar::Choice::CustomDate);
        enum_<UnitedStates::Market>("UnitedStatesMarket")
            .value("Settlement", UnitedStates::Market::Settlement)
            .value("NYSE", UnitedStates::Market::NYSE)
            .value("GovernmentBond", UnitedStates::Market::GovernmentBond)
            .value("NERC", UnitedStates::Market::NERC)
            .value("LiborImpact", UnitedStates::Market::LiborImpact)
            .value("FederalReserve", UnitedStates::Market::FederalReserve);
        enum_<UnitedKingdom::Market>("UnitedKingdomMarket")
            .value("Settlement", UnitedKingdom::Market::Settlement)
            .value("Exchange", UnitedKingdom::Market::Exchange)
            .value("Metals", UnitedKingdom::Market::Metals);
        enum_<JointCalendarRule>("JointCalendarRule")
            .value("JoinHolidays", JointCalendarRule::JoinHolidays)
            .value("JoinBusinessDays", JointCalendarRule::JoinBusinessDays);
        enum_<Frequency>("Frequency")
            .value("NoFrequency", NoFrequency)
            .value("Once", Once)
            .value("Annual", Annual)
            .value("Semiannual", Semiannual)
            .value("EveryFourthMonth", EveryFourthMonth)
            .value("Quarterly", Quarterly)
            .value("Bimonthly", Bimonthly)
            .value("Monthly", Monthly)
            .value("EveryFourthWeek", EveryFourthWeek)
            .value("Biweekly", Biweekly)
            .value("Weekly", Weekly)
            .value("Daily", Daily)
            .value("OtherFrequency", OtherFrequency);
        enum_<Compounding>("Compounding")
            .value("Simple", Simple)
            .value("Compounded", Compounded)
            .value("Continuous", Continuous)
            .value("SimpleThenCompounded", SimpleThenCompounded)
            .value("CompoundedThenSimple", CompoundedThenSimple);

        register_vector<int>("Vector<int>")
            .constructor<int>();
        register_vector<double>("Vector<double>")
            .constructor<int>();
        register_vector<Date>("Vector<Date>")
            .constructor<int>();
        register_vector<RateHelper *>("Vector<RateHelper>")
            .constructor<int>();

        class_<Date>("Date")
            .constructor<>()
            .constructor<int>()
            .constructor<int, Month, int>()
            .function("serialNumber", &Date::serialNumber)
            .function("weekday", &Date::weekday)
            .function("dayOfMonth", &Date::dayOfMonth)
            .function("dayOfYear", &Date::dayOfYear)
            .function("month", &Date::month)
            .function("year", &Date::year)
            .function("toISOString", &calendarToISOString)
            .function("toString", &calendarToString)
            .class_function("fromISOString", &dateFromISOString, allow_raw_pointers())
            .class_function("isLeap", &Date::isLeap);
        class_<Schedule>("Schedule")
            .constructor<vector<Date>>()
            .constructor<Date, Date, Period, Calendar, BusinessDayConvention, BusinessDayConvention, DateGeneration::Rule, bool, Date, Date>()
            .function("size", &Schedule::size)
            .function("dates", &Schedule::dates);

        /* Calendars */
        class_<Calendar>("Calendar")
            .function("name", &Calendar::name)
            .function("toString", &calendarToString)
            .function("isBusinessDay", &Calendar::isBusinessDay)
            .function("adjust", &Calendar::adjust)
            // .function<Calendar, Date, Integer, TimeUnit>("advance", &Calendar::advance)
            .function("advance", &calendarAdvance);
        class_<JointCalendar, base<Calendar>>("JointCalendar")
            .constructor<Calendar, Calendar, JointCalendarRule>()
            .constructor<Calendar, Calendar, Calendar, JointCalendarRule>()
            .constructor<Calendar, Calendar, Calendar, Calendar, JointCalendarRule>();
        class_<TARGET, base<Calendar>>("TARGET").constructor<>();
        class_<NullCalendar, base<Calendar>>("NullCalendar").constructor<>();
        class_<UnitedKingdom, base<Calendar>>("UnitedKingdom").constructor<UnitedKingdom::Market>();
        class_<UnitedStates, base<Calendar>>("UnitedStates").constructor().constructor<UnitedStates::Market>();
        class_<Sweden, base<Calendar>>("Sweden").constructor<>();

        /* Period */
        class_<Period>("Period")
            .constructor<int, TimeUnit>()
            .function("toString", &timeUnitToString);

        /* DayCounters */
        class_<DayCounter>("DayCounter")
            .function("name", &DayCounter::name)
            .function("dayCount", &DayCounter::dayCount)
            .function("yearFraction", &DayCounter::yearFraction);
        class_<Thirty360, base<DayCounter>>("Thirty360")
            .constructor<>()
            .constructor<Thirty360::Convention, bool>();
        class_<Actual360, base<DayCounter>>("Actual360")
            .constructor<>()
            .constructor<bool>();
        class_<Actual365Fixed, base<DayCounter>>("Actual365Fixed").constructor<>();
        class_<ActualActual, base<DayCounter>>("ActualActual")
            .constructor<>()
            .constructor<ActualActual::Convention, Schedule>();
        class_<Business252, base<DayCounter>>("Business252").constructor<>();
        class_<boost::optional<BusinessDayConvention>>("OptionalBusinessDayConvention");

        /* Misc */
        class_<VanillaSwap>("VanillaSwap")
            .constructor(&createVanillaSwap, allow_raw_pointers())
            // .constructor<VanillaSwap::Type, Rate, Schedule, Rate, DayCounter, Schedule, ext::shared_ptr<IborIndex>, Spread, DayCounter, boost::optional<BusinessDayConvention>>()
            .class_function("create", &createVanillaSwap, allow_raw_pointers())
            .function("setPricingEngine", &swapSetPricingEngine)
            .function("NPV", &swapNpv);
        class_<Handle<YieldTermStructure>>("Handle<YieldTermStructure>");
        emscripten::function("createVanillaSwap", &createVanillaSwap, allow_raw_pointers());
        emscripten::function("mallinfo", &emval_test_mallinfo);
        emscripten::function("setValuationDate", &setValuationDate);
        emscripten::function("createLogLinearYieldTermStructure", &createLogLinearYieldTermStructure, allow_raw_pointers());
        class_<InterestRate>("InterestRate")
            .function("rate", &InterestRate::rate)
            .function("toString", &interestRateToString);

        class_<Quote>("Quote");
        class_<SimpleQuote, base<Quote>>("SimpleQuote")
            .constructor<Real>();
        class_<Handle<Quote>>("QuoteHandle")
            .constructor(&createQuoteHandle, allow_raw_pointers())
            .function("value", &handleQuoteValue);
        class_<IMM>("IMM")
            .class_function("nextDate", &immNextDate, allow_raw_pointers());

        /* Indicies */
        class_<Index>("Index")
            .function("addFixing", &Index::addFixing);
        class_<InterestRateIndex, base<Index>>("InterestRateIndex")
            .function("fixingDate", &InterestRateIndex::fixingDate);
        class_<IborIndex, base<InterestRateIndex>>("IborIndex");
        class_<Euribor, base<IborIndex>>("Euribor")
            .constructor<Period, Handle<YieldTermStructure>>();
        class_<OvernightIndex, base<IborIndex>>("OvernightIndex");
        class_<Eonia, base<OvernightIndex>>("Eonia").constructor();
        class_<Libor, base<IborIndex>>("Libor");
        class_<USDLibor, base<Libor>>("USDLibor")
            .constructor<Period>()
            .constructor<Period, Handle<YieldTermStructure>>();

        /* Helpers */
        class_<RateHelper>("RateHelper")
            .function("maturityDate", &RateHelper::maturityDate)
            .function("quote", &RateHelper::quote);
        class_<DepositRateHelper, base<RateHelper>>("DepositRateHelper")
            .constructor<Handle<Quote>, Period, Natural, Calendar, BusinessDayConvention, bool, DayCounter>();
        class_<OISRateHelper, base<RateHelper>>("OISRateHelper").constructor(&createOISRateHelper, allow_raw_pointers());
        class_<DatedOISRateHelper, base<RateHelper>>("DatedOISRateHelper").constructor(&createDatedOISRateHelper, allow_raw_pointers());
        class_<FuturesRateHelper, base<RateHelper>>("FuturesRateHelper").constructor(&createFuturesRateHelper, allow_raw_pointers());
        class_<SwapRateHelper, base<RateHelper>>("SwapRateHelper").constructor(&createSwapRateHelper, allow_raw_pointers());

        class_<YieldTermStructure>("ZeroInflationTermStructure")
            .function("zeroRate", &yieldTermStructureZeroRate, allow_raw_pointers())
            .function("discount", &yieldTermStructureDiscount)
            .function("forwardRate", &yieldTermStructureForwardRate, allow_raw_pointers());
        class_<PiecewiseYieldCurve<Discount, Linear>, base<YieldTermStructure>>("PiecewiseYieldCurve<Discount,Linear>")
            .constructor(&createPiecewiseYieldCurveDiscountLinear, allow_raw_pointers());
    }

} // namespace

// In [9]: eonia_curve_c = PiecewiseLogCubicDiscount(0, TARGET(),
// helpers, Actual365Fixed())
// eonia_curve_c.enableExtrapolation()


// Example of using the GeographicLib::NearestNeighbor class.  WARNING: this
// creates a file, vptree.xml or vptree.bin, in the current directory.

#include <iostream>

#include <vector>
#include <cstdlib>              // For srand, rand
#include <cmath>                // For asin
#include <fstream>
#include <string>
#include <sstream>
#include <algorithm>            // For sort
#include <GeographicLib/NearestNeighbor.hpp>
#include <GeographicLib/Geodesic.hpp>

#if !defined(GEOGRAPHICLIB_HAVE_BOOST_SERIALIZATION)
#define GEOGRAPHICLIB_HAVE_BOOST_SERIALIZATION 0
#endif

#if GEOGRAPHICLIB_HAVE_BOOST_SERIALIZATION
// If Boost serialization is available, use it.
#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>
#endif

using namespace std;
using namespace GeographicLib;

// A structure to hold a geographic coordinate.  Also included is a field for a
// "name".  This is unused in this example.
struct pos {
  double lat, lon;
  string name;
  pos(double lat = 0, double lon = 0, const string& name = "")
    : lat(lat), lon(lon), name(name) {}
};

pos randompos() {
  double r, lat, lon;
  r = 2 * (rand() + 0.5) / (RAND_MAX + 1.0) - 1;
  lat = asin(r) / Math::degree();
  r = 2 * (rand() + 0.5) / (RAND_MAX + 1.0) - 1;
  lon = 180 * r;
  return pos(lat, lon);
}

// A class to compute the distance between 2 positions.
class DistanceCalculator {
private:
  Geodesic _geod;
public:
  explicit DistanceCalculator(const Geodesic& geod)
    : _geod(geod) {}
  double operator() (const pos& a, const pos& b) const {
    double s12;
    _geod.Inverse(a.lat, a.lon, b.lat, b.lon, s12);
    return s12;
  }
};

typedef NearestNeighbor<double, pos, DistanceCalculator> GeodesicNeighbor;

// Pick 10000 points on the ellipsoid and determine which ones are more than
// 350 km from all the others.

// In this example the NearestNeighbor object is saved to an external file and
// read back in.  This is unnecessary in this simple application, but is useful
// if many different applications need to query the same dataset.

int main() {
  try {
    // Define a distance function object
    DistanceCalculator distance(Geodesic::WGS84());
    srand(0);
    vector<pos> pts;
    int num = 10000;
    // Sample the points
    for (int i = 0; i < num; ++i) pts.push_back(randompos());
    {
      // Illustrate saving and restoring the GeodesicNeighbor
      // construct it
      GeodesicNeighbor posset(pts, distance);
      // and save it
#if GEOGRAPHICLIB_HAVE_BOOST_SERIALIZATION
      ofstream f("vptree.xml");
      boost::archive::xml_oarchive oa(f);
      oa << BOOST_SERIALIZATION_NVP(posset);
#else
      ofstream ofs("vptree.txt");
      ofs << posset << "\n";
#endif
    }
    // Construct an empty GeodesicNeighbor
    GeodesicNeighbor posset;
    // restore it from the file
    {
#if GEOGRAPHICLIB_HAVE_BOOST_SERIALIZATION
      ifstream f("vptree.xml");
      boost::archive::xml_iarchive ia(f);
      ia >> BOOST_SERIALIZATION_NVP(posset);
#else
      ifstream ifs("vptree.txt");
      ifs >> posset;
#endif
    }
    // Now use it
    vector<int> ind;
    int cnt = 0;
    double thresh = 325000;
    cout << "Points more than " << thresh/1000 << "km from their neighbors\n"
         << "latitude longitude distance\n";
    for (int i = 0; i < num; ++i) {
      // Call search with distance limits = (0, thresh].  Set exhaustive = false
      // so that the search ends as some as a neighbor is found.
      posset.Search(pts, distance, pts[i], ind, 1, thresh, 0, false);
      if (ind.size() == 0) {
        // If no neighbors in (0, thresh], search again with no upper limit and
        // with exhaustive = true (the default).
        double d = posset.Search(pts, distance, pts[i], ind, 1,
                                 numeric_limits<double>::max(), 0);
        cout << pts[i].lat << " " << pts[i].lon << " " << d << "\n";
        ++cnt;
      }
    }
    int setupcost, numsearches, searchcost, mincost, maxcost;
    double mean, sd;
    posset.Statistics(setupcost, numsearches, searchcost, mincost, maxcost,
                      mean, sd);
    int totcost = setupcost + searchcost, exhaustivecost = num * (num - 1) / 2;
    cout
      << "Number of distance calculations = " << totcost << "\n"
      << "With an exhaustive search = " << exhaustivecost << "\n"
      << "Ratio = " << double(totcost) / exhaustivecost << "\n"
      << "Efficiency improvement = "
      << 100 * (1 - double(totcost) / exhaustivecost) << "%\n";
  }
  catch (const exception& e) {
    cerr << "Caught exception: " << e.what() << "\n";
    return 1;
  }
}


/**
 * qvrefl.cc
 * Copyright 2016 John Lawson
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <iostream>
#include <string>
#include <unistd.h>

#include <boost/dynamic_bitset.hpp>

#include "qv/mutation_class_loader.h"
#include "qv/equiv_mutation_class_loader.h"
#include "qv/stream_iterator.h"

#include "filtered_iterator.h"
#include "cartan_equiv.h"
#include "cartan_iterator.h"
#include "cartan_mutator.h"
#include "compatible_cartan.h"
#include "mutation_star.h"
#include "semi_positive_filter.h"
#include "vector_mutator.h"
#include "unique_matrix_filter.h"
#include "util.h"

namespace refl {
namespace {
using MatrixVec = std::vector<arma::Mat<int>>;
using UniqueCartanIter =
    FilteredIterator<CartanIterator, arma::Mat<int>, UniqueMatrixFilter>;
using SemiPosIter =
    FilteredIterator<UniqueCartanIter, arma::Mat<int>, SemiPositiveFilter>;

auto get_cartan_iterator(cluster::QuiverMatrix const& q) {
  CartanIterator cartan(q);
  // UniqueCartanIter unique(std::move(cartan));
  // return SemiPosIter(std::move(unique));
  return UniqueCartanIter(std::move(cartan));
}
/**
 * For each vertex in the given quiver, find a semipositive quasi-Cartan which
 * serves that vertex. These matrices are returned in a vector indexed by the
 * label of the vertices.
 *
 * If no such quasi-Cartan companion exists for a given vertex, then there will
 * still be a matrix in the vector, however all its values will be zero.
 */
MatrixVec find_serving_cartans(cluster::QuiverMatrix const& q) {
  MatrixVec result(q.num_cols(), arma::Mat<int>(q.num_rows(), q.num_cols()));
  boost::dynamic_bitset<> found{q.num_cols(), 0};
  const MutationStar star(q);
  const arma::mat initial_vecs(q.num_rows(), q.num_cols(), arma::fill::eye);
  arma::mat mutated_vecs(q.num_rows(), q.num_cols());
  arma::mat gram_matrix(q.num_rows(), q.num_cols());

  CompatibleCartan compatible;

  auto cartan_iter = SemiPosIter(get_cartan_iterator(q));

  while (cartan_iter.has_next() && !found.all()) {
    arma::Mat<int> const& AQ = cartan_iter.next();
    VectorMutator vmut(q, AQ);
    int_fast16_t ncols = AQ.n_cols;
    for (int_fast16_t i = 0; i < ncols; ++i) {
      vmut.mutate(initial_vecs, i, mutated_vecs);
      gram_matrix = util::gram(mutated_vecs, AQ);
      if (!found[i] && compatible(star.qv(i), gram_matrix)) {
        result[i] = AQ;
        found[i] = true;
      }
    }
  }
  return result;
}
/**
 * Print the first semipositive quasi-Cartan companion serving each vertex of
 * the given quiver. If no such companion exists, then print that the vertex is
 * not served by a Cartan matrix.
 */
void output_serving_cartans(cluster::QuiverMatrix const& q,
                            std::ostream& os = std::cout) {
  auto res = refl::find_serving_cartans(q);
  int_fast16_t size = res.size();
  for (int_fast16_t i = 0; i < size; ++i) {
    if (res[i].at(0, 0) != 2) {
      os << i << " is not served by any semi-positive quasi-Cartan"
         << os.widen('\n');
    } else {
      std::string label;
      label.append(std::to_string(i)).append(" served by");
      res[i].print(os, label);
    }
  }
}
/**
 * Find the first semipositive quasi-Cartan fully compatible with q.
 *
 * If no such quasi-Cartan exists then the first item in the returned pair is
 * false, otherwise the first item is true and the second item is this first
 * Cartan matrix.
 */
std::pair<bool, arma::Mat<int>> first_compatible_cartan(
    cluster::QuiverMatrix const& q) {
  bool result = false;
  arma::Mat<int> cartan;
  const MutationStar star(q);
  arma::Mat<int> gram_matrix(q.num_rows(), q.num_cols());

  CompatibleCartan compatible;

  auto cartan_iter = get_cartan_iterator(q);
  SemiPositiveFilter semipos;

  CartanMutator cmut(q);
  while (cartan_iter.has_next() && !result) {
    bool is_comp = true;
    arma::Mat<int> const& AQ = cartan_iter.next();
    int_fast16_t ncols = AQ.n_cols;
    for (int_fast16_t i = 0; is_comp && i < ncols; ++i) {
      cmut(AQ, i, gram_matrix);
      is_comp = compatible(star.qv(i), gram_matrix);
    }
    result = is_comp && semipos(AQ);
    if (result) {
      cartan = AQ;
    }
  }
  return {result, cartan};
}
/* Check if a semipositive cartan matrix is fully compatible with the quiver. */
bool check_compatible(cluster::QuiverMatrix const& q,
                      arma::Mat<int> const& AQ,
                      std::ostream& os = std::cout) {
  bool result = true;
  const MutationStar star(q);
  arma::Mat<int> gram_matrix(q.num_rows(), q.num_cols());

  CompatibleCartan compatible;
  if (!compatible(q, AQ)) {
    os << "Initial matrix not compatible" << os.widen('\n');
    result = false;
  }
  CartanMutator cmut(q);
  int_fast16_t ncols = AQ.n_cols;
  for (int_fast16_t i = 0; i < ncols; ++i) {
    cmut(AQ, i, gram_matrix);
    if (!compatible(star.qv(i), gram_matrix)) {
      os << "Mutation at " << i << " not compatible" << os.widen('\n');
      result = false;
    }
  }
  return result;
}
/**
 * Check if the semipositive quasi-Cartan matrix AQ is fully compatible with the
 * given quiver.
 *
 * To check if it is, the Cartan is mutated in every possible direction, and for
 * each mutation is checked to be a companion of the corresponding mutated
 * quiver.
 */
bool repeat_check_compatible(cluster::QuiverMatrix const& q,
                             MutationStar const& star,
                             arma::Mat<int> const& AQ) {
  static arma::Mat<int> gram_matrix;
  bool result = true;
  gram_matrix.set_size(q.num_rows(), q.num_cols());

  CompatibleCartan compatible;
  result = compatible(q, AQ);

  CartanMutator cmut(q);
  int_fast16_t ncols = AQ.n_cols;
  for (int_fast16_t i = 0; result && i < ncols; ++i) {
    cmut(AQ, i, gram_matrix);
    result = compatible(star.qv(i), gram_matrix);
  }
  return result;
}
/**
 * Check whether the provided quasi-Cartan matrix is a companion of the given
 * quiver.
 *
 * The Cartan is provided as a QuiverMatrix, so will be converted into an arma
 * matrix. This will involve some allocation.
 */
bool check_compatible(cluster::QuiverMatrix const& q,
                      cluster::QuiverMatrix const& cartan,
                      std::ostream& os = std::cout) {
  arma::Mat<int> const AQ = util::to_arma(cartan);
  bool result = check_compatible(q, AQ, os);
  return result;
}
/**
 * Compute all fully-compatible, semipositive quasi-Cartan companions of the
 * given quiver. These Cartan matrices are returned in a vector.
 *
 * Only distinct, i.e. non-equivalent, Cartan matrices will be returned. (Where
 * equivalence is given by flipping signs at a vertex.)
 */
std::vector<arma::Mat<int>> all_compatible(cluster::QuiverMatrix const& q) {
  auto cartan_iter = get_cartan_iterator(q);
  MutationStar const star(q);
  CartanEquiv equiv;
  SemiPositiveFilter semipos;

  std::vector<arma::Mat<int>> result;
  result.reserve(2);

  do {
    auto const& first = cartan_iter.next();
    if (repeat_check_compatible(q, star, first) && semipos(first)) {
      result.push_back(first);
    }
  } while (result.empty() && cartan_iter.has_next());

  if (!result.empty()) {
    while (cartan_iter.has_next()) {
      auto const& n = cartan_iter.next();
      if (repeat_check_compatible(q, star, n) && semipos(n) &&
          std::find_if(result.begin(), result.end(),
                       [&n, &equiv](arma::Mat<int> const& c) {
                         return equiv(c, n);
                       }) == result.end()) {
        result.push_back(n);
      }
    }
  }
  return result;
}
/**
 * Check whether all fully-compatible, semi-positive quasi-Cartan companions of
 * the given quiver are equivalent (up to flipping signs at vertices). If not
 * all the possble companions are printed to the supplied ostream.
 */
bool check_all_compatible_equiv(cluster::QuiverMatrix const& q,
                                std::ostream& os = std::cout) {
  auto compatible_cartans = all_compatible(q);
  bool all_equiv = compatible_cartans.size() == 1;

  if (!all_equiv) {
    for (auto const& n : compatible_cartans) {
      n.print(os, "Compatible:");
    }
  }
  return all_equiv;
}
}
}
enum Func { ListCompatible, SameCompatible, CheckSingle, CompatibleEquiv };
void usage() {
  std::cout << "qvrefl -cels [-m matrix] [-i in_file] [-a cartan]"
            << std::cout.widen('\n');
  std::cout << "  -m Specify matrix to find quasi-Cartan companions of"
            << std::cout.widen('\n');
  std::cout << "  -i Specify input file of matrices to read"
            << std::cout.widen('\n');
  std::cout << "  -c Check the whole mutation class of the matrix (only with "
               "-l or -s)"
            << std::cout.widen('\n');
  std::cout << "  -e Check that all fully compatible cartans are equivalent"
            << std::cout.widen('\n');
  std::cout << "  -l List the first quasi-Cartan serving each vertex"
            << std::cout.widen('\n');
  std::cout << "  -s Check if there is a quasi-Cartan serving all vertices"
            << std::cout.widen('\n');
  std::cout
      << "  -a Specify a cartan matrix to check whether it serves every vertex"
      << std::cout.widen('\n');
  std::cout.flush();
}
int main(int argc, char* argv[]) {
  std::string matrix;
  std::string cartan;
  Func function = Func::ListCompatible;
  std::string input;
  bool mut_class = false;
  int c;
  while ((c = getopt(argc, argv, "elsm:i:hca:")) != -1) {
    switch (c) {
      case 'e':
        function = Func::CompatibleEquiv;
        break;
      case 'm':
        matrix = optarg;
        break;
      case 'a':
        cartan = optarg;
        function = Func::CheckSingle;
        break;
      case 'l':
        function = Func::ListCompatible;
        break;
      case 's':
        function = Func::SameCompatible;
        break;
      case 'i':
        input = optarg;
        break;
      case 'c':
        mut_class = true;
        break;
      case 'h':
      case '?':
      default:
        usage();
        return 1;
    }
  }
  if (matrix.length() < 4 && input.empty()) {
    usage();
    return 2;
  }
  if (function == Func::ListCompatible) {
    if (matrix.length() > 0) {
      if (mut_class) {
        cluster::EquivQuiverMatrix q(matrix);
        cluster::EquivMutationClassLoader cl(q);
        while (cl.has_next()) {
          auto quiver = cl.next_ptr();
          std::cout << *quiver << ":" << std::cout.widen('\n');
          refl::output_serving_cartans(*quiver);
        }
      } else {
        cluster::QuiverMatrix q(matrix);
        refl::output_serving_cartans(q);
      }
    } else {
      std::ifstream inf;
      inf.open(input);
      if (!inf.is_open()) {
        std::cerr << "Could not open file " << input << std::endl;
        return 1;
      }
      cluster::StreamIterator<cluster::QuiverMatrix> iter(inf);
      while (iter.has_next()) {
        auto quiver = iter.next();
        std::cout << *quiver << std::cout.widen('\n');
        refl::output_serving_cartans(*quiver);
      }
    }
  } else if (function == Func::SameCompatible) {
    if (matrix.length() > 0) {
      if (mut_class) {
        cluster::EquivQuiverMatrix q(matrix);
        cluster::EquivMutationClassLoader cl(q);
        while (cl.has_next()) {
          auto quiver = cl.next_ptr();
          bool result = refl::first_compatible_cartan(*quiver).first;
          std::cout << (result ? "True: " : "False: ") << *quiver
                    << std::cout.widen('\n');
        }
      } else {
        cluster::QuiverMatrix q(matrix);
        auto result = refl::first_compatible_cartan(q);
        std::cout << (result.first ? result.second : "False")
                  << std::cout.widen('\n');
      }
    } else {
      std::ifstream inf;
      inf.open(input);
      if (!inf.is_open()) {
        std::cerr << "Could not open file " << input << std::endl;
        return 1;
      }
      cluster::StreamIterator<cluster::QuiverMatrix> iter(inf);
      while (iter.has_next()) {
        auto quiver = iter.next();
        bool result = refl::first_compatible_cartan(*quiver).first;
        std::cout << (result ? "True: " : "False: ") << *quiver
                  << std::cout.widen('\n');
      }
    }
  } else if (function == Func::CheckSingle) {
    if (matrix.empty() || cartan.empty()) {
      usage();
      return 4;
    }
    refl::check_compatible(cluster::QuiverMatrix{matrix},
                           cluster::QuiverMatrix{cartan});
  } else if (function == Func::CompatibleEquiv) {
    if (!matrix.empty()) {
      std::cout << std::boolalpha << refl::check_all_compatible_equiv(
                                         cluster::QuiverMatrix{matrix})
                << std::cout.widen('\n');
    } else if (!input.empty()) {
      std::ifstream inf;
      inf.open(input);
      if (!inf.is_open()) {
        std::cerr << "Could not open file " << input << std::endl;
        return 1;
      }
      cluster::StreamIterator<cluster::QuiverMatrix> iter(inf);
      while (iter.has_next()) {
        auto quiver = iter.next();
        std::cout << std::boolalpha << refl::check_all_compatible_equiv(*quiver)
                  << ": " << *quiver << std::cout.widen('\n');
      }
    } else {
      usage();
      return 4;
    }
  } else {
    usage();
  }
  std::cout.flush();
  return 0;
}