//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : qi_class.c 
// Author      : Michael Jacobson, Jr. (MJ)
// Last change : MJ, September 24, 1996
//

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:quadratic_order.h>
#else
#include <LiDIA/quadratic_order.h>
#endif

qo_node *qi_class::current_order = NULL;
bigint qi_class::Delta = 0;
bigint qi_class::rootD = 0;
bigfloat qi_class::rd = 0.0;
bigint qi_class::PEA_L = 0;
bigint qi_class::ordmult = 0;
rational_factorization qi_class::omfact;
int qi_class::info = 0;



bigint
qi_class::conductor() const
{
  bigint temp;

  temp = gcd(a,b);
  return gcd(temp,get_c());
}



bool
qi_class::is_invertible() const
{
  bigint f;

  f = conductor();
  return (f.is_one());
}



bool
qi_class::is_normal() const
{
  bigint temp;

  if (a.is_zero())
    return true;

  if (Delta.is_lt_zero())
    return (b.compare(-a) > 0) && (b.compare(a) <= 0);
  else {
    if (a.compare(rootD) <= 0) {
      shift_left(temp,a,1);
      subtract(temp,rootD,temp);
      if (temp.is_negative())
        inc(temp);
      return (temp.compare(b) < 0) && (b.compare(rootD) <= 0);
    }
    else
      return (b.compare(-a) > 0) && (b.compare(a) <= 0);
  }
}



void
qi_class::normalize_imag()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  if (!is_normal()) {
    /* q = [(-b / 2a] */
    shift_left(a2,a,1);
    temp2.assign(-b,a2);
    add(temp2,temp2,bigfloat(0.5));
    temp3 = floor(temp2);
    temp3.bigintify(q);

    multiply(temp,a2,q);
    add(b,b,temp);
  }
}


void
qi_class::normalize_real()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  if (!is_normal()) {
    if (a <= rootD) {
      /* q = floor((rootD - b) / 2a) */
      shift_left(a2,a,1);
      subtract(q,rootD,b);
      temp2.assign(q,a2);
      temp3 = floor(temp2);
      temp3.bigintify(q);
    }
    else {
      /* q = floor((a - b) / 2a) */
      shift_left(a2,a,1);
      subtract(q,a,b);
      temp2.assign(q,a2);
      temp3 = floor(temp2);
      temp3.bigintify(q);
    }

    multiply(temp,a2,q);
    add(b,b,temp);
  }
}




void
qi_class::normalize()
{
  if (Delta.is_lt_zero())
    normalize_imag();
  else
    normalize_real();
}



void
qi_class::reduce_imag()
{
  bigint c,temp,q,a2;
  bigfloat temp2,temp3;

  normalize_imag();

  /* c = (b^2 - D) / 4a */
  shift_left(a2,a,2);
  square(temp,b);
  subtract(temp,temp,Delta);
  divide(c,temp,a2);

  while (!((a.compare(c) < 0) || ((!a.compare(c)) && (b.is_ge_zero())))) {
    /* a = c */
    a.assign(c);

    /* q = [b / 2a] */
    shift_left(a2,a,1);
    temp2.assign(b,a2);
    add(temp2,temp2,bigfloat(0.5));
    temp3 = floor(temp2);
    temp3.bigintify(q);

    /* b = 2aq - b */
    multiply(temp,a2,q);
    subtract(b,temp,b);

    /* c = (b^2 - D) / 4a */
    a2.multiply_by_2();
    square(temp,b);
    subtract(temp,temp,Delta);
    divide(c,temp,a2);
  }
}



void
qi_class::reduce_real()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  normalize_real();

  shift_left(temp,a,1);
  subtract(temp,rootD,temp);
  if (temp.is_negative())
    inc(temp);
  temp.absolute_value();

  while ((!((temp.compare(b) < 0) && (b.compare(rootD) <= 0))) || (a.is_lt_zero())) {
    /* a = (D - b^2) / 4a */
    shift_left(a2,a,2);
    square(temp,b);
    subtract(temp,Delta,temp);
    divide(a,temp,a2);

    if (a <= rootD) {
      /* q = floor((b + rootD) / 2a) */
      shift_left(a2,a,1);
      add(q,rootD,b);
      temp2.assign(q,a2);
      temp3 = floor(temp2);
      temp3.bigintify(q);
    }
    else {
      /* q = floor((b + a) / 2a) */
      shift_left(a2,a,1);
      add(q,a,b);
      temp2.assign(q,a2);
      temp3 = floor(temp2);
      temp3.bigintify(q);
    }

    /* b = 2aq - b */
    multiply(temp,a2,q);
    subtract(b,temp,b);

    shift_left(temp,abs(a),1);
    subtract(temp,rootD,temp);
    if (temp.is_negative())
      inc(temp);
    temp.absolute_value();
  }
}




void
qi_class::reduce()
{
  if (Delta.is_lt_zero())
    reduce_imag();
  else
    reduce_real();
}



qi_class::qi_class()
{
}



qi_class::qi_class(const quadratic_form & qf)
{
  if (!qf.is_primitive())
    lidia_error_handler("qi_class","qi_class() - the quadratic form is not \
primitive");

  if (qf.discriminant() != Delta)
    lidia_error_handler("qi_class","qi_class() - the quadratic form has a \
different discriminant (set the current order of qi_class first)");

  a.assign(abs(qf.get_a()));
  b.assign(qf.get_b());
  reduce();
}



qi_class::qi_class(const quadratic_ideal & A)
{
  if (!A.is_invertible())
    lidia_error_handler("qi_class","qi_class() - the quadratic ideal is not \
invertible");

  if (A.discriminant() != Delta)
    set_current_order(*A.which_order());

  a.assign(A.get_a());
  b.assign(A.get_b());
  reduce();
}



qi_class::qi_class(const qi_class_real & A)
{
  a.assign(A.get_a());
  b.assign(A.get_b());
}



qi_class::qi_class(const qi_class & A)
{
  a.assign(A.a);
  b.assign(A.b);
}



qi_class::~qi_class()
{
}



void
qi_class::set_current_order(quadratic_order & QO)
{
  bigfloat temp;

  if (!current_order)
    current_order = quadratic_order::add_to_list(current_order,QO);
  else if (current_order->get_qo() != &QO)
    current_order = quadratic_order::add_to_list(current_order,QO);

  Delta.assign(QO.discriminant());
  PEA_L.assign(QO.nu_bound());
  rd.assign(sqrt(bigfloat(abs(Delta))));
  floor(temp,rd);
  temp.bigintify(rootD);
  ordmult.assign_zero();
}



void
qi_class::verbose(int state)
{
  if (state <= 0)
    info = 0;
  else
    info = state;
}



void
qi_class::assign_zero()
{
  a.assign_zero();
  b.assign_zero();
}



void
qi_class::assign_one()
{
  a.assign_one();
  b.assign(Delta);

  normalize();
}



void
qi_class::assign_principal(const bigint & x, const bigint & y)
{
  bigint x2,y2,n,m,k,l,temp,temp2;

  /* use (x + y \sqrt(Delta))/2 form */
  y2.assign(y);
  shift_left(x2,x,1);
  if (Delta.is_odd())
    add(x2,x2,y);

  /* compute norm of (x2 + y2 sqrt(Delta))/2 */
  square(n,x2);
  square(temp,y2);
  multiply(temp,temp,Delta);
  subtract(n,n,temp);
  shift_right(n,n,2);
  n.absolute_value();

  /* solve m = k y2 + l (x2 + y2 Delta) / 2 */
  multiply(temp2,y2,Delta);
  add(temp2,temp2,x2);
  temp2.divide_by_2();
  m.assign(xgcd(k,l,y2,temp2));

  /* a = n / m^2 */
  square(temp,m);
  divide(a,n,temp);

  /* (b = kx2 + l(x2+y2)D/2 ) / m */
  add(temp2,x2,y2);
  multiply(temp2,temp2,l);
  multiply(b,temp2,Delta);
  b.divide_by_2();
  multiply(temp,k,x2);
  add(b,b,temp);
  divide(b,b,m);
  shift_left(temp,a,1);
  remainder(b,b,temp);

  reduce();
}



bool
qi_class::assign(const bigint & a2, const bigint & b2)
{
  bigint c,temp;

  if (a2.is_zero())
    if (b2.is_zero()) {
      a.assign_zero();
      b.assign_zero();
      return true;
    }
    else
      return false;
  else {
    square(c,b2);
    subtract(c,c,Delta);
    shift_left(temp,a2,2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      a.assign(a2);
      b.assign(b2);
      reduce();

      if (is_invertible())
        return true;
      else {
        a.assign_zero();
        b.assign_zero();
        return false;
      }
    }
    else
      return false;
  }
}



bool
qi_class::assign(const long a2, const long b2)
{
  bigint c,temp;

  if (!a2)
    if (!b2) {
      a.assign_zero();
      b.assign_zero();
      return true;
    }
    else
      return false;
  else {
    square(c,b2);
    subtract(c,c,Delta);
    shift_left(temp,a2,2);
    remainder(c,c,temp);
    if (c.is_zero()) {
      a.assign(a2);
      b.assign(b2);
      reduce();

      if (is_invertible())
        return true;
      else {
        a.assign_zero();
        b.assign_zero();
        return false;
      }
    }
    else
      return false;
  }
}



void
qi_class::assign(const quadratic_form & qf)
{
  if (qf.discriminant() != Delta)
    lidia_error_handler("qi_class","assign() - the quadratic form has a \
different discriminant");

  a.assign(abs(qf.get_a()));
  b.assign(qf.get_b());
  reduce();

  if (!is_invertible())
    lidia_error_handler("qi_class","assign() - the quadratic form is not \
invertible");
}



void
qi_class::assign(const quadratic_ideal & B)
{
  if (B.discriminant() != Delta)
    lidia_error_handler("qi_class","assign() - the quadratic ideal belongs to \
a different quadratic order");

  a.assign(B.get_a());
  b.assign(B.get_b());
  reduce();

  if (!is_invertible())
    lidia_error_handler("qi_class","assign() - the quadratic ideal is not \
invertible");
}



void
qi_class::assign(const qi_class_real & B)
{
  a.assign(B.a);
  b.assign(B.b);
}



void
qi_class::assign(const qi_class & B)
{
  a.assign(B.a);
  b.assign(B.b);
}



