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

#include <stdlib.h>
#include <openssl/ssl.h>
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>
#include "misc_log_ex.h"
#include "common/perf_timer.h"
extern "C"
{
#include "crypto/crypto-ops.h"
}
#include "rctOps.h"
#include "multiexp.h"
#include "bulletproofs.h"

#undef EVOLUTION_DEFAULT_LOG_CATEGORY
#define EVOLUTION_DEFAULT_LOG_CATEGORY "bulletproofs_legacy"

//#define DEBUG_BP

#define PERF_TIMER_START_BP(x) PERF_TIMER_START_UNIT(x, 1000000)

namespace rct
{

static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b);
static rct::keyV vector_powers(const rct::key &x, size_t n);
static rct::keyV vector_dup(const rct::key &x, size_t n);
static rct::key inner_product(const rct::keyV &a, const rct::keyV &b);

static constexpr size_t maxN = 64;
static constexpr size_t maxM = 16;
static rct::key Hi[maxN*maxM], Gi[maxN*maxM];
static ge_p3 Hi_p3[maxN*maxM], Gi_p3[maxN*maxM];
static ge_dsmp Gprecomp[maxN*maxM], Hprecomp[maxN*maxM];
static std::shared_ptr<straus_cached_data> HiGi_cache;
static const rct::key TWO = { {0x02, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00 , 0x00, 0x00, 0x00,0x00  } };
static const rct::keyV oneN = vector_dup(rct::identity(), maxN);
static const rct::keyV twoN = vector_powers(TWO, maxN);
static const rct::key ip12 = inner_product(oneN, twoN);
static boost::mutex init_mutex;

static inline rct::key multiexp(const std::vector<MultiexpData> &data, bool HiGi)
{
  static const size_t STEP = getenv("STRAUS_STEP") ? atoi(getenv("STRAUS_STEP")) : 0;
  if (HiGi || data.size() < 1000)
    return straus(data, HiGi ? HiGi_cache: NULL, STEP);
  else
    return bos_coster_heap_conv_robust(data);
}

//addKeys3acc_p3
//aAbB += a*A + b*B where a, b are scalars, A, B are curve points
//A and B must be input after applying "precomp"
static void addKeys3acc_p3(ge_p3 *aAbB, const key &a, const ge_dsmp A, const key &b, const ge_dsmp B)
{
    ge_p3 rv;
    ge_p1p1 p1;
    ge_p2 p2;
    ge_double_scalarmult_precomp_vartime2_p3(&rv, a.bytes, A, b.bytes, B);
    ge_cached cached;
    ge_p3_to_cached(&cached, aAbB);
    ge_add(&p1, &rv, &cached);
    ge_p1p1_to_p3(aAbB, &p1);
}

static void addKeys_acc_p3(ge_p3 *acc_p3, const rct::key &a, const rct::key &point)
{
    ge_p3 p3;
    CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&p3, point.bytes) == 0, "ge_frombytes_vartime failed");
    ge_scalarmult_p3(&p3, a.bytes, &p3);
    ge_cached cached;
    ge_p3_to_cached(&cached, acc_p3);
    ge_p1p1 p1;
    ge_add(&p1, &p3, &cached);
    ge_p1p1_to_p3(acc_p3, &p1);
}

static rct::key get_exponent(const rct::key &base, size_t idx)
{
  static const std::string salt("bulletproof");
  std::string hashed = std::string((const char*)base.bytes, sizeof(base)) + salt + tools::get_varint_data(idx);
  return rct::hashToPoint(rct::hash2rct(crypto::cn_fast_hash(hashed.data(), hashed.size())));
}

static void init_exponents()
{
  boost::lock_guard<boost::mutex> lock(init_mutex);

  static bool init_done = false;
  if (init_done)
    return;
  std::vector<MultiexpData> data;
  for (size_t i = 0; i < maxN*maxM; ++i)
  {
    Hi[i] = get_exponent(rct::H, i * 2);
    rct::precomp(Hprecomp[i], Hi[i]);
    CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Hi_p3[i], Hi[i].bytes) == 0, "ge_frombytes_vartime failed");
    Gi[i] = get_exponent(rct::H, i * 2 + 1);
    rct::precomp(Gprecomp[i], Gi[i]);
    CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&Gi_p3[i], Gi[i].bytes) == 0, "ge_frombytes_vartime failed");

    data.push_back({rct::zero(), Gi[i]});
    data.push_back({rct::zero(), Hi[i]});
  }
  HiGi_cache = straus_init_cache(data);
  size_t cache_size = (sizeof(Hi)+sizeof(Hprecomp)+sizeof(Hi_p3))*2 + straus_get_cache_size(HiGi_cache);
  MINFO("cache size: " << cache_size/1024 << " kB");
  init_done = true;
}

/* Given two scalar arrays, construct a vector commitment */
static rct::key vector_exponent(const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN");

  std::vector<MultiexpData> multiexp_data;
  multiexp_data.reserve(a.size()*2);
  for (size_t i = 0; i < a.size(); ++i)
  {
    multiexp_data.emplace_back(a[i], Gi_p3[i]);
    multiexp_data.emplace_back(b[i], Hi_p3[i]);
  }
  return multiexp(multiexp_data, true);
}

/* Compute a custom vector-scalar commitment */
static rct::key vector_exponent_custom(const rct::keyV &A, const rct::keyV &B, const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(A.size() == B.size(), "Incompatible sizes of A and B");
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  CHECK_AND_ASSERT_THROW_MES(a.size() == A.size(), "Incompatible sizes of a and A");
  CHECK_AND_ASSERT_THROW_MES(a.size() <= maxN*maxM, "Incompatible sizes of a and maxN");

  std::vector<MultiexpData> multiexp_data;
  multiexp_data.reserve(a.size()*2);
  for (size_t i = 0; i < a.size(); ++i)
  {
    multiexp_data.resize(multiexp_data.size() + 1);
    multiexp_data.back().scalar = a[i];
    CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&multiexp_data.back().point, A[i].bytes) == 0, "ge_frombytes_vartime failed");
    multiexp_data.resize(multiexp_data.size() + 1);
    multiexp_data.back().scalar = b[i];
    CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&multiexp_data.back().point, B[i].bytes) == 0, "ge_frombytes_vartime failed");
  }
  return multiexp(multiexp_data, false);
}

/* Given a scalar, construct a vector of powers */
static rct::keyV vector_powers(const rct::key &x, size_t n)
{
  rct::keyV res(n);
  if (n == 0)
    return res;
  res[0] = rct::identity();
  if (n == 1)
    return res;
  res[1] = x;
  for (size_t i = 2; i < n; ++i)
  {
    sc_mul(res[i].bytes, res[i-1].bytes, x.bytes);
  }
  return res;
}

/* Given a scalar, return the sum of its powers from 0 to n-1 */
static rct::key vector_power_sum(const rct::key &x, size_t n)
{
  if (n == 0)
    return rct::zero();
  rct::key res = rct::identity();
  if (n == 1)
    return res;
  rct::key prev = x;
  for (size_t i = 1; i < n; ++i)
  {
    if (i > 1)
      sc_mul(prev.bytes, prev.bytes, x.bytes);
    sc_add(res.bytes, res.bytes, prev.bytes);
  }
  return res;
}

/* Given two scalar arrays, construct the inner product */
static rct::key inner_product(const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  rct::key res = rct::zero();
  for (size_t i = 0; i < a.size(); ++i)
  {
    sc_muladd(res.bytes, a[i].bytes, b[i].bytes, res.bytes);
  }
  return res;
}

/* Given two scalar arrays, construct the Hadamard product */
static rct::keyV hadamard(const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  rct::keyV res(a.size());
  for (size_t i = 0; i < a.size(); ++i)
  {
    sc_mul(res[i].bytes, a[i].bytes, b[i].bytes);
  }
  return res;
}

/* Given two curvepoint arrays, construct the Hadamard product */
static rct::keyV hadamard2(const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  rct::keyV res(a.size());
  for (size_t i = 0; i < a.size(); ++i)
  {
    rct::addKeys(res[i], a[i], b[i]);
  }
  return res;
}

/* Add two vectors */
static rct::keyV vector_add(const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  rct::keyV res(a.size());
  for (size_t i = 0; i < a.size(); ++i)
  {
    sc_add(res[i].bytes, a[i].bytes, b[i].bytes);
  }
  return res;
}

/* Subtract two vectors */
static rct::keyV vector_subtract(const rct::keyV &a, const rct::keyV &b)
{
  CHECK_AND_ASSERT_THROW_MES(a.size() == b.size(), "Incompatible sizes of a and b");
  rct::keyV res(a.size());
  for (size_t i = 0; i < a.size(); ++i)
  {
    sc_sub(res[i].bytes, a[i].bytes, b[i].bytes);
  }
  return res;
}

/* Multiply a scalar and a vector */
static rct::keyV vector_scalar(const rct::keyV &a, const rct::key &x)
{
  rct::keyV res(a.size());
  for (size_t i = 0; i < a.size(); ++i)
  {
    sc_mul(res[i].bytes, a[i].bytes, x.bytes);
  }
  return res;
}

/* Create a vector from copies of a single value */
static rct::keyV vector_dup(const rct::key &x, size_t N)
{
  return rct::keyV(N, x);
}

/* Exponentiate a curve vector by a scalar */
static rct::keyV vector_scalar2(const rct::keyV &a, const rct::key &x)
{
  rct::keyV res(a.size());
  for (size_t i = 0; i < a.size(); ++i)
  {
    rct::scalarmultKey(res[i], a[i], x);
  }
  return res;
}

/* Get the sum of a vector's elements */
static rct::key vector_sum(const rct::keyV &a)
{
  rct::key res = rct::zero();
  for (size_t i = 0; i < a.size(); ++i)
  {
    sc_add(res.bytes, res.bytes, a[i].bytes);
  }
  return res;
}

static rct::key switch_endianness(rct::key k)
{
  std::reverse(k.bytes, k.bytes + sizeof(k));
  return k;
}

/* Compute the inverse of a scalar, the stupid way */
static rct::key invert(const rct::key &x)
{
  rct::key inv;

  BN_CTX *ctx = BN_CTX_new();
  BIGNUM *X = BN_new();
  BIGNUM *L = BN_new();
  BIGNUM *I = BN_new();

  BN_bin2bn(switch_endianness(x).bytes, sizeof(rct::key), X);
  BN_bin2bn(switch_endianness(rct::curveOrder()).bytes, sizeof(rct::key), L);

  CHECK_AND_ASSERT_THROW_MES(BN_mod_inverse(I, X, L, ctx), "Failed to invert");

  const int len = BN_num_bytes(I);
  CHECK_AND_ASSERT_THROW_MES((size_t)len <= sizeof(rct::key), "Invalid number length");
  inv = rct::zero();
  BN_bn2bin(I, inv.bytes);
  std::reverse(inv.bytes, inv.bytes + len);

  BN_free(I);
  BN_free(L);
  BN_free(X);
  BN_CTX_free(ctx);

#ifdef DEBUG_BP
  rct::key tmp;
  sc_mul(tmp.bytes, inv.bytes, x.bytes);
  CHECK_AND_ASSERT_THROW_MES(tmp == rct::identity(), "invert failed");
#endif
  return inv;
}

/* Compute the slice of a vector */
static rct::keyV slice(const rct::keyV &a, size_t start, size_t stop)
{
  CHECK_AND_ASSERT_THROW_MES(start < a.size(), "Invalid start index");
  CHECK_AND_ASSERT_THROW_MES(stop <= a.size(), "Invalid stop index");
  CHECK_AND_ASSERT_THROW_MES(start < stop, "Invalid start/stop indices");
  rct::keyV res(stop - start);
  for (size_t i = start; i < stop; ++i)
  {
    res[i - start] = a[i];
  }
  return res;
}

static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1)
{
  rct::keyV data;
  data.reserve(3);
  data.push_back(hash_cache);
  data.push_back(mash0);
  data.push_back(mash1);
  return hash_cache = rct::hash_to_scalar(data);
}

