#include <NTL/lzz_pX.h>

NTL_START_IMPL

// NOTE: these are declared extern in lzz_pX.h

const long zz_pX_mod_crossover[5] = {45, 45, 90, 180, 180};
const long zz_pX_mul_crossover[5] = {150, 150, 300, 500, 500};
const long zz_pX_newton_crossover[5] = {150, 150, 300, 700, 700};
const long zz_pX_div_crossover[5] = {180, 180, 350, 750, 750};
const long zz_pX_halfgcd_crossover[5] = {90, 90, 180, 350, 350};
const long zz_pX_gcd_crossover[5] = {400, 400, 800, 1400, 1400};
const long zz_pX_bermass_crossover[5] = {400, 480, 900, 1600, 1600};
const long zz_pX_trace_crossover[5] = {200, 350, 450, 800, 800};

const zz_pX& zz_pX::zero()
{
   static const zz_pX z; // GLOBAL (assumes C++11 thread-safe init)
   return z;
}

istream& operator>>(istream& s, zz_pX& x)
{
   NTL_INPUT_CHECK_RET(s, s >> x.rep);
   x.normalize();
   return s;
}

ostream& operator<<(ostream& s, const zz_pX& a)
{
   return s << a.rep;
}

void zz_pX::normalize()
{
   long n;
   const zz_p* p;

   n = rep.length();
   if (n == 0) return;
   p = rep.elts() + n;
   while (n > 0 && IsZero(*--p)) {
      n--;
   }
   rep.SetLength(n);
}

long IsZero(const zz_pX& a)
{
   return a.rep.length() == 0;
}

long IsOne(const zz_pX& a)
{
    return a.rep.length() == 1 && IsOne(a.rep[0]);
}

void GetCoeff(zz_p& x, const zz_pX& a, long i)
{
   if (i < 0 || i > deg(a))
      clear(x);
   else
      x = a.rep[i];
}

void SetCoeff(zz_pX& x, long i, zz_p a)
{
   long j, m;

   if (i < 0)
      LogicError("SetCoeff: negative index");

   if (NTL_OVERFLOW(i, 1, 0))
      ResourceError("overflow in SetCoeff");

   m = deg(x);

   if (i > m && IsZero(a)) return;

   if (i > m) {
      x.rep.SetLength(i+1);
      for (j = m+1; j < i; j++)
         clear(x.rep[j]);
   }
   x.rep[i] = a;
   x.normalize();
}

void SetCoeff(zz_pX& x, long i, long a)
{
   if (a == 1)
      SetCoeff(x, i);
   else
      SetCoeff(x, i, to_zz_p(a));
}

void SetCoeff(zz_pX& x, long i)
{
   long j, m;

   if (i < 0)
      LogicError("coefficient index out of range");

   if (NTL_OVERFLOW(i, 1, 0))
      ResourceError("overflow in SetCoeff");

   m = deg(x);

   if (i > m) {
      x.rep.SetLength(i+1);
      for (j = m+1; j < i; j++)
         clear(x.rep[j]);
   }
   set(x.rep[i]);
   x.normalize();
}

void SetX(zz_pX& x)
{
   clear(x);
   SetCoeff(x, 1);
}

long IsX(const zz_pX& a)
{
   return deg(a) == 1 && IsOne(LeadCoeff(a)) && IsZero(ConstTerm(a));
}

const zz_p coeff(const zz_pX& a, long i)
{
   if (i < 0 || i > deg(a))
      return zz_p::zero();
   else
      return a.rep[i];
}

const zz_p LeadCoeff(const zz_pX& a)
{
   if (IsZero(a))
      return zz_p::zero();
   else
      return a.rep[deg(a)];
}

const zz_p ConstTerm(const zz_pX& a)
{
   if (IsZero(a))
      return zz_p::zero();
   else
      return a.rep[0];
}

void conv(zz_pX& x, zz_p a)
{
   if (IsZero(a))
      x.rep.SetLength(0);
   else {
      x.rep.SetLength(1);
      x.rep[0] = a;
   }
}

void conv(zz_pX& x, long a)
{
   if (a == 0) {
      x.rep.SetLength(0);
      return;
   }

   zz_p t;

   conv(t, a);
   conv(x, t);
}

void conv(zz_pX& x, const ZZ& a)
{
   if (a == 0) {
      x.rep.SetLength(0);
      return;
   }

   zz_p t;

   conv(t, a);
   conv(x, t);
}

void conv(zz_pX& x, const vec_zz_p& a)
{
   x.rep = a;
   x.normalize();
}

void add(zz_pX& x, const zz_pX& a, const zz_pX& b)
{
   long da = deg(a);
   long db = deg(b);
   long minab = min(da, db);
   long maxab = max(da, db);
   x.rep.SetLength(maxab+1);

   long i;
   const zz_p *ap, *bp;
   zz_p* xp;
   long p = zz_p::modulus();

   for (i = minab+1, ap = a.rep.elts(), bp = b.rep.elts(), xp = x.rep.elts();
        i; i--, ap++, bp++, xp++)
      xp->LoopHole() = AddMod(rep(*ap), rep(*bp), p);

   if (da > minab && &x != &a)
      for (i = da-minab; i; i--, xp++, ap++)
         *xp = *ap;
   else if (db > minab && &x != &b)
      for (i = db-minab; i; i--, xp++, bp++)
         *xp = *bp;
   else
      x.normalize();
}

void add(zz_pX& x, const zz_pX& a, zz_p b)
{
   if (a.rep.length() == 0) {
      conv(x, b);
   }
   else {
      if (&x != &a) x = a;
      add(x.rep[0], x.rep[0], b);
      x.normalize();
   }
}

void sub(zz_pX& x, const zz_pX& a, const zz_pX& b)
{
   long da = deg(a);
   long db = deg(b);
   long minab = min(da, db);
   long maxab = max(da, db);
   x.rep.SetLength(maxab+1);

   long i;
   const zz_p *ap, *bp;
   zz_p* xp;
   long p = zz_p::modulus();

   for (i = minab+1, ap = a.rep.elts(), bp = b.rep.elts(), xp = x.rep.elts();
        i; i--, ap++, bp++, xp++)
      xp->LoopHole() = SubMod(rep(*ap), rep(*bp), p);

   if (da > minab && &x != &a)
      for (i = da-minab; i; i--, xp++, ap++)
         *xp = *ap;
   else if (db > minab)
      for (i = db-minab; i; i--, xp++, bp++)
         xp->LoopHole() = NegateMod(rep(*bp), p);
   else
      x.normalize();

}

void sub(zz_pX& x, const zz_pX& a, zz_p b)
{
   if (a.rep.length() == 0) {
      x.rep.SetLength(1);
      negate(x.rep[0], b);
   }
   else {
      if (&x != &a) x = a;
      sub(x.rep[0], x.rep[0], b);
   }
   x.normalize();
}

void sub(zz_pX& x, zz_p a, const zz_pX& b)
{
   negate(x, b);
   add(x, x, a);
}

void negate(zz_pX& x, const zz_pX& a)
{
   long n = a.rep.length();
   x.rep.SetLength(n);

   const zz_p* ap = a.rep.elts();
   zz_p* xp = x.rep.elts();
   long i;
   long p = zz_p::modulus();

   for (i = n; i; i--, ap++, xp++)
      xp->LoopHole() = NegateMod(rep(*ap), p);
}

void mul(zz_pX& x, const zz_pX& a, const zz_pX& b)
{
   if (&a == &b) {
      sqr(x, a);
      return;
   }

   if (deg(a) > NTL_zz_pX_MUL_CROSSOVER && deg(b) > NTL_zz_pX_MUL_CROSSOVER)
      FFTMul(x, a, b);
   else
      PlainMul(x, a, b);
}

void sqr(zz_pX& x, const zz_pX& a)
{
   if (deg(a) > NTL_zz_pX_MUL_CROSSOVER)
      FFTSqr(x, a);
   else
      PlainSqr(x, a);
}

/* "plain" multiplication and squaring actually incorporates Karatsuba */

void PlainMul(zz_p *xp, const zz_p *ap, long sa, const zz_p *bp, long sb)
{
   if (sa == 0 || sb == 0) return;

   long sx = sa+sb-1;

   if (sa < sb) {
      { long t = sa; sa = sb; sb = t; }
      { const zz_p *t = ap; ap = bp; bp = t; }
   }

   long i, j;

   for (i = 0; i < sx; i++)
      clear(xp[i]);

   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();

   for (i = 0; i < sb; i++) {
      long t1 = rep(bp[i]);
      mulmod_precon_t bpinv = PrepMulModPrecon(t1, p, pinv);
      zz_p *xp1 = xp+i;
      for (j = 0; j < sa; j++) {
         long t2;
         t2 = MulModPrecon(rep(ap[j]), t1, p, bpinv);
         xp1[j].LoopHole() = AddMod(t2, rep(xp1[j]), p);
      }
   }
}

static inline
void reduce(zz_p& r, long a, long p, mulmod_t pinv)
{
   // DIRT: uses undocumented MulMod feature (see sp_arith.h)
   r.LoopHole() = MulMod(a, 1L, p, pinv);
}

void PlainMul_long(zz_p *xp, const zz_p *ap, long sa, const zz_p *bp, long sb)
{
   if (sa == 0 || sb == 0) return;

   long d = sa+sb-2;

   long i, j, jmin, jmax;

   long accum;

   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();

   for (i = 0; i <= d; i++) {
      jmin = max(0, i-(sb-1));
      jmax = min((sa-1), i);
      accum = 0;
      for (j = jmin; j <= jmax; j++) {
         accum += rep(ap[j])*rep(bp[i-j]);
      }
      reduce(xp[i], accum, p, pinv);
   }
}

#define KARX (16)

void KarFold(zz_p *T, const zz_p *b, long sb, long hsa)
{
   long m = sb - hsa;
   long i;
   long p = zz_p::modulus();

   for (i = 0; i < m; i++)
      T[i].LoopHole() = AddMod(rep(b[i]), rep(b[hsa+i]), p);

   for (i = m; i < hsa; i++)
      T[i] = b[i];
}

void KarSub(zz_p *T, const zz_p *b, long sb)
{
   long i;
   long p = zz_p::modulus();

   for (i = 0; i < sb; i++)
      T[i].LoopHole() = SubMod(rep(T[i]), rep(b[i]), p);
}

void KarAdd(zz_p *T, const zz_p *b, long sb)
{
   long i;
   long p = zz_p::modulus();

   for (i = 0; i < sb; i++)
      T[i].LoopHole() = AddMod(rep(T[i]), rep(b[i]), p);
}

void KarFix(zz_p *c, const zz_p *b, long sb, long hsa)
{
   long i;
   long p = zz_p::modulus();

   for (i = 0; i < hsa; i++)
      c[i] = b[i];

   for (i = hsa; i < sb; i++)
      c[i].LoopHole() = AddMod(rep(c[i]), rep(b[i]), p);
}

void KarMul(zz_p *c, const zz_p *a, long sa, const zz_p *b, long sb, zz_p *stk)
{
   if (sa < sb) {
      { long t = sa; sa = sb; sb = t; }
      { const zz_p *t = a; a = b; b = t; }
   }

   if (sb < KARX) {
      PlainMul(c, a, sa, b, sb);
      return;
   }

   long hsa = (sa + 1) >> 1;

   if (hsa < sb) {
      /* normal case */

      long hsa2 = hsa << 1;

      zz_p *T1, *T2, *T3;

      T1 = stk; stk += hsa;
      T2 = stk; stk += hsa;
      T3 = stk; stk += hsa2 - 1;

      /* compute T1 = a_lo + a_hi */

      KarFold(T1, a, sa, hsa);

      /* compute T2 = b_lo + b_hi */

      KarFold(T2, b, sb, hsa);

      /* recursively compute T3 = T1 * T2 */

      KarMul(T3, T1, hsa, T2, hsa, stk);

      /* recursively compute a_hi * b_hi into high part of c */
      /* and subtract from T3 */

      KarMul(c + hsa2, a+hsa, sa-hsa, b+hsa, sb-hsa, stk);
      KarSub(T3, c + hsa2, sa + sb - hsa2 - 1);

      /* recursively compute a_lo*b_lo into low part of c */
      /* and subtract from T3 */

      KarMul(c, a, hsa, b, hsa, stk);
      KarSub(T3, c, hsa2 - 1);

      clear(c[hsa2 - 1]);

      /* finally, add T3 * X^{hsa} to c */

      KarAdd(c+hsa, T3, hsa2-1);
   }
   else {
      /* degenerate case */

      zz_p *T;

      T = stk; stk += hsa + sb - 1;

      /* recursively compute b*a_hi into high part of c */

      KarMul(c + hsa, a + hsa, sa - hsa, b, sb, stk);

      /* recursively compute b*a_lo into T */

      KarMul(T, a, hsa, b, sb, stk);

      KarFix(c, T, hsa + sb - 1, hsa);
   }
}

