/*
 * This file belongs to the Galois project, a C++ library for exploiting parallelism.
 * The code is being released under the terms of the 3-Clause BSD License (a
 * copy is located in LICENSE.txt at the top-level directory).
 *
 * Copyright (C) 2018, The University of Texas at Austin. All rights reserved.
 * UNIVERSITY EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES CONCERNING THIS
 * SOFTWARE AND DOCUMENTATION, INCLUDING ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR ANY PARTICULAR PURPOSE, NON-INFRINGEMENT AND WARRANTIES OF
 * PERFORMANCE, AND ANY WARRANTY THAT MIGHT OTHERWISE ARISE FROM COURSE OF
 * DEALING OR USAGE OF TRADE.  NO WARRANTY IS EITHER EXPRESS OR IMPLIED WITH
 * RESPECT TO THE USE OF THE SOFTWARE OR DOCUMENTATION. Under no circumstances
 * shall University be liable for incidental, special, indirect, direct or
 * consequential damages or loss of profits, interruption of business, or
 * related expenses which may arise from use of Software or Documentation,
 * including but not limited to those resulting from defects in Software and/or
 * Documentation, or loss or inaccuracy of data of any kind.
 */

#include "galois/Galois.h"
#include "galois/Bag.h"
#include "galois/Timer.h"
#include "galois/ParallelSTL.h"
#include "llvm/Support/CommandLine.h"
#include "Lonestar/BoilerPlate.h"

#include <gmpxx.h>
//#include <givaro/givgfq.h>
//#include <givaro/StaticElement.h>

#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_fusion.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/spirit/include/support_istream_iterator.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/bind.hpp>
#include <boost/utility.hpp>

#include <fstream>
#include <map>
#include <numeric>
#include <algorithm>

namespace cll = llvm::cl;

static const char* name = "Buchberger Algorithm";
static const char* desc =
    "Generates Groebner basis for polynomial ideal using Buchberger Algorithm";
static const char* url = 0;

enum MonomialOrder { lex, grevlex };

static cll::opt<std::string> filename(cll::Positional,
                                      cll::desc("<input file>"), cll::Required);
static cll::opt<MonomialOrder> monomialOrder(
    cll::desc("Monomial order:"),
    cll::values(clEnumVal(lex, "lexicographic"),
                clEnumVal(grevlex, "graded reverse lexicographic"),
                clEnumValEnd),
    cll::init(grevlex));

#if 0
//! Unfortunate pun
struct GaloisField {
  typedef Givaro::GFqDom<long> Field;
  typedef Field::Element element_type;
  Field F;

  GaloisField(): F(2, 1) { }

  void init() { }

  void inverse(element_type& a) const {
    F.invin(a);
  }

  void add(element_type& a, const element_type& b) const {
    F.addin(a, b);
  }

  void subtract(element_type& a, const element_type& b) const {
    F.subin(a, b);
  }

  void negate(element_type& a) const {
    F.negin(a);
  }

  void divide(element_type& a, const element_type& b) const {
    F.divin(a, b);
  }

  void multiply(element_type& a, const element_type& b) const {
    F.mulin(a, b);
  }

  void assign(element_type& a, const element_type& b) const {
    F.assign(a, b);
  }

  void assignOne(element_type& a) const {
    F.assign(a, F.one);
  }

  int sign(const element_type& a) const {
    return neq(a, 0) ? 1 : 0;
  }

  bool neq(const element_type& a, const element_type& b) const {
    return a != b;
  }

  void write(std::ostream& out, const element_type& a) const {
    out << a;
  }
};
#endif

struct RationalField {
  typedef mpq_class element_type;

  void init() {}

  void inverse(element_type& a) const { a = 1 / a; }

  void add(element_type& a, const element_type& b) const { a += b; }

  void subtract(element_type& a, const element_type& b) const { a -= b; }

  void negate(element_type& a) const { a = -a; }

  void divide(element_type& a, const element_type& b) const { a /= b; }

  void multiply(element_type& a, const element_type& b) const { a *= b; }

  void assign(element_type& a, const element_type& b) const { a = b; }

  void assignOne(element_type& a) const { a = 1; }

  int sign(const element_type& a) const { return sgn(a); }

  bool neq(const element_type& a, const element_type& b) const {
    return a != b;
  }

  void write(std::ostream& out, const element_type& a) const {
    out << a.get_str();
  }
};

typedef RationalField Field;
// typedef GaloisField Field;
typedef Field::element_type number;

Field TheField;

template <class Order, class Alloc>
class Ring;

//! Weights to ease vectorization of lexicographic sorting
static int powersOfTwo[16] __attribute__((__aligned__(16)));

struct Term {
  typedef char exp_type;
  typedef char __attribute__((__aligned__(16))) * __restrict__ exp_ptr_type;

private:
  template <class Order, class Alloc>
  friend class Ring;

  Field::element_type m_coef;
  exp_ptr_type m_exps;
  int m_totalDegree;

public:
  Term() : m_coef(1), m_exps(NULL), m_totalDegree(0) {}

  Field::element_type& coef() { return m_coef; }
  const Field::element_type& coef() const { return m_coef; }

  exp_type& exp(int i) { return m_exps[i]; }
  const exp_type& exp(int i) const { return m_exps[i]; }

  int totalDegree() const { return m_totalDegree; }

  exp_ptr_type const& exps() const { return m_exps; }
  exp_ptr_type& exps() { return m_exps; }

  //! Compute LCM
  template <class R>
  void makeLcm(const Term& a, const Term& b, R& ring) {
    // Vectorized.
    // GCC 4.7 vectorizer prefers (1) over (2)
    int N = ring.numVars();
    for (int i = 0; i < N; ++i) { // (1)
      // for (int i = 0; i < ring.numVars(); ++i) { // (2)
      m_exps[i] = std::max(a.m_exps[i], b.m_exps[i]);
    }
  }

  template <class R>
  bool equals(const Term& b, R& ring) {
    // TODO vectorize
    int N = ring.numVars();
    for (int i = 0; i < N; ++i) {
      if (b.exp(i) != exp(i))
        return false;
    }
    return true;
  }

  //! a | b ?
  template <class R>
  bool divides(const Term& b, const R& ring) const {
    // TODO vectorize
    for (int i = 0; i < ring.numVars(); ++i) {
      if (b.exp(i) < exp(i))
        return false;
    }
    return true;
  }

  //! Relatively prime?
  template <class R>
  bool relPrime(const Term& b, const R& ring) const {
    // TODO vectorize
    for (int i = 0; i < ring.numVars(); ++i) {
      if (exp(i) && b.exp(i))
        return false;
    }
    return true;
  }
};

//! Sequence of terms
class Poly {
  // TODO: more bucketed representations!!!
  typedef std::vector<Term*> Terms;
  Terms terms;
  int m_totalDegree;

public:
  typedef Terms::iterator iterator;
  typedef Terms::const_iterator const_iterator;

  Poly() : m_totalDegree(0) {}

  int totalDegree() const { return m_totalDegree; }

  const Term* head() const { return terms.front(); }

  Term* head() { return terms.front(); }

  const_iterator begin() const { return terms.begin(); }

  const_iterator end() const { return terms.end(); }

  iterator begin() { return terms.begin(); }

  iterator end() { return terms.end(); }

  void push(Term* t) {
    terms.push_back(t);
    m_totalDegree += t->totalDegree();
  }

  bool empty() const { return terms.empty(); }

  //! f = f * s
  template <class R>
  void scaleBy(const Field::element_type& s, R& ring) {
    for (iterator ii = begin(), ei = end(); ii != ei; ++ii) {
      Term* t = *ii;
      TheField.multiply(t->coef(), s);
    }
  }

  //! f = f * a/b
  template <class R>
  void scaleBy(const Term& a, const Term& b, R& ring) {
    Field::element_type s;
    TheField.assign(s, a.coef());
    TheField.divide(s, b.coef());

    m_totalDegree = 0;

    for (iterator ii = begin(), ei = end(); ii != ei; ++ii) {
      Term* t = *ii;
      // Vectorized.
      for (int i = 0; i < ring.numVars(); ++i) {
        assert(a.exp(i) >= b.exp(i));
        t->exp(i) += a.exp(i) - b.exp(i);
      }
      TheField.multiply(t->coef(), s);
      ring.generateTotalDegree(*t);
      m_totalDegree += t->totalDegree();
    }
  }
};

class PolySet {
  // TODO: better allocator
  typedef std::vector<Poly*> Polys;

  Polys polys;

public:
  typedef Polys::iterator iterator;
  typedef Polys::const_iterator const_iterator;

  void push(Poly* p) { polys.push_back(p); }

  iterator begin() { return polys.begin(); }

  iterator end() { return polys.end(); }

  const_iterator begin() const { return polys.begin(); }

  const_iterator end() const { return polys.end(); }
};

template <class Order, class Alloc = std::allocator<char>>
class Ring : private boost::noncopyable {
private:
  Order order;
  Alloc m_alloc;
  int m_numVars;
  std::list<Poly*> polys;
  std::list<Term*> terms;

  //! Sizeof Term plus padding and space for exps
  size_t sizeofTerm() const {
    return sizeof(Term) + 15 + m_numVars * sizeof(Term::exp_type);
  }

  typedef Term::exp_type* exps_ptr_t;

  //! Find 16-byte aligned address after ptr
  exps_ptr_t expsPtr(const Term* ptr) const {
    return reinterpret_cast<exps_ptr_t>(((uintptr_t)(ptr + 1) + 15) & ~0x0F);
  }

public:
  const static int kBlockSize = 16 / sizeof(Term::exp_type);

  template <class AllocNew>
  struct realloc {
    typedef Ring<Order, AllocNew> other;
  };

  Ring(int numVars, const Alloc& alloc = Alloc()) : m_alloc(alloc) {
    m_numVars = ((numVars + kBlockSize - 1) / kBlockSize) * kBlockSize;
  }

  ~Ring() {
    for (std::list<Poly*>::iterator ii = polys.begin(), ei = polys.end();
         ii != ei; ++ii) {
      (*ii)->~Poly();
      m_alloc.deallocate(reinterpret_cast<char*>(*ii), sizeof(Poly));
    }
    for (std::list<Term*>::iterator ii = terms.begin(), ei = terms.end();
         ii != ei; ++ii) {
      (*ii)->~Term();
      m_alloc.deallocate(reinterpret_cast<char*>(*ii), sizeofTerm());
    }
  }

  // TODO
  Ring tempRing() const { return *this; }

  //! For ease of vectorization, this will always be a multiple of blockSize
  int numVars() const { return m_numVars; }

  bool gt(const Term& a, const Term& b) const { return order.gt(a, b, *this); }

  Poly& makePoly(const Poly& p = Poly()) {
    Poly* ptr = reinterpret_cast<Poly*>(m_alloc.allocate(sizeof(Poly)));
    new (ptr) Poly();
    for (Poly::const_iterator ii = p.begin(), ei = p.end(); ii != ei; ++ii) {
      const Term* t = *ii;
      ptr->push(&makeTerm(*t));
    }
    polys.push_back(ptr);
    return *ptr;
  }

  Term& makeTerm(const Term& t = Term()) {
    Term* ptr = reinterpret_cast<Term*>(m_alloc.allocate(sizeofTerm()));
    new (ptr) Term(t);
    ptr->m_exps = expsPtr(ptr);

    if (t.exps() != NULL) {
      memcpy(ptr->m_exps, t.exps(), m_numVars * sizeof(Term::exp_type));
    } else {
      memset(ptr->m_exps, 0, m_numVars * sizeof(Term::exp_type));
    }
    terms.push_back(ptr);
    return *ptr;
  }

  void generateTotalDegree(Term& t) {
    // Vectorized.
    // GCC 4.7 vectorizer prefers (1) over (2)
    int sum               = 0;
    Term::exp_ptr_type ii = t.exps();
    for (int i = 0; i < numVars(); ++i) // (1)
      sum += ii[i];
    t.m_totalDegree = sum;
    // std::accumulate(t.exps(), t.exps() + numVars(), 0, std::plus<int>()); //
    // (2)
  }

