/* Copyright (C) 2012-2020 IBM Corp.
 * This program is Licensed under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. See accompanying LICENSE file.
 */
/* DoubleCRT.cpp - This class holds an integer polynomial in double-CRT form
 *
 * Double-CRT form is a matrix of L rows and phi(m) columns. The i'th row
 * contains the FFT of the element wrt the ith prime, i.e. the evaluations of
 * the polynomial at the primitive mth roots of unity mod the ith prime. The
 * polynomial thus represented is defined modulo the product of all the primes
 * in use. The list of primes is defined by the data member modChain, which is
 * a vector of Cmodulus objects.
 */
#include <NTL/ZZVec.h>
#include <NTL/BasicThreadPool.h>

#include "binio.h"
#include "io.h"

#include <helib/timing.h>
#include <helib/sample.h>
#include <helib/DoubleCRT.h>
#include <helib/Context.h>
#include <helib/norms.h>
#include <helib/fhe_stats.h>
#include <helib/log.h>

namespace helib {

// A threaded implementation of DoubleCRT operations

static long MakeIndexVector(const IndexSet& s, NTL::Vec<long>& v)
{
  long sz = s.card();
  v.SetLength(sz);
  for (long i = s.first(), j = 0; i <= s.last(); i = s.next(i), j++)
    v[j] = i;
  return sz;
}

// representing an integer polynomial as DoubleCRT. If the number of moduli
// to use is not specified, the resulting object uses all the moduli in
// the context. If the coefficients of poly are larger than the product of
// the used moduli, they are effectively reduced modulo that product

void DoubleCRT::FFT(const NTL::ZZX& poly, const IndexSet& s)
{
  HELIB_TIMER_START;

  if (empty(s))
    return;

  static thread_local NTL::Vec<long> tls_ivec;
  NTL::Vec<long>& ivec = tls_ivec;

  long icard = MakeIndexVector(s, ivec);
  NTL_EXEC_RANGE(icard, first, last)
  for (long j = first; j < last; j++) {
    long i = ivec[j];
    context.ithModulus(i).FFT(map[i], poly);
  }
  NTL_EXEC_RANGE_END
}

// FIXME: "code bloat": this just replicates the above with NTL::ZZX -> zzX
void DoubleCRT::FFT(const zzX& poly, const IndexSet& s)
{
  HELIB_TIMER_START;

  if (empty(s))
    return;

  static thread_local NTL::Vec<long> tls_ivec;
  NTL::Vec<long>& ivec = tls_ivec;

  long icard = MakeIndexVector(s, ivec);
  NTL_EXEC_RANGE(icard, first, last)
  for (long j = first; j < last; j++) {
    long i = ivec[j];
    context.ithModulus(i).FFT(map[i], poly);
  }
  NTL_EXEC_RANGE_END
}

// a "sanity check" function, verifies consistency of matrix with current
// moduli chain an error is raised if they are not consistent
void DoubleCRT::verify()
{
  assertTrue(map.getIndexSet() <=
                 (context.getSmallPrimes() | context.getSpecialPrimes() |
                  context.getCtxtPrimes()),
             "Index set must be a subset of the union of small primes, special "
             "primes, and ctxt primes");

  const IndexSet& s = map.getIndexSet();

  long phim = context.getPhiM();

  // check that the content of i'th row is in [0,pi) for all i
  for (long i : s) {
    NTL::vec_long& row = map[i];

    if (row.length() != phim)
      throw RuntimeError("DoubleCRT object has bad row length");

    long pi = context.ithPrime(i); // the i'th modulus
    for (long j : range(phim))
      if (row[j] < 0 || row[j] >= pi)
        throw RuntimeError("DoubleCRT object has inconsistent data");
  }
}

// Arithmetic operations. Only the "destructive" versions are used,
// i.e., a += b is implemented but not a + b.

// Generic operation, Fnc is AddMod, SubMod, or MulMod (from NTL's ZZ module)
template <typename Fun>
DoubleCRT& DoubleCRT::Op(const DoubleCRT& other, Fun fun, bool matchIndexSets)
{
  if (isDryRun())
    return *this;

  if (&context != &other.context)
    throw RuntimeError("DoubleCRT::Op: incompatible objects");

  // VJS-FIXME: experiment to ignore matchIndexSets
  // Match the index sets, if needed
  if (matchIndexSets && !(map.getIndexSet() >= other.map.getIndexSet())) {
#if 0
    HELIB_NTIMER_START(addPrimes_1);
    Warning("addPrimes called (1) in DoubleCRT::op");
    addPrimes(other.map.getIndexSet() / map.getIndexSet()); // This is expensive
#else
    throw RuntimeError("DoubleCRT::Op: matchIndexSets not honored");
#endif
  }

  // If you need to mod-up the other, do it on a temporary scratch copy
  DoubleCRT tmp(context, IndexSet());
  const IndexMap<NTL::vec_long>* other_map = &other.map;

  // VJS-FIXME: experiment to insist that
  // map.getIndexSet() <= other.map.getIndexSet()
  if (!(map.getIndexSet() <= other.map.getIndexSet())) { // Even more expensive
#if 0
    HELIB_NTIMER_START(addPrimes_2);
    tmp = other;
    Warning("addPrimes called (2) in DoubleCRT::op");
    tmp.addPrimes(map.getIndexSet() / other.map.getIndexSet());
    other_map = &tmp.map;
#else
    throw RuntimeError(
        "DoubleCRT::Op: !(map.getIndexSet() <= other.map.getIndexSet())");
#endif
  }

  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();

  // add/sub/mul the data, element by element, modulo the respective primes
  for (long i : s) {
    long pi = context.ithPrime(i);
    NTL::vec_long& row = map[i];
    const NTL::vec_long& other_row = (*other_map)[i];

    for (long j : range(phim))
      row[j] = fun.apply(row[j], other_row[j], pi);
  }
  return *this;
}

// Victor says: I added this routine so I could look
// examine its performance more carefully

DoubleCRT& DoubleCRT::do_mul(const DoubleCRT& other, bool matchIndexSets)
{
  HELIB_TIMER_START;

  if (isDryRun())
    return *this;

  if (&context != &other.context)
    throw RuntimeError("DoubleCRT::Op: incompatible objects");

  // VJS-FIXME: experiment to ignore matchIndexSets
  // Match the index sets, if needed
  if (matchIndexSets && !(map.getIndexSet() >= other.map.getIndexSet())) {
#if 0
    HELIB_NTIMER_START(addPrimes_3);
    Warning("addPrimes called (1) in DoubleCRT::mul");
    addPrimes(other.map.getIndexSet() / map.getIndexSet()); // This is expensive
#else
    throw RuntimeError("DoubleCRT::mul: matchIndexSets not honored");
#endif
  }

  // If you need to mod-up the other, do it on a temporary scratch copy
  DoubleCRT tmp(context, IndexSet());
  const IndexMap<NTL::vec_long>* other_map = &other.map;

  // VJS-FIXME: experiment to insist that
  // map.getIndexSet() <= other.map.getIndexSet()
  if (!(map.getIndexSet() <= other.map.getIndexSet())) { // Even more expensive
#if 0
    HELIB_NTIMER_START(addPrimes_4);
    tmp = other;
    Warning("addPrimes called (2) in DoubleCRT::mul");
    tmp.addPrimes(map.getIndexSet() / other.map.getIndexSet());
    other_map = &tmp.map;
#else
    throw RuntimeError(
        "DoubleCRT::mul: !(map.getIndexSet() <= other.map.getIndexSet())");
#endif
  }

  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();

  // add/sub/mul the data, element by element, modulo the respective primes
  for (long i : s) {
    long pi = context.ithPrime(i);
    NTL::mulmod_t pi_inv = context.ithModulus(i).getQInv();
    NTL::vec_long& row = map[i];
    const NTL::vec_long& other_row = (*other_map)[i];

    for (long j : range(phim))
      row[j] = MulMod(row[j], other_row[j], pi, pi_inv);
  }
  return *this;
}

#if 0
template
DoubleCRT& DoubleCRT::Op<DoubleCRT::MulFun>(const DoubleCRT &other, MulFun fun,
			 bool matchIndexSets);
#endif

template DoubleCRT& DoubleCRT::Op<DoubleCRT::AddFun>(const DoubleCRT& other,
                                                     AddFun fun,
                                                     bool matchIndexSets);

template DoubleCRT& DoubleCRT::Op<DoubleCRT::SubFun>(const DoubleCRT& other,
                                                     SubFun fun,
                                                     bool matchIndexSets);

template <typename Fun>
DoubleCRT& DoubleCRT::Op(const NTL::ZZ& num, Fun fun)
{
  if (isDryRun())
    return *this;

  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();

  for (long i : s) {
    long pi = context.ithPrime(i);
    long n = rem(num, pi); // n = num % pi
    NTL::vec_long& row = map[i];
    for (long j : range(phim))
      row[j] = fun.apply(row[j], n, pi);
  }
  return *this;
}

template DoubleCRT& DoubleCRT::Op<DoubleCRT::MulFun>(const NTL::ZZ& num,
                                                     MulFun fun);

template DoubleCRT& DoubleCRT::Op<DoubleCRT::AddFun>(const NTL::ZZ& num,
                                                     AddFun fun);

template DoubleCRT& DoubleCRT::Op<DoubleCRT::SubFun>(const NTL::ZZ& num,
                                                     SubFun fun);

DoubleCRT& DoubleCRT::Negate(const DoubleCRT& other)
{
  if (isDryRun())
    return *this;

  if (&context != &other.context)
    throw RuntimeError("DoubleCRT Negate: incompatible contexts");

  if (map.getIndexSet() != other.map.getIndexSet()) {
    map = other.map; // copy the data
  }
  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();
  for (long i : s) {
    long pi = context.ithPrime(i);
    NTL::vec_long& row = map[i];
    const NTL::vec_long& other_row = other.map[i];
    for (long j : range(phim))
      row[j] = NTL::NegateMod(other_row[j], pi);
  }
  return *this;
}

template <typename Fun>
DoubleCRT& DoubleCRT::Op(const NTL::ZZX& poly, Fun fun)
{
  if (isDryRun())
    return *this;

  const IndexSet& s = map.getIndexSet();
  DoubleCRT other(poly, context, s); // other defined wrt same primes as *this

  return Op(other, fun);
}

template DoubleCRT& DoubleCRT::Op<DoubleCRT::MulFun>(const NTL::ZZX& poly,
                                                     MulFun fun);

template DoubleCRT& DoubleCRT::Op<DoubleCRT::AddFun>(const NTL::ZZX& poly,
                                                     AddFun fun);

template DoubleCRT& DoubleCRT::Op<DoubleCRT::SubFun>(const NTL::ZZX& poly,
                                                     SubFun fun);

// break *this into n digits,according to the primeSets in context.digits
// returns the sum of the canonical embedding norms of the digits
NTL::xdouble DoubleCRT::breakIntoDigits(std::vector<DoubleCRT>& digits) const
{
  HELIB_TIMER_START;

  long phim = context.getPhiM();

  IndexSet remainingPrimes = getIndexSet();
  long n = 0;

  for (; !empty(remainingPrimes); n++) {
    IndexSet digitPrimes = context.getDigits().at(n);
    digitPrimes.retain(remainingPrimes);

    remainingPrimes.remove(context.getDigits().at(n));
  }

  IndexSet allPrimes = getIndexSet() | context.getSpecialPrimes();

  assertTrue(getIndexSet() <= context.getCtxtPrimes(),
             "Index set must be a subset of ctxt primes");
  // the calling routine should ensure that the index set
  // contains only ctxt primes

  assertTrue(n <= (long)context.getDigits().size(),
             "n cannot be larger than the size of context.digits");

  digits.resize(n, DoubleCRT(context, IndexSet::emptySet()));
  if (isDryRun())
    return NTL::conv<NTL::xdouble>(0.0);

  for (long i : range(n)) {
    digits[i] = *this;
    IndexSet notInDigit = digits[i].getIndexSet() / context.getDigit(i);
    digits[i].removePrimes(notInDigit); // reduce modulo the digit primes
  }

  NTL::xdouble noise(0.0);

  for (long i : range(digits.size())) {
    HELIB_NTIMER_START(addPrimes_5);
    IndexSet notInDigit = allPrimes / digits[i].getIndexSet();

#if 0
// This version coumputes a high-probability bound

    double digitSize = context.logOfProduct(digits[i].getIndexSet());
    NTL::xdouble norm_bnd =
      context.noiseBoundForUniform( NTL::xexp(digitSize)/2.0, phim );
    noise += norm_bnd;

    digits[i].addPrimes(notInDigit); // add back all the primes

#else
    // This version computes an "exact" value

    double digitSize = context.logOfProduct(digits[i].getIndexSet());
    NTL::xdouble norm_bnd =
        context.noiseBoundForUniform(NTL::xexp(digitSize) / 2.0, phim);

    NTL::ZZX poly;
    digits[i].addPrimes(notInDigit, &poly); // add back all the primes

    HELIB_NTIMER_START(NORM_VAL);
    NTL::xdouble norm_val = embeddingLargestCoeff(poly, context.getZMStar());
    HELIB_NTIMER_STOP(NORM_VAL);

    noise += norm_val;

    double ratio = NTL::conv<double>(norm_val / norm_bnd);
    HELIB_STATS_UPDATE("break-into-digits-ratio", ratio);

#endif

    NTL::ZZ pi = context.productOfPrimes(context.getDigit(i));
    for (long j : range(i + 1, digits.size())) {
      digits[j].Sub(digits[i], /*matchIndexSets=*/false);
      digits[j] /= pi;
    }
  }
  HELIB_TIMER_STOP;

  return noise;
}

// expand index set by s1.
// it is assumed that s1 is disjoint from the current index set.
void DoubleCRT::addPrimes(const IndexSet& s1, NTL::ZZX* poly_p)
{
  HELIB_TIMER_START;

  if (empty(s1)) {
    assertTrue(poly_p == 0, "poly_p must be null here");
    return; // nothing to do
  }
  // s1 is disjoint from *this
  assertTrue(disjoint(s1, map.getIndexSet()),
             "addPrimes can only be called on a disjoint set");

  if (empty(getIndexSet())) { // special case for empty DCRT
    map.insert(s1);           // just add new rows to the map and return
    SetZero();
    if (poly_p)
      clear(*poly_p);
    return;
  }
  NTL::ZZX poly;
  toPoly(poly); // recover in coefficient representation

  if (poly_p)
    *poly_p = poly;

  map.insert(s1); // add new rows to the map
  if (isDryRun())
    return;

  // fill in new rows
  if (deg(poly) <= 0)       // special case for a constant polynomial
    *this = coeff(poly, 0); // no FFT is needed
  else
    FFT(poly, s1);
}

// Expand index set by s1, and multiply by \prod{q \in s1}. s1 is assumed to
// be disjoint from the current index set. Returns the logarithm of product.
double DoubleCRT::addPrimesAndScale(const IndexSet& s1)
{
  if (empty(s1))
    return 0.0; // nothing to do
  // s1 is disjoint from *this
  assertTrue(empty(s1 & map.getIndexSet()),
             "addPrimes can only be called on a disjoint set");

  if (empty(getIndexSet())) { // special case for empty DCRT
    map.insert(s1);           // just add new rows to the map and return
    SetZero();
    return 0.0;
  }
  // compute factor to scale existing rows
  NTL::ZZ factor = NTL::to_ZZ(1);
  double logFactor = 0.0;
  for (long i : s1) {
    long qi = context.ithPrime(i);
    factor *= qi;
    logFactor += log((double)qi);
  }

  // scale existing rows
  long phim = context.getPhiM();
  const IndexSet& iSet = map.getIndexSet();
  for (long i : iSet) {
    long qi = context.ithPrime(i);
    long f = rem(factor, qi); // f = factor % qi
    NTL::vec_long& row = map[i];
    // scale row by a factor of f modulo qi
    NTL::mulmod_precon_t bninv = NTL::PrepMulModPrecon(f, qi);
    for (long j : range(phim))
      row[j] = NTL::MulModPrecon(row[j], f, qi, bninv);
  }

  // insert new rows and fill them with zeros
  map.insert(s1); // add new rows to the map
  for (long i : s1) {
    NTL::vec_long& row = map[i];
    for (long j : range(phim))
      row[j] = 0;
  }

  return logFactor;
}

// *****************************************************
DoubleCRTHelper::DoubleCRTHelper(const Context& context)
{
  val = context.getPhiM();
}

DoubleCRT::DoubleCRT(const NTL::ZZX& poly,
                     const Context& _context,
                     const IndexSet& s) :
    context(_context), map(new DoubleCRTHelper(_context))
{
  HELIB_TIMER_START;
  assertTrue(s.last() < context.numPrimes(),
             "s must end with a smaller element than context.numPrimes()");

  map.insert(s);
  if (isDryRun())
    return;

  // convert the integer polynomial to FFT representation modulo the primes
  if (deg(poly) <= 0)       // special case for a constant polynomial
    *this = coeff(poly, 0); // no FFT is needed
  else
    FFT(poly, s);
}

// FIXME-IndexSet
#if 0
DoubleCRT::DoubleCRT(const NTL::ZZX& poly, const Context &_context)
: context(_context), map(new DoubleCRTHelper(_context))
{
  HELIB_TIMER_START;
  IndexSet s = IndexSet(0, context.numPrimes()-1);
  // FIXME: maybe the default index set should be determined by context?

  map.insert(s);
  if (isDryRun()) return;

  // convert the integer polynomial to FFT representation modulo the primes
  if (deg(poly)<=0) // special case for a constant polynomial
    *this = coeff(poly,0); // no FFT is needed
  else
    FFT(poly, s);
}

DoubleCRT::DoubleCRT(const NTL::ZZX& poly)
: context(*activeContext), map(new DoubleCRTHelper(*activeContext))
{
  HELIB_TIMER_START;
  IndexSet s = IndexSet(0, context.numPrimes()-1);
  // FIXME: maybe the default index set should be determined by context?

  map.insert(s);
  if (isDryRun()) return;

  // convert the integer polynomial to FFT representation modulo the primes
  if (deg(poly)<=0) // special case for a constant polynomial
    *this = coeff(poly,0); // no FFT is needed
  else
    FFT(poly, s);
}
#endif

// *****************************************************
// FIXME: "code bloat": this just replicates the above with NTL::ZZX -> zzX

DoubleCRT::DoubleCRT(const zzX& poly,
                     const Context& _context,
                     const IndexSet& s) :
    context(_context), map(new DoubleCRTHelper(_context))
{
  HELIB_TIMER_START;
  assertTrue(s.last() < context.numPrimes(),
             "s must end with a smaller element than context.numPrimes()");

  map.insert(s);
  if (isDryRun())
    return;

  // convert the integer polynomial to FFT representation modulo the primes
  if (lsize(poly) <= 1) // special case for a constant polynomial
    *this = (lsize(poly) == 1) ? poly[0] : 0; // no FFT is needed
  else
    FFT(poly, s);
}

// FIXME-IndexSet
#if 0
DoubleCRT::DoubleCRT(const zzX& poly, const Context &_context)
: context(_context), map(new DoubleCRTHelper(_context))
{
  HELIB_TIMER_START;
  IndexSet s = IndexSet(0, context.numPrimes()-1);
  // FIXME: maybe the default index set should be determined by context?

  map.insert(s);
  if (isDryRun()) return;

  // convert the integer polynomial to FFT representation modulo the primes
  // convert the integer polynomial to FFT representation modulo the primes
  if (lsize(poly)<=1) // special case for a constant polynomial
    *this = (lsize(poly)==1)? poly[0] : 0;  // no FFT is needed
  else
    FFT(poly, s);
}

DoubleCRT::DoubleCRT(const zzX& poly)
: context(*activeContext), map(new DoubleCRTHelper(*activeContext))
{
  HELIB_TIMER_START;
  IndexSet s = IndexSet(0, context.numPrimes()-1);
  // FIXME: maybe the default index set should be determined by context?

  map.insert(s);
  if (isDryRun()) return;

  // convert the integer polynomial to FFT representation modulo the primes
  // convert the integer polynomial to FFT representation modulo the primes
  if (lsize(poly)<=1) // special case for a constant polynomial
    *this = (lsize(poly)==1)? poly[0] : 0;  // no FFT is needed
  else
    FFT(poly, s);
}
#endif

DoubleCRT::DoubleCRT(const Context& _context, const IndexSet& s) :
    context(_context), map(new DoubleCRTHelper(_context))
{
  assertTrue(s.last() < context.numPrimes(),
             "s must end with a smaller element than context.numPrimes()");

  map.insert(s);
  if (isDryRun())
    return;

  long phim = context.getPhiM();

  for (long i : s) {
    NTL::vec_long& row = map[i];
    for (long j : range(phim))
      row[j] = 0;
  }
}

// *****************************************************

// FIXME-IndexSet
#if 0
DoubleCRT::DoubleCRT(const Context &_context)
: context(_context), map(new DoubleCRTHelper(_context))
{
  IndexSet s = IndexSet(0, context.numPrimes()-1);
  // FIXME: maybe the default index set should be determined by context?

  map.insert(s);
  if (isDryRun()) return;

  long phim = context.getPhiM();

  for (long i = s.first(); i <= s.last(); i = s.next(i)) {
    NTL::vec_long& row = map[i];
    for (long j = 0; j < phim; j++) row[j] = 0;
  }
}
#endif

DoubleCRT& DoubleCRT::operator=(const DoubleCRT& other)
// optimized for the case of matching index sets
{
  if (this == &other)
    return *this;

  if (&context != &other.context)
    throw RuntimeError("DoubleCRT assignment: incompatible contexts");

  if (map.getIndexSet() != other.map.getIndexSet()) {
    map = other.map; // copy the data
  } else {
    const IndexSet& s = map.getIndexSet();
    long phim = context.getPhiM();
    for (long i : s) {
      NTL::vec_long& row = map[i];
      const NTL::vec_long& other_row = other.map[i];
      for (long j : range(phim))
        row[j] = other_row[j];
    }
  }
  return *this;
}

DoubleCRT& DoubleCRT::operator=(const NTL::ZZX& poly)
{
  if (isDryRun())
    return *this;

  const IndexSet& s = map.getIndexSet();
  if (deg(poly) <= 0)       // special case for a constant polynomial
    *this = coeff(poly, 0); // no FFT is needed
  else
    FFT(poly, s);
  return *this;
}

DoubleCRT& DoubleCRT::operator=(const zzX& poly)
{
  if (isDryRun())
    return *this;

  const IndexSet& s = map.getIndexSet();
  // convert the integer polynomial to FFT representation modulo the primes
  if (lsize(poly) <= 1) // special case for a constant polynomial
    *this = (lsize(poly) == 1) ? poly[0] : 0; // no FFT is needed
  else
    FFT(poly, s);
  return *this;
}

DoubleCRT& DoubleCRT::operator=(const NTL::ZZ& num)
{
  const IndexSet& s = map.getIndexSet();
  if (isDryRun())
    return *this;

  long phim = context.getPhiM();

  for (long i : s) {
    NTL::vec_long& row = map[i];
    long pi = context.ithPrime(i);
    long n = rem(num, pi);

    for (long j : range(phim))
      row[j] = n;
  }

  return *this;
}

// DIRT: this method affect the NTL zz_p::modulus
long DoubleCRT::getOneRow(NTL::zz_pX& row, long idx) const
{
  if (!map.getIndexSet().contains(idx)) // idx not in the primeset
    return 0;

  // convert from evaluation to standard coefficient representation
  context.ithModulus(idx).restoreModulus(); // recover NTL modulus for prime
  context.ithModulus(idx).iFFT(row, map[idx]);
  return context.ithPrime(idx);
}

// Get the row corresponding to the i'th moduli, in NTL::Vec<long> format.
// For convenience, returns the modulus that was used for this row.
// If idx is not in the current primesSet then do nothing and return 0;
long DoubleCRT::getOneRow(NTL::Vec<long>& row, long idx, bool positive) const
{
  NTL::zz_pBak bak;
  bak.save(); // backup NTL's current modulus

  NTL::zz_pX& tmp = Cmodulus::getScratch_zz_pX();
  long q = getOneRow(tmp, idx);
  if (q == 0)
    return 0; // no such index

  conv(row, tmp.rep); // copy the row to NTL::Vec<long> format

  // By default, integers are in [0,q).
  // If we need the symmetric interval then make it so.
  if (!positive) {
    long phim = context.getPhiM();
    for (long j : range(phim))
      if (row[j] > q / 2)
        row[j] -= q;
  }
  return q;
}

// A parallelizable implementation of toPoly
void DoubleCRT::toPoly(NTL::ZZX& poly, const IndexSet& s, bool positive) const
{
  HELIB_TIMER_START;
  if (isDryRun())
    return;

  IndexSet s1 = map.getIndexSet() & s;
  if (empty(s1)) { // nothing to do
    clear(poly);
    return;
  }

  // To avoid allocating these with every call, they are defined static
  // but thread_local, so concurrent calls to toPoly by multiple threads
  // will have different copies. (tls_ = "Thread-Local Storage")
  static thread_local NTL::Vec<long> tls_ivec;
  static thread_local NTL::Vec<long> tls_pvec;
  static thread_local NTL::Vec<NTL::Vec<long>> tls_remtab;
  static thread_local NTL::Vec<NTL::zz_pX> tls_tmpvec;

  // For readability, call them by names without the tls_
  NTL::Vec<long>& ivec = tls_ivec; // the indexes of the active primes
  // remtab[*][i] = coeffs of tmpvec[i]
  NTL::Vec<NTL::Vec<long>>& remtab = tls_remtab;
  // tmpvec[i] = current poly in i'th thread
  NTL::Vec<NTL::zz_pX>& tmpvec = tls_tmpvec;

  // initialize the ivec vector, ivec[j] = index of j'th active prime
  long phim = context.getPhiM();
  long icard = MakeIndexVector(s1, ivec); // icard = how many active primes

  // Which primes are handled by what thread
  NTL::PartitionInfo pinfo(icard); // allocate threads to handle icard primes
  long cnt = pinfo.NumIntervals(); // how many threads are allocated

  // allocate space for all the coefficients modulo all the primes
  remtab.SetLength(phim);
  for (long h : range(phim))
    remtab[h].SetLength(icard);

  // allocate space for the polynomials modulo all the primes
  tmpvec.SetLength(cnt);
  for (long i : range(cnt))
    tmpvec[i].SetMaxLength(phim);

  // Run the inverse FFT modulo the different primes in parallel
  {
    HELIB_NTIMER_START(toPoly_FFT);
    NTL_EXEC_INDEX(cnt, index)
    long first, last;
    pinfo.interval(first, last, index);

    NTL::zz_pX& tmp = tmpvec[index];

    for (long j : range(first, last)) {
      long i = ivec[j];
      context.ithModulus(i).iFFT(tmp, map[i]); // inverse FFT

      long d = deg(tmp); // copy the coefficients, pad by zeros if needed
      for (long h = 0; h <= d; h++)
        remtab[h][j] = rep(tmp.rep[h]);
      for (long h = d + 1; h < phim; h++)
        remtab[h][j] = 0;
    }
    NTL_EXEC_INDEX_END
  } // release space of local variables

  // Run the integer CRT in parallel for the different coefficients
  {
    HELIB_NTIMER_START(toPoly_CRT);
    NTL::PartitionInfo pinfo1(phim);
    long cnt1 = pinfo1.NumIntervals();

    // static thread-local variables to avoid re-allocation
    static thread_local NTL::ZZ tls_prod;
    static thread_local NTL::ZZ tls_prod_half;
    static thread_local NTL::Vec<long> tls_qvec;
    static thread_local NTL::Vec<double> tls_qrecipvec;
    static thread_local NTL::Vec<long> tls_tvec;
    static thread_local NTL::Vec<NTL::mulmod_precon_t> tls_tqinvvec;

    static thread_local NTL::ZZVec tls_prod1vec;
    static thread_local NTL::ZZVec tls_resvec;

    NTL::ZZ& prod = tls_prod;            // product of all the primes
    NTL::ZZ& prod_half = tls_prod_half;  // = (prod+1)/2
    NTL::ZZVec& prod1vec = tls_prod1vec; // prod1vec[i] = prod / qi
    NTL::Vec<long>& qvec = tls_qvec;     // vector of the primes themselves
    NTL::Vec<double>& qrecipvec = tls_qrecipvec; // keeps 1/qi for each prime qi
    NTL::Vec<long>& tvec = tls_tvec; // tvec[i] = (prod / qi)^{-1} mod qi
    // tvec with extra tables
    NTL::Vec<NTL::mulmod_precon_t>& tqinvvec = tls_tqinvvec;
    NTL::ZZVec& resvec = tls_resvec;

    qvec.SetLength(icard);
    qrecipvec.SetLength(icard);
    tvec.SetLength(icard);
    tqinvvec.SetLength(icard);

    // store the primes and reciprocals, and compute their product
    prod = 1;
    for (long j : range(icard)) {
      long i = ivec[j];
      long q = context.ithModulus(i).getQ(); // the prime
      qvec[j] = q;
      qrecipvec[j] = 1 / double(q);
      mul(prod, prod, q);
    }
    long sz = prod.size(); // size of the product

    // reallocate space only if needed
    if (prod1vec.length() != icard || prod1vec.BaseSize() != sz + 1) {
      prod1vec.kill();
      prod1vec.SetSize(icard, sz + 1); // icard integers of size<=sz+1 each
    }

    // compute (prod / qi)^{-1} mod qi
    for (long j : range(icard)) {
      long q = qvec[j];
      div(prod1vec[j], prod, q);
      long t = rem(prod1vec[j], q);
      t = NTL::InvMod(t, q);
      tvec[j] = t;
      tqinvvec[j] = NTL::PrepMulModPrecon(t, q);
    }

    if (resvec.length() != phim || resvec.BaseSize() != sz + 1) {
      resvec.kill();
      resvec.SetSize(phim, sz + 1);
    }

    if (!positive) { // prod_half = (prod+1)/2
      add(prod_half, prod, 1);
      div(prod_half, prod_half, 2);
    }

    // Compute the actual CRT reconstruction
    NTL_EXEC_INDEX(cnt1, index)
    NTL_IMPORT(icard)
    long first, last;
    pinfo1.interval(first, last, index);

    long* qvecp = qvec.elts();
    double* qrecipvecp = qrecipvec.elts();
    long* tvecp = tvec.elts();
    NTL::mulmod_precon_t* tqinvvecp = tqinvvec.elts();
    NTL::ZZ* prod1vecp = prod1vec.elts();

    NTL::ZZ tmp;
    tmp.SetSize(sz + 4);

    for (long h : range(first, last)) { // CRT the h'th coefficient
      clear(tmp);
      double quotient = 0;
      long* remvec = remtab[h].elts();

      for (long j : range(icard)) { // Add one prime at a time
        long q = qvecp[j];
        long t = tvecp[j];
        NTL::mulmod_precon_t tqinv = tqinvvecp[j];
        long r = remvec[j];
        double qrecip = qrecipvecp[j];
        r = NTL::MulModPrecon(r, t, q, tqinv);
        MulAddTo(tmp, prod1vecp[j], r);
        quotient += r * qrecip;
      }
      // reduce modulo prod
      MulSubFrom(tmp, prod, long(quotient));
      while (tmp < 0)
        add(tmp, tmp, prod);
      while (tmp >= prod)
        sub(tmp, tmp, prod);
      // if !positive, reduce to the interval [-prod/2, prod/2]
      if (!positive && tmp >= prod_half)
        tmp -= prod;
      resvec[h] = tmp;
    }
    NTL_EXEC_INDEX_END

    poly.SetLength(phim);
    for (long j : range(phim))
      poly[j] = resvec[j];
    poly.normalize();

    // NOTE: assigning to poly[j] within the parallel loop
    // leads to horrible performance, as there apparently is
    // a lot of contention within malloc.
  }
}

void DoubleCRT::toPoly(NTL::ZZX& p, bool positive) const
{
  const IndexSet& s = map.getIndexSet();
  toPoly(p, s, positive);
}

// Division by constant
DoubleCRT& DoubleCRT::operator/=(const NTL::ZZ& num)
{
  if (isDryRun())
    return *this;

  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();

  for (long i : s) {
    long pi = context.ithPrime(i);
    long n = NTL::InvMod(rem(num, pi), pi); // n = num^{-1} mod pi
    NTL::vec_long& row = map[i];
    NTL::mulmod_precon_t precon = NTL::PrepMulModPrecon(n, pi);
    for (long j : range(phim))
      row[j] = NTL::MulModPrecon(row[j], n, pi, precon);
  }
  return *this;
}

// Small-exponent polynomial exponentiation
void DoubleCRT::Exp(long e)
{
  if (isDryRun())
    return;

  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();

  for (long i : s) {
    long pi = context.ithPrime(i);
    NTL::vec_long& row = map[i];
    for (long j : range(phim))
      row[j] = NTL::PowerMod(row[j], e, pi);
  }
}

// Apply the automorphism F(X) --> F(X^k)  (with gcd(k,m)=1)
#if 1
void DoubleCRT::automorph(long k)
{
  if (isDryRun())
    return;

  const PAlgebra& zMStar = context.getZMStar();
  if (!zMStar.inZmStar(k))
    throw RuntimeError("DoubleCRT::automorph: k not in Zm*");

  long m = zMStar.getM();
  long phim = context.getPhiM();
  std::vector<long> tmp(m); // temporary array of size m
  NTL::mulmod_precon_t precon = NTL::PrepMulModPrecon(k, m);

  const IndexSet& s = map.getIndexSet();

  // go over the rows, permute them one at a time
  for (long i : s) {
    NTL::vec_long& row = map[i];

    // Compute new[j] = old[j*k mod m]

    // for (long j=1; j<m; j++) { // 1st pass: copy to temporary array
    //   long idx = zMStar.indexInZmstar_unchecked(j);
    //   if (idx>=0) tmp[j] = row[idx];
    // }
    // for (long j=1; j<m; j++) { // 2nd pass: copy back from temporary array
    //   long idx = zMStar.indexInZmstar_unchecked(j);
    //   if (idx>=0) row[idx] = tmp[NTL::MulModPrecon(j,k,m,precon)];
    //                                        // new[j] = old[j*k mod m]
    // }

    // slightly faster...

    for (long j : range(phim)) { // 1st pass: copy to temporary array
      tmp[zMStar.repInZmstar_unchecked(j)] = row[j];
    }
    for (long j : range(phim)) { // 2nd pass: copy back from temp array
      row[j] =
          tmp[NTL::MulModPrecon(zMStar.repInZmstar_unchecked(j), k, m, precon)];
    }
  }
}

#else
// VJS: I tried this as an alternative...it is slower :-(
void DoubleCRT::automorph(long k)
{
  if (isDryRun())
    return;
  const PAlgebra& zMStar = context.getZMStar();
  if (!zMStar.inZmStar(k))
    throw RuntimeError("DoubleCRT::automorph: k not in Zm*");
  long m = zMStar.getM();
  long phim = getPhiM();
  std::vector<long> tmp(phim); // temporary array of size m

  k = NTL::InvMod(k, m);
  NTL::mulmod_precon_t precon = NTL::PrepMulModPrecon(k, m);
  const IndexSet& s = map.getIndexSet();

  // go over the rows, permute them one at a time
  // new[j*k mod m] = old[j]
  for (long i = s.first(); i <= s.last(); i = s.next(i)) {
    NTL::vec_long& row = map[i];

    for (long j = 0; j < phim; j++)
      tmp[j] = row[j];

    for (long j = 0; j < phim; j++) {
      long rep = zMStar.repInZmstar_unchecked(j);
      rep = NTL::MulModPrecon(rep, k, m, precon);
      long idx = zMStar.indexInZmstar_unchecked(rep);
      row[idx] = tmp[j];
    }
  }
}
#endif

// Compute the complex conjugate, this is the same as automorph(m-1)
void DoubleCRT::complexConj()
{
  if (isDryRun())
    return;

  long phim = context.getPhiM();
  const IndexSet& s = map.getIndexSet();

  // go over the rows, permute them one at a time
  for (long i : s) {
    NTL::vec_long& row = map[i];
    for (long j : range(phim / 2)) { // swap i <-> phi(m)-i-1
      std::swap(row[j], row[phim - j - 1]);
    }
  }
}

// fills each row i with random integers mod pi
void DoubleCRT::randomize(const NTL::ZZ* seed)
{
  HELIB_TIMER_START;

  if (isDryRun())
    return;

  if (seed != nullptr)
    SetSeed(*seed);

  const IndexSet& s = map.getIndexSet();
  long phim = context.getPhiM();

  NTL::RandomStream& stream = NTL::GetCurrentRandomStream();
  const long bufsz = 2048;

  NTL::Vec<unsigned char> buf_storage;
  buf_storage.SetLength(bufsz);

  unsigned char* buf = buf_storage.elts();

  for (long i : s) {
    long pi = context.ithPrime(i);
    long k = NTL::NumBits(pi - 1);
    long nb = (k + 7) / 8;
    unsigned long mask = (1UL << k) - 1UL;

    NTL::vec_long& row = map[i];
    long j = 0;

    for (;;) {
      {
        HELIB_NTIMER_START(randomize_stream);
        stream.get(buf, bufsz);
      }

      for (long pos = 0; pos <= bufsz - nb; pos += nb) {

        // "Duff's device" used to avoid loops
        // Commented out. Reference of the operation done as loop.
        //  unsigned long utmp = 0;
        //  for (long cnt = nb-1;  cnt >= 0; cnt--)
        //    utmp = (utmp << 8) | buf[pos+cnt];

#if (defined(__GNUC__) || defined(__clang__))
        unsigned long utmp = buf[pos + nb - 1];

        {

          // This is gcc non-standard. Works also on clang and icc.
          // It's only about 2-3% faster than Duff.

          // The pragma below disables the gcc warning temporarily.

#pragma GCC diagnostic push
#ifdef __GNUC__
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wgnu-label-as-value"
#else
#pragma GCC diagnostic ignored "-Wpedantic"
#endif
#endif
          static void* dispatch_table[] =
              {&&L0, &&L1, &&L2, &&L3, &&L4, &&L5, &&L6, &&L7, &&L8};

          goto* dispatch_table[nb];
#pragma GCC diagnostic pop

        L8:
          utmp = (utmp << 8) | buf[pos + 6];
        L7:
          utmp = (utmp << 8) | buf[pos + 5];
        L6:
          utmp = (utmp << 8) | buf[pos + 4];
        L5:
          utmp = (utmp << 8) | buf[pos + 3];
        L4:
          utmp = (utmp << 8) | buf[pos + 2];
        L3:
          utmp = (utmp << 8) | buf[pos + 1];
        L2:
          utmp = (utmp << 8) | buf[pos + 0];
        L1:;
        L0:;
        }

#else
        // Duff's device about 25% faster
        unsigned long utmp = buf[pos + nb - 1];
        switch (nb) {
        case 8:
          utmp = (utmp << 8) | buf[pos + 6];
        case 7:
          utmp = (utmp << 8) | buf[pos + 5];
        case 6:
          utmp = (utmp << 8) | buf[pos + 4];
        case 5:
          utmp = (utmp << 8) | buf[pos + 3];
        case 4:
          utmp = (utmp << 8) | buf[pos + 2];
        case 3:
          utmp = (utmp << 8) | buf[pos + 1];
        case 2:
          utmp = (utmp << 8) | buf[pos + 0];
        }
#endif

        utmp = (utmp & mask);

        long tmp = utmp;

        row[j] = tmp;
        j += (tmp < pi);
        if (j >= phim)
          break;
      }
      if (j >= phim)
        break;
    }
  }
}

// Coefficients are -1/0/1, Prob[0]=1/2
double DoubleCRT::sampleSmall()
{
  zzX poly;
  // degree-(phi(m)-1) polynomial
  double retval = ::helib::sampleSmall(poly, context);
  *this = poly; // convert to DoubleCRT
  return retval;
}

double DoubleCRT::sampleSmallBounded()
{
  zzX poly;
  // degree-(phi(m)-1) polynomial
  double retval = ::helib::sampleSmallBounded(poly, context);
  *this = poly; // convert to DoubleCRT
  return retval;
}

// Coefficients are -1/0/1 with pre-specified number of nonzeros
double DoubleCRT::sampleHWt(long Hwt)
{
  zzX poly;
  double retval = ::helib::sampleHWt(poly, context, Hwt);
  *this = poly; // convert to DoubleCRT
  return retval;
}

// Coefficients are -1/0/1 with pre-specified number of nonzeros
double DoubleCRT::sampleHWtBounded(long Hwt)
{
  zzX poly;
  double retval = ::helib::sampleHWtBounded(poly, context, Hwt);
  *this = poly; // convert to DoubleCRT
  return retval;
}

// Coefficients are Gaussians
double DoubleCRT::sampleGaussian(double stdev)
{
  if (stdev == 0.0)
    stdev = to_double(context.getStdev());
  zzX poly;
  double retval = ::helib::sampleGaussian(poly, context, stdev);
  *this = poly; // convert to DoubleCRT
  return retval;
}

double DoubleCRT::sampleGaussianBounded(double stdev)
{
  if (stdev == 0.0)
    stdev = to_double(context.getStdev());
  zzX poly;
  double retval = ::helib::sampleGaussianBounded(poly, context, stdev);
  *this = poly; // convert to DoubleCRT
  return retval;
}

NTL::xdouble DoubleCRT::sampleGaussianBounded(NTL::xdouble stdev)
{
  NTL::ZZX poly;
  NTL::xdouble retval = ::helib::sampleGaussianBounded(poly, context, stdev);
  *this = poly; // convert to DoubleCRT
  return retval;
}

// Coefficients are uniform in [-B..B]

double DoubleCRT::sampleUniform(long B)
{
  zzX poly;
  double retval = ::helib::sampleUniform(poly, context, B);
  *this = poly;
  return retval;
}

NTL::xdouble DoubleCRT::sampleUniform(const NTL::ZZ& B)
{
  NTL::ZZX poly;
  NTL::xdouble retval = ::helib::sampleUniform(poly, context, B);
  *this = poly;
  return retval;
}

void DoubleCRT::scaleDownToSet(const IndexSet& s,
                               long ptxtSpace,
                               NTL::ZZX& delta)
{
  IndexSet diff = getIndexSet() / s;
  if (empty(diff))
    return; // nothing to do

  assertTrue(ptxtSpace >= 1, "ptxtSpace must be at least 1");
  // cannot mod-down to the empty set
  assertNeq(diff,
            getIndexSet(),
            "s and the index set must have some intersection");
  if (isDryRun()) {
    removePrimes(diff); // remove the primes from consideration
    return;
  }

  NTL::ZZ diffProd = context.productOfPrimes(diff); // mod-down by this factor
  toPoly(delta, diff); // convert to coeff-representation modulo diffProd

  if (ptxtSpace > 1) { // make delta divisible by ptxtSpace

    // Need to subtract from each coefficient delta[i] the integer
    //          diffProd * (delta[i] * diffProd^{-1} mod ptxtSpace).
    // This does not change delta modulo diffProd, but makes it
    // divisible by ptxtSpace.
    long p_over_2 = ptxtSpace / 2;
    long p_mod_2 = ptxtSpace % 2;
    long prodInv = NTL::InvMod(rem(diffProd, ptxtSpace), ptxtSpace);

    for (long i : range(delta.rep.length())) {
      long delta_i_modP = rem(delta.rep[i], ptxtSpace);
      if (delta_i_modP != 0) { // if not already 0 mod ptxtSpace
        delta_i_modP = NTL::MulMod(delta_i_modP, prodInv, ptxtSpace);

        // NOTE: this makes sure we get a more truly balanced remainder
        if (delta_i_modP > p_over_2 ||
            (p_mod_2 == 0 && delta_i_modP == p_over_2 &&
             (sign(delta.rep[i]) < 0 ||
              (sign(delta.rep[i]) == 0 && NTL::RandomBnd(2)))))
          delta_i_modP -= ptxtSpace;
        delta.rep[i] -= diffProd * delta_i_modP;
      }
    }

    delta.normalize(); // normalize after working directly on the coeffs
  }
  removePrimes(diff); // remove the primes from consideration
  *this -= delta;     // convert delta to DoubleCRT, then subtract
  *this /= diffProd;  // *this is divisible by diffProd, so this operation
                      // actually scales it down
}

std::ostream& operator<<(std::ostream& str, const DoubleCRT& d)
{
  str << d.writeToJSON();
  return str;
}

std::istream& operator>>(std::istream& str, DoubleCRT& d)
{
  d.readJSON(str);
  return str;
}

void DoubleCRT::writeTo(std::ostream& str) const
{
  const IndexSet& set = map.getIndexSet();
  //  std::cerr << "[DCRT::write] set: " << set << std::endl;
  set.writeTo(str);

  for (long i : set) {
    write_ntl_vec_long(str, map[i]);
    //   std::cerr << "[DCRT::write] map[i]: " << map[i] << std::endl;
  }
}

DoubleCRT DoubleCRT::readFrom(std::istream& str, const Context& context)
{
  DoubleCRT ret(context, IndexSet::emptySet());
  ret.read(str);

  return ret;
}

void DoubleCRT::read(std::istream& str)
{
  IndexSet set = IndexSet::readFrom(str); // read in the indexSet
  map.clear();
  map.insert(set); // fix the index set for the data
                   //  std::cerr << "[DCRT::read] set: " << set << std::endl;

  for (long i : set) {
    read_ntl_vec_long(str, map[i]);
    //   std::cerr << "[DCRT::read] map[i]: " << map[i] << std::endl;
  }
}

void DoubleCRT::writeToJSON(std::ostream& str) const
{
  str << this->writeToJSON();
}

JsonWrapper DoubleCRT::writeToJSON() const
{
  const IndexSet& set = this->map.getIndexSet();
  std::vector<NTL::Vec<long>> map_cnt;

  // check that the content of i'th row is in [0,pi) for all i
  for (long i : set)
    map_cnt.emplace_back(this->map[i]);

  json j = {{"set", unwrap(set.writeToJSON())}, {"map", map_cnt}};
  return wrap(j);
}

DoubleCRT DoubleCRT::readFromJSON(std::istream& str, const Context& context)
{
  json j;
  str >> j;
  return DoubleCRT::readFromJSON(wrap(j), context);
}

DoubleCRT DoubleCRT::readFromJSON(const JsonWrapper& j, const Context& context)
{
  DoubleCRT ret{context, IndexSet::emptySet()};
  ret.readJSON(j);
  return ret;
}

void DoubleCRT::readJSON(std::istream& str)
{
  json j;
  str >> j;
  return this->readJSON(wrap(j));
}

void DoubleCRT::readJSON(const JsonWrapper& jw)
{
  json j = unwrap(jw);

  const Context& context = this->context;
  long phim = context.getPhiM();

  IndexSet set = IndexSet::readFromJSON(wrap(j.at("set")));
  assertTrue(set <= (context.getSmallPrimes() | context.getSpecialPrimes() |
                     context.getCtxtPrimes()),
             "Stream does not contain subset of the context's primes");
  this->map.clear();
  this->map.insert(set); // fix the index set for the data

  std::vector<NTL::Vec<long>> map_cnt = j.at("map");

  std::size_t cnt = 0;
  for (long i : set) {
    this->map[i] = map_cnt[cnt++]; // read the actual data

    // verify that the data is valid
    assertEq(this->map[i].length(),
             phim,
             "Data not valid: d.map[i].length() != phim");
    for (long j : range(phim))
      assertInRange(
          this->map[i][j],
          0l,
          context.ithPrime(i),
          "this->map[i][j] invalid: must be between 0 and context.ithPrime(i)");
  }
}

} // namespace helib


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_RSHR_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_RSHR_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

 /*!

    @ingroup group-bitwise
    Function object implementing rshr capabilities

    Returns the first entry shifted right or left
    by the absolute value of the second entry,
    according to its sign.

    @par semantic:
    For any given value @c x of type @c T,  @c n  of type @c I:

    @code
    T r = rshr(x, n);
    @endcode

    @see  rshl, shr, shl

  **/
  Value rshr(Value const & x, IntegerValue const &n);
} }
#endif