static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1, const rct::key &mash2)
{
  rct::keyV data;
  data.reserve(4);
  data.push_back(hash_cache);
  data.push_back(mash0);
  data.push_back(mash1);
  data.push_back(mash2);
  return hash_cache = rct::hash_to_scalar(data);
}

static rct::key hash_cache_mash(rct::key &hash_cache, const rct::key &mash0, const rct::key &mash1, const rct::key &mash2, const rct::key &mash3)
{
  rct::keyV data;
  data.reserve(5);
  data.push_back(hash_cache);
  data.push_back(mash0);
  data.push_back(mash1);
  data.push_back(mash2);
  data.push_back(mash3);
  return hash_cache = rct::hash_to_scalar(data);
}

/* Given a value v (0..2^N-1) and a mask gamma, construct a range proof */
Bulletproof bulletproof_PROVE_old(const rct::key &sv, const rct::key &gamma)
{
  init_exponents();

  PERF_TIMER_UNIT(PROVE, 1000000);

  constexpr size_t logN = 6; // log2(64)
  constexpr size_t N = 1<<logN;

  rct::key V;
  rct::keyV aL(N), aR(N);

  PERF_TIMER_START_BP(PROVE_v);
  rct::addKeys2(V, gamma, sv, rct::H);
  PERF_TIMER_STOP(PROVE_v);

  PERF_TIMER_START_BP(PROVE_aLaR);
  for (size_t i = N; i-- > 0; )
  {
    if (sv[i/8] & (((uint64_t)1)<<(i%8)))
    {
      aL[i] = rct::identity();
    }
    else
    {
      aL[i] = rct::zero();
    }
    sc_sub(aR[i].bytes, aL[i].bytes, rct::identity().bytes);
  }
  PERF_TIMER_STOP(PROVE_aLaR);

  rct::key hash_cache = rct::hash_to_scalar(V);

  // DEBUG: Test to ensure this recovers the value
#ifdef DEBUG_BP
  uint64_t test_aL = 0, test_aR = 0;
  for (size_t i = 0; i < N; ++i)
  {
    if (aL[i] == rct::identity())
      test_aL += ((uint64_t)1)<<i;
    if (aR[i] == rct::zero())
      test_aR += ((uint64_t)1)<<i;
  }
  uint64_t v_test = 0;
  for (int n = 0; n < 8; ++n) v_test |= (((uint64_t)sv[n]) << (8*n));
  CHECK_AND_ASSERT_THROW_MES(test_aL == v_test, "test_aL failed");
  CHECK_AND_ASSERT_THROW_MES(test_aR == v_test, "test_aR failed");
#endif

  PERF_TIMER_START_BP(PROVE_step1);
  // PAPER LINES 38-39
  rct::key alpha = rct::skGen();
  rct::key ve = vector_exponent(aL, aR);
  rct::key A;
  rct::addKeys(A, ve, rct::scalarmultBase(alpha));

  // PAPER LINES 40-42
  rct::keyV sL = rct::skvGen(N), sR = rct::skvGen(N);
  rct::key rho = rct::skGen();
  ve = vector_exponent(sL, sR);
  rct::key S;
  rct::addKeys(S, ve, rct::scalarmultBase(rho));

  // PAPER LINES 43-45
  rct::key y = hash_cache_mash(hash_cache, A, S);
  rct::key z = hash_cache = rct::hash_to_scalar(y);

  // Polynomial construction before PAPER LINE 46
  rct::key t0 = rct::zero();
  rct::key t1 = rct::zero();
  rct::key t2 = rct::zero();

  const auto yN = vector_powers(y, N);

  rct::key ip1y = vector_sum(yN);
  rct::key tmp;
  sc_muladd(t0.bytes, z.bytes, ip1y.bytes, t0.bytes);

  rct::key zsq;
  sc_mul(zsq.bytes, z.bytes, z.bytes);
  sc_muladd(t0.bytes, zsq.bytes, sv.bytes, t0.bytes);

  rct::key k = rct::zero();
  sc_mulsub(k.bytes, zsq.bytes, ip1y.bytes, k.bytes);

  rct::key zcu;
  sc_mul(zcu.bytes, zsq.bytes, z.bytes);
  sc_mulsub(k.bytes, zcu.bytes, ip12.bytes, k.bytes);
  sc_add(t0.bytes, t0.bytes, k.bytes);

  // DEBUG: Test the value of t0 has the correct form
#ifdef DEBUG_BP
  rct::key test_t0 = rct::zero();
  rct::key iph = inner_product(aL, hadamard(aR, yN));
  sc_add(test_t0.bytes, test_t0.bytes, iph.bytes);
  rct::key ips = inner_product(vector_subtract(aL, aR), yN);
  sc_muladd(test_t0.bytes, z.bytes, ips.bytes, test_t0.bytes);
  rct::key ipt = inner_product(twoN, aL);
  sc_muladd(test_t0.bytes, zsq.bytes, ipt.bytes, test_t0.bytes);
  sc_add(test_t0.bytes, test_t0.bytes, k.bytes);
  CHECK_AND_ASSERT_THROW_MES(t0 == test_t0, "t0 check failed");
#endif
  PERF_TIMER_STOP(PROVE_step1);

  PERF_TIMER_START_BP(PROVE_step2);
  const auto HyNsR = hadamard(yN, sR);
  const auto vpIz = vector_dup(z, N);
  const auto vp2zsq = vector_scalar(twoN, zsq);
  const auto aL_vpIz = vector_subtract(aL, vpIz);
  const auto aR_vpIz = vector_add(aR, vpIz);

  rct::key ip1 = inner_product(aL_vpIz, HyNsR);
  sc_add(t1.bytes, t1.bytes, ip1.bytes);

  rct::key ip2 = inner_product(sL, vector_add(hadamard(yN, aR_vpIz), vp2zsq));
  sc_add(t1.bytes, t1.bytes, ip2.bytes);

  rct::key ip3 = inner_product(sL, HyNsR);
  sc_add(t2.bytes, t2.bytes, ip3.bytes);

  // PAPER LINES 47-48
  rct::key tau1 = rct::skGen(), tau2 = rct::skGen();

  rct::key T1 = rct::addKeys(rct::scalarmultKey(rct::H, t1), rct::scalarmultBase(tau1));
  rct::key T2 = rct::addKeys(rct::scalarmultKey(rct::H, t2), rct::scalarmultBase(tau2));

  // PAPER LINES 49-51
  rct::key x = hash_cache_mash(hash_cache, z, T1, T2);

  // PAPER LINES 52-53
  rct::key taux = rct::zero();
  sc_mul(taux.bytes, tau1.bytes, x.bytes);
  rct::key xsq;
  sc_mul(xsq.bytes, x.bytes, x.bytes);
  sc_muladd(taux.bytes, tau2.bytes, xsq.bytes, taux.bytes);
  sc_muladd(taux.bytes, gamma.bytes, zsq.bytes, taux.bytes);
  rct::key mu;
  sc_muladd(mu.bytes, x.bytes, rho.bytes, alpha.bytes);

  // PAPER LINES 54-57
  rct::keyV l = vector_add(aL_vpIz, vector_scalar(sL, x));
  rct::keyV r = vector_add(hadamard(yN, vector_add(aR_vpIz, vector_scalar(sR, x))), vp2zsq);
  PERF_TIMER_STOP(PROVE_step2);

  PERF_TIMER_START_BP(PROVE_step3);
  rct::key t = inner_product(l, r);

  // DEBUG: Test if the l and r vectors match the polynomial forms
#ifdef DEBUG_BP
  rct::key test_t;
  sc_muladd(test_t.bytes, t1.bytes, x.bytes, t0.bytes);
  sc_muladd(test_t.bytes, t2.bytes, xsq.bytes, test_t.bytes);
  CHECK_AND_ASSERT_THROW_MES(test_t == t, "test_t check failed");
#endif

  // PAPER LINES 32-33
  rct::key x_ip = hash_cache_mash(hash_cache, x, taux, mu, t);

  // These are used in the inner product rounds
  size_t nprime = N;
  rct::keyV Gprime(N);
  rct::keyV Hprime(N);
  rct::keyV aprime(N);
  rct::keyV bprime(N);
  const rct::key yinv = invert(y);
  rct::key yinvpow = rct::identity();
  for (size_t i = 0; i < N; ++i)
  {
    Gprime[i] = Gi[i];
    Hprime[i] = scalarmultKey(Hi[i], yinvpow);
    sc_mul(yinvpow.bytes, yinvpow.bytes, yinv.bytes);
    aprime[i] = l[i];
    bprime[i] = r[i];
  }
  rct::keyV L(logN);
  rct::keyV R(logN);
  int round = 0;
  rct::keyV w(logN); // this is the challenge x in the inner product protocol
  PERF_TIMER_STOP(PROVE_step3);

  PERF_TIMER_START_BP(PROVE_step4);
  // PAPER LINE 13
  while (nprime > 1)
  {
    // PAPER LINE 15
    nprime /= 2;

    // PAPER LINES 16-17
    rct::key cL = inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()));
    rct::key cR = inner_product(slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime));

    // PAPER LINES 18-19
    L[round] = vector_exponent_custom(slice(Gprime, nprime, Gprime.size()), slice(Hprime, 0, nprime), slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()));
    sc_mul(tmp.bytes, cL.bytes, x_ip.bytes);
    rct::addKeys(L[round], L[round], rct::scalarmultKey(rct::H, tmp));
    R[round] = vector_exponent_custom(slice(Gprime, 0, nprime), slice(Hprime, nprime, Hprime.size()), slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime));
    sc_mul(tmp.bytes, cR.bytes, x_ip.bytes);
    rct::addKeys(R[round], R[round], rct::scalarmultKey(rct::H, tmp));

    // PAPER LINES 21-22
    w[round] = hash_cache_mash(hash_cache, L[round], R[round]);

    // PAPER LINES 24-25
    const rct::key winv = invert(w[round]);
    Gprime = hadamard2(vector_scalar2(slice(Gprime, 0, nprime), winv), vector_scalar2(slice(Gprime, nprime, Gprime.size()), w[round]));
    Hprime = hadamard2(vector_scalar2(slice(Hprime, 0, nprime), w[round]), vector_scalar2(slice(Hprime, nprime, Hprime.size()), winv));

    // PAPER LINES 28-29
    aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), w[round]), vector_scalar(slice(aprime, nprime, aprime.size()), winv));
    bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), winv), vector_scalar(slice(bprime, nprime, bprime.size()), w[round]));

    ++round;
  }
  PERF_TIMER_STOP(PROVE_step4);

  // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20)
  return Bulletproof(V, A, S, T1, T2, taux, mu, L, R, aprime[0], bprime[0], t);
}

Bulletproof bulletproof_PROVE_old(uint64_t v, const rct::key &gamma)
{
  // vG + gammaH
  PERF_TIMER_START_BP(PROVE_v);
  rct::key sv = rct::zero();
  sv.bytes[0] = v & 255;
  sv.bytes[1] = (v >> 8) & 255;
  sv.bytes[2] = (v >> 16) & 255;
  sv.bytes[3] = (v >> 24) & 255;
  sv.bytes[4] = (v >> 32) & 255;
  sv.bytes[5] = (v >> 40) & 255;
  sv.bytes[6] = (v >> 48) & 255;
  sv.bytes[7] = (v >> 56) & 255;
  PERF_TIMER_STOP(PROVE_v);
  return bulletproof_PROVE_old(sv, gamma);
}