  void write(std::ostream& out, const Term& t,
             const std::vector<std::string>* idMap = NULL) const {
    if (TheField.sign(t.coef()) >= 0)
      out << "+";
    TheField.write(out, t.coef());

    for (int i = 0; i < numVars(); ++i) {
      int exp = t.exp(i);
      if (exp == 0)
        continue;
      out << " ";
      if (idMap)
        out << (*idMap)[i];
      else
        out << "x" << i;
      if (exp > 1)
        out << "^" << exp;
    }
  }

  void write(std::ostream& out, const Term* t,
             const std::vector<std::string>* idMap = NULL) const {
    write(out, *t, idMap);
  }

  void write(std::ostream& out, const Poly& p,
             const std::vector<std::string>* idMap = NULL) const {
    for (Poly::const_iterator ii = p.begin(), ei = p.end(); ii != ei; ++ii) {
      write(out, *ii, idMap);
    }
  }

  void write(std::ostream& out, const PolySet& polys,
             const std::vector<std::string>* idMap = NULL) const {
    for (PolySet::const_iterator ii = polys.begin(), ei = polys.end(); ii != ei;
         ++ii) {
      write(out, **ii, idMap);
      if (ii + 1 != ei)
        out << ", ";
    }
  }

  //! Rank under Buchberger's normal selection strategy
  int normalRank(const Poly& f, const Poly& g) const {
    const Term* fh = f.head();
    const Term* gh = g.head();
    int retval     = 0;

    // Vectorized.
    for (int i = 0; i < numVars(); ++i) {
      retval += std::max(fh->exp(i), gh->exp(i));
    }

    return retval;
  }

  //! Rank pairs using sugar strategy
  int rankPair(const Poly& f, const Poly& g) const {
    const Term* fh = f.head();
    const Term* gh = g.head();
    int retval     = std::max(f.totalDegree() - fh->totalDegree(),
                          g.totalDegree() - gh->totalDegree());
    int n          = normalRank(f, g);
    // prefer pairs with least sugar component, breaking ties with normalRank
    int sugar = retval + n;
    return (sugar << 8) | (n & 0xFF);
  }
};

class LexOrder {
  template <class R>
  bool gtSimple(const Term& a, const Term& b, const R& ring) const {
    for (int i = 0; i < ring.numVars(); ++i) {
      if (a.exp(i) < b.exp(i))
        return false;
      else if (a.exp(i) > b.exp(i))
        return true;
    }
    return false;
  }

  template <class R>
  bool gtVectorized(const Term& a, const Term& b, const R& ring) const {
    Term::exp_ptr_type aa = a.exps();
    Term::exp_ptr_type bb = b.exps();
    int* pp               = powersOfTwo;

    for (int block = 0; block < ring.numVars(); block += R::kBlockSize) {
      // Sigh. In GCC 4.7 this inner loop (1) is not vectorized because of
      // the condition (2)
      int smaller = 0;
      int larger  = 0;
      for (int i = 0; i < R::kBlockSize; ++i) { // (1)
        int idx = block + i;
        smaller += aa[idx] < bb[idx] ? pp[R::kBlockSize - i] : 0;
        larger += aa[idx] > bb[idx] ? pp[R::kBlockSize - i] : 0;
      }
      if (smaller == larger) // (2)
        continue;
      return larger > smaller;
    }

    return false;
  }

public:
  template <class R>
  bool gt(const Term& a, const Term& b, const R& ring) const {
    return gtVectorized(a, b, ring);
  }
};

class GrevlexOrder {
  template <class R>
  bool gtSimple(const Term& a, const Term& b, const R& ring) const {
    if (a.totalDegree() != b.totalDegree())
      return a.totalDegree() > b.totalDegree();

    for (int i = ring.numVars() - 1; i >= 0; --i) {
      if (a.exp(i) < b.exp(i))
        return true;
      else if (a.exp(i) > b.exp(i))
        return false;
    }
    return false;
  }

  template <class R>
  bool gtVectorized(const Term& a, const Term& b, const R& ring) const {
    if (a.totalDegree() != b.totalDegree())
      return a.totalDegree() > b.totalDegree();

    Term::exp_ptr_type aa = a.exps();
    Term::exp_ptr_type bb = b.exps();
    int* pp               = powersOfTwo;

    for (int block = ring.numVars() - 1; block >= 0; block -= R::kBlockSize) {
      // Sigh. In GCC 4.7 this inner loop (1) is not vectorized because of
      // the condition (2)
      int smaller = 0;
      int larger  = 0;
      for (int i = 0; i < R::kBlockSize; ++i) { // (1)
        int idx = block - i;
        smaller += aa[idx] < bb[idx] ? pp[R::kBlockSize - i] : 0;
        larger += aa[idx] > bb[idx] ? pp[R::kBlockSize - i] : 0;
      }
      if (smaller == larger) // (2)
        continue;
      return larger > smaller;
    }

    return false;
  }

public:
  template <class R>
  bool gt(const Term& a, const Term& b, const R& ring) const {
    return gtVectorized(a, b, ring);
  }
};

struct PolyPair {
  Poly* a;
  Poly* b;
  Term* lcm;
  int index;
  bool m_useless;
  PolyPair(Poly* _a, Poly* _b, Term* _lcm, int _index = 0)
      : a(_a), b(_b), lcm(_lcm), index(_index), m_useless(false) {}
  bool useless() const { return m_useless; }
  void makeUseless() { m_useless = true; }
};

//! r = a - b
template <class R>
Poly* subtract(const Poly& a, const Poly& b, R& ring) {
  Poly* result            = &ring.makePoly();
  Poly::const_iterator aa = a.begin(), ea = a.end();
  Poly::const_iterator bb = b.begin(), eb = b.end();

  while (aa != ea && bb != eb) {
    Term* a = *aa;
    Term* b = *bb;
    Term t;
    if (ring.gt(*a, *b)) {
      t = Term(*a);
      ++aa;
    } else if (ring.gt(*b, *a)) {
      t = Term(*b);
      TheField.negate(t.coef());
      ++bb;
    } else {
      t = Term(*a);
      TheField.subtract(t.coef(), b->coef());
      ++aa;
      ++bb;
    }
    if (TheField.neq(t.coef(), 0)) {
      result->push(&ring.makeTerm(t));
    }
  }
  for (; aa != ea; ++aa) {
    result->push(*aa);
  }
  for (; bb != eb; ++bb) {
    Term* b = *bb;
    Term t(*b);
    TheField.negate(t.coef());
    assert(TheField.neq(t.coef(), 0));
    result->push(&ring.makeTerm(t));
  }
  return result;
}

//! Compute s-polynomial.
//! spoly(f,g) = f * lcm/LT(f) - g * lcm/LT(g) where lcm = LCM(LM(f), LM(g))
template <class R>
Poly* spoly(const Term& lcm, const Poly& f, const Poly& g, R& ring) {
  // ff = f * lcm/LT(f)
  Poly& ff = ring.makePoly(f);
  ff.scaleBy(lcm, *f.head(), ring);
  // gg = g * lcm/LT(g)
  Poly& gg = ring.makePoly(g);
  gg.scaleBy(lcm, *g.head(), ring);

  return subtract(ff, gg, ring);
};

//! Reduce f with respect to polys.
//! Not canonical unless polys is groebner basis (obvs)
template <class R>
Poly* reduce(const Poly& f, const PolySet& polys, R& ring) {
  //  std::cerr << "    "; ring.write(std::cerr, f);

  const Poly* cur         = &f;
  Poly::const_iterator ff = cur->begin(), ef = cur->end();

  while (ff != ef) {
    bool reduced = false;

    for (PolySet::const_iterator pp = polys.begin(), ep = polys.end(); pp != ep;
         ++pp) {
      const Poly* p = *pp;

      // when we are doing inter reduction, i.e., f reduce G when f \in G, skip
      // ourselves
      if (&f == p)
        continue;

      // Right now we never delete terms reduced to zero by interReduce() so
      // ignore them here
      if (p->empty())
        continue;

      const Term* ph = p->head();

      if (!ph->divides(**ff, ring))
        continue;

      //      std::cerr << " ("; ring.write(std::cerr, *ph); std::cerr << "|";
      //      ring.write(std::cerr, **ff); std::cerr << ")";
      // cur = cur - p * ff/ph
      Poly& g = ring.makePoly(*p);
      g.scaleBy(**ff, *ph, ring);
      cur = subtract(*cur, g, ring);

      ff = cur->begin();
      ef = cur->end();
      //      std::cerr << " => "; ring.write(std::cerr, *cur);
      reduced = true;
      break;
    }
    if (!reduced)
      ++ff;
  }

  //  std::cerr << "\n";

  return const_cast<Poly*>(cur);
}

galois::Statistic bkUpdate("BKUpdate");
galois::Statistic mfUpdate("MFUpdate");
galois::Statistic bpUpdate("BPUpdate");

//! Updates basis with h, adds new pairs and marks some previous pairs as
//! useless.
template <class R1, class R2, class Pushable>
void update(Poly* h, PolySet& basis, galois::InsertBag<PolyPair>& pairs,
            R1& localRing, R2& ring, Pushable& out) {
  // Gebauer-Moeller criterion B_k
  Term& lcm_t = localRing.makeTerm();
  for (galois::InsertBag<PolyPair>::iterator ii = pairs.begin(),
                                             ei = pairs.end();
       ii != ei; ++ii) {
    PolyPair& p = *ii;
    if (p.useless())
      continue;
    if (!h->head()->divides(*p.lcm, localRing))
      continue;
    lcm_t.makeLcm(*h->head(), *p.a->head(), localRing);
    if (lcm_t.equals(*p.lcm, localRing))
      continue;
    lcm_t.makeLcm(*h->head(), *p.b->head(), localRing);
    if (lcm_t.equals(*p.lcm, localRing))
      continue;
    p.makeUseless();
    bkUpdate += 1;
  }

  // Successive application of various deletion criteria
  Term& lcm_hi = localRing.makeTerm();
  Term& lcm_hj = localRing.makeTerm();
  for (PolySet::const_iterator ii = basis.begin(), ei = basis.end(); ii != ei;
       ++ii) {

    // Buchberger's Product criterion
    if (h->head()->relPrime(*(*ii)->head(), localRing)) {
      bpUpdate += 1;
      continue;
    }

    // Gebauer-Moeller criteria M and F
    lcm_hi.makeLcm(*h->head(), *(*ii)->head(), localRing);
    bool condM = false;
    for (PolySet::const_iterator jj = ii + 1, ej = basis.end(); jj != ej;
         ++jj) {
      lcm_hj.makeLcm(*h->head(), *(*jj)->head(), localRing);
      if (lcm_hj.divides(lcm_hi, localRing)) {
        condM = true;
        break;
      }
    }

    if (!condM) {
      Term& lcm = ring.makeTerm(lcm_hi);
      out.push(&pairs.push(PolyPair(h, *ii, &lcm, ring.rankPair(*h, **ii))));
    } else {
      mfUpdate += 1;
    }
  }

  basis.push(h);
}

galois::Statistic zeroUpdate("ZeroUpdate");

template <class R>
struct Process {
  typedef typename R::template realloc<
      galois::PerIterAllocTy::rebind<char>::other>::other LocalRing;

  PolySet& basis;
  galois::InsertBag<PolyPair>& pairs;
  R& ring;

  Process(PolySet& _basis, galois::InsertBag<PolyPair>& _pairs, R& r)
      : basis(_basis), pairs(_pairs), ring(r) {}

  void operator()(const PolyPair* p, galois::UserContext<PolyPair*>& ctx) {
    if (p->useless()) {
      return;
    }

    LocalRing localRing(ring.numVars(), ctx.getPerIterAlloc());

    Poly* s = spoly(*p->lcm, *p->a, *p->b, localRing);
    Poly* h = reduce(*s, basis, localRing);

    if (!h->empty()) {
      Poly* hh = &ring.makePoly(*h);
      update(hh, basis, pairs, localRing, ring, ctx);
    } else {
      zeroUpdate += 1;
    }
  }
};