void KarMul_long(zz_p *c, const zz_p *a, long sa, const zz_p *b, long sb, zz_p *stk)
{
   if (sa < sb) {
      { long t = sa; sa = sb; sb = t; }
      { const zz_p *t = a; a = b; b = t; }
   }

   if (sb < KARX) {
      PlainMul_long(c, a, sa, b, sb);
      return;
   }

   long hsa = (sa + 1) >> 1;

   if (hsa < sb) {
      /* normal case */

      long hsa2 = hsa << 1;

      zz_p *T1, *T2, *T3;

      T1 = stk; stk += hsa;
      T2 = stk; stk += hsa;
      T3 = stk; stk += hsa2 - 1;

      /* compute T1 = a_lo + a_hi */

      KarFold(T1, a, sa, hsa);

      /* compute T2 = b_lo + b_hi */

      KarFold(T2, b, sb, hsa);

      /* recursively compute T3 = T1 * T2 */

      KarMul_long(T3, T1, hsa, T2, hsa, stk);

      /* recursively compute a_hi * b_hi into high part of c */
      /* and subtract from T3 */

      KarMul_long(c + hsa2, a+hsa, sa-hsa, b+hsa, sb-hsa, stk);
      KarSub(T3, c + hsa2, sa + sb - hsa2 - 1);

      /* recursively compute a_lo*b_lo into low part of c */
      /* and subtract from T3 */

      KarMul_long(c, a, hsa, b, hsa, stk);
      KarSub(T3, c, hsa2 - 1);

      clear(c[hsa2 - 1]);

      /* finally, add T3 * X^{hsa} to c */

      KarAdd(c+hsa, T3, hsa2-1);
   }
   else {
      /* degenerate case */

      zz_p *T;

      T = stk; stk += hsa + sb - 1;

      /* recursively compute b*a_hi into high part of c */

      KarMul_long(c + hsa, a + hsa, sa - hsa, b, sb, stk);

      /* recursively compute b*a_lo into T */

      KarMul_long(T, a, hsa, b, sb, stk);

      KarFix(c, T, hsa + sb - 1, hsa);
   }
}

void PlainMul(zz_pX& c, const zz_pX& a, const zz_pX& b)
{
   long sa = a.rep.length();
   long sb = b.rep.length();

   if (sa == 0 || sb == 0) {
      clear(c);
      return;
   }

   if (sa == 1) {
      mul(c, b, a.rep[0]);
      return;
   }

   if (sb == 1) {
      mul(c, a, b.rep[0]);
      return;
   }

   if (&a == &b) {
      PlainSqr(c, a);
      return;
   }

   vec_zz_p mem;

   const zz_p *ap, *bp;
   zz_p *cp;

   if (&a == &c) {
      mem = a.rep;
      ap = mem.elts();
   }
   else
      ap = a.rep.elts();

   if (&b == &c) {
      mem = b.rep;
      bp = mem.elts();
   }
   else
      bp = b.rep.elts();

   c.rep.SetLength(sa+sb-1);
   cp = c.rep.elts();

   long p = zz_p::modulus();
   long use_long = (p < NTL_SP_BOUND/KARX && p*KARX < NTL_SP_BOUND/p);

   if (sa < KARX || sb < KARX) {
      if (use_long)
         PlainMul_long(cp, ap, sa, bp, sb);
      else
         PlainMul(cp, ap, sa, bp, sb);
   }
   else {
      /* karatsuba */

      long n, hn, sp;

      n = max(sa, sb);
      sp = 0;
      do {
         hn = (n+1) >> 1;
         sp += (hn << 2) - 1;
         n = hn;
      } while (n >= KARX);

      vec_zz_p stk;
      stk.SetLength(sp);

      if (use_long)
         KarMul_long(cp, ap, sa, bp, sb, stk.elts());
      else
         KarMul(cp, ap, sa, bp, sb, stk.elts());
   }

   c.normalize();
}

void PlainSqr_long(zz_p *xp, const zz_p *ap, long sa)
{
   if (sa == 0) return;

   long da = sa-1;
   long d = 2*da;

   long i, j, jmin, jmax, m, m2;

   long accum;
   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();

   for (i = 0; i <= d; i++) {
      jmin = max(0, i-da);
      jmax = min(da, i);
      m = jmax - jmin + 1;
      m2 = m >> 1;
      jmax = jmin + m2 - 1;
      accum = 0;
      for (j = jmin; j <= jmax; j++) {
         accum += rep(ap[j])*rep(ap[i-j]);
      }
      accum += accum;
      if (m & 1) {
         accum += rep(ap[jmax + 1])*rep(ap[jmax + 1]);
      }

      reduce(xp[i], accum, p, pinv);
   }
}

void PlainSqr(zz_p *xp, const zz_p *ap, long sa)
{
   if (sa == 0) return;

   long i, j, k, cnt;

   cnt = 2*sa-1;
   for (i = 0; i < cnt; i++)
      clear(xp[i]);

   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();
   long t1, t2;

   i = -1;
   for (j = 0; j <= sa-2; j++) {
      i += 2;

      t1 = MulMod(rep(ap[j]), rep(ap[j]), p, pinv);
      t2 = rep(xp[i-1]);
      t2 = AddMod(t2, t2, p);
      t2 = AddMod(t2, t1, p);
      xp[i-1].LoopHole() = t2;

      cnt = sa - 1 - j;
      const zz_p *ap1 = ap+(j+1);
      zz_p *xp1 = xp+i;
      t1 = rep(ap[j]);
      mulmod_precon_t tpinv = PrepMulModPrecon(t1, p, pinv);

      for (k = 0; k < cnt; k++) {
         t2 = MulModPrecon(rep(ap1[k]), t1, p, tpinv);
         t2 = AddMod(t2, rep(xp1[k]), p);
         xp1[k].LoopHole() = t2;
      }
      t2 = rep(*xp1);
      t2 = AddMod(t2, t2, p);
      (*xp1).LoopHole() = t2;
   }

   t1 = rep(ap[sa-1]);
   t1 = MulMod(t1, t1, p, pinv);
   xp[2*sa-2].LoopHole() = t1;
}

#define KARSX (30)

void KarSqr(zz_p *c, const zz_p *a, long sa, zz_p *stk)
{
   if (sa < KARSX) {
      PlainSqr(c, a, sa);
      return;
   }

   long hsa = (sa + 1) >> 1;
   long hsa2 = hsa << 1;

   zz_p *T1, *T2;

   T1 = stk; stk += hsa;
   T2 = stk; stk += hsa2-1;

   KarFold(T1, a, sa, hsa);
   KarSqr(T2, T1, hsa, stk);

   KarSqr(c + hsa2, a+hsa, sa-hsa, stk);
   KarSub(T2, c + hsa2, sa + sa - hsa2 - 1);

   KarSqr(c, a, hsa, stk);
   KarSub(T2, c, hsa2 - 1);

   clear(c[hsa2 - 1]);

   KarAdd(c+hsa, T2, hsa2-1);
}

void KarSqr_long(zz_p *c, const zz_p *a, long sa, zz_p *stk)
{
   if (sa < KARSX) {
      PlainSqr_long(c, a, sa);
      return;
   }

   long hsa = (sa + 1) >> 1;
   long hsa2 = hsa << 1;

   zz_p *T1, *T2;

   T1 = stk; stk += hsa;
   T2 = stk; stk += hsa2-1;

   KarFold(T1, a, sa, hsa);
   KarSqr_long(T2, T1, hsa, stk);

   KarSqr_long(c + hsa2, a+hsa, sa-hsa, stk);
   KarSub(T2, c + hsa2, sa + sa - hsa2 - 1);

   KarSqr_long(c, a, hsa, stk);
   KarSub(T2, c, hsa2 - 1);

   clear(c[hsa2 - 1]);

   KarAdd(c+hsa, T2, hsa2-1);
}

void PlainSqr(zz_pX& c, const zz_pX& a)
{
   if (IsZero(a)) {
      clear(c);
      return;
   }

   vec_zz_p mem;

   const zz_p *ap;
   zz_p *cp;

   long sa = a.rep.length();

   if (&a == &c) {
      mem = a.rep;
      ap = mem.elts();
   }
   else
      ap = a.rep.elts();

   c.rep.SetLength(2*sa-1);
   cp = c.rep.elts();

   long p = zz_p::modulus();
   long use_long = (p < NTL_SP_BOUND/KARSX && p*KARSX < NTL_SP_BOUND/p);

   if (sa < KARSX) {
      if (use_long)
         PlainSqr_long(cp, ap, sa);
      else
         PlainSqr(cp, ap, sa);
   }
   else {
      /* karatsuba */

      long n, hn, sp;

      n = sa;
      sp = 0;
      do {
         hn = (n+1) >> 1;
         sp += hn+hn+hn - 1;
         n = hn;
      } while (n >= KARSX);

      vec_zz_p stk;
      stk.SetLength(sp);

      if (use_long)
         KarSqr_long(cp, ap, sa, stk.elts());
      else
         KarSqr(cp, ap, sa, stk.elts());
   }

   c.normalize();
}

void PlainDivRem(zz_pX& q, zz_pX& r, const zz_pX& a, const zz_pX& b)
{
   long da, db, dq, i, j, LCIsOne;
   const zz_p *bp;
   zz_p *qp;
   zz_p *xp;

   zz_p LCInv, t;
   zz_p s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("zz_pX: division by zero");

   if (da < db) {
      r = a;
      clear(q);
      return;
   }

   zz_pX lb;

   if (&q == &b) {
      lb = b;
      bp = lb.rep.elts();
   }
   else
      bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   vec_zz_p x;
   if (&r == &a)
      xp = r.rep.elts();
   else {
      x = a.rep;
      xp = x.elts();
   }

   dq = da - db;
   q.rep.SetLength(dq+1);
   qp = q.rep.elts();

   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();

   for (i = dq; i >= 0; i--) {
      t = xp[i+db];
      if (!LCIsOne)
         mul(t, t, LCInv);
      qp[i] = t;
      negate(t, t);

      long T = rep(t);
      mulmod_precon_t Tpinv = PrepMulModPrecon(T, p, pinv);

      for (j = db-1; j >= 0; j--) {
         long S = MulModPrecon(rep(bp[j]), T, p, Tpinv);
         S = AddMod(S, rep(xp[i+j]), p);
         xp[i+j].LoopHole() = S;
      }
   }

   r.rep.SetLength(db);
   if (&r != &a) {
      for (i = 0; i < db; i++)
         r.rep[i] = xp[i];
   }
   r.normalize();
}

void PlainDiv(zz_pX& q, const zz_pX& a, const zz_pX& b)
{
   long da, db, dq, i, j, LCIsOne;
   const zz_p *bp;
   zz_p *qp;
   zz_p *xp;

   zz_p LCInv, t;
   zz_p s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("zz_pX: division by zero");

   if (da < db) {
      clear(q);
      return;
   }

   zz_pX lb;

   if (&q == &b) {
      lb = b;
      bp = lb.rep.elts();
   }
   else
      bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   vec_zz_p x;
   x.SetLength(da+1-db);
   for (i = db; i <= da; i++)
      x[i-db] = a.rep[i];

   xp = x.elts();

   dq = da - db;
   q.rep.SetLength(dq+1);
   qp = q.rep.elts();

   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();

   for (i = dq; i >= 0; i--) {
      t = xp[i];
      if (!LCIsOne)
         mul(t, t, LCInv);
      qp[i] = t;
      negate(t, t);

      long T = rep(t);
      mulmod_precon_t Tpinv = PrepMulModPrecon(T, p, pinv);

      long lastj = max(0, db-i);

      for (j = db-1; j >= lastj; j--) {
         long S = MulModPrecon(rep(bp[j]), T, p, Tpinv);
         S = AddMod(S, rep(xp[i+j-db]), p);
         xp[i+j-db].LoopHole() = S;
      }
   }
}

void PlainRem(zz_pX& r, const zz_pX& a, const zz_pX& b)
{
   long da, db, dq, i, j, LCIsOne;
   const zz_p *bp;
   zz_p *xp;

   zz_p LCInv, t;
   zz_p s;

   da = deg(a);
   db = deg(b);

   if (db < 0) ArithmeticError("zz_pX: division by zero");

   if (da < db) {
      r = a;
      return;
   }

   bp = b.rep.elts();

   if (IsOne(bp[db]))
      LCIsOne = 1;
   else {
      LCIsOne = 0;
      inv(LCInv, bp[db]);
   }

   vec_zz_p x;

   if (&r == &a)
      xp = r.rep.elts();
   else {
      x = a.rep;
      xp = x.elts();
   }

   dq = da - db;

   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();

   for (i = dq; i >= 0; i--) {
      t = xp[i+db];
      if (!LCIsOne)
         mul(t, t, LCInv);
      negate(t, t);

      long T = rep(t);
      mulmod_precon_t Tpinv = PrepMulModPrecon(T, p, pinv);

      for (j = db-1; j >= 0; j--) {
         long S = MulModPrecon(rep(bp[j]), T, p, Tpinv);
         S = AddMod(S, rep(xp[i+j]), p);
         xp[i+j].LoopHole() = S;
      }
   }

   r.rep.SetLength(db);
   if (&r != &a) {
      for (i = 0; i < db; i++)
         r.rep[i] = xp[i];
   }
   r.normalize();
}

void mul(zz_pX& x, const zz_pX& a, zz_p b)
{
   if (IsZero(b)) {
      clear(x);
      return;
   }

   if (IsOne(b)) {
      x = a;
      return;
   }

   long i, da;

   const zz_p *ap;
   zz_p* xp;

   long t;
   t = rep(b);
   long p = zz_p::modulus();
   mulmod_t pinv = zz_p::ModulusInverse();
   mulmod_precon_t bpinv = PrepMulModPrecon(t, p, pinv);

   da = deg(a);
   x.rep.SetLength(da+1);
   ap = a.rep.elts();
   xp = x.rep.elts();

   for (i = 0; i <= da; i++)
      xp[i].LoopHole() = MulModPrecon(rep(ap[i]), t, p, bpinv);

   x.normalize();
}

void PlainGCD(zz_pX& x, const zz_pX& a, const zz_pX& b)
{
   zz_p t;

   if (IsZero(b))
      x = a;
   else if (IsZero(a))
      x = b;
   else {
      long n = max(deg(a),deg(b)) + 1;
      zz_pX u(INIT_SIZE, n), v(INIT_SIZE, n);

      u = a;
      v = b;
      do {
         PlainRem(u, u, v);
         swap(u, v);
      } while (!IsZero(v));

      x = u;
   }

   if (IsZero(x)) return;
   if (IsOne(LeadCoeff(x))) return;

   /* make gcd monic */

   inv(t, LeadCoeff(x));
   mul(x, x, t);
}