#include <boost/simd/function/scalar/rshr.hpp>
#include <boost/simd/function/simd/rshr.hpp>

#endif


/*!
@file
Forward declares `boost::hana::product`.

@copyright Louis Dionne 2013-2016
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_FWD_PRODUCT_HPP
#define BOOST_HANA_FWD_PRODUCT_HPP

#include <boost/hana/config.hpp>
#include <boost/hana/core/when.hpp>
#include <boost/hana/fwd/integral_constant.hpp>

BOOST_HANA_NAMESPACE_BEGIN
    //! Compute the product of the numbers of a structure.
    //! @ingroup group-Foldable
    //!
    //! More generally, `product` will take any foldable structure containing
    //! objects forming a Ring and reduce them using the Ring's binary
    //! operation. The initial state for folding is the identity of the
    //! Ring's operation. It is sometimes necessary to specify the Ring to
    //! use; this is possible by using `product<R>`. If no Ring is specified,
    //! the structure will use the Ring formed by the elements it contains
    //! (if it knows it), or `integral_constant_tag<int>` otherwise.
    //! Hence,
    //! @code
    //!     product<R>(xs) = fold_left(xs, one<R or inferred Ring>(), mult)
    //!     product<> = product<integral_constant_tag<int>>
    //! @endcode
    //!
    //! For numbers, this will just compute the product of the numbers in the
    //! `xs` structure.
    //!
    //! @note
    //! The elements of the structure are not actually required to be in the
    //! same Ring, but it must be possible to perform `mult` on any two
    //! adjacent elements of the structure, which requires each pair of
    //! adjacent element to at least have a common Ring embedding. The
    //! meaning of "adjacent" as used here is that two elements of the
    //! structure `x` and `y` are adjacent if and only if they are adjacent
    //! in the linearization of that structure, as documented by the Iterable
    //! concept.
    //!
    //! @note
    //! See the documentation for `sum` to understand why the Ring must
    //! sometimes be specified explicitly.
    //!
    //!
    //! Example
    //! -------
    //! @include example/product.cpp
#ifdef BOOST_HANA_DOXYGEN_INVOKED
    constexpr auto product = see documentation;
#else
    template <typename T, typename = void>
    struct product_impl : product_impl<T, when<true>> { };

    template <typename R>
    struct product_t;

    template <typename R = integral_constant_tag<int>>
    constexpr product_t<R> product{};
#endif
BOOST_HANA_NAMESPACE_END

#endif // !BOOST_HANA_FWD_PRODUCT_HPP


#include <iostream>
#include <cmath>
#include <thread>
#include <chrono>

#include <boost/asio/thread_pool.hpp>
#include <boost/asio/post.hpp>

using namespace std;
using namespace chrono;

// float seconds
using f_seconds = duration<float, seconds::period>;

void func(char label)
{
    double x = 12345.6789;
    for (int i = 0; i < 10000; i++)
    {
        for (int j = 0; j < 10000; j++)
        {
            x = sqrt(x);
            x = x + 0.000000001;
            x = pow(x, 2);
        }
    }
    cout << label << ": " << x << endl;
}

int main()
{
    // начинаем считать время
    auto start = high_resolution_clock::now();

    // создаем пул потоков
    boost::asio::thread_pool pool(9);

    // используем лямбда-функцию для передачи параметра для функции
    // вместо std::bind или функционального объекта
    boost::asio::post(pool, [](){ func('A'); });
    boost::asio::post(pool, [](){ func('B'); });
    boost::asio::post(pool, [](){ func('C'); });
    boost::asio::post(pool, [](){ func('D'); });
    boost::asio::post(pool, [](){ func('E'); });
    boost::asio::post(pool, [](){ func('F'); });
    boost::asio::post(pool, [](){ func('G'); });
    boost::asio::post(pool, [](){ func('H'); });
    boost::asio::post(pool, [](){ func('I'); });
    // ожидаем все потоки в пуле
    pool.join();

    // заканчиваем считать время
    auto end = high_resolution_clock::now();
    cout << duration_cast<f_seconds>(end-start).count() << endl;

    system("pause");
    return 0;
}


//
// Copyright 2005-2007 Adobe Systems Incorporated
//
// Distributed under the Boost Software License, Version 1.0
// See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt
//
#ifndef BOOST_GIL_EXTENSION_NUMERIC_SAMPLER_HPP
#define BOOST_GIL_EXTENSION_NUMERIC_SAMPLER_HPP

#include <boost/gil/extension/dynamic_image/dynamic_image_all.hpp>
#include <boost/gil/extension/numeric/pixel_numeric_operations.hpp>

namespace boost {
namespace gil {

// Nearest-neighbor and bilinear image samplers.
// NOTE: The code is for example use only. It is not optimized for performance

///////////////////////////////////////////////////////////////////////////
////
////     resample_pixels: set each pixel in the destination view as the result
///of a sampling function over the transformed coordinates of the source view
////
///////////////////////////////////////////////////////////////////////////
/*
template <typename Sampler>
concept SamplerConcept {
    template <typename DstP,      // Models PixelConcept
              typename SrcView,    // Models RandomAccessNDImageViewConcept
              typename S_COORDS>  // Models PointNDConcept, where
S_COORDS::num_dimensions == SrcView::num_dimensions bool sample(const Sampler&
s, const SrcView& src, const S_COORDS& p, DstP result);
};
*/