template <class R>
struct Verifier {
  PolySet& g;
  R& ring;

  Verifier(PolySet& _g, R& r) : g(_g), ring(r) {}

  bool operator()(const PolyPair& p) {
    // TODO opportunity for temporary
    Poly* s = spoly(*p.lcm, *p.a, *p.b, ring);
    Poly* h = reduce(*s, g, ring);
    return !h->empty();
  }
};

template <class C, class R>
void allPairs(const PolySet& ideal, C& c, R& ring) {
  for (PolySet::const_iterator ii = ideal.begin(), ei = ideal.end(); ii != ei;
       ++ii) {
    for (PolySet::const_iterator jj = ideal.begin(), ej = ideal.end(); jj != ej;
         ++jj) {
      if (*ii == *jj)
        continue;
      if ((*ii)->empty() || (*jj)->empty())
        continue;
      Term& lcm = ring.makeTerm();
      lcm.makeLcm(*(*ii)->head(), *(*jj)->head(), ring);
      c.push(PolyPair(*ii, *jj, &lcm, ring.rankPair(**ii, **jj)));
    }
  }
}

struct Indexer {
  int operator()(const PolyPair& p) const { return p.index; }
  int operator()(const PolyPair* p) const { return p->index; }
};

template <class R>
void interReduce(PolySet& polys, R& ring) {
  for (PolySet::iterator ii = polys.begin(), ei = polys.end(); ii != ei; ++ii) {
    Poly*& p      = *ii;
    Poly* reduced = reduce(*p, polys, ring);
    // TODO opportunity for temporary
    boost::swap(p, reduced);
    if (p->empty())
      continue;
    Field::element_type s(p->head()->coef());
    TheField.inverse(s);
    p->scaleBy(s, ring);
  }
}

template <class R>
void buchberger(PolySet& ideal, PolySet& basis, R& ring) {
  galois::InsertBag<PolyPair> pairs;
  galois::InsertBag<PolyPair*> initial;

  for (PolySet::iterator ii = ideal.begin(), ei = ideal.end(); ii != ei; ++ii) {
    if ((*ii)->empty())
      continue;
    update(*ii, basis, pairs, ring, ring, initial);
  }
  using namespace galois::worklists;
  typedef OrderedByIntegerMetric<Indexer, PerSocketChunkLIFO<8>> OBIM;
  galois::for_each(initial.begin(), initial.end(),
                   Process<R>(basis, pairs, ring), galois::wl<OBIM>());

  interReduce(basis, ring);
}

namespace parser {
namespace qi     = boost::spirit::qi;
namespace ascii  = boost::spirit::ascii;
namespace fusion = boost::fusion;

struct Coef {
  typedef boost::variant<char, char> Sign;
  Sign sign;
  typedef boost::variant<fusion::vector<int, int>, int> Rational;
  galois::optional<Rational> rational;
};

struct Mono {
  std::string id;
  galois::optional<int> expo;
};

struct Term {
  Coef coef;
  std::vector<Mono> monos;
};

struct Poly {
  std::vector<Term> terms;
};
} // namespace parser

// Macros need to be used outside of namespace scope

BOOST_FUSION_ADAPT_STRUCT(parser::Coef,
                          (parser::Coef::Sign,
                           sign)(galois::optional<parser::Coef::Rational>,
                                 rational))

BOOST_FUSION_ADAPT_STRUCT(parser::Mono,
                          (std::string, id)(galois::optional<int>, expo))

BOOST_FUSION_ADAPT_STRUCT(parser::Term,
                          (parser::Coef, coef)(std::vector<parser::Mono>,
                                               monos))

BOOST_FUSION_ADAPT_STRUCT(parser::Poly, (std::vector<parser::Term>, terms))

namespace parser {
namespace qi     = boost::spirit::qi;
namespace ascii  = boost::spirit::ascii;
namespace fusion = boost::fusion;

// +2xy^3 - z^3, ...
template <typename It>
struct PolySetGrammar
    : qi::grammar<It, std::vector<parser::Poly>(), ascii::space_type> {
  qi::rule<It, std::string(), ascii::space_type> id;
  qi::rule<It, parser::Coef::Sign(), ascii::space_type> sign;
  qi::rule<It, parser::Coef::Rational(), ascii::space_type> rational;
  qi::rule<It, parser::Coef(), ascii::space_type> coef;
  qi::rule<It, int(), ascii::space_type> expo;
  qi::rule<It, parser::Mono(), ascii::space_type> mono;
  qi::rule<It, parser::Term(), ascii::space_type> term;
  qi::rule<It, std::vector<parser::Term>(), ascii::space_type> poly;
  qi::rule<It, std::vector<parser::Poly>(), ascii::space_type> start;

  PolySetGrammar() : PolySetGrammar::base_type(start, "polynomial list") {
    // sign := + | -
    sign %= qi::char_("+") | qi::char_("-");
    sign.name("sign");
    // rational := (int / int) | int
    rational %= (qi::int_ >> qi::lit("/") > qi::int_) | qi::int_;
    rational.name("rational");
    // coef := sign rational?
    coef %= sign >> -rational;
    coef.name("coefficent");
    // expo := ^ int
    expo %= qi::lit("^") > qi::int_;
    expo.name("exponent");
    // mono := id expo?
    id %= qi::lexeme[+ascii::alnum];
    id.name("identifier");
    mono %= id >> -expo;
    mono.name("mononomial");
    // term := coef "*"? (mono "*"?)*
    term %= coef > -qi::lit("*") >> *(mono >> -qi::lit("*"));
    term.name("term");
    // poly := term+
    poly %= +term;
    poly.name("polynomial");
    // start := poly (, poly)*
    start %= poly % ',';

    using boost::phoenix::construct;
    using boost::phoenix::val;
    qi::on_error<qi::fail>(start, std::cerr
                                      << val("Error! Expecting ")
                                      << qi::labels::_4 << val(" here: \"")
                                      << construct<std::string>(qi::labels::_3,
                                                                qi::labels::_2)
                                      << val("\"\n"));
  }
};

// Q[x y z w ...]
template <class It>
struct HeaderGrammar
    : qi::grammar<It, std::vector<std::string>(), ascii::space_type> {
  qi::rule<It, std::string(), ascii::space_type> id;
  qi::rule<It, std::vector<std::string>(), ascii::space_type> start;

  HeaderGrammar() : HeaderGrammar::base_type(start, "Ring Definition") {
    id %= qi::lexeme[+ascii::alnum];
    id.name("id");
    // Q[ <id> (, <id>)* ]
    start %= qi::lit("Q[") >> id % ',' >> qi::lit("]");

    using boost::phoenix::construct;
    using boost::phoenix::val;
    qi::on_error<qi::fail>(start, std::cerr
                                      << val("Error! Expecting ")
                                      << qi::labels::_4 << val(" here: \"")
                                      << construct<std::string>(qi::labels::_3,
                                                                qi::labels::_2)
                                      << val("\"\n"));
  }
};

//! A simple parser.
template <class Order>
class Parser : private boost::noncopyable {
  typedef ::Term TheTerm;
  typedef ::Poly ThePoly;
  typedef Ring<Order> TheRing;
  typedef std::map<std::string, int> NameMap;
  typedef std::vector<std::string> IdMap;
  NameMap nameMap;
  IdMap idMap;
  PolySet m_polys;
  Order order;
  TheRing* m_ring;

  template <class It>
  void readHeader(It begin, It end) {
    HeaderGrammar<It> grammar;
    bool r = qi::phrase_parse(begin, end, grammar, ascii::space, idMap);

    if (!r || begin != end) {
      std::cerr << "Parse failure.\n";
      abort();
    }

    // Parse header
    for (IdMap::iterator ii = idMap.begin(), ei = idMap.end(); ii != ei; ++ii) {
      if (nameMap.find(*ii) != nameMap.end()) {
        std::cerr << "Duplicate variable name: " << *ii << "\n";
        abort();
      }
      int index    = nameMap.size();
      nameMap[*ii] = index;
    }
  }

  template <class It>
  void readPolys(It begin, It end, std::vector<parser::Poly>& polys) {
    PolySetGrammar<It> grammar;
    bool r = qi::phrase_parse(begin, end, grammar, ascii::space, polys);
    if (!r || begin != end) {
      std::cerr << "Parse failure at: " << std::string(begin, end) << "\n";
      abort();
    }
  }

  struct RationalVisitor : public boost::static_visitor<Field::element_type> {
    Field::element_type operator()(const fusion::vector<int, int>& x) const {
      Field::element_type a(fusion::at_c<0>(x));
      Field::element_type b(fusion::at_c<1>(x));
      TheField.divide(a, b);
      return a;
    }
    Field::element_type operator()(const int& x) const {
      return Field::element_type(x);
    }
  };

  void parseCoef(const Coef& coef, Field::element_type& c) {
    const char* sign = boost::get<char>(&coef.sign);
    bool neg         = *sign == '-';

    TheField.assign(c, 1);
    if (coef.rational) {
      c = boost::apply_visitor(RationalVisitor(), *coef.rational);
    }
    // c.canonicalize();
    if (neg)
      TheField.negate(c);
  }

  void parseMono(const Mono& mono, TheTerm& t) {
    NameMap::iterator ii = nameMap.find(mono.id);
    if (ii == nameMap.end()) {
      std::cerr << "Unknown variable name: " << mono.id << "\n";
      abort();
    }
    int index = ii->second;
    int expo  = mono.expo ? *mono.expo : 1;
    t.exp(index) += expo;
  }

  void parseTerm(const Term& term, std::vector<TheTerm*>& terms) {
    TheTerm& t = m_ring->makeTerm();
    std::for_each(term.monos.begin(), term.monos.end(),
                  boost::bind(&Parser::parseMono, this, _1, boost::ref(t)));
    parseCoef(term.coef, t.coef());
    m_ring->generateTotalDegree(t);
    terms.push_back(&t);
  }

  struct GreaterThan {
    Ring<Order>& ring;
    GreaterThan(Ring<Order>& r) : ring(r) {}
    bool operator()(const TheTerm* a, const TheTerm* b) const {
      return ring.gt(*a, *b);
    }
  };

  void parsePoly(const Poly& poly) {
    std::vector<TheTerm*> terms;
    std::for_each(poly.terms.begin(), poly.terms.end(),
                  boost::bind(&Parser::parseTerm, this, _1, boost::ref(terms)));

    std::sort(terms.begin(), terms.end(), GreaterThan(*m_ring));
    ThePoly& p = m_ring->makePoly();
    for (std::vector<TheTerm*>::iterator ii = terms.begin(), ei = terms.end();
         ii != ei; ++ii) {
      p.push(*ii);
    }
    m_polys.push(&p);
  }

  void writeHeader(std::ostream& out) const {
    out << "Q[";
    for (IdMap::const_iterator ii = idMap.begin(), ei = idMap.end(); ii != ei;
         ++ii) {
      out << *ii;
      if (ii + 1 != ei)
        out << ", ";
    }
    out << "]\n";
  }

  void writePolys(std::ostream& out, const PolySet& polys) const {
    m_ring->write(out, polys, &idMap);
    out << "\n";
  }

public:
  typedef TheRing RingTy;

  Parser() : m_ring(0) {}

  ~Parser() {
    if (m_ring)
      delete m_ring;
  }

  void read(std::istream& in) {
    std::string header;
    getline(in, header);
    readHeader(header.begin(), header.end());

    m_ring = new Ring<Order>(idMap.size());

    in.unsetf(std::ios::skipws);
    boost::spirit::istream_iterator begin(in), end;

    std::vector<Poly> polys;
    readPolys(begin, end, polys);
    std::for_each(polys.begin(), polys.end(),
                  boost::bind(&Parser::parsePoly, this, _1));

    std::cout << "Rational ring of " << nameMap.size() << " ("
              << m_ring->numVars() << ") variables\n";
    std::cout << "Ideal of " << polys.size() << " polynomials\n";
  }