void PlainXGCD(zz_pX& d, zz_pX& s, zz_pX& t, const zz_pX& a, const zz_pX& b)
{
   zz_p z;

   if (IsZero(b)) {
      set(s);
      clear(t);
      d = a;
   }
   else if (IsZero(a)) {
      clear(s);
      set(t);
      d = b;
   }
   else {
      long e = max(deg(a), deg(b)) + 1;

      zz_pX temp(INIT_SIZE, e), u(INIT_SIZE, e), v(INIT_SIZE, e), u0(INIT_SIZE, e), v0(INIT_SIZE, e),
            u1(INIT_SIZE, e), v1(INIT_SIZE, e), u2(INIT_SIZE, e), v2(INIT_SIZE, e), q(INIT_SIZE, e);

      set(u1); clear(v1);
      clear(u2); set(v2);
      u = a; v = b;

      do {
         DivRem(q, u, u, v);
         swap(u, v);
         u0 = u2;
         v0 = v2;
         mul(temp, q, u2);
         sub(u2, u1, temp);
         mul(temp, q, v2);
         sub(v2, v1, temp);
         u1 = u0;
         v1 = v0;
      } while (!IsZero(v));

      d = u;
      s = u1;
      t = v1;
   }

   if (IsZero(d)) return;
   if (IsOne(LeadCoeff(d))) return;

   /* make gcd monic */

   inv(z, LeadCoeff(d));
   mul(d, d, z);
   mul(s, s, z);
   mul(t, t, z);
}

void MulMod(zz_pX& x, const zz_pX& a, const zz_pX& b, const zz_pX& f)
{
   if (deg(a) >= deg(f) || deg(b) >= deg(f) || deg(f) == 0)
      LogicError("MulMod: bad args");

   zz_pX t;

   mul(t, a, b);
   rem(x, t, f);
}

void SqrMod(zz_pX& x, const zz_pX& a, const zz_pX& f)
{
   if (deg(a) >= deg(f) || deg(f) == 0) LogicError("SqrMod: bad args");

   zz_pX t;

   sqr(t, a);
   rem(x, t, f);
}

void InvMod(zz_pX& x, const zz_pX& a, const zz_pX& f)
{
   if (deg(a) >= deg(f) || deg(f) == 0) LogicError("InvMod: bad args");

   zz_pX d, xx, t;

   XGCD(d, xx, t, a, f);
   if (!IsOne(d))
      InvModError("zz_pX InvMod: can't compute multiplicative inverse");

   x = xx;
}

long InvModStatus(zz_pX& x, const zz_pX& a, const zz_pX& f)
{
   if (deg(a) >= deg(f) || deg(f) == 0) LogicError("InvModStatus: bad args");

   zz_pX d, t;

   XGCD(d, x, t, a, f);
   if (!IsOne(d)) {
      x = d;
      return 1;
   }
   else
      return 0;
}

static
void MulByXModAux(zz_pX& h, const zz_pX& a, const zz_pX& f)
{
   long i, n, m;
   zz_p* hh;
   const zz_p *aa, *ff;

   zz_p t, z;

   n = deg(f);
   m = deg(a);

   if (m >= n || n == 0) LogicError("MulByXMod: bad args");

   if (m < 0) {
      clear(h);
      return;
   }

   if (m < n-1) {
      h.rep.SetLength(m+2);
      hh = h.rep.elts();
      aa = a.rep.elts();
      for (i = m+1; i >= 1; i--)
         hh[i] = aa[i-1];
      clear(hh[0]);
   }
   else {
      h.rep.SetLength(n);
      hh = h.rep.elts();
      aa = a.rep.elts();
      ff = f.rep.elts();
      negate(z, aa[n-1]);
      if (!IsOne(ff[n]))
         div(z, z, ff[n]);
      for (i = n-1; i >= 1; i--) {
         mul(t, z, ff[i]);
         add(hh[i], aa[i-1], t);
      }
      mul(hh[0], z, ff[0]);
      h.normalize();
   }
}

void MulByXMod(zz_pX& h, const zz_pX& a, const zz_pX& f)
{
   if (&h == &f) {
      zz_pX hh;
      MulByXModAux(hh, a, f);
      h = hh;
   }
   else
      MulByXModAux(h, a, f);
}

void random(zz_pX& x, long n)
{
   x.rep.SetLength(n);
   VectorRandom(n, x.rep.elts());
   x.normalize();
}

void fftRep::DoSetSize(long NewK, long NewNumPrimes)
{
   if (NewK < -1) LogicError("bad arg to fftRep::SetSize()");

   if (NewK >= NTL_BITS_PER_LONG-1)
      ResourceError("bad arg to fftRep::SetSize()");

   if (NewK == -1) {
      k = -1;
      return;
   }

   if (NewNumPrimes == 0)
      NewNumPrimes = zz_pInfo->NumPrimes;

   if (MaxK >= 0 && NumPrimes != NewNumPrimes)
      LogicError("fftRep: inconsistent use");

   if (NewK <= MaxK) {
      k = NewK;
      return;
   }

   UniqueArray<long> new_tbl[4];
   long i;

   for (i = 0; i < NewNumPrimes; i++)
      new_tbl[i].SetLength(1L << NewK);

   for (i = 0; i < NewNumPrimes; i++)
      tbl[i].move(new_tbl[i]);

   NumPrimes = NewNumPrimes;
   k = MaxK = NewK;
}

void fftRep::SetSize(long NewK)
{
   DoSetSize(NewK, 0);
}

fftRep& fftRep::operator=(const fftRep& R)
{
   if (this == &R) return *this;

   if (MaxK >= 0 && R.MaxK >= 0 && NumPrimes != R.NumPrimes)
      LogicError("fftRep: inconsistent use");

   if (R.k < 0) {
      k = -1;
      len = 0;
      return *this;
   }

   DoSetSize(R.k, R.NumPrimes);
   len = R.len;

   long i, j;

   for (i = 0; i < NumPrimes; i++)
      for (j = 0; j < len; j++)
         tbl[i][j] = R.tbl[i][j];

   return *this;
}

static inline
void FromModularRep(zz_p& res, long *a, zz_pInfoT* info)
{
   long n = info->NumPrimes;
   long p = info->p;
   mulmod_t pinv = info->pinv;
   long *CoeffModP = info->CoeffModP.elts();
   double *x = info->x.elts();
   long *u = info->u.elts();
   mulmod_precon_t *uqinv = info->uqinv.elts();
   long MinusMModP = info->MinusMModP;
   mulmod_precon_t MinusMModPpinv = info->MinusMModPpinv;
   mulmod_precon_t *CoeffModPpinv = info->CoeffModPpinv.elts();

   long q, s, t;
   long i;
   double y;

   y = double(0L);
   t = 0;

   for (i = 0; i < n; i++) {
      s = MulModPrecon(a[i], u[i], GetFFTPrime(i), uqinv[i]);
      y = y + double(s)*GetFFTPrimeRecip(i);

      // DIRT: uses undocumented MulMod feature (see sp_arith.h)
      // input s is not reduced mod p
      s = MulModPrecon(s, CoeffModP[i], p, CoeffModPpinv[i]);

      t = AddMod(t, s, p);
   }

   q = (long) (y + 0.5);

   // DIRT: uses undocumented MulMod feature (see sp_arith.h)
   // input q may not be reduced mod p
   s = MulModPrecon(q, MinusMModP, p, MinusMModPpinv);

   t = AddMod(t, s, p);
   res.LoopHole() = t;

}

#if 0
// converts entries lo..lo+cnt-1 in R and stores results into res
static
void FromModularRep(zz_p* res, const fftRep& R, long lo, long cnt,
                    zz_pInfoT* info)
{
   if (cnt <= 0) return;

   long nprimes = info->NumPrimes;
   long p = info->p;
   mulmod_t pinv = info->pinv;
   long *CoeffModP = info->CoeffModP.elts();
   double *x = info->x.elts();
   long *u = info->u.elts();
   mulmod_precon_t *uqinv = info->uqinv.elts();
   long MinusMModP = info->MinusMModP;
   mulmod_precon_t MinusMModPpinv = info->MinusMModPpinv;
   mulmod_precon_t *CoeffModPpinv = info->CoeffModPpinv.elts();

   long primes[4];
   double prime_recip[4];
   long *tbl[4];

   long q, s, t;
   long i, j;
   double y;

   for (i = 0; i < nprimes; i++) {
      primes[i] = GetFFTPrime(i);
      prime_recip[i] = GetFFTPrimeRecip(i);
      tbl[i] = R.tbl[i].get();
   }

   for (j = 0; j < cnt; j++) {
      y = double(0L);
      t = 0;

      for (i = 0; i < nprimes; i++) {
         s = MulModPrecon(tbl[i][j+lo], u[i], primes[i], uqinv[i]);
         y = y + double(s)*prime_recip[i];

         // DIRT: uses undocumented MulMod feature (see sp_arith.h)
         // input s is not reduced mod p
         s = MulModPrecon(s, CoeffModP[i], p, CoeffModPpinv[i]);

         t = AddMod(t, s, p);
      }

      q = (long) (y + 0.5);

      // DIRT: uses undocumented MulMod feature (see sp_arith.h)
      // input q may not be reduced mod p
      s = MulModPrecon(q, MinusMModP, p, MinusMModPpinv);

      t = AddMod(t, s, p);
      res[j].LoopHole() = t;
   }

}
#else

#define NTL_FMR_LOOP_BODY(i) \
         s = MulModPrecon(tbl[i][j+lo], u[i], primes[i], uqinv[i]);\
         y = y + double(s)*prime_recip[i];\
\
\
         /* DIRT: uses undocumented MulMod feature (see sp_arith.h) */\
         /* input s is not reduced mod p */\
         s = MulModPrecon(s, CoeffModP[i], p, CoeffModPpinv[i]);\
\
         t = AddMod(t, s, p);\

#define NTL_FMP_OUTER_LOOP(XXX) \
   for (j = 0; j < cnt; j++) {\
      y = double(0L);\
      t = 0;\
      XXX \
      q = (long) (y + 0.5);\
      /* DIRT: uses undocumented MulMod feature (see sp_arith.h) */\
      /* input q may not be reduced mod p */\
      s = MulModPrecon(q, MinusMModP, p, MinusMModPpinv);\
      t = AddMod(t, s, p);\
      res[j].LoopHole() = t;\
   }\

// converts entries lo..lo+cnt-1 in R and stores results into res
static
void FromModularRep(zz_p* res, const fftRep& R, long lo, long cnt,
                    zz_pInfoT* info)
{
   if (cnt <= 0) return;

   long nprimes = info->NumPrimes;
   long p = info->p;
   mulmod_t pinv = info->pinv;
   long *CoeffModP = info->CoeffModP.elts();
   double *x = info->x.elts();
   long *u = info->u.elts();
   mulmod_precon_t *uqinv = info->uqinv.elts();
   long MinusMModP = info->MinusMModP;
   mulmod_precon_t MinusMModPpinv = info->MinusMModPpinv;
   mulmod_precon_t *CoeffModPpinv = info->CoeffModPpinv.elts();

   long primes[4];
   double prime_recip[4];
   long *tbl[4];

   long q, s, t;
   long i, j;
   double y;

   for (i = 0; i < nprimes; i++) {
      primes[i] = GetFFTPrime(i);
      prime_recip[i] = GetFFTPrimeRecip(i);
      tbl[i] = R.tbl[i].get();
   }

   if (nprimes == 1) {
      long *tbl_0 = tbl[0];
      mulmod_precon_t CoeffModPpinv_0 = CoeffModPpinv[0];
      long primes_0 = primes[0];
      long hp0 = primes_0 >> 1;

      for (j = 0; j < cnt; j++) {
         s = tbl_0[j+lo];

         // DIRT: uses undocumented MulMod feature (see sp_arith.h)
         // input s is not reduced mod p
         t = MulModPrecon(s, 1, p, CoeffModPpinv_0);

         res[j].LoopHole() = AddMod(t, sp_SignMask(hp0-s) & MinusMModP, p);
      }
   }
   else if (nprimes == 2) {
      NTL_FMP_OUTER_LOOP( NTL_FMR_LOOP_BODY(0) NTL_FMR_LOOP_BODY(1) )
   }
   else if (nprimes == 3) {
      NTL_FMP_OUTER_LOOP( NTL_FMR_LOOP_BODY(0) NTL_FMR_LOOP_BODY(1) NTL_FMR_LOOP_BODY(2) )
   }
   else { // nprimes == 4
      NTL_FMP_OUTER_LOOP( NTL_FMR_LOOP_BODY(0) NTL_FMR_LOOP_BODY(1) NTL_FMR_LOOP_BODY(2)  NTL_FMR_LOOP_BODY(3) )
   }
}

#endif

void TofftRep_trunc(fftRep& y, const zz_pX& x, long k,
                    long len, long lo, long hi)