/// \brief A sampler that sets the destination pixel to the closest one in the
/// source. If outside the bounds, it doesn't change the destination \ingroup
/// ImageAlgorithms
struct nearest_neighbor_sampler {};

template <typename DstP, typename SrcView, typename F>
bool sample(nearest_neighbor_sampler, SrcView const &src, point<F> const &p,
            DstP &result) {
  typename SrcView::point_t center(iround(p));
  if (center.x >= 0 && center.y >= 0 && center.x < src.width() &&
      center.y < src.height()) {
    result = src(center.x, center.y);
    return true;
  }
  return false;
}

struct cast_channel_fn {
  template <typename SrcChannel, typename DstChannel>
  void operator()(const SrcChannel &src, DstChannel &dst) {
    using dst_value_t = typename channel_traits<DstChannel>::value_type;
    dst = dst_value_t(src);
  }
};

template <typename SrcPixel, typename DstPixel>
void cast_pixel(const SrcPixel &src, DstPixel &dst) {
  static_for_each(src, dst, cast_channel_fn());
}

namespace detail {

template <typename Weight> struct add_dst_mul_src_channel {
  Weight _w;
  add_dst_mul_src_channel(Weight w) : _w(w) {}

  template <typename SrcChannel, typename DstChannel>
  void operator()(const SrcChannel &src, DstChannel &dst) const {
    dst += DstChannel(src * _w);
  }
};

// dst += DST_TYPE(src * w)
template <typename SrcP, typename Weight, typename DstP>
struct add_dst_mul_src {
  void operator()(const SrcP &src, Weight weight, DstP &dst) const {
    static_for_each(src, dst, add_dst_mul_src_channel<Weight>(weight));
    //        pixel_assigns_t<DstP,DstP&>()(
    //            pixel_plus_t<DstP,DstP,DstP>()(
    //                pixel_multiplies_scalar_t<SrcP,Weight,DstP>()(src,weight),
    //                dst),
    //            dst);
  }
};
} // namespace detail