  void write(std::ostream& out, const PolySet& polys) {
    writeHeader(out);
    writePolys(out, polys);
  }

  RingTy& ring() { return *m_ring; }

  PolySet& polys() { return m_polys; }
};

} // namespace parser

template <class Order>
void run() {
  std::ifstream scanner(filename.c_str());
  if (!scanner.good()) {
    std::cerr << "Couldn't open file: " << filename << "\n";
    abort();
  }

  typedef parser::Parser<Order> ParserTy;
  ParserTy P;
  P.read(scanner);
  scanner.close();

  P.write(std::cout, P.polys()); // REMOVe

  TheField.init();

  galois::StatTimer T;
  T.start();
  interReduce(P.polys(), P.ring());
  PolySet basis;
  buchberger(P.polys(), basis, P.ring());
  T.stop();

  if (!skipVerify) {
    galois::InsertBag<PolyPair> pairs;
    allPairs(basis, pairs, P.ring());
    Verifier<typename ParserTy::RingTy> v(basis, P.ring());
    if (galois::ParallelSTL::find_if(pairs.begin(), pairs.end(), v) !=
        pairs.end()) {
      std::cerr << "Basis is not Groebner.\n";
      assert(0 && "Triangulation failed");
      abort();
    }
  }
  P.write(std::cout, basis);
  std::cout << "Groebner basis with "
            << std::distance(basis.begin(), basis.end()) << " polynomials\n";
}

int main(int argc, char** argv) {
  galois::StatManager statManager;
  statManager.push(zeroUpdate);
  statManager.push(bkUpdate);
  statManager.push(mfUpdate);
  statManager.push(bpUpdate);

  LonestarStart(argc, argv, name, desc, url);

  for (unsigned i = 0; i < sizeof(powersOfTwo) / sizeof(*powersOfTwo); ++i)
    powersOfTwo[i] = 2 << (i - 1);

  switch (monomialOrder) {
  case lex:
    run<LexOrder>();
    break;
  case grevlex:
    run<GrevlexOrder>();
    break;
  default:
    abort();
  }

  return 0;
}


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

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

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

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

#ifndef BOOST_GEOMETRY_ARITHMETIC_DOT_PRODUCT_HPP
#define BOOST_GEOMETRY_ARITHMETIC_DOT_PRODUCT_HPP

#include <cstddef>

#include <boost/concept/requires.hpp>

#include <boost/geometry/geometries/concepts/point_concept.hpp>
#include <boost/geometry/util/select_coordinate_type.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail
{

template <typename P1, typename P2, std::size_t Dimension, std::size_t DimensionCount>
struct dot_product_maker
{
    typedef typename select_coordinate_type<P1, P2>::type coordinate_type;

    static inline coordinate_type apply(P1 const& p1, P2 const& p2)
    {
        return get<Dimension>(p1) * get<Dimension>(p2)
            + dot_product_maker<P1, P2, Dimension+1, DimensionCount>::apply(p1, p2);
    }
};

template <typename P1, typename P2, std::size_t DimensionCount>
struct dot_product_maker<P1, P2, DimensionCount, DimensionCount>
{
    typedef typename select_coordinate_type<P1, P2>::type coordinate_type;

    static inline coordinate_type apply(P1 const& p1, P2 const& p2)
    {
        return get<DimensionCount>(p1) * get<DimensionCount>(p2);
    }
};

} // namespace detail
#endif // DOXYGEN_NO_DETAIL

/*!
    \brief Computes the dot product (or scalar product) of 2 vectors (points).
    \ingroup arithmetic
    \param p1 first point
    \param p2 second point
    \return the dot product
 */
template <typename P1, typename P2>
inline typename select_coordinate_type<P1, P2>::type dot_product(
        P1 const& p1, P2 const& p2)
{
    BOOST_CONCEPT_ASSERT( (concept::ConstPoint<P1>) );
    BOOST_CONCEPT_ASSERT( (concept::ConstPoint<P2>) );

    return detail::dot_product_maker
        <
            P1, P2,
            0, dimension<P1>::type::value - 1
        >::apply(p1, p2);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ARITHMETIC_DOT_PRODUCT_HPP


#include <iostream>
#include <random>
#include <cmath>
#include <armadillo>
#include <sstream>
#include "logistic_regression.h"
#include "file.h"
//#include "barrier.h"
using namespace std;
using namespace arma;
using namespace multiverso;

//initialize the logistic regression parameter settings
logistic_regression::logistic_regression(){}
logistic_regression::logistic_regression(int data_size,
                            double learning_rate,
                            double regularized,
                            int max_num_iteration,
                            int epoch_size,
                            int dimention,
                            double gamma/*momentum*/)
{
    DATA_SIZE = data_size;
	LEARNING_RATE = learning_rate;
	REGULARIZED = regularized;
	MAX_NUM_ITERATION = max_num_iteration;
	EPOCH_SIZE = epoch_size;
    DIMENTION = dimention;
    parameter = zeros<vec>(DIMENTION);
    full_gradient = zeros<vec>(DIMENTION);
    local_parameter = zeros<vec>(DIMENTION);
    momentum = zeros<vec>(DIMENTION);
    GAMMA = gamma;
}
logistic_regression::logistic_regression(Option* option_)
{
    DATA_SIZE = option_->data_size;
	LEARNING_RATE = option_->learning_rate;
	REGULARIZED = option_->regularized;
	MAX_NUM_ITERATION = option_->max_num_iteration;
	EPOCH_SIZE = option_->epoch_size;
    DIMENTION = option_->dimention;
    sample_epoch = zeros<vec>(EPOCH_SIZE*MAX_NUM_ITERATION);
    parameter = zeros<vec>(DIMENTION);
    full_gradient = zeros<vec>(DIMENTION);
    local_parameter = zeros<vec>(DIMENTION);
    momentum = zeros<vec>(DIMENTION);
    GAMMA = option_->gamma;
    this->option_ = option_;

}

//record the begin time of the training process
void logistic_regression::begin(time_t& begin)
{
    time(&begin);
}
//record the end time of the training process
void logistic_regression::end(time_t& end)
{
    time(&end);
}
//training the parameters. The fl2-regularization is added
void logistic_regression::train(int trainer_id, multiverso::Barrier *barrier)
{
    //produce the samples by a certain probability distribution
    default_random_engine random(time(NULL));
    if(trainer_id==0)
    {
        //set the epoch size. Defaultly, it is set by the variable: EPOCH_SIZE
        setEpochSize(EPOCH_SIZE);
        sample_epoch = produceSamples(random);
    }
    barrier->Wait();
    for(int i=0;i<MAX_NUM_ITERATION;i++)
    {
multiverso::Log::Info(">>>>>>>>>>>>>>>>learning thread %dth!\n",trainer_id);
        //compute the full gradient in parallel way
        vec full_gradient_thread = computeFullGradient(parameter,trainer_id, option_->thread_cnt);
        //sum all the full gradient in serial way
        mutex_.lock();
        full_gradient = full_gradient+full_gradient_thread;
        mutex_.unlock();
        local_parameter = parameter;
        barrier->Wait();
        for(int j=trainer_id;j<EPOCH_SIZE;j+=option_->thread_cnt)
        {
            //compute the reduced variance
            vec vr = computeReducedVariance(parameter, local_parameter, full_gradient, sample_epoch(i*EPOCH_SIZE+j));
            //update the parameters
            //add the write lock
            mutex_.lock();
            updateParameters(local_parameter, vr, LEARNING_RATE);
            mutex_.unlock();
        }
        barrier->Wait();
        if(trainer_id == 0)
        {
           //Identify the parameters for the next iteration
           identifyParameters(local_parameter, parameter);
           //evaluate the loss
           if(i%1==0){
               computeLoss(parameter, training_x, training_y);
	       //parameter.load("parameter.txt");
 	       }
        }//end if
    }//end for
}

//produce the samples by a certain probability distribution
vec logistic_regression::produceSamples(std::default_random_engine random)
{
    int n_instances = EPOCH_SIZE*MAX_NUM_ITERATION;
    vec samples = zeros<vec>(n_instances);
    uniform_int_distribution<int> dis1(0, DATA_SIZE-1);
    for (int i=0;i<n_instances;i++)
    {
        samples(i) = dis1(random);
    }
    return samples;
}

//set the epoch size. Defaultly, it is set by the variable: EPOCH_SIZE
void logistic_regression::setEpochSize(int size)
{
    EPOCH_SIZE = size;
}

//compute the stochastic local gradient
vec logistic_regression::computeStochasticGradient(vec& parameters, int index)
{
    double w_x = as_scalar(parameters.t()*training_x.col(index));
    double y = training_y[index];
    double temp0 = (1/(1+std::exp(y*w_x)))*(-1*y);
    vec gradient(temp0*training_x.col(index)+2*REGULARIZED*parameters);
    return gradient;
}

//compute the full gradient via multiple threads
vec logistic_regression::computeFullGradient(vec& global_parameter,int trainer_id, int thread_cnt)
{
    vec full_gradient_local = zeros<vec>(DIMENTION);
    for(int i=trainer_id;i<DATA_SIZE;i+=thread_cnt)//consider the constant
    {
        vec temp0 = computeStochasticGradient(global_parameter, i);
        full_gradient_local = full_gradient_local + temp0;
    }
    full_gradient_local = full_gradient_local/DATA_SIZE;

    return full_gradient_local;
}

//compute the reduced variance
vec logistic_regression::computeReducedVariance(vec& global_parameter, vec& local_parameter, vec& full_gradient, int index)
{
    vec vr = zeros<vec>(DIMENTION);
    vec temp0 = computeStochasticGradient(local_parameter, index);
    vec temp1 = computeStochasticGradient(global_parameter, index);
    vr=temp0-temp1+full_gradient;
    return vr;
}

//update the parameters
void logistic_regression::updateParameters(vec& local_parameter, vec& vr, double learning_rate)
{
    vec temp = learning_rate*vr + GAMMA*momentum/*momentum*/;
    local_parameter = local_parameter - temp;
    momentum = temp;
}

//Identify the parameters for the next iteration
void logistic_regression::identifyParameters(vec& local_parameter, vec& global_parameter)
{
    global_parameter = local_parameter;
}

//compute the loss function
double logistic_regression::computeLoss(vec& parameter, sp_mat& x, vec& y)
{
    double loss=0;
    for(int i=0;i<DATA_SIZE;i++)
    {
        double temp = as_scalar(parameter.t()*x.col(i));
        loss = loss - log(1/(1+exp(-1*temp*y(i))));
    }
    loss = loss/DATA_SIZE+REGULARIZED*(as_scalar(parameter.t()*parameter));
    ostringstream s_loss;
    s_loss<<loss<<"\n";
    string loss_str = s_loss.str();
    file f("lr_loss.txt");
    f.write(loss_str);
    //multiverso::Log::Info("The loss now is: %f\n",loss);
    return loss;
}

//get ready to parepare the data from the multiverso framework
void logistic_regression::init(DataBlock* data_block)
{
    sp_mat temp_trn_x;
    sp_mat temp_tst_x;
    vec temp_trn_y;
    vec temp_tst_y;

    data_block->GetSamples(temp_trn_x,temp_tst_x,temp_trn_y,temp_tst_y);
    training_x = temp_trn_x.t();
    training_y = temp_trn_y;
}

//the entrance from the multiverso to the training thread, and the training process is finally started.
void logistic_regression::train_test(int trainer_id, multiverso::Barrier *barrier)
{
    int fast, slow, self;
    double wait_time=0;
    multiverso::Multiverso::GetClock(&fast, &slow, &self, &wait_time);
    //log the wait time

    //begin training
    multiverso::Log::Info("Process: %d and thread: %d begins!\n", multiverso::Multiverso::ProcessRank(),trainer_id);
    time_t begin_time=0;
    time_t end_time=0;
    begin(begin_time);
    train(trainer_id,barrier);
    end(end_time);
}

//set the parameters when pulling the parameters from multiverso
void logistic_regression::setParameters(std::vector<double*> &blocks)
{
    //NOTICE: since logistic regression conducts the binary classification, the number of class is 2. (Here, class_num=1 means the number of class is 2.)
    for(int i=0;i<option_->class_num;i++)
    {
        for (int j=0;j < option_->dimention;j++)
        {
            parameter[j] = blocks[i][j];//since class_num=1, parameter will be valued once.
        }
    }
}

//get the parameters
void logistic_regression::getParameters(std::vector<double*> &blocks)
{
    for(int i=0;i < option_->class_num; i++)
    {
        for (int j=0;j < option_->dimention;j++)
        {
            blocks[i][j] = parameter[j];
        }
    }
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_CORRECT_FMA_HPP_INCLUDED
#define BOOST_SIMD_ARITHMETIC_FUNCTIONS_SCALAR_CORRECT_FMA_HPP_INCLUDED

#include <boost/simd/arithmetic/functions/correct_fma.hpp>
#include <boost/simd/include/functions/scalar/two_add.hpp>
#include <boost/simd/include/functions/scalar/two_prod.hpp>
#include <boost/simd/include/functions/scalar/ldexp.hpp>
#include <boost/simd/include/functions/scalar/max.hpp>
#include <boost/simd/include/functions/scalar/multiplies.hpp>
#include <boost/simd/include/functions/scalar/exponent.hpp>
#include <boost/simd/include/functions/scalar/sign.hpp>
#include <boost/simd/include/functions/scalar/bitwise_cast.hpp>
#include <boost/dispatch/meta/as_integer.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{

  BOOST_DISPATCH_IMPLEMENT         ( correct_fma_, tag::cpu_
                                   , (A0)
                                   , (scalar_< single_<A0> >)
                                     (scalar_< single_<A0> >)
                                     (scalar_< single_<A0> >)
                                   )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(3)
    {
      return static_cast<A0>( static_cast<double>(a0)*static_cast<double>(a1)
                            + static_cast<double>(a2)
                            );
    }
  };

  BOOST_DISPATCH_IMPLEMENT         ( correct_fma_, tag::cpu_
                                   , (A0)
                                   , (scalar_< floating_<A0> >)
                                     (scalar_< floating_<A0> >)
                                     (scalar_< floating_<A0> >)
                                   )
  {
    typedef A0 result_type;

    BOOST_SIMD_FUNCTOR_CALL_REPEAT(3)
    {
      result_type p, rp, s, rs;
#ifndef BOOST_SIMD_DONT_CARE_FMA_OVERFLOW
      typedef typename boost::dispatch::meta::as_integer<A0>::type iA0;
      iA0 e0 = exponent(a0);
      iA0 e1 = exponent(a1);
      iA0 e = -boost::simd::max(e0, e1)/2;
      result_type ae2  = ldexp(a2, e);
      bool choose = (e0 > e1);
      result_type amax = choose ? ldexp(a0, e) : ldexp(a1, e);
      result_type amin = choose ? a1 : a0;
      two_prod(amax, amin, p, rp);
      two_add(p, ae2, s, rs);
      return ldexp(s+(rp+rs), -e);
#else
      two_prod(a0, a1, p, rp);
      two_add(p, a2, s, rs);
      return s+(rp+rs);
#endif
    }
  };

  BOOST_DISPATCH_IMPLEMENT         ( correct_fma_, tag::cpu_
                                   , (A0)
                                   , (scalar_< int_<A0> >)
                                     (scalar_< int_<A0> >)
                                     (scalar_< int_<A0> >)
                                   )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(3)
    {
      // correct fma has to ensure "no intermediate overflow".
      // This is done in the case of signed integers by transtyping to unsigned type
      // to perform the computations in a guaranteed 2-complement environment
      // since signed integer oveflow in C++ produces "undefined results"
      typedef typename dispatch::meta::as_integer<A0, unsigned>::type utype;
      return A0(correct_fma(utype(a0), utype(a1), utype(a2)));
    }
  };

  BOOST_DISPATCH_IMPLEMENT         ( correct_fma_, tag::cpu_
                                   , (A0)
                                   , (scalar_< uint_<A0> >)
                                     (scalar_< uint_<A0> >)
                                     (scalar_< uint_<A0> >)
                                   )
  {
    typedef A0 result_type;

    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(3)
    {
      return multiplies(a0, a1)+a2;
    }
  };
} } }