// computes an n = 2^k point convolution.
// if deg(x) >= 2^k, then x is first reduced modulo X^n-1.
{
   zz_pInfoT *info = zz_pInfo;
   long p = info->p;

   long n, i, j, m, j1;
   long accum;
   long nprimes = info->NumPrimes;

   if (k > info->MaxRoot)
      ResourceError("Polynomial too big for FFT");

   if (lo < 0)
      LogicError("bad arg to TofftRep");

   hi = min(hi, deg(x));

   y.SetSize(k);
   n = 1L << k;

   y.len = len = FFTRoundUp(len, k);

   m = max(hi-lo + 1, 0);
   long ilen = FFTRoundUp(m, k);

   const zz_p *xx = x.rep.elts();

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      if (n >= m) {
         long *yp = &y.tbl[0][0];
         for (j = 0; j < m; j++) {
            yp[j] = rep(xx[j+lo]);
         }
         for (j = m; j < ilen; j++) {
            yp[j] = 0;
         }
      }
      else {
         for (j = 0; j < n; j++) {
            accum = rep(xx[j+lo]);
            for (j1 = j + n; j1 < m; j1 += n)
               accum = AddMod(accum, rep(xx[j1+lo]), p);
            y.tbl[0][j] = accum;
         }
      }
   }
   else {
      if (n >= m) {
         for (i = 0; i < nprimes; i++) {
            long q = GetFFTPrime(i);
            long *yp = &y.tbl[i][0];
            for (j = 0; j < m; j++) {
               long t = rep(xx[j+lo]);
               t = sp_CorrectExcess(t, q);
               yp[j] = t;
            }
            for (j = m; j < ilen; j++) {
               yp[j] = 0;
            }
         }
      }
      else {
         for (j = 0; j < n; j++) {
            accum = rep(xx[j+lo]);
            for (j1 = j + n; j1 < m; j1 += n)
               accum = AddMod(accum, rep(xx[j1+lo]), p);
            for (i = 0; i < nprimes; i++) {
               long q = GetFFTPrime(i);
               long t = accum;
               t = sp_CorrectExcess(t, q);
               y.tbl[i][j] = t;
            }
         }
      }
   }

   if (p_info) {
      long *yp = &y.tbl[0][0];
      FFTFwd_trunc(yp, yp, k, *p_info, len, ilen);
   }
   else {
      for (i = 0; i < nprimes; i++) {
         long *yp = &y.tbl[i][0];
         FFTFwd_trunc(yp, yp, k, i, len, ilen);
      }
   }
}

void RevTofftRep(fftRep& y, const vec_zz_p& x,
                 long k, long lo, long hi, long offset)
// computes an n = 2^k point convolution of X^offset*x[lo..hi] mod X^n-1
// using "inverted" evaluation points.

{
   zz_pInfoT *info = zz_pInfo;
   long p = info->p;

   long n, i, j, m, j1;
   long accum;
   long NumPrimes = info->NumPrimes;

   if (k > info->MaxRoot)
      ResourceError("Polynomial too big for FFT");

   if (lo < 0)
      LogicError("bad arg to TofftRep");

   hi = min(hi, x.length()-1);

   y.SetSize(k);

   n = 1L << k;
   y.len = n;

   m = max(hi-lo + 1, 0);

   const zz_p *xx = x.elts();

   FFTPrimeInfo *p_info = info->p_info;

   offset = offset & (n-1);

   if (p_info) {
      for (j = 0; j < n; j++) {
         if (j >= m) {
            y.tbl[0][offset] = 0;
         }
         else {
            accum = rep(xx[j+lo]);
            for (j1 = j + n; j1 < m; j1 += n)
               accum = AddMod(accum, rep(xx[j1+lo]), p);
               y.tbl[0][offset] = accum;
         }
         offset = (offset + 1) & (n-1);
      }
   }
   else {
      for (j = 0; j < n; j++) {
         if (j >= m) {
            for (i = 0; i < NumPrimes; i++)
               y.tbl[i][offset] = 0;
         }
         else {
            accum = rep(xx[j+lo]);
            for (j1 = j + n; j1 < m; j1 += n)
               accum = AddMod(accum, rep(xx[j1+lo]), p);
            for (i = 0; i < NumPrimes; i++) {
               long q = GetFFTPrime(i);
               long t = accum;
               t = sp_CorrectExcess(t, q);
               y.tbl[i][offset] = t;
            }
         }
         offset = (offset + 1) & (n-1);
      }
   }

   if (p_info) {
      long *yp = &y.tbl[0][0];
      FFTRev1_trans(yp, yp, k, *p_info);
   }
   else {
      for (i = 0; i < info->NumPrimes; i++) {
         long *yp = &y.tbl[i][0];
         FFTRev1_trans(yp, yp, k, i);
      }
   }
}

void FromfftRep(zz_pX& x, fftRep& y, long lo, long hi)

   // converts from FFT-representation to coefficient representation
   // only the coefficients lo..hi are computed

{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j, l;
   long NumPrimes = info->NumPrimes;

   k = y.k;
   n = (1L << k);

   hi = min(hi, n-1);
   l = hi-lo+1;
   l = max(l, 0);

   long len = y.len;
   if (len <= hi) LogicError("FromfftRep: bad len");

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *yp = &y.tbl[0][0];
      FFTRev1_trunc(yp, yp, k, *p_info, len);
   }
   else {
      for (i = 0; i < NumPrimes; i++) {
         long *yp = &y.tbl[i][0];
         FFTRev1_trunc(yp, yp, k, i, len);
      }
   }

   x.rep.SetLength(l);

   if (p_info) {
      zz_p *xp = x.rep.elts();
      long *yp = &y.tbl[0][0];
      for (j = 0; j < l; j++)
         xp[j].LoopHole() = yp[j+lo];
   }
   else {
      FromModularRep(x.rep.elts(), y, lo, l, info);
   }

   x.normalize();
}

void RevFromfftRep(vec_zz_p& x, fftRep& y, long lo, long hi)

   // converts from FFT-representation to coefficient representation
   // using "inverted" evaluation points.
   // only the coefficients lo..hi are computed

{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j, l;
   long NumPrimes = info->NumPrimes;

   k = y.k;
   n = (1L << k);

   if (y.len != n) LogicError("RevFromfftRep: bad len");

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *yp = &y.tbl[0][0];
      FFTFwd_trans(yp, yp, k, *p_info);
   }
   else {
      for (i = 0; i < NumPrimes; i++) {
         long *yp = &y.tbl[i][0];
         FFTFwd_trans(yp, yp, k, i);
      }
   }

   hi = min(hi, n-1);
   l = hi-lo+1;
   l = max(l, 0);
   x.SetLength(l);

   if (p_info) {
      zz_p *xp = x.elts();
      long *yp = &y.tbl[0][0];
      for (j = 0; j < l; j++)
         xp[j].LoopHole() = yp[j+lo];
   }
   else {
      FromModularRep(x.elts(), y, lo, l, info);
   }
}

void NDFromfftRep(zz_pX& x, const fftRep& y, long lo, long hi, fftRep& z)
{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j, l;
   long NumPrimes = info->NumPrimes;

   k = y.k;
   n = (1L << k);

   hi = min(hi, n-1);
   l = hi-lo+1;
   l = max(l, 0);

   long len = y.len;
   if (len <= hi) LogicError("FromfftRep: bad len");

   z.SetSize(k);

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *zp = &z.tbl[0][0];
      const long *yp = &y.tbl[0][0];
      FFTRev1_trunc(zp, yp, k, *p_info, len);
   }
   else {
      for (i = 0; i < NumPrimes; i++) {
         long *zp = &z.tbl[i][0];
         const long *yp = &y.tbl[i][0];
         FFTRev1_trunc(zp, yp, k, i, len);
      }
   }

   x.rep.SetLength(l);

   if (p_info) {
      zz_p *xp = x.rep.elts();
      long *zp = &z.tbl[0][0];
      for (j = 0; j < l; j++)
         xp[j].LoopHole() = zp[j+lo];
   }
   else {
      FromModularRep(x.rep.elts(), z, lo, l, info);
   }

   x.normalize();
}

void NDFromfftRep(zz_pX& x, fftRep& y, long lo, long hi)
{
   fftRep z;
   NDFromfftRep(x, y, lo, hi, z);
}

void FromfftRep(zz_p* x, fftRep& y, long lo, long hi)

   // converts from FFT-representation to coefficient representation
   // only the coefficients lo..hi are computed

{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j;
   long NumPrimes = info->NumPrimes;

   k = y.k;
   n = (1L << k);

   //if (y.len <= min(hi, n-1)) LogicError("FromfftRep: bad len");
   if (y.len != n) LogicError("FromfftRep: bad len");

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *yp = &y.tbl[0][0];
      FFTRev1(yp, yp, k, *p_info);

      for (j = lo; j <= hi; j++) {
         if (j >= n)
            clear(x[j-lo]);
         else {
            x[j-lo].LoopHole() = y.tbl[0][j];
         }
      }
   }
   else {
      for (i = 0; i < NumPrimes; i++) {
         long *yp = &y.tbl[i][0];
         FFTRev1(yp, yp, k, i);
      }

      // take coefficients lo..min(hi, n-1) from y
      // zero out coefficients max(n, lo)..hi

      long l = min(hi, n-1) - lo + 1;
      l = max(l, 0);
      FromModularRep(x, y, lo, l, info);
      for (j = max(n, lo); j <= hi; j++) clear(x[j-lo]);
   }
}

void mul(fftRep& z, const fftRep& x, const fftRep& y)
{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j;

   if (x.k != y.k) LogicError("FFT rep mismatch");

   k = x.k;
   n = 1L << k;

   z.SetSize(k);

   long len = z.len = min(x.len, y.len);

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *zp = &z.tbl[0][0];
      const long *xp = &x.tbl[0][0];
      const long *yp = &y.tbl[0][0];
      long q = p_info->q;
      mulmod_t qinv = p_info->qinv;

      if (NormalizedModulus(qinv)) {
         for (j = 0; j < len; j++)
            zp[j] = NormalizedMulMod(xp[j], yp[j], q, qinv);
      }
      else {
         for (j = 0; j < len; j++)
            zp[j] = MulMod(xp[j], yp[j], q, qinv);
      }
   }
   else {
      for (i = 0; i < info->NumPrimes; i++) {
         long *zp = &z.tbl[i][0];
         const long *xp = &x.tbl[i][0];
         const long *yp = &y.tbl[i][0];
         long q = GetFFTPrime(i);
         mulmod_t qinv = GetFFTPrimeInv(i);

         for (j = 0; j < len; j++)
            zp[j] = NormalizedMulMod(xp[j], yp[j], q, qinv);
      }
   }
}

void sub(fftRep& z, const fftRep& x, const fftRep& y)
{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j;

   if (x.k != y.k) LogicError("FFT rep mismatch");

   k = x.k;
   n = 1L << k;

   z.SetSize(k);

   long len = z.len = min(x.len, y.len);

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *zp = &z.tbl[0][0];
      const long *xp = &x.tbl[0][0];
      const long *yp = &y.tbl[0][0];
      long q = p_info->q;

      for (j = 0; j < len; j++)
         zp[j] = SubMod(xp[j], yp[j], q);
   }
   else {
      for (i = 0; i < info->NumPrimes; i++) {
         long *zp = &z.tbl[i][0];
         const long *xp = &x.tbl[i][0];
         const long *yp = &y.tbl[i][0];
         long q = GetFFTPrime(i);

         for (j = 0; j < len; j++)
            zp[j] = SubMod(xp[j], yp[j], q);
      }
   }
}

void add(fftRep& z, const fftRep& x, const fftRep& y)
{
   zz_pInfoT *info = zz_pInfo;

   long k, n, i, j;

   if (x.k != y.k) LogicError("FFT rep mismatch");

   k = x.k;
   n = 1L << k;

   z.SetSize(k);

   long len = z.len = min(x.len, y.len);

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long *zp = &z.tbl[0][0];
      const long *xp = &x.tbl[0][0];
      const long *yp = &y.tbl[0][0];
      long q = p_info->q;

      for (j = 0; j < len; j++)
         zp[j] = AddMod(xp[j], yp[j], q);
   }
   else {
      for (i = 0; i < info->NumPrimes; i++) {
         long *zp = &z.tbl[i][0];
         const long *xp = &x.tbl[i][0];
         const long *yp = &y.tbl[i][0];
         long q = GetFFTPrime(i);

         for (j = 0; j < len; j++)
            zp[j] = AddMod(xp[j], yp[j], q);
      }
   }
}

void reduce(fftRep& x, const fftRep& a, long k)
  // reduces a 2^l point FFT-rep to a 2^k point FFT-rep
  // input may alias output
{
   zz_pInfoT *info = zz_pInfo;

   long i, j, l, n;
   long* xp;
   const long* ap;

   l = a.k;
   n = 1L << k;

   if (l < k) LogicError("reduce: bad operands");
   if (a.len < n) LogicError("reduce: bad len");

   x.SetSize(k);
   x.len = n;

   if (&x == &a) return;

   for (i = 0; i < info->NumPrimes; i++) {
      ap = &a.tbl[i][0];
      xp = &x.tbl[i][0];
      for (j = 0; j < n; j++)
         xp[j] = ap[j];
   }
}

void AddExpand(fftRep& x, const fftRep& a)
//  x = x + (an "expanded" version of a)
{
   zz_pInfoT *info = zz_pInfo;

   long i, j, l, k, n;

   l = x.k;
   k = a.k;
   n = 1L << k;

   if (l < k) LogicError("AddExpand: bad args");
   if (x.len < n) LogicError("AddExpand: bad len");

   FFTPrimeInfo *p_info = info->p_info;

   if (p_info) {
      long q = p_info->q;
      const long *ap = &a.tbl[0][0];
      long *xp = &x.tbl[0][0];
      for (j = 0; j < n; j++) {
         xp[j] = AddMod(xp[j], ap[j], q);
      }
   }
   else {
      for (i = 0; i < info->NumPrimes; i++) {
         long q = GetFFTPrime(i);
         const long *ap = &a.tbl[i][0];
         long *xp = &x.tbl[i][0];
         for (j = 0; j < n; j++) {
            xp[j] = AddMod(xp[j], ap[j], q);
         }
      }
   }
}