/* Given a set of values v (0..2^N-1) and masks gamma, construct a range proof */
Bulletproof bulletproof_PROVE_old(const rct::keyV &sv, const rct::keyV &gamma)
{
  CHECK_AND_ASSERT_THROW_MES(sv.size() == gamma.size(), "Incompatible sizes of sv and gamma");
  CHECK_AND_ASSERT_THROW_MES(!sv.empty(), "sv is empty");

  init_exponents();

  PERF_TIMER_UNIT(PROVE, 1000000);

  constexpr size_t logN = 6; // log2(64)
  constexpr size_t N = 1<<logN;
  size_t M, logM;
  for (logM = 0; (M = 1<<logM) <= maxM && M < sv.size(); ++logM);
  CHECK_AND_ASSERT_THROW_MES(M <= maxM, "sv/gamma are too large");
  const size_t logMN = logM + logN;
  const size_t MN = M * N;

  rct::keyV V(sv.size());
  rct::keyV aL(MN), aR(MN);
  rct::key tmp;

  PERF_TIMER_START_BP(PROVE_v);
  for (size_t i = 0; i < sv.size(); ++i)
    rct::addKeys2(V[i], gamma[i], sv[i], rct::H);
  PERF_TIMER_STOP(PROVE_v);

  PERF_TIMER_START_BP(PROVE_aLaR);
  for (size_t j = 0; j < M; ++j)
  {
    for (size_t i = N; i-- > 0; )
    {
      if (j >= sv.size())
      {
        aL[j*N+i] = rct::zero();
      }
      else if (sv[j][i/8] & (((uint64_t)1)<<(i%8)))
      {
        aL[j*N+i] = rct::identity();
      }
      else
      {
        aL[j*N+i] = rct::zero();
      }
      sc_sub(aR[j*N+i].bytes, aL[j*N+i].bytes, rct::identity().bytes);
    }
  }
  PERF_TIMER_STOP(PROVE_aLaR);

  rct::key hash_cache = rct::hash_to_scalar(V);

  // DEBUG: Test to ensure this recovers the value
#ifdef DEBUG_BP
  for (size_t j = 0; j < M; ++j)
  {
    uint64_t test_aL = 0, test_aR = 0;
    for (size_t i = 0; i < N; ++i)
    {
      if (aL[j*N+i] == rct::identity())
        test_aL += ((uint64_t)1)<<i;
      if (aR[j*N+i] == rct::zero())
        test_aR += ((uint64_t)1)<<i;
    }
    uint64_t v_test = 0;
    if (j < sv.size())
      for (int n = 0; n < 8; ++n) v_test |= (((uint64_t)sv[j][n]) << (8*n));
    CHECK_AND_ASSERT_THROW_MES(test_aL == v_test, "test_aL failed");
    CHECK_AND_ASSERT_THROW_MES(test_aR == v_test, "test_aR failed");
  }
#endif

  PERF_TIMER_START_BP(PROVE_step1);
  // PAPER LINES 38-39
  rct::key alpha = rct::skGen();
  rct::key ve = vector_exponent(aL, aR);
  rct::key A;
  rct::addKeys(A, ve, rct::scalarmultBase(alpha));

  // PAPER LINES 40-42
  rct::keyV sL = rct::skvGen(MN), sR = rct::skvGen(MN);
  rct::key rho = rct::skGen();
  ve = vector_exponent(sL, sR);
  rct::key S;
  rct::addKeys(S, ve, rct::scalarmultBase(rho));

  // PAPER LINES 43-45
  rct::key y = hash_cache_mash(hash_cache, A, S);
  rct::key z = hash_cache = rct::hash_to_scalar(y);

  // Polynomial construction by coefficients
  const auto zMN = vector_dup(z, MN);
  rct::keyV l0 = vector_subtract(aL, zMN);
  const rct::keyV &l1 = sL;

  // This computes the ugly sum/concatenation from PAPER LINE 65
  rct::keyV zero_twos(MN);
  const rct::keyV zpow = vector_powers(z, M+2);
  for (size_t i = 0; i < MN; ++i)
  {
    zero_twos[i] = rct::zero();
    for (size_t j = 1; j <= M; ++j)
    {
      if (i >= (j-1)*N && i < j*N)
      {
        CHECK_AND_ASSERT_THROW_MES(1+j < zpow.size(), "invalid zpow index");
        CHECK_AND_ASSERT_THROW_MES(i-(j-1)*N < twoN.size(), "invalid twoN index");
        sc_muladd(zero_twos[i].bytes, zpow[1+j].bytes, twoN[i-(j-1)*N].bytes, zero_twos[i].bytes);
      }
    }
  }

  rct::keyV r0 = vector_add(aR, zMN);
  const auto yMN = vector_powers(y, MN);
  r0 = hadamard(r0, yMN);
  r0 = vector_add(r0, zero_twos);
  rct::keyV r1 = hadamard(yMN, sR);

  // Polynomial construction before PAPER LINE 46
  rct::key t1_1 = inner_product(l0, r1);
  rct::key t1_2 = inner_product(l1, r0);
  rct::key t1;
  sc_add(t1.bytes, t1_1.bytes, t1_2.bytes);
  rct::key t2 = inner_product(l1, r1);

  PERF_TIMER_STOP(PROVE_step1);

  PERF_TIMER_START_BP(PROVE_step2);
  // PAPER LINES 47-48
  rct::key tau1 = rct::skGen(), tau2 = rct::skGen();

  rct::key T1 = rct::addKeys(rct::scalarmultKey(rct::H, t1), rct::scalarmultBase(tau1));
  rct::key T2 = rct::addKeys(rct::scalarmultKey(rct::H, t2), rct::scalarmultBase(tau2));

  // PAPER LINES 49-51
  rct::key x = hash_cache_mash(hash_cache, z, T1, T2);

  // PAPER LINES 52-53
  rct::key taux;
  sc_mul(taux.bytes, tau1.bytes, x.bytes);
  rct::key xsq;
  sc_mul(xsq.bytes, x.bytes, x.bytes);
  sc_muladd(taux.bytes, tau2.bytes, xsq.bytes, taux.bytes);
  for (size_t j = 1; j <= sv.size(); ++j)
  {
    CHECK_AND_ASSERT_THROW_MES(j+1 < zpow.size(), "invalid zpow index");
    sc_muladd(taux.bytes, zpow[j+1].bytes, gamma[j-1].bytes, taux.bytes);
  }
  rct::key mu;
  sc_muladd(mu.bytes, x.bytes, rho.bytes, alpha.bytes);

  // PAPER LINES 54-57
  rct::keyV l = l0;
  l = vector_add(l, vector_scalar(l1, x));
  rct::keyV r = r0;
  r = vector_add(r, vector_scalar(r1, x));
  PERF_TIMER_STOP(PROVE_step2);

  PERF_TIMER_START_BP(PROVE_step3);
  rct::key t = inner_product(l, r);

  // DEBUG: Test if the l and r vectors match the polynomial forms
#ifdef DEBUG_BP
  rct::key test_t;
  const rct::key t0 = inner_product(l0, r0);
  sc_muladd(test_t.bytes, t1.bytes, x.bytes, t0.bytes);
  sc_muladd(test_t.bytes, t2.bytes, xsq.bytes, test_t.bytes);
  CHECK_AND_ASSERT_THROW_MES(test_t == t, "test_t check failed");
#endif

  // PAPER LINES 32-33
  rct::key x_ip = hash_cache_mash(hash_cache, x, taux, mu, t);

  // These are used in the inner product rounds
  size_t nprime = MN;
  rct::keyV Gprime(MN);
  rct::keyV Hprime(MN);
  rct::keyV aprime(MN);
  rct::keyV bprime(MN);
  const rct::key yinv = invert(y);
  rct::key yinvpow = rct::identity();
  for (size_t i = 0; i < MN; ++i)
  {
    Gprime[i] = Gi[i];
    Hprime[i] = scalarmultKey(Hi[i], yinvpow);
    sc_mul(yinvpow.bytes, yinvpow.bytes, yinv.bytes);
    aprime[i] = l[i];
    bprime[i] = r[i];
  }
  rct::keyV L(logMN);
  rct::keyV R(logMN);
  int round = 0;
  rct::keyV w(logMN); // this is the challenge x in the inner product protocol
  PERF_TIMER_STOP(PROVE_step3);

  PERF_TIMER_START_BP(PROVE_step4);
  // PAPER LINE 13
  while (nprime > 1)
  {
    // PAPER LINE 15
    nprime /= 2;

    // PAPER LINES 16-17
    rct::key cL = inner_product(slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()));
    rct::key cR = inner_product(slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime));

    // PAPER LINES 18-19
    L[round] = vector_exponent_custom(slice(Gprime, nprime, Gprime.size()), slice(Hprime, 0, nprime), slice(aprime, 0, nprime), slice(bprime, nprime, bprime.size()));
    sc_mul(tmp.bytes, cL.bytes, x_ip.bytes);
    rct::addKeys(L[round], L[round], rct::scalarmultKey(rct::H, tmp));
    R[round] = vector_exponent_custom(slice(Gprime, 0, nprime), slice(Hprime, nprime, Hprime.size()), slice(aprime, nprime, aprime.size()), slice(bprime, 0, nprime));
    sc_mul(tmp.bytes, cR.bytes, x_ip.bytes);
    rct::addKeys(R[round], R[round], rct::scalarmultKey(rct::H, tmp));

    // PAPER LINES 21-22
    w[round] = hash_cache_mash(hash_cache, L[round], R[round]);

    // PAPER LINES 24-25
    const rct::key winv = invert(w[round]);
    Gprime = hadamard2(vector_scalar2(slice(Gprime, 0, nprime), winv), vector_scalar2(slice(Gprime, nprime, Gprime.size()), w[round]));
    Hprime = hadamard2(vector_scalar2(slice(Hprime, 0, nprime), w[round]), vector_scalar2(slice(Hprime, nprime, Hprime.size()), winv));

    // PAPER LINES 28-29
    aprime = vector_add(vector_scalar(slice(aprime, 0, nprime), w[round]), vector_scalar(slice(aprime, nprime, aprime.size()), winv));
    bprime = vector_add(vector_scalar(slice(bprime, 0, nprime), winv), vector_scalar(slice(bprime, nprime, bprime.size()), w[round]));

    ++round;
  }
  PERF_TIMER_STOP(PROVE_step4);

  // PAPER LINE 58 (with inclusions from PAPER LINE 8 and PAPER LINE 20)
  return Bulletproof(V, A, S, T1, T2, taux, mu, L, R, aprime[0], bprime[0], t);
}

Bulletproof bulletproof_PROVE_old(const std::vector<uint64_t> &v, const rct::keyV &gamma)
{
  CHECK_AND_ASSERT_THROW_MES(v.size() == gamma.size(), "Incompatible sizes of v and gamma");

  // vG + gammaH
  PERF_TIMER_START_BP(PROVE_v);
  rct::keyV sv(v.size());
  for (size_t i = 0; i < v.size(); ++i)
  {
    sv[i] = rct::zero();
    sv[i].bytes[0] = v[i] & 255;
    sv[i].bytes[1] = (v[i] >> 8) & 255;
    sv[i].bytes[2] = (v[i] >> 16) & 255;
    sv[i].bytes[3] = (v[i] >> 24) & 255;
    sv[i].bytes[4] = (v[i] >> 32) & 255;
    sv[i].bytes[5] = (v[i] >> 40) & 255;
    sv[i].bytes[6] = (v[i] >> 48) & 255;
    sv[i].bytes[7] = (v[i] >> 56) & 255;
  }
  PERF_TIMER_STOP(PROVE_v);
  return bulletproof_PROVE_old(sv, gamma);
}