#endif


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2016 Alec Jacobson <alecjacobson@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include <igl/polyvector_field_poisson_reconstruction.h>
#include <igl/grad.h>
#include <igl/doublearea.h>
#include <igl/sparse.h>
#include <igl/repdiag.h>
#include <igl/slice.h>
#include <igl/slice_into.h>
#include <igl/colon.h>

#include <Eigen/Sparse>

template <typename DerivedV, typename DerivedF, typename DerivedSF, typename DerivedS>
IGL_INLINE void igl::polyvector_field_poisson_reconstruction(
  const Eigen::PlainObjectBase<DerivedV> &Vcut,
  const Eigen::PlainObjectBase<DerivedF> &Fcut,
  const Eigen::PlainObjectBase<DerivedS> &sol3D_combed,
                                                               Eigen::PlainObjectBase<DerivedSF> &scalars)
  {
    Eigen::SparseMatrix<typename DerivedV::Scalar> gradMatrix;
    igl::grad(Vcut, Fcut, gradMatrix);

    Eigen::VectorXd FAreas;
    igl::doublearea(Vcut, Fcut, FAreas);
    FAreas = FAreas.array() * .5;

    int nf = FAreas.rows();
    Eigen::SparseMatrix<typename DerivedV::Scalar> M,M1;
    Eigen::VectorXi II = igl::colon<int>(0, nf-1);

    igl::sparse(II, II, FAreas, M1);
    igl::repdiag(M1, 3, M) ;

    int half_degree = sol3D_combed.cols()/3;

    int numF = Fcut.rows();
    scalars.setZero(Vcut.rows(),half_degree);

    Eigen::SparseMatrix<typename DerivedV::Scalar> Q = gradMatrix.transpose()* M *gradMatrix;

    //fix one point at Ik=fix, value at fixed xk=0
    int fix = 0;
    Eigen::VectorXi Ik(1);Ik<<fix;
    Eigen::VectorXd xk(1);xk<<0;

    //unknown indices
    Eigen::VectorXi Iu(Vcut.rows()-1,1);
    Iu<<igl::colon<int>(0, fix-1),  igl::colon<int>(fix+1,Vcut.rows()-1);

    Eigen::SparseMatrix<typename DerivedV::Scalar> Quu, Quk;
    igl::slice(Q, Iu, Iu, Quu);
    igl::slice(Q, Iu, Ik, Quk);
    Eigen::SimplicialLDLT<Eigen::SparseMatrix<typename DerivedV::Scalar> > solver;
    solver.compute(Quu);

    Eigen::VectorXd vec; vec.setZero(3*numF,1);
    for (int i =0; i<half_degree; ++i)
    {
      vec<<sol3D_combed.col(i*3+0),sol3D_combed.col(i*3+1),sol3D_combed.col(i*3+2);
      Eigen::VectorXd b = gradMatrix.transpose()* M * vec;
      Eigen::VectorXd bu = igl::slice(b, Iu);

      Eigen::VectorXd rhs = bu-Quk*xk;
      Eigen::VectorXd yu = solver.solve(rhs);

      Eigen::VectorXd y(Vcut.rows(),1);
      igl::slice_into(yu, Iu, 1, y);y(Ik[0])=xk[0];
      scalars.col(i) = y;
    }
}

template <typename DerivedV, typename DerivedF, typename DerivedSF, typename DerivedS, typename DerivedE>
IGL_INLINE double igl::polyvector_field_poisson_reconstruction(
                                                               const Eigen::PlainObjectBase<DerivedV> &Vcut,
                                                               const Eigen::PlainObjectBase<DerivedF> &Fcut,
                                                               const Eigen::PlainObjectBase<DerivedS> &sol3D_combed,
                                                               Eigen::PlainObjectBase<DerivedSF> &scalars,
                                                               Eigen::PlainObjectBase<DerivedS> &sol3D_recon,
                                                               Eigen::PlainObjectBase<DerivedE> &max_error )
{

  igl::polyvector_field_poisson_reconstruction(Vcut, Fcut, sol3D_combed, scalars);

  Eigen::SparseMatrix<typename DerivedV::Scalar> gradMatrix;
  igl::grad(Vcut, Fcut, gradMatrix);
  int numF = Fcut.rows();
  int half_degree = sol3D_combed.cols()/3;

    //    evaluate gradient of found scalar function
  sol3D_recon.setZero(sol3D_combed.rows(),sol3D_combed.cols());

    for (int i =0; i<half_degree; ++i)
    {
      Eigen::VectorXd vec_poisson = gradMatrix*scalars.col(i);
      sol3D_recon.col(i*3+0) = vec_poisson.segment(0*numF, numF);
      sol3D_recon.col(i*3+1) = vec_poisson.segment(1*numF, numF);
      sol3D_recon.col(i*3+2) = vec_poisson.segment(2*numF, numF);
    }

    max_error.setZero(numF,1);
    for (int i =0; i<half_degree; ++i)
    {
      Eigen::VectorXd diff = (sol3D_recon.block(0, i*3, numF, 3)-sol3D_combed.block(0, i*3, numF, 3)).rowwise().norm();
      diff = diff.array() / sol3D_combed.block(0, i*3, numF, 3).rowwise().norm().array();
      max_error = max_error.cwiseMax(diff.cast<typename DerivedE::Scalar>());
    }

    return max_error.mean();
  }

  #ifdef IGL_STATIC_LIBRARY
  // Explicit template instantiation
  template double igl::polyvector_field_poisson_reconstruction<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<float, -1, 1, 0, -1, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<float, -1, 1, 0, -1, 1> >&);
template double igl::polyvector_field_poisson_reconstruction<Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<int, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, -1, 0, -1, -1>, Eigen::Matrix<double, -1, 1, 0, -1, 1> >(Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<int, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> > const&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, -1, 0, -1, -1> >&, Eigen::PlainObjectBase<Eigen::Matrix<double, -1, 1, 0, -1, 1> >&);
  #endif


#include <jni.h>
#include <string>
#include <math.h>
#include <iostream>
#include <Eigen/Dense>
#include <ceres/ceres.h>

using Eigen::MatrixXd;

extern "C" JNIEXPORT jstring JNICALL
Java_com_qyh_cerestest_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";

//    Eigen::MatrixXd m = Eigen::MatrixXd::Random(3, 3);
//    m = MatrixXd::Constant(3, 3, 1.2) * 50;

    return env->NewStringUTF(hello.c_str());
}

using namespace Eigen;

Vector3f vec;
Vector3f vec2;
Vector3f vecRtrn;

void vecLoad(float x, float y, float z, float x2, float y2, float z2) {
    vec(0) = x;
    vec(1) = y;
    vec(2) = z;
    vec2(0) = x2;
    vec2(1) = y2;
    vec2(2) = z2;
}

void vecAdd(Vector3f vecA, Vector3f vecB) {
    vecRtrn = vecA + vecB;
}

extern "C"
JNIEXPORT jfloatArray JNICALL
Java_com_qyh_cerestest_MainActivity_test(JNIEnv *env, jobject instance, jfloatArray array1_,
                                         jfloatArray array2_) {
    jfloatArray result;
    result = env->NewFloatArray(3);
    if (result == NULL) {
        return NULL; /* out of memory error thrown */
    }

    jfloat array1[3];
    jfloat* flt1 = env->GetFloatArrayElements( array1_,0);
    jfloat* flt2 = env->GetFloatArrayElements( array2_,0);

    vecLoad(flt1[0], flt1[1], flt1[2], flt2[0], flt2[1], flt2[2]);
    vecAdd(vec, vec2);

    array1[0] = vecRtrn[0];
    array1[1] = vecRtrn[1];
    array1[2] = vecRtrn[2];

    env->ReleaseFloatArrayElements(array1_, flt1, 0);
    env->ReleaseFloatArrayElements(array2_, flt2, 0);
    env->SetFloatArrayRegion(result, 0, 3, array1);
    return result;

}