/// \brief A sampler that sets the destination pixel as the bilinear
/// interpolation of the four closest pixels from the source. If outside the
/// bounds, it doesn't change the destination \ingroup ImageAlgorithms
struct bilinear_sampler {};

template <typename DstP, typename SrcView, typename F>
bool sample(bilinear_sampler, SrcView const &src, point<F> const &p,
            DstP &result) {
  using SrcP = typename SrcView::value_type;
  point_t p0(ifloor(p.x),
             ifloor(p.y)); // the closest integer coordinate top left from p
  point<F> frac(p.x - p0.x, p.y - p0.y);

  if (p0.x < -1 || p0.y < -1 || p0.x >= src.width() || p0.y >= src.height()) {
    return false;
  }

  pixel<F, devicen_layout_t<num_channels<SrcView>::value>> mp(0); // suboptimal
  typename SrcView::xy_locator loc = src.xy_at(p0.x, p0.y);

  if (p0.x == -1) {
    if (p0.y == -1) {
      // the top-left corner pixel
      ++loc.y();
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], 1, mp);
    } else if (p0.y + 1 < src.height()) {
      // on the first column, but not the top-left nor bottom-left corner pixel
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], (1 - frac.y), mp);
      ++loc.y();
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], frac.y, mp);
    } else {
      // the bottom-left corner pixel
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], 1, mp);
    }
  } else if (p0.x + 1 < src.width()) {
    if (p0.y == -1) {
      // on the first row, but not the top-left nor top-right corner pixel
      ++loc.y();
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, (1 - frac.x), mp);
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], frac.x, mp);
    } else if (p0.y + 1 < src.height()) {
      // most common case - inside the image, not on the frist nor last
      // row/column
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, (1 - frac.x) * (1 - frac.y), mp);
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], frac.x * (1 - frac.y), mp);
      ++loc.y();
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, (1 - frac.x) * frac.y, mp);
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], frac.x * frac.y, mp);
    } else {
      // on the last row, but not the bottom-left nor bottom-right corner pixel
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, (1 - frac.x), mp);
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          loc.x()[1], frac.x, mp);
    }
  } else {
    if (p0.y == -1) {
      // the top-right corner pixel
      ++loc.y();
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, 1, mp);
    } else if (p0.y + 1 < src.height()) {
      // on the last column, but not the top-right nor bottom-right corner pixel
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, (1 - frac.y), mp);
      ++loc.y();
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, frac.y, mp);
    } else {
      // the bottom-right corner pixel
      detail::add_dst_mul_src<
          SrcP, F, pixel<F, devicen_layout_t<num_channels<SrcView>::value>>>()(
          *loc, 1, mp);
    }
  }

  // Convert from floating point average value to the source type
  SrcP src_result;
  cast_pixel(mp, src_result);

  color_convert(src_result, result);
  return true;
}

} // namespace gil
} // namespace boost