/* Given a range proof, determine if it is valid */
bool bulletproof_VERIFY_old(const std::vector<const Bulletproof*> &proofs)
{
  init_exponents();

  PERF_TIMER_START_BP(VERIFY);

  // sanity and figure out which proof is longest
  size_t max_length = 0;
  for (const Bulletproof *p: proofs)
  {
    const Bulletproof &proof = *p;
    CHECK_AND_ASSERT_MES(proof.V.size() >= 1, false, "V does not have at least one element");
    CHECK_AND_ASSERT_MES(proof.L.size() == proof.R.size(), false, "Mismatched L and R sizes");
    CHECK_AND_ASSERT_MES(proof.L.size() > 0, false, "Empty proof");

    max_length = std::max(max_length, proof.L.size());
  }
  CHECK_AND_ASSERT_MES(max_length < 32, false, "At least one proof is too large");
  size_t maxMN = 1u << max_length;

  const size_t logN = 6;
  const size_t N = 1 << logN;
  rct::key tmp;

  // setup weighted aggregates
  rct::key Z0 = rct::identity();
  rct::key z1 = rct::zero();
  rct::key Z2 = rct::identity();
  rct::key z3 = rct::zero();
  rct::keyV z4(maxMN, rct::zero()), z5(maxMN, rct::zero());
  for (const Bulletproof *p: proofs)
  {
    const Bulletproof &proof = *p;

    size_t M, logM;
    for (logM = 0; (M = 1<<logM) <= maxM && M < proof.V.size(); ++logM);
    CHECK_AND_ASSERT_MES(proof.L.size() == 6+logM, false, "Proof is not the expected size");
    const size_t MN = M*N;
    rct::key weight = rct::skGen();

    // Reconstruct the challenges
    PERF_TIMER_START_BP(VERIFY_start);
    rct::key hash_cache = rct::hash_to_scalar(proof.V);
    rct::key y = hash_cache_mash(hash_cache, proof.A, proof.S);
    rct::key z = hash_cache = rct::hash_to_scalar(y);
    rct::key x = hash_cache_mash(hash_cache, z, proof.T1, proof.T2);
    rct::key x_ip = hash_cache_mash(hash_cache, x, proof.taux, proof.mu, proof.t);
    PERF_TIMER_STOP(VERIFY_start);

    PERF_TIMER_START_BP(VERIFY_line_61);
    // PAPER LINE 61
    rct::key L61Left, L61Right;
    rct::addKeys2(L61Left, proof.taux, proof.t, rct::H);

    const rct::keyV zpow = vector_powers(z, M+3);

    rct::key k;
    const rct::key ip1y = vector_power_sum(y, MN);
    sc_mulsub(k.bytes, zpow[2].bytes, ip1y.bytes, rct::zero().bytes);
    for (size_t j = 1; j <= M; ++j)
    {
      CHECK_AND_ASSERT_MES(j+2 < zpow.size(), false, "invalid zpow index");
      sc_mulsub(k.bytes, zpow[j+2].bytes, ip12.bytes, k.bytes);
    }
    PERF_TIMER_STOP(VERIFY_line_61);

    // bos coster is slower for small numbers of calcs, straus seems not
    if (1)
    {
      PERF_TIMER_START_BP(VERIFY_line_61rl_new);
      sc_muladd(tmp.bytes, z.bytes, ip1y.bytes, k.bytes);
      std::vector<MultiexpData> multiexp_data;
      multiexp_data.reserve(3+proof.V.size());
      multiexp_data.emplace_back(tmp, rct::H);
      for (size_t j = 0; j < proof.V.size(); j++)
      {
        multiexp_data.emplace_back(zpow[j+2], proof.V[j]);
      }
      multiexp_data.emplace_back(x, proof.T1);
      rct::key xsq;
      sc_mul(xsq.bytes, x.bytes, x.bytes);
      multiexp_data.emplace_back(xsq, proof.T2);
      L61Right = multiexp(multiexp_data, false);
      PERF_TIMER_STOP(VERIFY_line_61rl_new);
    }
    else
    {
      PERF_TIMER_START_BP(VERIFY_line_61rl_old);
      sc_muladd(tmp.bytes, z.bytes, ip1y.bytes, k.bytes);
      L61Right = rct::scalarmultKey(rct::H, tmp);
      ge_p3 L61Right_p3;
      CHECK_AND_ASSERT_THROW_MES(ge_frombytes_vartime(&L61Right_p3, L61Right.bytes) == 0, "ge_frombytes_vartime failed");
      for (size_t j = 0; j+1 < proof.V.size(); j += 2)
      {
        CHECK_AND_ASSERT_MES(j+2+1 < zpow.size(), false, "invalid zpow index");
        ge_dsmp precomp0, precomp1;
        rct::precomp(precomp0, j < proof.V.size() ? proof.V[j] : rct::identity());
        rct::precomp(precomp1, j+1 < proof.V.size() ? proof.V[j+1] : rct::identity());
        rct::addKeys3acc_p3(&L61Right_p3, zpow[j+2], precomp0, zpow[j+2+1], precomp1);
      }
      for (size_t j = proof.V.size() & 0xfffffffe; j < M; j++)
      {
        CHECK_AND_ASSERT_MES(j+2 < zpow.size(), false, "invalid zpow index");
        // faster equivalent to:
        // tmp = rct::scalarmultKey(j < proof.V.size() ? proof.V[j] : rct::identity(), zpow[j+2]);
        // rct::addKeys(L61Right, L61Right, tmp);
        if (j < proof.V.size())
          addKeys_acc_p3(&L61Right_p3, zpow[j+2], proof.V[j]);
      }

      addKeys_acc_p3(&L61Right_p3, x, proof.T1);

      rct::key xsq;
      sc_mul(xsq.bytes, x.bytes, x.bytes);
      addKeys_acc_p3(&L61Right_p3, xsq, proof.T2);
      ge_p3_tobytes(L61Right.bytes, &L61Right_p3);
      PERF_TIMER_STOP(VERIFY_line_61rl_old);
    }

    if (!(L61Right == L61Left))
    {
      MERROR("Verification failure at step 1");
      return false;
    }

    PERF_TIMER_START_BP(VERIFY_line_62);
    // PAPER LINE 62
    rct::addKeys(Z0, Z0, rct::scalarmultKey(rct::addKeys(proof.A, rct::scalarmultKey(proof.S, x)), weight));
    PERF_TIMER_STOP(VERIFY_line_62);

    // Compute the number of rounds for the inner product
    const size_t rounds = logM+logN;
    CHECK_AND_ASSERT_MES(rounds > 0, false, "Zero rounds");

    PERF_TIMER_START_BP(VERIFY_line_21_22);
    // PAPER LINES 21-22
    // The inner product challenges are computed per round
    rct::keyV w(rounds);
    for (size_t i = 0; i < rounds; ++i)
    {
      w[i] = hash_cache_mash(hash_cache, proof.L[i], proof.R[i]);
    }
    PERF_TIMER_STOP(VERIFY_line_21_22);

    PERF_TIMER_START_BP(VERIFY_line_24_25);
    // Basically PAPER LINES 24-25
    // Compute the curvepoints from G[i] and H[i]
    rct::key yinvpow = rct::identity();
    rct::key ypow = rct::identity();

    PERF_TIMER_START_BP(VERIFY_line_24_25_invert);
    const rct::key yinv = invert(y);
    rct::keyV winv(rounds);
    for (size_t i = 0; i < rounds; ++i)
      winv[i] = invert(w[i]);
    PERF_TIMER_STOP(VERIFY_line_24_25_invert);

    for (size_t i = 0; i < MN; ++i)
    {
      // Convert the index to binary IN REVERSE and construct the scalar exponent
      rct::key g_scalar = proof.a;
      rct::key h_scalar;
      sc_mul(h_scalar.bytes, proof.b.bytes, yinvpow.bytes);

      for (size_t j = rounds; j-- > 0; )
      {
        size_t J = w.size() - j - 1;

        if ((i & (((size_t)1)<<j)) == 0)
        {
          sc_mul(g_scalar.bytes, g_scalar.bytes, winv[J].bytes);
          sc_mul(h_scalar.bytes, h_scalar.bytes, w[J].bytes);
        }
        else
        {
          sc_mul(g_scalar.bytes, g_scalar.bytes, w[J].bytes);
          sc_mul(h_scalar.bytes, h_scalar.bytes, winv[J].bytes);
        }
      }

      // Adjust the scalars using the exponents from PAPER LINE 62
      sc_add(g_scalar.bytes, g_scalar.bytes, z.bytes);
      CHECK_AND_ASSERT_MES(2+i/N < zpow.size(), false, "invalid zpow index");
      CHECK_AND_ASSERT_MES(i%N < twoN.size(), false, "invalid twoN index");
      sc_mul(tmp.bytes, zpow[2+i/N].bytes, twoN[i%N].bytes);
      sc_muladd(tmp.bytes, z.bytes, ypow.bytes, tmp.bytes);
      sc_mulsub(h_scalar.bytes, tmp.bytes, yinvpow.bytes, h_scalar.bytes);

      sc_muladd(z4[i].bytes, g_scalar.bytes, weight.bytes, z4[i].bytes);
      sc_muladd(z5[i].bytes, h_scalar.bytes, weight.bytes, z5[i].bytes);

      if (i != MN-1)
      {
        sc_mul(yinvpow.bytes, yinvpow.bytes, yinv.bytes);
        sc_mul(ypow.bytes, ypow.bytes, y.bytes);
      }
    }

    PERF_TIMER_STOP(VERIFY_line_24_25);

    // PAPER LINE 26
    PERF_TIMER_START_BP(VERIFY_line_26_new);
    std::vector<MultiexpData> multiexp_data;
    multiexp_data.reserve(2*rounds);

    sc_muladd(z1.bytes, proof.mu.bytes, weight.bytes, z1.bytes);
    for (size_t i = 0; i < rounds; ++i)
    {
      sc_mul(tmp.bytes, w[i].bytes, w[i].bytes);
      multiexp_data.emplace_back(tmp, proof.L[i]);
      sc_mul(tmp.bytes, winv[i].bytes, winv[i].bytes);
      multiexp_data.emplace_back(tmp, proof.R[i]);
    }
    rct::key acc = multiexp(multiexp_data, false);
    rct::addKeys(Z2, Z2, rct::scalarmultKey(acc, weight));
    sc_mulsub(tmp.bytes, proof.a.bytes, proof.b.bytes, proof.t.bytes);
    sc_mul(tmp.bytes, tmp.bytes, x_ip.bytes);
    sc_muladd(z3.bytes, tmp.bytes, weight.bytes, z3.bytes);
    PERF_TIMER_STOP(VERIFY_line_26_new);
  }

  // now check all proofs at once
  PERF_TIMER_START_BP(VERIFY_step2_check);
  rct::key Y = Z0;
  sc_sub(tmp.bytes, rct::zero().bytes, z1.bytes);
  rct::addKeys(Y, Y, rct::scalarmultBase(tmp));
  rct::addKeys(Y, Y, Z2);
  rct::addKeys(Y, Y, rct::scalarmultKey(rct::H, z3));

  std::vector<MultiexpData> multiexp_data;
  multiexp_data.reserve(2 * maxMN);
  for (size_t i = 0; i < maxMN; ++i)
  {
    sc_sub(tmp.bytes, rct::zero().bytes, z4[i].bytes);
    multiexp_data.emplace_back(tmp, Gi_p3[i]);
    sc_sub(tmp.bytes, rct::zero().bytes, z5[i].bytes);
    multiexp_data.emplace_back(tmp, Hi_p3[i]);
  }
  rct::addKeys(Y, Y, multiexp(multiexp_data, true));
  PERF_TIMER_STOP(VERIFY_step2_check);

  if (!(Y == rct::identity()))
  {
    MERROR("Verification failure at step 2");
    return false;
  }

  PERF_TIMER_STOP(VERIFY);
  return true;
}

bool bulletproof_VERIFY_old(const std::vector<Bulletproof> &proofs)
{
  std::vector<const Bulletproof*> proof_pointers;
  for (const Bulletproof &proof: proofs)
    proof_pointers.push_back(&proof);
  return bulletproof_VERIFY_old(proof_pointers);
}

bool bulletproof_VERIFY_old(const Bulletproof &proof)
{
  std::vector<const Bulletproof*> proofs;
  proofs.push_back(&proof);
  return bulletproof_VERIFY_old(proofs);
}

}