using namespace ceres;

struct CostFunctor {
    template <typename T>
    bool operator()(const T* const x, T* residual) const {
        residual[0] = T(10.0) - x[0];
        return true;
    }
};

extern "C"
JNIEXPORT jstring JNICALL
Java_com_qyh_cerestest_MainActivity_testCeres(JNIEnv *env, jobject instance) {
    std::string hello = "Hello Ceres";

    // 寻优参数x的初始值，为5
    double initial_x = 5.0;
    double x = initial_x;

    // 第二部分：构建寻优问题
    Problem problem;
    CostFunction* cost_function =
            new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor); //使用自动求导，将之前的代价函数结构体传入，第一个1是输出维度，即残差的维度，第二个1是输入维度，即待寻优参数x的维度。
    problem.AddResidualBlock(cost_function, NULL, &x); //向问题中添加误差项，本问题比较简单，添加一个就行。

    //第三部分： 配置并运行求解器
    Solver::Options options;
    options.linear_solver_type = ceres::DENSE_QR; //配置增量方程的解法
    options.minimizer_progress_to_stdout = true;//输出到cout
    Solver::Summary summary;//优化信息
    ceres::Solve(options, &problem, &summary);//求解!!!

    std::cout << summary.BriefReport() << "\n";//输出优化的简要信息
    //最终结果
    std::cout << "x : " << initial_x
              << " -> " << x << "\n";

    return env->NewStringUTF(summary.BriefReport().c_str());
}


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

/*
 Copyright (C) 2009, 2014 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 quantlib_recursive_loss_model_hpp
#define quantlib_recursive_loss_model_hpp

#include <ql/experimental/credit/constantlosslatentmodel.hpp>
#include <ql/experimental/credit/defaultlossmodel.hpp>

#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ > 4))
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-local-typedefs"
#endif
#include <boost/bind.hpp>
#if defined(__GNUC__) && (((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)) || (__GNUC__ > 4))
#pragma GCC diagnostic pop
#endif
#include <map>
#include <algorithm>

namespace QuantLib {

    /*! Recursive STCDO default loss model for a heterogeneous pool of names.
    The pool names are heterogeneous in their default probabilities, notionals
    and recovery rates. Correlations are given by the latent model.
    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).

        \todo Make the loss unit equal to some small fraction depending on the
        portfolio loss weights (notionals and recoveries). As it is now this
        is ok for pricing but not for risk metrics. See the discussion in O'Kane
        18.3.2
        \todo Intengrands should all use the inverted probabilities for
        performance instead of calling the copula inversion with the same vals.
    */
    template<class copulaPolicy>
    class RecursiveLossModel : public DefaultLossModel {
    public:
        RecursiveLossModel(
            const ext::shared_ptr<ConstantLossLatentmodel<copulaPolicy> >& m,
// nope! use max common divisor. See O'Kane. Or give both options at least.
            Size nbuckets  = 1)
        : copula_(m), nBuckets_(nbuckets), wk_() { }
      private:
          /*!
          @param pDefDate Vector of unconditional default probabilities for each
          live name (at the current evaluation date). This is passed instead of
          the date for performance reasons (if in the future other magnitudes
          -e.g. lgd- are contingent on the date they shouldd be passed too).
          */
        Disposable<std::map<Real, Probability> > conditionalLossDistrib(
            const std::vector<Probability>& pDefDate,
            const std::vector<Real>& mktFactor) const;
        Real expectedConditionalLoss(const std::vector<Probability>& pDefDate, //<< never used!!
            const std::vector<Real>& mktFactor) const;
        Disposable<std::vector<Real> > conditionalLossProb(
            const std::vector<Probability>& pDefDate,
            //const Date& date,
            const std::vector<Real>& mktFactor) const;
        //versions using the P-inverse, deprecate the former
        Disposable<std::map<Real, Probability> > conditionalLossDistribInvP(
            const std::vector<Real>& pDefDate,
            //const Date& date,
            const std::vector<Real>& mktFactor) const;
        Real expectedConditionalLossInvP(const std::vector<Real>& pDefDate,
            //const Date& date,
            const std::vector<Real>& mktFactor) const;
    protected:
        void resetModel();
    public:
        /*  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;
       Disposable<std::vector<Real> > lossProbability(const Date& date) const;
       // REMEBER THIS HAS TO BE MOVED TO A DISTRIBUTION OBJECT.............
       Disposable<std::map<Real, Probability> > lossDistribution(
           const Date& d) const;
       // INTEGRATE THEN SEARCH RATHER THAN SEARCH AND THEN INTEGRATE:
       // Here I am not using a search because the point might not be attainable
       //  (loss distrib is not continuous)
       Real percentile(const Date& d, Real percentile) const;
       Real expectedShortfall(const Date& d, Real perctl) const;
    protected:
        const ext::shared_ptr<ConstantLossLatentmodel<copulaPolicy> > copula_;
    private:
        // loss model descriptor members
        const Size nBuckets_;
        mutable std::vector<Real> wk_;
        mutable Real lossUnit_;
        //! name to name factor. 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.
        ////////in the latent model now: mutable std::vector<Real> oneFactorCorrels_;
        // cached remaining basket magnitudes:
        mutable Real attachAmount_,
            detachAmount_,
            notional_;
        mutable Size remainingBsktSize_;
        mutable std::vector<Real> notionals_;
    };

    typedef RecursiveLossModel<GaussianCopulaPolicy> RecursiveGaussLossModel;

    // Inlines ------------------------------------------------

    template<class CP>
    inline Real RecursiveLossModel<CP>::expectedTrancheLoss(
        const Date& date) const
    {
/*
        std::map<Real, Probability> dist = lossDistribution(date);

        Real expLoss = 0.;
        std::map<Real, Probability>::iterator distIt = dist.begin();

        while(distIt != dist.end()) {
            Real loss = distIt->first * lossUnit_;
            loss = std::max(std::min(loss, detachAmount_)-attachAmount_, 0.);
            // MIN MAX BUGS ....??
            expLoss += loss * distIt->second;
            distIt++;
        }
        return expLoss ;

    ///////////////////////////////////////////////////////////////////////

        // calculate inverted unconditional Ps first so we save the inversion:
        // TO DO : turn to STL algorithm code
        std::vector<Probability> uncDefProb =
            basket_->remainingProbabilities(date);

        return copula_->integratedExpectedValue(
            boost::function<Real (const std::vector<Real>& v1)>(
                boost::bind(
                    &RecursiveLossModel::expectedConditionalLoss,
                    this,
                    boost::cref(uncDefProb),
                    _1)
                )
            );
            */
/**/
        std::vector<Probability> uncDefProb =
            basket_->remainingProbabilities(date);
        std::vector<Real> invProb;
        for(Size i=0; i<uncDefProb.size(); ++i)
           invProb.push_back(copula_->inverseCumulativeY(uncDefProb[i], i));
           ///  invProb.push_back(CP::inverseCumulativeY(uncDefProb[i], i));//<-static call
        return copula_->integratedExpectedValue(
            boost::function<Real (const std::vector<Real>& v1)>(
                boost::bind(
                    &RecursiveLossModel::expectedConditionalLossInvP,
                    this,
                    boost::cref(invProb),
                    _1)
                )
            );

    }

    template<class CP>
    inline Disposable<std::vector<Real> >
        RecursiveLossModel<CP>::lossProbability(const Date& date) const {

        std::vector<Probability> uncDefProb =
            basket_->remainingProbabilities(date);
        return copula_->integratedExpectedValue(
            boost::function<Disposable<std::vector<Real> > (const std::vector<Real>& v1)>(
                boost::bind(
                    &RecursiveLossModel::conditionalLossProb,
                    this,
                    boost::cref(uncDefProb),
                    _1)
                )
            );
    }

    // -------------------------------------------------------------------

    template<class CP>
    void RecursiveLossModel<CP>::resetModel() {
        // basket update:
        notionals_ = basket_->remainingNotionals();
        notional_  = basket_->remainingNotional();
        attachAmount_ = basket_->remainingAttachmentAmount();
        detachAmount_ = basket_->remainingDetachmentAmount();
        // model parameters:
        remainingBsktSize_ = notionals_.size();

        copula_->resetBasket(basket_.currentLink());

        std::vector<Real> lgdsTmp, lgds;
        for(Size i=0; i<remainingBsktSize_; ++i)
            lgds.push_back(notionals_[i]*(1.-copula_->recoveries()[i]));
        lgdsTmp = lgds;
        ///////////////std::remove(lgds.begin(), lgds.end(), 0.);
        lgds.erase(std::remove(lgds.begin(), lgds.end(), 0.), lgds.end());
        lossUnit_ = *(std::min_element(lgds.begin(), lgds.end()))
            / nBuckets_;
        for(Size i=0; i<remainingBsktSize_; ++i)
            wk_.push_back(std::floor(lgdsTmp[i]/lossUnit_ + .5));
    }

    // make it return a distribution object?
    template<class CP>
    Disposable<std::map<Real, Probability> >
        RecursiveLossModel<CP>::lossDistribution(const Date& d) const
    {
        std::map<Real, Probability> distrib;
        std::vector<Real> values  = lossProbability(d);
        Real sum = 0.;
        for(Size i=0; i<values.size(); ++i) {
            distrib.insert(std::make_pair<Real, Probability>(i * lossUnit_,
                sum + values[i]));
            sum += values[i];
        }
        return distrib;
    }

    // Integrate then search rather than search and then integrate?
    // Here I am not using a search because the point might be not attainable
    //   (loss distrib is not continuous)
    template<class CP>
    Real RecursiveLossModel<CP>::percentile(const Date& d,
        Real percentile) const
    {
        std::map<Real, Probability> dist = lossDistribution(d);

        if(dist.begin()->second >=1.) return dist.begin()->first;

        // deterministic case (e.g. date requested is todays date)
        if(dist.size() == 1) return dist.begin()->first;

        if(percentile == 1.) return dist.rbegin()->second;
        if(percentile == 0.) return dist.begin()->second;
        std::map<Real, Probability>::const_iterator itdist = dist.begin();
        while (itdist->second <= percentile) ++itdist;
        Real valPlus = itdist->second;
        Real xPlus   = itdist->first;
        --itdist;  //we're never 1st or last, because of tests above
        Real valMin  = itdist->second;
        Real xMin    = itdist->first;

        // return xPlus-(xPlus-xMin)*(valPlus-percentile)/(valPlus-valMin);
        Real portfLoss =  xPlus-(xPlus-xMin)*(valPlus-percentile)
            /(valPlus-valMin);
        return //remainingNotional_ *
            std::min(std::max(portfLoss - attachAmount_, 0.),
                detachAmount_ - attachAmount_);/////(detach_ - attach_);
    }

    template<class CP>
    Real RecursiveLossModel<CP>::expectedShortfall(const Date& d,
        Real perctl) const
    {
        if(d == Settings::instance().evaluationDate()) return 0.;
        std::map<Real, Probability> distrib = lossDistribution(d);

        std::map<Real, Probability>::iterator itNxt, itDist =
            distrib.begin();
        for(; itDist != distrib.end(); ++itDist)
            if(itDist->second >= perctl) break;
        itNxt = itDist;
        --itDist; // what if we are on the first one?!!!

        // One could linearly triangulate the exact point and get extra
        // precission on the first(broken) period.
        if(itNxt != distrib.end()) {
            Real lossNxt = std::min(std::max(itNxt->first - attachAmount_,
                0.), detachAmount_ - attachAmount_);
            Real lossHere = std::min(std::max(itDist->first - attachAmount_,
                0.), detachAmount_ - attachAmount_);

            Real val =  lossNxt - (itNxt->second - perctl) *
                (lossNxt - lossHere) / (itNxt->second - itDist->second);
            Real suma = (itNxt->second - perctl) * (lossNxt + val) * .5;
            ++itDist; ++itNxt;
            do{
                lossNxt = std::min(std::max(itNxt->first - attachAmount_,
                    0.), detachAmount_ - attachAmount_);
                lossHere = std::min(std::max(itDist->first - attachAmount_,
                    0.), detachAmount_ - attachAmount_);
                suma += .5 * (lossHere + lossNxt) * (itNxt->second -
                    itDist->second);
                ++itDist; ++itNxt;
            }while(itNxt != distrib.end());
            return suma / (1.-perctl);
        }
        return 0.;// well, we are in error....  fix: FAIL
    }

    template<class CP>
    Disposable<std::map<Real, Probability> >
        RecursiveLossModel<CP>::conditionalLossDistrib(
            const std::vector<Probability>& pDefDate,
            //const Date& date,
            const std::vector<Real>& mktFactor) const
    {
        //eq. 10 p.68
        //attainable losses distribution, recursive algorithm
        const std::vector<Probability>& uncDefProb = pDefDate;// alias, remove

        std::map<Real, Probability> pIndepDistrib;
        ////////  K=0
        pIndepDistrib.insert(std::make_pair(0., 1.));
        for(Size iName=0; iName<remainingBsktSize_; ++iName) {
            Probability pDef =
                copula_->conditionalDefaultProbability(uncDefProb[iName], 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;
        }
        /* Apply tranche limits now .... mind you this could be done outside*/
        ////  to be done....
        return pIndepDistrib;
    }

    template<class CP>
    Disposable<std::map<Real, Probability> >
        // twice?! rewrite one in terms of the other, this is a duplicate!
        RecursiveLossModel<CP>::conditionalLossDistribInvP(
            const std::vector<Real>& invpDefDate,
            //const Date& date,
            const std::vector<Real>& mktFactor) const
    {
        // eq. 10 p.68
        // attainable losses distribution, recursive algorithm

        std::map<Real, Probability> pIndepDistrib;
        // K=0
        pIndepDistrib.insert(std::make_pair(0., 1.));
        for(Size iName=0; iName<remainingBsktSize_; ++iName) {
            Probability pDef =
                copula_->conditionalDefaultProbabilityInvP(invpDefDate[iName],
                    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;
        }
        /* Apply tranche limits now .... mind you this could be done outside*/
        return pIndepDistrib;
    }

    /*
    Bugs here???. The max min on the tranche looks
    wrong. It is better to have a tranche function since that way we can avoid
    adding up losses over all the posible losses rather than just over the
    tranche limits.
    */
    //! Portfolio loss conditional to the market factor value
    template<class CP>
    Real RecursiveLossModel<CP>::expectedConditionalLoss(
        const std::vector<Probability>& pDefDate,
        //const Date& date,
        const std::vector<Real>& mktFactor) const
    {
        std::map<Real, Probability> pIndepDistrib =
            conditionalLossDistrib(pDefDate, mktFactor);

        // 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 * lossUnit_;
     //       loss = std::max(std::min(loss, detachAmount_)-attachAmount_, 0.);
            loss = std::min(std::max(loss - attachAmount_, 0.),
                detachAmount_ - attachAmount_);
            // MIN MAX BUGS ....??
            expLoss += loss * distIt->second;
            ++distIt;
        }
        return expLoss ;
    }

    template<class CP>
    // again, I am duplicating code.
    Real RecursiveLossModel<CP>::expectedConditionalLossInvP(
                                 const std::vector<Real>& invPDefDate,
                                 //const Date& date,
                                 const std::vector<Real>& mktFactor) const
    {
        std::map<Real, Probability> pIndepDistrib =
            conditionalLossDistribInvP(invPDefDate, mktFactor);

        // 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 * lossUnit_;
   //         loss = std::max(std::min(loss, detachAmount_)-attachAmount_, 0.);
            loss = std::min(std::max(loss - attachAmount_, 0.),
                detachAmount_ - attachAmount_);
            // MIN MAX BUGS ....???
            expLoss += loss * distIt->second;
            ++distIt;
        }
        return expLoss ;
    }

    template<class CP>
    Disposable<std::vector<Real> > RecursiveLossModel<CP>::conditionalLossProb(
        const std::vector<Probability>& pDefDate,
        //const Date& date,
        const std::vector<Real>& mktFactor) const
    {
        std::map<Real, Probability> pIndepDistrib =
            conditionalLossDistrib(pDefDate, mktFactor);

        std::vector<Real> results;
        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;

            results.push_back(distIt->second);
             ++distIt;
        }
        return results;
    }

}