qi_class & qi_class::operator = (const qi_class & A)
{
  assign(A);
  return *this;
}



quadratic_order *
qi_class::get_current_order()
{
  return current_order->get_qo();
}



bigint
qi_class::discriminant()
{
  return Delta;
}



bigint
qi_class::get_rootD()
{
  return rootD;
}



bigfloat
qi_class::get_rd()
{
  return rd;
}



void
multiply(qi_class & C, const qi_class & A, const qi_class & B)
{
  if (qi_class::Delta.is_lt_zero())
    multiply_imag(C,A,B);
  else
    multiply_real(C,A,B);
}



void
multiply_imag(qi_class & C, const qi_class & A, const qi_class & B)
{
  bigint newa,newb,dpr,v,d,w,ab2,temp;

  /* solve dpr = v A.a + w B.a */
  dpr.assign(xgcd_left(v,A.a,B.a));

  /* C.b = v A.a (B.b - A.b) */
  multiply(newb,v,A.a);
  subtract(temp,B.b,A.b);
  multiply(newb,newb,temp);

  /* C.a = A.a B.a */
  multiply(newa,A.a,B.a);

  if (!dpr.is_one()) {
    add(ab2,A.b,B.b);
    ab2.divide_by_2();
    d.assign(xgcd(v,w,dpr,ab2));

    /* C.b = (C.b*v + w(Delta - A.b^2)/2) / d */
    multiply(newb,newb,v);

    square(temp,A.b);
    subtract(temp,qi_class::Delta,temp);
    temp.divide_by_2();
    multiply(temp,temp,w);

    add(newb,newb,temp);
    divide(newb,newb,d);

    /* C.a = C.a / (d^2) */
    square(temp,d);
    divide(newa,newa,temp);

    dpr.assign(d);
  }

  C.a.assign(newa);
  add(newb,newb,A.b);
  shift_left(ab2,C.a,1);
  remainder(C.b,newb,ab2);

  C.reduce_imag();
}



void
nucomp(qi_class & C, const qi_class & A, const qi_class & B)
{
  bigint a1,b1,c1,a2,b2,c2,s,n,d1,d;
  bigint u,v,u1,l,aa;
  bigint v1,v3;
  bigint b,e,f,g,q;
  bigint temp;
  bool flag;

  if (A.a < B.a) {
    a1.assign(B.a);
    b1.assign(B.b);
    square(c1,b1);
    subtract(c1,c1,qi_class::Delta);
    shift_left(temp,a1,2);
    divide(c1,c1,temp);

    a2.assign(A.a);
    b2.assign(A.b);
    square(c2,b2);
    subtract(c2,c2,qi_class::Delta);
    shift_left(temp,a2,2);
    divide(c2,c2,temp);
  }
  else {
    a1.assign(A.a);
    b1.assign(A.b);
    square(c1,b1);
    subtract(c1,c1,qi_class::Delta);
    shift_left(temp,a1,2);
    divide(c1,c1,temp);

    a2.assign(B.a);
    b2.assign(B.b);
    square(c2,b2);
    subtract(c2,c2,qi_class::Delta);
    shift_left(temp,a2,2);
    divide(c2,c2,temp);
  }

  // initialize
  add(s,b1,b2);
  s.divide_by_2();
  subtract(n,b2,s);

  // first Euclidean pass
  /* solve d = u a1 + v a2 */
  d.assign(xgcd(u,v,a2,a1));
  if (d.is_one()) {
    multiply(aa,u,n);
    aa.negate();
    flag = false;
  }
  else {
    // second Euclidean pass
    d1.assign(xgcd_left(u1,s,d));

    if (!d1.is_one()) {
      divide(a1,a1,d1);
      divide(a2,a2,d1);
      divide(s,s,d1);
      divide(d,d,d1);
      flag = true;
    }
    else
      flag = false;

    // l = - u1 * ( c1 * u + c2 * v ) mod d */
    remainder(c1,c1,d);
    remainder(temp,c2,d);
    multiply(temp,v,temp);
    multiply(l,u,c1);
    add(l,l,temp);
    multiply(l,l,u1);
    l.negate();
    remainder(l,l,d);
    if (l.is_lt_zero())
      add(l,l,d);

    // aa = - u * ( n / d ) + l * ( a1 / d ) */
    divide(aa,n,d);
    multiply(aa,aa,u);
    divide(temp,a1,d);
    multiply(temp,temp,l);
    subtract(aa,temp,aa);
  }

  // partial reduction
  d.assign(a1);
  v3.assign(aa);
  nugcd(u,d,v1,v3,qi_class::PEA_L);

  if (u.is_zero()) {
    //  u = 0; d = a1; v1 = 1; v3 = aa 
    // b = a2 * d + n * u ) / a1  --->    b = a2 (-) 
    // e = s * d + c2 * u ) / a1  --->   e = s  (-)

    // f = ( b * v3 + n ) / d 
    // b1 = 2 * b * v3 + n 

    multiply(temp,a2,v3);
    add(f,temp,n);
    add(b1,temp,f);

    // q = 2 * e * v1 - s       --->   q = s (-) 
      
    if (flag)
      multiply(s,s,d1);

    // a1 = d * b + e * u    --->   a1 = d * b 
    multiply(a1,d,a2);

    // b1 = b1 + q
    add(b1,b1,s);
  }
  else {
    // u != 0

    // b = a2 * d + n * u ) / a1 
    multiply(temp,a2,d);
    multiply(b,n,u);
    add(b,b,temp);
    divide(b,b,a1);

    // e = s * d + c2 * u ) / a1
    multiply(temp,s,d);
    multiply(e,c2,u);
    add(e,e,temp);
    divide(e,e,a1);

    // f = ( b * v3 + n ) / d
    // b1 = 2 * b * v3 + n 
    multiply(q,b,v3);
    add(f,q,n);
    add(b1,q,f);

    // g = ( e * v1 - s ) / u 
    // q = 2 * e * v1 - s
    multiply(q,e,v1);
    subtract(g,q,s);
    add(q,q,g);

    if ( flag ) {
      multiply(v1,v1,d1);
      multiply(u,u,d1);
      multiply(q,q,d1);
    }

    // a1 = d * b + e * u
    multiply(temp,d,b);
    multiply(a1,e,u);
    add(a1,a1,temp);

    // b1 = b1 + q 
    add(b1,b1,q);
  }

  C.a.assign(a1);
  C.b.assign(b1);
  C.reduce();
}



void
multiply_real(qi_class & C, const qi_class & A, const qi_class & B)
{
  bigint newa,newb,dpr,v,d,w,ab2,temp;

  /* solve dpr = v A.a + w B.a */
  dpr.assign(xgcd_left(v,A.a,B.a));

  /* C.b = v A.a (B.b - A.b) */
  multiply(newb,v,A.a);
  subtract(temp,B.b,A.b);
  multiply(newb,newb,temp);

  /* C.a = A.a B.a */
  multiply(newa,A.a,B.a);

  if (!dpr.is_one()) {
    add(ab2,A.b,B.b);
    ab2.divide_by_2();
    d.assign(xgcd(v,w,dpr,ab2));

    /* C.b = (C.b*v + w(D - A.b^2)/2) / d */
    multiply(newb,newb,v);

    square(temp,A.b);
    subtract(temp,qi_class::Delta,temp);
    temp.divide_by_2();
    multiply(temp,temp,w);

    add(newb,newb,temp);
    divide(newb,newb,d);

    /* C.a = C.a / (d^2) */
    square(temp,d);
    divide(newa,newa,temp);

    dpr.assign(d);
  }

  C.a.assign(newa);
  add(newb,newb,A.b);
  shift_left(ab2,C.a,1);
  remainder(C.b,newb,ab2);

  C.reduce_real();
}



void
qi_class::invert()
{
  b.negate();
  normalize();
}



void
inverse(qi_class & A, const qi_class & B)
{
  A.assign(B);
  A.b.negate();
  A.normalize();
}



qi_class
inverse(const qi_class & B)
{
  qi_class A;

  A.assign(B);
  A.b.negate();
  A.normalize();

  return A;
}



void
divide(qi_class & C, const qi_class & A, const qi_class & B)
{
  qi_class temp;

  if (B.is_zero())
    lidia_error_handler("qi_class","divide() - zero divisor");

  temp = inverse(B);
  multiply(C,A,temp);
}



void
square(qi_class & C, const qi_class & A)
{
  if (qi_class::Delta.is_lt_zero())
    square_imag(C,A);
  else
    square_real(C,A);
}



void
square_imag(qi_class & C, const qi_class & A)
{
  bigint newb,d,w,temp;

  /* solve d = v A.a + w A.b */
  d.assign(xgcd_right(w,A.a,A.b));

  /* C.b = A.b + w (D - A.b^2) / (2d) */
  square(newb,A.b);
  subtract(newb,qi_class::Delta,newb);
  shift_left(temp,d,1);
  divide(newb,newb,temp);
  multiply(newb,newb,w);
  add(C.b,newb,A.b);

  /* C.a = (A.a/d)^2 */
  divide(temp,A.a,d);
  square(C.a,temp);

  shift_left(temp,C.a,1);
  remainder(C.b,C.b,temp);

  C.reduce_imag();
}