/*
 * Copyright 2020 Adobe. All rights reserved.
 * This file is licensed to you 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 REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
#include <Eigen/Core>
#include <lagrange/testing/common.h>

#include <lagrange/attributes/attribute_utils.h>
#include <lagrange/attributes/condense_indexed_attribute.h>
#include <lagrange/common.h>
#include <lagrange/create_mesh.h>
#include <lagrange/utils/range.h>

TEST_CASE("CondenseIndexedAttribute", "[attribute][condense][indexed]")
{
    using namespace lagrange;

    SECTION("uv from corner attribute 2")
    {
        Vertices2D vertices(4, 2);
        vertices << 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0;
        Triangles facets(2, 3);
        facets << 0, 1, 2, 2, 1, 3;

        auto mesh = create_mesh(vertices, facets);

        using MeshType = decltype(mesh)::element_type;
        using AttributeArray = MeshType::AttributeArray;
        const std::string attr_name = "uv";
        AttributeArray attr(6, 2);

        SECTION("Consistent UV")
        {
            attr << 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 1.0;
            mesh->add_corner_attribute(attr_name);
            mesh->import_corner_attribute(attr_name, attr);
            map_corner_attribute_to_indexed_attribute(*mesh, attr_name);
            mesh->remove_corner_attribute(attr_name);

            REQUIRE(mesh->is_uv_initialized());

            const auto& uv = mesh->get_uv();
            const auto& uv_indices = mesh->get_uv_indices();
            REQUIRE(uv.rows() == 4);
            REQUIRE(uv_indices.rows() == 2);
            for (auto i : range(2)) {
                REQUIRE(uv(uv_indices(i, 0), 0) == vertices(facets(i, 0), 0));
                REQUIRE(uv(uv_indices(i, 1), 0) == vertices(facets(i, 1), 0));
                REQUIRE(uv(uv_indices(i, 2), 0) == vertices(facets(i, 2), 0));

                REQUIRE(uv(uv_indices(i, 0), 1) == vertices(facets(i, 0), 1));
                REQUIRE(uv(uv_indices(i, 1), 1) == vertices(facets(i, 1), 1));
                REQUIRE(uv(uv_indices(i, 2), 1) == vertices(facets(i, 2), 1));
            }
        }

        SECTION("Inconsistent UV")
        {
            attr << 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.1, 1.0, 1.0, 0.0, 1.0, 1.0;
            mesh->add_corner_attribute(attr_name);
            mesh->import_corner_attribute(attr_name, attr);
            map_corner_attribute_to_indexed_attribute(*mesh, attr_name);
            mesh->remove_corner_attribute(attr_name);

            REQUIRE(mesh->is_uv_initialized());

            const auto& uv = mesh->get_uv();
            REQUIRE(uv.rows() == 5);
            const auto& uv_indices = mesh->get_uv_indices();
            REQUIRE(uv_indices.rows() == 2);
        }
    }
}


#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE JPetEventTypeTest
#include <boost/test/unit_test.hpp>

#include "./JPetEventType/JPetEventType.h"

BOOST_AUTO_TEST_SUITE(FirstSuite)

BOOST_AUTO_TEST_CASE( JPetEventTest )
{
  auto eventType1 = JPetEventType::kUnknown;
  auto eventType2 = JPetEventType::k2Gamma;
  auto eventTypeRes = eventType1 | eventType2;
  BOOST_REQUIRE((eventTypeRes & JPetEventType::kUnknown) == JPetEventType::kUnknown);
  BOOST_REQUIRE((eventTypeRes & JPetEventType::k2Gamma) == JPetEventType::k2Gamma);
  BOOST_REQUIRE((eventTypeRes & JPetEventType::k2Gamma) != JPetEventType::k3Gamma);
}

BOOST_AUTO_TEST_CASE( JPetEventTest2 )
{
  auto eventType1 = JPetEventType::kUnknown | JPetEventType::k2Gamma | JPetEventType::kPrompt;
  auto eventTypeRes = eventType1 & ~JPetEventType::k2Gamma; /// unset flag
  BOOST_REQUIRE((eventTypeRes & JPetEventType::kUnknown) == JPetEventType::kUnknown);
  BOOST_REQUIRE((eventTypeRes & JPetEventType::k2Gamma) != JPetEventType::k2Gamma);
  BOOST_REQUIRE((eventTypeRes & JPetEventType::kPrompt) == JPetEventType::kPrompt);
}

BOOST_AUTO_TEST_SUITE_END()


// Andrew Naplavkov

#ifndef BARK_GEOMETRY_GRAMMAR_HPP
#define BARK_GEOMETRY_GRAMMAR_HPP

#include <bark/geometry/detail/utility.hpp>
#include <bark/geometry/geometry.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/spirit/include/qi.hpp>
#include <type_traits>

#define BARK_GEOMETRY_GRAMMAR_CTOR(T)                                     \
    template <class Dumb = result_type,                                   \
              std::enable_if_t<std::is_same_v<Dumb, T>, void*> = nullptr> \
    grammar() : grammar::base_type(T##_tagged_text)                       \
    {                                                                     \
        init();                                                           \
    }

#define BARK_GEOMETRY_GRAMMAR_CHAIN_DO(item, actor) \
    '(' >> item[actor] >> *(',' >> item[actor]) >> ')'

#define BARK_GEOMETRY_GRAMMAR_CHAIN(item) \
    BARK_GEOMETRY_GRAMMAR_CHAIN_DO(item, phoenix::push_back(qi::_val, qi::_1))

namespace bark::geometry {

namespace phoenix = boost::phoenix;
namespace qi = boost::spirit::qi;

template <class T>
using rule = qi::rule<const char*, T(), qi::blank_type>;

/// BNF Productions for Two-Dimension Geometry WKT
template <class T>
class grammar : public qi::grammar<const char*, T(), qi::blank_type> {
    using result_type = T;

    rule<double> x;
    rule<double> y;
    rule<point> point_;
    rule<point> point_text;
    rule<point> point_tagged_text;
    rule<linestring> linestring_text;
    rule<linestring> linestring_tagged_text;
    rule<polygon> polygon_text;
    rule<polygon> polygon_tagged_text;
    rule<multi_point> multi_point_text;
    rule<multi_point> multi_point_tagged_text;
    rule<multi_linestring> multi_linestring_text;
    rule<multi_linestring> multi_linestring_tagged_text;
    rule<multi_polygon> multi_polygon_text;
    rule<multi_polygon> multi_polygon_tagged_text;
    rule<geometry_collection> geometry_collection_text;
    rule<geometry_collection> geometry_collection_tagged_text;
    rule<geometry> geometry_tagged_text;

    void init()
    {
        geometry_tagged_text =
            point_tagged_text | linestring_tagged_text | polygon_tagged_text |
            multi_point_tagged_text | multi_linestring_tagged_text |
            multi_polygon_tagged_text | geometry_collection_tagged_text;
        point_tagged_text = qi::no_case["point"] >> point_text;
        linestring_tagged_text = qi::no_case["linestring"] >> linestring_text;
        polygon_tagged_text = qi::no_case["polygon"] >> polygon_text;
        multi_point_tagged_text = qi::no_case["multipoint"] >> multi_point_text;
        multi_linestring_tagged_text =
            qi::no_case["multilinestring"] >> multi_linestring_text;
        multi_polygon_tagged_text =
            qi::no_case["multipolygon"] >> multi_polygon_text;
        geometry_collection_tagged_text =
            qi::no_case["geometrycollection"] >> geometry_collection_text;
        point_text = '(' >> point_ >> ')';
        point_ = x[phoenix::bind(&point::set<0>, qi::_val, qi::_1)] >>
                 y[phoenix::bind(&point::set<1>, qi::_val, qi::_1)];
        x = qi::double_;
        y = qi::double_;
        linestring_text = BARK_GEOMETRY_GRAMMAR_CHAIN(point_);
        polygon_text = BARK_GEOMETRY_GRAMMAR_CHAIN_DO(
            linestring_text, phoenix::bind(&push_ring, qi::_val, qi::_1));
        multi_point_text = BARK_GEOMETRY_GRAMMAR_CHAIN(point_text);
        multi_linestring_text = BARK_GEOMETRY_GRAMMAR_CHAIN(linestring_text);
        multi_polygon_text = BARK_GEOMETRY_GRAMMAR_CHAIN(polygon_text);
        geometry_collection_text =
            BARK_GEOMETRY_GRAMMAR_CHAIN(geometry_tagged_text);
    }

public:
    BARK_GEOMETRY_GRAMMAR_CTOR(geometry);
    BARK_GEOMETRY_GRAMMAR_CTOR(point);
    BARK_GEOMETRY_GRAMMAR_CTOR(linestring);
    BARK_GEOMETRY_GRAMMAR_CTOR(polygon);
    BARK_GEOMETRY_GRAMMAR_CTOR(multi_point);
    BARK_GEOMETRY_GRAMMAR_CTOR(multi_linestring);
    BARK_GEOMETRY_GRAMMAR_CTOR(multi_polygon);
    BARK_GEOMETRY_GRAMMAR_CTOR(geometry_collection);
};

}  // namespace bark::geometry

#endif  // BARK_GEOMETRY_GRAMMAR_HPP


#include <boost/icl/separate_interval_set.hpp>


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#include <iostream>
#include <complex>
#include <cmath>

#include <boost/numeric/mtl/matrix/dense2D.hpp>
#include <boost/numeric/mtl/matrix/morton_dense.hpp>
#include <boost/numeric/mtl/matrix/compressed2D.hpp>
#include <boost/numeric/mtl/matrix/transposed_view.hpp>

#include <boost/numeric/mtl/recursion/predefined_masks.hpp>
#include <boost/numeric/mtl/operation/print.hpp>
#include <boost/numeric/mtl/operation/set_to_zero.hpp>
#include <boost/numeric/mtl/operation/copy.hpp>

using namespace mtl::recursion;
using namespace std;

template <typename Matrix>
void init_matrix(Matrix& matrix, int offset= 0)
{
    set_to_zero(matrix);
    mtl::mat::inserter<Matrix> ins(matrix);
    for (unsigned i= 0; i < matrix.num_rows(); i++)
	for (unsigned j= 0; j < matrix.num_cols(); j++)
	    if ((i + j + offset) & 1)
		ins(i, j) << i + 2*j;
}

template <typename MatrixSrc, typename MatrixDest>
void test(MatrixSrc& src, const char* name_src,
	  MatrixDest&, const char* name_dest)
{
    std::cout << "\nAssign " << name_src << " to " << name_dest << "\n";

    MatrixDest new_dest(3, 4);
    init_matrix(new_dest);
    std::cout << "dest initialized:\n" << new_dest << "\n";

    init_matrix(src, 1);
    std::cout << "source initialized:\n" << src << "\n";

    // mat::copy(src, new_dest);
    new_dest.change_dim(num_rows(src), num_cols(src));
    new_dest= src;
    std::cout << "dest after assignment:\n" << new_dest << "\n\n";

    MTL_THROW_IF(new_dest.num_rows() != 5 || new_dest.num_cols() != 7, mtl::runtime_error("wrong dimension"));
    typename MatrixDest::value_type zero(0.0), three(3.0);
    MTL_THROW_IF(new_dest(1, 2) != zero, mtl::runtime_error("m[1][2] should be zero"));
    MTL_THROW_IF(new_dest(1, 1) != three, mtl::runtime_error("m[1][1] should be three"));

}

int main(int, char**)
{
    using namespace mtl;

    dense2D<double>                                dr(5, 7);
    dense2D<double, mat::parameters<col_major> > dc(5, 7);
    dense2D<std::complex<double> >                 cdr(5, 7);
    morton_dense<double,  morton_mask>             md(5, 7);
    morton_dense<double,  doppled_16_row_mask>     d16r(5, 7);
    compressed2D<double>                           comp(5, 7);
    compressed2D<std::complex<double> >            ccomp(5, 7);

    test(dc, "Dense column major", dc, "Dense column major");
    test(dc, "Dense column major", cdr, "Complex dense row major");
    test(dc, "Dense column major", md, "Morton N-order");
    test(dc, "Dense column major", d16r, "Hybrid 16 row-major");
    test(dc, "Dense column major", comp, "compressed2D");
    test(dc, "Dense column major", ccomp, "complex compressed2D");

    test(d16r, "Hybrid 16 row-major", dr, "Dense row major");
    test(d16r, "Hybrid 16 row-major", dc, "Dense column major");
    test(d16r, "Hybrid 16 row-major", cdr, "Complex dense row major");
    test(d16r, "Hybrid 16 row-major", md, "Morton N-order");
    test(d16r, "Hybrid 16 row-major", d16r, "Hybrid 16 row-major");
    test(d16r, "Hybrid 16 row-major", comp, "compressed2D");
    test(d16r, "Hybrid 16 row-major", ccomp, "complex compressed2D");

    test(comp, "compressed2D", dr, "Dense row major");
    test(comp, "compressed2D", dc, "Dense column major");
    test(comp, "compressed2D", cdr, "Complex dense row major");
    test(comp, "compressed2D", md, "Morton N-order");
    test(comp, "compressed2D", d16r, "Hybrid 16 row-major");
    test(comp, "compressed2D", comp, "compressed2D");
    test(comp, "compressed2D", ccomp, "complex compressed2D");

    return 0;
}


// Copyright (C) 2016 Gideon May (gideon@borges.xyz)
//
// Permission to copy, use, sell and distribute this software is granted
// provided this copyright notice appears in all copies.
// Permission to modify the code and to distribute modified code is granted
// provided this copyright notice appears in all copies, and a notice
// that the code was modified is included with the copyright notice.
//
// This software is provided "as is" without express or implied warranty,
// and with no claim as to its suitability for any purpose.

#include <boost/python.hpp>

#include <sstream>
#include <string>
#include <iostream>

#include <osg/Quat>
#include <osg/Matrix>

#include "Math.hpp"

using namespace boost::python;

namespace {
osg::Quat* Quat_tuple(tuple data)
{
    osg::Quat::value_type x = 0, y = 0, z = 0, a = 1;

    // Fall through intentionally!
    switch (len(data)) {
        case 4: a = extract<osg::Quat::value_type>(data[3]);
        case 3: z = extract<osg::Quat::value_type>(data[2]);
        case 2: y = extract<osg::Quat::value_type>(data[1]);
        case 1: x = extract<osg::Quat::value_type>(data[0]);
        default: break;

    }
    return new osg::Quat(x, y, z, a);
}

void set_tuple(osg::Quat& self, tuple data)
{
    osg::Quat::value_type x = 0, y = 0, z = 0, a = 1;

    // Fall through intentionally!
    switch (len(data)) {
        case 4: a = extract<osg::Quat::value_type>(data[3]);
        case 3: z = extract<osg::Quat::value_type>(data[2]);
        case 2: y = extract<osg::Quat::value_type>(data[1]);
        case 1: x = extract<osg::Quat::value_type>(data[0]);
        default: break;
    }
    self.set(x, y, z, a);
}

std::string osgQuat_str(osg::Quat * self)
{
    std::ostringstream ost;
    ost << "(" << self->_v[0] << " " << self->_v[1] << " " << self->_v[2] << " " << self->_v[3] << ")";
    return ost.str();
}

std::string osgQuat_repr(osg::Quat * self)
{
    std::ostringstream ost;
    ost << "Quat(" << self->_v[0] << "," << self->_v[1] << "," << self->_v[2] << "," << self->_v[3] << ")";
    return ost.str();
}

const tuple osgQuat_getRotate(osg::Quat* q)
{
    osg::Quat::value_type angle, x, y, z;
    q->getRotate(angle, x, y, z);
    return boost::python::make_tuple(angle, x, y, z);
}

void set(osg::Quat * self, osg::Quat& from) { * self = from; }

void __setitem__(osg::Quat * self, int idx, osg::Quat::value_type val) { (*self)[idx] = val; }

osg::Quat::value_type get_x(osg::Quat * self) { return self->_v[0]; }
void  set_x(osg::Quat * self, osg::Quat::value_type x) { self->_v[0] = x; }

osg::Quat::value_type get_y(osg::Quat * self) { return self->_v[1]; }
void  set_y(osg::Quat * self, osg::Quat::value_type y) { self->_v[1] = y; }

osg::Quat::value_type get_z(osg::Quat * self) { return self->_v[2]; }
void  set_z(osg::Quat * self, osg::Quat::value_type z) { self->_v[2] = z; }

osg::Quat::value_type get_w(osg::Quat * self) { return self->_v[3]; }
void  set_w(osg::Quat * self, osg::Quat::value_type a) { self->_v[3] = a; }
}

namespace PyOSG {
void init_Quat()
{
    class_<osg::Quat> quat("Quat");
    quat
        .def(init<osg::Quat::value_type, osg::Quat::value_type, osg::Quat::value_type, osg::Quat::value_type>())
        .def(init<const osg::Vec4&>())
        .def(init<osg::Quat::value_type, const osg::Vec3&>())
        .def(init<osg::Quat::value_type, const osg::Vec3&, osg::Quat::value_type, const osg::Vec3&, osg::Quat::value_type, const osg::Vec3&>())
        .def("__init__", make_constructor(Quat_tuple))
    ;quat
        .def("asVec4", (osg::Vec4&(osg::Quat::*)()) &osg::Quat::asVec4, return_internal_reference<>())
        .def("asVec3", (osg::Vec3&(osg::Quat::*)()) &osg::Quat::asVec3, return_value_policy<return_by_value>())
        .def("set", (void (osg::Quat::*)(osg::Quat::value_type, osg::Quat::value_type, osg::Quat::value_type, osg::Quat::value_type))&osg::Quat::set)
        .def("set", (void (osg::Quat::*)(const osg::Vec4&))&osg::Quat::set)
        .def("set", &set_tuple)
#ifdef OSG_USE_DOUBLE_MATRICES
        .def("set", (void (osg::Quat::*)(const osg::Matrixd&))&osg::Quat::set)
#else
        .def("set", (void (osg::Quat::*)(const osg::Matrixf&))&osg::Quat::set)
#endif
    ;quat
        .def("__getitem__", (osg::Quat::value_type &(osg::Quat::*)(int)) &osg::Quat::operator[], return_value_policy<copy_non_const_reference>())
    ;quat
        .def("__setitem__", &__setitem__)
    ;quat

        .def("x", (osg::Quat::value_type (osg::Quat::*)() const)&osg::Quat::x)
        .def("y", (osg::Quat::value_type (osg::Quat::*)() const)&osg::Quat::y)
        .def("z", (osg::Quat::value_type (osg::Quat::*)() const)&osg::Quat::z)
        .def("w", (osg::Quat::value_type (osg::Quat::*)() const)&osg::Quat::w)

        .add_property("_x", &get_x, &set_x)
        .add_property("_y", &get_y, &set_y)
        .add_property("_z", &get_z, &set_z)
        .add_property("_w", &get_w, &set_w)

        .def("zeroRotation", &osg::Quat::zeroRotation,
            "return true if the Quat represents a zero rotation, and therefore can be ignored in computations.")

        .def(self * osg::Quat::value_type())
        .def(self *= osg::Quat::value_type())
        .def(self * self)
        .def(self *= self)

        .def(self / osg::Quat::value_type())
        .def(self /= osg::Quat::value_type())
        .def(self / self)
        .def(self /= self)

        .def(self + self)
        .def(self += self)

        .def(self - self)
        .def(self -= self)
        .def(-self)

        .def("length", &osg::Quat::length,
            "Length of the quaternion = sqrt( vec . vec )")

        .def("length2", &osg::Quat::length2,
            "Length of the quaternion =  vec . vec ")

        .def("conj", &osg::Quat::conj, "Conjugate")

        .def("inverse", &osg::Quat::inverse,
            "Multiplicative inverse method: q^(-1) = q^*/(q.q^*)")

        .def("makeRotate", (void (osg::Quat::*)(osg::Quat::value_type, osg::Quat::value_type, osg::Quat::value_type, osg::Quat::value_type))&osg::Quat::makeRotate)
        .def("makeRotate", (void (osg::Quat::*)(osg::Quat::value_type, const osg::Vec3&))&osg::Quat::makeRotate)
        .def("makeRotate", (void (osg::Quat::*)(const osg::Vec3&, const osg::Vec3&))&osg::Quat::makeRotate,
            "Make a rotation Quat which will rotate vec1 to vec2.\n"
            "Generally take adot product to get the angle between these\n"
            "and then use a cross product to get the rotation axis\n"
            "Watch out for the two special cases of when the vectors\n"
            "are co-incident or opposite in direction.\n")

        .def("getRotate", &osgQuat_getRotate)

        .def("slerp", &osg::Quat::slerp,
            "Spherical Linear Interpolation.\n"
            "As t goes from 0 to 1, the Quat object goes from 'from' to 'to'. \n")