#endif


// C++ standard library headers
#include <cmath> // for std::isfinite
#include <cstdlib> // for std::exit
#include <cstring> // for std::strlen
#include <ctime> // for std::clock
#include <iostream> // for std::cout
#include <limits> // for std::numeric_limits

// Boost library headers
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/eigen.hpp>

// RKTK headers
#include "OptimizerDriver.hpp"

template <unsigned Digits>
using boost_float = boost::multiprecision::number<
    boost::multiprecision::cpp_bin_float<Digits,
        boost::multiprecision::digit_base_2>,
    boost::multiprecision::et_off>;

long long int get_positive_integer_argument(char *str) {
    char *end;
    const long long int result = std::strtoll(str, &end, 10);
    const bool read_whole_arg = (
            std::strlen(str) == static_cast<std::size_t>(end - str));
    const int is_positive = (result > 0);
    if (!read_whole_arg || !is_positive) {
        std::cerr << "ERROR: Expected command-line argument '" << str
                  << "' to be a positive integer." << std::endl;
        std::exit(EXIT_FAILURE);
    }
    return result;
}

#define RETURN_PRECISION(T)                                                    \
        return std::numeric_limits<T>::digits;                                 \
    } else if (precision == std::numeric_limits<T>::digits) {                  \
        return std::numeric_limits<T>::digits;                                 \
    }