void FFTMul(zz_pX& x, const zz_pX& a, const zz_pX& b)
{
   if (IsZero(a) || IsZero(b)) {
      clear(x);
      return;
   }

   long da = deg(a);
   long db = deg(b);
   long d = da+db;
   long k = NextPowerOfTwo(d+1);

   fftRep R1(INIT_SIZE, k), R2(INIT_SIZE, k);

   TofftRep_trunc(R1, a, k, d+1);
   TofftRep_trunc(R2, b, k, d+1);
   mul(R1, R1, R2);
   FromfftRep(x, R1, 0, d);
}

void FFTSqr(zz_pX& x, const zz_pX& a)
{
   if (IsZero(a)) {
      clear(x);
      return;
   }

   long da = deg(a);
   long d = 2*da;
   long k = NextPowerOfTwo(d+1);

   fftRep R1(INIT_SIZE, k);

   TofftRep_trunc(R1, a, k, d+1);
   mul(R1, R1, R1);
   FromfftRep(x, R1, 0, d);
}

void CopyReverse(zz_pX& x, const zz_pX& a, long lo, long hi)

   // x[0..hi-lo] = reverse(a[lo..hi]), with zero fill
   // input may not alias output

{
   long i, j, n, m;

   n = hi-lo+1;
   m = a.rep.length();

   x.rep.SetLength(n);

   const zz_p* ap = a.rep.elts();
   zz_p* xp = x.rep.elts();

   for (i = 0; i < n; i++) {
      j = hi-i;
      if (j < 0 || j >= m)
         clear(xp[i]);
      else
         xp[i] = ap[j];
   }

   x.normalize();
}

void copy(zz_pX& x, const zz_pX& a, long lo, long hi)

   // x[0..hi-lo] = a[lo..hi], with zero fill
   // input may not alias output

{
   long i, j, n, m;

   n = hi-lo+1;
   m = a.rep.length();

   x.rep.SetLength(n);

   const zz_p* ap = a.rep.elts();
   zz_p* xp = x.rep.elts();

   for (i = 0; i < n; i++) {
      j = lo + i;
      if (j < 0 || j >= m)
         clear(xp[i]);
      else
         xp[i] = ap[j];
   }

   x.normalize();
}

void rem21(zz_pX& x, const zz_pX& a, const zz_pXModulus& F)
{
   long i, da, ds, n, kk;

   da = deg(a);
   n = F.n;

   if (da > 2*n-2)
      LogicError("bad args to rem(zz_pX,zz_pX,zz_pXModulus)");

   if (da < n) {
      x = a;
      return;
   }

   if (!F.UseFFT || da - n <= NTL_zz_pX_MOD_CROSSOVER) {
      PlainRem(x, a, F.f);
      return;
   }

   fftRep R1(INIT_SIZE, F.l);
   zz_pX P1(INIT_SIZE, n);

   TofftRep_trunc(R1, a, F.l, 2*n-3, n, 2*(n-1));
   mul(R1, R1, F.HRep);
   FromfftRep(P1, R1, n-2, 2*n-4);

   TofftRep(R1, P1, F.k);
   mul(R1, R1, F.FRep);
   FromfftRep(P1, R1, 0, n-1);

   ds = deg(P1);

   kk = 1L << F.k;

   x.rep.SetLength(n);
   const zz_p* aa = a.rep.elts();
   const zz_p* ss = P1.rep.elts();
   zz_p* xx = x.rep.elts();

   for (i = 0; i < n; i++) {
      if (i <= ds)
         sub(xx[i], aa[i], ss[i]);
      else
         xx[i] = aa[i];

      if (i + kk <= da)
         add(xx[i], xx[i], aa[i+kk]);
   }

   x.normalize();
}

void DivRem21(zz_pX& q, zz_pX& x, const zz_pX& a, const zz_pXModulus& F)
{
   long i, da, ds, n, kk;

   da = deg(a);
   n = F.n;

   if (da > 2*n-2)
      LogicError("bad args to rem(zz_pX,zz_pX,zz_pXModulus)");

   if (da < n) {
      x = a;
      clear(q);
      return;
   }

   if (!F.UseFFT || da - n <= NTL_zz_pX_MOD_CROSSOVER) {
      PlainDivRem(q, x, a, F.f);
      return;
   }

   fftRep R1(INIT_SIZE, F.l);
   zz_pX P1(INIT_SIZE, n), qq;

   TofftRep_trunc(R1, a, F.l, 2*n-3, n, 2*(n-1));
   mul(R1, R1, F.HRep);
   FromfftRep(P1, R1, n-2, 2*n-4);
   qq = P1;

   TofftRep(R1, P1, F.k);
   mul(R1, R1, F.FRep);
   FromfftRep(P1, R1, 0, n-1);

   ds = deg(P1);

   kk = 1L << F.k;

   x.rep.SetLength(n);
   const zz_p* aa = a.rep.elts();
   const zz_p* ss = P1.rep.elts();
   zz_p* xx = x.rep.elts();

   for (i = 0; i < n; i++) {
      if (i <= ds)
         sub(xx[i], aa[i], ss[i]);
      else
         xx[i] = aa[i];

      if (i + kk <= da)
         add(xx[i], xx[i], aa[i+kk]);
   }

   x.normalize();
   q = qq;
}

void div21(zz_pX& x, const zz_pX& a, const zz_pXModulus& F)
{
   long da, n;

   da = deg(a);
   n = F.n;

   if (da > 2*n-2)
      LogicError("bad args to rem(zz_pX,zz_pX,zz_pXModulus)");

   if (da < n) {
      clear(x);
      return;
   }

   if (!F.UseFFT || da - n <= NTL_zz_pX_MOD_CROSSOVER) {
      PlainDiv(x, a, F.f);
      return;
   }

   fftRep R1(INIT_SIZE, F.l);
   zz_pX P1(INIT_SIZE, n);

   TofftRep_trunc(R1, a, F.l, 2*n-3, n, 2*(n-1));
   mul(R1, R1, F.HRep);
   FromfftRep(x, R1, n-2, 2*n-4);
}

void rem(zz_pX& x, const zz_pX& a, const zz_pXModulus& F)
{
   long da = deg(a);
   long n = F.n;

   if (n < 0) LogicError("rem: uninitialized modulus");

   if (da <= 2*n-2) {
      rem21(x, a, F);
      return;
   }
   else if (!F.UseFFT || da-n <= NTL_zz_pX_MOD_CROSSOVER) {
      PlainRem(x, a, F.f);
      return;
   }

   zz_pX buf(INIT_SIZE, 2*n-1);

   long a_len = da+1;

   while (a_len > 0) {
      long old_buf_len = buf.rep.length();
      long amt = min(2*n-1-old_buf_len, a_len);

      buf.rep.SetLength(old_buf_len+amt);

      long i;

      for (i = old_buf_len+amt-1; i >= amt; i--)
         buf.rep[i] = buf.rep[i-amt];

      for (i = amt-1; i >= 0; i--)
         buf.rep[i] = a.rep[a_len-amt+i];

      buf.normalize();

      rem21(buf, buf, F);

      a_len -= amt;
   }

   x = buf;
}

void DivRem(zz_pX& q, zz_pX& r, const zz_pX& a, const zz_pXModulus& F)
{
   long da = deg(a);
   long n = F.n;

   if (n < 0) LogicError("DivRem: uninitialized modulus");

   if (da <= 2*n-2) {
      DivRem21(q, r, a, F);
      return;
   }
   else if (!F.UseFFT || da-n <= NTL_zz_pX_MOD_CROSSOVER) {
      PlainDivRem(q, r, a, F.f);
      return;
   }

   zz_pX buf(INIT_SIZE, 2*n-1);
   zz_pX qbuf(INIT_SIZE, n-1);

   zz_pX qq;
   qq.rep.SetLength(da-n+1);

   long a_len = da+1;
   long q_hi = da-n+1;

   while (a_len > 0) {
      long old_buf_len = buf.rep.length();
      long amt = min(2*n-1-old_buf_len, a_len);

      buf.rep.SetLength(old_buf_len+amt);

      long i;

      for (i = old_buf_len+amt-1; i >= amt; i--)
         buf.rep[i] = buf.rep[i-amt];

      for (i = amt-1; i >= 0; i--)
         buf.rep[i] = a.rep[a_len-amt+i];

      buf.normalize();

      DivRem21(qbuf, buf, buf, F);
      long dl = qbuf.rep.length();
      a_len = a_len - amt;
      for(i = 0; i < dl; i++)
         qq.rep[a_len+i] = qbuf.rep[i];
      for(i = dl+a_len; i < q_hi; i++)
         clear(qq.rep[i]);
      q_hi = a_len;
   }

   r = buf;

   qq.normalize();
   q = qq;
}

void div(zz_pX& q, const zz_pX& a, const zz_pXModulus& F)
{
   long da = deg(a);
   long n = F.n;

   if (n < 0) LogicError("div: uninitialized modulus");

   if (da <= 2*n-2) {
      div21(q, a, F);
      return;
   }
   else if (!F.UseFFT || da-n <= NTL_zz_pX_MOD_CROSSOVER) {
      PlainDiv(q, a, F.f);
      return;
   }

   zz_pX buf(INIT_SIZE, 2*n-1);
   zz_pX qbuf(INIT_SIZE, n-1);

   zz_pX qq;
   qq.rep.SetLength(da-n+1);

   long a_len = da+1;
   long q_hi = da-n+1;

   while (a_len > 0) {
      long old_buf_len = buf.rep.length();
      long amt = min(2*n-1-old_buf_len, a_len);

      buf.rep.SetLength(old_buf_len+amt);

      long i;

      for (i = old_buf_len+amt-1; i >= amt; i--)
         buf.rep[i] = buf.rep[i-amt];

      for (i = amt-1; i >= 0; i--)
         buf.rep[i] = a.rep[a_len-amt+i];

      buf.normalize();

      a_len = a_len - amt;
      if (a_len > 0)
         DivRem21(qbuf, buf, buf, F);
      else
         div21(qbuf, buf, F);

      long dl = qbuf.rep.length();
      for(i = 0; i < dl; i++)
         qq.rep[a_len+i] = qbuf.rep[i];
      for(i = dl+a_len; i < q_hi; i++)
         clear(qq.rep[i]);
      q_hi = a_len;
   }

   qq.normalize();
   q = qq;
}

void MulMod(zz_pX& x, const zz_pX& a, const zz_pX& b, const zz_pXModulus& F)
{
   long  da, db, d, n, k;

   da = deg(a);
   db = deg(b);
   n = F.n;

   if (n < 0) LogicError("MulMod: uninitialized modulus");

   if (da >= n || db >= n)
      LogicError("bad args to MulMod(zz_pX,zz_pX,zz_pX,zz_pXModulus)");

   if (da < 0 || db < 0) {
      clear(x);
      return;
   }

   if (!F.UseFFT || da <= NTL_zz_pX_MUL_CROSSOVER || db <= NTL_zz_pX_MUL_CROSSOVER) {
      zz_pX P1;
      mul(P1, a, b);
      rem(x, P1, F);
      return;
   }

   d = da + db + 1;

   k = NextPowerOfTwo(d);
   k = max(k, F.k);

   fftRep R1(INIT_SIZE, k), R2(INIT_SIZE, F.l);
   zz_pX P1(INIT_SIZE, n);

   long len;
   if (zz_p::IsFFTPrime())
      len = n;
   else
      len = 1L << F.k;

   TofftRep_trunc(R1, a, k, max(1L << F.k, d));
   TofftRep_trunc(R2, b, k, max(1L << F.k, d));
   mul(R1, R1, R2);
   NDFromfftRep(P1, R1, n, d-1, R2); // save R1 for future use

   TofftRep_trunc(R2, P1, F.l, 2*n-3);
   mul(R2, R2, F.HRep);
   FromfftRep(P1, R2, n-2, 2*n-4);

   TofftRep_trunc(R2, P1, F.k, len);
   mul(R2, R2, F.FRep);
   reduce(R1, R1, F.k);
   sub(R1, R1, R2);
   FromfftRep(x, R1, 0, n-1);
}

void SqrMod(zz_pX& x, const zz_pX& a, const zz_pXModulus& F)
{
   long  da, d, n, k;

   da = deg(a);
   n = F.n;

   if (n < 0) LogicError("SqrMod: uninitialized modulus");

   if (da >= n)
      LogicError("bad args to SqrMod(zz_pX,zz_pX,zz_pXModulus)");

   if (!F.UseFFT || da <= NTL_zz_pX_MUL_CROSSOVER) {
      zz_pX P1;
      sqr(P1, a);
      rem(x, P1, F);
      return;
   }

   d = 2*da + 1;

   k = NextPowerOfTwo(d);
   k = max(k, F.k);

   fftRep R1(INIT_SIZE, k), R2(INIT_SIZE, F.l);
   zz_pX P1(INIT_SIZE, n);

   long len;
   if (zz_p::IsFFTPrime())
      len = n;
   else
      len = 1L << F.k;

   TofftRep_trunc(R1, a, k, max(1L << F.k, d));
   mul(R1, R1, R1);
   NDFromfftRep(P1, R1, n, d-1, R2); // save R1 for future use

   TofftRep_trunc(R2, P1, F.l, 2*n-3);
   mul(R2, R2, F.HRep);
   FromfftRep(P1, R2, n-2, 2*n-4);

   TofftRep_trunc(R2, P1, F.k, len);
   mul(R2, R2, F.FRep);
   reduce(R1, R1, F.k);
   sub(R1, R1, R2);
   FromfftRep(x, R1, 0, n-1);
}

void PlainInvTrunc(zz_pX& x, const zz_pX& a, long m)

   /* x = (1/a) % X^m, input not output, constant term a is nonzero */