#ifdef OSG_USE_DOUBLE_MATRICES
        .def("set", (void(osg::Quat::*)(const osg::Matrixd&)) &osg::Quat::set,
#else
        .def("set", (void(osg::Quat::*)(const osg::Matrixf&)) &osg::Quat::set,
#endif
            "Set quaternion to be equivalent to specified matrix.")
#ifdef OSG_USE_DOUBLE_MATRICES
        .def("get", (void (osg::Quat::*)(osg::Matrixd&) const) &osg::Quat::get,
#else
        .def("get", (void (osg::Quat::*)(osg::Matrixf&) const) &osg::Quat::get,
#endif
            "Get the equivalent matrix for this quaternion.")

        .def("__str__", &osgQuat_str)
        .def("__repr__", &osgQuat_repr)
        ;
}
} // namespace


//
// Created by Hamza El-Kebir on 4/21/21.
//

#ifndef LODESTAR_FIXEDGAIN_HPP
#define LODESTAR_FIXEDGAIN_HPP

#include <Eigen/Dense>

namespace ls {
    namespace control {
        class FixedGain {
        public:
            FixedGain() : gain(new Eigen::MatrixXd)
            {}

            Eigen::MatrixXd *gain;
        };
    }
}

#endif //LODESTAR_FIXEDGAIN_HPP


// Author(s): Jan Friso Groote
// Copyright: see the accompanying file COPYING or copy at
// https://github.com/mCRL2org/mCRL2/blob/master/COPYING
//
// 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)
//
/// \file linearization_test.cpp
/// \brief Add your file description here.

#define BOOST_TEST_MODULE linearization_test
#include <boost/test/included/unit_test_framework.hpp>

#include <iostream>
#include <string>
#include "mcrl2/data/detail/rewrite_strategies.h"
#include "mcrl2/lps/linearise.h"
#include "mcrl2/lts/detail/exploration.h"
#include "mcrl2/lts/detail/exploration_strategy.h"
#include "mcrl2/lts/lts_aut.h"
#include "mcrl2/lts/lts_algorithm.h"
#include "mcrl2/utilities/logger.h"
#include "mcrl2/utilities/test_utilities.h"

using namespace mcrl2;
using namespace mcrl2::lts;

typedef data::rewriter::strategy rewrite_strategy;
typedef std::vector<rewrite_strategy> rewrite_strategy_vector;

template<class LTS_TYPE>
inline
LTS_TYPE translate_lps_to_lts(const lps::specification& specification)
{
  lts_generation_options options;
  // options.trace_prefix = "linearization_instantiation_compare_test";
  options.specification = specification;
  options.filename = utilities::temporary_filename("linearization_instantiation_compare_test_file");

  LTS_TYPE result;
  options.outformat = result.type();

  if (options.use_enumeration_caching)
  {
    lps2lts_algorithm<lps::cached_next_state_generator> lps2lts;
    lps2lts.generate_lts(options);
  }
  else
  {
    lps2lts_algorithm<lps::next_state_generator> lps2lts;
    lps2lts.generate_lts(options);
  }

  result.load(options.filename);
  std::remove(options.filename.c_str()); // Clean up after ourselves
  return result;
}