void
nudupl(qi_class & C, const qi_class & A)
{
  bigint a,b,c;
  bigint temp,u,d1,d,v1,v3,e,g;
  bool flag;

  a.assign(A.a);
  b.assign(A.b);
  square(c,b);
  subtract(c,c,qi_class::Delta);
  shift_left(temp,a,2);
  divide(c,c,temp);

  // Euclidian pass
  d1.assign(xgcd_left(u,b,a));
  flag = (!d1.is_one());
  if (flag) {
    divide(a,a,d1);
    divide(b,b,d1);
  }

  multiply(v3,u,c);
  v3.negate();

  // partial euclidian algorithm 
  d.assign(a);
  nugcd(u,d,v1,v3,qi_class::PEA_L);

  // final squaring
  if (u.is_zero()) {
    if (flag)
      multiply(b,b,d1);

    square(a,d);
    square(c,v3);

    add(temp,d,v3);
    square(temp,temp);
    add(b,b,temp);
    subtract(b,b,a);
    subtract(b,b,c);
  }
  else {
    // u != 0
    multiply(e,b,d);
    multiply(temp,c,u);
    add(e,e,temp);
    divide(e,e,a);

    multiply(temp,e,v1);
    subtract(g,temp,b);
    add(b,temp,g);

    if (flag) {
      multiply(b,b,d1);
      multiply(u,u,d1);
      multiply(v1,v1,d1);
    }
   
    square(a,d);
    square(c,v3);

    add(temp,d,v3);
    square(temp,temp);
    add(b,b,temp);
    subtract(b,b,a);
    subtract(b,b,c);

    multiply(temp,e,u);
    add(a,a,temp);
  }
  
  C.a.assign(a);
  C.b.assign(b);
  C.reduce();
}


 
void
square_real(qi_class & C, const qi_class & A)
{
  bigint newb,d,w,temp;

  /* solve d = v A.a + w A.b */
  d.assign(xgcd_right(w,A.a,A.b));

  /* C.b = A.b + w (D - A.b^2) / (2d) */
  square(newb,A.b);
  subtract(newb,qi_class::Delta,newb);
  shift_left(temp,d,1);
  divide(newb,newb,temp);
  multiply(newb,newb,w);
  add(C.b,newb,A.b);

  /* C.a = (A.a/d)^2 */
  divide(temp,A.a,d);
  square(C.a,temp);

  shift_left(temp,C.a,1);
  remainder(C.b,C.b,temp);

  C.reduce_real();
}



void
power(qi_class & C, const qi_class & A, const bigint & i)
{
  if (qi_class::Delta.is_lt_zero())
    power_imag(C,A,i);
  else
    power_real(C,A,i);
}



void
power(qi_class & C, const qi_class & A, const long i)
{
  if (qi_class::Delta.is_lt_zero())
    power_imag(C,A,i);
  else
    power_real(C,A,i);
}



void
power_imag(qi_class & C, const qi_class & A, const bigint & i)
{
  qi_class B;
  bigint j;

  B.assign(A);
  j.assign(i);
  if (j.is_lt_zero()) {
    B.invert();
    j.absolute_value();
  }
  C.assign_one(); 
  while (j.is_gt_zero()) {
    if (j.is_odd())
      multiply_imag(C,C,B);
    j.divide_by_2();
    if (j.is_gt_zero())
      square_imag(B,B);
  }
}



void
power_imag(qi_class & C, const qi_class & A, const long i)
{
  qi_class B;
  register long j;

  B.assign(A);
  j = i;
  if (j < 0) {
    B.invert();
    j = -j;
  }
  C.assign_one();
  while (j > 0) {
    if ((j & 1) == 1)
      multiply_imag(C,C,B);
    j >>= 1;
    if (j > 0)
      square_imag(B,B);
  }
}



void
nupower(qi_class & C, const qi_class & A, const bigint & i)
{
  qi_class B;
  bigint j;

  B.assign(A);
  j.assign(i);
  if (j.is_lt_zero()) {
    B.invert();
    j.absolute_value();
  }
  C.assign_one(); 
  while (j.is_gt_zero()) {
    if (j.is_odd())
      nucomp(C,C,B);
    j.divide_by_2();
    if (j.is_gt_zero())
      nudupl(B,B);
  }
}



void
nupower(qi_class & C, const qi_class & A, const long i)
{
  qi_class B;
  register long j;

  B.assign(A);
  j = i;
  if (j < 0) {
    B.invert();
    j = -j;
  }
  C.assign_one();
  while (j > 0) {
    if ((j & 1) == 1)
      nucomp(C,C,B);
    j >>= 1;
    if (j > 0)
      nudupl(B,B);
  }
}



void
power_real(qi_class & C, const qi_class & A, const bigint & i)
{
  qi_class B;
  bigint j;

  B.assign(A);
  j.assign(i);
  if (j.is_lt_zero()) {
    B.invert();
    j.absolute_value();
  }
  C.assign_one(); 
  while (j.is_gt_zero()) {
    if (j.is_odd())
      multiply_real(C,C,B);
    j.divide_by_2();
    if (j.is_gt_zero())
      square_real(B,B);
  }
}



void
power_real(qi_class & C, const qi_class & A, const long i)
{
  qi_class B;
  register long j;

  B.assign(A);
  j = i;
  if (j < 0) {
    B.invert();
    j = -i;
  }
  C.assign_one();
  while (j > 0) {
    if ((j & 1) == 1)
      multiply_real(C,C,B);
    j >>= 1;
    if (j > 0)
      square_real(B,B);
  }
}



qi_class operator - (const qi_class & A)
{
  qi_class B;

  B = inverse(A);
  return B;
}



qi_class operator * (const qi_class & A, const qi_class & B)
{
  qi_class C;

  multiply(C,A,B);
  return C;
}



qi_class operator / (const qi_class & A, const qi_class & B)
{
  qi_class C;

  divide(C,A,B);
  return C;
}



qi_class & qi_class::operator *= (const qi_class & A)
{
  multiply(*this,*this,A);
  return *this;
}



qi_class & qi_class::operator /= (const qi_class & A)
{
  multiply(*this,*this,inverse(A));
  return *this;
}




bool
qi_class::is_zero() const
{
  return a.is_zero();
}



bool
qi_class::is_one() const
{
  return a.is_one();
}



bool
qi_class::is_equal(const qi_class & B) const
{
  return (!(a.compare(B.a)) && !(b.compare(B.b)));
}



bool operator == (const qi_class & A, const qi_class & B)
{
  return (A.is_equal(B));
}



bool operator != (const qi_class & A, const qi_class & B)
{
  return (!A.is_equal(B));
}



bool operator ! (const qi_class & A)
{
  return (A.is_zero());
}



void
swap(qi_class & A, qi_class & B)
{
  qi_class C;

  C.assign(A);
  A.assign(B);
  B.assign(C);
}



bool
prime_ideal(qi_class & A, const bigint & p)
{
  bigint D,b,temp;
  int kro,Dp,ip;

  D.assign(qi_class::discriminant());

  kro = kronecker(D,p);
  if (kro < 0)
    return false;
  else {
    if (p == 2) {
      if (kro == 0) {
        Dp = (int) remainder(D,16);
        if (Dp < 0)
          Dp += 16;
        if (Dp == 8)
          b.assign_zero();
        else if (Dp == 12)
          b.assign(p);
        else
          return false;
      }
      else
        b.assign(1);
    }
    else {
      if (kro == 0) {
        remainder(temp,D,p*p);
        if (temp.is_zero())
          return false;
        else
          b.assign_zero();
      }
      else {
        if (is_int(p)) {
          p.intify(ip);
          Dp = (int) remainder(D,(long) ip);
          if (Dp < 0)
            Dp += ip;

          b.assign(ressol(Dp,ip));
        }
        else {
          remainder(temp,D,p);
          if (temp.is_lt_zero())
            add(temp,temp,p);
          ressol(b,temp,p);
        }

        if (b.is_lt_zero())
          add(b,b,p);
      }

      if ((D.is_odd()) != (b.is_odd()))
        subtract(b,p,b);
    }

    A.assign(p,b);
    return true;
  }
}



void
qi_class::rho()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class","rho() - the current quadratic order must \
be real");

  /* a = (D - b^2) / 4a */
  shift_left(a2,a,2);
  square(temp,b);
  subtract(temp,Delta,temp);
  divide(a,temp,a2);
 
  /* q = floor((b + rootD) / 2a) */
  shift_left(a2,a,1);
  add(q,rootD,b);
  temp2.assign(q,a2);
  temp3 = floor(temp2);
  temp3.bigintify(q);

  /* b = 2aq - b */
  multiply(temp,a2,q);
  subtract(b,temp,b);
}



void
apply_rho(qi_class & A, const qi_class & B)
{
  A.assign(B);
  A.rho();
}



qi_class
apply_rho(const qi_class & A)
{
  qi_class B;
  
  B.assign(A);
  B.rho();
  return B;
}



void
qi_class::inverse_rho()
{
  bigint a2,q,temp;
  bigfloat temp2,temp3;

  if (Delta.is_lt_zero())
    lidia_error_handler("qi_class","inverse_rho() - the current quadratic \
order must be real");

  /* q = floor((b + rootD) / 2a) */
  shift_left(a2,a,1);
  add(q,b,rootD);
  temp2.assign(q,a2);
  temp3 = floor(temp2);
  temp3.bigintify(q);

  /* b = 2aq - b */
  multiply(temp,a2,q);
  subtract(b,temp,b);

  /* a = (D - b^2) / 4a */
  a2.multiply_by_2();
  square(temp,b);
  subtract(temp,Delta,temp);
  divide(a,temp,a2);
}



void
apply_inverse_rho(qi_class & A, const qi_class & B)
{
  A.assign(B);
  A.inverse_rho();
}



qi_class
apply_inverse_rho(const qi_class & A)
{
  qi_class B;
  
  B.assign(A);
  B.inverse_rho();
  return B;
}


bool
qi_class::is_equivalent(const qi_class & B) const
{
  qi_class_real nA,nB;
  bool equiv;

  if (Delta.is_lt_zero())
    equiv = (this->is_equal(B));
  else {
    nA.assign(*this);
    nB.assign(B);
    equiv = nA.is_equivalent(nB);
  }

  return equiv;
}




bool
qi_class::is_principal() const
{
  bigfloat d;
  qi_class_real nA;
  bool prin;

  if (Delta.is_lt_zero())
    prin = is_one();
  else {
    nA.assign(*this);
    prin = nA.is_principal(d);
  }

  return prin;
}