{
   long i, k, n, lb;
   zz_p v, t;
   zz_p s;
   const zz_p* ap;
   zz_p* xp;

   n = deg(a);

   if (n < 0) ArithmeticError("division by zero");

   inv(s, ConstTerm(a));

   if (n == 0) {
      conv(x, s);
      return;
   }

   ap = a.rep.elts();
   x.rep.SetLength(m);
   xp = x.rep.elts();

   xp[0] = s;

   long is_one = IsOne(s);

   for (k = 1; k < m; k++) {
      clear(v);
      lb = max(k-n, 0);
      for (i = lb; i <= k-1; i++) {
         mul(t, xp[i], ap[k-i]);
         add(v, v, t);
      }
      xp[k] = v;
      negate(xp[k], xp[k]);
      if (!is_one) mul(xp[k], xp[k], s);
   }

   x.normalize();
}

void trunc(zz_pX& x, const zz_pX& a, long m)

// x = a % X^m, output may alias input

{
   if (m < 0) LogicError("trunc: bad args");

   if (&x == &a) {
      if (x.rep.length() > m) {
         x.rep.SetLength(m);
         x.normalize();
      }
   }
   else {
      long n;
      long i;
      zz_p* xp;
      const zz_p* ap;

      n = min(a.rep.length(), m);
      x.rep.SetLength(n);

      xp = x.rep.elts();
      ap = a.rep.elts();

      for (i = 0; i < n; i++) xp[i] = ap[i];

      x.normalize();
   }
}

void CyclicReduce(zz_pX& x, const zz_pX& a, long m)

// computes x = a mod X^m-1

{
   long n = deg(a);
   long i, j;
   long accum;
   long p = zz_p::modulus();

   if (n < m) {
      x = a;
      return;
   }

   if (&x != &a)
      x.rep.SetLength(m);

   for (i = 0; i < m; i++) {
      accum = rep(a.rep[i]);
      for (j = i + m; j <= n; j += m)
         accum = AddMod(accum, rep(a.rep[j]), p);
      x.rep[i].LoopHole() = accum;
   }

   if (&x == &a)
      x.rep.SetLength(m);

   x.normalize();
}

void InvTrunc(zz_pX& x, const zz_pX& a, long m)
{
   if (m < 0) LogicError("InvTrunc: bad args");
   if (m == 0) {
      clear(x);
      return;
   }

   if (NTL_OVERFLOW(m, 1, 0))
      ResourceError("overflow in InvTrunc");

   if (&x == &a) {
      zz_pX la;
      la = a;
      if (m > NTL_zz_pX_NEWTON_CROSSOVER && deg(a) > 0)
         NewtonInvTrunc(x, la, m);
      else
         PlainInvTrunc(x, la, m);
   }
   else {
      if (m > NTL_zz_pX_NEWTON_CROSSOVER && deg(a) > 0)
         NewtonInvTrunc(x, a, m);
      else
         PlainInvTrunc(x, a, m);
   }
}

void build(zz_pXModulus& x, const zz_pX& f)
{
   x.f = f;
   x.n = deg(f);

   x.tracevec.make();

   if (x.n <= 0)
      LogicError("build: deg(f) must be at least 1");

   if (x.n <= NTL_zz_pX_MOD_CROSSOVER + 1) {
      x.UseFFT = 0;
      return;
   }

   x.UseFFT = 1;

   x.k = NextPowerOfTwo(x.n);
   x.l = NextPowerOfTwo(2*x.n - 3);
   TofftRep(x.FRep, f, x.k);

   zz_pX P1(INIT_SIZE, x.n+1), P2(INIT_SIZE, x.n);

   CopyReverse(P1, f, 0, x.n);
   InvTrunc(P2, P1, x.n-1);

   CopyReverse(P1, P2, 0, x.n-2);
   TofftRep(x.HRep, P1, x.l);
}

zz_pXModulus::zz_pXModulus(const zz_pX& ff)
{
   build(*this, ff);
}

zz_pXMultiplier::zz_pXMultiplier(const zz_pX& b, const zz_pXModulus& F)
{
   build(*this, b, F);
}

void build(zz_pXMultiplier& x, const zz_pX& b,
                         const zz_pXModulus& F)
{
   long db;
   long n = F.n;

   if (n < 0) LogicError("build zz_pXMultiplier: uninitialized modulus");

   x.b = b;
   db = deg(b);

   if (db >= n) LogicError("build zz_pXMultiplier: deg(b) >= deg(f)");

   if (!F.UseFFT || db <= NTL_zz_pX_MOD_CROSSOVER) {
      x.UseFFT = 0;
      return;
   }

   x.UseFFT = 1;

   fftRep R1(INIT_SIZE, F.l);
   zz_pX P1(INIT_SIZE, n);

   TofftRep_trunc(R1, b, F.l, 2*n-2);
   reduce(x.B2, R1, F.k);
   mul(R1, R1, F.HRep);
   FromfftRep(P1, R1, n-1, 2*n-3);

   TofftRep(x.B1, P1, F.l);
   // could be truncated to length max(1L << F.k, 2*n-2), except
   // for the usage in UpdateMap, where we would have to investigate
   // further

}

void MulMod(zz_pX& x, const zz_pX& a, const zz_pXMultiplier& B,
                                      const zz_pXModulus& F)
{

   long n = F.n;
   long da;

   da = deg(a);

   if (da >= n)
      LogicError(" bad args to MulMod(zz_pX,zz_pX,zz_pXMultiplier,zz_pXModulus)");

   if (da < 0) {
      clear(x);
      return;
   }

   if (!B.UseFFT || !F.UseFFT || da <= NTL_zz_pX_MOD_CROSSOVER) {
      zz_pX P1;
      mul(P1, a, B.b);
      rem(x, P1, F);
      return;
   }

   zz_pX P1(INIT_SIZE, n), P2(INIT_SIZE, n);
   fftRep R1(INIT_SIZE, F.l), R2(INIT_SIZE, F.l);

   long len;
   if (zz_p::IsFFTPrime())
      len = n;
   else
      len = 1L << F.k;

   TofftRep_trunc(R1, a, F.l, max(1L << F.k, 2*n-2));
   mul(R2, R1, B.B1);
   FromfftRep(P1, R2, n-1, 2*n-3);

   reduce(R1, R1, F.k);
   mul(R1, R1, B.B2);
   TofftRep_trunc(R2, P1, F.k, len);
   mul(R2, R2, F.FRep);
   sub(R1, R1, R2);

   FromfftRep(x, R1, 0, n-1);
}

void PowerXMod(zz_pX& hh, const ZZ& e, const zz_pXModulus& F)
{
   if (F.n < 0) LogicError("PowerXMod: uninitialized modulus");

   if (IsZero(e)) {
      set(hh);
      return;
   }

   long n = NumBits(e);
   long i;

   zz_pX h;

   h.SetMaxLength(F.n);
   set(h);

   for (i = n - 1; i >= 0; i--) {
      SqrMod(h, h, F);
      if (bit(e, i))
         MulByXMod(h, h, F.f);
   }

   if (e < 0) InvMod(h, h, F);

   hh = h;
}

void PowerXPlusAMod(zz_pX& hh, zz_p a, const ZZ& e, const zz_pXModulus& F)
{
   if (F.n < 0) LogicError("PowerXPlusAMod: uninitialized modulus");

   if (IsZero(e)) {
      set(hh);
      return;
   }

   zz_pX t1(INIT_SIZE, F.n), t2(INIT_SIZE, F.n);
   long n = NumBits(e);
   long i;

   zz_pX h;

   h.SetMaxLength(F.n);
   set(h);

   for (i = n - 1; i >= 0; i--) {
      SqrMod(h, h, F);
      if (bit(e, i)) {
         MulByXMod(t1, h, F.f);
         mul(t2, h, a);
         add(h, t1, t2);
      }
   }

   if (e < 0) InvMod(h, h, F);

   hh = h;
}

void PowerMod(zz_pX& h, const zz_pX& g, const ZZ& e, const zz_pXModulus& F)
{
   if (deg(g) >= F.n) LogicError("PowerMod: bad args");

   if (IsZero(e)) {
      set(h);
      return;
   }

   zz_pXMultiplier G;

   zz_pX res;

   long n = NumBits(e);
   long i;

   build(G, g, F);

   res.SetMaxLength(F.n);
   set(res);

   for (i = n - 1; i >= 0; i--) {
      SqrMod(res, res, F);
      if (bit(e, i))
         MulMod(res, res, G, F);
   }

   if (e < 0) InvMod(res, res, F);

   h = res;
}

void NewtonInvTrunc(zz_pX& x, const zz_pX& a, long m)
{
   x.SetMaxLength(m);

   long i;
   long t;

   t = NextPowerOfTwo(2*m-1);

   fftRep R1(INIT_SIZE, t), R2(INIT_SIZE, t);
   zz_pX P1(INIT_SIZE, m);

   long log2_newton = NextPowerOfTwo(NTL_zz_pX_NEWTON_CROSSOVER)-1;

   PlainInvTrunc(x, a, 1L << log2_newton);
   long k = 1L << log2_newton;
   long a_len = min(m, a.rep.length());

   while (k < m) {
      long l = min(2*k, m);

      t = NextPowerOfTwo(2*k);
      TofftRep(R1, x, t);
      mul(R1, R1, R1);
      FromfftRep(P1, R1, 0, l-1);

      t = NextPowerOfTwo(deg(P1) + min(l, a_len));
      TofftRep(R1, P1, t);
      TofftRep(R2, a, t, 0, min(l, a_len)-1);
      mul(R1, R1, R2);
      FromfftRep(P1, R1, k, l-1);

      x.rep.SetLength(l);
      long y_len = P1.rep.length();
      for (i = k; i < l; i++) {
         if (i-k >= y_len)
            clear(x.rep[i]);
         else
            negate(x.rep[i], P1.rep[i-k]);
      }
      x.normalize();

      k = l;
   }
}

void FFTDivRem(zz_pX& q, zz_pX& r, const zz_pX& a, const zz_pX& b)
{
   long n = deg(b);
   long m = deg(a);
   long k, l;

   if (m < n) {
      clear(q);
      r = a;
      return;
   }

   if (m >= 3*n) {
      zz_pXModulus B;
      build(B, b);
      DivRem(q, r, a, B);
      return;
   }

   zz_pX P1, P2, P3;

   CopyReverse(P3, b, 0, n);
   InvTrunc(P2, P3, m-n+1);
   CopyReverse(P1, P2, 0, m-n);

   k = NextPowerOfTwo(2*(m-n)+1);
   long k1 = NextPowerOfTwo(n);
   long mx = max(k1, k);

   fftRep R1(INIT_SIZE, mx), R2(INIT_SIZE, mx);

   TofftRep(R1, P1, k);
   TofftRep(R2, a, k, n, m);
   mul(R1, R1, R2);
   FromfftRep(P3, R1, m-n, 2*(m-n));

   l = 1L << k1;

   TofftRep(R1, b, k1);
   TofftRep(R2, P3, k1);
   mul(R1, R1, R2);
   FromfftRep(P1, R1, 0, n-1);
   CyclicReduce(P2, a, l);
   trunc(r, P2, n);
   sub(r, r, P1);
   q = P3;
}

void FFTDiv(zz_pX& q, const zz_pX& a, const zz_pX& b)
{

   long n = deg(b);
   long m = deg(a);
   long k;

   if (m < n) {
      clear(q);
      return;
   }

   if (m >= 3*n) {
      zz_pXModulus B;
      build(B, b);
      div(q, a, B);
      return;
   }

   zz_pX P1, P2, P3;

   CopyReverse(P3, b, 0, n);
   InvTrunc(P2, P3, m-n+1);
   CopyReverse(P1, P2, 0, m-n);

   k = NextPowerOfTwo(2*(m-n)+1);

   fftRep R1(INIT_SIZE, k), R2(INIT_SIZE, k);

   TofftRep(R1, P1, k);
   TofftRep(R2, a, k, n, m);
   mul(R1, R1, R2);
   FromfftRep(q, R1, m-n, 2*(m-n));
}

void FFTRem(zz_pX& r, const zz_pX& a, const zz_pX& b)
{
   long n = deg(b);
   long m = deg(a);
   long k, l;

   if (m < n) {
      r = a;
      return;
   }

   if (m >= 3*n) {
      zz_pXModulus B;
      build(B, b);
      rem(r, a, B);
      return;
   }

   zz_pX P1, P2, P3;

   CopyReverse(P3, b, 0, n);
   InvTrunc(P2, P3, m-n+1);
   CopyReverse(P1, P2, 0, m-n);

   k = NextPowerOfTwo(2*(m-n)+1);
   long k1 = NextPowerOfTwo(n);
   long mx = max(k, k1);

   fftRep R1(INIT_SIZE, mx), R2(INIT_SIZE, mx);

   TofftRep(R1, P1, k);
   TofftRep(R2, a, k, n, m);
   mul(R1, R1, R2);
   FromfftRep(P3, R1, m-n, 2*(m-n));

   l = 1L << k1;

   TofftRep(R1, b, k1);
   TofftRep(R2, P3, k1);
   mul(R1, R1, R2);
   FromfftRep(P3, R1, 0, n-1);
   CyclicReduce(P2, a, l);
   trunc(r, P2, n);
   sub(r, r, P3);
}

void DivRem(zz_pX& q, zz_pX& r, const zz_pX& a, const zz_pX& b)
{
   if (deg(b) > NTL_zz_pX_DIV_CROSSOVER && deg(a) - deg(b) > NTL_zz_pX_DIV_CROSSOVER)
      FFTDivRem(q, r, a, b);
   else
      PlainDivRem(q, r, a, b);
}

void div(zz_pX& q, const zz_pX& a, const zz_pX& b)
{
   if (deg(b) > NTL_zz_pX_DIV_CROSSOVER && deg(a) - deg(b) > NTL_zz_pX_DIV_CROSSOVER)
      FFTDiv(q, a, b);
   else
      PlainDiv(q, a, b);
}