#endif


#include "NRGclasses.hpp"
#include "NRGfunctions.hpp"

#include <iostream>
#include <vector>
#include <math.h>

#include <boost/timer.hpp>

#include "OneChQS.hpp"

int OneChQS_DiagHN( CNRGmatrix Qm1fNQ,
		    CNRGbasisarray Abasis, CNRGbasisarray SingleSite,
		    CNRGarray &Aeig,
		    double eps_N, double chi_N, double Lambda,bool display){

  int NstOld=Aeig.dEn.size();

  double prefactor,auxEl;

  CNRGmatrix HN(Abasis);

  double MyZero=1.0e-15;

  // Check time
  boost::timer t;

  // Setup HN

  // NEW: Special storage

  HN.UpperTriangular=true;

  int icount=0;

  for (int ii=0;ii<Abasis.NumBlocks();ii++)
    {
      double Qi=Abasis.GetQNumber(ii,0);
      double Si=Abasis.GetQNumber(ii,1);

      int sizebl=Abasis.GetBlockSize(ii);

      HN.MatBlockMap.push_back(ii);
      HN.MatBlockMap.push_back(ii);
      HN.MatBlockBegEnd.push_back(icount);
      // New
      if (HN.UpperTriangular)
 	icount+=(sizebl*(sizebl+1))/2;
      else
	icount+=sizebl*sizebl;

      HN.MatBlockBegEnd.push_back(icount-1);

      //cout << "  Setting up HN Block : " << ii ;
      //cout << "  size: " << Abasis.GetBlockSize(ii) << endl;

      int ibl=0;
      // Start timer
      t.restart();
      for (int ist=Abasis.GetBlockLimit(ii,0);
	       ist<=Abasis.GetBlockLimit(ii,1);ist++)
	{
	  int type_i = Abasis.iType[ist];

	  int jbl=0;
// 	  for (int jst=Abasis.GetBlockLimit(ii,0);
// 	           jst<=Abasis.GetBlockLimit(ii,1);jst++)
	  int j0;
	  // NEW THING! only calculates half of matrix els!
	  if (HN.UpperTriangular) j0=ist;
	  else j0=Abasis.GetBlockLimit(ii,0);
	  for (int jst=j0;
	           jst<=Abasis.GetBlockLimit(ii,1);jst++)
	    {
	      int type_j = Abasis.iType[jst];
	      int rold=ij2r(NstOld,Abasis.StCameFrom[ist],
			    Abasis.StCameFrom[jst]);

 	      //cout << "  Limit in j : " << Abasis.BlockBegEnd[ii+1];
 	      //cout << "  ist : " << ist << "  jst : " << jst;
 	      //cout << "  type i : " << type_i << " type j : " << type_j <<endl;

	      if (ist==jst)
		{
		  // Diagonal terms
		  auxEl=sqrt(Lambda)*Abasis.dEn[ist];
		  HN.MatEl.push_back(auxEl);
		  //cout << " H( = " << ist <<  "," << jst << ") ="
		  //     << auxEl << endl;

		}
	      else
		{
		  // Off-diagonal terms: watch out for the h.c. term
		  // Nov 07: New off-diag approach,
		  // valid for two-channel as well
		  auxEl=Qm1fNQ.GetMatEl(Abasis.StCameFrom[ist],
					Abasis.StCameFrom[jst]);
		  int typep=type_i;
		  int type=type_j;

      		  // if Zero, calculate <i|h.c|j>=<j|fd|i>
		  // DIAGONAL BLOCKS: Si=Sj
		  if (fabs(auxEl)<MyZero)
		    {
		      auxEl=Qm1fNQ.GetMatEl(Abasis.StCameFrom[jst],
					    Abasis.StCameFrom[ist]);
		      typep=type_j;
		      type=type_i;
		    }

		  // Find Qcfp, Scfp, Scf, Stildep, Stilde

		  // Qtildep, Qtilde

		  double Qtildep=SingleSite.GetQNumber(typep,0);
		  double Stildep=SingleSite.GetQNumber(typep,1);
		  double Sztildep=SingleSite.GetQNumber(typep,2);

		  double Qtilde=SingleSite.GetQNumber(type,0);
		  double Stilde=SingleSite.GetQNumber(type,1);
		  double Sztilde=SingleSite.GetQNumber(type,2);

		  // Watch out: fermionic sign: (one for fd_{N+1} f_{N} )
		  // if using fd_{N}f_{N+1}=-f_{N+1}fd_{N} put a minus sign in front

		  double FermiSign=1.0;
		  if (dEqual(Qtilde,0.0)) FermiSign=-1.0;

		  double Scfp=Si-Sztildep;
		  double Scf=Si-Sztilde;

		  // Round-off things
		  //Stilde=0.5*((int)(2.0*Stilde));

		  //cout << " fN-1 = " << auxEl << endl;
		  //cout << "Si = " << Si
		  //    << " typep = " << typep
		  //    << " type = " << type << endl;
 		  //cout << "Scfp = " << Scfp
 		  //     << " Scf = " << Scf << endl;
		  //
 		  //cout << "Stildep = " << Stildep
 		  //     << " Stilde = " << Stilde
		  //     << " FermiSign = " << FermiSign << endl;

		  double CoefMatEl=0.0;
		  double auxCG[]={0.0,0.0,0.0,0.0};
		  double siteqnumsp[]={Qtildep,Stildep,Sztildep};
		  double siteqnums[]={Qtilde,Stilde,Sztilde};
		  // Sum in Sztilde
		  for (Sztilde=-Stilde;Sztilde<=Stilde;Sztilde+=1.0)
		    {
		      // Sum in sigma: Note that Sztildep=Sztilde+sigma
		      siteqnums[2]=Sztilde;
		      int sitestate=SingleSite.GetBlockFromQNumbers(siteqnums);
		      for (double sigma=-0.5;sigma<=0.5;sigma+=1.0)
			{
			  siteqnumsp[2]=Sztilde+sigma;
			  // Test Find site state
			  int sitestatep=SingleSite.GetBlockFromQNumbers(siteqnumsp);

			  //cout << "Sz~ = " << Sztilde
			  //   << " sigma = " << sigma << endl;

			  auxCG[0]=CGordan(Scf,Si-Sztilde,
					   Stilde,Sztilde,Si,Si);
			  //cout << "CG1( " << Scf << "," << Si-Sztilde << ","
			  //              <<Stilde<< "," <<Sztilde<< ","
			  //              <<Si<< "," <<Si<< ") = "
			  //   << auxCG[0] << endl;
			  auxCG[1]=CGordan(Scfp,Si-Sztilde-sigma,
					   Stildep,Sztilde+sigma,Si,Si);
			  // cout << "CG2( " << Scfp << "," << Si-Sztilde-sigma << ","
			  //              <<Stildep<< "," <<Sztilde+sigma<< ","
			  //               <<Si<< "," <<Si<< ") = "
			  //   << auxCG[1] << endl;
			  //cout << "CG3( " << Scfp << "," << Si-Sztilde-sigma << ","
			  //     <<0.5<< "," <<sigma<< ","
			  //     <<Scf<< "," <<Si-Sztilde<< ") = ";
			  auxCG[2]=CGordan(Scfp,Si-Sztilde-sigma,0.5,sigma,Scf,Si-Sztilde);
			  //cout << auxCG[2] << endl;
			  //cout << "typep = "<< typep << "type = " << type << endl;
			  //cout << "sitestp = "<< sitestatep
			  //     << " sitest = " << sitestate << endl;

			  auxCG[3]=fd_table((int)(sigma/0.5),sitestatep,sitestate);
			  //cout << "<sitestp|f|sitest> = " << auxCG[3] << endl;
			  CoefMatEl+=auxCG[0]*auxCG[1]*auxCG[2]*auxCG[3];
			;
			}
		    }

		  //auxEl*=CoefMatEl;
		  auxEl*=CoefMatEl*FermiSign;
		  //cout << " auxEl = " << auxEl << endl;
		  auxEl*=chi_N;
		  HN.MatEl.push_back(auxEl);
		}
	      // end if ist==jst
	      jbl++;
	    }
	  // end loop in jst
	  ibl++;
	}
      // end loop in ist
      cout << " Block : " << ii
	   << " size: " << sizebl
	   <<"  Time for set-up: " << t.elapsed() << endl;
    }
  // end loop in blocks

   cout << "Num Blocks in HN     : " << HN.NumBlocks() << endl;

//   cout << "HN : " << endl;

//   int jj=0;
//   for (int ii=0;ii<HN.MatEl.size();ii++)
//      {
//        cout << "Block = " << HN.MatBlockMap[jj] << " " << HN.MatBlockMap[jj+1];
//        cout << "  i in block = " << HN.vec[ii].ibl
// 	    << "  j in block = " << HN.vec[ii].jbl;
//        cout << "  i = " << HN.vec[ii].ist << "  j = " << HN.vec[ii].jst;
//        cout << "  val = " << scientific << HN.MatEl[ii] << endl;
//        jj+=2;

//      }

  // Set FilterMap_BegEnd:
  // Takes tooo much time!
  //it will get rid of repetitions
  //in the block map and set MatBlockBegEnd

//    cout << "HN MatBlockMap : ";
//    for (int ii=0;ii<HN.MatBlockMap.size();ii++)
//      cout << HN.MatBlockMap[ii] << " ";
//    cout << endl;

   //HN.FilterMap_SetBegEnd();

//    cout << "Filtered HN MatBlockMap : ";
//    for (int ii=0;ii<HN.MatBlockMap.size();ii++)
//      cout << HN.MatBlockMap[ii] << " ";
//    cout << endl;
//    cout << "HN MatBlockBegEnd : ";
//    for (int ii=0;ii<HN.MatBlockBegEnd.size();ii++)
//      cout << HN.MatBlockBegEnd[ii] << " ";
//    cout << endl;

  // Some debugging:

   if (display){
     cout << "Basis and HN for Nshell = " << Abasis.Nshell << endl;
     for (int ibl=0;ibl<Abasis.NumBlocks();ibl++){
       Abasis.PrintBlockBasis(ibl);
       HN.PrintMatBlock(ibl,ibl);
     }
   }
   // end if display

  // Update Aeig

  cout << " Updating Aeig. " << endl;

  Aeig.ClearAll();

  // This works!
  Aeig=HN;

  Aeig.Nshell=Abasis.Nshell;
  // Diagonalize blocks
  Aeig.dEn.clear();
  Aeig.dEigVec.clear();
  cout << "HN eigenvalues for Nshell = " << Abasis.Nshell << endl;
  for (int ii=0;ii<HN.NumBlocks();ii++)
    {
      HN.DiagBlock(ii, Aeig.dEn, Aeig.dEigVec);
      if (display){Aeig.PrintBlock(ii);}
    }

  //Set min to zero
  Aeig.SetE0zero();

  return(0);
}