static
void run_linearisation_instance(const std::string& spec, const lps::t_lin_options& options,
                                const lts_aut_t& expected_statespace)
{
  std::clog << "  Linearisation method " << options.lin_method << std::endl
            << "    rewrite strategy: " << options.rewrite_strategy << std::endl
            << "    binary: " << std::boolalpha << options.binary << std::endl
            << "    nocluster: " << std::boolalpha << options.no_intermediate_cluster << std::endl;

  lps::specification s = lps::remove_stochastic_operators(lps::linearise(spec, options));
  BOOST_CHECK(s != lps::specification());

  lts_aut_t result = translate_lps_to_lts<lts_aut_t>(s);
  BOOST_CHECK(compare(result, expected_statespace, lts_eq_bisim));
}

static
void run_linearisation_test_case(const std::string& spec, const lts_aut_t& expected_statespace)
{
  // Set various rewrite strategies
  rewrite_strategy_vector rewrite_strategies = data::detail::get_test_rewrite_strategies(false);

  for (auto rewrite_strategy : rewrite_strategies)
  {
    lps::t_lin_options options;
    options.rewrite_strategy = rewrite_strategy;

    run_linearisation_instance(spec, options, expected_statespace);

    options.lin_method = lps::lmRegular2;
    run_linearisation_instance(spec, options, expected_statespace);

    options.lin_method = lps::lmStack;
    run_linearisation_instance(spec, options, expected_statespace);

    options.binary = true;
    run_linearisation_instance(spec, options, expected_statespace);

    options.lin_method = lps::lmRegular;
    run_linearisation_instance(spec, options, expected_statespace);

    options.binary = false; // reset binary
    options.no_intermediate_cluster = true;
    run_linearisation_instance(spec, options, expected_statespace);
  }
}

BOOST_AUTO_TEST_CASE(bad_renaming_non_bisimilar)
{
  const std::string spec =
          "sort D = struct d1;\n"
          "act a, b;\n"
          "proc\n"
          "P(p:D) = a. Q(p);\n"
          "Q(q:D) = sum l:List(D). (#l<=1) ->\n"
          "             b.\n"
          "            (([] == l) -> P(q) <>  Q());\n"
          "init P(d1);\n";

  const std::string expected_statespace =
          "des (0,6,4)\n"
          "(0,\"a\",1)\n"
          "(1,\"b\",2)\n"
          "(1,\"b\",3)\n"
          "(2,\"a\",1)\n"
          "(3,\"b\",2)\n"
          "(3,\"b\",3)\n";
  std::stringstream is(expected_statespace);

  lts_aut_t statespace;
  statespace.load(is);
  run_linearisation_test_case(spec, statespace);
}

BOOST_AUTO_TEST_CASE(where_clauses_in_conditions_of_rewrite_rules)
{
  const std::string spec =
          "act a:Bool;\n"
          "\n"
          "map is_null:List(Bool) -> Bool;\n"
          "\n"
          "var  L:List(Bool);\n"
          "eqn  (n>0 whr n=#L end) -> is_null(L)=false;\n"
          "     (n==0 whr n=#L end) -> is_null(L)= true;\n"
          "\n"
          "proc X(L:List(Bool))=a(is_null(L)).X([true]);\n"
          "init X([]);\n";

  const std::string expected_statespace =
          "des (0,2,2)\n"
          "(0,\"a(true)\",1)\n"
          "(1,\"a(false)\",1)\n";

  std::stringstream is(expected_statespace);

  lts_aut_t statespace;
  statespace.load(is);
  run_linearisation_test_case(spec, statespace);
}


/**
 * Copyright (c) 2009 Carnegie Mellon University.
 *     All rights reserved.
 *
 *  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.
 *
 * For more about this software visit:
 *
 *      http://www.graphlab.ml.cmu.edu
 *
 */

#include <vector>
#include <string>
#include <fstream>
#include <limits.h>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_stl.hpp>
#include <boost/unordered_set.hpp>

#include <graphlab.hpp>

#include <graphlab/util/stl_util.hpp>

#include <graphlab/macros_def.hpp>
#include <graphlab/ui/metrics_server.hpp>

/**
 * \brief The type used to measure distances in the graph.
 */
typedef float distance_type;

typedef int		wiki_page_type;

/**
 * \brief The current distance of the vertex.
 */
struct vertex_data {
  distance_type dist;
	wiki_page_type type;
	boost::unordered_set<graphlab::vertex_id_type> vid_set;

  vertex_data(wiki_page_type type = 0, distance_type dist = std::numeric_limits<distance_type>::max()) :
    dist(dist), type(type) { 		}

	void save(graphlab::oarchive& oarc) const {
		oarc << dist << type << vid_set;
	}

	void load(graphlab::iarchive& iarc) {
		iarc >> dist >> type >> vid_set;
	}

};

/**
 * \brief The distance associated with the edge.
 */
struct edge_data : graphlab::IS_POD_TYPE {
  distance_type dist;
  edge_data(distance_type dist = 1) : dist(dist) { }
}; // end of edge data

/**
 * \brief The graph type encodes the distances between vertices and
 * edges
 */
typedef graphlab::distributed_graph<vertex_data, edge_data> graph_type;

/**
 * \brief Get the other vertex in the edge.
 */
inline graph_type::vertex_type
get_other_vertex(const graph_type::edge_type& edge,
                 const graph_type::vertex_type& vertex) {
  return vertex.id() == edge.source().id()? edge.target() : edge.source();
}

/**
 * \brief Use directed or undireced edges.
 */
bool DIRECTED_SSSP = false;

/**
 * \brief This class is used as the gather type.
 */
struct min_distance_type : graphlab::IS_POD_TYPE {
  distance_type dist;
  min_distance_type(distance_type dist =
                    std::numeric_limits<distance_type>::max()) : dist(dist) { }
  min_distance_type& operator+=(const min_distance_type& other) {
    dist = std::min(dist, other.dist);
    return *this;
  }
};

bool PER_VERTEX_COUNT = false;

struct triple : graphlab::IS_POD_TYPE {
	graphlab::vertex_id_type vid;
	distance_type from_last_art;
	distance_type from_src;

	triple(): vid(0) {}

	triple(graphlab::vertex_id_type a, distance_type b, distance_type c){
		vid =a;
		from_last_art = c;
		from_src = b;
	}

};

struct set_union_gather {
	  boost::unordered_set<graphlab::vertex_id_type> vid_set;

		  /*
			 * * Combining with another collection of vertices.
			 * * Union it into the current set.
			 * */
			  set_union_gather& operator+=(const set_union_gather& other) {
					    foreach(graphlab::vertex_id_type othervid, other.vid_set) {
								      vid_set.insert(othervid);
						    }
						    return *this;
			  }

			  // serialize
			  void save(graphlab::oarchive& oarc) const {
				       oarc << vid_set;
	        }

	      // deserialize
	      void load(graphlab::iarchive& iarc) {
	             iarc >> vid_set;
         }
 };

struct our_msg {

std::vector<triple> collection;

our_msg& operator+=(const our_msg& other){

	std::map<graphlab::vertex_id_type, std::pair<distance_type,distance_type> > temp;

	for(int i=0; i< other.collection.size(); i++){
   temp[other.collection[i].vid]=std::make_pair(other.collection[i].from_src,other.collection[i].from_last_art);
	}
	for(int i=0; i< collection.size(); i++){
		if(temp.find(collection[i].vid) != temp.end()){
     if(temp[collection[i].vid].first< collection[i].from_src){
        collection[i].from_src= temp[collection[i].vid].first;
		    collection[i].from_last_art = temp[collection[i].vid].second;
				temp[collection[i].vid].first = -2; // just to mark it
	  	}
		}
	}
	std::map<graphlab::vertex_id_type,std::pair<distance_type,distance_type> >::iterator it;

	//put the ones not icnluded in the collection
	for( it = temp.begin(); it!= temp.end(); it++){
    if( (*it).second.first != -2 ){
			triple tp2(it->first,(*it).second.first,(*it).second.second);
		collection.push_back(tp2);
		}
	}
	return *this;

}
  // serialize
	 void save(graphlab::oarchive& oarc) const {
    oarc << collection;
	   }

	// deserialize
	void load(graphlab::iarchive& iarc) {
	iarc >> collection;

	}
};

class add_neighbours :
      public graphlab::ivertex_program<graph_type,
                                      set_union_gather>,
      /* I have no data. Just force it to POD */
      public graphlab::IS_POD_TYPE {
public:
  // Gather on all edges
  edge_dir_type gather_edges(icontext_type& context,
                             const vertex_type& vertex) const {
    return graphlab::IN_EDGES;
  }

  /*
* For each edge, figure out the ID of the "other" vertex
* and accumulate a set of the neighborhood vertex IDs.
*/
  gather_type gather(icontext_type& context,
                     const vertex_type& vertex,
                     edge_type& edge) const {
    set_union_gather gather;
    //std::cout<<vertex.id()<<" gather\n";
    // Insert the opposite end of the edge IF the opposite end has
    // ID greater than the current vertex
    // If we are getting per vertex counts, we need the entire neighborhood
    vertex_id_type otherid = edge.source().id() == vertex.id() ?
                             edge.target().id() : edge.source().id();
     if(vertex.data().type== 0 && edge.target().data().type == 0)
		 gather.vid_set.insert(otherid);
    return gather;
  }

  /*
* the gather result now contains the vertex IDs in the neighborhood.
* store it on the vertex.
*/
  void apply(icontext_type& context, vertex_type& vertex,
             const gather_type& neighborhood) {
    vertex.data().vid_set = neighborhood.vid_set;
  } // end of apply

  /*
* Scatter over all edges to compute the intersection.
* I only need to touch each edge once, so if I scatter just on the
* out edges, that is sufficient.
*/
  edge_dir_type scatter_edges(icontext_type& context,
                              const vertex_type& vertex) const {
    return graphlab::NO_EDGES;
  }

  // void scatter(icontext_type& context,
//                 const vertex_type& vertex,
//                 edge_type& edge) const {
//   not needed
//     }

};

class main_algo:
      public graphlab::ivertex_program<graph_type,
																			graphlab::empty,
                                      our_msg>  // our_msg is my message type not gather type

       {

 std::vector<triple> temp1;
public:
  void init(icontext_type& context, const vertex_type& vertex,
           const our_msg& msg) {
    temp1 = msg.collection;
  }

  // Gather on all edges
  edge_dir_type gather_edges(icontext_type& context,
                             const vertex_type& vertex) const {
    return graphlab::NO_EDGES;
  }

  void apply(icontext_type& context, vertex_type& vertex,
              const graphlab::empty& empty) {
    distance_type tp=INT_MAX;
  if(vertex.data().type==0){ // if a article
		for(int i=0; i<temp1.size();i++){
     if(temp1[i].from_src < tp){
			 tp=temp1[i].from_src;
	  	}
    }
		if( tp< vertex.data().dist){
			vertex.data().dist = tp;
    }
	}//end of if an article
	 else if(vertex.data().type==14){//its a category
    // dont do anything

	 }
  } // end of apply

  edge_dir_type scatter_edges(icontext_type& context,
                              const vertex_type& vertex) const {
    return graphlab::OUT_EDGES;
  }

  // void scatter(icontext_type& context,
//                 const vertex_type& vertex,
//                 edge_type& edge) const {
//   not needed
//     }

void scatter(icontext_type& context, const vertex_type& vertex,
               edge_type& edge) const {
    const vertex_type other = get_other_vertex(edge, vertex);

//		std::cout<<"scattering to "<<other.id()<<"\n";
if(other.data().type==14 && vertex.data().type == 14) {//category to category
	our_msg for_cat;

	for(int i=0; i< temp1.size(); i++){
    if(temp1[i].from_last_art <12){
	   	triple tp2(temp1[i].vid,temp1[i].from_src +1,temp1[i].from_last_art+1);
      for_cat.collection.push_back(tp2); //updating distance
	   }
	 }
	if(for_cat.collection.size() !=0)
	context.signal(other,for_cat);
}
else if ( other.data().type == 0 && vertex.data().type==14){//category to article
    our_msg for_art;
		for(int i=0; i<temp1.size(); i++){
      if(other.data().vid_set.find( temp1[i].vid) != other.data().vid_set.end()){// It exists in the neighbours
       triple tp2(temp1[i].vid,temp1[i].from_src+1,temp1[i].from_last_art+1);
			 for_art.collection.push_back(tp2);
			}
		}
		if(for_art.collection.size() !=0)
		context.signal(other,for_art);
}
else if( other.data().type==14 && vertex.data().type ==0){//article to category
 our_msg for_cat1;
 triple tp2(vertex.id(),vertex.data().dist,0);
 for_cat1.collection.push_back(tp2);
 context.signal(other,for_cat1);
}

  } // end of scatter

// serialize
	 void save(graphlab::oarchive& oarc) const {
    oarc << temp1;
	   }

	// deserialize
	void load(graphlab::iarchive& iarc) {
	iarc >> temp1;
	}

};