void div(zz_pX& q, const zz_pX& a, zz_p b)
{
   zz_p t;
   inv(t, b);
   mul(q, a, t);
}

void rem(zz_pX& r, const zz_pX& a, const zz_pX& b)
{
   if (deg(b) > NTL_zz_pX_DIV_CROSSOVER && deg(a) - deg(b) > NTL_zz_pX_DIV_CROSSOVER)
      FFTRem(r, a, b);
   else
      PlainRem(r, a, b);
}

long operator==(const zz_pX& a, long b)
{
   if (b == 0)
      return IsZero(a);

   if (b == 1)
      return IsOne(a);

   long da = deg(a);

   if (da > 0)
      return 0;

   zz_p bb;
   bb = b;

   if (da < 0)
      return IsZero(bb);

   return a.rep[0] == bb;
}

long operator==(const zz_pX& a, zz_p b)
{
   if (IsZero(b))
      return IsZero(a);

   long da = deg(a);

   if (da != 0)
      return 0;

   return a.rep[0] == b;
}

void power(zz_pX& x, const zz_pX& a, long e)
{
   if (e < 0) {
      ArithmeticError("power: negative exponent");
   }

   if (e == 0) {
      x = 1;
      return;
   }

   if (a == 0 || a == 1) {
      x = a;
      return;
   }

   long da = deg(a);

   if (da == 0) {
      x = power(ConstTerm(a), e);
      return;
   }

   if (da > (NTL_MAX_LONG-1)/e)
      ResourceError("overflow in power");

   zz_pX res;
   res.SetMaxLength(da*e + 1);
   res = 1;

   long k = NumBits(e);
   long i;

   for (i = k - 1; i >= 0; i--) {
      sqr(res, res);
      if (bit(e, i))
         mul(res, res, a);
   }

   x = res;
}

void reverse(zz_pX& x, const zz_pX& a, long hi)
{
   if (hi < 0) { clear(x); return; }
   if (NTL_OVERFLOW(hi, 1, 0))
      ResourceError("overflow in reverse");

   if (&x == &a) {
      zz_pX tmp;
      CopyReverse(tmp, a, 0, hi);
      x = tmp;
   }
   else
      CopyReverse(x, a, 0, hi);
}

NTL_END_IMPL


/********************************************************************************
 * Copyright 2016 The Robotics Group, The Maersk Mc-Kinney Moller Institute,
 * Faculty of Engineering, University of Southern Denmark
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ********************************************************************************/

#include "Random.hpp"

#include <rw/common/TimerUtil.hpp>
#include <rw/core/macros.hpp>

#include <boost/random/mersenne_twister.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/random/variate_generator.hpp>

using namespace rw::math;

namespace {
boost::mt19937 generator;
boost::uniform_real<> distributor;

typedef boost::normal_distribution< double > dist_type;

boost::variate_generator< boost::mt19937&, dist_type >
    normal_distribution (generator, boost::normal_distribution< double > (0, 1));
}    // namespace

double Random::ranNormalDist (double mean, double sigma)
{
    return mean + sigma * normal_distribution ();
}

double Random::ran ()
{
    return distributor (generator);
}

void Random::seed (unsigned seed)
{
    // VC++ can't select the correct seed() method without a cast here.
    generator.seed (static_cast< boost::mt19937::result_type > (seed));
}

void Random::seed ()
{
    seed ((unsigned) rw::common::TimerUtil::currentTimeMs ());
}

double Random::ran (double from, double to)
{
    if (from > to) {
        RW_THROW ("From must be smaller than to: " << from << ">" << to);
    }
    else if (from == to) {
        return from;
    }

    double res = from;
    do {
        res = from + (to - from) * Random::ran ();
    } while (res >= to);

    return res;
}

int Random::ranI (int from, int to)
{
    return (int) floor (Random::ran (from, to));
}


//
//  test_network.cpp
//  NeuralNetwork
//
//  Created by Nikhil Joshi on 11/15/14.
//  Copyright (c) 2014 Nikhil Joshi. All rights reserved.
//

#include "network.hpp"

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

BOOST_AUTO_TEST_SUITE(tests_network)

BOOST_AUTO_TEST_CASE(topologyTests) {

    unsigned int input(10), output(12);

    NNetwork::Network n1({input, output});

    BOOST_CHECK_EQUAL(n1.numInputs(), input);
    BOOST_CHECK_EQUAL(n1.numOutputs(), output);
    BOOST_CHECK_EQUAL(n1.numLayers(), 2);
}

BOOST_AUTO_TEST_CASE(networkImplementationTests) {

    // A small test network
    unsigned int in(4), hidden(5), hidden2(4), out(2);
    NNetwork::Network testNet({in, hidden, hidden2, out});

    // A pure logistic node network
    std::cout << "Logistic Network test started!" << std::endl;
    BOOST_ASSERT(NNetwork::isValidNetworkImplementation(testNet));
    std::cout << "Logistic Network test ended!" << std::endl;

    // A logistic network with output softmax
    testNet.setSoftmaxAtOutputLayer();
    std::cout << "Logistic Network with softmax output test started!" << std::endl;
    BOOST_ASSERT(NNetwork::isValidNetworkImplementation(testNet));
    std::cout << "Logistic Network with softmax output test ended!" << std::endl;

    // A tan hyperbolic network
    size_t numLayers = testNet.numLayers();
    for (int layer = 0; layer < numLayers; layer++)
        testNet.setFunctionalityAtLayer(layer, *FunctionalitySuite::tanHyperbolic());

    std::cout << "TanHyperbolic Network with softmax output test started!" << std::endl;
    BOOST_ASSERT(NNetwork::isValidNetworkImplementation(testNet));
    std::cout << "TanHyperbolic Network with softmax output test ended!" << std::endl;

}

BOOST_AUTO_TEST_CASE(networkTrainingTest) {
//
//    // Just in case
//    srand(static_cast<unsigned int>(time(NULL)));
//
//    // A test network
//    NNetwork::Network testNet({2, 1});
//
//    // Network topology
//    size_t in(testNet.numInputs()),
//    out(testNet.numOutputs());
//
//    // Generate a number of toy examples
//    int numExamples(50);
//
//    // Original weights (some known random values)
//    auto originalWeights = Eigen::VectorXd(in+out);
//    originalWeights << 5.5, 0.3, 1.4;
//
//    // Setup inputs and labels
//    Eigen::MatrixXd data(numExamples, in + out);
//    // TEMP (passing eigen doesn't work :()
//    std::vector<std::vector<double>> stdData(numExamples);
//    for (int i=0; i < numExamples; i++) {
//        auto inputs = Eigen::random<Eigen::VectorXd>(in, 1, -10, 10);
//
//        Eigen::VectorXd inAug(in+1);
//        inAug << Eigen::appendOnesAtHead(inputs);
//
//        // prepare the label
//        auto labels = 1.0 / (1 + std::exp(-(static_cast<double>(originalWeights.transpose()*inAug))));
//
//        Eigen::VectorXd example(in + out);
//        example << inputs, labels;
//        data.row(i) = example;
//        stdData[i] = Eigen::toStdVector(example);
//        std::cout << example.transpose() << std::endl;
//    }
//
//    double tolerance(0.01);
//    double regularizer(0.01);
//
//
//    testNet.train(stdData, regularizer, tolerance);
//
//    testNet.printWeights();
//

}

BOOST_AUTO_TEST_SUITE_END()


#include <boost/mpi.hpp>
#include <elemental.hpp>
#include <skylark.hpp>
#include <iostream>
#include <boost/test/minimal.hpp>

#include "test_utils.hpp"

/** Aliases */

typedef elem::Matrix<double>     dense_matrix_t;
typedef elem::DistMatrix<double> dist_dense_matrix_t;
typedef elem::DistMatrix<double, elem::CIRC, elem::CIRC>
dist_CIRC_CIRC_dense_matrix_t;

typedef dist_dense_matrix_t input_matrix_t;
typedef dist_dense_matrix_t output_matrix_t;
typedef skylark::sketch::JLT_t<input_matrix_t, output_matrix_t>
sketch_transform_t;

typedef skylark::sketch::JLT_t<dense_matrix_t, dense_matrix_t>
sketch_transform_local_t;

int test_main(int argc, char* argv[]) {

    /** Initialize Elemental */
    elem::Initialize (argc, argv);

    /** Initialize MPI  */
    boost::mpi::environment env(argc, argv);
    boost::mpi::communicator world;

    MPI_Comm mpi_world(world);
    elem::Grid grid(mpi_world);

    /** Example parameters */
    int height      = 20;
    int width       = 10;
    int sketch_size = 5;

    dist_CIRC_CIRC_dense_matrix_t A_CIRC_CIRC(grid);
    input_matrix_t A(grid);
    elem::Uniform(A_CIRC_CIRC, height, width);
    A = A_CIRC_CIRC;
    int size;

    /** Sketch transform rowwise (rw) */
    size = width;
    skylark::base::context_t context_rw(0);
    output_matrix_t sketched_A_rw(height, sketch_size);
    sketch_transform_t sketch_transform_rw(size, sketch_size, context_rw);
    sketch_transform_rw.apply(A, sketched_A_rw,
        skylark::sketch::rowwise_tag());
    dist_CIRC_CIRC_dense_matrix_t sketched_A_rw_CIRC_CIRC = sketched_A_rw;

    if(world.rank() == 0) {
        dense_matrix_t sketched_A_rw_gathered =
            sketched_A_rw_CIRC_CIRC.Matrix();

        skylark::base::context_t context_rw_local(0);
        dense_matrix_t A_rw_local = A_CIRC_CIRC.Matrix();
        dense_matrix_t sketched_A_rw_local(height, sketch_size);
        sketch_transform_local_t sketch_transform_rw_local(size, sketch_size,
            context_rw_local);
        sketch_transform_rw_local.apply(A_rw_local, sketched_A_rw_local,
            skylark::sketch::rowwise_tag());

        if (!equal(sketched_A_rw_gathered, sketched_A_rw_local))
            BOOST_FAIL("Rowwise sketching resuts are not equal");
    }

    /** Sketch transform columnwise (cw) */
    size = height;
    skylark::base::context_t context_cw(0);
    output_matrix_t sketched_A_cw(sketch_size, width);
    sketch_transform_t sketch_transform_cw(size, sketch_size, context_cw);
    sketch_transform_cw.apply(A, sketched_A_cw,
        skylark::sketch::columnwise_tag());
    dist_CIRC_CIRC_dense_matrix_t sketched_A_cw_CIRC_CIRC = sketched_A_cw;

    if(world.rank() == 0) {
        dense_matrix_t sketched_A_cw_gathered =
            sketched_A_cw_CIRC_CIRC.Matrix();

        skylark::base::context_t context_cw_local(0);
        dense_matrix_t A_cw_local = A_CIRC_CIRC.Matrix();
        dense_matrix_t sketched_A_cw_local(sketch_size, width);
        sketch_transform_local_t sketch_transform_cw_local(size, sketch_size,
            context_cw_local);
        sketch_transform_cw_local.apply(A_cw_local, sketched_A_cw_local,
            skylark::sketch::columnwise_tag());

        if (!equal(sketched_A_cw_gathered, sketched_A_cw_local))
            BOOST_FAIL("Columnwise sketching resuts are not equal");
    }

    elem::Finalize();
    return 0;
}


#define BOOST_TEST_MODULE ExpressionTypeDeduction
#include <boost/test/unit_test.hpp>
#include <vexcl/vector.hpp>
#include <vexcl/element_index.hpp>
#include <vexcl/mba.hpp>
#ifdef RESOLVED_70
#include <vexcl/spmat.hpp>
#endif
#include <vexcl/tagged_terminal.hpp>
#include <vexcl/temporary.hpp>
#include <vexcl/cast.hpp>
#include <vexcl/vector_view.hpp>
#include <vexcl/reductor.hpp>
#include <vexcl/function.hpp>
#include "context_setup.hpp"

template <typename T>
inline std::array<T, 2> make_array(T x,  T y) {
    std::array<T, 2> p = {{x, y}};
    return p;
}

template <class Result, class Expr>
void check(const Expr &expr) {
    typedef typename vex::detail::return_type<Expr>::type Deduced;

    boost::proto::display_expr( boost::proto::as_child(expr) );
    std::cout << vex::type_name<Deduced>() << std::endl << std::endl;

    BOOST_CHECK( (std::is_same<Deduced, Result>::value) );
}

BOOST_AUTO_TEST_CASE(terminals)
{
    const size_t n = 1024;

    vex::vector<double>     x;
    vex::vector<int>        y;
    vex::vector<cl_double2> z;

    check<int>       (5);
    check<double>    (4.2);
    check<double>    (x);
    check<int>       (y);
    check<cl_double2>(z);
    check<size_t>(vex::element_index());

    {
        vex::mba<2, float> *surf= 0;
        check<float>( (*surf)(x, y) );
    }

#ifdef RESOLVED_70
    {
        vex::SpMatCCSR<double> *A = 0;
        check<double>( (*A) * x );
    }

    {
        std::vector<cl::CommandQueue> q1(1, ctx.queue(0));
        vex::vector<double> x1(q1, n);
        vex::SpMat<double> *A = 0;
        check<double>( vex::make_inline( (*A) * x1 ) );
    }
#endif

    check<double>( vex::tag<1>(x) );

    {
        auto tmp = vex::make_temp<1>(x * y);
        check<double>( tmp );
    }

    {
        std::vector<vex::command_queue> q1(1, ctx.queue(0));
        vex::vector<int> x1(q1, n);
        vex::slicer<1> slice(vex::extents[n]);
        check<int>( slice[vex::range(0, 2, n)](x1) );
    }
}