// 		  if ( ((type_i==1)&&(type_j==2))||
// 		       ((type_i==2)&&(type_j==1))||
// 		       ((type_i==1)&&(type_j==3))||
// 		       ((type_i==3)&&(type_j==1))||
// 		       ((type_i==2)&&(type_j==4))||
// 		       ((type_i==4)&&(type_j==2))||
// 		       ((type_i==3)&&(type_j==4))||
// 		       ((type_i==4)&&(type_j==3)) )
// 		    {
// 		      auxEl=OneChQS_prefactorHN(type_i,type_j,Si)*
// 		       ( Qm1fNQ.GetMatEl(Abasis.StCameFrom[ist],
// 					Abasis.StCameFrom[jst])+
// 			 Qm1fNQ.GetMatEl(Abasis.StCameFrom[jst],
// 					 Abasis.StCameFrom[ist]) );
// 		      auxEl*=chi_N;
// 		      HN.MatEl.push_back(auxEl);
// 		    }
// 		  else  HN.MatEl.push_back(0.0);


// Boost.Geometry

// Copyright (c) 2020, Oracle and/or its affiliates.

// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_EXPAND_SPHERICAL_HPP
#define BOOST_GEOMETRY_STRATEGIES_EXPAND_SPHERICAL_HPP

#include <type_traits>

#include <boost/geometry/strategy/spherical/expand_box.hpp>
#include <boost/geometry/strategy/spherical/expand_point.hpp>
#include <boost/geometry/strategy/spherical/expand_segment.hpp>

#include <boost/geometry/strategies/detail.hpp>
#include <boost/geometry/strategies/expand/services.hpp>

namespace boost { namespace geometry
{

namespace strategies { namespace expand
{

template
<
    typename CalculationType = void
>
class spherical : strategies::detail::spherical_base<void>
{
    using base_t = strategies::detail::spherical_base<void>;

public:
    template <typename Box, typename Geometry>
    static auto expand(Box const&, Geometry const&,
                       typename util::enable_if_point_t<Geometry> * = nullptr)
    {
        return strategy::expand::spherical_point();
    }

    template <typename Box, typename Geometry>
    static auto expand(Box const&, Geometry const&,
                       typename util::enable_if_box_t<Geometry> * = nullptr)
    {
        return strategy::expand::spherical_box();
    }

    template <typename Box, typename Geometry>
    static auto expand(Box const&, Geometry const&,
                       typename util::enable_if_segment_t<Geometry> * = nullptr)
    {
        return strategy::expand::spherical_segment<CalculationType>();
    }
};

namespace services
{

template <typename Box, typename Geometry>
struct default_strategy<Box, Geometry, spherical_tag>
{
    using type = strategies::expand::spherical<>;
};

template <typename Box, typename Geometry>
struct default_strategy<Box, Geometry, spherical_equatorial_tag>
{
    using type = strategies::expand::spherical<>;
};

template <typename Box, typename Geometry>
struct default_strategy<Box, Geometry, spherical_polar_tag>
{
    using type = strategies::expand::spherical<>;
};

template <>
struct strategy_converter<strategy::expand::spherical_point>
{
    static auto get(strategy::expand::spherical_point const& )
    {
        return strategies::expand::spherical<>();
    }
};

template <>
struct strategy_converter<strategy::expand::spherical_box>
{
    static auto get(strategy::expand::spherical_box const& )
    {
        return strategies::expand::spherical<>();
    }
};

template <typename CT>
struct strategy_converter<strategy::expand::spherical_segment<CT> >
{
    static auto get(strategy::expand::spherical_segment<CT> const&)
    {
        return strategies::expand::spherical<CT>();
    }
};

} // namespace services

}} // namespace strategies::envelope

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_EXPAND_SPHERICAL_HPP


// Boost.Geometry

// Copyright (c) 2021, Oracle and/or its affiliates.

// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle

// Licensed under the Boost Software License version 1.0.
// http://www.boost.org/users/license.html

#ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_CLOSEST_POINTS_CROSS_TRACK_HPP
#define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_CLOSEST_POINTS_CROSS_TRACK_HPP

#include <algorithm>
#include <type_traits>

#include <boost/config.hpp>
#include <boost/concept_check.hpp>

#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/core/access.hpp>
#include <boost/geometry/core/coordinate_promotion.hpp>
#include <boost/geometry/core/radian_access.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/formulas/spherical.hpp>

#include <boost/geometry/strategies/distance.hpp>
#include <boost/geometry/strategies/concepts/distance_concept.hpp>
#include <boost/geometry/strategies/spherical/distance_haversine.hpp>
#include <boost/geometry/strategies/spherical/distance_cross_track.hpp>
#include <boost/geometry/strategies/spherical/point_in_point.hpp>
#include <boost/geometry/strategies/spherical/intersection.hpp>

#include <boost/geometry/util/math.hpp>
#include <boost/geometry/util/select_calculation_type.hpp>

#ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
#  include <boost/geometry/io/dsv/write.hpp>
#endif

namespace boost { namespace geometry
{

namespace strategy { namespace closest_points
{

template
<
    typename CalculationType = void,
    typename Strategy = distance::comparable::haversine<double, CalculationType>
>
class cross_track
{
public:
    template <typename Point, typename PointOfSegment>
    struct calculation_type
        : promote_floating_point
          <
              typename select_calculation_type
                  <
                      Point,
                      PointOfSegment,
                      CalculationType
                  >::type
          >
    {};

    using radius_type = typename Strategy::radius_type;

    cross_track() = default;

    explicit inline cross_track(typename Strategy::radius_type const& r)
        : m_strategy(r)
    {}

    inline cross_track(Strategy const& s)
        : m_strategy(s)
    {}

