#define DEBUG 1
/**
 * File    : F.cpp
 * Author  : Kazune Takahashi
 * Created : 2020/7/3 5:48:55
 * Powered by Visual Studio Code
 */
#include <algorithm>
#include <bitset>
#include <cassert>
#include <cctype>
#include <chrono>
#include <climits>
#include <cmath>
#include <complex>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <iomanip>
#include <iostream>
#include <limits>
#include <map>
#include <queue>
#include <random>
#include <set>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <vector>
// ----- boost -----
#include <boost/integer/common_factor_rt.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/rational.hpp>
// ----- using directives and manipulations -----
using namespace std;
using boost::rational;
using boost::integer::gcd; // for C++14 or for cpp_int
using boost::integer::lcm; // for C++14 or for cpp_int
using boost::multiprecision::cpp_int;
using ll = long long;
using ld = long double;
template <typename T>
using max_heap = priority_queue<T>;
template <typename T>
using min_heap = priority_queue<T, vector<T>, greater<T>>;
// ----- constexpr for Mint and Combination -----
constexpr ll MOD{1'000'000'007LL};
// constexpr ll MOD{998'244'353LL}; // be careful
constexpr ll MAX_SIZE{3'000'010LL};
// constexpr ll MAX_SIZE{30'000'010LL}; // if 10^7 is needed
// ----- ch_max and ch_min -----
template <typename T>
bool ch_max(T &left, T right)
{
  if (left < right)
  {
    left = right;
    return true;
  }
  return false;
}
template <typename T>
bool ch_min(T &left, T right)
{
  if (left > right)
  {
    left = right;
    return true;
  }
  return false;
}
// ----- Mint -----
template <ll MOD = MOD>
class Mint
{
public:
  ll x;
  Mint() : x{0LL} {}
  Mint(ll x) : x{(x % MOD + MOD) % MOD} {}
  Mint operator-() const { return x ? MOD - x : 0; }
  Mint &operator+=(Mint const &a)
  {
    if ((x += a.x) >= MOD)
    {
      x -= MOD;
    }
    return *this;
  }
  Mint &operator-=(Mint const &a) { return *this += -a; }
  Mint &operator++() { return *this += 1; }
  Mint operator++(int)
  {
    Mint tmp{*this};
    ++*this;
    return tmp;
  }
  Mint &operator--() { return *this -= 1; }
  Mint operator--(int)
  {
    Mint tmp{*this};
    --*this;
    return tmp;
  }
  Mint &operator*=(Mint const &a)
  {
    (x *= a.x) %= MOD;
    return *this;
  }
  Mint &operator/=(Mint const &a)
  {
    Mint b{a};
    return *this *= b.power(MOD - 2);
  }
  Mint operator+(Mint const &a) const { return Mint(*this) += a; }
  Mint operator-(Mint const &a) const { return Mint(*this) -= a; }
  Mint operator*(Mint const &a) const { return Mint(*this) *= a; }
  Mint operator/(Mint const &a) const { return Mint(*this) /= a; }
  bool operator<(Mint const &a) const { return x < a.x; }
  bool operator<=(Mint const &a) const { return x <= a.x; }
  bool operator>(Mint const &a) const { return x > a.x; }
  bool operator>=(Mint const &a) const { return x >= a.x; }
  bool operator==(Mint const &a) const { return x == a.x; }
  bool operator!=(Mint const &a) const { return !(*this == a); }
  Mint power(ll N) const
  {
    if (N == 0)
    {
      return 1;
    }
    else if (N % 2 == 1)
    {
      return *this * power(N - 1);
    }
    else
    {
      Mint half = power(N / 2);
      return half * half;
    }
  }
};
template <ll MOD>
Mint<MOD> operator+(ll lhs, Mint<MOD> const &rhs) { return rhs + lhs; }
template <ll MOD>
Mint<MOD> operator-(ll lhs, Mint<MOD> const &rhs) { return -rhs + lhs; }
template <ll MOD>
Mint<MOD> operator*(ll lhs, Mint<MOD> const &rhs) { return rhs * lhs; }
template <ll MOD>
Mint<MOD> operator/(ll lhs, Mint<MOD> const &rhs) { return Mint<MOD>{lhs} / rhs; }
template <ll MOD>
istream &operator>>(istream &stream, Mint<MOD> &a) { return stream >> a.x; }
template <ll MOD>
ostream &operator<<(ostream &stream, Mint<MOD> const &a) { return stream << a.x; }
// ----- Combination -----
template <ll MOD = MOD, ll MAX_SIZE = MAX_SIZE>
class Combination
{
public:
  vector<Mint<MOD>> inv, fact, factinv;
  Combination() : inv(MAX_SIZE), fact(MAX_SIZE), factinv(MAX_SIZE)
  {
    inv[1] = 1;
    for (auto i{2LL}; i < MAX_SIZE; i++)
    {
      inv[i] = (-inv[MOD % i]) * (MOD / i);
    }
    fact[0] = factinv[0] = 1;
    for (auto i{1LL}; i < MAX_SIZE; i++)
    {
      fact[i] = Mint<MOD>(i) * fact[i - 1];
      factinv[i] = inv[i] * factinv[i - 1];
    }
  }
  Mint<MOD> operator()(int n, int k)
  {
    if (n >= 0 && k >= 0 && n - k >= 0)
    {
      return fact[n] * factinv[k] * factinv[n - k];
    }
    return 0;
  }
  Mint<MOD> catalan(int x, int y)
  {
    return (*this)(x + y, y) - (*this)(x + y, y - 1);
  }
};
// ----- for C++14 -----
using mint = Mint<MOD>;
using combination = Combination<MOD, MAX_SIZE>;
// ----- for C++17 -----
template <typename T, typename enable_if<is_integral<T>::value>::type * = nullptr>
size_t popcount(T x) { return bitset<64>(x).count(); }
size_t popcount(string const &S) { return bitset<200010>{S}.count(); }
// ----- Infty -----
template <typename T>
constexpr T Infty() { return numeric_limits<T>::max(); }
template <typename T>
constexpr T mInfty() { return numeric_limits<T>::min(); }
// ----- frequently used constexpr -----
// constexpr double epsilon{1e-10};
// constexpr ll infty{1'000'000'000'000'010LL}; // or
// constexpr int infty{1'000'000'010};
// ----- Yes() and No() -----
void Yes()
{
  cout << "Yes" << endl;
  exit(0);
}
void No()
{
  cout << "INF" << endl;
  exit(0);
}

// ----- 2D, 3D, 4D vectors -----
// Referring to ymatsux-san's source code: https://atcoder.jp/contests/abc138/submissions/7018300

template <typename T>
vector<vector<T>> Make2DVector(size_t d0, size_t d1, T v = T{})
{
  return vector<vector<T>>(d0, vector<T>(d1, v));
}

template <typename T>
vector<vector<vector<T>>> Make3DVector(size_t d0, size_t d1, size_t d2, T v = T{})
{
  return vector<vector<vector<T>>>(d0, Make2DVector(d1, d2, v));
}

template <typename T>
vector<vector<vector<vector<T>>>> Make4DVector(size_t d0, size_t d1, size_t d2, size_t d3, T v = T{})
{
  return vector<vector<vector<vector<T>>>>(d0, Make3DVector(d1, d2, d3, v));
}

// ----- Compressor -----
// referring to ymatsux-san's code:
// https://atcoder.jp/contests/abc168/submissions/13337691

template <typename T = ll>
class Compressor
{
  vector<T> raw;
  map<T, int> index;

public:
  Compressor() {}

  template <typename Container>
  Compressor(Container const &V) { append(V); }

  template <typename Iter>
  Compressor(Iter first, Iter last) { append(first, last); }

  template <typename Container>
  void append(Container const &V) { append(V.begin(), V.end()); }

  template <typename Iter>
  void append(Iter first, Iter last)
  {
    set<T> S(first, last);
    raw = vector<T>(S.begin(), S.end());
    sort(raw.begin(), raw.end());
    for (auto i = size_t{0}; i < raw.size(); ++i)
    {
      index[raw[i]] = i;
    }
  }

  T to_raw(int i) { return raw[i]; }
  int to_index(T t) { return index[t]; }
  size_t size() { return raw.size(); }
};

constexpr int dx[4] = {1, 0, -1, 0};
constexpr int dy[4] = {0, 1, 0, -1};

// ----- Solve -----

struct Point
{
  int x, y;
};

ostream &operator<<(ostream &os, vector<vector<bool>> v)
{
  for (auto i{size_t{0}}; i < v.size(); ++i)
  {
    for (auto j{size_t{0}}; j < v[i].size(); ++j)
    {
      os << (v[i][j] ? '#' : '.');
    }
    os << endl;
  }
  return os;
}

class Solve
{
  int n, m;
  vector<ll> a, b, c, d, e, f;
  Compressor<ll> x, y;
  vector<vector<bool>> visited;
  vector<vector<vector<bool>>> dir;
  int size_x, size_y;

public:
  Solve(int n, int m) : n{n}, m{m}, a(n), b(n), c(n), d(m), e(m), f(m)
  {
    for (auto i{0}; i < n; ++i)
    {
      cin >> a[i] >> b[i] >> c[i];
    }
    for (auto i{0}; i < m; ++i)
    {
      cin >> d[i] >> e[i] >> f[i];
    }
    vector<ll> xs{mInfty<ll>(), 0LL, Infty<ll>()}, ys{mInfty<ll>(), 0LL, Infty<ll>()};
    copy(a.begin(), a.end(), back_inserter(xs));
    copy(b.begin(), b.end(), back_inserter(xs));
    copy(c.begin(), c.end(), back_inserter(ys));
    copy(d.begin(), d.end(), back_inserter(xs));
    copy(e.begin(), e.end(), back_inserter(ys));
    copy(f.begin(), f.end(), back_inserter(ys));
    x.append(xs);
    y.append(ys);
    size_x = x.size() - 1;
    size_y = y.size() - 1;
    visited = Make2DVector<bool>(size_x, size_y, false);
    dir = Make3DVector<bool>(size_x, size_y, 4, true);
    set_segment();
  }

  void flush()
  {
    bfs();
#if DEBUG == 1
    cerr << visited;
#endif
    cout << answer() << endl;
  }

private:
  void bfs()
  {
    queue<Point> q;
    {
      int i{x.to_index(0)};
      int j{y.to_index(0)};
      q.push(Point{i, j});
    }
    while (!q.empty())
    {
      auto p{q.front()};
      q.pop();
      for (auto k{0}; k < 4; ++k)
      {
        if (!dir[p.x][p.y][k])
        {
          continue;
        }
        auto nx{p.x + dx[k]};
        auto ny{p.y + dy[k]};
        if (!valid(nx, ny))
        {
          continue;
        }
        if (visited[nx][ny])
        {
          continue;
        }
        visited[nx][ny] = true;
        q.push(Point{nx, ny});
      }
    }
  }

  void set_segment()
  {
    for (auto i{0}; i < n; ++i)
    {
      set_segment_x(a[i], b[i], c[i]);
    }
    for (auto i{0}; i < m; ++i)
    {
      set_segment_y(d[i], e[i], f[i]);
    }
  }

  void set_segment_x(ll xa, ll xb, ll yc)
  {
    int ia = x.to_index(xa);
    int ib = x.to_index(xb);
    int jc = y.to_index(yc);
    for (auto i{ia}; i < ib; ++i)
    {
      dir.at(i).at(jc - 1).at(1) = false;
      dir.at(i).at(jc).at(3) = false;
    }
  }

  void set_segment_y(ll xd, ll ye, ll yf)
  {
    int id = x.to_index(xd);
    int je = y.to_index(ye);
    int jf = y.to_index(yf);
    for (auto j{je}; j < jf; ++j)
    {
      dir.at(id - 1).at(j).at(0) = false;
      dir.at(id).at(j).at(2) = false;
    }
  }

  bool valid(int i, int j)
  {
    return 0 <= i && i < size_x && 0 <= j && j < size_y;
  }

  ll area(int i, int j)
  {
    ll width{x.to_raw(i + 1) - x.to_raw(i)};
    ll height{y.to_raw(j + 1) - y.to_raw(j)};
    return height * width;
  }

  ll answer()
  {
    for (auto i{0}; i < size_x; ++i)
    {
      if (visited[i][0] || visited[i][size_y - 1])
      {
        No();
      }
    }
    for (auto j{0}; j < size_y; ++j)
    {
      if (visited[0][j] || visited[size_x - 1][j])
      {
        No();
      }
    }
    ll ans{0};
    for (auto i{1}; i < size_x - 1; ++i)
    {
      for (auto j{1}; j < size_y - 1; ++j)
      {
        if (visited[i][j])
        {
          ans += area(i, j);
        }
      }
    }
    return ans;
  }
};

// ----- main() -----

int main()
{
  int n, m;
  cin >> n >> m;
  Solve solve(n, m);
  solve.flush();
}


#include "pso.h"

#include <Eigen/StdVector>

template <
  typename Scalar,
  typename DerivedX,
  typename DerivedLB,
  typename DerivedUB>
IGL_INLINE Scalar igl::pso(
  const std::function< Scalar (DerivedX &) > f,
  const Eigen::MatrixBase<DerivedLB> & LB,
  const Eigen::MatrixBase<DerivedUB> & UB,
  const int max_iters,
  const int population,
  DerivedX & X)
{
  const Eigen::Array<bool,Eigen::Dynamic,1> P =
    Eigen::Array<bool,Eigen::Dynamic,1>::Zero(LB.size(),1);
  return igl::pso(f,LB,UB,P,max_iters,population,X);
}

template <
  typename Scalar,
  typename DerivedX,
  typename DerivedLB,
  typename DerivedUB,
  typename DerivedP>
IGL_INLINE Scalar igl::pso(
  const std::function< Scalar (DerivedX &) > f,
  const Eigen::MatrixBase<DerivedLB> & LB,
  const Eigen::MatrixBase<DerivedUB> & UB,
  const Eigen::DenseBase<DerivedP> & P,
  const int max_iters,
  const int population,
  DerivedX & X)
{
  const int dim = LB.size();
  assert(UB.size() == dim && "UB should match LB size");
  assert(P.size() == dim && "P should match LB size");
  typedef std::vector<DerivedX,Eigen::aligned_allocator<DerivedX> > VectorList;
  VectorList position(population);
  VectorList best_position(population);
  VectorList velocity(population);
  Eigen::Matrix<Scalar,Eigen::Dynamic,1> best_f(population);
  // https://en.wikipedia.org/wiki/Particle_swarm_optimization#Algorithm
  //
  // g → X
  // p_i → best[i]
  // v_i → velocity[i]
  // x_i → position[i]
  Scalar min_f = std::numeric_limits<Scalar>::max();
  for(int p=0;p<population;p++)
  {
    {
      const DerivedX R = DerivedX::Random(dim).array()*0.5+0.5;
      position[p] = LB.array() + R.array()*(UB-LB).array();
    }
    best_f[p] = f(position[p]);
    best_position[p] = position[p];
    if(best_f[p] < min_f)
    {
      min_f = best_f[p];
      X = best_position[p];
    }
    {
      const DerivedX R = DerivedX::Random(dim);
      velocity[p] = (UB-LB).array() * R.array();
    }
  }

  int iter = 0;
  Scalar omega = 0.98;
  Scalar phi_p = 0.01;
  Scalar phi_g = 0.01;
  while(true)
  {
    //if(iter % 10 == 0)
    //{
    //  std::cout<<iter<<":"<<std::endl;
    //  for(int p=0;p<population;p++)
    //  {
    //    std::cout<<"  "<<best_f[p]<<", "<<best_position[p]<<std::endl;
    //  }
    //  std::cout<<std::endl;
    //}

    for(int p=0;p<population;p++)
    {
      const DerivedX R_p = DerivedX::Random(dim).array()*0.5+0.5;
      const DerivedX R_g = DerivedX::Random(dim).array()*0.5+0.5;
      velocity[p] =
        omega * velocity[p].array() +
        phi_p * R_p.array() *(best_position[p] - position[p]).array() +
        phi_g * R_g.array() *(               X - position[p]).array();
      position[p] += velocity[p];
      // Clamp to bounds
      for(int d = 0;d<dim;d++)
      {
//#define IGL_PSO_REFLECTION
#ifdef IGL_PSO_REFLECTION
        assert(!P(d));
        // Reflect velocities if exceeding bounds
        if(position[p](d) < LB(d))
        {
          position[p](d) = LB(d);
          if(velocity[p](d) < 0.0) velocity[p](d) *= -1.0;
        }
        if(position[p](d) > UB(d))
        {
          position[p](d) = UB(d);
          if(velocity[p](d) > 0.0) velocity[p](d) *= -1.0;
        }
#else
//#warning "trying no bounds on periodic"
//        // TODO: I'm not sure this is the right thing to do/enough. The
//        // velocities could be weird. Suppose the current "best" value is ε and
//        // the value is -ε and the "periodic bounds" [0,2π]. Moding will send
//        // the value to 2π-ε but the "velocity" term will now be huge pointing
//        // all the way from 2π-ε to ε.
//        //
//        // Q: Would it be enough to try (all combinations) of ±(UB-LB) before
//        // computing velocities to "best"s? In the example above, instead of
//        //
//        //     v += best - p = ε - (2π-ε) = -2π+2ε
//        //
//        // you'd use
//        //
//        //     v +=  / argmin  |b - p|            \  - p = (ε+2π)-(2π-ε) = 2ε
//        //          |                              |
//        //           \ b∈{best, best+2π, best-2π} /
//        //
//        // Though, for multivariate b,p,v this would seem to explode
//        // combinatorially.
//        //
//        // Maybe periodic things just shouldn't be bounded and we hope that the
//        // forces toward the current minima "regularize" them away from insane
//        // values.
//        if(P(d))
//        {
//          position[p](d) = std::fmod(position[p](d)-LB(d),UB(d)-LB(d))+LB(d);
//        }else
//        {
//          position[p](d) = std::max(LB(d),std::min(UB(d),position[p](d)));
//        }
        position[p](d) = std::max(LB(d),std::min(UB(d),position[p](d)));
#endif
      }
      const Scalar fp = f(position[p]);
      if(fp<best_f[p])
      {
        best_f[p] = fp;
        best_position[p] = position[p];
        if(best_f[p] < min_f)
        {
          min_f = best_f[p];
          X = best_position[p];
        }
      }
    }
    iter++;
    if(iter>=max_iters)
    {
      break;
    }
  }
  return min_f;
}

#ifdef IGL_STATIC_LIBRARY
template float igl::pso<float, Eigen::Matrix<float, 1, -1, 1, 1, -1>, Eigen::Matrix<float, 1, -1, 1, 1, -1>, Eigen::Matrix<float, 1, -1, 1, 1, -1> >(std::function<float (Eigen::Matrix<float, 1, -1, 1, 1, -1>&)>, Eigen::MatrixBase<Eigen::Matrix<float, 1, -1, 1, 1, -1> > const&, Eigen::MatrixBase<Eigen::Matrix<float, 1, -1, 1, 1, -1> > const&, int, int, Eigen::Matrix<float, 1, -1, 1, 1, -1>&);
#endif


/* The Computer Language Benchmarks Game
 * http://benchmarksgame.alioth.debian.org/
 *
 * contributed by Marcin Zalewski & Jeremiah Willcock
 */

#include <iostream>
#include <gmpxx.h>
#include <boost/lexical_cast.hpp>
#include <boost/tuple/tuple.hpp>
#include <algorithm>

using namespace boost;

class Digits {
private:
  unsigned int j;
  tuple<mpz_class, mpz_class, mpz_class> nad;
  mpz_class tmp1, tmp2;

public:
  Digits() { j = 0; get<0>(nad) = 1; get<1>(nad) = 0; get<2>(nad) = 1; }

  inline char operator()() {
    ++j;
    next_term();

    if(get<0>(nad) > get<1>(nad)) return (*this)();

    mpz_mul_2exp(tmp1.get_mpz_t(), get<0>(nad).get_mpz_t(), 1);
    tmp1 += get<0>(nad);
    tmp1 += get<1>(nad);

    mpz_fdiv_qr(tmp1.get_mpz_t(), tmp2.get_mpz_t(), tmp1.get_mpz_t(), get<2>(nad).get_mpz_t());

    tmp2 += get<0>(nad);

    if(tmp2 >= get<2>(nad)) {
      return (*this)();
    } else {
      unsigned int d = tmp1.get_ui();
      eliminate_digit(d);
      return d + '0';
    }
  }

private:

  inline void next_term() {
    unsigned int y = j * 2 + 1;
    mpz_mul_2exp(tmp1.get_mpz_t(), get<0>(nad).get_mpz_t(), 1);
    get<1>(nad) += tmp1;
    get<1>(nad) *= y;
    get<0>(nad) *= j;
    get<2>(nad) *= y;
  }

  inline void eliminate_digit(unsigned int d) {
    mpz_submul_ui(get<1>(nad).get_mpz_t(), get<2>(nad).get_mpz_t(), d);
    get<0>(nad) *= 10;
    get<1>(nad) *= 10;
  }

};

void pi(unsigned int n) {
  unsigned int i = 0;
  Digits digits;

  while((i += 10) <= n) {
    for(int count = 0; count < 10; ++count) {
      std::cout << digits();
    }
    std::cout << "\t:" << i << '\n';
  }

  i -= 10;
  if(n > i) {
    for(int count = 0; count < n - i; ++count) {
      std::cout << digits();
    }
    i += 10;
    for(int count = 0; count < i - n; ++count) {
      std::cout << ' ';
    }
    std::cout << "\t:" << n << '\n';
  }
}

int main(int argc, char** argv) {
  std::cout.sync_with_stdio(false);
  unsigned int count = (argc >= 2 ? boost::lexical_cast<unsigned int>(argv[1]) : 10000);
  pi(count);
  return 0;
}


// Copyright (c) 2019, Torsten Sattler
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
//     * Neither the name of the copyright holder nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// author: Torsten Sattler, torsten.sattler.de@googlemail.com

#include <iostream>

#include <ceres/ceres.h>
#include <ceres/rotation.h>
#include <Eigen/StdVector>

#include "calibrated_absolute_pose_estimator.h"

namespace ransac_lib {

namespace calibrated_absolute_pose {

struct NormalizedReprojectionError {
  NormalizedReprojectionError(double x, double y, double X, double Y, double Z,
                              double fx, double fy)
      : point2D_x(x),
        point2D_y(y),
        point3D_X(X),
        point3D_Y(Y),
        point3D_Z(Z),
        f_x(fx),
        f_y(fy) {}

  template <typename T>
  bool operator()(const T* const camera, T* residuals) const {
    // The last three entries are the camera position.
    T p[3];
    p[0] = point3D_X - camera[3];
    p[1] = point3D_Y - camera[4];
    p[2] = point3D_Z - camera[5];

    // The first three entries correspond to the rotation matrix stored in an
    // angle-axis representation.
    T p_rot[3];
    ceres::AngleAxisRotatePoint(camera, p, p_rot);

    T x_proj = static_cast<T>(f_x) * p_rot[0] / p_rot[2];
    T y_proj = static_cast<T>(f_y) * p_rot[1] / p_rot[2];

    residuals[0] = static_cast<T>(point2D_x) - x_proj;
    residuals[1] = static_cast<T>(point2D_y) - y_proj;

    return true;
  }

  // Factory function
  static ceres::CostFunction* CreateCost(const double x, const double y,
                                         const double X, const double Y,
                                         const double Z, const double fx,
                                         const double fy) {
    return (new ceres::AutoDiffCostFunction<NormalizedReprojectionError, 2, 6>(
        new NormalizedReprojectionError(x, y, X, Y, Z, fx, fy)));
  }

  // Assumes that the measurement is centered around the principal point.
  // This camera model does not take any radial distortion into account. If
  // radial distortion is present, one should undistort the measurements first.
  double point2D_x;
  double point2D_y;
  // The 3D point position is fixed as we are only interested in refining the
  // camera parameters.
  double point3D_X;
  double point3D_Y;
  double point3D_Z;
  double f_x;
  double f_y;
};

CalibratedAbsolutePoseEstimator::CalibratedAbsolutePoseEstimator(
    const double f_x, const double f_y, const double squared_inlier_threshold,
    const Points2D& points2D, const ViewingRays& rays, const Points3D& points3D)
    : focal_x_(f_x),
      focal_y_(f_y),
      squared_inlier_threshold_(squared_inlier_threshold),
      points2D_(points2D),
      points3D_(points3D),
      adapter_(rays, points3D) {
  num_data_ = static_cast<int>(points2D_.size());
}

int CalibratedAbsolutePoseEstimator::MinimalSolver(
    const std::vector<int>& sample, CameraPoses* poses) const {
  poses->clear();
  CameraPoses p3p_poses = opengv::absolute_pose::p3p_kneip(adapter_, sample);
  if (p3p_poses.empty()) return 0;
  for (const CameraPose& pose : p3p_poses) {
    CameraPose P = pose;
    // OpenGV returns the transformation from the camera to the world coordinate
    // system. We store the rotation from the world to the local coordinate
    // system instead.
    P.topLeftCorner<3, 3>() = pose.topLeftCorner<3, 3>().transpose();
    const double kError = EvaluateModelOnPoint(P, sample[3]);
    if (kError < squared_inlier_threshold_) {
      //      // Refine using all four points.
      //      LeastSquares(sample, &P);
      poses->push_back(P);
      // At most one pose should be correct.
      break;
    }
  }

  return static_cast<int>(poses->size());
}

// Returns 0 if no model could be estimated and 1 otherwise.
// Implemented by a simple linear least squares solver.
int CalibratedAbsolutePoseEstimator::NonMinimalSolver(
    const std::vector<int>& sample, CameraPose* pose) const {
  // Alternative: Run minimal solver and polish.
  CameraPoses poses;
  if (MinimalSolver(sample, &poses) == 1) {
    *pose = poses[0];
    LeastSquares(sample, pose);
    return 1;
  } else {
    return 0;
  }

//   CameraPose P = opengv::absolute_pose::epnp(adapter_, sample);
//   // OpenGV returns the transformation from the camera to the world coordinate
//   // system. We store the rotation from the world to the local coordinate
//   // system instead.
//   *pose = P;
//   pose->topLeftCorner<3, 3>() = P.topLeftCorner<3, 3>().transpose();
//   return 1;
}

// Evaluates the pose on the i-th data point.
double CalibratedAbsolutePoseEstimator::EvaluateModelOnPoint(
    const CameraPose& pose, int i) const {
  Eigen::Vector3d p_c =
      pose.topLeftCorner<3, 3>() * (points3D_[i] - pose.col(3));

  // Check whether point projects behind the camera.
  if (p_c[2] < 0.0) return std::numeric_limits<double>::max();

  Eigen::Vector2d p_2d = p_c.head<2>() / p_c[2];
  p_2d[0] *= focal_x_;
  p_2d[1] *= focal_y_;

  return (p_2d - points2D_[i]).squaredNorm();
}

// Reference implementation using Ceres for refinement.
void CalibratedAbsolutePoseEstimator::LeastSquares(
    const std::vector<int>& sample, CameraPose* pose) const {
  Eigen::AngleAxisd aax(pose->topLeftCorner<3, 3>());
  Eigen::Vector3d aax_vec = aax.axis() * aax.angle();
  double camera[6];
  camera[0] = aax_vec[0];
  camera[1] = aax_vec[1];
  camera[2] = aax_vec[2];
  camera[3] = pose->col(3)[0];
  camera[4] = pose->col(3)[1];
  camera[5] = pose->col(3)[2];

  ceres::Problem refinement_problem;
//  ceres::LossFunction* cauchy_loss = new ceres::CauchyLoss(1.0);
  const int kSampleSize = static_cast<int>(sample.size());
  for (int i = 0; i < kSampleSize; ++i) {
    const int kIdx = sample[i];
    const Eigen::Vector2d& p_img = points2D_[kIdx];
    const Eigen::Vector3d& p_3D = points3D_[kIdx];
    ceres::CostFunction* cost_function =
        NormalizedReprojectionError::CreateCost(
            p_img[0], p_img[1], p_3D[0], p_3D[1], p_3D[2], focal_x_, focal_y_);
//     double error_i = std::sqrt(EvaluateModelOnPoint(*pose, sample[i]));
//     error_i = std::max(0.00001, error_i);
    refinement_problem.AddResidualBlock(cost_function, nullptr, camera);
//    refinement_problem.AddResidualBlock(cost_function, cauchy_loss, camera);
//     refinement_problem.AddResidualBlock(cost_function,
//                                         new ceres::ScaledLoss(nullptr,
//                                                               1.0 / error_i,
//                                                               ceres::DO_NOT_TAKE_OWNERSHIP),
//                                         camera);
  }

  ceres::Solver::Options options;
  options.linear_solver_type = ceres::DENSE_QR;
  options.minimizer_progress_to_stdout = false;
  //  options.function_tolerance = 0.000001;
  ceres::Solver::Summary summary;
  ceres::Solve(options, &refinement_problem, &summary);

  //  std::cout << summary.BriefReport() << std::endl;
//  delete cauchy_loss;
//  cauchy_loss = nullptr;

  if (summary.IsSolutionUsable()) {
    Eigen::Vector3d axis(camera[0], camera[1], camera[2]);
    double angle = axis.norm();
    axis.normalize();
    aax.axis() = axis;
    aax.angle() = angle;

    pose->topLeftCorner<3, 3>() = aax.toRotationMatrix();
    pose->col(3) = Eigen::Vector3d(camera[3], camera[4], camera[5]);
  }
}

//// Reference implementation using OpenGV's non-linear refinement.
// void CalibratedAbsolutePoseEstimator::LeastSquares(
//    const std::vector<int>& sample, CameraPose* pose) const {
//  // OpenGV returns the transformation from the camera to the world coordinate
//  // system. We store the rotation from the world to the local coordinate
//  // system instead.
//  Eigen::Matrix3d R = pose->topLeftCorner<3, 3>().transpose();
//  Eigen::Vector3d c = pose->col(3);
//  // At the moment, we need to copy data as we need to add the current pose
//  // estimate to the adapter, which would break the requirement that this
//  // function is constant.
//  const int kSampleSize = static_cast<int>(sample.size());
//  opengv::bearingVectors_t bearing_vectors(kSampleSize);
//  opengv::points_t points(kSampleSize);
//  for (int i = 0; i < kSampleSize; ++i) {
//    const int kIdx = sample[i];
//    bearing_vectors[i] = adapter_.getBearingVector(kIdx);
//    points[i] = adapter_.getPoint(kIdx);
//  }
//
//  opengv::absolute_pose::CentralAbsoluteAdapter lsq_adapter(bearing_vectors,
//                                                            points, c, R);
//
//  CameraPose P = opengv::absolute_pose::optimize_nonlinear(lsq_adapter);
//  *pose = P;
//  pose->topLeftCorner<3, 3>() = P.topLeftCorner<3, 3>().transpose();
//}

void CalibratedAbsolutePoseEstimator::PixelsToViewingRays(
    const double focal_x, const double focal_y, const Points2D& points2D,
    ViewingRays* rays) {
  const int kNumData = static_cast<int>(points2D.size());

  // Creates the bearing vectors and points for the OpenGV adapter.
  rays->resize(kNumData);
  for (int i = 0; i < kNumData; ++i) {
    (*rays)[i] = points2D[i].homogeneous();
    (*rays)[i][0] /= focal_x;
    (*rays)[i][1] /= focal_y;
    (*rays)[i].normalize();
  }
}
}  // namespace calibrated_absolute_pose

}  // namespace ransac_lib


#include <rubbishrsa/keys.hpp>

#include <rubbishrsa/log.hpp>

#include <boost/property_tree/json_parser.hpp>

#include <boost/multiprecision/miller_rabin.hpp>

namespace rubbishrsa {
  private_key private_key::from_factors(const bigint& p, const bigint& q, bigint e) {
    // We can now start filling in our result
    private_key ret;
    ret.n = p * q;
    // This is automatically done
    ret.e = e;

    auto lambda_n = carmichael_semiprime(p, q);
    ret.d = modinv(ret.e, lambda_n); // $d \equiv e^{-1} \pmod{\lambda(n)}$

    return ret;
  }

  private_key private_key::generate(uint_fast16_t bits) {
    // Apparently we should differ in lengths by a few digits
    // this will differ in length by log10(2^8) = ~3 digits
    auto p = generate_prime(bits / 2 + 4);
    auto q = generate_prime(bits / 2 - 3);

    RUBBISHRSA_LOG_INFO(std::cerr << "(p, q) = (" << p.str() << ", " << q.str() << ')' << std::endl);

    // Now we have a good p and q, we can pass it along
    return private_key::from_factors(p, q);
  }

  void public_key::serialise(std::ostream& os) const {
    boost::property_tree::ptree data;
    data.put("e", e);
    data.put("n", n);
    boost::property_tree::write_json(os, data, false);
  }

  void private_key::serialise(std::ostream& os) const {
    boost::property_tree::ptree data;
    data.put("e", e);
    data.put("d", d);
    data.put("n", n);
    boost::property_tree::write_json(os, data, false);
  }

  public_key public_key::deserialise(std::istream& is) {
    boost::property_tree::ptree data;
    boost::property_tree::read_json(is, data);
    public_key ret;

    ret.e = data.get<bigint>("e");
    ret.n = data.get<bigint>("n");

    return ret;
  }

  private_key private_key::deserialise(std::istream& is) {
    boost::property_tree::ptree data;
    boost::property_tree::read_json(is, data);
    private_key ret;

    ret.e = data.get<bigint>("e");
    ret.d = data.get<bigint>("d");
    ret.n = data.get<bigint>("n");

    return ret;
  }
}


#include <maya/MVector.h>
#include <maya/MFnTypedAttribute.h>
#include <maya/MFnNumericAttribute.h>
#include <maya/MFnPlugin.h>
#include <maya/MPointArray.h>
#include <maya/MFnMesh.h>
#include <maya/MMatrix.h>
#include <maya/MItGeometry.h>
#include <maya/MDagPath.h>

#include <numeric>
#include <cmath>
#include <vector>
#include <array>

#include "tbb/tick_count.h"
#include "tbb/parallel_for.h"
#include "tbb/task_scheduler_init.h"

#include <Eigen/Dense>
#include <Eigen/StdVector>

#include "green_cage_deformer.h"

const MTypeId GreenCageDeformer::s_node_id(0xBBBB9255);
const MString GreenCageDeformer::s_node_name("green_cage_deformer");
MObject GreenCageDeformer::s_cage_mesh;

namespace {
	const Eigen::Vector3d NULL_VECTOR(0.0, 0.0, 0);
	constexpr auto TOLERANCE = 1e-6;
}

namespace MathConstants {
	constexpr auto PI = 3.14159265358979323846;
	constexpr auto ONE_OVER_FOUR_PI = 0.07957747154594767;
	constexpr auto SQRT8 = 2.828427124746190097603;
};

const double GCTriInt(const Eigen::Vector3d& p, const Eigen::Vector3d& v1, const Eigen::Vector3d& v2)
{
	const auto v2_v1 = v2 - v1;
	const auto p_v1 = p - v1;
	const auto v1_p = v1 - p;
	const auto v2_p = v2 - p;

	const auto alpha = std::acos(std::min(std::max(((v2_v1).dot((p_v1))) / ((v2_v1).norm() * (p_v1).norm()), -1.0), 1.0));
	if (abs(alpha - MathConstants::PI) < TOLERANCE || abs(alpha) < TOLERANCE) {
		return 0.0;
	}

	const auto beta = std::acos(std::min(std::max((((v1_p).dot((v2_p)))) / ((v1_p).norm() * (v2_p).norm()), -1.0), 1.0));
	const auto lambda = p_v1.squaredNorm() * std::sin(alpha) * std::sin(alpha);
	const auto c = p.squaredNorm();

	const auto sqrt_c = sqrt(c);
	const auto sqrt_lambda = sqrt(lambda);

	const std::array<double, 2> theta{ MathConstants::PI - alpha, MathConstants::PI - alpha - beta };
	std::array<double, 2> I;

	for (size_t i = 0; i < 2; ++i)
	{
		const auto S = std::sin(theta[i]);
		const auto C = std::cos(theta[i]);
		const auto sign = S < 0 ? -1.0 : 1.0;

		const auto SS = S*S;
		const auto half_sign = (-sign * 0.5);
		const auto tan_part = (2 * sqrt_c * std::atan2((sqrt_c * C), sqrt(lambda + (SS * c))));
		const auto log_part = log(((2 * sqrt_lambda * SS) / std::pow(1.0 - C, 2)) * (1.0 - ((2 * c * C) / ((c*(1 + C) + lambda + sqrt((lambda * lambda) + (lambda * c * SS)))))));

		I[i] = half_sign * (tan_part + (sqrt_lambda * log_part));
	}

	return -MathConstants::ONE_OVER_FOUR_PI * abs(I[0] - I[1] - sqrt_c * beta);
}

void GCDeformData::setup_cage() {
	MStatus stat;
	MFnMesh fn_mesh(m_cage_object);
	MIntArray m_tri_counts;
	fn_mesh.getTriangles(m_tri_counts, m_tri_verts);

	m_nr_of_tris = 0;
	for (unsigned int i = 0; i < m_tri_counts.length(); ++i)
	{
		m_nr_of_tris += m_tri_counts[i];
	}

	MPointArray cage_points;
	fn_mesh.getPoints(cage_points);
	m_nr_of_verts = fn_mesh.numVertices();
	m_vertices.resize(m_nr_of_verts);

	for (unsigned int i = 0; i < cage_points.length(); ++i) {
		const auto point = cage_points[i];
		m_vertices[i] = Eigen::Vector3d(point.x, point.y, point.z);
	}

	m_scale_factor.resize(m_nr_of_tris);
	m_tri_areas.resize(m_nr_of_tris);
	m_tri_normals.resize(m_nr_of_tris);
	m_tri_points.resize(m_nr_of_tris);
	m_tri_edges.resize(m_nr_of_tris);

	for (unsigned int j = 0; j < m_nr_of_tris; ++j) {
		const auto tri_idx = j * 3;
		const auto v0 = m_vertices[m_tri_verts[tri_idx + 0]];
		const auto v1 = m_vertices[m_tri_verts[tri_idx + 1]];
		const auto v2 = m_vertices[m_tri_verts[tri_idx + 2]];
		m_tri_points[j][0] = v0;
		m_tri_points[j][1] = v1;
		m_tri_points[j][2] = v2;
		// ccw order
		const Eigen::Vector3d edge1{ v1 - v0 };
		const Eigen::Vector3d edge2{ v2 - v1 };
		const Eigen::Vector3d cross = edge1.cross(edge2);
		m_tri_areas[j] = cross.norm()*0.5;
		m_tri_normals[j] = cross.normalized();
		m_tri_edges[j] = std::make_pair(edge1, edge2);
	}

}

// variable names based on pseudocode
void GCDeformData::calc_green_coords(MItGeometry& iter, const MMatrix& mat)
{
	MPointArray target_vertices;
	iter.allPositions(target_vertices);
	m_nr_of_targetverts = iter.exactCount();
	m_psi.resize(m_nr_of_tris*m_nr_of_targetverts, 0.0);
	m_phi.resize(m_nr_of_verts*m_nr_of_targetverts, 0.0);

	tbb::parallel_for(tbb::blocked_range<unsigned int>(0, m_nr_of_targetverts), [&](const tbb::blocked_range<unsigned int>& r) {
		for (unsigned int idx = r.begin(); idx < r.end(); ++idx) {
			const auto maya_vec = target_vertices[idx] * mat;
			const Eigen::Vector3d pvec(maya_vec.x, maya_vec.y, maya_vec.z);
			Eigen::Vector3d s;
			Eigen::Vector3d I;
			std::array<double, 3> II;
			std::array<Eigen::Vector3d, 3> N;
			for (unsigned int i = 0; i < m_nr_of_tris; ++i) {
				const auto nrm = m_tri_normals[i];
				std::array<Eigen::Vector3d, 3> vj;
				for (size_t l = 0; l < 3; ++l) {
					vj[l] = m_tri_points[i][l] - pvec;
				}
				const auto p = nrm * (vj[0].dot(nrm));
				for (size_t k = 0; k < 3; ++k) {
					const auto v0 = vj[k];
					const auto v1 = vj[(k + 1) % 3];
					const auto lg = ((v0 - p).cross((v1 - p))).dot(nrm);
					s[k] = lg < 0 ? -1.0 : 1.0;
					// eta always zero(?)
					I[k] = GCTriInt(p, v0, v1);
					II[k] = GCTriInt(NULL_VECTOR, v1, v0);
					N[k] = (v1.cross(v0)).normalized();
				}

				const auto I_ = -abs(s.dot(I));

				m_psi[(idx*m_nr_of_tris) + i] = -I_;

				Eigen::Vector3d w = I_ * nrm;
				for (int k = 0; k < 3; k++)
					w += (II[k] * N[k]);

				if (w.norm() > DBL_EPSILON) {
					for (unsigned int l = 0; l < 3; l++)
					{
						const auto l1 = (l + 1) % 3;
						const auto a1 = N[l1];
						const auto a2 = w;
						const auto a3 = vj[l];
						const auto val = ((a1.dot(a2)) / (a1.dot(a3)));
						m_phi[m_nr_of_verts*idx + m_tri_verts[(i * 3) + l]] += val;
					}
				}
			}
		}
	});
}

const MPoint GCDeformData::calc_pos(unsigned int idx) const
{
	Eigen::Vector3d pnt(0.0, 0.0, 0.0);
	const auto current_tri = idx*m_nr_of_tris;
	const auto current_vert = idx*m_nr_of_verts;

	for (unsigned int i = 0; i < m_nr_of_verts; ++i) {
		pnt += m_phi[current_vert + i] * m_vertices[i];
	}

	for (unsigned int i = 0; i < m_nr_of_tris; ++i) {

		pnt += m_psi[current_tri + i] * m_scale_factor[i] * m_tri_normals[i];
	}

	return MPoint(pnt.x(), pnt.y(), pnt.z());
}

void GCDeformData::calc_scale_factor()
{

	for (unsigned int i = 0; i < m_nr_of_tris; ++i) {
		const auto rest_edges = m_original_edges[i];
		const auto current_edges = m_tri_edges[i];

		const auto u0 = rest_edges.first;
		const auto v0 = rest_edges.second;

		const auto u1 = current_edges.first;
		const auto v1 = current_edges.second;

		m_scale_factor[i] = sqrt((u1.squaredNorm()) * (v0.squaredNorm()) - 2.0 * (u1.dot(v1)) * (u0.dot(v0)) + (v1.squaredNorm()) * (u0.squaredNorm())) / (MathConstants::SQRT8 * m_original_areas[i]);

	}
}

void GCDeformData::save_original_cage_data()
{
	m_original_areas.reserve(m_nr_of_tris);
	m_original_areas = m_tri_areas;

	m_original_edges.reserve(m_nr_of_tris);
	m_original_edges = m_tri_edges;
}

MStatus GreenCageDeformer::deform(MDataBlock& block, MItGeometry& iter, const MMatrix& mat, unsigned int multiIndex)
{
	MStatus stat;

	// not yet supported
	if (multiIndex > 0)
		return stat;

	if (!m_cage_connected)
		return stat;

	const auto envelope_val = block.inputValue(envelope).asDouble();
	if (envelope_val <= 0.0)
		return stat;

	const auto h_cage = block.inputValue(s_cage_mesh);

	// since we are getting the worldMesh lets not fiddle around with local to world transformations
	const auto cage_object = h_cage.asMeshTransformed();

	p_cage_data->m_cage_object = cage_object;
	p_cage_data->setup_cage();

	if (!m_green_calculated) {
		MArrayDataHandle h_input_array = block.outputArrayValue(input, &stat);
		// atm multiIndex will be only 0
		stat = h_input_array.jumpToElement(multiIndex);
		p_cage_data->calc_green_coords(iter, mat);
		p_cage_data->save_original_cage_data();
		m_green_calculated = true;
		// TODO: write out green coords w/ original cage to be able to reload scene without calculating(also correctly) everytime we open it
	}

	p_cage_data->calc_scale_factor();
	MPointArray new_arr;
	new_arr.setLength(p_cage_data->m_nr_of_targetverts);

	const auto inverse_mat = mat.inverse();
	tbb::parallel_for(tbb::blocked_range<unsigned int>(0, p_cage_data->m_nr_of_targetverts), [&](const tbb::blocked_range<unsigned int>& r) {
		for (unsigned int idx = r.begin(); idx < r.end(); ++idx) {
			new_arr[idx] = p_cage_data->calc_pos(idx) * inverse_mat;
		}
	});

	iter.setAllPositions(new_arr);
	return stat;
}

void GreenCageDeformer::postConstructor()
{
	m_cage_connected = false;
	m_green_calculated = false;
}

MStatus GreenCageDeformer::connectionMade(const MPlug & plug, const MPlug & otherPlug, bool asSrc)
{
	if (!asSrc && plug == s_cage_mesh) {
		p_cage_data.reset(new GCDeformData());
		m_cage_connected = true;
	}

	return MPxDeformerNode::connectionMade(plug, otherPlug, asSrc);
}

MStatus GreenCageDeformer::connectionBroken(const MPlug & plug, const MPlug & otherPlug, bool asSrc)
{
	if (!asSrc && plug == s_cage_mesh) {
		p_cage_data.reset();
		m_cage_connected = false;
		m_green_calculated = false;
	}

	return MPxDeformerNode::connectionBroken(plug, otherPlug, asSrc);
}

void* GreenCageDeformer::creator()
{
	return new GreenCageDeformer();
}

MStatus GreenCageDeformer::initialize()
{
	MStatus stat;
	MFnTypedAttribute tAttr;

	s_cage_mesh = tAttr.create("cageMesh", "cm", MFnData::kMesh, &stat);
	CHECK_MSTATUS_AND_RETURN_IT(stat);
	stat = addAttribute(s_cage_mesh);
	CHECK_MSTATUS_AND_RETURN_IT(stat);
	stat = attributeAffects(s_cage_mesh, outputGeom);

	return stat;
}

MStatus initializePlugin(MObject obj)
{
	MStatus stat;

	MFnPlugin plugin(obj, "Balazs Pataki", "1.0", "Any");
	stat = plugin.registerNode(GreenCageDeformer::s_node_name, GreenCageDeformer::s_node_id, &GreenCageDeformer::creator, &GreenCageDeformer::initialize, MPxNode::kDeformerNode);
	CHECK_MSTATUS_AND_RETURN_IT(stat);

	return stat;
}

MStatus uninitializePlugin(MObject obj)
{
	MStatus stat;

	MFnPlugin plugin(obj);
	stat = plugin.deregisterNode(GreenCageDeformer::s_node_id);
	CHECK_MSTATUS_AND_RETURN_IT(stat);

	return stat;
}


#include <iostream>
#include <cassert>
#include <vector>
#include <map>
#include <deque>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/kruskal_min_spanning_tree.hpp>
#include <boost/iterator/function_output_iterator.hpp>
#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>

const int debug_level = 0;

#define DEBUG(min_level, x)      \
  if (debug_level >= min_level)  \
  {                              \
    std::cerr << x << std::endl; \
  }

typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, boost::no_property, boost::property<boost::edge_weight_t, long>> Graph;

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef CGAL::Delaunay_triangulation_2<K> Triangulation;

bool testcase()
{
  int n;
  std::cin >> n;
  if (n == 0)
  {
    return false;
  }
  assert(n >= 2 && n <= 6e4);

  DEBUG(1, "\ntest case; n = " << n);

  std::vector<K::Point_2> infected_points;
  for (int i = 0; i < n; i++)
  {
    int x, y;
    std::cin >> x >> y;
    assert(abs(x) < (1 << 24) && abs(y) < (1 << 24));
    infected_points.emplace_back(x, y);
  }

  int m;
  std::cin >> m;
  assert(m >= 1 && m <= 4e4);

  std::vector<std::pair<K::Point_2, long>> healthy_points;
  for (int i = 0; i < m; i++)
  {
    int x, y;
    long d;
    std::cin >> x >> y >> d;
    assert(abs(x) < (1 << 24) && abs(y) < (1 << 24));
    assert(d >= 0 && d < (long(1) << 49));
    healthy_points.push_back(std::make_pair(K::Point_2(x, y), d));
  }

  Triangulation triangulation;
  triangulation.insert(infected_points.begin(), infected_points.end());

  const int indexed_face_count = triangulation.number_of_faces() + 1;
  Graph mst_input_graph(indexed_face_count);
  auto mst_input_weights = boost::get(boost::edge_weight, mst_input_graph);
  std::map<Triangulation::Face_handle, int> face_indices_by_handle;
  int next_free_face_index = 0;
  const int infinite_face_index = next_free_face_index++;
  auto index_from_face_handle = [&face_indices_by_handle, &next_free_face_index, &triangulation, indexed_face_count, infinite_face_index](Triangulation::Face_handle fh) {
    if (triangulation.is_infinite(fh))
    {
      DEBUG(4, "infinite face");
      return infinite_face_index;
    }

    int face_index;
    if (face_indices_by_handle.count(fh) == 1)
    {
      DEBUG(4, "existing face");
      face_index = face_indices_by_handle.at(fh);
    }
    else
    {
      DEBUG(4, "new face");
      assert(face_indices_by_handle.count(fh) == 0);
      face_index = next_free_face_index++;
      face_indices_by_handle.insert(std::make_pair(fh, face_index));
    }
    DEBUG(4, "face_index " << face_index << " limit " << indexed_face_count);
    assert(face_index >= 0 && face_index < indexed_face_count);
    return face_index;
  };
  for (auto it = triangulation.finite_edges_begin(); it != triangulation.finite_edges_end(); it++)
  {
    long weight = -CGAL::squared_distance(it->first->vertex((it->second + 1) % 3)->point(), it->first->vertex((it->second + 2) % 3)->point());
    int vertex_a = index_from_face_handle(it->first);
    int vertex_b = index_from_face_handle(it->first->neighbor(it->second));
    if (vertex_a > vertex_b)
    {
      std::swap(vertex_a, vertex_b);
    }
    assert(vertex_a < vertex_b);
    DEBUG(3, "vertex_a " << vertex_a << " vertex_b " << vertex_b << " weight " << weight);
    auto edge_result = boost::edge(vertex_a, vertex_b, mst_input_graph);
    if (edge_result.second)
    {
      long &saved_weight = mst_input_weights[edge_result.first];
      saved_weight = std::min(saved_weight, weight);
    }
    else
    {
      boost::add_edge(vertex_a, vertex_b, weight, mst_input_graph);
    }
  }

  Graph mst(indexed_face_count);
  auto add_mst_edge = [&mst, &mst_input_graph](Graph::edge_descriptor edge) {
    long weight = boost::get(boost::edge_weight_t(), mst_input_graph, edge);
    DEBUG(3, "add_mst_edge " << edge.m_source << " " << edge.m_target << " " << weight);
    boost::add_edge(edge.m_source, edge.m_target, weight, mst);
  };
  boost::kruskal_minimum_spanning_tree(mst_input_graph, boost::make_function_output_iterator(std::ref(add_mst_edge)));

  std::deque<std::pair<int, long>> bfs_queue{std::make_pair(infinite_face_index, std::numeric_limits<long>::max())};
  std::vector<long> largest_escape_by_face(indexed_face_count, 0);
  while (!bfs_queue.empty())
  {
    int prev_vertex = bfs_queue.front().first;
    long prev_largest_escape = bfs_queue.front().second;
    bfs_queue.pop_front();

    assert(largest_escape_by_face.at(prev_vertex) == 0);
    largest_escape_by_face.at(prev_vertex) = prev_largest_escape;
    DEBUG(3, "prev_vertex " << prev_vertex << " prev_largest_escape " << prev_largest_escape);

    for (auto its = boost::out_edges(prev_vertex, mst); its.first != its.second; its.first++)
    {
      int next_vertex = its.first->m_target;
      assert(next_vertex != prev_vertex);
      if (largest_escape_by_face.at(next_vertex) > 0)
      {
        continue;
      }
      DEBUG(3, "next_vertex " << next_vertex);

      long limit_of_this_edge = -boost::get(boost::edge_weight_t(), mst, *its.first);
      long next_largest_escape = std::min(prev_largest_escape, limit_of_this_edge);
      DEBUG(3, "limit_of_this_edge " << limit_of_this_edge << " next_largest_escape " << next_largest_escape)
      assert(next_largest_escape > 0);

      bfs_queue.push_back(std::make_pair(next_vertex, next_largest_escape));
    }
  }
  int min_largest_escape_by_triangle = *std::min_element(largest_escape_by_face.begin(), largest_escape_by_face.end());
  DEBUG(2, "min_largest_escape_by_triangle " << min_largest_escape_by_triangle);
  assert(min_largest_escape_by_triangle > 0);

  std::vector<bool> can_escape_by_index(m);
  for (int i = 0; i < m; i++)
  {
    auto &healthy_point = healthy_points.at(i);
    Triangulation::Face_handle face = triangulation.locate(healthy_point.first);
    int face_index = index_from_face_handle(face);
    DEBUG(2, "face_index " << face_index);
    long largest_escape = largest_escape_by_face.at(face_index);
    DEBUG(2, "largest_escape (initial) " << largest_escape);

    auto nearest = triangulation.nearest_vertex(healthy_point.first);
    DEBUG(3, "limiting using point " << nearest->point());
    largest_escape = std::min(largest_escape, 4 * long(CGAL::squared_distance(healthy_point.first, nearest->point())));

    DEBUG(2, "largest_escape (adjusted) " << largest_escape);
    can_escape_by_index.at(i) = 4 * healthy_point.second <= largest_escape;
  }

  for (bool v : can_escape_by_index)
  {
    std::cout << (v ? "y" : "n");
  }
  std::cout << "\n";

  return true;
}

int main()
{
  std::ios_base::sync_with_stdio(false);

  while (testcase())
  {
  }

  return 0;
}


#define _USE_MATH_DEFINES

#include "mesh.hpp"
#include "plane2d.hpp"
#include "line_interval.hpp"
#include "found_path.hpp"
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <limits>
#include <functional>
#include <future>
#include <cmath>
#include <thread>

#include <Eigen/Dense>

using namespace Eigen;
using namespace std;

Mesh::Mesh(const int numVertices, const int numFaces, const int numCells){
    vertices.reserve(numVertices);
    faces.reserve(numFaces);
    tetrahedrons.reserve(numCells);
}

void Mesh::setVertices(const vector<array<double, 3>> & _vertices){

    for(unsigned long int i=0; i < _vertices.size(); ++i){
	Vertex3d pt(_vertices[i]);
	vertices.push_back(pt);
    }

}

void Mesh::addTetrahedron(const int id, const array<int, 4> vertexIds,
	const vector<unsigned long int> neighborIds, const double weight, const int label){

    vector<reference_wrapper<Vertex3d>> refs = {vertices[vertexIds[0]], vertices[vertexIds[1]], vertices[vertexIds[2]], vertices[vertexIds[3]]};

    Tetrahedron tet(id, refs, weight, label);

    unsigned long int current_size = tetrahedrons.size();

    for(unsigned long int i=0; i<neighborIds.size(); ++i){
	if(neighborIds[i]<current_size){
	    Tetrahedron& neighbor = tetrahedrons[neighborIds[i]];

	    neighbor.addNeighbor(tet.Id());
	    tet.addNeighbor(neighbor.Id());
	}
    }
    tetrahedrons.push_back(tet);
}

void Mesh::addFace(const array<int, 3> vertexIds, const int tetId){
    vector<reference_wrapper<Vertex3d>> refs = {vertices[vertexIds[0]], vertices[vertexIds[1]], vertices[vertexIds[2]]};

    Face face(refs, tetId);
    faces.push_back(face);
}

bool Mesh::setTarget(array<double, 3> _target){
    bool targetSet=false;
    for(unsigned long int i=0; i < tetrahedrons.size(); ++i){
	if (tetrahedrons[i].contains(_target)){
	    Vector3d t(_target[0], _target[1], _target[2]);
	    target = t;
	    targetTetId=i;
	    targetSet=true;
	    break;
	}
    }
    return targetSet;
}

vector<Shape3d> Mesh::sliceIndv(array<double, 2> rotation){
    vector<int> planeLastChecked;
    planeLastChecked.reserve(tetrahedrons.size());
    for(unsigned long int i=0; i<tetrahedrons.size(); i++){
	planeLastChecked.push_back(-1);
    }

    Plane3d plane(0, rotation[0], rotation[1], target);
    return slice(plane, planeLastChecked);
}

vector<FoundPath> Mesh::findPaths(vector<Plane3d> planes, double distBound){
    vector<int> planeLastChecked;
    planeLastChecked.reserve(tetrahedrons.size());
    for(unsigned long int i=0; i<tetrahedrons.size(); i++){
	planeLastChecked.push_back(-1);
    }

    double minUpperBound=numeric_limits<double>::max();

    //vector<vector<LineInterval>> allCandidateIntervals;
    vector<vector<FoundPath>> allFoundPaths;

    for(int i=0; i<planes.size(); ++i){
	vector<Shape3d> planeSlice = slice(planes[i], planeLastChecked);
	Plane2d plane2d(planeSlice, planes[i]);
	vector<FoundPath> planePaths=plane2d.FindPaths(distBound);
	//cout << "plane paths size:" << endl;
	//cout << planePaths.size() << endl;
	allFoundPaths.push_back(planePaths);
	if(plane2d.MinUpperBound()<minUpperBound){
	    minUpperBound=plane2d.MinUpperBound();
	}
    }
    vector<FoundPath> foundPaths;
    //cout << "min upper bound:" << endl;
    //cout << minUpperBound << endl;
    //cout << "all found paths size:" << endl;
    //cout << allFoundPaths.size() << endl;

    for(int i=0; i<planes.size(); ++i){
	vector<FoundPath> planePaths=allFoundPaths[i];

	for(int j=0; j<planePaths.size(); ++j){
	    FoundPath foundPath=planePaths[j];
	    if(foundPath.LowerBound()<minUpperBound){
		array<Vector3d, 2> endPoints=foundPath.Points();
		Vector3d pt0=planes[i].Get3dPoint(endPoints[0]);
		Vector3d pt1=planes[i].Get3dPoint(endPoints[1]);

		FoundPath fp(planes[i].Id(), pt0, pt1, foundPath.LowerBound(), foundPath.UpperBound());
		foundPaths.push_back(fp);
	    }
	}
    }
    //cout << "return paths size:" << endl;
    //cout << foundPaths.size() << endl;
    return foundPaths;
}

vector<Shape3d> Mesh::slice(Plane3d plane, vector<int> &planeLastChecked){
    vector<Shape3d> slice = computeSliceComponent(plane, planeLastChecked, targetTetId);

    for(unsigned long int i=0; i<faces.size(); i++){
	if(faces[i].intersectsPlane(plane) && planeLastChecked[faces[i].TetId()]!=plane.Id()){
	    vector<Shape3d> sliceComponent = computeSliceComponent(plane, planeLastChecked, faces[i].TetId());
	    slice.insert(slice.end(), sliceComponent.begin(), sliceComponent.end());
	}
    }
    return slice;
}

vector<Shape3d> Mesh::computeSliceComponent(Plane3d plane, vector<int> &planeLastChecked, unsigned long int initTet){

    vector<unsigned long int> tetStack;
    tetStack.push_back(initTet);

    vector<Shape3d> allPoints;

    while(tetStack.size() > 0){
	Tetrahedron& tet = tetrahedrons[tetStack.back()];
	tetStack.pop_back();

	if(planeLastChecked[tet.Id()]!=plane.Id()){

	    vector<array<double, 3>> intersectionPoints = tet.intersectsPlane(plane);

	    if(intersectionPoints.size()>2){
		Shape3d shape(tet.Id(), intersectionPoints, tet.Weight(), tet.Label());
		allPoints.push_back(shape);
		vector<unsigned long int> neighbors = tet.Neighbors();

		for(int i=0; i<neighbors.size();++i){
		    tetStack.push_back(neighbors[i]);
		}
	    }

	    planeLastChecked[tet.Id()]=plane.Id();
	}
    }

    return allPoints;
}

vector<FoundPath> Mesh::shortestPaths(const int epsilon, const int numThreads, double distBound){

    future<vector<FoundPath>> futures[numThreads];

    vector<Plane3d> planes;
    //cout << numThreads << endl;

    for(int i=0; i<epsilon; i++){
	for(int j=0; j<epsilon; j++){
	    Plane3d plane((i*epsilon)+j, i*M_PI/epsilon, j*M_PI/epsilon, target);
	    planes.push_back(plane);
	}
    }

    int subarraySize = ((epsilon * epsilon) + numThreads - 1) / numThreads;

    for(int i=0; i<numThreads; i++){
	int start=i*subarraySize;
	int end=start+subarraySize;
	if(end>(epsilon*epsilon)){
	    end=epsilon*epsilon;
	}

	vector<Plane3d> planeVector;

	for(int idx=start; idx<end; idx++){
	    planeVector.push_back(planes[idx]);
	}

	futures[i]=async(&Mesh::findPaths, this, planeVector, distBound);
    }

    vector<FoundPath> allFoundPaths;
    double minUpperBound=numeric_limits<double>::max();
    for(unsigned long int i=0; i<numThreads; ++i){
	//cout << i << endl;
	vector<FoundPath> foundPaths=futures[i].get();
	for(unsigned long int j=0; j<foundPaths.size(); ++j){
	    if(foundPaths[j].UpperBound()<minUpperBound){
		minUpperBound=foundPaths[j].UpperBound();
	    }
	    allFoundPaths.push_back(foundPaths[j]);
	}
	//vector<FoundPath> foundPaths=futures[i].get();
	//allFoundPaths.push_back(foundPaths);
    }

    vector<FoundPath> ret;
    for(unsigned long int i=0; i<allFoundPaths.size(); ++i){
	if(allFoundPaths[i].LowerBound()<minUpperBound){
	    ret.push_back(allFoundPaths[i]);
	}
    }

    //cout << "ret size:" << endl;
    //cout << ret.size() << endl;
    return ret;
}

unsigned long int Mesh::getTargetTetId(){
    return targetTetId;
}


/*
 * G_mod_p.cpp
 *
 *  Created on: 20.09.2010
 *      Author: stephaniebayer
 */

#include "G_mod_p.h"
#include <NTL/ZZ.h>
NTL_CLIENT

#include <time.h>
G_mod_p::G_mod_p() {
	// TODO Auto-generated constructor stub

}

//Sets the generator to gen and the mod to p, checks if the generator has the right modular value p
G_mod_p::G_mod_p(Mod_p gen,  long p){

	generator = gen;
	mod = to_ZZ(p);
	if (gen.get_mod() != p)
		cout  << "The modular value of the generator and p are not equal" << endl;
}

//Sets the generator to gen and the mod to p, checks if the generator has the right modular value p
G_mod_p::G_mod_p(Mod_p gen,  ZZ p){

	generator = gen;
	mod = p;

	if (gen.get_mod() != p)
		cout  << "The modular value of the generator and p are not equal" << endl;

}

//Sets the generator to the value gen and the mod to p
G_mod_p::G_mod_p(ZZ val,  long p){

	generator = Mod_p(val, p);
	mod = to_ZZ(p);

}

//Sets the generator to the value gen and the mod to p
G_mod_p::G_mod_p(ZZ val, ZZ p){

	generator = Mod_p(val, p);
	mod = p;

}

//Sets the generator to the value gen and the mod to p
G_mod_p::G_mod_p(long val,  long p){

	generator = Mod_p(val, p);
	mod = to_ZZ(p);

}

//Sets the generator to the value gen and the mod to p
G_mod_p::G_mod_p(long val, ZZ p){

	generator = Mod_p(val, p);
	mod = p;

}

//Creates a group given the generator, mod is set to modular value if gen
G_mod_p::G_mod_p(Mod_p gen){

	generator = gen;
	mod = gen.get_mod();
}

//Creates a group ZZ/p, the function sets the generator to the smallest possible one
G_mod_p::G_mod_p(ZZ p){

	ZZ i;
	mod = p;
	for (i = to_ZZ(1); i < p; ++i)
	{
		if (is_generator(i))
		{
			generator = Mod_p(i,p);
			break;
		}
	}
}

//Creates a group ZZ/p, the function sets the generator to the smallest possible one
G_mod_p::G_mod_p(long p){

	long i;
	mod = p;
	for (i = 1; i < p; ++i)
	{
		if (is_generator(i))
		{
			generator = Mod_p(i,p);
			break;
		}
	}
}

//Destructor
G_mod_p::~G_mod_p() {
	// TODO Auto-generated destructor stub
}

//Returns the generator
Mod_p G_mod_p::get_gen()const{

	return generator;
}

//Returns the modular value
ZZ G_mod_p::get_mod()const{

	return mod;
}

//Checks if an element is a generator of the group
bool G_mod_p::is_generator(const Mod_p& el){
	ZZ pow;
	bool b;
	b=false;
	pow = PowerMod(el.get_val(),(mod-1)/2,mod);
	if(pow == (mod-1))
	{
		if(el.get_val()!=(mod-1))
		{b=true;
		}
	}
	return b;
}

//Checks if an element with value x is a generator of the group
bool G_mod_p::is_generator(const ZZ& x){
	ZZ pow;
	bool b;
	b=false;
	pow = PowerMod(x,(mod-1)/2,mod);

	if(pow == (mod-1))
	{
		if(x!=(mod-1))
		{b=true;
		}
	}

	return b;
}

//Checks if an element with value x is a generator of the group
bool G_mod_p::is_generator(const long& x){
	ZZ pow;
	bool b;
	pow = PowerMod(to_ZZ(x),(mod-1)/2, mod);
	if(pow == (mod-1))
	{
		if(x!=(mod-1))
		{b=true;
		}
	}

	return b;
}

//Returns the identity of the group
Mod_p G_mod_p::identity(){

	return Mod_p(1, mod);
}

//return a random element of the group
Mod_p G_mod_p::random_el(){

	ZZ ran;
	SetSeed(to_ZZ(time(0)));
	ran = RandomBnd(mod);

	return Mod_p(ran,mod);

}

//Creates an element with the value v modular mod
Mod_p G_mod_p::element(ZZ v){

	return Mod_p(v,mod);

}

//Creates an element with the value v modular mod
Mod_p G_mod_p::element(long v){

	return Mod_p(v,mod);

}

//Returns a n-th root of unity of the group
Mod_p G_mod_p::rootofunity(long n){

	ZZ i;
	ZZ pow;
	if ((mod-1) % n == 0)
	{	for(i =  to_ZZ(2); i<mod;++i)
		{
			if(GCD(i,mod)==to_ZZ(1))
			{

				if (n&1)
				{
					pow = PowerMod(i,n, mod);
					if(pow==1)
						return Mod_p(i,mod);
				}
				else
				{
					pow = PowerMod(i,n/2,mod);
					if(pow == (mod-1))
					{
						return Mod_p(i,mod);
					}
				}
			}
		}
	}
	else
	{
		cout << "There is no" << n <<"-th root of unity"<< endl;
		return Mod_p(1,mod);
	}
	return Mod_p(1,mod);
}

//Returns a n-th root of unity of the group
Mod_p G_mod_p::rootofunity(ZZ n){

	ZZ i;
	ZZ pow;
	if ((mod-1) % n == 0)
	{	for(i =  to_ZZ(2); i<mod;++i)
		{
			if(GCD(i,mod)==to_ZZ(1))
			{

				if (IsOdd(n))
				{
					pow = PowerMod(i,n, mod);
					if(pow==1)
						return Mod_p(i,mod);
				}
				else
				{
					pow = PowerMod(i,n/2,mod);
					if(pow == (mod-1))
					{
						return Mod_p(i,mod);
					}
				}
			}
		}
	}
	else
	{
		cout << "There is no" << n <<"-th root of unity"<< endl;
		return Mod_p(1,mod);
	}
	return Mod_p(1,mod);
}

//Returns the inverse of an element with value x
Mod_p G_mod_p::inverse(ZZ x){

	ZZ temp;
	temp = InvMod(x,mod);
	return Mod_p(temp,mod);
}

//Returns the inverse of an element with value x
Mod_p G_mod_p::inverse(long x){

	ZZ temp;
	temp = InvMod(to_ZZ(x),mod);
	return Mod_p(temp,mod);
}

//Assignment operator of the group
void G_mod_p::operator =(const G_mod_p& H){

	generator = H.get_gen();
	mod = H.get_mod();
}


#include <TriPQ/CGAL_Spherical_Polyhedron_Traits.h>
#include <TriPQ/SelectNearestEdge3.h>
#include <TriPQ/StartFromMostLocatedEdge.h>
#include <TriPQ/StartFromFixedEdge.h>
#include <TriPQ/TriPQ.h>

#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Polyhedron_items_with_id_3.h>
#include <CGAL/Polyhedron_incremental_builder_3.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Polyhedron_3.h>
#include <CGAL/Simple_cartesian.h>
#include <boost/iterator/transform_iterator.hpp>
#include <CGAL/IO/Polyhedron_iostream.h>

#include <array>
#include <cmath>
#include <chrono>
#include <random>
#include <vector>

typedef CGAL::Simple_cartesian<double> Kernel;
typedef CGAL::Polyhedron_items_with_id_3 Polyhedron_items;
typedef CGAL::Polyhedron_3<Kernel, Polyhedron_items> Polyhedron;
typedef CGAL::Triangulation_vertex_base_with_info_2<std::size_t, Kernel> Vb;
typedef CGAL::Triangulation_face_base_2<Kernel> Fb;
typedef CGAL::Triangulation_data_structure_2<Vb, Fb> Tds;
typedef CGAL::Delaunay_triangulation_2<Kernel, Tds> Delaunay;
typedef Kernel::Point_3 Point_3;
typedef Kernel::Point_2 Point_2;

Point_3 sphericalToCart(Point_2 const &s) {
  using std::sin;
  using std::cos;
  return Point_3(sin(s[0]) * cos(s[1]), sin(s[0]) * sin(s[1]), -cos(s[0]));
}

inline Point_2 PhiN(Point_3 const &x) {
  return Point_2(x[0] / (1.0 - x[2]), x[1] / (1.0 - x[2]));
}

std::vector<std::array<std::size_t, 3>>
triangulate(std::vector<Point_3> &points) {
  std::size_t vertexIndex = 0;
  auto const f = [&vertexIndex](auto const &p) {
    return std::make_pair(PhiN(p), vertexIndex++);
  };

  typedef boost::transform_iterator<
      decltype(f), std::vector<Point_3>::const_iterator> Iterator;

  // Delaunay dt(Iterator(points.cbegin(), f), Iterator(points.cend(), f));
  Delaunay dt;

  // ensure that no three points inserted to dt are collinear
  std::vector<std::pair<Point_2, std::size_t>> pt(Iterator(points.cbegin(), f),
                                                  Iterator(points.cend(), f));
  auto p0 = pt[0], p1 = pt[1];
  pt.erase(pt.begin());
  pt.erase(pt.begin());
  auto iter = pt.begin();
  while (!pt.empty()) {
    auto p2 = *iter;
    if (CGAL::collinear(p0.first, p1.first, p2.first)) {
      ++iter;
      continue;
    }
    dt.push_back(iter->first)->info() = iter->second;
    p0 = p1;
    p1 = p2;
    iter = pt.erase(iter);
    if (iter == pt.end()) iter = pt.begin();
  }

  std::size_t const N = points.size();
  std::vector<std::array<std::size_t, 3>> facets;
  facets.reserve(dt.number_of_faces());
  for (auto t = dt.all_faces_begin(); t != dt.all_faces_end(); ++t) {
    auto const v0 = t->vertex(0);
    auto const v1 = t->vertex(1);
    auto const v2 = t->vertex(2);
    auto const i0 = dt.is_infinite(v0) ? N : v0->info();
    auto const i1 = dt.is_infinite(v1) ? N : v1->info();
    auto const i2 = dt.is_infinite(v2) ? N : v2->info();
    facets.push_back({i0, i1, i2});
  }
  // The infinite point is the south pole
  points.emplace_back(0, 0, 1);

  return facets;
}

std::vector<Point_3> genPoints(std::size_t const N,
                               Point_2 offset = Point_2(0, 0)) {
  std::vector<Point_3> v;
  assert(N >= 2 && "N must be at leat 2");
  v.reserve(N * (N - 2) + 2);
  for (unsigned int i = 0; i <= N; ++i) {
    double const theta = offset[0] + M_PI * double(i) / double(N);
    for (unsigned int j = 0; j < N; ++j) {
      double const phi = offset[1] + 2.0 * M_PI * double(j) / double(N);
      Point_3 const pCart(sphericalToCart(Point_2(theta, phi)));
      v.push_back(pCart);
      if (i == 0 || i == N) break;
    }
  }
  return v;
}

Polyhedron constructPolyhedron() {
  typedef Polyhedron::HalfedgeDS HDS;
  auto v = genPoints(500);
  // remove north pole
  v.pop_back();
  auto const f = triangulate(v);

  struct Builder : public CGAL::Modifier_base<HDS> {
    std::vector<Point_3> const &v_;
    std::vector<std::array<std::size_t, 3>> const &f_;
    Builder(std::vector<Point_3> const &vv,
            std::vector<std::array<std::size_t, 3>> const &ff)
        : v_(vv), f_(ff) {}
    void operator()(HDS &hds) {
      CGAL::Polyhedron_incremental_builder_3<HDS> b(hds, true);
      std::size_t vIdx = 0;
      b.begin_surface(v_.size(), f_.size());
      for (auto const &p : v_) { b.add_vertex(p)->id() = vIdx++; }
      for (auto const &t : f_) {
        b.begin_facet();
        b.add_vertex_to_facet(t[2]);
        b.add_vertex_to_facet(t[1]);
        b.add_vertex_to_facet(t[0]);
        b.end_facet();
      }
      b.end_surface();
    }
  } builder(v, f);
  Polyhedron p;
  p.delegate(builder);
  return p;
}

static std::size_t comparisonCount = 0;

template <class P>
struct CountingTraits
    : public TriPQ::CGALSphericalPolyhedronTraitsBase<P, CountingTraits<P>> {
  typedef TriPQ::CGALSphericalPolyhedronTraits<P> Base;
  typedef CountingTraits<P> Self;
  struct IsRightOf {
    template <class Point>
    inline bool operator()(typename Self::Edge e, Point const &p) const {
      ++comparisonCount;
      return typename Base::IsRightOf()(e, p);
    }
  };
};

namespace std {

template <>
struct hash<typename CountingTraits<Polyhedron>::Edge>
    : public hash<void const *> {
  size_t operator()(typename CountingTraits<Polyhedron>::Edge e) const {
    return hash<void const *>()(static_cast<void const *>(&*e));
  }
};

} // namespace std

template <class Edge, class Point>
bool assertPointInTriangle(Edge e, Point const &x) {
  typedef TriPQ::CGALSphericalPolyhedronTraits<Polyhedron> T;

  if (typename T::IsRightOf()(e, x)) return false;
  if (typename T::IsRightOf()(e->next(), x)) return false;
  if (typename T::IsRightOf()(e->next()->next(), x)) return false;
  return true;
}

template <class Query, class Points>
void runQuery(Query const &q, Points const &p) {
  comparisonCount = 0;
  using Clock = std::chrono::high_resolution_clock;
  using TimePoint = std::chrono::time_point<Clock>;
  std::cout << "\tQuerying " << p.size() << " points..." << std::flush;
  TimePoint const start = Clock::now();

  auto const edges = q(p);

  using ms = std::chrono::milliseconds;
  TimePoint const end = Clock::now();
  auto const timeDiff = end - start;
  std::cout << "Done." << std::endl;
  std::cout << "\tTook " << std::chrono::duration_cast<ms>(timeDiff).count()
            << "ms" << std::endl;
  std::cout << "\tOn average " << double(comparisonCount) / double(p.size())
            << " comparisons" << std::endl;

  for (unsigned int i = 0; i < p.size(); ++i) {
    if (!assertPointInTriangle(edges[i], p[i])) {
      std::cerr << "Point not in triangle" << std::endl;
      std::cerr << "Point " << p[i] << std::endl;
      std::cerr << "Edge: (" << edges[i]->opposite()->vertex()->id() << ", "
                << edges[i]->vertex()->id() << ")" << std::endl;
      std::cerr << "Triangle " << edges[i]->vertex()->id() << ", "
                << edges[i]->next()->vertex()->id() << ", "
                << edges[i]->next()->next()->vertex()->id() << std::endl;
      exit(EXIT_FAILURE);
    }
  }
}

int main(int, char **) {
  std::random_device rd;
  std::mt19937 gen(rd());
  std::uniform_real_distribution<double> theta(0, M_PI);
  std::uniform_real_distribution<double> phi(0, 2.0 * M_PI);

  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromFixedEdge,
                            TriPQ::RandomEdgeSelect> QueryFixedRandom;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromFixedEdge,
                            TriPQ::SelectNearestEdge3> QueryFixedNearest;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromLastEdge,
                            TriPQ::RandomEdgeSelect> QueryLastRandom;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromLastEdge,
                            TriPQ::SelectNearestEdge3> QueryLastNearest;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromMostLocatedEdge,
                            TriPQ::RandomEdgeSelect> QueryMRRandom;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromMostLocatedEdge,
                            TriPQ::SelectNearestEdge3> QueryMRNearest;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromMostLocatedEdgeUnordered,
                            TriPQ::RandomEdgeSelect> QueryMRURandom;
  typedef TriPQ::PointQuery<CountingTraits<Polyhedron>,
                            TriPQ::StartFromMostLocatedEdgeUnordered,
                            TriPQ::SelectNearestEdge3> QueryMRUNearest;

  std::size_t const N = 40000;
  // std::size_t const N = 4;

  // Generate polyhedron
  std::cout << "Constructing triangulation... " << std::flush;
  auto const p = constructPolyhedron();
  std::cout << "done." << std::endl;

  std::vector<Point_3> randomPoints(N - 1);
  // generate query points
  for (unsigned int i = 0; i < N - 1; ++i) {
    randomPoints[i] = sphericalToCart(Point_2(theta(gen), phi(gen)));
  }

  auto const sequencialPoints =
      genPoints(std::sqrt(N), Point_2(theta(gen), phi(gen)));

  auto const e0 = p.halfedges_begin();

  std::cout << "Triangulation dimensions:" << std::endl;
  std::cout << "\t" << p.size_of_vertices() << " vertices" << std::endl;
  std::cout << "\t" << p.size_of_facets() << " triangles" << std::endl;
  std::cout << "\t" << p.size_of_halfedges() / 2 << " edges" << std::endl;

  std::cout << "Fixed starting edge, random edge select" << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryFixedRandom(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryFixedRandom(e0), sequencialPoints);

  std::cout << "Fixed starting edge, nearest edge" << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryFixedNearest(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryFixedNearest(e0), sequencialPoints);

  std::cout << "Last starting edge, random edge" << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryLastRandom(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryLastRandom(e0), sequencialPoints);

  std::cout << "Last starting edge, nearest edge" << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryLastNearest(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryLastNearest(e0), sequencialPoints);

  std::cout << "Most located starting edge, random edge" << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryMRRandom(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryMRRandom(e0), sequencialPoints);

  std::cout << "Most located starting edge, nearest edge" << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryMRNearest(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryMRNearest(e0), sequencialPoints);

  std::cout << "Most located starting edge (unordered map), random edge"
            << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryMRUNearest(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryMRURandom(e0), sequencialPoints);

  std::cout << "Most located starting edge (unordered map), nearest edge"
            << std::endl;
  std::cout << "Random points" << std::endl;
  runQuery(QueryMRUNearest(e0), randomPoints);
  std::cout << "Sequencial points" << std::endl;
  runQuery(QueryMRUNearest(e0), sequencialPoints);

  return EXIT_SUCCESS;
}


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

#ifndef CRYPTO3_MATH_EVALUATE_HPP
#define CRYPTO3_MATH_EVALUATE_HPP

#include <algorithm>
#include <vector>

#include <boost/math/tools/polynomial.hpp>

namespace nil {
    namespace crypto3 {
        namespace math {
            /*!
             * @brief
             * Naive evaluation of a *single* polynomial, used for testing purposes.
             *
             * The inputs are:
             * - an integer m
             * - a vector coeff representing monomial P of size m
             * - a field element element t
             * The output is the polynomial P(x) evaluated at x = t.
             */
            template<typename FieldValueType, typename ContiguousIterator>
            inline FieldValueType evaluate_polynomial(ContiguousIterator first, ContiguousIterator last,
                                                      const FieldValueType &t, std::size_t m) {
                BOOST_ASSERT(std::distance(first, last) == m);

                return boost::math::tools::evaluate_polynomial(&*first, t, m);
            }

            template<typename FieldValueType, typename ContiguousContainer>
            inline FieldValueType evaluate_polynomial(const ContiguousContainer &coeff, const FieldValueType &t,
                                                      std::size_t m) {
                return evaluate_polynomial(coeff.begin(), coeff.end(), t, m);
            }

            /*!
             * @brief
             * Naive evaluation of a *single* Lagrange polynomial, used for testing purposes.
             *
             * The inputs are:
             * - an integer m
             * - a domain S = (a_{0},...,a_{m-1}) of size m
             * - a field element element t
             * - an index idx in {0,...,m-1}
             * The output is the polynomial L_{idx,S}(z) evaluated at z = t.
             */
            template<typename FieldValueType, typename InputIterator>
            inline FieldValueType evaluate_lagrange_polynomial(InputIterator first, InputIterator last,
                                                               const FieldValueType &t, std::size_t m,
                                                               std::size_t idx) {
                typedef typename std::iterator_traits<InputIterator>::value_type value_type;

                BOOST_STATIC_ASSERT(std::is_same<value_type, FieldValueType>::value);

                if (m != std::distance(first, last)) {
                    throw std::invalid_argument("expected m == domain.size()");
                }
                if (idx >= m) {
                    throw std::invalid_argument("expected idx < m");
                }

                value_type num = value_type::one();
                value_type denom = value_type::one();

                for (std::size_t k = 0; k < m; ++k) {
                    if (k == idx) {
                        continue;
                    }

                    num *= t - *(first + k);
                    denom *= *(first + idx) - *(first + k);
                }

                return num * denom.inversed();
            }

            template<typename FieldValueType, typename Range>
            inline FieldValueType evaluate_lagrange_polynomial(const Range &domain, const FieldValueType &t,
                                                               std::size_t m, std::size_t idx) {
                typedef FieldValueType value_type;
                BOOST_STATIC_ASSERT(std::is_same<value_type, typename std::iterator_traits<decltype(std::begin(
                                                                 std::declval<Range>()))>::value_type>::value);

                return evaluate_lagrange_polynomial(domain.begin(), domain.end(), t, m, idx);
            }
        }    // namespace math
    }        // namespace crypto3
}    // namespace nil

#endif    // ALGEBRA_FFT_NAIVE_EVALUATE_HPP


//////////////////////////////////////////////////////////////////////////////
// random::poisson::devroye::crtp::parameters.hpp                        	//
//                                                                          //
//                                                                          //
//  (C) Copyright 2010 Erwann Rogard                                        //
//  Use, modification and distribution are subject to the                   //
//  Boost Software License, Version 1.0. (See accompanying file             //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)        //
//////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_RANDOM_POISSON_EXT_DEVROYE_CRTP_PARAMETERS_HPP_ER_2010
#define BOOST_RANDOM_POISSON_EXT_DEVROYE_CRTP_PARAMETERS_HPP_ER_2010
#include <boost/math/constants/constants.hpp>

namespace boost{
namespace random{
namespace poisson{
namespace devroye{

    // These are quantities that remain fixed throughout sampling and are only
    // modified if the poisson mean is changed.
	template<typename Int,typename T, typename P,typename IntT>
    struct parameters{
        typedef IntT converter_;

    	public:
        static const T pi;
        static const T one;
        static const T two;
        static const T eps;

        typedef Int result_type;
        typedef T input_type;

        parameters(){}
    	explicit parameters(const Int& i_mean)
        {
            using namespace boost::math;
			this->m1_ = converter_::convert( i_mean );
			this->m2_ = converter_::convert( 2 * i_mean );
			T m8  = converter_::convert( 8 * i_mean );
            T m32 = converter_::convert( 32 * i_mean );
            this->c1_ = converter_::convert(1) / m8; // Equation (6)

            // Equation (10)
            this->delta_ = 	log1p( m32 / pi, P() );
            this->delta_ = 	sqrt( this->delta() * this->m1() );

			// Below equation (7)
			this->sd1_ = sqrt( this->m1() + this->delta() / two );
            this->shape2_ = this->delta() / ( this->m2() + this->delta() );

            T a1, a2, a3, a;

			a1 = sqrt( pi * ( this->m2() + this->delta() ) );
            a1 *= exp( this->c1() );
            a2 = exp( -( this->delta() + one ) * this->shape2());
            a2 /= this->shape2();
            a3 = one;
            a = a1 + a2 + a3;
            this->p1_ = a1 / a;
            this->p2_ = a2 / a;
            this->p3_ = a3 / a;

			// Reconciliation with Fortran code
            // RL					m1
            // TWO					m2
			// CON 					c1
            // D 					delta
            // D2					delta + m2
			// D3 					1 / shape2
            // STDDEV 				sd1
            // SUM 					a1 + a2 + a3
            // PBODY 				p3 = a3/(a+a2+a3)
			// PTAIL 				p2 + p3

        }

		std::ostream& parameters_description(std::ostream& os)const{
        	return os
                << '('
                << "m1 = " 		<< this->m1()
                << ','
                << " m2 = "		<< this->m2()
                << ','
                << " delta = " 	<< this->delta()
                << ','
                << " loc1 = "	<< loc1()
                << ','
                << " sd1 = " 	<< this->sd1()
                << ','
                << " shape2 = "	<< this->shape2()
                << ','
                << " c1 = "		<< this->c1()
                << ','
                << " p1 = "		<< this->p1()
                << ','
                << " p2 = "		<< this->p2()
                << ','
                << " p3 = "		<< this->p3()
                << ')';
        }

		const T& m1()const{ return this->m1_; }
		const T& m2()const{ return this->m2_; }
		const T& delta()const{ return this->delta_; }
		static T loc1(){ return -one/two; }
		const T& sd1()const{ return this->sd1_; }
		const T& shape2()const{ return this->shape2_; }
        const T& c1()const{ return this->c1_; }
		const T& p1()const{ return this->p1_; }
		const T& p2()const{ return this->p2_; }
		const T& p3()const{ return this->p3_; }

		private:
        T inv_m1_;
		T m1_;
		T m2_;
        T delta_;
		T sd1_;
		T shape2_;

        T c1_;
        T p1_;
        T p2_;
		T p3_;

    };

    template<typename Int,typename T,typename P,typename IntT>
    const T parameters<Int,T,P,IntT>::pi = boost::math::constants::pi<T>();

    template<typename Int,typename T,typename P,typename IntT>
    const T parameters<Int,T,P,IntT>::one = IntT::convert( 1 );

    template<typename Int,typename T,typename P,typename IntT>
    const T parameters<Int,T,P,IntT>::two = IntT::convert( 2 );

    template<typename Int,typename T,typename P,typename IntT>
    const T parameters<Int,T,P,IntT>::eps
        = boost::numeric::bounds<T>::smallest();

}// devroye
}// poisson
}// random
}// boost

#endif


/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

/*
 Copyright (C) 2009 Jose Aparicio

 This file is part of QuantLib, a free-software/open-source library
 for financial quantitative analysts and developers - http://quantlib.org/

 QuantLib is free software: you can redistribute it and/or modify it
 under the terms of the QuantLib license.  You should have received a
 copy of the license along with this program; if not, please email
 <quantlib-dev@lists.sf.net>. The license is also available online at
 <http://quantlib.org/license.shtml>.

 This program is distributed in the hope that it will be useful, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 FOR A PARTICULAR PURPOSE.  See the license for more details.
*/

#ifndef recursive_cdo_engine_hpp
#define recursive_cdo_engine_hpp

#include <ql/math/integrals/gaussianquadratures.hpp>
#include <ql/math/distributions/normaldistribution.hpp>
#include <ql/math/matrixutilities/factorreduction.hpp>
#include <ql/experimental/credit/syntheticcdoengines.hpp>
#include <ql/experimental/credit/onefactorgaussiancopula.hpp>
#include <ql/experimental/credit/onefactorstudentcopula.hpp>
#include <boost/bind.hpp>
#include <map>
#include <algorithm>

namespace QuantLib {

    /*! Recursive STCDO pricing for a heterogeneous pool of names. The pool
        names are heterogeneous in their default probabilities, notionals
        and recovery rates. Correlations are pairwise. The recursive pricing
        algorithm used here is described in Andersen, Sidenius and Basu;
        "All your hedges in one basket", Risk, November 2003, pages 67-72

        Notice that using copulas other than Gaussian it is only an
        approximation (see remark on p.68).
    */
    template <class CDOEngine, class copulaT>
    class RecursiveCdoEngine : public CDOEngine {
      public:
        // Base constructors call default Handle constructor, the copula is to
        // be relinked by template partial specializations on the copula type

        //! Single correlation construction
        RecursiveCdoEngine(const Handle<Quote>& correl,
                           Size nbuckets  = 1,
                           Size quadOrder = 20)
        : correlQuote_(correl), copula_(), nBuckets_(nbuckets),
          integral_(quadOrder), wk_()
        {
            this->registerWith(correl);
        }

        //! Correlation name to name single factor construction
        RecursiveCdoEngine(const Handle<Quote>& correl,
                           const Matrix& correlMtrx,
                           Size nbuckets  = 1,
                           Size quadOrder = 20)
        : correlQuote_(correl), copula_(), nBuckets_(nbuckets),
          integral_(quadOrder), wk_(),
          oneFactorCorrels_(factorReduction(correlMtrx))
        {
            // at least
            QL_REQUIRE(!oneFactorCorrels_.empty(),
                "Invalid correlation parameter matrix.");
        }
      protected:
        void initialize() const;
      private:
        //! Weights the conditional portfolio loss by the mkt factor
        //    distribtion
        Real integratorLoss(const Date& date, Real mktFactor) const {
            return expectedConditionalLoss(date, mktFactor) *
               copula_->density(mktFactor);
        }
        //! Portfolio loss conditional to the market factor value
        Real expectedConditionalLoss(const Date& date,
                                     Real mktFactor) const;
      public:
        void update();

        /*  Expected tranche Loss calculation.
            This is computed from the first equation on page 70 (not numbered)
            Notice that while we want to compute:
            \f[
            EL(t) = \sum_{l_k}l_k P(l;t) =
              \sum_{l_k}l_k \int P(l_k;t|\omega) d\omega q(\omega)
            \f]
            One can invert the sumation and the integral order to:
            \f[
            EL(t) = \int\,q(\omega)\,d\omega\,\sum_{l_k}\,l_k\,P(l_k;t|\omega) =
              \int\,q(\omega)\,d\omega\,EL(t|\omega)
            \f]
            and this is the way it is integrated here. The recursion formula makes
            it easier this way.
        */
        Real expectedTrancheLoss(const Date& date) const {
            return
                integral_(boost::bind(
                    &RecursiveCdoEngine<CDOEngine, copulaT>::integratorLoss,
                    this,
                    date,
                    _1)
                );
        }
      protected:
        const Handle<Quote> correlQuote_;
        mutable RelinkableHandle<copulaT> copula_;
      private:
        // loss model descriptor members
        Size nBuckets_;
       const GaussHermiteIntegration integral_;
        mutable std::vector<Real> wk_;
        mutable Real loss_unit_;
        //! name to name factor loadings (betas). In the single factor copula:
        //    correl = beta * beta
        // When constructing through a single correlation number the factor is
        //   taken to be the positive swuare root of this number in the copula.
        mutable std::vector<Real> oneFactorCorrels_;
    };

    template <class CDOEngine, class copulaT>
    void RecursiveCdoEngine<CDOEngine, copulaT>::update() {
        oneFactorCorrels_.clear();
        CDOEngine::update();
    }

    template <class CDOEngine, class copulaT>
    void RecursiveCdoEngine<CDOEngine, copulaT>::initialize() const {
        wk_.clear();
        Date today = Settings::instance().evaluationDate();
        Date start = this->arguments_.schedule.startDate();
        boost::shared_ptr<Basket>& basket = this->arguments_.basket;
        /*
          Remove defaulted names and adjust the subordination.
        */
        std::vector<std::string> names =
            basket->remainingNames(start, today);
        std::vector<Real> notionals
            = basket->remainingNotionals(start, today);
        Real a = basket->remainingAttachmentRatio(start, today);
        Real d = basket->remainingDetachmentRatio(start, today);
        const boost::shared_ptr<Pool> pool = basket->pool();
        this->remainingBasket_ =
            boost::shared_ptr<Basket>(new Basket(names, notionals, pool,
                                                basket->remainingDefaultKeys(start, today),
                                                basket->remainingRecModels(start, today),
                                                 a, d));

        this->results_.xMin = this->remainingBasket_->attachmentAmount();
        this->results_.xMax = this->remainingBasket_->detachmentAmount();
        this->results_.remainingNotional =
            this->results_.xMax - this->results_.xMin;
        //----------------------------------------------------------------
        if(oneFactorCorrels_.empty())
            oneFactorCorrels_ = std::vector<Real>(names.size(),
                                    correlQuote_->value());
        // check size of factors:
        QL_REQUIRE(oneFactorCorrels_.size() == names.size(),
            "Size of matrix must match number of names in the basket.");
        //
        std::vector<Real> lgdsTmp, lgds = this->remainingBasket_->LGDs();
        lgdsTmp = lgds;
        lgds.erase(std::remove(lgds.begin(), lgds.end(), 0.), lgds.end());
        loss_unit_ = *(std::min_element(lgds.begin(), lgds.end()))
            / nBuckets_;

        for(Size i = 0; i<names.size(); i++)
            wk_.push_back(std::floor(lgdsTmp[i]/loss_unit_ + .5));

        // Could not check parameters at construction time because we
        //   had no arguments yet, do it now:
        if(oneFactorCorrels_.size() == 1)
            oneFactorCorrels_ =
                std::vector<Real>(pool->size(), oneFactorCorrels_[0]);
        else
            QL_REQUIRE(oneFactorCorrels_.size() == this->remainingBasket_->size(),
                "Incompatible correl matrix, pool size.");
        //----------------------------------------------------------------
        const std::vector<Date>& dates = this->arguments_.schedule.dates();
        for (Size i = 0; i < dates.size(); i++) {
            if (dates[i] <= today)
                this->results_.expectedTrancheLoss.push_back(0.0);
            else {
                Real L = expectedTrancheLoss(dates[i]);
                this->results_.expectedTrancheLoss.push_back(L);
            }
        }

    }

    //! Portfolio loss conditional to the market factor value
    template <class CDOEngine, class copulaT>
    Real RecursiveCdoEngine<CDOEngine, copulaT>::expectedConditionalLoss(
                                 const Date& date,
                                 Real mktFactor) const {
        const std::vector<std::string>& names = this->remainingBasket_->names();

        // eq. 10 p.68
        // attainable losses distribution, recursive algorithm
        std::vector<Probability> uncDefProb =
            this->remainingBasket_->probabilities(date);;
        std::map<Real, Probability> pIndepDistrib;
        // K=0
        pIndepDistrib.insert(std::make_pair(0., 1.));
        for(Size iName=0; iName<names.size(); iName++) {

            // to do: allow for matrix constructor and uncoment this
            // correlQuote_->setValue(oneFactorCorrels_[iName]);

            Probability pDef =
                copula_->conditionalProbability(uncDefProb[iName],
                                                mktFactor);
            // iterate on all possible losses in the distribution:
            std::map<Real, Probability> pDistTemp;
            std::map<Real, Probability>::iterator distIt =
                pIndepDistrib.begin();
            while(distIt != pIndepDistrib.end()) {
                // update prob if this name does not default
                std::map<Real, Probability>::iterator matchIt
                    = pDistTemp.find(distIt->first);
                if(matchIt != pDistTemp.end()) {
                    matchIt->second += distIt->second * (1.-pDef);
                }else{
                    pDistTemp.insert(std::make_pair(distIt->first,
                        distIt->second * (1.-pDef)));
                }
                // and if it does
                matchIt = pDistTemp.find(distIt->first + wk_[iName]);
                if(matchIt != pDistTemp.end()) {
                    matchIt->second += distIt->second * pDef;
                }else{
                    pDistTemp.insert(std::make_pair(
                        distIt->first+wk_[iName], distIt->second * pDef));
                }
                distIt++;
            }
            // copy back
            pIndepDistrib = pDistTemp;
        }

        // get the expected value subject to the value of the market
        //   factor.
        Real expLoss = 0.;
        //---------------------------------------------------------------
        /* This is the original (easy to read) loop which I have partially
             unroll below to take profit of the fact that once we go over
             the tranche top the loss amount is fixed:

        std::map<Real, Probability>::iterator distIt =
            pIndepDistrib.begin();
        while(distIt != pIndepDistrib.end()) {
            Real loss = distIt->first * loss_unit_
                                ;
            loss = std::max(std::min(loss,
                results_.xMax)-results_.xMin, 0.);
            expLoss += loss * distIt->second;
            distIt++;
        }
        return expLoss ;
        */
        //---------------------------------------------------------------
        Real relativeMax = this->results_.xMax / loss_unit_;
        Real relativeMin = this->results_.xMin / loss_unit_;
        Size relativeMinIdx = std::floor(relativeMin);
        std::map<Real, Probability>::iterator
            distIt = pIndepDistrib.lower_bound(relativeMinIdx),
            itTop  = pIndepDistrib.lower_bound(relativeMax);
        for(; distIt != itTop; distIt++)
            expLoss += std::max(std::min(distIt->first, relativeMax)
                                -relativeMin, 0.) * distIt->second;
        Real sumProbs = 0.;
        for(;distIt != pIndepDistrib.end(); distIt++)
            sumProbs += distIt->second;
        return expLoss * loss_unit_ + this->results_.remainingNotional * sumProbs;
    }

    // Partial specializations on the copula type. Kind
    //   of template virtual constructions. Allows to own the correlation
    //   quote and the copula for each specific copula type. It only
    //   needs to know its particular copula constructor.
    // These and the base correlation pricers use a unifactorial copula
    //   which needs to be modified for different parameters of the
    //   correlation per name to name if theres such an structure or
    //   because the correlation parameter has got a time or/and loss
    //   level surface.
    //
    // TO do: Correlation matrix constructors.

    //! Specialization for Gaussian copula, the integration still remains
    //    to be defined by the user out of the available ones in
    //    syntheticcdoengines.
    template <class CDOEngine>
    class GaussianRecursiveCdoEngine : public
        RecursiveCdoEngine<CDOEngine, OneFactorGaussianCopula> {
      public:
        //! quote constructor.
        GaussianRecursiveCdoEngine(
            const Handle<Quote>& correlQuote,
            Size nbuckets  = 1,
            Size quadOrder = 12,
            Real maxval    = 5.,
            Size steps     = 50)
            :
            RecursiveCdoEngine<CDOEngine, OneFactorGaussianCopula>(correlQuote,
                    nbuckets, quadOrder) {
                this->copula_.linkTo(boost::shared_ptr<OneFactorGaussianCopula>(
                new OneFactorGaussianCopula(correlQuote,  maxval, steps)), true);
        }
    };

    template <class CDOEngine>
    class StudentRecursiveCdoEngine : public
        RecursiveCdoEngine<CDOEngine, OneFactorStudentCopula> {
      public:
        //! quote constructor.
        StudentRecursiveCdoEngine(
            const Handle<Quote>& correlQuote,
            Size nz,
            Size nm,
            Size nbuckets  = 1,
            Size quadOrder = 12,
            Real maxval    = 5.,
            Size steps     = 50)
            :
            RecursiveCdoEngine<CDOEngine, OneFactorStudentCopula>(correlQuote,
                    nbuckets, quadOrder) {
                this->copula_.linkTo(boost::shared_ptr<OneFactorStudentCopula>(
                new OneFactorStudentCopula(correlQuote, nz, nm, maxval, steps)), true);
        }
    };

    typedef GaussianRecursiveCdoEngine<MidPointCDOEngine> GaussRecCDOEngine;
    typedef StudentRecursiveCdoEngine<MidPointCDOEngine>  StudentRecCDOEngine;

}

#endif