BOOST_AUTO_TEST_CASE(logical_expr)
{
    vex::vector<double> x;
    vex::vector<int> y;

    check<cl_long>(x < y);
    check<cl_long>(5 > pow(x, 2.0 * y));
    check<cl_long>(!x);
}

BOOST_AUTO_TEST_CASE(nary_expr)
{
    vex::vector<double>     x;
    vex::vector<cl_double2> x2;
    vex::vector<int>        y;
    vex::vector<cl_int2>    y2;

    check<double>    (x + y);
    check<double>    (x + 2 * y);
    check<int>       (-y);
    check<cl_double2>(x * x2);
    check<cl_double2>(y * x2);
    check<cl_double2>(x * y2);
}

BOOST_AUTO_TEST_CASE(user_functions)
{
    vex::vector<double> x;
    vex::vector<int> y;

    VEX_FUNCTION(double, f1, (double, x),            return 42;);
    VEX_FUNCTION(int,    f2, (double, x)(double, y), return 42;);

    check<double>( f1(x) );
    check<int>   ( f2(x, y) );
    check<int>   ( f2(x + y, x - y) );
}

BOOST_AUTO_TEST_CASE(ternary_operator)
{
    vex::vector<double> x;
    vex::vector<int> y;

    check<int>   (  if_else(x < 0,  1,  y) );
    check<double>( *if_else(x < 0, &x, &y) );
}

BOOST_AUTO_TEST_CASE(builtin_functions)
{
    vex::vector<double> x;
    vex::vector<int> y;

    check<double>( cos(x) - sin(y) );
    check<double>( pow(x, 2.0 * y) );
}

BOOST_AUTO_TEST_CASE(reduced_view)
{
    const size_t n = 1024;

    std::vector<vex::command_queue> q1(1, ctx.queue(0));
    vex::vector<double> x(q1, n);

    vex::slicer<2> s(vex::extents[32][32]);

    check<double>( vex::reduce<vex::SUM>(s[vex::_](x), 1) );
}

BOOST_AUTO_TEST_CASE(casted_terminals)
{
    check<double>(vex::cast<double>(42));
    check<int>(vex::cast<int>(4.2));
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright (C) 2010  Davis E. King (davis@dlib.net)
// License: Boost Software License   See LICENSE.txt for the full license.

#include "tester.h"
#include <dlib/svm_threaded.h>
#include <dlib/statistics.h>
#include <vector>
#include <sstream>

namespace
{
    using namespace test;
    using namespace dlib;
    using namespace std;
    dlib::logger dlog("test.one_vs_one_trainer");

    class test_one_vs_one_trainer : public tester
    {
        /*!
            WHAT THIS OBJECT REPRESENTS
                This object represents a unit test.  When it is constructed
                it adds itself into the testing framework.
        !*/
    public:
        test_one_vs_one_trainer (
        ) :
            tester (
                "test_one_vs_one_trainer",       // the command line argument name for this test
                "Run tests on the one_vs_one_trainer stuff.", // the command line argument description
                0                     // the number of command line arguments for this test
            )
        {
        }

        template <typename sample_type, typename label_type>
        void generate_data (
            std::vector<sample_type>& samples,
            std::vector<label_type>& labels
        )
        {
            const long num = 50;

            sample_type m;

            dlib::rand rnd;

            // make some samples near the origin
            double radius = 0.5;
            for (long i = 0; i < num+10; ++i)
            {
                double sign = 1;
                if (rnd.get_random_double() < 0.5)
                    sign = -1;
                m(0) = 2*radius*rnd.get_random_double()-radius;
                m(1) = sign*sqrt(radius*radius - m(0)*m(0));

                // add this sample to our set of samples we will run k-means
                samples.push_back(m);
                labels.push_back(1);
            }

            // make some samples in a circle around the origin but far away
            radius = 10.0;
            for (long i = 0; i < num+20; ++i)
            {
                double sign = 1;
                if (rnd.get_random_double() < 0.5)
                    sign = -1;
                m(0) = 2*radius*rnd.get_random_double()-radius;
                m(1) = sign*sqrt(radius*radius - m(0)*m(0));

                // add this sample to our set of samples we will run k-means
                samples.push_back(m);
                labels.push_back(2);
            }

            // make some samples in a circle around the point (25,25)
            radius = 4.0;
            for (long i = 0; i < num+30; ++i)
            {
                double sign = 1;
                if (rnd.get_random_double() < 0.5)
                    sign = -1;
                m(0) = 2*radius*rnd.get_random_double()-radius;
                m(1) = sign*sqrt(radius*radius - m(0)*m(0));

                // translate this point away from the origin
                m(0) += 25;
                m(1) += 25;

                // add this sample to our set of samples we will run k-means
                samples.push_back(m);
                labels.push_back(3);
            }
        }

        template <typename label_type, typename scalar_type>
        void run_test (
        )
        {
            print_spinner();
            typedef matrix<scalar_type,2,1> sample_type;

            std::vector<sample_type> samples, norm_samples;
            std::vector<label_type> labels;

            // First, get our labeled set of training data
            generate_data(samples, labels);

            typedef one_vs_one_trainer<any_trainer<sample_type,scalar_type>,label_type > ovo_trainer;

            ovo_trainer trainer;

            typedef histogram_intersection_kernel<sample_type> hist_kernel;
            typedef radial_basis_kernel<sample_type> rbf_kernel;

            // make the binary trainers and set some parameters
            krr_trainer<rbf_kernel> rbf_trainer;
            svm_nu_trainer<hist_kernel> hist_trainer;
            rbf_trainer.set_kernel(rbf_kernel(0.1));

            trainer.set_trainer(rbf_trainer);
            trainer.set_trainer(hist_trainer, 1, 2);

            randomize_samples(samples, labels);
            matrix<double> res = cross_validate_multiclass_trainer(trainer, samples, labels, 2);

            print_spinner();

            matrix<scalar_type> ans(3,3);
            ans = 60,  0,  0,
                  0, 70,  0,
                  0,  0, 80;

            DLIB_TEST_MSG(ans == res, "res: \n" << res);

            // test using a normalized_function with a one_vs_one_decision_function
            {
                trainer.set_trainer(hist_trainer, 1, 2);
                vector_normalizer<sample_type> normalizer;
                normalizer.train(samples);
                for (unsigned long i = 0; i < samples.size(); ++i)
                    norm_samples.push_back(normalizer(samples[i]));
                normalized_function<one_vs_one_decision_function<ovo_trainer> > ndf;
                ndf.function = trainer.train(norm_samples, labels);
                ndf.normalizer = normalizer;
                DLIB_TEST(ndf(samples[0])  == labels[0]);
                DLIB_TEST(ndf(samples[40])  == labels[40]);
                DLIB_TEST(ndf(samples[90])  == labels[90]);
                DLIB_TEST(ndf(samples[120])  == labels[120]);
                trainer.set_trainer(hist_trainer, 1, 2);
                print_spinner();
            }

            one_vs_one_decision_function<ovo_trainer> df = trainer.train(samples, labels);

            DLIB_TEST(df.number_of_classes() == 3);

            DLIB_TEST(df(samples[0])  == labels[0]);
            DLIB_TEST(df(samples[90])  == labels[90]);

            one_vs_one_decision_function<ovo_trainer,
                decision_function<hist_kernel>,  // This is the output of the hist_trainer
                decision_function<rbf_kernel>    // This is the output of the rbf_trainer
            > df2, df3;

            df2 = df;
            ofstream fout("df.dat", ios::binary);
            serialize(df2, fout);
            fout.close();

            // load the function back in from disk and store it in df3.
            ifstream fin("df.dat", ios::binary);
            deserialize(df3, fin);

            DLIB_TEST(df3(samples[0])  == labels[0]);
            DLIB_TEST(df3(samples[90])  == labels[90]);
            res = test_multiclass_decision_function(df3, samples, labels);

            DLIB_TEST(res == ans);

        }

        void perform_test (
        )
        {
            dlog << LINFO << "run_test<double,double>()";
            run_test<double,double>();

            dlog << LINFO << "run_test<int,double>()";
            run_test<int,double>();

            dlog << LINFO << "run_test<double,float>()";
            run_test<double,float>();

            dlog << LINFO << "run_test<int,float>()";
            run_test<int,float>();
        }
    };

    test_one_vs_one_trainer a;

}


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

#include <boost/hana/ext/std/utility.hpp>
#include <boost/hana/maybe.hpp>
#include <boost/hana/tuple.hpp>
#include <boost/hana/type.hpp>

#include <type_traits>
using namespace boost::hana;

//! [foldlM]
auto builtin_common_t = sfinae([](auto t, auto u) -> decltype(type<
    std::decay_t<decltype(true ? traits::declval(t) : traits::declval(u))>
>) { return {}; });

template <typename ...T>
struct common_type { };

template <typename T, typename U>
struct common_type<T, U>
    : std::conditional_t<std::is_same<std::decay_t<T>, T>{} &&
                         std::is_same<std::decay_t<U>, U>{},
        decltype(builtin_common_t(type<T>, type<U>)),
        common_type<std::decay_t<T>, std::decay_t<U>>
    >
{ };

template <typename T1, typename ...Tn>
struct common_type<T1, Tn...>
    : decltype(foldlM<Maybe>(tuple_t<Tn...>,
                             type<std::decay_t<T1>>,
                             sfinae(metafunction<common_type>)))
{ };

template <typename ...Ts>
using common_type_t = typename common_type<Ts...>::type;

static_assert(std::is_same<
    common_type_t<char, short, char, short>, int>{}
, "");

static_assert(std::is_same<
    common_type_t<char, double, short, char, short, double>, double>{}
, "");

static_assert(std::is_same<
    common_type_t<char, short, float, short>, float
>{}, "");

static_assert(
    sfinae(metafunction<common_type>)(type<int>, type<int>, type<int*>) == nothing
, "");
//! [foldlM]

int main() { }


//
// Copyright © 2017 Arm Ltd. All rights reserved.
// SPDX-License-Identifier: MIT
//

#include <boost/test/unit_test.hpp>
#include "ParserFlatbuffersFixture.hpp"
#include "../TfLiteParser.hpp"

BOOST_AUTO_TEST_SUITE(TensorflowLiteParser)

struct TransposeConvFixture : public ParserFlatbuffersFixture
{
    explicit TransposeConvFixture(const std::string& inputShape,
                                  const std::string& outputShape,
                                  const std::string& filterShape,
                                  const std::string& filterData,
                                  const std::string& strideX,
                                  const std::string& strideY,
                                  const std::string& dataType)
    {
        m_JsonString = R"(
            {
                "version": 3,
                "operator_codes": [ { "builtin_code": "TRANSPOSE_CONV" } ],
                "subgraphs": [ {
                    "tensors": [
                        {
                            "shape": [ 4 ],
                            "type": "UINT8",
                            "buffer": 0,
                            "name": "outputShapeTensor",
                            "quantization": {
                                "min": [ 0.0 ],
                                "max": [ 255.0 ],
                                "scale": [ 1.0 ],
                                "zero_point": [ 0 ],
                            }
                        },
                        {
                            "shape": )" + filterShape + R"(,
                            "type": ")" + dataType + R"(",
                            "buffer": 1,
                            "name": "filterTensor",
                            "quantization": {
                                "min": [ 0.0 ],
                                "max": [ 255.0 ],
                                "scale": [ 1.0 ],
                                "zero_point": [ 0 ],
                            }
                        },
                        {
                            "shape": )" + inputShape + R"(,
                            "type": ")" + dataType + R"(",
                            "buffer": 2,
                            "name": "inputTensor",
                            "quantization": {
                                "min": [ 0.0 ],
                                "max": [ 255.0 ],
                                "scale": [ 1.0 ],
                                "zero_point": [ 0 ],
                            }
                        },
                        {
                            "shape": )" + outputShape + R"(,
                            "type": ")" + dataType + R"(",
                            "buffer": 3,
                            "name": "outputTensor",
                            "quantization": {
                                "min": [ 0.0 ],
                                "max": [ 255.0 ],
                                "scale": [ 1.0 ],
                                "zero_point": [ 0 ],
                            }
                        }
                    ],
                    "inputs": [ 2 ],
                    "outputs": [ 3 ],
                    "operators": [
                        {
                            "opcode_index": 0,
                            "inputs": [ 0, 1, 2 ],
                            "outputs": [ 3 ],
                            "builtin_options_type": "TransposeConvOptions",
                            "builtin_options": {
                                "padding": "VALID",
                                "stride_w": )" + strideX + R"(,
                                "stride_h": )" + strideY + R"(
                            },
                            "custom_options_format": "FLEXBUFFERS"
                        }
                    ],
                } ],
                "buffers" : [
                    { "data": )" + outputShape + R"( },
                    { "data": )" + filterData + R"( },
                    { },
                    { }
                ]
            }
        )";
        SetupSingleInputSingleOutput("inputTensor", "outputTensor");
    }
};

struct SimpleTransposeConvFixture : TransposeConvFixture
{
    SimpleTransposeConvFixture()
    : TransposeConvFixture("[ 1, 2, 2, 1 ]",  // inputShape
                           "[ 1, 3, 3, 1 ]",  // outputShape
                           "[ 1, 2, 2, 1 ]",  // filterShape
                           "[ 0, 1, 2, 4 ]",  // filterData
                           "1",               // strideX
                           "1",               // strideY
                           "UINT8")           // dataType
    {}
};

BOOST_FIXTURE_TEST_CASE( ParseSimpleTransposeConv, SimpleTransposeConvFixture )
{
    RunTest<4, armnn::DataType::QAsymmU8>(
        0,
        {
            1, 2,
            3, 4
        },
        {
            0, 1,  2,
            2, 11, 12,
            6, 20, 16
        });
}

BOOST_AUTO_TEST_SUITE_END()