    template <typename Point, typename PointOfSegment>
    inline auto apply(Point const& p,
                      PointOfSegment const& sp1,
                      PointOfSegment const& sp2) const
    {
        using CT = typename calculation_type<Point, PointOfSegment>::type;

        // http://williams.best.vwh.net/avform.htm#XTE
        CT d3 = m_strategy.apply(sp1, sp2);

        if (geometry::math::equals(d3, 0.0))
        {
            // "Degenerate" segment, return either d1 or d2
            return sp1;
        }

        CT d1 = m_strategy.apply(sp1, p);
        CT d2 = m_strategy.apply(sp2, p);

        auto d_crs_pair = distance::detail::compute_cross_track_pair<CT>::apply(
            p, sp1, sp2);

        // d1, d2, d3 are in principle not needed, only the sign matters
        CT projection1 = cos(d_crs_pair.first) * d1 / d3;
        CT projection2 = cos(d_crs_pair.second) * d2 / d3;

#ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
        std::cout << "Course " << dsv(sp1) << " to " << dsv(p) << " "
                  << crs_AD * geometry::math::r2d<CT>() << std::endl;
        std::cout << "Course " << dsv(sp1) << " to " << dsv(sp2) << " "
                  << crs_AB * geometry::math::r2d<CT>() << std::endl;
        std::cout << "Course " << dsv(sp2) << " to " << dsv(sp1) << " "
                  << crs_BA * geometry::math::r2d<CT>() << std::endl;
        std::cout << "Course " << dsv(sp2) << " to " << dsv(p) << " "
                  << crs_BD * geometry::math::r2d<CT>() << std::endl;
        std::cout << "Projection AD-AB " << projection1 << " : "
                  << d_crs1 * geometry::math::r2d<CT>() << std::endl;
        std::cout << "Projection BD-BA " << projection2 << " : "
                  << d_crs2 * geometry::math::r2d<CT>() << std::endl;
        std::cout << " d1: " << (d1 )
                  << " d2: " << (d2 )
                  << std::endl;
#endif

        if (projection1 > 0.0 && projection2 > 0.0)
        {
#ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
            CT XTD = radius() * geometry::math::abs( asin( sin( d1 ) * sin( d_crs1 ) ));

            std::cout << "Projection ON the segment" << std::endl;
            std::cout << "XTD: " << XTD
                      << " d1: " << (d1 * radius())
                      << " d2: " << (d2 * radius())
                      << std::endl;
#endif
            auto distance = distance::detail::compute_cross_track_distance::apply(
                d_crs_pair.first, d1);

            CT lon1 = geometry::get_as_radian<0>(sp1);
            CT lat1 = geometry::get_as_radian<1>(sp1);
            CT lon2 = geometry::get_as_radian<0>(sp2);
            CT lat2 = geometry::get_as_radian<1>(sp2);

            CT dist = CT(2) * asin(math::sqrt(distance)) * m_strategy.radius();
            CT dist_d1 = CT(2) * asin(math::sqrt(d1)) * m_strategy.radius();

            // Note: this is similar to spherical computation in geographic
            // point_segment_distance formula
            CT earth_radius = m_strategy.radius();
            CT cos_frac = cos(dist_d1 / earth_radius) / cos(dist / earth_radius);
            CT s14_sph = cos_frac >= 1
                ? CT(0) : cos_frac <= -1 ? math::pi<CT>() * earth_radius
                                         : acos(cos_frac) * earth_radius;

            CT a12 = geometry::formula::spherical_azimuth<>(lon1, lat1, lon2, lat2);
            auto res_direct = geometry::formula::spherical_direct
                <
                    true,
                    false
                >(lon1, lat1, s14_sph, a12, srs::sphere<CT>(earth_radius));

            model::point
                <
                    CT,
                    dimension<PointOfSegment>::value,
                    typename coordinate_system<PointOfSegment>::type
                > cp;

            geometry::set_from_radian<0>(cp, res_direct.lon2);
            geometry::set_from_radian<1>(cp, res_direct.lat2);

            return cp;
        }
        else
        {
#ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
            std::cout << "Projection OUTSIDE the segment" << std::endl;
#endif
            return d1 < d2 ? sp1 : sp2;
        }
    }

    template <typename T1, typename T2>
    inline radius_type vertical_or_meridian(T1 lat1, T2 lat2) const
    {
        return m_strategy.radius() * (lat1 - lat2);
    }