bigint
qi_class::order_in_CL() const
{
  bigfloat temp;
  long upper,v;
  bigint ord;
  int num;
  char str[150];

  if (!current_order)
    lidia_error_handler("qi_class", "order_in_CL() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "order_in_CL() - the current quadratic \
order has been deleted");


  /* class number known */
  if (current_order->get_qo()->is_h_computed())
    ord = order_h();
  else {
    /* class number not known */
    if (Delta.is_lt_zero())
      num = bigint_to_string(-Delta,str);
    else
      num = bigint_to_string(Delta,str);

    if (num < OBJTB) {
      if (Delta.is_lt_zero()) {
        temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
        temp.longify(upper);
        v = (upper >> 1);
      }
      else 
        v = 1;
      ord = order_BJT(v);
    }
    else if (num < OSHANKSB) {
      if (Delta.is_lt_zero())
        ord = order_shanks();
      else
        ord = order_BJT(1);
    }
    else
      ord = order_subexp();
  }

  return ord;
}




bigint
qi_class::order_BJT(long v) const
{
  bigint ord;
  timer t;

  if (!current_order)
    lidia_error_handler("qi_class", "order_BJT() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "order_BJT() - the current quadratic \
order has been deleted");

  t.start_timer();

  if (Delta.is_lt_zero()) {
    if (v < 2)
      v = 2;
    if ((v & 1) == 1)
      ++v;
    ord = oBJT_imag(v);
  }
  else
    ord = oBJT_real();

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (order_BJT) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return ord;
}



bigint
qi_class::order_h() const
{
  bigint h,ord;
  rational_factorization hfact;
  timer t;

  if (!current_order)
    lidia_error_handler("qi_class", "order_h() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "order_h() - the current quadratic \
order has been deleted");

  t.start_timer();

  h = current_order->get_qo()->class_number();
  hfact = current_order->get_qo()->factor_h();
  if (Delta.is_lt_zero())
    ord = omult_imag(h,hfact);
  else
    ord = omult_real(h,hfact);

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (order_h) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return ord;
}



bigint
qi_class::order_mult(const bigint & h, const rational_factorization & hfact) const
{
  bigint ord;

  if (!current_order)
    lidia_error_handler("qi_class", "order_mult() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "order_mult() - the current quadratic \
order has been deleted");

  if (h.is_one())
    ord.assign_one();
  else if (Delta.is_lt_zero())
    ord = omult_imag(h,hfact);
  else
    ord = omult_real(h,hfact);

  return ord;
}




bigint
qi_class::order_shanks() const
{
  long OQ,v;
  bigint hstar,ord;
  bigfloat R,nFI,temp,A,F;
  timer t;

  if (!current_order)
    lidia_error_handler("qi_class", "order_shanks() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "order_shanks() - the current quadratic \
order has been deleted");

  t.start_timer();

  if (Delta.is_lt_zero()) {
    /* compute approximation of h */
    OQ = current_order->get_qo()->get_optimal_Q_cnum();
    nFI = current_order->get_qo()->estimate_L1(OQ);
    nFI *= sqrt(bigfloat(-Delta)) / Pi();
    if (Delta == -3)
      nFI *= bigfloat(3.0);
    if (Delta == -4)
      nFI *= bigfloat(2.0);
    temp.assign(round(nFI));
    temp.bigintify(hstar);

    A = current_order->get_qo()->estimate_L1_error(OQ);
    F = exp(A) - bigfloat(1);
    temp = bigfloat(1) - exp(-A);
    if (temp > F)
      F = temp;
    temp = sqrt(nFI*F + abs(nFI - bigfloat(hstar)));
    temp.longify(v);
    if (v < 2)
      v = 2;
    if ((v & 1) == 1)
      ++v;

    ord = os_imag(v,hstar);
  }
  else {
    /* compute approximation of h */
    R = current_order->get_qo()->regulator();
    OQ = current_order->get_qo()->get_optimal_Q_cnum();
    nFI = current_order->get_qo()->estimate_L1(OQ);
    nFI *= sqrt(bigfloat(Delta)) / (bigfloat(2)*R);
    temp.assign(round(nFI));
    temp.bigintify(hstar);

    A = current_order->get_qo()->estimate_L1_error(OQ);
    F = exp(A) - bigfloat(1);
    temp = bigfloat(1) - exp(-A);
    if (temp > F)
      F = temp;
    temp = sqrt(nFI*F + abs(nFI - bigfloat(hstar)));
    temp.longify(v);
    if (v < 2)
      v = 2;
    if ((v & 1) == 1)
      ++v;

    ord = os_real(v,hstar);
  }

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (order_shanks) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return ord;
}



bigint
qi_class::order_subexp() const
{
  bigint ord;
  timer t;

  if (!current_order)
    lidia_error_handler("qi_class", "order_h() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "order_h() - the current quadratic \
order has been deleted");

  t.start_timer();

  ord = order_h();

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (order_subexp) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return ord;
}



bool
qi_class::DL(const qi_class & G, bigint & x) const
{
  bigfloat temp;
  long upper,v;
  bool isDL;
  int num;
  char str[150];

  if (!current_order)
    lidia_error_handler("qi_class", "DL() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "DL() - the current quadratic \
order has been deleted");


  if (current_order->get_qo()->is_h_computed()) {
/*
    isDL = DL_h(G,x);
*/
    isDL = DL_BJT(G,x,2);
  }
  else {
    /* class number not known */
    if (Delta.is_lt_zero())
      num = bigint_to_string(-Delta,str);
    else
      num = bigint_to_string(Delta,str);
    if (num < DLBJTB) {
      temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
      temp.longify(upper);
      v = (upper >> 1);
        isDL = DL_BJT(G,x,v);
    }
    else
      /* use subexponential method */
      isDL = DL_subexp(G,x);
  }

  return isDL;
}



bool
qi_class::DL_BJT(const qi_class & G, bigint & x, long v) const
{
  bool isDL;
  timer t;

  if (!current_order)
    lidia_error_handler("qi_class", "DL_BJT() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "DL_BJT() - the current quadratic \
order has been deleted");

  t.start_timer();

  if (v < 2)
    v = 2;
  if ((v & 1) == 1)
    ++v;

  if (Delta.is_lt_zero())
    isDL = DLBJT_imag(G,x,v);
  else
    isDL = DLBJT_real(G,x);

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (DL_BJT) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return isDL;
}



bool
qi_class::DL_h(const qi_class & G, bigint & x) const
{
  bool isDL;
  timer t;

  warning_handler_c("qi_class","DL_h() - not implemented", return false);

  if (!current_order)
    lidia_error_handler("qi_class", "DL_h() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "DL_h() - the current quadratic \
order has been deleted");

  t.start_timer();

  if (Delta.is_lt_zero())
    isDL = DLh_imag(G,x);
  else
    isDL = DLh_real(G,x);

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (DL_h) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return isDL;
}



bool
qi_class::DL_subexp(const qi_class & G, bigint & x) const
{
  bool isDL;
  timer t;

  warning_handler_c("qi_class","DL_subexp() - not implemented", return false);

  if (!current_order)
    lidia_error_handler("qi_class", "DL_subexp() - no current quadratic \
order has been defined");

  if (!current_order->get_qo())
    lidia_error_handler("qi_class", "DL_subexp() - the current quadratic \
order has been deleted");

  t.start_timer();

  if (Delta.is_lt_zero())
    isDL = DLsubexp_imag(G,x);
  else
    isDL = DLsubexp_real(G,x);

  t.stop_timer();

  if (info) {
    cout << "\nelapsed CPU time (DL_subexp) = ";
    MyTime(t.user_time());
    cout << "\n";
  }

  return isDL;
}




base_vector <bigint>
subgroup(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U)
{
  lidia_size_t i,Gsize;
  base_vector <long> v;
  base_vector <bigint> S;

  if (!qi_class::current_order)
    lidia_error_handler("qi_class", "subgroup() - no current quadratic \
order has been defined");

  if (!qi_class::current_order->get_qo())
    lidia_error_handler("qi_class", "subgroup() - the current quadratic \
order has been deleted");

  v.set_mode(EXPAND);
  v.set_size(0);
  S.set_mode(EXPAND);
  S.set_size(0);

  if (qi_class::current_order->get_qo()->is_h_computed()) {
/*
    S = subgroup_h(G,factor_base,U);
*/
    Gsize = G.size();
    v.set_size(Gsize);
    for (i=0; i<Gsize; ++i)
      v[i] = 2;
    S = subgroup_BJT(G,factor_base,U,v);
  }
  else {
    /* class number not known */
    Gsize = G.size();
    v.set_size(Gsize);
    for (i=0; i<Gsize; ++i)
      v[i] = 2;
    S = subgroup_BJT(G,factor_base,U,v);
  }

  return S;
}



base_vector <bigint>
subgroup_BJT(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U, base_vector <long> & v)
{
  base_vector <bigint> S;
  timer t;

  if (!qi_class::current_order)
    lidia_error_handler("qi_class", "subgroup_BJT() - no current quadratic \
order has been defined");

  if (!qi_class::current_order->get_qo())
    lidia_error_handler("qi_class", "subgroup_BJT() - the current quadratic \
order has been deleted");

  S.set_mode(EXPAND);
  S.set_size(0);

  t.start_timer();

  if (qi_class::Delta.is_lt_zero())
    S = subBJT_imag(G,factor_base,U,v);
  else
    S = subBJT_real(G,factor_base,U);

  t.stop_timer();

  if (qi_class::info) {
    cout << "\nelapsed CPU time (subgroup_BJT) = ";
    MyTime(t.user_time());
    cout << "\n";
  }
  
  return S;
}



base_vector <bigint>
subgroup_h(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U)
{
  base_vector <bigint> S;
  timer t;

  warning_handler_c("qi_class","subgroup_h() - not implemented", return base_vector <bigint>());

  if (!qi_class::current_order)
    lidia_error_handler("qi_class", "subgroup_h() - no current quadratic \
order has been defined");

  if (!qi_class::current_order->get_qo())
    lidia_error_handler("qi_class", "subgroup_h() - the current quadratic \
order has been deleted");

  t.start_timer();

  S.set_mode(EXPAND);
  S.set_size(0);

  if (qi_class::Delta.is_lt_zero())
    S = subh_imag(G,factor_base,U);
  else
    S = subh_real(G,factor_base,U);

  t.stop_timer();

  if (qi_class::info) {
    cout << "\nelapsed CPU time (subgroup_h) = ";
    MyTime(t.user_time());
    cout << "\n";
  }
  
  return S;
}



istream & operator >> (istream & in, qi_class & A)
{
  int n = 0, sz = 2;
  char c;
  bigint *ibuf;

  ibuf = new bigint[sz];
  in >> c;
  if (c != '(')
    lidia_error_handler("qi_class", "operator>>::( expected");
  in >> c;
  while (c != ')' && n != 2)
    {
      in.putback(c);
      in >> ibuf[n];
      n++;
      in >> c;
    }
  A.assign(ibuf[0], ibuf[1]);
  delete[] ibuf;
  return in;
}



ostream & operator << (ostream & out, const qi_class & A)
{
  out << "(" << A.a << ", " << A.b << ")";

  return out;
}



/********************/
/* PRIVATE FUNCTIONS */
/********************/


bigint 
qi_class::oBJT_imag(long v) const
{
  bigint y,usqr;
  long upper,u,r,s;
  bigint x;
  bigfloat temp;
  qi_class A,B,C,CINV;
  ideal_node *Inode;
  hash_table <ideal_node> htable;

  /* get hash table size */
  temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
  temp.multiply_by_2();
  multiply(temp,temp,bigfloat(1.33));
  temp.longify(upper);

  htable.initialize((lidia_size_t) upper);
  htable.set_key_function(&ideal_node_key);

  x.assign_zero();
  u = v;
  s = 1;
  power_imag(C,*this,u);
  CINV = inverse(*this);

  A.assign_one();
  B.assign(C);
  y.assign(v);

  while (x.is_zero()) {
    /* compute more baby steps */
    for (r=s; r<=u; ++r) {
      multiply_imag(A,A,CINV);
      if (s == 1) {
        if (A.is_one()) {
          x.assign(r);
          break;
        }
        else
          htable.hash(ideal_node(A,r));
      }
      else
        htable.hash(ideal_node(A,r));
    }

    /* compute giant steps to u^2 */
    square(usqr,u);
    while ((y.compare(usqr) < 0) && (x.is_zero())) { 
      Inode = htable.search(ideal_node(B,0));
      if (Inode) {
        /* found b in list:  x = y+r */
        r = Inode->get_index();
        add(x,y,r);
      }
      else {
        /* not found, take another giant step */
        add(y,y,u);
        multiply_imag(B,B,C);
      }
    }

    if (x.is_zero()) {
      /* double u */
      s = u+1;
      u <<= 1;
      square_imag(C,C);
    }
  }

  return x;
}



bigint 
qi_class::oBJT_real() const
{
  bigint y,usqr;
  long upper,u,r,s;
  bigint x;
  bigfloat temp,Reg,sqReg,GStepWidth;
  qi_class A,Aprime,B,C,CINV;
  qi_class_real Areal,F,Gstep;
  ideal_node *Inode;
  hash_table <ideal_node> htable;
  quadratic_order *cur_qo;

  cur_qo = current_order->get_qo();

  /* compute regulator */
  Reg = cur_qo->regulator();
  sqrt(sqReg,Reg);
  F.assign_one();
  Gstep.assign(nearest(F,sqReg));
  if (Gstep.get_distance() > sqReg)
    Gstep.inverse_rho();
  F.assign(cur_qo->prin_list.last_entry());
  if (Gstep.is_one()) {
    while (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
  }
  else {
    while ((F.get_distance() < Gstep.get_distance()) && (!F.is_one())) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (F.is_one())
      Gstep.assign(F);
  }
  floor(GStepWidth,Gstep.get_distance());

  /* get hash table size */
  temp = ceil(power(bigfloat(Delta),bigfloat(0.25)));
  temp.multiply_by_2();
  multiply(temp,temp,bigfloat(1.33));
  temp.longify(upper);

  htable.initialize((lidia_size_t) upper);
  htable.set_key_function(&ideal_node_key);

  x.assign_zero();
  s = u = 1;
  C.assign(*this);
  CINV = inverse(*this);

  A.assign_one();
  B.assign(*this);
  y.assign_one();

  /* principality test */
  if (is_principal())
    x.assign_one();

  while (x.is_zero()) {
    /* compute more baby steps */
    for (r=s; r<=u; ++r) {
      multiply_real(A,A,CINV);
      htable.hash(ideal_node(A,r));

      if (Gstep.is_one()) {
        /* store each cycle of ideals */
        apply_rho(Aprime,A);
        while (!Aprime.is_equal(A)) {
          htable.hash(ideal_node(Aprime,r));
          Aprime.rho();
        }
      }
      else {
        /* store ideals with distance < sqReg */
        Areal.assign(A);
        while (Areal.get_distance() < sqReg) {
          Areal.rho();
          htable.hash(ideal_node(qi_class(Areal),r));
        }
        Areal.rho();
        htable.hash(ideal_node(qi_class(Areal),r));
      }
    }

    /* compute giant steps to u^2 */
    square(usqr,u);
    while ((y.compare(usqr) <= 0) && (x.is_zero())) { 
      if (Gstep.is_one()) {
        Inode = htable.search(ideal_node(B,0));
        if (Inode) {
          /* found b in list:  x = y+r */
          r = Inode->get_index();
          add(x,y,r);
        }
      }
      else {
        F.assign(B,0.0);
        temp.assign_zero();
        while ((F.get_distance() <= Reg) && (x.is_zero())) {
          Inode = htable.search(ideal_node(qi_class(F),0));
          if (Inode) {
            /* found b in list:  x = y+r */
            r = Inode->get_index();
            add(x,y,r);
          }
          else {
            add(temp,temp,GStepWidth);
            multiply_real(F,F,Gstep);
            while (F.get_distance() <= temp)
              F.rho();
            while (F.get_distance() > temp)
              F.inverse_rho();
          }
        }
      }
      if (x.is_zero()) {
        /* not found, take another giant step */
        add(y,y,u);
        multiply_real(B,B,C);
        if (y.is_one()) {
          multiply_real(B,B,C);
          inc(y);
        }
      }
    }

    if (x.is_zero()) {
      /* double u */
      s = u+1;
      u <<= 1;
      square_real(C,C);
    }
  }

  return x;
}



bigint
qi_class::omult_imag(const bigint & h, const rational_factorization & hfact) const
{
  lidia_size_t i;
  int j,ex,num;
  bigint ord,p,pwr;
  qi_class A;

  ord.assign_one();
  
  if ((!h.is_one()) && (!this->is_one())) {
    num = hfact.no_of_comp();
    for (i=0; i<num; ++i) {
      A.assign(*this);
      p.assign(hfact.base(i));
      ex = hfact.exponent(i);
      pwr.assign(h);
      for (j=0; j<ex; ++j)
        divide(pwr,pwr,p);
      if (pwr > 1)
        power_imag(A,A,pwr);

      while (!A.is_one()) {
        multiply(ord,ord,p);
        power_imag(A,A,p);
      }
    }
  }

  return ord;
}



bigint
qi_class::omult_real(const bigint & h, const rational_factorization & hfact) const
{
  lidia_size_t i;
  int j,ex,num;
  bigint ord,p,pwr;
  qi_class A;

  ord.assign_one();

  if (!h.is_one()) {
    num = hfact.no_of_comp();
    for (i=0; i<num; ++i) {
      A.assign(*this);
      p.assign(hfact.base(i));
      ex = hfact.exponent(i);
      pwr.assign(h);
      for (j=0; j<ex; ++j)
        divide(pwr,pwr,p);
      if (pwr > 1)
        power_real(A,A,pwr);

      while (!A.is_principal()) {
        multiply(ord,ord,p);
        power_real(A,A,p);
      }
    }
  }

  return ord;
}



bigint
qi_class::os_imag(long v, const bigint & hstar) const
{
  bigint y,usqr;
  long upper,u,r,s;
  bigint x;
  bigfloat temp;
  qi_class A,B,Bcomp,C,CINV,GINV;
  ideal_node *Inode;
  hash_table <ideal_node> htable;

  x.assign_zero();
  u = v;
  y.assign_zero();
  s = 1;

  /* check whether this^ordmult = (1) */
  if (!ordmult.is_zero()) {
    power_imag(B,*this,ordmult);
    if (B.is_one())
      x = omult_imag(ordmult,omfact);
  }

  if (x.is_zero()) {
    /* check whether this^hstar = (1) */
    power_imag(B,*this,hstar);
    if (B.is_one()) {
      ordmult.assign(hstar);
      omfact.assign(ordmult);
      omfact.factor();
      x = omult_imag(ordmult,omfact);
    }
    else {
      /* get hash table size */
      temp = ceil(power(bigfloat(-Delta),bigfloat(0.2)));
      if (bigfloat(v) > temp)
        temp.assign(v);
      temp *= bigfloat(1.33);
      temp.longify(upper);
      htable.initialize((lidia_size_t) upper);
      htable.set_key_function(&ideal_node_key);


      A.assign_one();
      htable.hash(ideal_node(A,0));
      GINV = inverse(*this);
      power_imag(C,*this,u);
      CINV = inverse(C);
      Bcomp.assign(B);
    }
  }

  while (x.is_zero()) {
    /* compute more baby steps */
    for (r=s; r<=u; ++r) {
      multiply_imag(A,A,GINV);
      if (A.is_one()) {
        x = r;
        break;
      }
      else
        htable.hash(ideal_node(A,r));
    }

    /* compute giant steps to u^2 */
    square(usqr,u);
    while ((y.compare(usqr) < 0) && (x.is_zero())) {
      Inode = htable.search(ideal_node(Bcomp,0));
      if (Inode) {
        /* found Bcomp in list:  x = hstar - y + r) */
        r = Inode->get_index();
        subtract(x,hstar,y);
        add(x,x,r);
        if (x.is_gt_zero()) {
          ordmult.assign(x);
          omfact.assign(x);
          omfact.factor();
          x = omult_imag(ordmult,omfact);
        }
      }

      if (x.is_zero()) {
        Inode = htable.search(ideal_node(B,0));
        if (Inode) {
          /* found B in list:  x = hstar + y +r */
          r = Inode->get_index();
          add(ordmult,hstar,y);
          add(ordmult,ordmult,r);
          omfact.assign(ordmult);
          omfact.factor();
          x = omult_imag(ordmult,omfact);
        }
      }

      if (x.is_zero()) {
        /* not found, take another giant step */
        add(y,y,u);
        multiply_imag(B,B,C);
        if (B.is_one()) {
          add(ordmult,hstar,y);
          omfact.assign(ordmult);
          omfact.factor();
          x = omult_imag(ordmult,omfact);
        }
        else {
          multiply_imag(Bcomp,Bcomp,CINV);
          if (Bcomp.is_one()) {
            subtract(ordmult,hstar,y);
            omfact.assign(ordmult);
            omfact.factor();
            x = omult_imag(ordmult,omfact);
          }
        }
      }
    }

    if (x.is_zero()) {
      /* double u */
      s = u+1;
      u <<= 1;
      square_imag(C,C);
      CINV = inverse(C);
    }
  }
 
  return x;
}




bigint
qi_class::os_real(long v, const bigint & hstar) const
{
  bigint y,usqr;
  long upper,u,r,s;
  bigint x;
  bigfloat temp,Reg,sqReg,GStepWidth;
  qi_class A,Aprime,B,Bcomp,C,CINV,G,GINV;
  qi_class_real Areal,F,Gstep;
  ideal_node *Inode;
  hash_table <ideal_node> htable;
  quadratic_order *cur_qo;

  cur_qo = current_order->get_qo();

  x.assign_zero();

  /* compute regulator */
  Reg = cur_qo->regulator();
  sqrt(sqReg,Reg);
  F.assign_one();
  Gstep.assign(nearest(F,sqReg));
  if (Gstep.get_distance() > sqReg)
    Gstep.inverse_rho();
  F.assign(cur_qo->prin_list.last_entry());
  if (Gstep.is_one()) {
    while (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
  }
  else {
    while ((F.get_distance() < Gstep.get_distance()) && (!F.is_one())) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (F.is_one())
      Gstep.assign(F);
  }
  floor(GStepWidth,Gstep.get_distance());

  /* check whether this^ordmult = (1) */
  if (!ordmult.is_zero()) {
    power_real(B,*this,ordmult);
    if (B.is_principal())
      x = omult_real(ordmult,omfact);
  }

  s = u = 1;
  y.assign_zero();

  if (x.is_zero()) {
    /* check whether this^hstar = (1) */
    power_real(B,*this,hstar);
    if (B.is_principal()) {
      ordmult.assign(hstar);
      omfact.assign(ordmult);
      omfact.factor();
      x = omult_real(ordmult,omfact);
    }
    else {
      /* get hash table size */
      temp = ceil(power(bigfloat(-Delta),bigfloat(0.2)));
      if (bigfloat(v) > temp)
        temp.assign(v);
      temp *= bigfloat(1.33);
      temp.longify(upper);
      htable.initialize((lidia_size_t) upper);
      htable.set_key_function(&ideal_node_key);

      A.assign_one();
      GINV = inverse(*this);
      power_real(C,*this,u);
      CINV = inverse(C);
      Bcomp.assign(B);
    }
  }

  while (x.is_zero()) {
    /* compute more baby steps */
    for (r=s; r<=u; ++r) {
      multiply_real(A,A,GINV);
      htable.hash(ideal_node(A,r));

      if (Gstep.is_one()) {
        /* store each cycle of ideals */
        apply_rho(Aprime,A);
        while (!Aprime.is_equal(A)) {
          htable.hash(ideal_node(Aprime,r));
          Aprime.rho();
        }
      }
      else {
        /* store ideals with distance < sqReg */
        Areal.assign(A,0.0);
        while (Areal.get_distance() < sqReg) {
          Areal.rho();
          htable.hash(ideal_node(qi_class(Areal),r));
        }
        Areal.rho();
        htable.hash(ideal_node(qi_class(Areal),r));
      }
    }

    /* compute giant steps to u^2 */
    r = -1;
    square(usqr,u);
    while ((y.compare(usqr) < 0) && (x.is_zero())) {
      if (Gstep.is_one()) {
        Inode = htable.search(ideal_node(Bcomp,0));
        if (Inode) {
          /* found Bcomp in list:  x = hstar - y + r) */
          r = Inode->get_index();
        }
      }
      else {
        F.assign(Bcomp,0.0);
        temp.assign_zero();
        while ((F.get_distance() <= Reg) && (x.is_zero())) {
          Inode = htable.search(ideal_node(qi_class(F),0));
          if (Inode) {
            /* found Bcomp in list:  x = hstar - y + r) */
            r = Inode->get_index();
          }
          else {
            add(temp,temp,GStepWidth);
            multiply_real(F,F,Gstep);
            while (F.get_distance() <= temp)
              F.rho();
            while (F.get_distance() > temp)
              F.inverse_rho();
          }
        }
      }
      if (r >= 0) {
        subtract(x,hstar,y);
        add(x,x,r);
        if (x.is_gt_zero()) {
          ordmult.assign(x);
          omfact.assign(x);
          omfact.factor();
          x = omult_real(ordmult,omfact);
        }
      }

      if (x.is_zero()) {
        r = -1;
        if (Gstep.is_one()) {
          Inode = htable.search(ideal_node(B,0));
          if (Inode) {
            /* found B in list:  x = hstar + y + r) */
            r = Inode->get_index();
          }
        }
        else {
          F.assign(B,0.0);
          temp.assign_zero();
          while ((F.get_distance() <= Reg) && (x.is_zero())) {
            Inode = htable.search(ideal_node(qi_class(F),0));
            if (Inode) {
              /* found Bin list:  x = hstar + y + r) */
              r = Inode->get_index();
            }
            else {
              add(temp,temp,GStepWidth);
              multiply_real(F,F,Gstep);
              while (F.get_distance() <= temp)
                F.rho();
              while (F.get_distance() > temp)
                F.inverse_rho();
            }
          }
        }
        if (r >= 0) {
          add(x,hstar,y);
          add(x,x,r);
          ordmult.assign(x);
          omfact.assign(x);
          omfact.factor();
          x = omult_real(ordmult,omfact);
        }
      }

      if (x.is_zero()) {
        /* not found, take another giant step */
        add(y,y,u);
        multiply_real(B,B,C);
        multiply_real(Bcomp,Bcomp,CINV);
        if (y.is_one()) {
          multiply_real(B,B,C);
          multiply_real(Bcomp,Bcomp,CINV);
          inc(y);
        }
        if (Bcomp.is_principal()) {
          subtract(x,hstar,y);
          ordmult.assign(x);
          omfact.assign(x);
          omfact.factor();
          x = omult_real(ordmult,omfact);
        }
        if (Bcomp.is_principal()) {
          subtract(x,hstar,y);
          ordmult.assign(x);
          omfact.assign(x);
          omfact.factor();
          x = omult_real(ordmult,omfact);
        }
      }
    }

    if (x.is_zero()) {
      /* double u */
      s = u+1;
      u <<= 1;
      square_real(C,C);
      CINV = inverse(C);
    }
  }
 
  return x;
}



/* FIX */
bigint
qi_class::osubexp_imag() const
{
  return 1;
}



/* FIX */
bigint
qi_class::osubexp_real() const
{
  return 1;
}



bool
qi_class::DLBJT_imag(const qi_class & G, bigint & x, long v) const
{
  bigint usqr,y;
  long upper,u,r,s;
  bigfloat temp;
  qi_class A,B,C,CINV,DINV,E;
  ideal_node *Inode;
  hash_table <ideal_node> htable;
  bool DL;

  if (is_one()) {
    x.assign_zero();
    return true;
  }

  if (is_equal(G)) {
    x.assign_one();
    return true;
  }

  /* get hash table size */
  temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
  temp.multiply_by_2();
  multiply(temp,temp,bigfloat(1.33));
  temp.longify(upper);

  htable.initialize((lidia_size_t) upper);
  htable.set_key_function(&ideal_node_key);

  DL = false;
  x.assign_zero();
  u = v;
  s = 1;
  power_imag(C,G,u);
  CINV = inverse(G);
  DINV = inverse(*this);

  A.assign_one();
  B.assign(C);
  y.assign(v);

  while (x.is_zero()) {
    /* compute more baby steps */
    for (r=s; r<=u; ++r) {
      multiply_imag(A,A,CINV);
      if (s == 1) {
        if (A.is_equal(DINV)) {
          x.assign(r);
          DL = true;
          break;
        }

        if (A.is_one()) {
          x.assign(r);
          break;
        }

        htable.hash(ideal_node(A,r));
      }
      else
        htable.hash(ideal_node(A,r));
    }

    /* compute giant steps to u^2 */
    square(usqr,u);
    while ((y.compare(usqr) < 0) && (x.is_zero())) {
      multiply_imag(E,DINV,B);
      Inode = htable.search(ideal_node(E,0));
      if (Inode) {
        /* found b*d^-1 in list:  x = y+r */
        r = Inode->get_index();
        add(x,y,r);
        DL = true;
      }
      else {
        Inode = htable.search(ideal_node(B,0));
        if (Inode) {
          /* found b in list:  x = y+r */
          r = Inode->get_index();
          add(x,y,r);
        }
        else {
          /* not found, take another giant step */
          add(y,y,u);
          multiply_imag(B,B,C);
        }
      }
    }

    /* double u */
    if (x.is_zero()) {
      s = u+1;
      u <<= 1;
      square_imag(C,C);
    }
  }

  return DL;
}



bool
qi_class::DLBJT_real(const qi_class & G, bigint & x) const
{
  bigint y,usqr;
  long upper,u,r,s;
  bigfloat temp,Reg,sqReg,GStepWidth;
  qi_class A,Aprime,B,C,CINV,DINV,E;
  qi_class_real Areal,F,Gstep;
  ideal_node *Inode;
  hash_table <ideal_node> htable;
  bool is_DL;
  quadratic_order *cur_qo;

  cur_qo = current_order->get_qo();

  /* compute regulator */
  Reg = cur_qo->regulator();
  sqrt(sqReg,Reg);
  F.assign_one();
  Gstep.assign(nearest(F,sqReg));
  if (Gstep.get_distance() > sqReg)
    Gstep.inverse_rho();
  F.assign(cur_qo->prin_list.last_entry());
  if (Gstep.is_one()) {
    while (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
  }
  else {
    while ((F.get_distance() < Gstep.get_distance()) && (!F.is_one())) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (F.is_one())
      Gstep.assign(F);
  }
  floor(GStepWidth,Gstep.get_distance());

  /* get hash table size */
  temp = ceil(power(bigfloat(Delta),bigfloat(0.25)));
  temp.multiply_by_2();
  multiply(temp,temp,bigfloat(1.33));
  temp.longify(upper);

  htable.initialize((lidia_size_t) upper);
  htable.set_key_function(&ideal_node_key);

  x.assign_zero();
  s = u = 1;
  y.assign_one();
  C.assign(G);
  CINV = inverse(G);
  DINV = inverse(*this);

  A.assign_one();
  B.assign(G);
  is_DL = false;

  /* principality test */
  if (is_principal()) {
    x.assign_zero();
    is_DL = true;
  }
  else if (G.is_principal())
    x.assign_one();
  
  while ((x.is_zero()) && (!is_DL)) {
    /* compute more baby steps */
    for (r=s; r<=u; ++r) {
      multiply_real(A,A,CINV);
      htable.hash(ideal_node(A,r));

      if (Gstep.is_one()) {
        /* store each cycle of ideals */
        apply_rho(Aprime,A);
        while (!Aprime.is_equal(A)) {
          htable.hash(ideal_node(Aprime,r));
          Aprime.rho();
        }
      }
      else {
        /* store ideals with distance < sqReg */
        Areal.assign(A);
        while (Areal.get_distance() < sqReg) {
          Areal.rho();
          htable.hash(ideal_node(qi_class(Areal),r));
        }
        Areal.rho();
        htable.hash(ideal_node(qi_class(Areal),r));
      }

      if (s == 1) {
        if (Gstep.is_one()) {
          Inode = htable.search(ideal_node(DINV,0));
          if (Inode) {
            x.assign_one();
            is_DL = true;
          }
        }
        else {
          F.assign(DINV,0.0);
          temp.assign_zero();
          while ((F.get_distance() <= Reg) && (x.is_zero())) {
            Inode = htable.search(ideal_node(qi_class(F),0));
            if (Inode) {
              x.assign_one();
              is_DL = true;
            }
            else {
              add(temp,temp,GStepWidth);
              multiply_real(F,F,Gstep);
              while (F.get_distance() <= temp)
                F.rho();
              while (F.get_distance() > temp)
                F.inverse_rho();
            }
          }
        }
      }
    }

    /* compute giant steps to u^2 */
    square(usqr,u);
    while ((y.compare(usqr) <= 0) && (x.is_zero())) { 
      multiply_real(E,B,DINV);
      if (Gstep.is_one()) {
        Inode = htable.search(ideal_node(E,0));
        if (Inode) {
          /* found b*d^-1 in list:  x = y+r */
          r = Inode->get_index();
          add(x,y,r);
          is_DL = true;
        }
      }
      else {
        F.assign(E,0.0);
        temp.assign_zero();
        while ((F.get_distance() <= Reg) && (x.is_zero())) {
          Inode = htable.search(ideal_node(qi_class(F),0));
          if (Inode) {
            /* found b*d^-1 in list:  x = y+r */
            r = Inode->get_index();
            add(x,y,r);
            is_DL = true;
          }
          else {
            add(temp,temp,GStepWidth);
            multiply_real(F,F,Gstep);
            while (F.get_distance() <= temp)
              F.rho();
            while (F.get_distance() > temp)
              F.inverse_rho();
          }
        }
      }

      if (x.is_zero()) {
        if (Gstep.is_one()) {
          Inode = htable.search(ideal_node(B,0));
          if (Inode) {
            /* found b in list:  z = y+r */
            r = Inode->get_index();
            add(x,y,r);
          }
        }
        else {
          F.assign(B,0.0);
          temp.assign_zero();
          while ((F.get_distance() <= Reg) && (x.is_zero())) {
            Inode = htable.search(ideal_node(qi_class(F),0));
            if (Inode) {
              /* found b in list:  z = y+r */
              r = Inode->get_index();
              add(x,y,r);
            }
            else {
              add(temp,temp,GStepWidth);
              multiply_real(F,F,Gstep);
              while (F.get_distance() <= temp)
                F.rho();
              while (F.get_distance() > temp)
                F.inverse_rho();
            }
          }
        }
      }

      if (x.is_zero()) {
        /* not found, take another giant step */
        add(y,y,u);
        multiply_real(B,B,C);
        if (y.is_one()) {
          multiply_real(B,B,C);
          inc(y);
        }
      }
    }

    if (x.is_zero()) {
      /* double u */
      s = u+1;
      u <<= 1;
      square_real(C,C);
    }
  }

  return is_DL;
}



/* FIX */
bool
qi_class::DLh_imag(const qi_class & G, bigint & x) const
{
  bigint h;

  h = current_order->get_qo()->class_number();

  bigfloat temp;
  long v;

  temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
  temp.longify(v);
  v >>= 1;
  return DL_BJT(G,x,v);
}



/* FIX */
bool
qi_class::DLh_real(const qi_class & G, bigint & x) const
{
  bigint h;

  x = 0;

  h = current_order->get_qo()->class_number();

  return this->is_equal(G);
}



/* FIX */
bool
qi_class::DLsubexp_imag(const qi_class & G, bigint & x) const
{
  bigfloat temp;
  long v;

  temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
  temp.longify(v);
  v >>= 1;
  return DL_BJT(G,x,v);
}



/* FIX */
bool
qi_class::DLsubexp_real(const qi_class & G, bigint & x) const
{
  x = 0;
  return this->is_equal(G);
}



base_vector <bigint>
subBJT_imag(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U, base_vector <long> & v)
{
  bigint usqr,y,det,Bjj;
  long s,u,r,q,upper,Bj,idx;
  lidia_size_t i,j,k,Gsize,rank,numRpr,numQ,curr_index;
  bigfloat temp;
  qi_class Gidl,A,B,C,D,E,H,Gq,GBj;
  bigint_matrix Bmat,junk;
  base_vector <bigint> CL;
  base_vector <long> Rvec,Qvec;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> Q,R;

  Rvec.set_mode(EXPAND);
  Qvec.set_mode(EXPAND);
  CL.set_mode(EXPAND);
  factor_base.set_mode(EXPAND);
  Rvec.set_size(0);
  Qvec.set_size(0);
  CL.set_size(0);
  factor_base.set_size(0);

  Gsize = G.size();

  /* get maximum set sizes */
  temp = ceil(power(bigfloat(-qi_class::discriminant()),bigfloat(0.25)));
  temp.longify(upper);

  Q.initialize((lidia_size_t) upper << 1);
  Q.set_key_function(&ideal_node_key);
  B.assign_one();
  Q.hash(ideal_node(B,0));

  R.initialize((lidia_size_t) upper << 1);
  R.set_key_function(&ideal_node_key);
  R.hash(ideal_node(B,0));

  rank = 0;
  det.assign_one();
  for (j=0; j<Gsize; ++j) {
    u = v[j];
    if (u < 2)
      u = 2;
    if ((u & 1) == 1)
      ++u;
    y.assign(u);
    s = 1;

    Gidl = G[j];
    A.assign_one();
    power_imag(B,Gidl,u);
    C.assign(B);
    H.assign(inverse(Gidl));

    numRpr = R.no_of_elements();
    curr_index = numRpr;
    numQ = Q.no_of_elements();

    if (Gidl.is_one())
      Bjj.assign_one();
    else
      Bjj.assign_zero();

    /* check whether current ideal is in previously generated subgroup */
    if ((rank > 0) && (Bjj.is_zero())) {
      for (i=0; i<numQ; ++i) {
        D.assign(Q[i].get_A());
        multiply_imag(E,D,Gidl);
        Inode = R.search(ideal_node(E,0));
        if ((E.is_one()) || (Inode)) {
          Bjj.assign_one();
          break;
        }
      }
    }

    while (Bjj.is_zero()) {
      /* compute more baby steps */
      for (r=s; r<=u; ++r) {
        multiply_imag(A,A,H);
        if ((s == 1) && (r > 1)) {
          for (k=0; k<numRpr; ++k) {
            D.assign(R[k].get_A());
            multiply_imag(E,D,A);

            /* check if relation already found */
            Inode = Q.search(ideal_node(E,0));
            if (Inode) {
              q = Inode->get_index();
              Bjj.assign(r);
              decode_vector(Bmat,Bjj,k,q,Rvec,Qvec,numRpr,numQ);
              break;
            }
            else {
              R.hash(ideal_node(E,curr_index));
              ++curr_index;
            }
          }
          if (!Bjj.is_zero())
            break;
        }
        else {
          for (k=0; k<numRpr; ++k) {
            D.assign(R[k].get_A());
            multiply_imag(E,D,A);
            R.hash(ideal_node(E,curr_index));
            ++curr_index;
          }
        }
      }

      /* compute giant steps to u^2 */
      square(usqr,u);
      while ((y.compare(usqr) < 0) && (Bjj.is_zero())) {
        /* search */
        for (i=0; i<numQ; ++i) {
          E.assign(Q[i].get_A());
          multiply_imag(D,E,B);
          Inode = R.search(ideal_node(D,0));
          if (Inode) {
            r = Inode->get_index();
            q = i;
            add(Bjj,y,(r/numRpr));
            if (Bjj > 1) {
              r %= numRpr;
              decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
            }
            break;
          }
        }

        if (Bjj.is_zero()) {
          /* not found, take another giant step */
          add(y,y,u);
          multiply_imag(B,B,C);
        }
      }

      /* double u */
      if (Bjj.is_zero()) {
        s = u+1;
        u <<= 1;
        square_imag(C,C);
      }
    }

    if (!Bjj.is_one()) {
      factor_base[rank] = Gidl;
      ++rank;
    }

    if (j < (Gsize-1)) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

      /* compute new R' (remove entries with too large exponents) */
      det *= Bj;
      det.longify(idx);
      numRpr = R.no_of_elements();
      for (i=numRpr-1; i>=idx; --i)
        R.remove_from(i);

      if (!Bjj.is_one()) {
        Rvec[rank-1] = Bj;
        Qvec[rank-1] = 1;

        /* compute new Q */
        numQ = Q.no_of_elements();
        curr_index = numQ;
        power_imag(GBj,Gidl,Bj);
        Gq.assign(GBj);
        for (i=1; i<Bj; ++i) {
          if (Bjj.compare(i*Bj) <= 0)
            break;
          for (k=0; k<numQ; ++k) {
            E.assign(Q[k].get_A());
            multiply_imag(D,E,Gq);
            Q.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_imag(Gq,Gq,GBj);
          ++Qvec[rank-1];
        }
      }
    }
  }

  /* compute structure */
  if (rank == 0)
    CL[0] = 1;
  else {
    Bmat.snf_havas(U,junk);
    i = 0;
    for (j=0; j<rank; ++j) {
      Bjj = Bmat.member(j,j);
      if (!Bjj.is_one()) {
        CL[i] = Bjj;
        ++i;
      }
    }
  }

  return CL;
}



base_vector <bigint>
subBJT_real(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U)
{
  bigint usqr,y,det,Bjj;
  long s,u,r,q,upper,Bj,idx;
  lidia_size_t i,j,k,Gsize,rank,numRpr,numQ,curr_index,numRreps;
  bigfloat temp,Reg,sqReg,GStepWidth;
  qi_class Gidl,A,Aprime,B,C,D,E,H,Gq,GBj;
  qi_class_real Areal,F,Gstep,*FS;
  bigint_matrix Bmat,junk;
  base_vector <bigint> CL;
  base_vector <long> Rvec,Qvec;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> Q,R;
  base_vector <qi_class> Rreps;
  quadratic_order *cur_qo;

  cur_qo = qi_class::current_order->get_qo();

  /* compute regulator */
  Reg = cur_qo->regulator();
  sqrt(sqReg,Reg);
  F.assign_one();
  Gstep.assign(nearest(F,sqReg));
  if (Gstep.get_distance() > sqReg)
    Gstep.inverse_rho();
  F.assign(cur_qo->prin_list.last_entry());
  if (Gstep.is_one()) {
    while (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
  }
  else {
    while ((F.get_distance() < Gstep.get_distance()) && (!F.is_one())) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (!F.is_one()) {
      F.rho();
      cur_qo->prin_list.hash(F);
    }
    if (F.is_one())
      Gstep.assign(F);
  }
  floor(GStepWidth,Gstep.get_distance());

  Rvec.set_mode(EXPAND);
  Qvec.set_mode(EXPAND);
  CL.set_mode(EXPAND);
  Rreps.set_mode(EXPAND);
  factor_base.set_mode(EXPAND);
  Rvec.set_size(0);
  Qvec.set_size(0);
  CL.set_size(0);
  Rreps.set_size(0);
  factor_base.set_size(0);

  Gsize = G.size();

  /* get maximum set sizes */
  temp = ceil(power(bigfloat(-qi_class::discriminant()),bigfloat(0.25)));
  temp.longify(upper);

  Q.initialize((lidia_size_t) upper << 1);
  Q.set_key_function(&ideal_node_key);
  B.assign_one();
  Q.hash(ideal_node(B,0));

  R.initialize((lidia_size_t) upper << 1);
  R.set_key_function(&ideal_node_key);
  R.hash(ideal_node(B,0));

  Rreps[0] = B;

  rank = 0;
  det.assign_one();
  for (j=0; j<Gsize; ++j) {
    s = u = 1;
    y.assign_one();

    Gidl = G[j];
    A.assign_one();
    B.assign(Gidl);
    C.assign(B);
    H.assign(inverse(Gidl));

    numQ = Q.no_of_elements();
    curr_index = numRpr = numRreps = Rreps.size();

    Bjj.assign_zero();

    /* check whether current ideal is in previously generated subgroup */
    if (Bjj.is_zero()) {
      for (i=0; i<numQ; ++i) {
        D.assign(Q[i].get_A());
        multiply_real(E,D,Gidl);

        if (Gstep.is_one()) {
          FS = cur_qo->prin_list.search(qi_class_real(E));
          if (FS)
            Bjj.assign_one();
          else {
            Inode = R.search(ideal_node(E,0));
            if (Inode)
              Bjj.assign_one();
          }
        }
        else {
          F.assign(E,0.0);
          temp.assign_zero();
          while ((F.get_distance() <= Reg) && (Bjj.is_zero())) {
            FS = cur_qo->prin_list.search(F);
            if (FS)
              Bjj.assign_one();
            else {
              Inode = R.search(ideal_node(qi_class(F),0));
              if (Inode)
                Bjj.assign_one();
              else {
                add(temp,temp,GStepWidth);
                multiply_real(F,F,Gstep);
                while (F.get_distance() <= temp)
                  F.rho();
                while (F.get_distance() > temp)
                  F.inverse_rho();
              }
            }
          }
        }

        if (Bjj.is_one())
          break;
      }
    }

    while (Bjj.is_zero()) {
      /* compute more baby steps */
      for (r=s; r<=u; ++r) {
        multiply_real(A,A,H);
        for (k=0; k<numRreps; ++k) {
          D.assign(Rreps[k]);
          multiply_real(E,D,A);
          Rreps[curr_index] = E;
          R.hash(ideal_node(E,curr_index));

          if (Gstep.is_one()) {
            /* store each cycle of ideals */
            apply_rho(Aprime,E);
            while (!Aprime.is_equal(E)) {
              R.hash(ideal_node(Aprime,curr_index));
              Aprime.rho();
            }
          }
          else {
            /* store ideals with distance < sqReg */
            Areal.assign(E);
            while (Areal.get_distance() < sqReg) {
              Areal.rho();
              R.hash(ideal_node(qi_class(Areal),curr_index));
            }
            Areal.rho();
            R.hash(ideal_node(qi_class(Areal),curr_index));
          }

          ++curr_index;
        }
      }

      /* compute giant steps to u^2 */
      square(usqr,u);
      while ((y.compare(usqr) <= 0) && (Bjj.is_zero())) {
        /* search */
        for (i=0; i<numQ; ++i) {
          D.assign(Q[i].get_A());
          multiply_real(E,D,B);

          if (Gstep.is_one()) {
            FS = cur_qo->prin_list.search(qi_class_real(E));
            if (FS) {
              r = 0;
              q = i;
              add(Bjj,y,(r/numRpr));
              r %= numRpr;
              decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
            }
            else {
              Inode = R.search(ideal_node(E,0));
              if (Inode) {
                r = Inode->get_index();
                q = i;
                add(Bjj,y,(r/numRpr));
                r %= numRpr;
                decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
              }
            }
          }
          else {
            F.assign(E,0.0);
            temp.assign_zero();
            while ((F.get_distance() <= Reg) && (Bjj.is_zero())) {
              FS = cur_qo->prin_list.search(F);
              if (FS) {
                r = 0;
                q = i;
                add(Bjj,y,(r/numRpr));
                r %= numRpr;
                decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
              }
              else {
                Inode = R.search(ideal_node(qi_class(F),0));
                if (Inode) {
                  r = Inode->get_index();
                  q = i;
                  add(Bjj,y,(r/numRpr));
                  r %= numRpr;
                  decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
                }
                else {
                  add(temp,temp,GStepWidth);
                  multiply_real(F,F,Gstep);
                  while (F.get_distance() <= temp)
                    F.rho();
                  while (F.get_distance() > temp)
                    F.inverse_rho();
                }
              }
            }
          }

          if (Bjj.is_gt_zero())
            break;
        }

        if (Bjj.is_zero()) {
          /* not found, take another giant step */
          add(y,y,u);
          multiply_real(B,B,C);
          if (y.is_one()) {
            multiply_real(B,B,C);
            inc(y);
          }
        }
      }

      /* double u */
      if (Bjj.is_zero()) {
        s = u+1;
        u <<= 1;
        square_real(C,C);
      }
    }

    if (!Bjj.is_one()) {
      factor_base[rank] = Gidl;
      ++rank;
    }

    if (j < (Gsize-1)) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

      /* compute new R' (remove entries with too large exponents) */
      det *= Bj;
      det.longify(idx);
      numRpr = R.no_of_elements();
      i = numRpr-1;
      while (R[i].get_index() >= idx) {
        R.remove_from(i);
        --i;
      }
      Rreps.set_size((lidia_size_t) idx);

      if (!Bjj.is_one()) {
        Rvec[rank-1] = Bj;
        Qvec[rank-1] = 1;

        /* compute new Q */
        numQ = Q.no_of_elements();
        curr_index = numQ;
        power_real(GBj,Gidl,Bj);
        Gq.assign(GBj);
        for (i=1; i<Bj; ++i) {
          if (Bjj.compare(i*Bj) <= 0)
            break;
          for (k=0; k<numQ; ++k) {
            E.assign(Q[k].get_A());
            multiply_real(D,E,Gq);
            Q.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_real(Gq,Gq,GBj);
          ++Qvec[rank-1];
        }
      }
    }
  }

  /* compute structure */
  if (rank == 0)
    CL[0] = 1;
  else {
    Bmat.snf_havas(U,junk);
    i = 0;
    for (j=0; j<rank; ++j) {
      Bjj = Bmat.member(j,j);
      if (!Bjj.is_one()) {
        CL[i] = Bjj;
        ++i;
      }
    }
  }

  return CL;
}




/* FIX */
base_vector <bigint>
subh_imag(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U)
{
  base_vector <bigint> CL;
  bigint h;

  CL.set_mode(EXPAND);
  CL.set_size(0);
  factor_base.set_mode(EXPAND);
  factor_base.set_size(0);

  h = qi_class::current_order->get_qo()->class_number();

  G = G;
  U = U;

  return CL;
}



/* FIX */
base_vector <bigint>
subh_real(base_vector <qi_class> & G, base_vector <qi_class> & factor_base, bigint_matrix & U)
{
  base_vector <bigint> CL;
  bigint h;

  CL.set_mode(EXPAND);
  CL.set_size(0);
  factor_base.set_mode(EXPAND);
  factor_base.set_size(0);

  h = qi_class::current_order->get_qo()->class_number();

  G = G;
  U = U;

  return CL;
}



void
decode_vector(bigint_matrix & Bmat, const bigint & Bjj, long r, long q, base_vector <long> & Rvec, base_vector <long> & Qvec, long nR, long nQ)
{
  lidia_size_t i,rank;
  bigint temp;
  base_vector <bigint> Bj;

  /* compute powers in index vector */
  if (Bmat.member(0,0) == 0) {
    Bmat.sto(0,0,Bjj);
  }
  else {
    rank = Bmat.get_no_of_columns();
    Bj.set_mode(EXPAND);
    Bj.set_size(0);

    Bj[rank] = Bjj;
    for (i=rank-1; i>=0; --i) {
      nR /= Rvec[i];
      nQ /= Qvec[i];
      temp.assign(q/nQ);
      multiply(temp,temp,Rvec[i]);
      add(Bj[i],temp,(r/nR));
      r %= nR;
      q %= nQ;
    }

    /* new row and column */
    ++rank;
    Bmat.set_no_of_rows(rank);
    Bmat.set_no_of_columns(rank);
    Bmat.sto_column_vector(Bj,rank,rank-1);
  }
}