/**
 * \brief We want to save the final graph so we define a write which will be
 * used in graph.save("path/prefix", pagerank_writer()) to save the graph.
 */
//------Output of the final graph----------
struct shortest_path_writer {
  std::string save_vertex(const graph_type::vertex_type& vtx) {
    std::stringstream strm;
    if(vtx.data().dist != std::numeric_limits<distance_type>::max() )
		strm << vtx.id() << "\t"<<"ns:"<<"\t"<<vtx.data().type<<"\t"<<vtx.data().dist<<"\n";
    /* boost::unordered_set<graphlab::vertex_id_type>::iterator it;
		 for( it = vtx.data().vid_set.begin(); it !=vtx.data().vid_set.end(); it++){
        strm<<*it<<" ";
		 }*/

    return strm.str();
  }
  std::string save_edge(graph_type::edge_type e) { return ""; }
}; // end of shortest_path_writer

struct graph_writer {
  std::string save_vertex(const graph_type::vertex_type& vtx) {
   return "";
  }
  std::string save_edge(graph_type::edge_type e) {

		 std::stringstream strm;
		 strm<<e.source().data().type<<" "<<e.target().data().type<<"\n";
		 return strm.str();
		 }
}; // end of shortest_path_writer

struct max_deg_vertex_reducer: public graphlab::IS_POD_TYPE {
  size_t degree;
  graphlab::vertex_id_type vid;
  max_deg_vertex_reducer& operator+=(const max_deg_vertex_reducer& other) {
    if (degree < other.degree) {
      (*this) = other;
    }
    return (*this);
  }
};

max_deg_vertex_reducer find_max_deg_vertex(const graph_type::vertex_type vtx) {
  max_deg_vertex_reducer red;
  red.degree = vtx.num_in_edges() + vtx.num_out_edges();
  red.vid = vtx.id();
  return red;
}

bool line_parser_art(graph_type& graph,
                 const std::string& filename,
                 const std::string& textline) {
  std::stringstream strm(textline);
  graphlab::vertex_id_type vid;
	graphlab::vertex_id_type tid;
  wiki_page_type type =1;

	// first entry in the line is a vertex ID
  strm >> vid;
  strm >> tid;

 // std::cout<<textline<<std::endl;
	//dc.cout()<<textline<<std::endl;
  if(tid!=vid){

	//	std::cout<<"one \n"
 //	std::cout<<"adding edges \n";
		graph.add_edge(vid, tid);
    //std::cout<<"two \n";
	}
  return true;
}

bool line_parser_categ (graph_type& graph,
                 const std::string& filename,
                 const std::string& textline) {
  std::stringstream strm(textline);
  graphlab::vertex_id_type vid;
	graphlab::vertex_id_type tid;
  wiki_page_type type =1;

	// first entry in the line is a vertex ID
  strm >> vid;
  strm >> tid;

 // std::cout<<textline<<std::endl;
	//dc.cout()<<textline<<std::endl;
  if(tid!=vid){

	//	std::cout<<"one \n"
 //	std::cout<<"adding edges \n";
		graph.add_edge(vid, tid);
		graph.add_edge(tid,vid);
    //std::cout<<"two \n";
	}
  return true;
}
bool all_vertex_parser(graph_type& graph,
                  const std::string& filename,
                  const std::string& textline){
std::stringstream strm(textline);
graphlab::vertex_id_type vid;
wiki_page_type type;

// first entry in the line is a vertex ID
strm >> vid;
strm >> type;

// std::cout<<textline<<std::endl;
//dc.cout()<<textline<<std::endl;
  if(type==0 || type ==14)
  graph.add_vertex(vid, vertex_data(type));

	return true;

}
int main(int argc, char** argv) {
  // Initialize control plain using mpi
  graphlab::mpi_tools::init(argc, argv);
  graphlab::distributed_control dc;
  global_logger().set_log_level(LOG_INFO);

  // Parse command line options -----------------------------------------------
  graphlab::command_line_options
    clopts("Single Source Shortest Path Algorithm.");
  std::string graph_dir;
  std::string format = "adj";
  std::string exec_type = "synchronous";
  size_t powerlaw = 0;
  std::vector<graphlab::vertex_id_type> sources;
  bool max_degree_source = false;
  clopts.attach_option("graph", graph_dir,
                       "The graph file.  If none is provided "
                       "then a toy graph will be created");
  clopts.add_positional("graph");
  clopts.attach_option("format", format,
                       "graph format");
  clopts.attach_option("source", sources,
                       "The source vertices");
  clopts.attach_option("max_degree_source", max_degree_source,
                       "Add the vertex with maximum degree as a source");

  clopts.add_positional("source");

  clopts.attach_option("directed", DIRECTED_SSSP,
                       "Treat edges as directed.");

  clopts.attach_option("engine", exec_type,
                       "The engine type synchronous or asynchronous");

  clopts.attach_option("powerlaw", powerlaw,
                       "Generate a synthetic powerlaw out-degree graph. ");
  std::string saveprefix;
  clopts.attach_option("saveprefix", saveprefix,
                       "If set, will save the resultant pagerank to a "
                       "sequence of files with prefix saveprefix");

  if(!clopts.parse(argc, argv)) {
    dc.cout() << "Error in parsing command line arguments." << std::endl;
    return EXIT_FAILURE;
  }

  // Build the graph ----------------------------------------------------------
  graph_type graph(dc, clopts);

//	dc.cout() << "Loading graph in format: "<< format << std::endl;
  //Loading pages.txt for all the vertices!!
  graph.load("hdfs://dsg2.crc.nd.edu/data/enwiki/page.txt" , all_vertex_parser);

  graph.load("hdfs://dsg2.crc.nd.edu/data/enwiki/pagelinks10m.txt", line_parser_art);

  graph.load("hdfs://dsg2.crc.nd.edu/data/enwiki/categorylinks.txt",line_parser_categ);
	dc.cout()<<"Sources are---------after "<< sources[0]<<std::endl;
	// must call finalize before querying the graph
  graph.finalize();
  dc.cout() << "#vertices:  " << graph.num_vertices() << std::endl
            << "#edges:     " << graph.num_edges() << std::endl;

  if(sources.empty()) {
    if (max_degree_source == false) {
      dc.cout()
        << "No source vertex provided. Adding vertex 0 as source"
        << std::endl;
      sources.push_back(0);
    }
  }

  if (max_degree_source) {
    max_deg_vertex_reducer v = graph.map_reduce_vertices<max_deg_vertex_reducer>(find_max_deg_vertex);
    dc.cout()
      << "No source vertex provided.  Using highest degree vertex " << v.vid << " as source."
      << std::endl;
    sources.push_back(v.vid);
  }
  // Running The Engine -------------------------------------------------------
  sources.clear();
  sources.push_back(303);
	graphlab::omni_engine<add_neighbours> engine(dc, graph, exec_type, clopts);
  engine.signal_all();
	engine.start();
 /*
	saveprefix = "/home/anambiar/algo_results/edges";
  if (saveprefix != "") {
    graph.save(saveprefix, graph_writer(),
               false,    // do not gzip
               false,     // save vertices
               true);   // do not save edges
  }
*/

	graphlab::omni_engine<main_algo> engine2(dc, graph, exec_type, clopts);
  our_msg init_msg;
 triple tp2(sources[0],0,0);
 init_msg.collection.push_back(tp2);

  engine2.signal(sources[0], init_msg);

  //Signal all the vertices in the source set
 /* for(size_t i = 0; i < sources.size(); ++i) {
    engine.signal(sources[i], min_distance_type(0));
  }
	*/
	//NO SIGNALLING CORRECT IT
  /*engine.signal(sources[0], min_distance_type(0));
	 *
	 * */
  engine2.start();
  const float runtime = engine2.elapsed_seconds();
  dc.cout() << "Finished Running engine in " << runtime
            << " seconds." << std::endl;

  // Save the final graph -----------------------------------------------------
  saveprefix = "/home/anambiar/algo_results/Yay";
  if (saveprefix != "") {
    graph.save(saveprefix, shortest_path_writer(),
               false,    // do not gzip
               true,     // save vertices
               false);   // do not save edges
  }

  // Tear-down communication layer and quit -----------------------------------
  graphlab::mpi_tools::finalize();

  return EXIT_SUCCESS;
} // End of main

// We render this entire program in the documentation


//==================================================================================================
/*!
  @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_IS_NOT_NAN_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_IS_NOT_NAN_HPP_INCLUDED

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

  /*!
    @ingroup group-predicates

    This function object returns @ref False or @ref True according x is @ref Nan or not.

    @par Header <boost/simd/function/is_not_nan.hpp>

    @par Note

     Using `is_not_nan(x)` is similar to: `x == x`

    @par Example:

      @snippet is_not_nan.cpp is_not_nan

    @par Possible output:

      @snippet is_not_nan.txt is_not_nan

  **/
  as_logical_t<Value> is_not_nan(Value const& x);
} }
#endif

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

#endif


//          Copyright Carl Philipp Reh 2009 - 2016.
// 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 FCPPT_CONTAINER_GRID_POS_ITERATOR_DECL_HPP_INCLUDED
#define FCPPT_CONTAINER_GRID_POS_ITERATOR_DECL_HPP_INCLUDED

#include <fcppt/container/grid/min.hpp>
#include <fcppt/container/grid/pos.hpp>
#include <fcppt/container/grid/pos_iterator_fwd.hpp>
#include <fcppt/container/grid/size_type.hpp>
#include <fcppt/container/grid/sup.hpp>
#include <fcppt/container/grid/detail/pos_iterator_base.hpp>
#include <fcppt/preprocessor/disable_gcc_warning.hpp>
#include <fcppt/preprocessor/pop_warning.hpp>
#include <fcppt/preprocessor/push_warning.hpp>
#include <fcppt/config/external_begin.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <fcppt/config/external_end.hpp>

namespace fcppt
{
namespace container
{
namespace grid
{

FCPPT_PP_PUSH_WARNING
FCPPT_PP_DISABLE_GCC_WARNING(-Weffc++)

/**
\brief An iterator over grid position

\ingroup fcpptcontainergrid
*/
template<
	typename SizeType,
	fcppt::container::grid::size_type Size
>
class pos_iterator final
:
	public
		fcppt::container::grid::detail::pos_iterator_base<
			SizeType,
			Size
		>
{
	typedef
	fcppt::container::grid::detail::pos_iterator_base<
		SizeType,
		Size
	>
	base_type;
public:
	typedef
	typename
	base_type::value_type
	value_type;

	typedef
	value_type
	pos;

	typedef
	typename
	pos::value_type
	size_type;

	typedef
	typename
	base_type::reference
	reference;

	typedef
	typename
	base_type::pointer
	pointer;

	typedef
	typename
	base_type::iterator_category
	iterator_category;

	typedef
	typename
	base_type::difference_type
	difference_type;

	typedef
	fcppt::container::grid::min<
		size_type,
		Size
	>
	min;

	typedef
	fcppt::container::grid::sup<
		size_type,
		Size
	>
	sup;

	pos_iterator(
		pos,
		min,
		sup
	);
private:
	friend class boost::iterator_core_access;

	void
	increment();

	reference
	dereference() const;

	bool
	equal(
		pos_iterator const &
	) const;

	pos current_;

	min min_;

	sup sup_;
};

FCPPT_PP_POP_WARNING

}
}
}

#endif