    inline typename Strategy::radius_type radius() const
    { return m_strategy.radius(); }

private :
    Strategy m_strategy;
};

}} // namespace strategy::closest_points

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_CLOSEST_POINTS_CROSS_TRACK_HPP


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

#include <cmath>
#include "DGaussSampler.h"
#include "GGH15.h"
#include "obfus.h"
#include "CRTmatrix.h"
#include <NTL/mat_ZZ.h>
#include <NTL/mat_lzz_p.h>
#include <NTL/BasicThreadPool.h>

NTL_CLIENT

//#define PRINTDOT
#ifdef PRINTDOT
#define printDot cerr << "." << std::flush
#else
#define printDot
#endif

//#define DEBUG
//#define DEBUGPRINT

static void prepare4encoding(const NTL::Mat< NTL::Mat<long> >& trans,
			     Mat<GGH15ptxt>& mainGGH15ptxt,
			     Mat<GGH15ptxt>& dummyGGH15ptxt,
			     CRTmatrix& An, TDMatrixParams* params,
			     long nThreads);
static void encodeBranch(const std::string& dirName,
			 const Mat<GGH15ptxt>& branch,
			 CRTmatrix& An, TDMatrixParams* params,
			 SynchronizedPipe<GGH15node*>* inPipe=NULL,
			 SynchronizedPipe<encPair>* outPipe=NULL);
static void readNodesPipe(SynchronizedPipe<GGH15node*>& pipe,
			  const Mat<GGH15ptxt>& mainBranch,
			  const Mat<GGH15ptxt>& dummyBranch,
			  TDMatrixParams* params);
static void writeEncodingPipe(SynchronizedPipe<encPair>& pipe);
static GGH15node* getNode(long idx, TDMatrixParams* params);
static void evalBranch(GGH15encoding& mainPathEnc,
		       const NTL::Vec<int>& indexes,
		       const NTL::Vec<long>& symbolString,
		       TDMatrixParams* params);

// Obfuscate the given NFA and store the result in the directory 'name'
void initObfuscateNFA(const std::string& dirName,
                      const NTL::Mat< NTL::Mat<long> >& trans, long sec,
		      TDMatrixParams* params, bool bPipe, long threads)
{
    FHE_TIMER_START;

    long L = trans.NumRows(); // BP-length, # of edges
    if (L==0) return; // sanity check, nothing to do

    // compute the parameters if they are not provided by caller
    TDMatrixParams p;
    if (params==NULL)
    {
        long e = 3;
        long dim = trans[0][0].NumRows();

#ifdef DEBUG //small parameters in DEBUG mode
        long k = L;
        p.init(dim+2, k, e);

#else // "real" parameters in non-DEBUG mode
        GGH15basicParams bp(COMP_GGH15PRMS, dim, L, sec, e);
        p.init(bp.n, bp.k, bp.e, bp.m);
#endif // DEBUG

        params = &p; // pointer points to local p

	cout << "m=" << p.m << ", kFactors=" << p.kFactors
	 << ", n=" << p.n << ", e=" << p.e
	 << ", sigmaX=" << p.sigmaX << endl;
    }
    if (bPipe) {
      pipedInit(dirName, *params, 2*L, threads);
    } else {
      SetNumThreads(threads);
      initAllNodes(dirName, *params, 2*L);
    }

    // do actual obfuscation
    obfuscateNFASavedNodes(dirName, trans, params, bPipe, threads);
}

// This is called after initAllNodes, so all the nodes are already
// stored in dirName
void obfuscateNFASavedNodes(const std::string& dirName,
                            const NTL::Mat< NTL::Mat<long> >& trans,
			    TDMatrixParams* params, bool pipes, long nThreads)
{
    FHE_TIMER_START;

    cout << "obfuscating\n" << std::flush;

    char cwdOrig[1024];
    if (!getcwd(cwdOrig, sizeof(cwdOrig)))
      throw std::logic_error("Cannot get current directory");

    long ret = chdir(dirName.c_str());  // move into the new directory
    if (ret != 0) {
      cout << "   original directory was "<<cwdOrig<<endl;
      throw std::logic_error("Cannot change directory");
    }

    // Encode each tranition matrix via two encoded randomized matrices

    Mat<GGH15ptxt> mainGGH15ptxt, dummyGGH15ptxt; // main & dummy branches
    CRTmatrix An;             // The A_n matrix (actually n-by-1 "matrix")

    SetNumThreads(3); // For prepare we need 2, for encode we may need 3

    // First randomize the transition matrices and read A_n
    prepare4encoding(trans,mainGGH15ptxt,dummyGGH15ptxt,An,params,nThreads);

    //Next encode the randomized matrices, one branch at a time

    // FIXME: The piped implemtation below is fragile, relying on the reader
    // to read the nodes in exactly the right order that the worker expect
    // them. A more robust implementation would have the worker ask for a
    // node and then the reader read that node, but it is a lot more work
    // to implement this more robust solution (with pre-fetching).

    if (pipes) {
      SynchronizedPipe<GGH15node*> inPipe;
      SynchronizedPipe<encPair> outPipe;
      // "fork" three threads, reader, worker, and writer
      EXEC_INDEX(3, index)
      switch (index) {

      case 0: // The "reader thread, no multi-threading here
	readNodesPipe(inPipe,mainGGH15ptxt,dummyGGH15ptxt,params);
	inPipe.end();
	break;

      case 1: // The "worker" thread, can be multi-threaded
	SetNumThreads((nThreads>2)? (nThreads-2) : 1);
	encodeBranch(dirName, mainGGH15ptxt, An, params, &inPipe, &outPipe);
	encodeBranch(dirName, dummyGGH15ptxt, An, params,&inPipe, &outPipe);
	outPipe.end();
	{GGH15node *tmp; inPipe.receive(tmp);} // clear end-of-pipe signal
	break;

      case 2: // The "writer thread
	writeEncodingPipe(outPipe);
	break;
      }
      EXEC_INDEX_END
    }
    else { // non-piped implementation
      SetNumThreads(nThreads);
      encodeBranch(dirName, mainGGH15ptxt, An, params);
      encodeBranch(dirName, dummyGGH15ptxt, An, params);
    }
    // Restore working directory
    if (!chdir(cwdOrig)) return; // ignore this value
}

//////
//evalNFA gets a bitstring that defines the path
//////
bool evalNFA(const std::string& dirName, const Vec<long>& symbolString,
             TDMatrixParams* params, long nThreads)
{
    FHE_TIMER_START;
    long nEdges = symbolString.length();
    assert(nEdges>0); // sanity check

    char cwd[1024];
    if (!getcwd(cwd, sizeof(cwd))) // store cwd before switching
      throw std::logic_error("Cannot get current directory");
    if (chdir(dirName.c_str()) != 0)
      throw std::logic_error("Cannot change directory " + dirName);

    // read the parameters from file, if not given
    TDMatrixParams p;
    if (params==NULL)
    {
        params = &p;
        FILE* handle = fopen("params.dat", "rb");
        params->readFromFile(handle);
        fclose(handle);
    }

    // The "main" path is 0,1,3,5,...,2*nSymbols-1,
    // the "dummy" path is 0,2,4,..., 2*nSymbols-2, 2*nSymbols-1.
    // First and last nodes are the same on both paths.

    // Along each path we read the matrices that are determined by the
    // symbols in symbolString. For example if symbolString is (0,2,1,...)
    // then on the main path we read 0_1_0, 1_3_2, 3_5_1,...
    // and on the dummy path we read 0_2_0, 2_4_2, 4_6_1,...

    // Then we multiply the matrices along each path (getting two matrices
    // relative to 0-to-(2*nSymbols-1)), subtract these matrices off of each
    // other, and test if the result is a zero-encoding.

    NTL::Vec<int> realIdx(INIT_SIZE, nEdges+1);
    NTL::Vec<int> dummyIdx(INIT_SIZE, nEdges+1);
    for (long i = nEdges-1; i>=0; --i) {
      realIdx[i+1] = 2*i+1;
      dummyIdx[i+1] = (i==nEdges-1)? (2*i +1) : (2*i +2);
    }
    realIdx[0] = dummyIdx[0] = 0;

    // Multiply all matrices, from right to left. Note that rightmost
    // matrices are m-by-1, middle matrices are m-by-m, and leftmost
    // matrices are 7-by-m (?), so right-to-left product will always
    // have a products of m-by-m X m-by-1.

    GGH15encoding mainPathEnc;
    GGH15encoding dummyPathEnc;

    // No. of threads to allocate each branch
    if (nThreads>2) nThreads /= 2;
    else nThreads = 1;

    SetNumThreads(3);
    EXEC_INDEX(3, index)
    switch (index) {
    case 0: break; // Cannot multi-thread theard 0
    case 1:
      SetNumThreads(nThreads);
      evalBranch(mainPathEnc, realIdx, symbolString, params);
      break;
    case 2:
      SetNumThreads(nThreads);
      evalBranch(dummyPathEnc, dummyIdx, symbolString, params);
      break;
    }
    EXEC_INDEX_END

    // subtract and check for zero encoding
    mainPathEnc -= dummyPathEnc;

    bool success = (mainPathEnc.fromNode()==0)
      && (mainPathEnc.toNode()==2*nEdges-1)
      && mainPathEnc.getData().isSmall();

    if (chdir(cwd) != 0) { // restore working directory
      cout << "evalNFA: attempted return to "<<cwd<<endl;
      throw std::logic_error("Cannot change directory");
    }
    return success;
}

//pipedInit forks two threads, one "writer" thread and the "producer" thread, which can be further multi-threaded
bool pipedInit(const std::string& dirName, TDMatrixParams& p, long n,
	       long nThreads)
{

 FHE_TIMER_START;

  bool bsuccess=false;
  SynchronizedPipe<nodePair> pipe;

  // "fork" two thread
  SetNumThreads(2);
  EXEC_INDEX(2, index)
    switch (index) {

    case 0: // The "writer thread, no multi-threading here
      writeNodesToFile(pipe);
      break;

      // NOTE: Starting with NTL v9.10, the current thread
      // always gets assigned index == 0. This can be convenient to know:
      // the current thread's thread pool (which is thread_local) is already
      // in use; however, the other threads' thread pools are not in use.
      // Thus, if we want, the functions Process1, Process2, Process3 (below)
      // could each independently call SetNumThreads to work with their own
      // thread pools.

    case 1:
      SetNumThreads((nThreads>1)? (nThreads-1): 1);
      bsuccess=initAllNodes(dirName, p, n, &pipe);
      pipe.end();
      // The "producer" thread, can be multi-threaded
      break;
    }
  EXEC_INDEX_END

    return bsuccess;
}

// p1 includes the transition matrix and a few extra random dimensions,
// p2 includes the same random dimensions as p1 but the identity or
// part of it instead of the transition matrix
void randomizeTransitions(GGH15ptxt& p1, GGH15ptxt& p2,
                          const NTL::Mat<long>& transition,
                          long i, long j, long nSteps, long dim)
{
    FHE_TIMER_START;
    // set the to,from indexes and the tags
    p1.setFrom((i==0)? (2*i) : (2*i -1));
    p1.setTo(2*i +1);

    p2.setFrom(2*i);
    p2.setTo((i==nSteps-1)? (2*i +1) : (2*i +2));

    std::string tag = ToString(j);
    p1.setTag(tag);
    p2.setTag(tag);

    // Set the data part of the plaintext
    long extraDims = dim - transition.NumRows();  // how many extra dimensions
    mat_l& p1Data = p1.getData();
    mat_l& p2Data = p2.getData();
    p1Data.SetDims(transition.NumRows()+extraDims,
                   transition.NumCols()+extraDims);
    p2Data.SetDims(transition.NumRows()+extraDims,
                   transition.NumCols()+extraDims);
    clear(p1Data);
    clear(p2Data);

    // Copy the transition matrix to p1
    for (long ii=0; ii<transition.NumRows(); ii++)
        for (long jj=0; jj<transition.NumCols(); jj++)
            p1Data[ii][jj] = (long) transition[ii][jj];

    // Set p2 as the identity or part of it
    long smallDim = min(transition.NumRows(), transition.NumCols());
    if (i < nSteps-1) // all but the last step
        for (long ii=0; ii<smallDim/2; ii++) p2Data[ii][ii] = 1;
    if (i > 0)      // all but the first step
        for (long ii=smallDim/2; ii<smallDim; ii++) p2Data[ii][ii] = 1;

    // Choose a small random matrix and store in the extra dimensions of p1,p2
    mat_l rand;
    setSmall(rand, extraDims, extraDims, /*sigma=*/256);
    for (long ii=0; ii<extraDims; ii++) for (long jj=0; jj<extraDims; jj++)
        {
            long row = ii + transition.NumRows();
            long col = jj + transition.NumCols();
            p1Data[row][col] = p2Data[row][col] = rand[ii][jj];
        }
}

/* Function creates a new sub-directory and moves into it. it then creates
 * a binary file "params.dat", opens it and writes the values of p into it.
 * At the end, it moves back to the initial directory.
 */
long saveParamstoNewDir(const std::string& dirName, TDMatrixParams& p)
{
    FHE_TIMER_START;
#if defined(_WIN32) // Windows mkdir takes only one parameter
    int ret = mkdir(dirName.c_str());
#else               // *nix mkdir takes two parameters
    int ret = mkdir(dirName.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
#endif

    char cwdOrig[1024];
    if (!getcwd(cwdOrig, sizeof(cwdOrig)))
      throw std::logic_error("Cannot get current directory");
    ret = chdir(dirName.c_str());  // move into the new directory
    if (ret != 0) {
      cout << "saveParamstoNewDir: attempted chdir to "<<dirName<<endl;
      throw std::logic_error("Cannot change directory");
    }
    FILE* handle = fopen("params.dat", "wb"); // binary file, open for writing
    long count = p.writeToFile(handle);        // write parameters to file
    fclose(handle);

    ret = chdir(cwdOrig);
    if (ret != 0) {
      cout << "saveParamstoNewDir: attempted return to "<<cwdOrig<<endl;
      throw std::logic_error("Cannot change directory");
    }
    return count;
}

//create a random transitions file - for debug purposes,
long makeRandomBP(std::string dirName, std::string name, long dim, long sig, long L)
{
    Mat< Mat<long> > trans(INIT_SIZE, L, sig);
    for (long i=0; i<L; i++) for (long iSig=0; iSig<sig; iSig++) {
	Mat<long>& M = trans[i][iSig];
	M.SetDims(dim,dim);
	if (NTL::RandomBnd(L)==0) clear(M);
	else
	  for (long row=0; row<dim; row++) for (long col=0; col<dim; col++) {
	      M[row][col] = NTL::RandomBnd(2);
	    }
      }

    // Write the transitions matrices to file

    std::string filename = dirName + "/" + name + "_dim" + ToString(dim)
                           + "_sig" + ToString(sig) + "_L" + ToString(L)
                           + ".txt";
    std::fstream fs;
    fs.open(filename.c_str(), std::ios::out); // E.g., "BPs/P_dim2_sig2_L3.txt"
    if (!fs.is_open()) {
        char cwd[1024];
        if (getcwd(cwd, sizeof(cwd)))
          throw std::logic_error("Cannot get current directory");
        std::cout << "Cannot open input file "<< filename
		  << ", working directory=" << cwd << endl;
        exit(0);
    }
    fs << trans << endl;
    fs.close();

    return 0;
}

// Read the A_n matrix from disk and randomize transition matrices
static void prepare4encoding(const NTL::Mat< NTL::Mat<long> >& trans,
			     Mat<GGH15ptxt>& mainGGH15ptxt,
			     Mat<GGH15ptxt>& dummyGGH15ptxt,
			     CRTmatrix& An, TDMatrixParams* params,
			     long nThreads)
{
    long L = trans.NumRows(); // # of steps
    long nLoops = L*trans.NumCols();
    FILE* handle;

    mainGGH15ptxt.SetDims(L, trans.NumCols()); //main branch
    dummyGGH15ptxt.SetDims(L,trans.NumCols()); //dummy branch

    // "fork" two threads, one for reading An, one for randomizing matrices
    EXEC_INDEX(2, index)
    switch (index) {

    case 0: //read An
      handle = fopen("nAn.dat", "rb");
      if (handle == 0) NTL::Error("Cannot read An");
      else {
	long nn; // nn not used for anything
        fread(&nn, sizeof(nn),1, handle);
	An.readFromFile(handle, params);
	fclose(handle);
      }
      break;

    case 1: // randomize transition matrices, multi-threaded
      SetNumThreads((nThreads>1)? (nThreads-1) : 1);
      EXEC_RANGE(nLoops, first, last);
      for (long iLoop = first; iLoop < last; iLoop++) {

        // represent each transition matrix by two randomized matrices,
        // one on the main path and the other on the dummy path

        long j = iLoop % trans.NumCols();   //i = step, j = trans.col
        long i = (iLoop - j) / trans.NumCols();

        GGH15ptxt& p1=mainGGH15ptxt[i][j];
        GGH15ptxt& p2=dummyGGH15ptxt[i][j];

	//we chose the from and to in the following manner:
        p1.setFrom((i==0)? (2*i) : (2*i -1));
        p1.setTo(2*i +1);
        p2.setFrom(2*i);
        p2.setTo((i==L-1)? (2*i +1) : (2*i +2));
        randomizeTransitions(p1, p2, trans[i][j], i, j, L, params->n);
      } //create all ptxt matrices
      EXEC_RANGE_END
    }
    EXEC_INDEX_END
}

static void encodeBranch(const std::string& dirName,
			 const Mat<GGH15ptxt>& branch,
			 CRTmatrix& An, TDMatrixParams* params,
			 SynchronizedPipe<GGH15node*>* inPipe,
			 SynchronizedPipe<encPair>* outPipe)
{
    assert(branch.NumRows()>0 && branch.NumCols()>0);

    GGH15node *toNode=NULL, *fromNode=NULL;
    TaggedCRTmatrix mat1;
    GGH15encoding* enc;

    long lastNode = branch[branch.NumRows()-1][0].getTo();
    for (long i=0; i<branch.NumRows(); i++) {
      long from = branch[i][0].getFrom();
      long to = branch[i][0].getTo();

      // Read from disk the next node that's needed for this step
      if (to >= lastNode) toNode =NULL; // no toNode for last-step encoding
      else if (inPipe != NULL)
        assert(inPipe->receive(toNode));// get node from pipe, failure forbidden
      else
        toNode = getNode(to, params);   // read it yourself

      // Encode all matrices wrt from -> to, one per alphabet symbol
      for (long j=0; j<branch.NumCols(); j++) {
	enc = new(GGH15encoding);
	encodeMatrix(dirName, *enc, branch[i][j].getData(), from, to,
                     fromNode, toNode, An, &mat1, params, lastNode+1);
	// When from==0, encodeMatrix does not use the pointer fromNode

	// Write the encoded matrices to disk
	std::string FileName = ToString(from)+"_"+ToString(to)+"_"
                               + branch[i][j].getTag()+".dat";
	FILE* handle = fopen((const char*)FileName.c_str(), "wb");
	if (handle==0) NTL::Error("Cannot write encoding to disk");

	if (outPipe!=NULL) { // Send matrix over pipe to be written
	  encPair ePair(handle,enc);
	  outPipe->send(ePair);
	} else {             // write it yourself
	  enc->writeToFile(handle);
	  fclose(handle);
	  delete enc;
	}
      }
      if (fromNode!=NULL) delete fromNode;
      fromNode = toNode;
    }
}

static void readNodesPipe(SynchronizedPipe<GGH15node*>& pipe,
			  const Mat<GGH15ptxt>& mainBranch,
			  const Mat<GGH15ptxt>& dummyBranch,
			  TDMatrixParams* params)
{
  GGH15node *node;
  // First read the nodes on the main branch in order
  long lastNode = mainBranch[mainBranch.NumRows()-1][0].getTo();
  for (long i=0; i<mainBranch.NumRows(); i++) {
    long to = mainBranch[i][0].getTo();
    if (to >= lastNode) break;
    node = getNode(to, params);
    pipe.send(node);
  }
  // Next read the nodes on the dummy branch in order
  lastNode = dummyBranch[mainBranch.NumRows()-1][0].getTo();
  for (long i=0; i<dummyBranch.NumRows(); i++) {
    long to = dummyBranch[i][0].getTo();
    if (to >= lastNode) break;
    node = getNode(to, params);
    pipe.send(node);
  }
}

static void writeEncodingPipe(SynchronizedPipe<encPair>& pipe)
{
  GGH15encoding* enc;
  FILE* handle;
  encPair ePair;

  while (true) {
    if (!pipe.receive(ePair)) break;
    handle = ePair.a;
    enc = ePair.b;
    enc->writeToFile(handle);
    delete(enc);
    fclose(handle);
  }
}

static GGH15node* getNode(long idx, TDMatrixParams* params)
{
  std::string fileName = "node"+ToString(idx)+ ".dat";
  FILE* handle = fopen(fileName.c_str(), "rb");
  if (handle==0) NTL::Error("Cannot open node file on disk");
  GGH15node* node = new GGH15node();
  node->readFromFile(handle, params);
  fclose(handle);
  return node;
}

static void evalBranch(GGH15encoding& mainPathEnc,
		       const NTL::Vec<int>& indexes,
		       const NTL::Vec<long>& symbolString,
		       TDMatrixParams* params)
{
  long nEdges = indexes.length()-1;
  for (long i = nEdges-1; i>=0; --i) {
    GGH15encoding cc;

    long from1 = indexes[i];
    long to1   = indexes[i+1];
    std::string fName1 = ToString(from1) + "_"
      + ToString(to1) + "_" + ToString(symbolString[i]) + ".dat";

    // read encodings from files and multiply into the path encodings

    FILE* handle = fopen(fName1.c_str(), "rb");
    if (handle == NULL) // error
      throw std::logic_error("Cannot open file " + fName1);

    if (i == nEdges-1) // read directly to mainPathEnc
      mainPathEnc.readFromFile(handle, *params);
    else {       // multiply into mainPathEnc
      cc.readFromFile(handle, *params);
      mainPathEnc.leftMultBy(cc);
    }
    fclose(handle);
  }
}


#include <iostream>
#include <string>
#include <armadillo>
#include <time.h>
#include "ga.h"

using namespace std;
using namespace arma;

//------------------------------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------------------------------
int main(){
  srand(time(NULL));

mat DNAS=randu<mat>(100,10);
vec fitness=randu<vec>(100);
double alpha = 5.0;

DNAS = ga_eval(DNAS, fitness, alpha);

DNAS.print(" ");

return 0;
}