#define HANDLE_SMALLEST_PRECISION(T)                                           \
    if (precision < std::numeric_limits<T>::digits) {                          \
        std::cout << "Rounding up to " << std::numeric_limits<T>::digits       \
                  << "-bit precision, which is the smallest "                  \
                        "available machine precision." << std::endl;           \
        RETURN_PRECISION(T)

#define HANDLE_MACHINE_PRECISION(T)                                            \
    else if (precision < std::numeric_limits<T>::digits) {                     \
        std::cout << "Rounding up to " << std::numeric_limits<T>::digits       \
                  << "-bit precision, which is the next "                      \
                        "available machine precision." << std::endl;           \
        RETURN_PRECISION(T)

#define HANDLE_EXTENDED_PRECISION(T)                                           \
    else if (precision < std::numeric_limits<T>::digits) {                     \
        std::cout << "Rounding up to " << std::numeric_limits<T>::digits       \
                  << "-bit precision, which is the next "                      \
                        "available extended precision." << std::endl;          \
        RETURN_PRECISION(T)

#define HANDLE_LARGEST_PRECISION(T)                                            \
    HANDLE_EXTENDED_PRECISION(T) else {                                        \
        std::cout << "WARNING: Requested precision exceeds "                   \
                     "available precision. Rounding down to "                  \
                  << std::numeric_limits<T>::digits                            \
                  << "-bit precision, which is the highest "                   \
                     "available extended precision." << std::endl;             \
        return std::numeric_limits<T>::digits;                                 \
    }

int get_precision(int argc, char **argv, int index) {
    if (argc > index) {
        char *end;
        const long long int precision = std::strtoll(argv[index], &end, 10);
        const bool read_whole_arg = (std::strlen(argv[index]) ==
                                     static_cast<std::size_t>(
                                             end - argv[index]));
        const bool is_positive = (precision > 0);
        if (read_whole_arg && is_positive) {
            std::cout << "Requested " << precision << "-bit precision."
                      << std::endl;
            HANDLE_SMALLEST_PRECISION(float)
            HANDLE_MACHINE_PRECISION(double)
            HANDLE_MACHINE_PRECISION(long double)
            HANDLE_EXTENDED_PRECISION(boost_float<128>)
            HANDLE_EXTENDED_PRECISION(boost_float<256>)
            HANDLE_EXTENDED_PRECISION(boost_float<384>)
            HANDLE_EXTENDED_PRECISION(boost_float<512>)
            HANDLE_EXTENDED_PRECISION(boost_float<640>)
            HANDLE_EXTENDED_PRECISION(boost_float<768>)
            HANDLE_EXTENDED_PRECISION(boost_float<896>)
            HANDLE_LARGEST_PRECISION(boost_float<1024>)
        } else {
            std::cout << "WARNING: Could not interpret command-line argument "
                      << argv[index] << " as a positive integer."
                                        " Defaulting to double ("
                      << std::numeric_limits<double>::digits
                      << "-bit) precision." << std::endl;
            return std::numeric_limits<double>::digits;
        }
    } else {
        std::cout << "Defaulting to double ("
                  << std::numeric_limits<double>::digits
                  << "-bit) precision." << std::endl;
        return std::numeric_limits<double>::digits;
    }
}

double get_print_period(int argc, char **argv, int index) {
    if (argc > index) {
        char *end;
        const double print_period = std::strtod(argv[index], &end);
        const bool read_whole_arg = (
                std::strlen(argv[index]) ==
                static_cast<std::size_t>(end - argv[index]));
        const bool is_finite = std::isfinite(print_period);
        const bool is_positive = (print_period >= 0.0);
        if (read_whole_arg && is_finite && is_positive) {
            return print_period;
        }
    }
    return 0.5;
}

int get_print_precision(int argc, char **argv, int index) {
    if (argc > index) {
        char *end;
        const long long print_precision = std::strtoll(argv[index], &end, 10);
        const bool read_whole_arg = (
                std::strlen(argv[index]) ==
                static_cast<std::size_t>(end - argv[index]));
        const bool is_non_negative = (print_precision >= 0);
        const bool in_range = (print_precision <=
                               std::numeric_limits<int>::max());
        if (read_whole_arg && is_non_negative && in_range) {
            return static_cast<int>(print_precision);
        }
    }
    return 0;
}

enum class SearchMode { EXPLORE, REFINE };

template <typename T>
void run_main_loop(int order, std::size_t num_steps,
                   char *filename, SearchMode mode, int print_prec,
                   std::clock_t clocks_between_prints) {
    rktk::OptimizerDriver<T> optimizer(order, num_steps);
    if (mode == SearchMode::REFINE) {
        optimizer.initialize_from_file(std::string(filename));
    }
    std::clock_t last_print_clock;
    reset_d:
    if (mode == SearchMode::EXPLORE) { optimizer.initialize_random(); }
    optimizer.print(print_prec);
    optimizer.write_to_file();
    last_print_clock = std::clock();
    optimizer.set_step_size(65536 * std::numeric_limits<T>::epsilon());
    while (true) {
        if (!optimizer.step()) {
            optimizer.print(print_prec);
            std::cout << "Located candidate local minimum." << std::endl;
            optimizer.write_to_file();
            if (mode == SearchMode::EXPLORE) { goto reset_d; }
            std::exit(EXIT_SUCCESS);
        }
        if (optimizer.get_iteration_count() % 1000 == 0) {
            optimizer.write_to_file();
        }
        if (mode == SearchMode::EXPLORE &&
            optimizer.get_iteration_count() >= 100000000) {
            std::cout << "NOTICE: Exceeded maximum number "
                         "of BFGS iterations. Restarting from "
                         "new random point." << std::endl;
            goto reset_d;
        }
        const std::clock_t current_clock = std::clock();
        if (current_clock - last_print_clock >= clocks_between_prints) {
            optimizer.print(print_prec);
            last_print_clock = current_clock;
        }
    }
}

#define RUN_MAIN_LOOP(T)                                                       \
    if (prec == std::numeric_limits<T>::digits) {                              \
        run_main_loop<T>(order, num_stages, argv[6], mode,                     \
                         print_prec, clocks_between_prints);                   \
    }

int main(int argc, char **argv) {
    if (argc < 3) {
        std::cerr << "Usage: " << argv[0] << " order num-stages num-bits "
                  << "print-period print-precision [input-filename]"
                  << std::endl;
        return EXIT_FAILURE;
    }
    const unsigned long long int order = static_cast<unsigned long long int>(
            get_positive_integer_argument(argv[1]));
    const std::size_t num_stages = static_cast<std::size_t>(
            get_positive_integer_argument(argv[2]));
    if (order > 15) {
        std::cerr << "ERROR: Runge-Kutta methods of order greater than 15 "
                  << "are not yet supported." << std::endl;
        std::exit(EXIT_FAILURE);
    }
    if (order > num_stages) {
        std::cerr << "ERROR: The order of a Runge-Kutta method cannot exceed "
                  << "its number of stages." << std::endl;
        std::exit(EXIT_FAILURE);
    }
    const int prec = get_precision(argc, argv, 3);
    const auto clocks_between_prints = static_cast<std::clock_t>(
            get_print_period(argc, argv, 4) * CLOCKS_PER_SEC);
    const int print_prec = get_print_precision(argc, argv, 5);
    const SearchMode mode = (argc > 6)
                            ? SearchMode::REFINE
                            : SearchMode::EXPLORE;
    RUN_MAIN_LOOP(float)
    else RUN_MAIN_LOOP(double)
    else RUN_MAIN_LOOP(long double)
    else RUN_MAIN_LOOP(boost_float<128>)
    else RUN_MAIN_LOOP(boost_float<256>)
    else RUN_MAIN_LOOP(boost_float<384>)
    else RUN_MAIN_LOOP(boost_float<512>)
    else RUN_MAIN_LOOP(boost_float<640>)
    else RUN_MAIN_LOOP(boost_float<768>)
    else RUN_MAIN_LOOP(boost_float<896>)
    else RUN_MAIN_LOOP(boost_float<1024>)
}


#include <iostream>
#include <vector>
#include <algorithm>

#include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
#include <CGAL/Delaunay_triangulation_2.h>
#include <CGAL/Triangulation_vertex_base_with_info_2.h>
#include <CGAL/Triangulation_face_base_2.h>
#include <boost/graph/adjacency_list.hpp>
#include <boost/pending/disjoint_sets.hpp>

typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
typedef int                                            Index;
typedef CGAL::Triangulation_vertex_base_with_info_2<Index,K>   Vb;
typedef CGAL::Triangulation_face_base_2<K>                     Fb;
typedef CGAL::Triangulation_data_structure_2<Vb,Fb>            Tds;
typedef CGAL::Delaunay_triangulation_2<K,Tds>                  DT;
typedef std::pair<K::Point_2,Index> IPoint;

typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS,
  boost::no_property, boost::property<boost::edge_weight_t, long> >      weighted_graph;
typedef boost::property_map<weighted_graph, boost::edge_weight_t>::type weight_map;
typedef boost::graph_traits<weighted_graph>::edge_descriptor            edge_desc;
typedef boost::graph_traits<weighted_graph>::vertex_descriptor          vertex_desc;

struct Edge {
  int u, v;
  long w;

  bool operator<(Edge e) const {
    return w < e.w;
  }
};

typedef std::vector<Edge> EdgeV;

using namespace std;

int maxFamilies(vector<int> &componentsOfSize, int k) {
  int f = componentsOfSize[k];
  if (k == 4) {
    if (componentsOfSize[3] <= componentsOfSize[1]) {
      f += componentsOfSize[3];
      f += ((componentsOfSize[1] - componentsOfSize[3]) / 2 + componentsOfSize[2]) / 2;
    }
    else {
      f += componentsOfSize[1];
      f += ((componentsOfSize[3] - componentsOfSize[1]) + componentsOfSize[2]) / 2;
    }
  }
  else if (k == 3) {
    if (componentsOfSize[2] <= componentsOfSize[1]) {
      f += componentsOfSize[2];
      f += (componentsOfSize[1] - componentsOfSize[2]) / 3;
    }
    else {
      f += componentsOfSize[1];
      f += (componentsOfSize[2] - componentsOfSize[1]) / 2;
    }
  }
  else if (k == 2) {
    f += componentsOfSize[1] / 2;
  }
  return f;
}

void solve() {
  int n, k, f0;
  long s0;
  cin >> n >> k >> f0 >> s0;

  vector<IPoint> pts(n);
  for (std::size_t i = 0; i < n; ++i) {
    int x, y;
    cin >> x >> y;
    pts[i] = {{x, y}, i};
  }

  DT dt;
  dt.insert(pts.begin(), pts.end());

  EdgeV edges;
  edges.reserve(3*n);
  for (auto e = dt.finite_edges_begin(); e != dt.finite_edges_end(); ++e) {
    int u = e->first->vertex((e->second+1)%3)->info();
    int v = e->first->vertex((e->second+2)%3)->info();
    if (u > v) swap(u, v);
    edges.push_back({u, v, long(dt.segment(e).squared_length())});
  }

  std::sort(edges.begin(), edges.end());

  boost::disjoint_sets_with_storage<> uf(n);
  Index n_components = n;
  vector<int> sizeOfComponent(n, 1);
  vector<int> componentsOfSize(k + 1, 0);
  long s = -1;
  int f = -1;
  componentsOfSize[1] = n;
  vector<long> squaredDistances;
  for (auto e : edges) {
    Index c1 = uf.find_set(e.u);
    Index c2 = uf.find_set(e.v);
    if (c1 != c2) {

      int fMax = maxFamilies(componentsOfSize, k);

      if (fMax >= f0) {
        s = e.w;
      }
      if (e.w >= s0) {
        f = max(f, fMax);
      }

      squaredDistances.push_back(e.w);

      uf.link(c1, c2);
      int c = uf.find_set(c1);

      componentsOfSize[sizeOfComponent[c1]] -= 1;
      componentsOfSize[sizeOfComponent[c2]] -= 1;
      sizeOfComponent[c] = min(k, sizeOfComponent[c1] + sizeOfComponent[c2]);
      componentsOfSize[sizeOfComponent[c]] += 1;

      if (--n_components == 1) break;
    }
  }
  int fMax = maxFamilies(componentsOfSize, k);
  f = max(f, fMax);

  cout << s << " " << f << endl;
}

int main()
{
  ios_base::sync_with_stdio(false);
  int t; cin >> t;
  while (t--) {
    solve();
  }
  return 0;
}


// The Command Line Interface
// Created by haoming on 10/4/17.
//

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>
#include <numeric>
#include <chrono>

#include <fmlbase/utils.h>
#include <fmlbase/SolverBase.h>
#include <fmlbase/PIS2TASQRTLassoSolver.h>
#include <fmlbase/PISTALassoSolver.h>
#include <fmlbase/PIS2TACMRSolver.h>

#include <Eigen/Dense>

using namespace Eigen;
using namespace std;
using namespace std::chrono;

void runCLITask(const std::string &task_path)
{
    fmlbase::utils::FmlParam param(task_path);

    vector<double> times;

    if(param.getStrArg("algorithm") == "sqrtlasso") {
        fmlbase::PIS2TASQRTLassoSolver solver(param);
        solver.initialize();
        MatrixXd* testx;
        fmlbase::utils::readCsvMat(testx, param.getStrArg("rootpath") + "/"+param.getStrArg("testdata"));
        VectorXd* testy;
        fmlbase::utils::readCsvVec(testy, param.getStrArg("rootpath") + "/"+param.getStrArg("testlabel"));

        for (int i = 0; i < param.getIntArg("nexp"); ++i) {
            solver.reinitialize();
            auto begin = std::chrono::steady_clock::now();
            solver.train();
            auto end = std::chrono::steady_clock::now();
            auto diff = 1. * (end - begin).count() * nanoseconds::period::num / nanoseconds::period::den;
            times.emplace_back(diff);
            cout << i << "th trail, training time (/s): " << diff << endl;
            cout << i << "train error: " << solver.eval() << endl;
            cout << i << "test error: " << solver.eval(*testx, *testy) << endl;
        }
    }
    if(param.getStrArg("algorithm") == "lasso") {
        fmlbase::PISTALassoSolver solver(param);
        solver.initialize();
        MatrixXd* testx;
        fmlbase::utils::readCsvMat(testx, param.getStrArg("rootpath") + "/"+param.getStrArg("testdata"));
        VectorXd* testy;
        fmlbase::utils::readCsvVec(testy, param.getStrArg("rootpath") + "/"+param.getStrArg("testlabel"));

        for (int i = 0; i < param.getIntArg("nexp"); ++i) {
            solver.reinitialize();
            auto begin = std::chrono::steady_clock::now();
            solver.train();
            auto end = std::chrono::steady_clock::now();
            auto diff = 1. * (end - begin).count() * nanoseconds::period::num / nanoseconds::period::den;
            times.emplace_back(diff);
            cout << i << "th trail, training time (/s): " << diff << endl;
            cout << i << "train error: " << solver.eval() << endl;
            cout << i << "test error: " << solver.eval(*testx, *testy) << endl;
        }
    }
    if(param.getStrArg("algorithm") == "CMR") {
        fmlbase::PIS2TACMRSolver solver(param);
        solver.initialize();
        MatrixXd* testx;
        fmlbase::utils::readCsvMat(testx, param.getStrArg("rootpath") + "/"+param.getStrArg("testdata"));
        MatrixXd* testy;
        fmlbase::utils::readCsvMat(testy, param.getStrArg("rootpath") + "/"+param.getStrArg("testlabel"));

        for (int i = 0; i < param.getIntArg("nexp"); ++i) {
            solver.reinitialize();
            auto begin = std::chrono::steady_clock::now();
            solver.train();
            auto end = std::chrono::steady_clock::now();
            auto diff = 1. * (end - begin).count() * nanoseconds::period::num / nanoseconds::period::den;
            times.emplace_back(diff);
            cout << i << "th trail, training time (/s): " << diff << endl;
            cout << i << "train error: " << solver.eval() << endl;
            cout << i << "test error: " << solver.eval(*testx, *testy) << endl;
        }
    }
    if(param.getStrArg("algorithm") == "SPME") {
        MatrixXd* S;
        fmlbase::utils::readCsvMat(S, param.getStrArg("rootpath") + "/"+param.getStrArg("data"));
        auto nfeature = S->cols();
        vector<fmlbase::PIS2TASQRTLassoSolver*> solver_vec;
        double errors = 0;
        for (int j = 0; j < nfeature; ++j) {
            auto temp_col = S->col(0);
            S->col(0) = S->col(j);
            S->col(j) = temp_col;
            fmlbase::PIS2TASQRTLassoSolver *new_solver = new fmlbase::PIS2TASQRTLassoSolver(param, S->rightCols(nfeature-1), S->col(0));
            new_solver->initialize();
            solver_vec.push_back(new_solver);
        }
        cout << "Construction Completed \n";
        for (int i = 0; i < param.getIntArg("nexp"); ++i) {
            for (int j = 0; j < nfeature; ++j)
                solver_vec[j]->reinitialize();
            auto begin = std::chrono::steady_clock::now();
            for (int j = 0; j < nfeature; ++j)
            {
                solver_vec[j]->train();
                //cout << "trained"<<j<<"th solver \n";
            }
            auto end = std::chrono::steady_clock::now();
            auto diff = 1. * (end - begin).count() * nanoseconds::period::num / nanoseconds::period::den;
            times.emplace_back(diff);
            cout << i << "th trail, training time (/s): " << diff << endl;
            errors = 0;
            for (int j = 0; j < nfeature; ++j)
                errors+=solver_vec[j]->eval();
        }
        int num_zeros = 0;
        for (int j = 0; j < nfeature; ++j)
        {
            num_zeros += ((*(solver_vec[j]->theta)).array().abs()<=0.00001).count();
        }
        cout << "Sparsity: " << 1-1.*num_zeros/nfeature/(nfeature-1) << endl;
        cout << "Errors: " << errors << endl;

    }

    double sumT = 0;
    for(double t : times)
        sumT += t;

    cout<<"mean training time (/s): "<<1.0*sumT/param.getIntArg("nexp")<<endl;

}

int main(int argc, const char * argv[])
{

    if(argc < 2)
        runCLITask("./Tasks/Arabidopsis");
    else
        runCLITask(argv[1]);

    return 0;
}