//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : quadratic_order.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_list quadratic_order::qo_l;
int quadratic_order::info = 0;
prime_lists quadratic_order::PL;
char quadratic_order::FAKM[35];
char quadratic_order::NEWR[35];

const long OQvals[] = {1663,4177,7523,11657,16477,21991,28151,34913,42293,
50261,58787,67883,77527,87719,98419,109661,121403,133649,146407,159667};

const long OQvals_cnum[] = {2269,5741,10427,16183,22901,30631,
39209,48731,59063,70237,82223,95009,108571,122921,137983,153817,170341,
187631,205589,224261};

const long OQvals_cfunc[] = {630000,825000,995000,1145000,
1290000,1425000,1555000,1675000,1795000,1910000,2020000,2125000,2230000,
2335000,2435000,2530000,2625000,2720000,2810000,2900000};

const float qo_params[71][4] = { {5, 0.7, 500, 13},
                                 {6, 0.7, 500, 13},
                                 {7, 0.7, 500, 13},
                                 {8, 0.7, 500, 13},
                                 {9, 0.7, 500, 13},
                                 {10, 0.7, 500, 15},
                                 {11, 0.7, 600, 20},
                                 {12, 0.7, 700, 25},
                                 {13, 0.8, 800, 35}, // ??
                                 {14, 0.8, 1000, 35},
                                 {15, 0.8, 1200, 35},
                                 {16, 0.8, 1400, 35},
                                 {17, 0.8, 3000, 40},//
                                 {18, 0.8, 3000, 60},
                                 {19, 0.8, 3600, 80},
                                 {20, 0.8, 4000, 100}, //
                                 {21, 0.8, 4250, 100},
                                 {22, 0.8, 4500, 120},
                                 {23, 0.8, 4750, 140},
                                 {24, 0.8, 5000, 160},
                                 {25, 0.8, 5000, 180},
                                 {26, 0.9, 6000, 200}, //
                                 {27, 0.9, 6000, 220},
                                 {28, 0.9, 6500, 240},
                                 {29, 0.9, 6500, 260},
                                 {30, 1.05, 7000, 325}, //
                                 {31, 1.05, 7000, 355},
                                 {32, 1.05, 7500, 375},
                                 {33, 1.1,  7500, 400},
                                 {34, 1.1,  7500, 425},
                                 {35, 1.15, 7500, 550}, //
                                 {36, 1.15, 8000, 650},
                                 {37, 1.2, 9000, 750},
                                 {38, 1.2, 10000, 850},
                                 {39, 1.2, 11000, 950},
                                 {40, 1.25, 14000, 1000}, //
                                 {41, 1.25, 15500, 1150},
                                 {42, 1.3, 12000, 1300}, //
                                 {43, 1.3, 12000, 1600},
                                 {44, 1.3, 15000, 1900},
                                 {45, 1.3, 15000, 2200},
                                 {46, 1.3, 15000, 2500}, //
                                 {47, 1.3, 18000, 2500},
                                 {48, 1.3, 21000, 2500},
                                 {49, 1.35, 45000, 3000},
                                 {50, 1.4, 50000, 3000},
                                 {51, 1.4, 55000, 3000},
                                 {52, 1.5, 60000, 3200}, //
                                 {53, 1.5, 50000, 4300}, //
                                 {54, 1.5, 80000, 3800},
                                 {55, 1.6, 90000, 4100},
                                 {56, 1.6, 100000, 4400},
                                 {57, 1.6, 110000, 4700},
                                 {58, 1.6, 120000, 5000},
                                 {59, 1.7, 130000, 5500},
                                 {60, 1.7, 140000, 5800},
                                 {61, 1.7, 150000, 6100},
                                 {62, 1.7, 160000, 6400},
                                 {63, 1.8, 170000, 6700},
                                 {64, 1.8, 180000, 7000},
                                 {65, 1.8, 190000, 7300},
                                 {66, 1.8, 200000, 7600},
                                 {67, 1.8, 210000, 7900},
                                 {68, 1.8, 220000, 8200},
                                 {69, 1.8, 230000, 8500},
                                 {70, 1.8, 240000, 8800},
                                 {71, 1.9, 250000, 9100},
                                 {72, 1.9, 260000, 9400},
                                 {73, 1.9, 270000, 9700},
                                 {74, 1.9, 280000,10000},
                                 {75, 1.9, 290000,10300}};



qo_node *
quadratic_order::add_to_list(qo_node *old, quadratic_order & newQO)
{
  if (old)
    if (old->get_qo() == &newQO)
      return old;

  qo_l.clear(old);
  return qo_l.insert(newQO,0);
}



qo_node *
quadratic_order::add_dynamic_to_list(qo_node *old, quadratic_order & newQO)
{
  qo_l.clear(old);
  return qo_l.insert(newQO,1);
}



void
quadratic_order::clear(qo_node *old)
{
  qo_l.clear(old);
}



quadratic_order *
quadratic_order::last_order()
{
  if (qo_l.first())
    return qo_l.first()->get_qo();
  else
    return (quadratic_order *) NULL;
}



qo_node *
quadratic_order::add_last(qo_node *old)
{
  qo_l.clear(old);
  return qo_l.set_to(qo_l.first());
}



void
quadratic_order::print_all_orders()
{
  bool temp;

  temp = qo_l.print();
}



quadratic_order::quadratic_order()
{
  CL.set_mode(EXPAND);
  gens.set_mode(EXPAND);
  fact_base.set_mode(EXPAND);
  CL.set_size(0);
  gens.set_size(0);
  fact_base.set_size(0);

  PL.GetPrimes();
}



quadratic_order::quadratic_order(const long D)
{
  char str[150];
  bigfloat temp;

  /* get list of small primes */
  PL.GetPrimes();

  /* initialization */
  CL.set_mode(EXPAND);
  gens.set_mode(EXPAND);
  fact_base.set_mode(EXPAND);
  CL.set_size(0);
  gens.set_size(0);
  fact_base.set_size(0);
  prin_list.empty();
  hfact = rational_factorization();
  disc_fact = rational_factorization();
  U.assign(bigint_matrix());
  UINV.assign(bigint_matrix());

  if (is_quadratic_discriminant(D))
    Delta.assign(D);
  else
    Delta.assign_zero();
  R.assign_zero();
  h.assign_zero();
  L.assign_zero();
  Cfunc.assign_zero();
  FI.assign_zero();
  Q = 0;

  if (Delta.is_lt_zero()) {
    temp.assign(floor(power(bigfloat(Delta >> 2),bigfloat(0.25))));
    temp.bigintify(nucomp_bound);
  }
  else
    nucomp_bound.assign_zero();

  prec = (long) bigint_to_string(Delta,str);
  prec = (prec >> 1) + 10;
  bigfloat::precision(prec);
}



quadratic_order::quadratic_order(const bigint & D)
{
  char str[150];
  bigfloat temp;

  /* get list of small primes */
  PL.GetPrimes();

  /* initialization */
  CL.set_mode(EXPAND);
  gens.set_mode(EXPAND);
  fact_base.set_mode(EXPAND);
  CL.set_size(0);
  gens.set_size(0);
  fact_base.set_size(0);
  prin_list.empty();
  hfact = rational_factorization();
  disc_fact = rational_factorization();
  U.assign(bigint_matrix());
  UINV.assign(bigint_matrix());

  if (is_quadratic_discriminant(D))
    Delta.assign(D);
  else
    Delta.assign_zero();
  R.assign_zero();
  h.assign_zero();
  L.assign_zero();
  Cfunc.assign_zero();
  FI.assign_zero();
  Q = 0;

  if (Delta.is_lt_zero()) {
    temp.assign(floor(power(bigfloat(Delta >> 2),bigfloat(0.25))));
    temp.bigintify(nucomp_bound);
  }
  else
    nucomp_bound.assign_zero();

  prec = (long) bigint_to_string(Delta,str);
  prec = (prec >> 1) + 10;
  bigfloat::precision(prec);
}



quadratic_order::quadratic_order(const quadratic_order & QO)
{
  CL.set_mode(EXPAND);
  gens.set_mode(EXPAND);
  fact_base.set_mode(EXPAND);

  Delta.assign(QO.Delta);
  R.assign(QO.R);
  h.assign(QO.h);
  L.assign(QO.L);
  Cfunc.assign(QO.Cfunc);
  CL = QO.CL;
  gens = QO.gens;
  hfact.assign(QO.hfact);
  disc_fact.assign(QO.disc_fact);
  fact_base = QO.fact_base;
  prin_list = QO.prin_list;
  FI = QO.FI;
  Q = QO.Q;
  nucomp_bound = QO.nucomp_bound;
  U = QO.U;
  UINV = QO.UINV;
  prec = QO.prec;
  bigfloat::precision(prec);
}



quadratic_order::~quadratic_order()
{
  qo_l.nullify(this);
}



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



bool
quadratic_order::assign(const long D)
{
  char str[150];
  bigfloat temp;
  bool is_disc;

  is_disc = is_quadratic_discriminant(D);

  if (is_disc)
    Delta.assign(D);
  else
    Delta.assign_zero();

  R.assign_zero();
  h.assign_zero();
  L.assign_zero();
  Cfunc.assign_zero();
  CL.set_size(0);
  gens.set_size(0);
  hfact = rational_factorization();
  disc_fact = rational_factorization();
  U.assign(bigint_matrix());
  UINV.assign(bigint_matrix());
  fact_base.set_size(0);
  prin_list.empty();
  FI.assign_zero();
  Q = 0;

  if (Delta.is_lt_zero()) {
    temp.assign(floor(power(bigfloat(Delta >> 2),bigfloat(0.25))));
    temp.bigintify(nucomp_bound);
  }
  else
    nucomp_bound.assign_zero();

  prec = (long) bigint_to_string(Delta,str);
  prec = (prec >> 1) + 10;
  bigfloat::precision(prec);

  return is_disc;
}



bool
quadratic_order::assign(const bigint & D)
{
  char str[150];
  bigfloat temp;
  bool is_disc;

  is_disc = is_quadratic_discriminant(D);

  if (is_disc)
    Delta.assign(D);
  else
    Delta.assign_zero();

  R.assign_zero();
  h.assign_zero();
  L.assign_zero();
  Cfunc.assign_zero();
  CL.set_size(0);
  gens.set_size(0);
  hfact = rational_factorization();
  disc_fact = rational_factorization();
  U.assign(bigint_matrix());
  UINV.assign(bigint_matrix());
  fact_base.set_size(0);
  prin_list.empty();
  FI.assign_zero();
  Q = 0;

  if (Delta.is_lt_zero()) {
    temp.assign(floor(power(bigfloat(Delta >> 2),bigfloat(0.25))));
    temp.bigintify(nucomp_bound);
  }
  else
    nucomp_bound.assign_zero();

  prec = (long) bigint_to_string(Delta,str);
  prec = (prec >> 1) + 10;
  bigfloat::precision(prec);

  return is_disc;
}



void
quadratic_order::assign(const quadratic_order & QO)
{
  Delta.assign(QO.Delta);
  R.assign(QO.R);
  h.assign(QO.h);
  L.assign(QO.L);
  Cfunc.assign(QO.Cfunc);
  CL = QO.CL;
  gens = QO.gens;
  hfact.assign(QO.hfact);
  disc_fact.assign(QO.disc_fact);
  fact_base = QO.fact_base;
  prin_list = QO.prin_list;
  FI = QO.FI;
  Q = QO.Q;
  nucomp_bound = QO.nucomp_bound;
  U = QO.U;
  UINV = QO.UINV;
  prec = QO.prec;
  bigfloat::precision(prec);
}



quadratic_order & quadratic_order::operator = (const quadratic_order & QO)
{
  Delta.assign(QO.Delta);
  R.assign(QO.R);
  h.assign(QO.h);
  L.assign(QO.L);
  Cfunc.assign(QO.Cfunc);
  CL = QO.CL;
  gens = QO.gens;
  hfact.assign(QO.hfact);
  disc_fact.assign(QO.disc_fact);
  fact_base = QO.fact_base;
  prin_list = QO.prin_list;
  FI = QO.FI;
  Q = QO.Q;
  nucomp_bound = QO.nucomp_bound;
  U = QO.U;
  UINV = QO.UINV;
  prec = QO.prec;
  bigfloat::precision(prec);

  return *this;
}



bool
quadratic_order::is_zero() const
{
  return Delta.is_zero();
}



bool
quadratic_order::is_equal(const quadratic_order & QO) const
{
  return !Delta.compare(QO.Delta);
}



bool
quadratic_order::is_subset(quadratic_order & QO2)
{
  return ((this->is_proper_subset(QO2)) || (this->is_equal(QO2)));
}


bool
quadratic_order::is_proper_subset(quadratic_order & QO2)
{
  bigint c1,c2,m1,m2,r;

  c1 = conductor();
  c2 = QO2.conductor();
  divide(m1,Delta,c1*c1);
  divide(m2,QO2.Delta,c2*c2);
  remainder(r,c1,c2);

  return ((m1 == m2) && (r.is_zero()) && (c1 != c2));
}



bool operator == (const quadratic_order & QO1, const quadratic_order & QO2)
{
  return !QO1.Delta.compare(QO2.Delta);
}



bool operator != (const quadratic_order & QO1, const quadratic_order & QO2)
{
  return QO1.Delta.compare(QO2.Delta);
}



bool operator <= (quadratic_order & QO1, quadratic_order & QO2)
{
  return (QO1.is_subset(QO2));
}



bool operator < (quadratic_order & QO1, quadratic_order & QO2)
{
  return (QO1.is_proper_subset(QO2));
}



bool operator >= (quadratic_order & QO1, quadratic_order & QO2)
{
  return (QO2.is_subset(QO1));
}



bool operator > (quadratic_order & QO1, quadratic_order & QO2)
{
  return (QO2.is_proper_subset(QO1));
}



bool operator ! (const quadratic_order & QO)
{
  return (QO.is_zero());
}



bool
is_quadratic_discriminant(const long D)
{
  bigint base;
  long k;
  bool is_disc;
  
  is_disc = true;

  /* testing whether D is = 0,1 (mod 4) */
  k = D & 3;
  if (k < 0)
    k += 4;
  if (k > 1)
    is_disc = false;
  else {
    /* testing whether D is a perfect square */
    k = power_test(base,abs(bigint(D)));
    if (k == 2)
      is_disc = false;
  }

  return is_disc;
}



bool
is_quadratic_discriminant(const bigint & D)
{
  bigint base;
  long k;
  bool is_disc;
  
  is_disc = true;

  /* testing whether D is = 0,1 (mod 4) */
  k = remainder(D,4);
  if (k < 0)
    k += 4;
  if (k > 1)
    is_disc = false;
  else {
    /* testing whether D is a perfect square */
    k = power_test(base,abs(D));
    if (k == 2)
      is_disc = false;
  }

  return is_disc;
}



bool
quadratic_order::is_imaginary() const
{
  return Delta.is_lt_zero();
}



bool
quadratic_order::is_real() const
{
  return Delta.is_gt_zero();
}



bool
quadratic_order::is_R_computed() const
{
  return (R.is_gt_zero()) || (Delta.is_zero());
}



bool
quadratic_order::is_h_computed() const
{
  return (h.is_gt_zero()) || (Delta.is_zero());
}



bool
quadratic_order::is_L_computed() const
{
  return (L.is_gt_zero()) || (Delta.is_zero());
}



bool
quadratic_order::is_C_computed() const
{
  return (Cfunc.is_gt_zero()) || (Delta.is_zero());
}



bool
quadratic_order::is_CL_computed() const
{
  return (CL.size() > 0) || (Delta.is_zero());
}



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

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



bigint
quadratic_order::conductor()
{
  bigint cond,b,temp;
  int e;
  long m4;
  lidia_size_t num_facts,i;

  cond.assign_one();
  factor_discriminant();
  num_facts = disc_fact.no_of_comp();
  for (i=0; i<num_facts; ++i) {
    b = disc_fact.base(i);
    e = disc_fact.exponent(i);
    if ((b > 0) && (e > 1)) {
      if (b == 2) {
        if ((e % 2) == 1)
          shift_right(temp,Delta,e-1);
        else
          shift_right(temp,Delta,e);
        remainder(m4,temp,4);
        if (m4 < 0)
          m4 += 4;
        if (m4 == 1)
          e >>= 1;
        else
          e = (e-2) >> 1;
      }
      else
        e >>= 1;

      if (e > 0) {
        power(temp,b,(long) e);
        multiply(cond,cond,temp);
      }
    }
  }

  return cond;
}



bool
quadratic_order::is_maximal()
{
  return (conductor() == bigint(1));
}



quadratic_order
quadratic_order::maximize()
{
  quadratic_order max_ord;
  bigint cond;

  square(cond,conductor());
  max_ord.assign(Delta / cond);

  return max_ord;
}




int
kronecker(const bigint & D, const bigint & p)
{
  bigint c1;
  int kro;

  /* if p > 2, use jacobi symbol */
  if (p.compare(bigint(2))) {
    remainder(c1,D,p);
    if (c1.is_lt_zero())
      add(c1,c1,p);
    kro = jacobi(c1,p);
  }
  else {
    remainder(c1,D,8);
    if (c1.is_lt_zero())
      add(c1,c1,8);
    if (c1.is_one())
      kro = 1;
    else if (!c1.compare(bigint(5)))
      kro = -1;
    else
      kro = 0;
  }

  return kro;
}



long
quadratic_order::generate_optimal_Q()
{
  long OQ;
  bigfloat A,l2;
  short *bitptr;

  PL.PrimeGen(3,1000000);

  l2 = log(sqrt(bigfloat(2)));
  bitptr = PL.bitarray;
  OQ = 3;
  A = estimate_L1_error(OQ);
  while (A >= l2) {
    do {
      OQ += 2;
      ++bitptr;
    } while (!(*bitptr));
    A = estimate_L1_error(OQ);
  }

  return OQ;
}



long
quadratic_order::get_optimal_Q()
{
  long Dlog;
  bigfloat temp;

  temp = floor(log(bigfloat(abs(Delta))) / log(10));
  temp.longify(Dlog);

  return OQvals[Dlog / 5];
}



long
quadratic_order::generate_optimal_Q_cnum(const bigfloat & h2, const bigfloat & t)
{
  long OQ;
  bigfloat A,val;
  short *bitptr;

  PL.PrimeGen(3,1000000);

  val = log((h2 + bigfloat(1)) / (h2 + t));
  bitptr = PL.bitarray;
  OQ = 3; 
  A = estimate_L1_error(OQ);
  while (A >= val) {
    do {
      OQ += 2;
      ++bitptr;
    } while (!(*bitptr));
    A = estimate_L1_error(OQ);
  }

  return OQ;
}



long
quadratic_order::get_optimal_Q_cnum()
{
  long Dlog;
  bigfloat temp;

  temp = floor(log(bigfloat(abs(Delta))) / log(10));
  temp.longify(Dlog);

  return OQvals_cnum[Dlog / 5];
}



long
quadratic_order::generate_optimal_Q_cfunc()
{
  long OQ;
  bigfloat B,sb,val,lDelta,lQ,temp1,temp2;

  sqrt(val,bigfloat(1.0000002));
  inc(val);
  val.divide_by_2();
  val.assign(log(val));

  lDelta.assign(log(bigfloat(abs(Delta))));

  OQ = 100;
  lQ.assign(log(bigfloat(OQ)));

  multiply(temp1,Pi(),lQ);
  temp1.invert();
  square(temp2,lQ);
  divide(temp2,bigfloat(5.3),temp2);
  add(temp1,temp1,temp2);
  multiply(B,lDelta,temp1);

  divide(temp1,bigfloat(4.0),lQ);
  divide(temp2,bigfloat(1.0),Pi());
  add(B,B,temp1);
  add(B,B,temp2);

  power(temp1,bigfloat(OQ),bigfloat(1.5));
  multiply(temp1,temp1,9);
  multiply(temp2,lQ,13);
  add(temp2,temp2,8);
  divide(sb,temp2,temp1);
  multiply(sb,sb,B);
  
  square(temp1,bigfloat(OQ));
  divide(temp1,bigfloat(2.0),temp1);
  add(sb,sb,temp1);

  while (sb >= val) {
    if (OQ == 100)
      OQ = 1000;
    else if (OQ < 5000)
      OQ += 1000;
    else
      OQ += 5000;

    lQ.assign(log(bigfloat(OQ)));

    multiply(temp1,Pi(),lQ);
    temp1.invert();
    square(temp2,lQ);
    divide(temp2,bigfloat(5.3),temp2);
    add(temp1,temp1,temp2);
    multiply(B,lDelta,temp1);

    divide(temp1,bigfloat(4.0),lQ);
    divide(temp2,bigfloat(1.0),Pi());
    add(B,B,temp1);
    add(B,B,temp2);

    power(temp1,bigfloat(OQ),bigfloat(1.5));
    multiply(temp1,temp1,9);
    multiply(temp2,lQ,13);
    add(temp2,temp2,8);
    divide(sb,temp2,temp1);
    multiply(sb,sb,B);
  
    square(temp1,bigfloat(OQ));
    divide(temp1,bigfloat(2.0),temp1);
    add(sb,sb,temp1);
  }

  return OQ;
}



long
quadratic_order::get_optimal_Q_cfunc()
{
  long Dlog;
  bigfloat temp;

  temp = floor(log(bigfloat(abs(Delta))) / log(10));
  temp.longify(Dlog);

  return OQvals_cfunc[Dlog / 5];
}



bigfloat
quadratic_order::estimate_L(const int s, const long nQ)
{
  int kron,i;
  short *bitptr;
  long m8,P,s2;
  double E,Ps;

  bigfloat::precision(prec);

  /* compute partial product for p = 2 */
  m8 = remainder(Delta,8);
  if (m8 < 0)
    m8 += 8;
  if (m8 == 1)
    kron = 1;
  else if (m8 == 5)
    kron = -1;
  else
    kron = 0;
  s2 = 1;
  s2 <<= s;
  E = log((double) s2 / ((double) s2-kron));

  /* generate sufficiently many primes */
  if (nQ > PL.Hbitval)
    PL.PrimeGen(3,nQ+10);

  /* compute partial product for p < nQ */
  bitptr = PL.bitarray;
  P = 3;
  while (P < nQ) {
    if (*bitptr) {
      kron = kronecker(Delta,bigint(P));
      Ps = P;
      for (i=1; i<s; ++i)
        Ps *= P;
      E += log(Ps / (Ps - kron));
    }
    P += 2;
    ++bitptr;
  }

  return exp(E);
}



bigfloat
quadratic_order::estimate_L1(const long nQ)
{
  int kron;
  long Q2,m8,P;
  short *bitptr;
  double E,C,wt;
  register long i;
  bigfloat nFI;

  bigfloat::precision(prec);

  /* if nQ = Q, then the estimate has already been computed */
  if ((nQ == Q) && (!FI.is_zero())) {
    nFI = FI;
    return(nFI);
  }

  /* compute partial product for p = 2 */
  Q = nQ;
  m8 = remainder(Delta,8);
  if (m8 < 0)
    m8 += 8;
  if (m8 == 1)
    E = log(2.0);
  else if (m8 == 5)
    E = log(2.0 / 3.0);
  else
    E = 0.0;

  /* compute weights */
  Q2 = Q << 1;
  C = 0.0;
  for (i=Q; i<=Q2-1; ++i)
    C += (double) i * log((double) i);

  /* generate list of primes */
  if (Q2 > PL.Hbitval)
    PL.PrimeGen(3,Q2+10);

  /* compute partial product for p < Q */
  bitptr = PL.bitarray;
  P = 3;
  while (P < Q) {
    if (*bitptr) {
      kron = kronecker(Delta,bigint(P));
      E += log((double) P / (P - kron));
    }
    P += 2;
    ++bitptr;
  }

  /* computed weighted partial products for Q < p < 2Q */
  wt = 1.0;
  for (i=Q; i<=P; ++i)
    wt -= (double) i*log((double) i) / C;
  while (P < Q2) {
    if (*bitptr) {
      kron = kronecker(Delta,bigint(P));
      E += wt*log((double) P / (P - kron));
    }
    P += 2;
    ++bitptr;
    wt -= (double) ((P-1.0)*log((double) P-1.0) + P*log((double) P)) / C;
  }

  nFI = exp(bigfloat(E));

  /* set static value */
  FI = nFI;

  return(nFI);
}



bigfloat
quadratic_order::estimate_L1_error(const long nQ) const
{
  bigfloat A,lq;

  lq.assign(log(bigfloat(nQ)));
  if (Q >= 100000)
    A = (6.338*log(bigfloat(abs(Delta))) + 17.031) / (lq * sqrt(bigfloat(nQ)));
  else if (Q >= 50000)
    A = (6.378*log(bigfloat(abs(Delta))) + 17.397) / (lq * sqrt(bigfloat(nQ)));
  else if (Q >= 10000)
    A = (6.510*log(bigfloat(abs(Delta))) + 18.606) / (lq * sqrt(bigfloat(nQ)));
  else if (Q >= 5000)
    A = (6.593*log(bigfloat(abs(Delta))) + 19.321) / (lq * sqrt(bigfloat(nQ)));
  else if (Q >=1000)
    A = (6.897*log(bigfloat(abs(Delta))) + 21.528) / (lq * sqrt(bigfloat(nQ)));
  else
    A = (7.106*log(bigfloat(abs(Delta))) + 22.845) / (lq * sqrt(bigfloat(nQ)));

  return A;
}




bigfloat
quadratic_order::Lfunction()
{
  bigfloat::precision(prec);

  if (!is_L_computed()) {
    /* L has not been computed - compute it */

    /* compute class number, if needed */
    if (h.is_zero())
      class_number();

    if (is_imaginary()) {
      /* L = pi*h / sqrt(Delta) */
      multiply(L,Pi(),bigfloat(h));
      divide(L,L,sqrt(bigfloat(-Delta)));
    }
    else {
      /* L = 2hR / sqrt(Delta) */
      multiply(L,R,bigfloat(h));
      L.multiply_by_2();
      divide(L,L,sqrt(bigfloat(Delta)));
    }
  }

  return L;
}



bigfloat
quadratic_order::LDfunction()
{
  bigfloat LD;
  long m8;

  bigfloat::precision(prec);

  m8 = remainder(Delta,8);
  if (m8 < 0)
    m8 += 8;
  LD.assign(Lfunction());
  if (m8 == 1)
    LD.divide_by_2();
  else if (m8 == 5)
    multiply(LD,LD,bigfloat(1.5));

  return LD;
}



bigfloat
quadratic_order::LLI()
{
  bigfloat LLI,c;

  if (Delta.is_zero())
    LLI.assign_zero();
  else {
    c.assign(exp(Euler()) / (Pi() * Pi()));
  
    if (Delta.is_odd())
      c *= bigfloat(12.0);
    else
      c *= bigfloat(8.0);

    LLI = Lfunction() * c * log(log(bigfloat(abs(Delta))));
  }

  return LLI;
}



bigfloat
quadratic_order::LLI_D()
{
  bigfloat LLI,c;

  if (Delta.is_zero())
    LLI.assign_zero();
  else {
    c.assign(bigfloat(8.0) * exp(Euler()) / (Pi() * Pi()));
    LLI = LDfunction() * c * log(log(bigfloat(abs(Delta << 2))));
  }

  return LLI;
}



bigfloat
quadratic_order::ULI()
{
  bigfloat ULI,c;

  if (Delta.is_zero())
    ULI.assign_zero();
  else {
    c.assign(exp(Euler()));

    if (Delta.is_odd())
      c.multiply_by_2();

    ULI = Lfunction() / (c * log(log(bigfloat(abs(Delta)))));
  }

  return ULI;

}



bigfloat
quadratic_order::ULI_D()
{
  bigfloat ULI,c;

  if (Delta.is_zero())
    ULI.assign_zero();
  else {
    c.assign(exp(Euler()));
    ULI = LDfunction() / (c * log(log(bigfloat(abs(Delta << 2)))));
  }

  return ULI;
}



bigfloat
quadratic_order::Cfunction()
{
  bigfloat temp,E4;
  int kron;
  long Q2,m8,P,num;
  short *bitptr;
  double E,E2,E3,C,wt,dP,dP2;
  register long i;

  bigfloat::precision(prec);

  if (!is_C_computed()) {
    Q2 = get_optimal_Q_cfunc();

    if (h.is_zero()) {
      Q = Q2 >> 1;

      /* compute partial product for p = 2 */
      m8 = remainder(Delta,8);
      if (m8 < 0)
        m8 += 8;
      if (m8 == 1) {
        E = log(2.0);
        E2 = log(4.0 / 3.0);
        Cfunc.assign(2.5);
      }
      else if (m8 == 5) {
        E = log(2.0 / 3.0);
        E2 = log(4.0 / 5.0);
        Cfunc.assign(0.5);
      }
      else {
        E = 0.0;
        E2 = 0.0;
        Cfunc.assign(15.0/16.0);
      }
      E3 = 0.0;

      /* compute weights */
      C = 0.0;
      for (i=Q; i<=Q2-1; ++i)
        C += (double) i * log((double) i);

      /* generate list of primes */
      if (Q2 > PL.Hbitval)
        PL.PrimeGen(3,Q2+10);

      /* compute partial product for p < Q */
      bitptr = PL.bitarray;
      P = 3;
      while (P < Q) {
        if (*bitptr) {
          kron = kronecker(Delta,bigint(P));
          E += log((double) P / (P - kron));

          dP = (double) P;
          dP2 = dP*dP;
          E2 += log( dP2 / (dP2 - kron));

          if (kron == 1) {
            dP2 = (dP - 1.0) * (dP - 1.0);
            dP2 *= dP;
            E3 += log(1.0 - (2.0 / dP2));
          }
        }
        P += 2;
        ++bitptr;
      }

      /* computed weighted partial products for Q < p < 2Q */
      wt = 1.0;
      for (i=Q; i<=P; ++i)
        wt -= (double) i*log((double) i) / C;
      while (P < Q2) {
        if (*bitptr) {
          kron = kronecker(Delta,bigint(P));
          E += wt*log((double) P / (P - kron));

          dP = (double) P;
          dP2 = dP*dP;
          E2 += log( dP2 / (dP2 - kron));

          if (kron == 1) {
            dP2 = (dP - 1.0) * (dP - 1.0);
            dP2 *= dP;
            E3 += log(1.0 - (2.0 / dP2));
          }
        }
        P += 2;
        ++bitptr;
        wt -= (double) ((P-1.0)*log((double) P-1.0) + P*log((double) P)) / C;
      }

      FI = exp(bigfloat(E));
      class_number();
    }
    else {
      /* compute partial product for p = 2 */
      m8 = remainder(Delta,8);
      if (m8 < 0)
        m8 += 8;
      if (m8 == 1) {
        E2 = log(4.0 / 3.0);
        Cfunc.assign(2.5);
      }
      else if (m8 == 5) {
        E2 = log(4.0 / 5.0);
        Cfunc.assign(0.5);
      }
      else {
        E2 = 0.0;
        Cfunc.assign(15.0/16.0);
      }
      E3 = 0.0;

      /* generate list of primes */
      if (Q2 > PL.Hbitval)
        PL.PrimeGen(3,Q2+10);

      /* compute partial product for p < Q */
      bitptr = PL.bitarray;
      P = 3;
      while (P < Q2) {
        if (*bitptr) {
          kron = kronecker(Delta,bigint(P));

          dP = (double) P;
          dP2 = dP*dP;
          E2 += log( dP2 / (dP2 - kron));

          if (kron == 1) {
            dP2 = (dP - 1.0) * (dP - 1.0);
            dP2 *= dP;
            E3 += log(1.0 - (2.0 / dP2));
          }
        }
        P += 2;
        ++bitptr;
      }
    }

    /* compute C */
    if (is_imaginary()) {
      multiply(Cfunc,Cfunc,sqrt(bigfloat(-Delta)));
      power(temp,Pi(),3);
      divide(temp,temp,bigfloat(h));
      divide(temp,temp,90);
      multiply(Cfunc,Cfunc,temp);
    }
    else {
      multiply(Cfunc,Cfunc,sqrt(bigfloat(Delta)));
      power(temp,Pi(),4);
      divide(temp,temp,bigfloat(h));
      divide(temp,temp,R);
      divide(temp,temp,180);
      multiply(Cfunc,Cfunc,temp);
    }

    factor_discriminant();
    E4.assign_one();
    num = disc_fact.no_of_comp();
    for (i=0; i<num; ++i) {
      if (disc_fact.base((lidia_size_t) i) > 2) {
        power(temp,bigfloat(P),4);
        temp.invert();
        subtract(temp,1,temp);
        E4 *= temp;
      }
    }

    multiply(Cfunc,Cfunc,E4);
    divide(Cfunc,Cfunc,bigfloat(exp(E2)));
    multiply(Cfunc,Cfunc,bigfloat(exp(E3)));
  }
 
  return Cfunc;
}



bigfloat
quadratic_order::regulator()
{
  int num;
  char str[150];

  if (!is_R_computed()) {
    /* R has not been computed yet - compute it */

    if (is_imaginary())
      R.assign_one();

    else {
      num = bigint_to_string(Delta,str);
      if (num < RBJTB)
        regulator_BJT();
      else if (num < RSHANKSB)
        regulator_shanks();
      else 
        class_group_subexp();
    }
  }

  return R;
}



bigint
quadratic_order::class_number()
{
  int num;
  char str[150];

  if (!is_h_computed()) {
    /* h has not been computed yet - compute it */

    num = bigint_to_string(abs(Delta),str);

    if (is_real())
      class_number_shanks();
    else {
      if (num < CGBJTB)
        class_group_BJT();
      if (num < CGSHANKSB)
        class_number_shanks();
      else
        class_group_subexp();
    }
  }

  return h;
}



base_vector <bigint>
quadratic_order::class_group()
{
  int num;
  char str[150];

  if (!is_CL_computed()) {
    /* CL has not been computed yet - compute it */

    if (h.is_gt_zero()) {
      /* class number is known */
/*
      class_group_h();
*/
      h.assign_zero();
      class_group();
    }
    else {
      num = bigint_to_string(abs(Delta),str);

      if (num < CGBJTB)
        class_group_BJT();
      else if (num < CGSHANKSB)
        class_group_shanks();
      else {
        if (is_imaginary())
          class_group_subexp();
        else
          class_group_shanks();
      }
    }
  }

  return CL;
}




bigfloat
quadratic_order::regulator_BJT()
{
  qi_class_real A;
  timer t;

  bigfloat::precision(prec);

  if (!is_R_computed()) {
    if (is_imaginary())
      R.assign_one();
    else {
      if (prin_list.no_of_elements() > 0) {
        A = prin_list.last_entry();
        if ((A.is_one()) && (!R.is_zero()))
          R.assign(A.get_distance());
      }

      if (R.is_zero()) {
        t.start_timer();

        rBJT();

        t.stop_timer();

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

  return R;
}



bigfloat
quadratic_order::regulator_shanks()
{
  qi_class_real A;
  timer t;

  bigfloat::precision(prec);

  if (!is_R_computed()) {
    if (is_imaginary())
      R.assign_one();
    else {
      if (prin_list.no_of_elements() > 0) {
        A = prin_list.last_entry();
        if ((A.is_one()) && (!R.is_zero()))
          R.assign(A.get_distance());
      }

      if (R.is_zero()) {
        t.start_timer();

        rshanks();

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

  return R;
}



bigint
quadratic_order::class_number_shanks()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  if (!is_h_computed()) {
    if (is_imaginary())
      cns_imag();
    else
      cns_real();
  }

  t.stop_timer();

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

  return h;
}



base_vector <bigint>
quadratic_order::class_group_BJT()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  if (!is_CL_computed()) {
    if (is_imaginary())
      cgBJT_imag();
    else
      cgBJT_real();
  }

  t.stop_timer();

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

  return CL;
}



base_vector <bigint>
quadratic_order::class_group_shanks()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  if (!is_CL_computed()) {
    if (is_imaginary())
      cgs_imag();
    else
      cgs_real();
  }

  t.stop_timer();

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

  return CL;
}



base_vector <bigint>
quadratic_order::class_group_h()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  if (!is_CL_computed()) {
    /* compute h if necessary */
    if (h.is_zero());
      class_number();

    if (is_imaginary()) {
      warning_handler_c("quadratic_order","class_group_h() - not implemented", return CL);
      cgh_imag();
    }
    else {
      cgh_real();
      warning_handler_c("quadratic_order","class_group_h() - not implemented", return CL);
    }
  }

  t.stop_timer();

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

  return CL;
}



base_vector <bigint>
quadratic_order::class_group_subexp()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  if (!is_CL_computed()) {
    if (is_imaginary())
      if (Delta >= -10000)
        cgBJT_imag();
      else
        cgsubexp_imag();
    else {
      if (Delta < 10000)
        cgBJT_real();
      else
        cgsubexp_real();
    }
  }

  t.stop_timer();

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




base_vector <bigint>
quadratic_order::class_group_subexp_more()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  cgsubexp_imag_more();

  t.stop_timer();

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




base_vector <bigint>
quadratic_order::class_group_subexp_end()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  cgsubexp_imag_end();

  t.stop_timer();

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




rational_factorization
quadratic_order::factor_h()
{
  /* compute class number, if needed */
  if (!is_h_computed())
    class_number();

  /* factor h, if necessary */
  if (h.is_gt_zero()) {
    if (hfact != h) {
      hfact.assign(h);
      hfact.factor();
    }
  }

  return hfact;
}



bigint
quadratic_order::exponent()
{
  bigint expo;

  /* compute class group, if needed */
  if (!is_CL_computed())
    class_group();

  /* return largest non-cyclic factor */
  if (!is_CL_computed())
    expo.assign_zero();
  else
    expo.assign(CL[CL.size()-1]);

  return expo;
}



int
quadratic_order::p_rank(const bigint & p)
{
  int prank;
  lidia_size_t i,rank;
  bigint rem;

  /* compute class group, if needed */
  if (!is_CL_computed())
    class_group();

  if (h.is_zero())
    prank = 0;
  else {
    /* compute p-rank */
    rank = CL.size();
    i = 0;
    remainder(rem,CL[i],p);
    while ((!rem.is_zero()) && (i<rank)) {
      ++i;
      if (i < rank)
        remainder(rem,CL[i],p);
    }
    prank = rank - i;
  }

  return prank;
}




base_vector <qi_class>
quadratic_order::generators()
{
  qi_class G,A;
  lidia_size_t i,j,numCL,numFB;

  if ((gens.size() == 0) && (!Delta.is_zero())) {
    /* compute class group, if needed */
    if (!is_CL_computed())
      class_group();

    /* columns of UINV are the exponents vectors for the factor base elements
       which correspond to each generator */
    numCL = CL.size();
    numFB = fact_base.size();
    for (i=0; i<numCL; ++i) {
      G.assign_one();
      for (j=0; j<numFB; ++j) {
        power(A,fact_base[j],UINV.member(j,i));
        G *= A;
      }
      gens[i] = G;
    }
  }

  return gens;
}



rational_factorization
quadratic_order::factor_discriminant()
{
  timer t;

  bigfloat::precision(prec);

  t.start_timer();

  if (!Delta.is_zero()) {
    if (is_imaginary())
      factor_imag();
    else
      factor_real();
  }
  
  t.stop_timer();

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

  return disc_fact;
}



istream & operator >> (istream & in, quadratic_order & QO)
{
  bigint D;

  in >> D;
  QO.assign(D);

  return in;
}



ostream & operator << (ostream & out, const quadratic_order & QO)
{
  int num;
  char str[150];

  num = bigint_to_string(abs(QO.Delta),str);
  bigfloat::precision(QO.prec);

  out << "Quadratic Order:\n" << flush;
  out << "   Delta = " << QO.Delta << " (" << num << ")\n" << flush;
  if (!QO.Delta.is_zero()) {
    if (QO.disc_fact.no_of_comp() > 0)
      out << "         =" << QO.disc_fact << "\n" << flush;
    if (QO.is_R_computed())
      out << "   R = " << QO.R << "\n" << flush;
    if (QO.is_h_computed())
      out << "   h = " << QO.h << "\n" << flush;
    if (QO.is_CL_computed())
      out << "   CL = " << QO.CL << "\n" << flush;
    if (QO.gens.size() > 0)
      out << "   generators = " << QO.gens << "\n" << flush;
    if (QO.is_L_computed())
      out << "   L(1,X) = " << QO.L << "\n" << flush;
    if (QO.is_C_computed()) {
      bigfloat::precision(8);
      out << "   C(Delta) = " << QO.Cfunc << "\n" << flush;
    }
  }

  bigfloat::precision(QO.prec);

  return out;
}



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


void
quadratic_order::rBJT()
{
  bigfloat y,usqr,u,temp;
  long upper;
  bigint temp2;
  qi_class_real A,C,D,*F,UU; // MM

  qi_class::set_current_order(*this);
  UU.assign_one(); // MM

  if (prin_list.no_of_elements() > 0) {
    A.assign(prin_list.last_entry());
    ceil(temp,A.get_distance());
    temp.bigintify(temp2);
    if (temp2.is_odd())
      inc(temp2);
    u.assign(temp2);
  }
  else {
    /* 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);
    prin_list.initialize((lidia_size_t) upper);
    prin_list.set_key_function(&qi_class_real_key);

    add(temp,log(bigfloat(Delta)),3);
    shift_right(temp,temp,2);
    ceil(temp,temp);
    temp.bigintify(temp2);
    if (temp2.is_odd())
      inc(temp2);
    u.assign(temp2);

    A.assign_one();
  }

  D.assign_one();
  y.assign(u);
  while (R.is_zero()) {
    /* compute more baby steps */
    while (A.get_distance() < u) {
      A.rho();
      prin_list.hash(A);
      if (A.is_one()) {
        R.assign(A.get_distance());
        break;
      }
    }

    /* compute giant steps to u^2 */
    inverse(C,A);
    while (abs(C.get_distance()) <= u)
      C.inverse_rho();
    while (abs(C.get_distance()) > u)
      C.rho();

    square(usqr,u);
    while ((y.compare(usqr) < 0) && (R.is_zero())) {
      multiply_real(D,D,C);
      while (abs(D.get_distance()) <= y)
        D.inverse_rho();
      while (abs(D.get_distance()) > y)
        D.rho();

      F = prin_list.search(D);
      if (F) {
        /* found D in list:  R = y-r */
        subtract(R,D.get_distance(),F->get_distance());
        R.absolute_value();
      }

      add(y,y,u);
    }

    u.multiply_by_2();
  }
}



void
quadratic_order::rshanks()
{
  bigfloat y,usqr,u,hR,temp,E,Aval,Fval,l2;
  long upper,OQ;
  bigint temp2;
  qi_class_real A,C,CINV,BB,D,*F,UU; // MM

  qi_class::set_current_order(*this);
  UU.assign_one(); // MM

  /* compute approximation of h */
  OQ = get_optimal_Q_cnum();
  E = estimate_L1(OQ);
  E *= sqrt(bigfloat(Delta)) / (bigfloat(2));

  Aval = estimate_L1_error(OQ);
  Fval = exp(Aval) - bigfloat(1);
  temp = bigfloat(1) - exp(-Aval);
  if (temp > Fval)
    Fval = temp;
  sqrt(temp,E*Fval);
  ceil(temp2,temp);
  if (temp2.is_odd())
    inc(temp2);
  u.assign(temp2);

  power(temp,bigfloat(Delta),bigfloat(0.2));
  ceil(temp,temp);

  if (prin_list.no_of_elements() > 0) {
    A.assign(prin_list.last_entry());
    if (A.get_distance() > u) {
      ceil(temp,A.get_distance());
      temp.bigintify(temp2);
      if (temp2.is_odd())
        inc(temp2);
      u.assign(temp2);
    }
  }
  else {
    /* get hash table size */
    temp.multiply_by_2();
    multiply(temp,temp,bigfloat(1.33));
    temp.longify(upper);
    prin_list.initialize((lidia_size_t) upper);
    prin_list.set_key_function(&qi_class_real_key);

    add(temp,log(bigfloat(Delta)),3);
    shift_right(temp,temp,2);
    ceil(temp,temp);
    temp.bigintify(temp2);
    if (temp2.is_odd())
      inc(temp2);
    if (bigfloat(temp2) > u)
      u.assign(temp2);

    A.assign_one();
    prin_list.hash(A);
  }

  log(l2,bigfloat(2));
  y.assign_zero();
  D.assign(nearest(UU,E)); // MM
  BB.assign(D);

  while (R.is_zero()) {
    /* compute more baby steps */
    while (A.get_distance() < u) {
      A.rho();
      prin_list.hash(A);
      if (A.is_one()) {
        R.assign(A.get_distance());
        break;
      }
    }

    /* compute giant steps to u^2 */
    C.assign(A);
    while (C.get_distance() <= u)
      C.rho();
    while (C.get_distance() > u)
      C.inverse_rho();
 
    inverse(CINV,C);
    while (abs(CINV.get_distance()) <= u)
      CINV.inverse_rho();
    while (abs(CINV.get_distance()) > u)
      CINV.rho();

    square(usqr,u);
    while ((y.compare(usqr) < 0) && (R.is_zero())) {
      subtract(temp,E,y);
      while (D.get_distance() <= temp)
        D.rho();
      while (D.get_distance() > temp)
        D.inverse_rho();
      F = prin_list.search(D);
      if (F) {
        /* found BINV in list */
        subtract(hR,D.get_distance(),F->get_distance());
        if (hR >= l2)
          R = find_hstar(hR,E);
        else
          hR.assign_zero();
      }

      if (R.is_zero()) {
        add(temp,E,y);
        while (BB.get_distance() <= temp)
          BB.rho();
        while (BB.get_distance() > temp)
          BB.inverse_rho();

        F = prin_list.search(BB);
        if (F) {
          /* found B in list */
          subtract(hR,BB.get_distance(),F->get_distance());
          if (hR >= l2)
            R = find_hstar(hR,E);
          else
            hR.assign_zero();
        }
      }

      if (R.is_zero()) {
        multiply_real(D,D,CINV);
        multiply_real(BB,BB,C);
        add(y,y,u);
      }
    }

    u.multiply_by_2();
  }
}



bigfloat
quadratic_order::find_hstar(const bigfloat & hR, const bigfloat & E)
{
  bigfloat u,Lval,Eval,Rval,y,Bval,temp,dist,app_zero;
  bigint QQ,P;
  qi_class_real A,B,C,CINV,*F,UU; // MM
  base_vector <qi_class_real> Ilist;
  lidia_size_t i;
  long upper;
  short *bitptr;

  power(app_zero,bigfloat(0.1),prec-10);
  Ilist.set_mode(EXPAND);
  UU.assign_one(); // MM

  Rval.assign_zero();
  C = prin_list.last_entry();
  Lval.assign(C.get_distance());
  divide(Eval,E,sqrt(Lval));

  /* check whether R < E / sqrt(dist(C)) */
  floor(u,C.get_distance());
  u.bigintify(P);
  if (P.is_odd())
    inc(P);
  if (P.is_zero())
    P.assign(2);
  u.assign(P);
  y.assign(u);

  inverse(CINV,C);
  while (abs(CINV.get_distance()) <= u)
    CINV.inverse_rho();
  while (abs(CINV.get_distance()) > u)
    CINV.rho();
  B.assign_one();

  while ((y <= Eval) && (Rval.is_zero())) {
    multiply_real(B,B,CINV);
    while (abs(B.get_distance()) <= y)
      B.inverse_rho();
    while (abs(B.get_distance()) > y)
      B.rho();

    F = prin_list.search(B);
    if (F) {
      subtract(Rval,B.get_distance(),F->get_distance());
      Rval.absolute_value();
      if (R < app_zero)
        R.assign_zero();
    }

    add(y,y,u);
  }

  /* if R > E / sqrt(dist(C)), compute h* */
  if (Rval.is_zero()) {
    Rval.assign(hR);

    /* Bval = sqrt(L) + (L^2 sqrt(L)) / E */
    sqrt(Eval,Lval);
    square(temp,Lval);
    multiply(temp,temp,Eval);
    divide(temp,temp,E);
    add(Bval,Eval,temp);

    /* compute table of powers of C */
    shift_right(Eval,hR,1);
    temp.assign(C.get_distance());
    i = 0;
    A.assign(C);
    Ilist[0] = A;
    while (A.get_distance() < Eval) {
      ++i;
      temp.multiply_by_2();
      square(A,A);
      while (A.get_distance() < temp)
        A.rho();
      A.inverse_rho();
      Ilist[i] = A;
    }
    
    bitptr = NULL;
    Bval.longify(upper);
    if (upper > 2) {
      if (!(upper & 1))
        ++upper;
      PL.PrimeGen(3,upper);
      bitptr = &PL.bitarray[(upper-3) >> 1];
      P.assign(upper);
      while (!*bitptr) {
        P -= 2;
        if (P.is_one()) {
          P.assign(2);
          break;
        }
        --bitptr;
      }
    }
    else if (upper == 2)
      P = 2;
    else
      P = 0;

    A.assign_one();

    while (P.is_gt_zero()) {
      divide(dist,Rval,bigfloat(P)); 
      subtract(temp,dist,A.get_distance());
      divide(temp,temp,Lval);
      floor(temp,temp);
      temp.bigintify(QQ);
      inc(QQ);
      i = 0;
      while (QQ.is_gt_zero()) {
        if (QQ.is_odd())
          multiply_real(A,A,Ilist[i]);
        QQ.divide_by_2();
        ++i;
      }

      while (A.get_distance() <= dist)
        A.rho();
      add(temp,dist,C.get_distance());
      while (A.get_distance() >= temp)
        A.inverse_rho();

      F = prin_list.search(A);
      if (F) {
        subtract(temp,A.get_distance(),F->get_distance());
        subtract(temp,temp,dist);
        temp.absolute_value();
        if (temp < app_zero) {
          divide(Rval,Rval,bigfloat(P));
          A.assign_one();
        }
        else {
          if (P == bigint(2))
            P.assign_zero();
          else {
            do {
              P -= 2;
              if (P.is_one()) {
                P.assign(2);
                break;
                }
              --bitptr;
            } while (!*bitptr);
          }
        }
      }
      else {
        if (P == bigint(2))
          P.assign_zero();
        else {
          do {
            P -= 2;
            if (P.is_one()) {
              P.assign(2);
              break;
              }
            --bitptr;
          } while (!*bitptr);
        }
      }
    }
  }

  return Rval;
}



bigint
quadratic_order::divisor_imag(const bigfloat & nFI, const bigfloat & F)
{
  long *pptr;
  bigint h1,y,usqr,det,Bjj,ht;
  long s,u,r,q,upper,Bj;
  lidia_size_t i,k,crank,numRpr,numR,numQ,curr_index,idx;
  base_vector <long> Rvec,Qvec;
  qi_class G,A,B,Bcomp,C,D,E,HI,Gq,GBj,CINV;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> QT,RT;
  bigfloat temp,minh1,tFI;
  rational_factorization htfact;

  qi_class::set_current_order(*this);

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

  /* initialize sets R and Q */
  temp = ceil(power(bigfloat(-Delta),bigfloat(0.2)));
  temp.longify(upper);

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

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

  crank = 0;
  det.assign_one();
  h1.assign_one();
  pptr = PL.primes;

  tFI.assign(nFI);
  minh1 = power(bigfloat(-Delta),bigfloat(0.4));
  temp = nFI / 3;
  if (temp < minh1)
    minh1 = temp;

  while (bigfloat(h1) < minh1) {
    /* get prime ideal */
    while (!prime_ideal(G,bigint(*pptr)))
      ++pptr;

    numRpr = RT.no_of_elements();
    curr_index = numRpr;
    numQ = QT.no_of_elements();

    s = 1;

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

    if (Bjj.is_zero()) {
      HI.assign(inverse(G));
      A.assign_one();

      tFI.bigintify(ht);
      temp = sqrt(tFI*F + abs(tFI - bigfloat(ht)));
      temp.longify(u);

      if (u == 0)
        u = 2;
      if ((u & 1) == 1)
        ++u;
      y.assign_zero();

      power_imag(B,G,ht);
      if (crank == 0) {
        /* check whether G^ht = (1) */
        if (B.is_one()) {
          htfact.assign(ht);
          htfact.factor();
          Bjj = G.order_mult(ht,htfact);
        }
      }
      else {
        /* check whether G^ht is in currently generated subgroup */
        for (i=0; i<numQ; ++i) {
          D.assign(QT[i].get_A());
          multiply_imag(E,D,B);
          if (E.is_one())
            r = 0;
          else {
            Inode = RT.search(ideal_node(E,0));
            if (Inode)
              r = Inode->get_index();
            else
              r = -1;
          }
          if (r >= 0) {
            /* determine smallest divisor of ht */
            q = i;
            Bjj = exact_power(ht,G,RT,QT,numRpr,r,q);
            break;
          }
        }
      }

      if (Bjj.is_zero()) {
        power_imag(C,G,u);
        CINV = inverse(C);
        Bcomp.assign(B);
      }
      else 
        u = 0;
    }

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

            /* check if relation already found */
            Inode = QT.search(ideal_node(E,0));
            if (Inode) {
              Bjj.assign(r);
              break;
            }
            else {
              RT.hash(ideal_node(E,curr_index));
              ++curr_index;
            }
          }
          if (!Bjj.is_zero())
            break;
        }
        else {
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);
            RT.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(QT[i].get_A());

          multiply_imag(D,E,Bcomp);
          Inode = RT.search(ideal_node(D,0));
          if (Inode) {
            r = Inode->get_index();
            subtract(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            if (!Bjj.is_zero()) {
              /* determine smallest divisor of Bjj */
              ht.assign(Bjj);
              if (crank == 0) {
                htfact.assign(Bjj);
                htfact.factor();
                Bjj = G.order_mult(Bjj,htfact);
              }
              else {
                q = i;
                r %= numRpr;
                Bjj = exact_power(Bjj,G,RT,QT,numRpr,r,q);
              }
              break;
            }
          }

          multiply_imag(D,E,B);
          Inode = RT.search(ideal_node(D,0));
          if (Inode) {
            r = Inode->get_index();
            add(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            /* determine smallest divisor of Bjj */
            ht.assign(Bjj);
            if (crank == 0) {
              htfact.assign(Bjj);
              htfact.factor();
              Bjj = G.order_mult(Bjj,htfact);
            }
            else {
              q = i;
              r %= numRpr;
              Bjj = exact_power(Bjj,G,RT,QT,numRpr,r,q);
            }
            break;
          }
        }

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

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

    if (!Bjj.is_one()) {
      h1 *= Bjj;
      divide(tFI,tFI,bigfloat(Bjj));
      ++crank;
    }

    if (bigfloat(h1) < minh1) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

      /* compute new R' (remove entries with too large exponents) */
      det *= Bj;
      det.longify(upper);
      idx = (lidia_size_t) upper;
      numR = RT.no_of_elements();
      if (idx < numR) {
        /* removing entries */
        for (i=numR-1; i>=idx; --i)
          RT.remove_from(i);
      }
      else {
        /* adding entries */
        idx = (lidia_size_t) u+1;
        for (r=idx; r<Bj; ++r) {
          multiply_imag(A,A,HI);
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);
            RT.hash(ideal_node(E,curr_index));
            ++curr_index;
          }
        }
      }

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

        /* compute new Q */
        numQ = QT.no_of_elements();
        curr_index = numQ;
        power_imag(GBj,G,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(QT[k].get_A());
            multiply_imag(D,E,Gq);
            QT.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_imag(Gq,Gq,GBj);
          ++Qvec[crank-1];
        }
      }

      ++pptr;
    }
  }

  return h1;
}


bigint
quadratic_order::divisor_real(const bigfloat & nFI)
{
  long *pptr;
  bigint h1,y,usqr,det,Bjj,ht;
  long s,u,r,q,upper,Bj;
  lidia_size_t i,k,crank,numRpr,numQ,curr_index,idx,numRreps;
  base_vector <long> Rvec,Qvec;
  qi_class G,A,B,Bcomp,C,D,E,HI,Gq,GBj,CINV,Aprime;
  qi_class_real Areal,FIDL,Gstep,*FS;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> QT,RT;
  rational_factorization htfact;
  base_vector <qi_class> Rreps;
  bigfloat temp,minh1,sqReg,GStepWidth;

  qi_class::set_current_order(*this);

  sqrt(sqReg,R);
  FIDL.assign_one();
  Gstep.assign(nearest(FIDL,sqReg));
  if (Gstep.get_distance() > sqReg)
    Gstep.inverse_rho();
  FIDL.assign(prin_list.last_entry());
  if (Gstep.is_one()) {
    while (!FIDL.is_one()) {
      FIDL.rho();
      prin_list.hash(FIDL);
    }
  }
  else {
    while ((FIDL.get_distance() < Gstep.get_distance()) && (!FIDL.is_one())) {
      FIDL.rho();
      prin_list.hash(FIDL);
    }
    if (!FIDL.is_one()) {
      FIDL.rho();
      prin_list.hash(FIDL);
    }
    if (FIDL.is_one())
      Gstep.assign(FIDL);
  }
  floor(GStepWidth,Gstep.get_distance());

  crank = 0;
  det.assign_one();
  h1.assign_one();
  pptr = PL.primes;

  nFI.bigintify(ht);
  minh1 = power(bigfloat(Delta),bigfloat(0.4));
  temp = nFI / 3;
  if (temp < minh1)
    minh1 = temp;

  Rvec.set_mode(EXPAND);
  Qvec.set_mode(EXPAND);
  Rreps.set_mode(EXPAND);
  Rvec.set_size(0);
  Qvec.set_size(0);
  Rreps.set_size(0);
 
  /* initialize sets R and Q */
  temp = ceil(power(bigfloat(Delta),bigfloat(0.2)));
  temp.longify(upper);

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

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

  Rreps[0] = B;

  while (bigfloat(h1) < minh1) {
    /* get prime ideal */
    while (!prime_ideal(G,bigint(*pptr)))
      ++pptr;

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

    A.assign_one();
    HI.assign(inverse(G));

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

    Bjj.assign_zero();

    HI.assign(inverse(G));
    A.assign_one();

    power_real(B,G,ht);

    if (crank == 0) {
      /* check whether G^ht = (1) */
      if (Gstep.is_one()) {
        FS = prin_list.search(qi_class_real(B));
        if (FS) {
          htfact.assign(ht);
          htfact.factor();
          Bjj = G.order_mult(ht,htfact);
        }
      }
      else {
        FIDL.assign(B,0.0);
        temp.assign_zero();
        while ((FIDL.get_distance() <= R) && (Bjj.is_zero())) {
          FS = prin_list.search(FIDL);
          if (FS) {
            htfact.assign(ht);
            htfact.factor();
            Bjj = G.order_mult(ht,htfact);
          }
          else {
            add(temp,temp,GStepWidth);
            multiply_real(FIDL,FIDL,Gstep);
            while (FIDL.get_distance() <= temp)
              FIDL.rho();
            while (FIDL.get_distance() > temp)
              FIDL.inverse_rho();
          }
        }
      }
    }
    else {
      /* check whether G^ht is in currently generated subgroup */
      r = -1;
      for (i=0; i<numQ; ++i) {
        D.assign(QT[i].get_A());
        multiply_real(E,D,B);

        if (Gstep.is_one()) {
          FS = prin_list.search(qi_class_real(E));
          if (FS)
            r = 0;
          else {
            Inode = RT.search(ideal_node(E,0));
            if (Inode)
              r = Inode->get_index();
            else
              r = -1;
          }
        }
        else {
          FIDL.assign(E,0.0);
          temp.assign_zero();
          while ((FIDL.get_distance() <= R) && (r < 0)) {
            FS = prin_list.search(FIDL);
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(qi_class(FIDL),0));
              if (Inode)
                r = Inode->get_index();
              else {
                add(temp,temp,GStepWidth);
                multiply_real(FIDL,FIDL,Gstep);
                while (FIDL.get_distance() <= temp)
                  FIDL.rho();
                while (FIDL.get_distance() > temp)
                  FIDL.inverse_rho();
              }
            }
          }
        }

        if (r >= 0) {
          /* determine smallest divisor of ht */
          q = i;
          Bjj = exact_power_real(ht,G,RT,QT,numRpr,r,q,Gstep);
          break;
        }
      }
    }

    if (Bjj.is_zero()) {
      C.assign(G);
      CINV = inverse(C);
      Bcomp.assign(B);
    }
    else
      u = 0;

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

          if (Gstep.is_one()) {
            /* store each cycle of ideals */
            apply_rho(Aprime,E);
            while (!Aprime.is_equal(E)) {
              RT.hash(ideal_node(Aprime,curr_index));
              Aprime.rho();
            }
          }
          else {
            /* store ideals with distance < sqReg */
            Areal.assign(E);
            while (Areal.get_distance() < sqReg) {
              Areal.rho();
              RT.hash(ideal_node(qi_class(Areal),curr_index));
            }
            Areal.rho();
            RT.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) {
          E.assign(QT[i].get_A());
          multiply_real(D,E,Bcomp);
          r = -1;

          if (Gstep.is_one()) {
            FS = prin_list.search(qi_class_real(D));
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(D,0));
              if (Inode)
                r = Inode->get_index();
            }
          }
          else {
            FIDL.assign(D,0.0);
            temp.assign_zero();
            while ((FIDL.get_distance() <= R) && (r < 0)) {
              FS = prin_list.search(FIDL);
              if (FS)
                r = 0;
              else {
                Inode = RT.search(ideal_node(qi_class(FIDL),0));
                if (Inode)
                  r = Inode->get_index();
                else {
                  add(temp,temp,GStepWidth);
                  multiply_real(FIDL,FIDL,Gstep);
                  while (FIDL.get_distance() <= temp)
                    FIDL.rho();
                  while (FIDL.get_distance() > temp)
                    FIDL.inverse_rho();
                }
              }
            }
          }

          if (r >= 0) {
            subtract(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            if (!Bjj.is_zero()) {
              /* determine smallest divisor of Bjj */
              if (crank == 0) {
                ht.assign(Bjj);
                htfact.assign(Bjj);
                htfact.factor();
                Bjj = G.order_mult(Bjj,htfact);
              }
              else {
                q = i;
                r %= numRpr;
                Bjj = exact_power_real(Bjj,G,RT,QT,numRpr,r,q,Gstep);
              }
            }
            if (!Bjj.is_zero())
              break;
          }

          multiply_real(D,E,B);
          r = -1;

          if (Gstep.is_one()) {
            FS = prin_list.search(qi_class_real(D));
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(D,0));
              if (Inode)
                r = Inode->get_index();
            }
          }
          else {
            FIDL.assign(D,0.0);
            temp.assign_zero();
            while ((FIDL.get_distance() <= R) && (r < 0)) {
              FS = prin_list.search(FIDL);
              if (FS)
                r = 0;
              else {
                Inode = RT.search(ideal_node(qi_class(FIDL),0));
                if (Inode)
                  r = Inode->get_index();
                else {
                  add(temp,temp,GStepWidth);
                  multiply_real(FIDL,FIDL,Gstep);
                  while (FIDL.get_distance() <= temp)
                    FIDL.rho();
                  while (FIDL.get_distance() > temp)
                    FIDL.inverse_rho();
                }
              }
            }
          }

          if (r >= 0) {
            add(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            /* determine smallest divisor of Bjj */
            if (!Bjj.is_zero()) {
              ht.assign(Bjj);
              if (crank == 0) {
                htfact.assign(Bjj);
                htfact.factor();
                Bjj = G.order_mult(Bjj,htfact);
              }
              else {
                q = i;
                r %= numRpr;
                Bjj = exact_power_real(Bjj,G,RT,QT,numRpr,r,q,Gstep);
              }
            }
            break;
          }
        }

        if (Bjj.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);
          }
        }
      }

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

    if (!Bjj.is_one()) {
      h1 *= Bjj;
      divide(ht,ht,Bjj);
      ++crank;
    }

    if (bigfloat(h1) < minh1) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

      /* compute new R' (remove entries with too large exponents) */
      det *= Bj;
      det.longify(upper);
      idx = (lidia_size_t) upper;
      if (idx < curr_index) {
        numRpr = RT.no_of_elements();
        /* removing entries */
        i = numRpr-1;
        while (RT[i].get_index() >= idx) {
          RT.remove_from(i);
          --i;
        }
        Rreps.set_size((lidia_size_t) idx);
      }
      else {
        /* adding entries */
        idx = (lidia_size_t) u+1;
        for (r=idx; r<Bj; ++r) {
          multiply_real(A,A,HI);
          for (k=0; k<numRreps; ++k) {
            D.assign(Rreps[k]);
            multiply_real(E,D,A);
            Rreps[curr_index] = E;
            RT.hash(ideal_node(E,curr_index));

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

            ++curr_index;
          }
        }
      }


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

        /* compute new Q */
        numQ = QT.no_of_elements();
        curr_index = numQ;
        power_real(GBj,G,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(QT[k].get_A());
            multiply_real(D,E,Gq);
            QT.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_real(Gq,Gq,GBj);
          ++Qvec[crank-1];
        }
      }

      ++pptr;
    }
  }

  return h1;
}




void
quadratic_order::cns_imag()
{
  bigfloat nFI,temp,temp1,temp2,F,A,k,B1,B2,val,bfone;
  bigint h1;

  qi_class::set_current_order(*this);

  /* compute approximation of h */
  Q = get_optimal_Q_cnum();
  nFI.assign(estimate_L1(Q));
  nFI *= sqrt(bigfloat(-Delta)) / Pi();
  if (Delta == -3)
    nFI *= bigfloat(3.0);
  if (Delta == -4)
    nFI *= bigfloat(2.0);
  nFI.bigintify(h);

  A = estimate_L1_error(Q);
  h1.assign_one();
  /* compute divisor of h, if necessary */
  if (h > 3) {
    temp1 = exp(A) - bigfloat(1);
    temp2 = bigfloat(1) - exp(-A);
    if (temp1 > temp2)
      F = temp1;
    else
      F = temp2;
    h1 = divisor_imag(nFI,F);
    nFI /= h1;
    nFI.bigintify(h);
  }

  /* compute sufficiently accurate estimate of L(1,X) to prove correctness */
  bfone = bigfloat(-1.0);
  k = nFI - bigfloat(h);
  B1 = nFI*exp(A);
  B2 = abs(A) + B1 - nFI;
  val = bigfloat(h) - B2 - floor(B2 + bigfloat(h));
  while (val <= bfone) {
    Q += 10000;
    nFI.assign(estimate_L1(Q));
    nFI *= sqrt(bigfloat(-Delta)) / (Pi()*bigfloat(h1));
    nFI.bigintify(h);
    A = estimate_L1_error(Q);
    k = nFI - bigfloat(h);
    B1 = nFI*exp(A);
    B2 = abs(A) + B1 - nFI;
    val = bigfloat(h) - B2 - floor(B2 + bigfloat(h));
  }

  add(B2,B2,h);
  temp.assign(floor(B2));
  temp.bigintify(h);
  h *= h1;
}



void
quadratic_order::cns_real()
{
  bigfloat nFI,temp,temp1,temp2,F,A,val;
  bigint h1;

  qi_class::set_current_order(*this);

  /* compute regulator, if needed */
  if (R.is_zero())
    regulator();

  /* compute estimate of h */
  Q = get_optimal_Q_cnum();
  nFI.assign(estimate_L1(Q));
  nFI *= sqrt(bigfloat(Delta)) / (R*bigfloat(2.0));
  nFI.bigintify(h);

  A = estimate_L1_error(Q);
  val.assign(h);
  inc(val);
  temp = bigfloat(h) + abs(nFI - bigfloat(h));
  val = log(val/temp);
  h1.assign_one();

  /* compute divisor of h, if necessary */
  if ((h > 3) && (A >= val)) {
    temp1 = exp(A) - bigfloat(1);
    temp2 = bigfloat(1) - exp(-A);
    if (temp1 > temp2)
      F = temp1;
    else
      F = temp2;
    h1 = divisor_real(nFI);
    nFI /= h1;
    nFI.bigintify(h);
    
    val.assign(h);
    inc(val);
    temp = bigfloat(h) + abs(nFI - bigfloat(h));
    val = log(val/temp);
  }
    
  /* compute sufficiently accurate estimate of L(1,X) to prove correctness */
  while (A >= val) {
    Q += 5000;
    nFI.assign(estimate_L1(Q));
    nFI *= sqrt(bigfloat(Delta)) / (R*bigfloat(h1 << 1));
    temp.bigintify(h);

    A = estimate_L1_error(Q);
    val.assign(h);
    inc(val);
    temp = bigfloat(h) + abs(nFI - bigfloat(h));
    val = log(val/temp);
  }

  h *= h1;
}



void
quadratic_order::set_transformations()
{
  bigint_matrix Imat,temp;
  lidia_size_t i,j,dif,numFB,numCL;

  numFB = fact_base.size();
  numCL = CL.size();
  dif = numFB - numCL;

  /* reduce transformation matrix */
  for (i=dif; i<numFB; ++i)
    for (j=0; j<numFB; ++j)
      U.sto(i,j,U.member(i,j) % CL.member(i-dif));

  /* compute inverse */
  Imat.resize(numFB,numFB);
  Imat.diag(1,0);
  UINV.reginvimage(U,Imat);
  UINV.set_no_of_rows(numFB);

  if (dif) {
    /* remove unneccessary rows of U and cols of UINV */
    temp.resize(numCL,numFB);
    Imat.resize(dif,numFB);
    U.split_v(Imat,temp);
    U = temp;

    temp.resize(numFB,numCL);
    Imat.resize(numFB,dif);
    UINV.split_h(Imat,temp);
    UINV = temp;
  }
}



void
quadratic_order::cgBJT_imag()
{
  long *pptr;
  bigint y,usqr,det,Bjj;
  long s,u,r,q,upper,Bj;
  lidia_size_t i,j,k,crank,numRpr,numQ,curr_index,idx;
  qi_class G,A,B,C,D,E,HI,Gq,GBj;
  bigint_matrix Bmat,junk;
  base_vector <long> Rvec,Qvec;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> QT,RT;
  bigfloat temp,hstar,nFI;

  qi_class::set_current_order(*this);

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

  /* initialize sets R and Q */
  temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
  temp.longify(upper);

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

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

  crank = 0;
  det.assign_one();
  h.assign_one();
  pptr = PL.primes;

  /* compute estimate of h */
  Q = get_optimal_Q();
  nFI.assign(estimate_L1(Q));
  nFI *= sqrt(bigfloat(-Delta)) / Pi();
  if (Delta == -3)
    nFI *= bigfloat(3.0);
  if (Delta == -4)
    nFI *= bigfloat(2.0);
  hstar = nFI / sqrt(bigfloat(2));

  /* while h < hstar, we only have a subgroup */
  while (bigfloat(h) < hstar) {
    /* get prime ideal */
    while (!prime_ideal(G,bigint(*pptr)))
      ++pptr;
    fact_base[crank] = G;

    s = 1;

    /* compute initial step-width */
    temp.assign(floor(sqrt(nFI)));
    temp.longify(u);
    if (u == 0) u = 2;
    if ((u & 1) == 1)
      ++u;
    y.assign(u);

    A.assign_one();
    power_imag(B,G,u);
    C.assign(B);
    HI.assign(inverse(G));

    numRpr = RT.no_of_elements();
    curr_index = numRpr;
    numQ = QT.no_of_elements();

    /* if G is one, we have no new information */
    if (G.is_one())
      Bjj.assign_one();
    else
      Bjj.assign_zero();

    /* check whether current ideal is in previously generated subgroup */
    if ((Bjj.is_zero()) && (crank > 0)) {
      for (i=0; i<numQ; ++i) {
        D.assign(QT[i].get_A());
        multiply_imag(E,D,G);
        Inode = RT.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,HI);

        if ((s == 1) && (r > 1)) {
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);

            /* check if relation already found */
            Inode = QT.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 {
              RT.hash(ideal_node(E,curr_index));
              ++curr_index;
            }
          }
          if (!Bjj.is_zero())
            break;
        }
        else {
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);

            RT.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(QT[i].get_A());
          multiply_imag(D,E,B);

          Inode = RT.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()) {
      h *= Bjj;
      divide(nFI,nFI,bigfloat(Bjj));
      ++crank;
    }

    if (bigfloat(h) < hstar) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

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

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

        /* compute new Q */
        numQ = QT.no_of_elements();
        curr_index = numQ;
        power_imag(GBj,G,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(QT[k].get_A());
            multiply_imag(D,E,Gq);
            QT.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_imag(Gq,Gq,GBj);
          ++Qvec[crank-1];
        }
      }

      ++pptr;
    }
  }

  /* compute structure */
  if (h == 1)
    CL[0] = 1;
  else {
    Bmat.snf_havas(U,junk);
    i = 0;
    for (j=0; j<crank; ++j) {
      Bjj.assign(Bmat.member(j,j));
      if (!Bjj.is_one()) {
        CL[i] = Bjj;
        ++i;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }
}



void
quadratic_order::cgBJT_real()
{
  long *pptr;
  bigint y,usqr,det,Bjj,ht;
  long s,u,r,q,upper,Bj;
  lidia_size_t i,j,k,crank,numRpr,numQ,curr_index,idx,numRreps;
  qi_class G,A,Aprime,B,C,D,E,HI,Gq,GBj;
  qi_class_real Areal,F,Gstep,*FS;
  bigint_matrix Bmat,junk;
  base_vector <long> Rvec,Qvec;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> QT,RT;
  base_vector <qi_class> Rreps;
  bigfloat temp,hstar,nFI,sqReg,val,Aval,GStepWidth;

  qi_class::set_current_order(*this);

  Q = get_optimal_Q_cnum();
  nFI.assign(estimate_L1(Q));

  /* compute regulator, if needed */
  if (R.is_zero())
    regulator();
  
  crank = 0;
  det.assign_one();
  h.assign_one();
  pptr = PL.primes;

  /* compute estimate of h */
  nFI *= sqrt(bigfloat(Delta)) / (R*bigfloat(2.0));
  nFI.bigintify(ht);
  hstar = nFI / sqrt(bigfloat(2));

  Aval = estimate_L1_error(Q);
  val.assign(ht);
  inc(val);
  temp = bigfloat(ht) + abs(nFI - bigfloat(ht));
  val = log(val/temp);
  if ((Aval < val) && (ht < bigint(8)) && (ht != bigint(4)) && (ht != bigint(6))) {
    h.assign(ht);
    CL[0] = h;
    /* find generator */
    while (!prime_ideal(G,*pptr))
      ++pptr;
    while ((h > 1) && (G.is_principal())) {
      ++pptr;
      while (!prime_ideal(G,*pptr))
        ++pptr;
    }
    fact_base[0] = G;
    U.sto(0,0,bigint(1));
    UINV.sto(0,0,bigint(1));
    gens[0] = G;
  }

  if (bigfloat(h) < hstar) {
    Rvec.set_mode(EXPAND);
    Qvec.set_mode(EXPAND);
    Rreps.set_mode(EXPAND);
    Rvec.set_size(0);
    Qvec.set_size(0);
    Rreps.set_size(0);
 
    /* initialize sets R and Q */
    temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
    temp.longify(upper);

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

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

    Rreps[0] = B;

    sqrt(sqReg,R);
    F.assign_one();
    Gstep.assign(nearest(F,sqReg));
    if (Gstep.get_distance() > sqReg)
      Gstep.inverse_rho();
    F.assign(prin_list.last_entry());
    if (Gstep.is_one()) {
      while (!F.is_one()) {
        F.rho();
        prin_list.hash(F);
      }
    }
    else  {
      while ((F.get_distance() < Gstep.get_distance()) && (!F.is_one())) {
        F.rho();
        prin_list.hash(F);
      }
      if (!F.is_one()) {
        F.rho();
        prin_list.hash(F);
      }
      if (F.is_one())
        Gstep.assign(F);
    }
    floor(GStepWidth,Gstep.get_distance());
  }

  /* while h < hstar, we only have a subgroup */
  while (bigfloat(h) < hstar) {
    /* get prime ideal */
    while (!prime_ideal(G,bigint(*pptr)))
      ++pptr;
    fact_base[crank] = G;

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

    A.assign_one();
    B.assign(G);
    C.assign(B);
    HI.assign(inverse(G));

    numQ = QT.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(QT[i].get_A());
        multiply_real(E,D,G);

        if (Gstep.is_one()) {
          FS = prin_list.search(qi_class_real(E));
          if (FS)
            Bjj.assign_one();
          else {
            Inode = RT.search(ideal_node(E,0));
            if (Inode)
              Bjj.assign_one();
          }
        }
        else {
          F.assign(E,0.0);
          temp.assign_zero();
          while ((F.get_distance() <= R) && (Bjj.is_zero())) {
            FS = prin_list.search(F);
            if (FS)
              Bjj.assign_one();
            else {
              Inode = RT.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,HI);

        for (k=0; k<numRreps; ++k) {
          D.assign(Rreps[k]);
          multiply_real(E,D,A);
          Rreps[curr_index] = E;
          RT.hash(ideal_node(E,curr_index));

          if (Gstep.is_one()) {
            /* store each cycle of ideals */
            apply_rho(Aprime,E);
            while (!Aprime.is_equal(E)) {
              RT.hash(ideal_node(Aprime,curr_index));
              Aprime.rho();
            }
          }
          else {
            /* store ideals with distance < sqReg */
            Areal.assign(E);
            while (Areal.get_distance() < sqReg) {
              Areal.rho();
              RT.hash(ideal_node(qi_class(Areal),curr_index));
            }
            Areal.rho();
            RT.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(QT[i].get_A());
          multiply_real(E,D,B);

          if (Gstep.is_one()) {
            FS = 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 = RT.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() <= R) && (Bjj.is_zero())) {
              FS = 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 = RT.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()) {
      h *= Bjj;
      ++crank;
    }

    if (bigfloat(h) < hstar) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

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

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

        /* compute new Q */
        numQ = QT.no_of_elements();
        curr_index = numQ;
        power_real(GBj,G,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(QT[k].get_A());
            multiply_real(D,E,Gq);
            QT.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_real(Gq,Gq,GBj);
          ++Qvec[crank-1];
        }
      }

      ++pptr;
    }
  }

  /* compute structure */
  if (h == 1)
    CL[0] = 1;
  else {
    Bmat.snf_havas(U,junk);
    i = 0;
    for (j=0; j<crank; ++j) {
      Bjj.assign(Bmat.member(j,j));
      if (!Bjj.is_one()) {
        CL[i] = Bjj;
        ++i;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }

}



void
quadratic_order::cgs_imag()
{
  long *pptr;
  bigint y,usqr,det,Bjj,ht;
  long s,u,r,q,upper,Bj;
  lidia_size_t i,j,k,crank,numRpr,numR,numQ,curr_index,idx;
  bigint_matrix Bmat,junk;
  base_vector <long> Rvec,Qvec;
  qi_class G,A,B,Bcomp,C,D,E,HI,Gq,GBj,CINV;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> QT,RT;
  bigfloat temp,temp1,temp2,hstar,nFI,F;
  rational_factorization htfact;

  qi_class::set_current_order(*this);

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

  /* initialize sets R and Q */
  temp = ceil(power(bigfloat(-Delta),bigfloat(0.25)));
  temp.longify(upper);

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

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

  crank = 0;
  det.assign_one();
  h.assign_one();
  pptr = PL.primes;

  Q = get_optimal_Q_cnum();
  nFI.assign(estimate_L1(Q));
  nFI *= sqrt(bigfloat(-Delta)) / Pi();
  if (Delta == -3)
    nFI *= bigfloat(3.0);
  if (Delta == -4)
    nFI *= bigfloat(2.0);
  hstar = nFI / sqrt(bigfloat(2));

  temp = estimate_L1_error(Q);
  temp1 = exp(temp) - bigfloat(1);
  temp2 = bigfloat(1) - exp(-temp);
  if (temp1 > temp2)
    F = temp1;
  else
    F = temp2;

  while (bigfloat(h) < hstar) {
    /* get prime ideal */
    while (!prime_ideal(G,bigint(*pptr)))
      ++pptr;

    fact_base[crank] = G;

    numRpr = RT.no_of_elements();
    curr_index = numRpr;
    numQ = QT.no_of_elements();

    s = 1;

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

    if (Bjj.is_zero()) {
      HI.assign(inverse(G));
      A.assign_one();

      nFI.bigintify(ht);
      temp = sqrt(nFI*F + abs(nFI - bigfloat(ht)));
      temp.longify(u);

      if (u == 0)
        u = 2;
      if ((u & 1) == 1)
        ++u;
      y.assign_zero();

      power_imag(B,G,ht);

      if (crank == 0) {
        /* check whether G^ht = (1) */
        if (B.is_one()) {
          htfact.assign(ht);
          htfact.factor();
          Bjj = G.order_mult(ht,htfact);
          Bmat.sto(0,0,Bjj);
        }
      }
      else {
        /* check whether G^ht is in currently generated subgroup */
        for (i=0; i<numQ; ++i) {
          D.assign(QT[i].get_A());
          multiply_imag(E,D,B);

          if (E.is_one())
            r = 0;
          else {
            Inode = RT.search(ideal_node(E,0));
            if (Inode)
              r = Inode->get_index();
            else
              r = -1;
          }
          if (r >= 0) {
            /* determine smallest divisor of ht */
            q = i;
            Bjj = exact_power(ht,G,RT,QT,numRpr,r,q);
            if (!Bjj.is_one()) 
              decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
            break;
          }
        }
      }

      if (Bjj.is_zero()) {
        power_imag(C,G,u);
        CINV = inverse(C);
        Bcomp.assign(B);
      }
      else
        u = 0;
    }

    while (Bjj.is_zero()) {
      /* compute more baby steps */
      for (r=s; r<=u; ++r) {
        multiply_imag(A,A,HI);

        if (s == 1) {
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);

            /* check if relation already found */
            Inode = QT.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 {
              RT.hash(ideal_node(E,curr_index));
              ++curr_index;
            }
          }
          if (!Bjj.is_zero())
            break;
        }
        else {
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);

            RT.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(QT[i].get_A());
          multiply_imag(D,E,Bcomp);

          Inode = RT.search(ideal_node(D,0));
          if (Inode) {
            r = Inode->get_index();
            subtract(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            if (!Bjj.is_zero()) {
              /* determine smallest divisor of Bjj */
              ht.assign(Bjj);
              if (crank == 0) {
                htfact.assign(Bjj);
                htfact.factor();
                Bjj = G.order_mult(Bjj,htfact);
                Bmat.sto(0,0,Bjj);
              }
              else {
                q = i;
                r %= numRpr;
                Bjj = exact_power(Bjj,G,RT,QT,numRpr,r,q);
                if (!Bjj.is_one()) 
                  decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
              }
              break;
            }
          }

          multiply_imag(D,E,B);
          Inode = RT.search(ideal_node(D,0));
          if (Inode) {
            r = Inode->get_index();
            add(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            /* determine smallest divisor of Bjj */
            ht.assign(Bjj);
            if (crank == 0) {
              htfact.assign(Bjj);
              htfact.factor();
              Bjj = G.order_mult(Bjj,htfact);
              Bmat.sto(0,0,Bjj);
            }
            else {
              q = i;
              r %= numRpr;
              Bjj = exact_power(Bjj,G,RT,QT,numRpr,r,q);
              if (!Bjj.is_one()) 
                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);
          multiply_imag(Bcomp,Bcomp,CINV);
        }
      }

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

    if (!Bjj.is_one()) {
      h *= Bjj;
      divide(nFI,nFI,bigfloat(Bjj));
      ++crank;
    }

    if (bigfloat(h) < hstar) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

      /* compute new R' (remove entries with too large exponents) */
      det *= Bj;
      det.longify(upper);
      idx = (lidia_size_t) upper;
      numR = RT.no_of_elements();
      if (idx < numR) {
        /* removing entries */
        for (i=numR-1; i>=idx; --i)
          RT.remove_from(i);
      }
      else {
        /* adding entries */
        idx = (lidia_size_t) u+1;
        for (r=idx; r<Bj; ++r) {
          multiply_imag(A,A,HI);
          for (k=0; k<numRpr; ++k) {
            D.assign(RT[k].get_A());
            multiply_imag(E,D,A);
            RT.hash(ideal_node(E,curr_index));
            ++curr_index;
          }
        }
      }

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

        /* compute new Q */
        numQ = QT.no_of_elements();
        curr_index = numQ;
        power_imag(GBj,G,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(QT[k].get_A());
            multiply_imag(D,E,Gq);
            QT.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_imag(Gq,Gq,GBj);
          ++Qvec[crank-1];
        }
      }

      ++pptr;
    }
  }

  /* compute structure */
  if (h == 1)
    CL[0] = 1;
  else {
    Bmat.snf_havas(U,junk);
    i = 0;
    for (j=0; j<crank; ++j) {
      Bjj.assign(Bmat.member(j,j));
      if (!Bjj.is_one()) {
        CL[i] = Bjj;
        ++i;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }
}




void
quadratic_order::cgs_real()
{
  long *pptr;
  bigint y,usqr,det,Bjj,ht;
  long s,u,r,q,upper,Bj;
  lidia_size_t i,j,k,crank,numRpr,numQ,curr_index,idx,numRreps;
  bigint_matrix Bmat,junk;
  base_vector <long> Rvec,Qvec;
  qi_class G,A,B,Bcomp,C,D,E,HI,Gq,GBj,CINV,Aprime;
  qi_class_real Areal,FIDL,Gstep,*FS;
  ideal_node *Inode;
  indexed_hash_table <ideal_node> QT,RT;
  rational_factorization htfact;
  base_vector <qi_class> Rreps;
  bigfloat temp,temp1,temp2,hstar,nFI,sqReg,Aval,val,GStepWidth;

  qi_class::set_current_order(*this);

  Q = get_optimal_Q_cnum();
  nFI.assign(estimate_L1(Q));

  /* compute regulator, if needed */
  if (R.is_zero())
    regulator();

  crank = 0;
  det.assign_one();
  h.assign_one();
  pptr = PL.primes;

  /* compute estimate of h */
  nFI *= sqrt(bigfloat(Delta)) / (R*bigfloat(2.0));
  hstar = nFI / sqrt(bigfloat(2));
  nFI.bigintify(ht);

  Aval = estimate_L1_error(Q);
  val.assign(ht);
  inc(val);
  temp = ht + abs(nFI - bigfloat(ht));
  val = log(val/temp);
  if ((Aval < val) && (ht < bigint(8)) && (ht != bigint(4)) && (ht != bigint(6))) {
    h.assign(ht);
    CL[0] = h;
    /* find generator */
    while (!prime_ideal(G,*pptr))
      ++pptr;
    while ((h > 1) && (G.is_principal())) {
      ++pptr;
      while (!prime_ideal(G,*pptr))
        ++pptr;
    }
    fact_base[0] = G;
    U.sto(0,0,bigint(1));
    UINV.sto(0,0,bigint(1));
    gens[0] = G;
  }

  if (bigfloat(h) < hstar) {
    Rvec.set_mode(EXPAND);
    Qvec.set_mode(EXPAND);
    Rreps.set_mode(EXPAND);
    Rvec.set_size(0);
    Qvec.set_size(0);
    Rreps.set_size(0);
 
    /* initialize sets R and Q */
    temp = ceil(power(bigfloat(Delta),bigfloat(0.2)));
    temp.longify(upper);

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

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

    Rreps[0] = B;

    sqrt(sqReg,R);
    FIDL.assign_one();
    Gstep.assign(nearest(FIDL,sqReg));
    if (Gstep.get_distance() > sqReg)
      Gstep.inverse_rho();
    FIDL.assign(prin_list.last_entry());
    if (Gstep.is_one()) {
      while (!FIDL.is_one()) {
        FIDL.rho();
        prin_list.hash(FIDL);
      }
    }
    else {
      while ((FIDL.get_distance() < Gstep.get_distance()) && (!FIDL.is_one())) {
        FIDL.rho();
        prin_list.hash(FIDL);
      }
      if (!FIDL.is_one()) {
        FIDL.rho();
        prin_list.hash(FIDL);
      }
      if (FIDL.is_one())
        Gstep.assign(FIDL);
    }
    floor(GStepWidth,Gstep.get_distance());
  }

  while (bigfloat(h) < hstar) {
    /* get prime ideal */
    while (!prime_ideal(G,bigint(*pptr)))
      ++pptr;

    fact_base[crank] = G;

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

    A.assign_one();
    HI.assign(inverse(G));

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

    Bjj.assign_zero();

    HI.assign(inverse(G));
    A.assign_one();

    power_real(B,G,ht);

    if (crank == 0) {
      /* check whether G^ht = (1) */
      if (Gstep.is_one()) {
        FS = prin_list.search(qi_class_real(B));
        if (FS) {
          htfact.assign(ht);
          htfact.factor();
          Bjj = G.order_mult(ht,htfact);
          Bmat.sto(0,0,Bjj);
        }
      }
      else {
        FIDL.assign(B,0.0);
        temp.assign_zero();
        while ((FIDL.get_distance() <= R) && (Bjj.is_zero())) {
          FS = prin_list.search(FIDL);
          if (FS) {
            htfact.assign(ht);
            htfact.factor();
            Bjj = G.order_mult(ht,htfact);
            Bmat.sto(0,0,Bjj);
          }
          else {
            add(temp,temp,GStepWidth);
            multiply_real(FIDL,FIDL,Gstep);
            while (FIDL.get_distance() <= temp)
              FIDL.rho();
            while (FIDL.get_distance() > temp)
              FIDL.inverse_rho();
          }
        }
      }
    }
    else {
      /* check whether G^ht is in currently generated subgroup */
      r = -1;
      for (i=0; i<numQ; ++i) {
        D.assign(QT[i].get_A());
        multiply_real(E,D,B);

        if (Gstep.is_one()) {
          FS = prin_list.search(qi_class_real(E));
          if (FS)
            r = 0;
          else {
            Inode = RT.search(ideal_node(E,0));
            if (Inode)
              r = Inode->get_index();
            else
              r = -1;
          }
        }
        else {
          FIDL.assign(E,0.0);
          temp.assign_zero();
          while ((FIDL.get_distance() <= R) && (r < 0)) {
            FS = prin_list.search(FIDL);
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(qi_class(FIDL),0));
              if (Inode)
                r = Inode->get_index();
              else {
                add(temp,temp,GStepWidth);
                multiply_real(FIDL,FIDL,Gstep);
                while (FIDL.get_distance() <= temp)
                  FIDL.rho();
                while (FIDL.get_distance() > temp)
                  FIDL.inverse_rho();
              }
            }
          }
        }

        if (r >= 0) {
          /* determine smallest divisor of ht */
          q = i;
          Bjj = exact_power_real(ht,G,RT,QT,numRpr,r,q,Gstep);
          if (!Bjj.is_one()) 
            decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
          break;
        }
      }
    }

    if (Bjj.is_zero()) {
      C.assign(G);
      CINV = inverse(C);
      Bcomp.assign(B);
    }
    else
      u = 0;

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

          if (Gstep.is_one()) {
            /* store each cycle of ideals */
            apply_rho(Aprime,E);
            while (!Aprime.is_equal(E)) {
              RT.hash(ideal_node(Aprime,curr_index));
              Aprime.rho();
            }
          }
          else {
            /* store ideals with distance < sqReg */
            Areal.assign(E);
            while (Areal.get_distance() < sqReg) {
              Areal.rho();
              RT.hash(ideal_node(qi_class(Areal),curr_index));
            }
            Areal.rho();
            RT.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) {
          E.assign(QT[i].get_A());
          multiply_real(D,E,Bcomp);
          r = -1;

          if (Gstep.is_one()) {
            FS = prin_list.search(qi_class_real(D));
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(D,0));
              if (Inode)
                r = Inode->get_index();
            }
          }
          else {
            FIDL.assign(D,0.0);
            temp.assign_zero();
            while ((FIDL.get_distance() <= R) && (r < 0)) {
              FS = prin_list.search(FIDL);
              if (FS)
                r = 0;
              else {
                Inode = RT.search(ideal_node(qi_class(FIDL),0));
                if (Inode)
                  r = Inode->get_index();
                else {
                  add(temp,temp,GStepWidth);
                  multiply_real(FIDL,FIDL,Gstep);
                  while (FIDL.get_distance() <= temp)
                    FIDL.rho();
                  while (FIDL.get_distance() > temp)
                    FIDL.inverse_rho();
                }
              }
            }
          }

          if (r >= 0) {
            subtract(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            if (!Bjj.is_zero()) {
              /* determine smallest divisor of Bjj */
              ht.assign(Bjj);
              if (crank == 0) {
                htfact.assign(Bjj);
                htfact.factor();
                Bjj = G.order_mult(Bjj,htfact);
                Bmat.sto(0,0,Bjj);
              }
              else {
                q = i;
                r %= numRpr;
                Bjj = exact_power_real(Bjj,G,RT,QT,numRpr,r,q,Gstep);
                if (!Bjj.is_one()) 
                  decode_vector(Bmat,Bjj,r,q,Rvec,Qvec,numRpr,numQ);
              }
              break;
            }
          }

          multiply_real(D,E,B);
          r = -1;

          if (Gstep.is_one()) {
            FS = prin_list.search(qi_class_real(D));
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(D,0));
              if (Inode)
                r = Inode->get_index();
            }
          }
          else {
            FIDL.assign(D,0.0);
            while ((FIDL.get_distance() <= R) && (r < 0)) {
              FS = prin_list.search(FIDL);
              if (FS)
                r = 0;
              else {
                Inode = RT.search(ideal_node(qi_class(FIDL),0));
                if (Inode)
                  r = Inode->get_index();
                else {
                  add(temp,temp,GStepWidth);
                  multiply_real(FIDL,FIDL,Gstep);
                  while (FIDL.get_distance() <= temp)
                    FIDL.rho();
                  while (FIDL.get_distance() > temp)
                    FIDL.inverse_rho();
                }
              }
            }
          }

          if (r >= 0) {
            add(Bjj,ht,y);
            add(Bjj,Bjj,(r/numRpr));
            /* determine smallest divisor of Bjj */
            ht.assign(Bjj);
            if (crank == 0) {
              htfact.assign(Bjj);
              htfact.factor();
              Bjj = G.order_mult(Bjj,htfact);
              Bmat.sto(0,0,Bjj);
            }
            else {
              q = i;
              r %= numRpr;
              Bjj = exact_power_real(Bjj,G,RT,QT,numRpr,r,q,Gstep);
              if (!Bjj.is_one()) 
                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_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);
          }
        }
      }

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

    if (!Bjj.is_one()) {
      h *= Bjj;
      divide(ht,ht,Bjj);
      ++crank;
    }

    if (bigfloat(h) < hstar) {
      temp = ceil(sqrt(bigfloat(Bjj)));
      temp.longify(Bj);

      /* compute new R' (remove entries with too large exponents) */
      det *= Bj;
      det.longify(upper);
      idx = (lidia_size_t) upper;
      if (idx < curr_index) {
        numRpr = RT.no_of_elements();
        /* removing entries */
        i = numRpr-1;
        while (RT[i].get_index() >= idx) {
          RT.remove_from(i);
          --i;
        }
        Rreps.set_size((lidia_size_t) idx);
      }
      else {
        /* adding entries */
        idx = (lidia_size_t) u+1;
        for (r=idx; r<Bj; ++r) {
          multiply_real(A,A,HI);
          for (k=0; k<numRreps; ++k) {
            D.assign(Rreps[k]);
            multiply_real(E,D,A);
            Rreps[curr_index] = E;
            RT.hash(ideal_node(E,curr_index));

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

            ++curr_index;
          }
        }
      }


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

        /* compute new Q */
        numQ = QT.no_of_elements();
        curr_index = numQ;
        power_real(GBj,G,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(QT[k].get_A());
            multiply_real(D,E,Gq);
            QT.hash(ideal_node(D,curr_index));
            ++curr_index;
          }
          multiply_real(Gq,Gq,GBj);
          ++Qvec[crank-1];
        }
      }

      ++pptr;
    }
  }

  /* compute structure */
  if (h == 1)
    CL[0] = 1;
  else {
    Bmat.snf_havas(U,junk);
    i = 0;
    for (j=0; j<crank; ++j) {
      Bjj.assign(Bmat.member(j,j));
      if (!Bjj.is_one()) {
        CL[i] = Bjj;
        ++i;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }
}



/* FIX */
void
quadratic_order::cgh_imag()
{
  qi_class::set_current_order(*this);
}



/* FIX */
void
quadratic_order::cgh_real()
{
  qi_class::set_current_order(*this);
}



inline int str_length(char *s, char c)
{
  // computes the number of char c
  // in string s

  int l = 0;

  while (*s++)
    if (*s == c)       l++;

  return(l);
}



static void
qo_reverse(char *s)
{
        int             c, i, j;

        for (i = 0, j = strlen(s) - 1; i < j; i++, j--) {
                c = *(s + i);
                *(s + i) = *(s + j);
                *(s + j) = c;
        }
}




static void
qo_itoa(unsigned long n, char *s, int base)
{
        unsigned long   i, digit, dig16;

        if (base > 16)
          lidia_error_handler("qo_itoa","itoa - maxbase = 16");

        i = 0;
        do {
                digit = n % base + '0';
                if (base == 16) {
                        dig16 = digit - '0';
                        switch (dig16) {
                        case 10:
                        case 11:
                        case 12:
                        case 13:
                        case 14:
                        case 15:
                                s[i++] = (char) 'a' + dig16 - 10;
                                break;
                      default:
                                s[i++] = (char) digit;
                                break;
                        }
                } else
                        s[i++] = (char) digit;
        } while ((n /= base) > 0);
        s[i] = '\0';
        qo_reverse(s);
}

// MM

LIDIA_SIGNAL_FUNCTION(qo_stop_it)
   {
     qo_transform_relations(1);
     unlink(quadratic_order::FAKM);
     unlink(quadratic_order::NEWR);
     lidia_error_handler("quadratic_order","class_group_subexp - the \
program was interrupted");
   }



void
qo_get_name(char *s)
{
  FILE *fp;
  static int first_one=1;
  static char *hilf;   /* static */
  char *physical;

  physical= (char*) malloc(35 * sizeof(char));

  if (first_one) {
    first_one = 0;
    hilf=(char*) malloc(19 * sizeof(char));

    if ((fp=fopen("/tmp/FAKT", "w"))==NULL) {
      strcpy(hilf,"./");
      qo_itoa(getpid(),physical,10);
      strcat(hilf,physical);
    }
    else {
      fclose(fp); unlink("/tmp/FAKT");
      strcpy(hilf,"/tmp/");

      qo_itoa(getpid(),physical,10);
      strcat( hilf,physical);
    }
  }

  if (!strcmp(s,"FAKM")) {
    strcpy(physical,hilf);
    strcat(physical,"QO_FAKM");
    strcpy(quadratic_order::FAKM,physical);
  }
  if (!strcmp(s,"NEWR")) {
    strcpy(physical,hilf);
    strcat(physical,"QO_NEWR");
    strcpy(quadratic_order::NEWR,physical);
  }

  free(physical);
}



bool
qo_transform_relations(bool back)
{
  static int position=0;
  int i = 0;
  char *p, *pp, zeile[1000];
  FILE *fpin,*fpout;

  if (back) { position=0; return(false); }

  fpin  = fopen(quadratic_order::FAKM,"r");
  fpout = fopen(quadratic_order::NEWR,"w");

  if (!fpin || !fpout) {
    if (fpin)
      fclose(fpin);
    if (fpout)
      fclose(fpout);
    unlink(quadratic_order::FAKM);
    unlink(quadratic_order::NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order", "qo_transform_relations() - can't \
open FAKTORENMATRIX or NEWRELATIONS");
  }

  fseek(fpin, position, 0);

  while (fgets(zeile, 1000, fpin)) {
    if ((pp = (char*) strchr(zeile, ':'))) {
      pp += 2;

      fprintf(fpout, "%d %d 0 ", i++, str_length(pp, ' '));
      p = (char*) strtok(pp, " \n");

      while (p != NULL) {
        fprintf(fpout, "%d ", atoi(p));
        p = (char*) strtok(NULL, " \n");
      }
      fprintf(fpout, "\n");
    }
  }

  position = (unsigned int) ftell(fpin);

  fclose(fpin);
  fclose(fpout);
  return(false);
}



void
read_relation_matrix(bigint_matrix & A)
{
  char *pp, zeile[1000];
  int i,j,k,nitems,val;
  lidia_size_t rows,cols;
  base_vector <bigint> cvec;
  FILE *fpin;

  fpin = fopen(quadratic_order::NEWR,"r");
  if (!fpin) {
    unlink(quadratic_order::FAKM);
    unlink(quadratic_order::NEWR);
    lidia_error_handler("quadratic_order", "read_relation_matrix() - can't \
open NEWRELATIONS");
  }

  rows = A.get_no_of_rows();
  cols = A.get_no_of_columns();
  if (cols == 1)
    cols = 0;

  cvec.set_mode(EXPAND);

  while (fgets(zeile, 1000, fpin)) {
    /* read row number, number of entries, and a zero */
    sscanf(zeile,"%d%d%d",&i,&nitems,&k);
    pp = (char *) strchr(zeile, ' ');
    ++pp;
    ++pp = (char *) strchr(pp,' ');
    ++pp = (char *) strchr(pp,' ');

    cvec.reset();
    cvec.set_size(rows);
    nitems >>= 1;
    for (k=0; k<nitems; ++k) {
      sscanf(pp,"%d%d",&val,&j);
      cvec[j] = bigint(val);
      ++pp = (char *) strchr(pp,' ');
      ++pp = (char *) strchr(pp,' ');
    }
    A.set_no_of_columns(cols+1);
    A.sto_column_vector(cvec,rows,cols);
    ++cols;
  }

  fclose(fpin);
}



inline void compute_roots(quadratic_form F, int *FB, int *SQRTkN, int *START1, int *START2, int SIEBSTART, sort_vector <int> & Q_primes, sort_vector <int> & D_primes)
{
  bigint bigtemp;
  int *fbp, p, AINV, A2, SIEBS, i1, i2, temp1, temp2;
  lidia_size_t n;

  n = 2;
  fbp = &FB[2];
  p = *fbp;

  while (p) {
    SIEBS = SIEBSTART % p;

    if (Q_primes.linear_search(p,i1)) {
      // p divides A - root = -c * b^-1 (mod p)

      temp1 = (int) remainder(-F.get_c(),p);
      temp2 = (int) remainder(F.get_b(),p);
      temp2 = invert(temp2, p);

      bigtemp = bigint(temp1) * bigint(temp2);
      temp1 = (int) remainder(bigtemp,p);
      START1[n] = (int) (temp1 + SIEBS) %  p;
      if (START1[n] < 0)
        START1[n] += p;
      START2[n] = START1[n];
    }

    else {
      // compute (2A)^-1
      A2 = (int) remainder(F.get_a(),p);
      A2 <<= 1;
      A2 %= p;
      AINV = invert(A2,p);

      if ((D_primes.bin_search(p,i1)) || (D_primes.bin_search(-p,i2))) {
        // p divides Delta - root = -b * (2a)^-1 (mod p)

        temp1 = (int) remainder(-F.get_b(),p);
        bigtemp = bigint(temp1) * bigint(AINV);
        temp1 = (int) remainder(bigtemp,p);
        START1[n] = (int) (temp1 + SIEBS) % p;
        if (START1[n] < 0)
          START1[n] += p;
        START2[n] = START1[n];
      }
      else {
        // temp1 = -b + sqrt(Delta)
        // temp2 = -b - sqrt(Delta)

        temp1 = (int) remainder(-F.get_b(),p);
        temp2 = temp1 - SQRTkN[n];
        temp1 += SQRTkN[n];

        bigtemp = bigint(temp1) * bigint(AINV);
        temp1 = (int) remainder(bigtemp,p);
        START1[n] = (int) (temp1 + SIEBS) % p;
        if (START1[n] < 0)
          START1[n] += p;

        bigtemp = bigint(temp2) * bigint(AINV);
        temp2 = (int) remainder(bigtemp,p);
        START2[n] = (temp2 + SIEBS) % p;
        if (START2[n] < 0)
          START2[n] += p;
      }
    }

    ++fbp;
    p = *fbp;
    ++n;
  }
}



inline void qo_sieve_interval(int *FB, SIEBTYP *LOGP, int *START1, int *START2, SIEBTYP *sieb, SIEBTYP *ende, int M, int *CANDIDATE)
{
  register int p, l = 2, *fbp, *lsieb= (int*) sieb;
  register SIEBTYP logp;
  register SIEBTYP *begin;
  register int x, counter=0, M_2=M<<1;

  memset(sieb, 0, (M_2) * sizeof(SIEBTYP));
  fbp = &FB[2];

  while ( (p = *fbp++) != 0 ) {
    logp = LOGP[l];

    if ( START1[l] == START2[l] ) {
      begin = sieb + START1[l];
      for(;;) {
        if (begin <= ende) {
          (*begin) += logp;
          begin += p;
        }
        else {
          START1[l] = START2[l] = (begin - ende -1);
          break;
        }
      }
    }
    else {
      begin = sieb + START1[l];
      for(;;) {
        if (begin <= ende) {
          (*begin) += logp;
          begin += p;
        }
        else {
          START1[l] = (begin - ende -1);
          break;
        }
      }
      begin = sieb + START2[l];
      for(;;) {
        if (begin <= ende) {
          (*begin) += logp;
          begin += p;
        }
        else {
          START2[l] = (begin - ende -1);
          break;
        }
      }
    }
    l++;
  }

  l=0;
  while (l<M_2) {
    // check whether at least one of four consequent sieve entries
    // is a candidate
    if ((p= (*lsieb))& (0x80808080)) {
      x=l;

#if WORDS_BIGENDIAN != 1
// on PCs the highest byte is the lowest
      if (p & (0x00000080)) {
#else
      if (p & (0x80000000)) {
#endif
        CANDIDATE[counter++] = x;
        sieb[x] = 0;
      }
      else {

#if WORDS_BIGENDIAN != 1
        if (p & (0x00008000)) {
#else
        if (p & (0x00800000)) {
#endif
          x++;
          CANDIDATE[counter++] = x;
          sieb[x] = 0;
        }
        else {

#if WORDS_BIGENDIAN != 1
          if (p & (0x00800000)) {
#else
          if (p & (0x00008000)) {
#endif
            x+= 2;
            CANDIDATE[counter++] = x;
            sieb[x] = 0;
          }
          else {

#if WORDS_BIGENDIAN != 1
            if (p & (0x80000000)) {
#else
            if (p & (0x00000080)) {
#endif
              l+=3;
              CANDIDATE[counter++] = l;
              lsieb++;
              l++;
            }
          }
        }
      }
    }
    else {
      lsieb++;
      l+=4;
    }
  }

  CANDIDATE[counter] = 0;
}



inline int test_candidates_imag(quadratic_form & F, int *FB, base_vector < qi_class> & f_base, int *START1, int *START2, char *faktor, int M, sort_vector <int> & Q_primes, sort_vector <int> & Q_exp, sort_vector <int> & D_primes, int *CANDIDATE, hash_table <bigint> & HT, sort_vector <int> & used_primes, int needed, int force_prime)
{
  matrix_GL2Z UT;
  quadratic_form G;
  bigint u,v,H,Q_von_x,temp;
  int div2,fak_i,vorber,x,p,divides,counter,p2,B2p,Bpr2p;
  long rest;
  FILE  *fp;
  char Hstring[150];
  short small=0;
  lidia_size_t i,k,idx;
  int i1,i2;
  base_vector <int> plist;
  bool found_force;

  if (force_prime)
    found_force = false;
  else
    found_force = true;

  plist.set_mode(EXPAND);

  if ( !(fp = fopen(quadratic_order::FAKM,"a")) ) {
    unlink(quadratic_order::FAKM);
    unlink(quadratic_order::NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order","test_candidates_imag() - can't \
open FAKTORENMATRIX");
    return(-1);
  }

  // while there are candidates to test
  counter = 0;
  while (((x=CANDIDATE[counter++])!=0) && ((small < needed) || (!found_force))) {
    x-=M;
    Q_von_x.assign(F.eval(bigint(x),bigint(1)));
    multiply(H,Q_von_x,F.get_a());

    xgcd(u,v,bigint(x),bigint(1));
    UT = matrix_GL2Z(bigint(x),-v,bigint(1),u);
    G.assign(F);
    G.transform(UT);

    // compute powers for primes p
    fak_i = 2;
    plist.set_size(0);
    k = 0;
    memset(faktor, 0, 600);

    while ( (p=FB[fak_i]) != 0 ) {
      divides = 0;

      // check if p divides norm of ideal -- adjust exponent accordingly
      if (Q_primes.linear_search(p,i1))
        divides -= Q_exp[i1];

      vorber = p - (M - x) % p;
      if ( vorber == p )
        vorber = 0;

      // if p divides Q_von_x
      if ( (vorber == START1[fak_i]) || (vorber == START2[fak_i]) ) {
        div2 = 0;
        do {
          div_rem(temp, rest, Q_von_x, (long) p);
          if ( rest==0 ) {
            div2++;
            Q_von_x.assign(temp);
          }
        } while ( rest == 0 );

        // compute sign of exponent
        p2 = (p << 1);
        B2p = (int) remainder(G.get_b(), p2);
        if (B2p < 0)
          B2p += p2;

        temp = f_base[fak_i-2].get_b();
        Bpr2p = (int) remainder(temp, p2);
        if (Bpr2p < 0)
          Bpr2p += p2;
        if (B2p != Bpr2p)
          div2 = -div2;
        divides += div2;
      }

      if ((D_primes.bin_search(p,i1)) || (D_primes.bin_search(-p,i2))) {
        // p divides Delta -- take exponent mod 2
        divides %= 2;
        if (divides)
          divides = 1;
      }

      if (divides) {
        if (p == force_prime)
          found_force = true;
        plist[k++] = fak_i-2;
        sprintf(faktor,"%s %d %d", faktor, divides, fak_i-2);
      }

      fak_i++;
    }

    // to avoid duplication of relations (which might cause problems in the
    // linear equation system) use hash function on H(x)

    if ((Q_von_x.is_one()) && (plist.size() > 0)) {
      if (!HT.search(H)) {
        small++;
        bigint_to_string(H, Hstring);
        fprintf(fp, "%s :%s 0\n", Hstring, faktor);
        HT.hash(H);

        // update used primes list for all primes occuring in the relation
        for (i=0; i<k; ++i)
          if (used_primes.bin_search(plist[i],idx))
            used_primes.remove_from(idx);
      }
    }
  }

  fclose(fp);
  return(small);
}


inline int test_candidates_real(quadratic_form & F, int *FB, base_vector <qi_class> & f_base, int *START1, int *START2, char *faktor, int M, sort_vector <int> & Q_primes, sort_vector <int> & Q_exp, sort_vector <int> & D_primes, int *CANDIDATE, hash_table <bigint> & HT, sort_vector <int> & used_primes, math_vector <bigfloat> & minima, int needed, int force_prime, bigfloat & rd)
{
  matrix_GL2Z UT;
  quadratic_form G;
  bigint u,v,H,Q_von_x,temp;
  bigfloat Rtemp,Rtemp2,dfact;
  int div2,fak_i,vorber,x,p,divides,counter,p2,B2p,Bpr2p;
  long rest;
  FILE  *fp;
  char Hstring[150];
  short small=0;
  lidia_size_t i,k,idx;
  int i1,i2;
  base_vector <int> plist,elist;
  bool found_force;

  if (force_prime)
    found_force = false;
  else
    found_force = true;

  plist.set_mode(EXPAND);
  elist.set_mode(EXPAND);

  if ( !(fp = fopen(quadratic_order::FAKM,"a")) ) {
    unlink(quadratic_order::FAKM);
    unlink(quadratic_order::NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order","test_candidates_real() - can't \
open FAKTORENMATRIX");
    return(-1);
  }

  // while there are candidates to test
  counter = 0;
  while (((x=CANDIDATE[counter++])!=0) && ((small < needed) || (!found_force))) {
    x-=M;
    Q_von_x.assign(F.eval(bigint(x),bigint(1)));
    Q_von_x.absolute_value();
    multiply(H,Q_von_x,F.get_a());

    xgcd(u,v,bigint(x),bigint(1));
    UT = matrix_GL2Z(bigint(x),-v,bigint(1),u);
    G.assign(F);
    G.transform(UT);

    // compute powers for other primes p
    fak_i = 2;
    plist.set_size(0);
    k = 0;
    memset(faktor, 0, 600);
    dfact.assign_one();

    while ( (p=FB[fak_i]) != 0 ) {
      divides = 0;

      // check if p divides norm of ideal -- adjust exponent accordingly
      if (Q_primes.linear_search(p,i1)) {
        divides += Q_exp[i1];
        if (Q_exp[i1] > 0)
          multiply(dfact,dfact,bigfloat(p));
      }

      vorber = p - (M - x) % p;
      if ( vorber == p )
        vorber = 0;

      // if p divides Q_von_x
      if ( (vorber == START1[fak_i]) || (vorber == START2[fak_i]) ) {
        div2 = 0;
        do {
          div_rem(temp, rest, Q_von_x, (long) p);
          if ( rest==0 ) {
            div2++;
            Q_von_x.assign(temp);
          }
        } while ( rest == 0 );

        // compute sign of exponent
        p2 = (p << 1);
        B2p = (int) remainder(G.get_b(), p2);
        if (B2p < 0)
          B2p += p2;

        temp = f_base[fak_i-2].get_b();
        Bpr2p = (int) remainder(temp, p2);
        if (Bpr2p < 0)
          Bpr2p += p2;
        if (B2p != Bpr2p)
          div2 = -div2;
        divides -= div2;
      }

      if ((D_primes.bin_search(p,i1)) || (D_primes.bin_search(-p,i2))) {
        // p divides Delta -- take exponent mod 2
        divides %= 2;
        if (divides)
          divides = 1;
      }

      if (divides) {
        if (p == force_prime)
          found_force = true;
        plist[k] = fak_i-2;
        elist[k++] = divides;
        sprintf(faktor,"%s %d %d", faktor, divides, fak_i-2);
      }

      fak_i++;
    }

    // to avoid duplication of relations (which might cause problems in the
    // linear equation system) use hash function on H(x)

    if ((Q_von_x.is_one()) && (plist.size() > 0)) {
      if (!HT.search(H)) {
        small++;
        bigint_to_string(H, Hstring);
        fprintf(fp, "%s :%s 0\n", Hstring, faktor);
        HT.hash(H);

        // update used primes list for all primes occuring in the relation
        for (i=0; i<k; ++i)
          if (used_primes.bin_search(plist[i],idx))
            used_primes.remove_from(idx);

        // compute generator of principal ideal
        add(Rtemp,rd,bigfloat(F.get_b()));
        Rtemp.divide_by_2();
        multiply(Rtemp2,bigfloat(F.get_a()),bigfloat(x));
        add(Rtemp,Rtemp,Rtemp2);
        divide(Rtemp,Rtemp,dfact);
        minima[minima.size()] = log(abs(Rtemp));

//        cout << "\nrelation:\n" << flush;
//        cout << "F = " << F << ", x = " << x << "\n";
//        cout << "G = " << G << "\n" << flush;
//        cout << "Q_primes = " << Q_primes << "\n" << flush;
//        cout << "Q_exp = " << Q_exp << "\n" << flush;
//        cout << "dfact = " << dfact << "\n" << flush;
//        cout << log(abs(Rtemp)) << "\n";
//        cout << faktor << "\n\n" << flush;
      }
    }
  }

  fclose(fp);
  return(small);
}



inline bool test_single(quadratic_form & F, int *FB, int *START1, int *START2, int M, int *CANDIDATE)
{
  bigint Q_von_x,temp;
  int fak_i,vorber,x,p,counter;
  long rest;
  bool found;

  // while there are candidates to test
  counter = 0;
  found = false;
  while (((x=CANDIDATE[counter++])!=0) && (!found)) {
    x -= M;
    Q_von_x.assign(F.eval(bigint(x),bigint(1)));
    Q_von_x.absolute_value();

    // compute powers for primes p
    fak_i = 2;
    while ( (p=FB[fak_i]) != 0 ) {
      vorber = p - (M - x) % p;
      if ( vorber == p )
        vorber = 0;

      // if p divides Q_von_x
      if ( (vorber == START1[fak_i]) || (vorber == START2[fak_i]) ) {
        do {
          div_rem(temp, rest, Q_von_x, (long) p);
          if ( rest==0 )
            Q_von_x.assign(temp);
        } while ( rest == 0 );
      }
      fak_i++;
    }

    found = (Q_von_x.is_one());
  }

  return(found);
}



void
quadratic_order::cgsubexp_imag()
{
  int BachBound,needed,more_rows,p,dense_size;
  lidia_size_t ii,j,k;
  bigfloat hstar,temp;
  bigint Bjj;
  bigint_matrix A,junk;
  hash_table <bigint> HT;
  hash_table <quadratic_form> used_polys;
  sort_vector <int> used_primes,Q_primes, Q_exp, D_primes;
  qi_class Fideal;
  quadratic_form F;
  register int tmp, decimal_digits, i;
  FILE *fp;
  int small, smalls=0, size_FB, M, *SQRTkN = NULL, *FB = NULL, *START1 = NULL;
  int *CANDIDATE=NULL;
  int *START2 = NULL, vergleich =10,prozent;
  bool found;
  SIEBTYP *sieb = NULL, *ende, *LOGP = NULL;
  char    *faktor, zeile[150];
  double  T, LOGMUL, d_wurz, Delta_double, size_A;
  sort_vector <lidia_size_t> good_rows;
  short *bitptr;
  timer t;

  qo_get_name("FAKM");
  qo_get_name("NEWR");

  unlink(NEWR);
  unlink(FAKM);

  t.start_timer();
  qi_class::set_current_order(*this);

  Q_primes.set_mode(EXPAND);
  Q_exp.set_mode(EXPAND);
  D_primes.set_mode(EXPAND);
  good_rows.set_mode(EXPAND);

  decimal_digits = bigint_to_string(-Delta,zeile);

  if (info) {
    cout << "\n\nquadratic order sieve\n";
    cout << "======================\n\n";
    cout << "discriminant of order: -" << zeile << " (" << decimal_digits << ")\n";
    cout.flush();
  }

  qo_read_par(decimal_digits, T, M, size_FB);

  if (info > 2) {
    cout << "T = " << T << "\n" << flush;
    cout << "Size of sieve interval " << M << "\n" << flush;
    cout << "Requested factor base size " << size_FB << "\n" << flush;
  }

  //
  // compute approximation of h and get factor base */
  // set BachBound = 12 log^2 Delta */
  //

  temp.assign(log(bigfloat(-Delta)));
  square(temp,temp);

  int p_bound;
  temp.intify(p_bound);
//  p_bound = (int) ceil(0.6*((double) p_bound));

  multiply(temp,temp,6);
  temp.intify(BachBound);
  divide(temp,bigfloat(-Delta),3);
  sqrt(temp,temp);
  if (temp < bigfloat(BachBound))
    temp.intify(BachBound);

  hstar = init_subexp(size_FB, &FB, p_bound, used_primes, D_primes);
  hstar *= sqrt(bigfloat(-(Delta << 1))) / Pi();

  M = 4*FB[size_FB+1];

  //
  // initialize sieve interval, etc... 
  //

  if ( !(sieb = new SIEBTYP[M<<1])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate SIEB");
  }

  if (!(faktor = new char[600])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate FAKTOR");
  }

  if (!(LOGP = new SIEBTYP[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate LOGP");
  }

  if (!(SQRTkN = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate SQRTkN");
  }

  if (!(START1 = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate START1");
  }

  if (!(START2 = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate START2");
  }

  if (!(CANDIDATE=new int[CAND_NUMBER])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate CANDIDATE");
  }

  ende = sieb + (M<<1) - 1;

  // use hash tables to avoid duplicate relations and duplicate polynomials
  HT.initialize(size_FB);
  HT.set_key_function(&bigint_key);
  used_polys.initialize(10*size_FB);
  used_polys.set_key_function(&quadratic_form_key);

  Delta_double = dbl(-Delta);
  d_wurz = sqrt( Delta_double );

  LOGMUL=127.0/(2 * (SIEBTYP)(0.5 * log2(Delta_double) + log2((double)M)-T*log2((double) FB[FB[0]+1])));

  // compute log(p) and sqrt(Delta) mod p for all p in the factor base
  tmp = size_FB + 1;
  for (i = 2; i < tmp; i++) {
    p = FB[i];
    LOGP[i] = (SIEBTYP)( LOGMUL*log2((double)p)*2 );
    if ( (SQRTkN[i] = ressol( p + (int) remainder(Delta, p), p)) < 0 )
      SQRTkN[i] += p;
  }

  if ((fp = fopen(FAKM, "w")) == NULL) {
    unlink(FAKM);
    unlink(NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order", "cgsubexp_imag() - can't open \
FAKTORENMATRIX");
  }

  fprintf(fp, "Discriminant = -%s\n", zeile);
  fprintf(fp, "%10d relations\n", NULL);
  fclose(fp);

  // central loop of
  // - computing polynomials and zeros
  // - sieving
  // - testing candidates of the sieve array

  signal(SIGTERM,&qo_stop_it);
  signal(SIGINT, &qo_stop_it);
  signal(SIGHUP, &qo_stop_it);
  signal(SIGQUIT, &qo_stop_it);

  seed(bigint(size_FB));

  // the size of coefficient A should be approximately sqrt(Delta)/M
  size_A = (double) d_wurz/M;
  needed = size_FB+20;

  //
  // compute size of dense part of matrix
  //

  dense_size = size_FB / 50;
  if (dense_size < 20)
    dense_size = 20;
  if (dense_size < 50)
    dense_size = 50;
  if (dense_size > size_FB)
    dense_size = size_FB;

  t.stop_timer();

  if (info > 2) {
    cout << "size of A = " << size_A << "\n" << flush;
    cout << "largest prime in dense = " << FB[dense_size+2] << "\n" << flush;
  }
  if (info) {
    cout << "\ninitializtion time:  ";
    MyTime(t.user_time());
    cout << "\n\n" << flush;
    cout << "Size of factor base for sieving " << size_FB << "\n";
    cout << "Number of ``dense'' rows " << dense_size << "\n";
    cout << "Bach bound = " << BachBound << "\n" << flush;
    cout << "max prime in factor base " << FB[size_FB+1] << "\n";
    cout << "value of M used " << M << "\n" << flush;
    cout << "h* = " << hstar << "\n\n" << flush;
  }
  
  //
  // test primes not in FB, but < BachBound
  //

  p = FB[size_FB+1] + 2;
  p = BachBound+1;
  if (p <= BachBound) {
    if (info)
      cout << "testing primes not in FB...\n" << flush;

    t.start_timer();

    PL.PrimeGen(p,BachBound+10);
    bitptr = PL.bitarray;
    i = k = 0;
    while (p <= BachBound) {
      j = 0;
      if (*bitptr) {
        if (prime_ideal(Fideal,p)) {
          ++i;
          found = false;
          while (!found) {
            ++k;
            ++j;

            F.assign(Fideal);
            new_polynomial_imag(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, p);

            if (info > 2) {
              cout << "sieving with:  " << F << "\n" << flush;
            }

            // process polynomial 
            compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

            // sieve using one polynomial
            qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

            // output relations
            found = test_single(F, FB, START1, START2, M, CANDIDATE);

            if (j > 1000) {
              unlink(FAKM);
              unlink(NEWR);
              cout << "\np = " << p << "\n" << flush;
              lidia_error_handler("quadratic_order", "test_factor_base::unable \
to factor p over factor base");
            }
          }
        }
      }
      p += 2;
      ++bitptr;
    }

    t.stop_timer();
   
    used_polys.empty();

    if (info) {
      cout << "time:  ";
      MyTime(t.user_time());
      cout << "\n" << flush;
      cout << i << " primes tested\n" << flush;
      cout << k << " polys required\n\n" << flush;
    }
  }

  if (info)
    cout << "starting sieving:\n\n" << flush;

  t.start_timer();

  while (1) {
    // compute new polynomial 
    new_polynomial_imag(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, 0);
    if (info > 2) {
      cout << "sieving with:  " << F << "\n" << flush;
    }

    // process polynomial 
    compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

    // sieve using one polynomial
    qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

    // output relations
    small = test_candidates_imag(F, FB, fact_base, START1, START2, faktor, M, Q_primes, Q_exp, D_primes, CANDIDATE, HT, used_primes, needed+1-smalls, 0);

    if (small==-1) { // error has occurred
      unlink(FAKM);
      unlink(NEWR);
      lidia_error_handler("quadratic_order","cgsubexp_imag() - error in \
testing candidates");
    }

    smalls +=small;
    prozent = (int)((double)smalls/(double)size_FB*100);
    if ((prozent >=vergleich) && (smalls <= needed)) {
      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n" << flush;
      }
      vergleich +=10;
    }

    if (smalls > needed) {
      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n\n" << flush;
      }

      //
      // compute relations for unused primes, if necessary
      //

      for (i=dense_size; i<size_FB; ++i)
        if (FB[i+2] > p_bound)
          break;
      dense_size = i;

      if (used_primes.size() > 0) {
        if (info)
          cout << "computing relations for unrepresented primes...\n\n" << flush;

        while (used_primes.size() > 0) {
          p = FB[used_primes[0]+2];
          Fideal.assign(inverse(fact_base[used_primes[0]]));

          small = 0;
          while (!small) {
            F.assign(Fideal);
            new_polynomial_imag(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, -p);

            // process polynomial
            compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

            // sieve using one polynomial
            qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

            // output relations
            small = test_candidates_imag(F, FB, fact_base, START1, START2, faktor, M, Q_primes, Q_exp, D_primes, CANDIDATE, HT, used_primes, 1, p);

          }

          smalls += small;
        }

        prozent = (int)((double)smalls/(double)size_FB*100);
      }

      t.stop_timer();

      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n" << flush;
        cout << "\nsieving time:  ";
        MyTime(t.user_time());
        cout << "\n" << flush;
      }

      used_primes.reset();

      //
      // read relation matrix and compute HNF
      //

      if (qo_transform_relations(false)) {
        unlink(FAKM);
        unlink(NEWR);
        lidia_error_handler("quadratic_order","cgsubexp_imag() - error in \
transforming relations");
      }

      if (size_FB < 600) {
        A.set_no_of_rows(size_FB);
        read_relation_matrix(A);
      }
      else {
        qo_transform_relations(true);  // setze static-Variable zurueck
        if ( FB )          delete [] FB;
        if ( LOGP )        delete [] LOGP;
        if ( SQRTkN )      delete [] SQRTkN;
        if ( START1 )      delete [] START1;
        if ( START2 )      delete [] START2;
        if ( sieb )        delete [] sieb;
        if ( CANDIDATE )   delete [] CANDIDATE;

        cout << "quadratic_order::cgsubexp_imag() - matrix now has full dimension.  Waiting for HNF routines for sparse matrices...\n" << flush;
        cout << "dimension:  " << size_FB << " x " << smalls << "\n" << flush;
        cout << "matrix file: " << NEWR << "\n" << flush;
        return;
      }

      /* compute HNF and determinant */
      if (info) {
        cout << "\nComputing HNF of " << A.get_no_of_rows() << " x " << A.get_no_of_columns() << " matrix:\n" << flush;
      }

      t.start_timer();

      A.hnf_havas_cont();

      /* remove zero columns */
      i = 0;
      while (A.is_column_zero(i))
        ++i;
      j = A.get_no_of_columns();
      for (ii=i; ii<j; ++ii)
        A.sto_column(A.column(ii),size_FB,ii-i);
      A.resize(size_FB,j-i);

      t.stop_timer();

      if (info) {
        cout << "HNF done...\n" << flush;
        cout << "HNF time:  ";
        MyTime(t.user_time());
        cout << "\n" << flush;
      }

      if (info > 2) {
        cout << "\nMatrix after HNF:  " << A.get_no_of_rows() << " x " << A.get_no_of_columns() << "\n" << flush;
      }

      /* check for zero rows */
      more_rows = A.get_no_of_rows() - A.get_no_of_columns();
      if (more_rows > 0) {
        if (info)
          cout << "\nmatrix does not have full rank:  need more relations\n" << flush;
        used_primes[0] = A.get_no_of_rows() - more_rows;
      }
      else {
        h = A.det();
        if (info) {
          cout << "determinant = " << h << "\n" << flush;
        }

        if (bigfloat(h) < hstar)
          break;
        else {
          if (info)
            cout << "\ndeterminant too large:  need more relations\n" << flush;
        }
      }

      dense_size = size_FB;
      needed = size_FB / 50;
      if (needed < 20)
        needed = 20;
      needed += smalls + more_rows;
      t.start_timer();
    }
  } 

  //
  // compute structure
  //

  t.start_timer();

  if (h == 1)
    CL[0] = 1;
  else {
    /* delete all rows and columns with diagonal 1 */
    good_rows.set_size(0);
    ii = A.get_no_of_rows();
    i = 0;
    for (j=0; j<ii; ++j) 
      if (!(A.member(j,j).is_one()))
        good_rows[i++] = j;

    /* update factor base */
    k = i;
    for (j=0; j<k; ++j) {
      fact_base[j] = fact_base[good_rows[j]];
      A.sto_row(A.row(good_rows[j]),ii,j);
      A.sto_column(A.column(good_rows[j]),ii,j);
    }
    fact_base.set_size(i);
    A.resize(i,i);

    A.snf_havas(U,junk);
    ii = 0;
    for (j=0; j<A.get_no_of_columns(); ++j) {
      Bjj.assign(A.member(j,j));
      if (!Bjj.is_one()) {
        CL[ii] = Bjj;
        ++ii;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }

  t.stop_timer();

  if (info) {
    cout << "\nSNF time:  ";
    MyTime(t.user_time());
    cout << "\n" << flush;
  }

  qo_transform_relations(true);  // setze static-Variable zurueck
  unlink(NEWR);
  unlink(FAKM);

  if ( FB )          delete [] FB;
  if ( LOGP )        delete [] LOGP;
  if ( SQRTkN )      delete [] SQRTkN;
  if ( START1 )      delete [] START1;
  if ( START2 )      delete [] START2;
  if ( sieb )        delete [] sieb;
  if ( faktor )      delete [] faktor;
  if ( CANDIDATE )   delete [] CANDIDATE;
}



void
quadratic_order::cgsubexp_real()
{
  int BachBound,needed,more_rows,p,dense_size;
  lidia_size_t ii,j,k;
  bigfloat hstar,temp,rtemp,rtemp2,rd;
  bigint Bjj;
  bigint_matrix A,junk;
  hash_table <bigint> HT;
  hash_table <quadratic_form> used_polys;
  sort_vector <int> used_primes,Q_primes, Q_exp, D_primes;
  qi_class Fideal;
  quadratic_form F;
  register int tmp, decimal_digits, i;
  FILE *fp;
  int small, smalls=0, size_FB, M, *SQRTkN = NULL, *FB = NULL, *START1 = NULL;
  int *CANDIDATE=NULL;
  int *START2 = NULL, vergleich =10,prozent;
  bool found;
  SIEBTYP *sieb = NULL, *ende, *LOGP = NULL;
  char    *faktor, zeile[150];
  double  d_wurz, Delta_double, T, LOGMUL,size_A;
  sort_vector <lidia_size_t> good_rows;
  base_vector <bigint> v;
  short *bitptr;
  timer t;
  math_vector <bigfloat> minima,new_minima;
  // bigfloat_lattice unit_lattice;

  qo_get_name("FAKM");
  qo_get_name("NEWR");

  unlink(NEWR);
  unlink(FAKM);

  t.start_timer();
  qi_class::set_current_order(*this);

  Q_primes.set_mode(EXPAND);
  Q_exp.set_mode(EXPAND);
  D_primes.set_mode(EXPAND);
  good_rows.set_mode(EXPAND);
  minima.set_mode(EXPAND);
  new_minima.set_mode(EXPAND);

  sqrt(rd,bigfloat(Delta));

  decimal_digits = bigint_to_string(Delta,zeile);

  if (info) {
    cout << "\n\nquadratic order sieve\n";
    cout << "======================\n\n";
    cout << "discriminant of order: " << zeile << " (" << decimal_digits << ")\n";
    cout.flush();
  }

  qo_read_par(decimal_digits, T, M, size_FB);

  if (info > 2) {
    cout << "\nT = " << T << "\n" << flush;
    cout << "Size of sieve interval " << M << "\n" << flush;
    cout << "Requested factor base size " << size_FB << "\n" << flush;
  }

  //
  // compute approximation of h and get factor base */
  // set BachBound = 12 log^2 Delta */
  //

  temp.assign(log(bigfloat(Delta)));
  square(temp,temp);

  int p_bound;
  temp.intify(p_bound);
//  p_bound = (int) ceil(0.6*((double) p_bound));

  multiply(temp,temp,6);
  temp.intify(BachBound);
  sqrt(temp,bigfloat(Delta));
  if (temp < bigfloat(BachBound))
    temp.intify(BachBound);

  p_bound = 40;
  size_FB = 7;
  hstar = init_subexp(size_FB, &FB, p_bound, used_primes, D_primes);
  hstar *= sqrt(bigfloat(Delta >> 1));

  M = 4*FB[size_FB+1];

  //
  // initialize sieve interval, etc... 
  //

  if ( !(sieb = new SIEBTYP[M<<1])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate SIEB");
  }

  if (!(faktor = new char[600])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate FAKTOR");
  }

  if (!(LOGP = new SIEBTYP[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate LOGP");
  }

  if (!(SQRTkN = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate SQRTkN");
  }

  if (!(START1 = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate START1");
  }

  if (!(START2 = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate START2");
  }

  if (!(CANDIDATE=new int[CAND_NUMBER])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_real::allocate CANDIDATE");
  }

  ende = sieb + (M<<1) - 1;

  // use hash tables to avoid duplicate relations and duplicate polynomials
  HT.initialize(size_FB);
  HT.set_key_function(&bigint_key);
  used_polys.initialize(10*size_FB);
  used_polys.set_key_function(&quadratic_form_key);

  Delta_double = dbl(Delta);
  d_wurz = sqrt( Delta_double );

  LOGMUL=127.0/(2 * (SIEBTYP)(0.5 * log2(Delta_double) + log2((double)M)-T*log2((double) FB[FB[0]+1])));
 
  // compute log(p) and sqrt(Delta) mod p for all p in the factor base
  tmp = size_FB + 1;
  for (i = 2; i < tmp; i++) {
    p = FB[i];
    LOGP[i] = (SIEBTYP)( LOGMUL*log2((double)p)*2 );
    if ( (SQRTkN[i] = ressol( (int) remainder(Delta, p), p)) < 0 )
      SQRTkN[i] += p;
  }

  if ((fp = fopen(FAKM, "w")) == NULL) {
    unlink(FAKM);
    unlink(NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order", "cgsubexp_real() - can't open \
FAKTORENMATRIX");
  }

  fprintf(fp, "Discriminant = %s\n", zeile);
  fprintf(fp, "%10d relations\n", NULL);
  fclose(fp);

  // central loop of
  // - computing polynomials and zeros
  // - sieving
  // - testing candidates of the sieve array

  signal(SIGTERM,&qo_stop_it);
  signal(SIGINT, &qo_stop_it);
  signal(SIGHUP, &qo_stop_it);
  signal(SIGQUIT, &qo_stop_it);

  seed(bigint(size_FB));

  // the size of coefficient A should be approximately sqrt(Delta)/M
  size_A = (double) d_wurz/M;

  //needed = size_FB+20;
  needed = size_FB+2;
  
  //
  // compute size of dense part of matrix
  //

  dense_size = size_FB / 50;
  if (dense_size < 20)
    dense_size = 20;
  if (dense_size > size_FB)
    dense_size = size_FB;

  t.stop_timer();

  if (info > 2) {
    cout << "size of A = " << size_A << "\n" << flush;
    cout << fact_base << "\n" << flush;
  }
  if (info) {
    cout << "\ninitializtion time:  ";
    MyTime(t.user_time());
    cout << "\n\n" << flush;
    cout << "Size of factor base for sieving " << size_FB << "\n";
    cout << "Number of ``dense'' rows " << dense_size << "\n";
    cout << "Bach bound = " << BachBound << "\n" << flush;
    cout << "max prime in factor base " << FB[size_FB+1] << "\n";
    cout << "value of M used " << M << "\n" << flush;
    cout << "h* = " << hstar << "\n\n" << flush;
  }
  
  //
  // test primes not in FB, but < BachBound
  //

  p = FB[size_FB+1] + 2;
  if (p <= BachBound) {
    if (info)
      cout << "testing primes not in FB...\n" << flush;

    t.start_timer();

    PL.PrimeGen(p,BachBound+10);
    bitptr = PL.bitarray;
    i = k = 0;
    while (p <= BachBound) {
      j = 0;
      if (*bitptr) {
        if (prime_ideal(Fideal,p)) {
          ++i;
          found = false;
          while (!found) {
            ++k;
            ++j;
            F.assign(Fideal);
            new_polynomial_real(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, p);

            // process polynomial 
            compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

            // sieve using one polynomial
            qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

            // output relations
            found = test_single(F, FB, START1, START2, M, CANDIDATE);

            if (j > 1000) {
              unlink(FAKM);
              unlink(NEWR);
              cout << "\np = " << p << "\n" << flush;
              lidia_error_handler("quadratic_order", "cgsubexp_real::unable \
to factor p over factor base");
            }
          }
        }
      }
      p += 2;
      ++bitptr;
    }

    t.stop_timer();
   
    used_polys.empty();

    if (info) {
      cout << "time:  ";
      MyTime(t.user_time());
      cout << "\n" << flush;
      cout << i << " primes tested\n" << flush;
      cout << k << " polys required\n\n" << flush;
    }
  }
  
  if (info)
    cout << "\nstarting sieving:\n\n" << flush;

  t.start_timer();

  while (1) {
    // compute new polynomial 
    new_polynomial_real(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, 0);

    // process polynomial 
    compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

    // sieve using one polynomial
    qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

    // output relations
    small = test_candidates_real(F, FB, fact_base, START1, START2, faktor, M, Q_primes, Q_exp, D_primes, CANDIDATE, HT, used_primes, minima, needed+1-smalls, 0, rd);

    if (small==-1) { // error has occurred
      unlink(FAKM);
      unlink(NEWR);
      lidia_error_handler("quadratic_order","cgsubexp_real() - error in \
testing candidates");
    }

    smalls +=small;
    prozent = (int)((double)smalls/(double)size_FB*100);
    if ((prozent >=vergleich) && (smalls <= needed)) {
      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n" << flush;
      }
      vergleich +=10;
    }

    if (smalls > needed) {
      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n\n" << flush;
      }

      //
      // compute relations for unused primes, if necessary
      //

      for (i=dense_size; i<size_FB; ++i)
        if (FB[i+2] > p_bound)
          break;
      dense_size = i;

      if (used_primes.size() > 0) {
        if (info)
          cout << "computing relations for unrepresented primes...\n\n" << flush;

        while (used_primes.size() > 0) {
          p = FB[used_primes[0]+2];
          Fideal.assign(fact_base[used_primes[0]]);
          small = 0;
          while (!small) {
            F.assign(Fideal);
            new_polynomial_real(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, p);

            // process polynomial
            compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

            // sieve using one polynomial
            qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

            // output relations
            small = test_candidates_real(F, FB, fact_base, START1, START2, faktor, M, Q_primes, Q_exp, D_primes, CANDIDATE, HT, used_primes, minima, 1, p, rd);
          }

          smalls += small;
        }

        prozent = (int)((double)smalls/(double)size_FB*100);
      }

      t.stop_timer();

      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n" << flush;
        cout << "\nsieving time:  ";
        MyTime(t.user_time());
        cout << "\n" << flush;
      }

      used_primes.reset();

      //
      // read relation matrix and compute HNF
      //

      if (qo_transform_relations(false)) {
        unlink(FAKM);
        unlink(NEWR);
        lidia_error_handler("quadratic_order","cgsubexp_real() - error in \
transforming relations");
      }

      if (size_FB < 600) {
        A.set_no_of_rows(size_FB);
        read_relation_matrix(A);
      }
      else {
        qo_transform_relations(true);  // setze static-Variable zurueck
        if ( FB )          delete [] FB;
        if ( LOGP )        delete [] LOGP;
        if ( SQRTkN )      delete [] SQRTkN;
        if ( START1 )      delete [] START1;
        if ( START2 )      delete [] START2;
        if ( sieb )        delete [] sieb;
        if ( CANDIDATE )   delete [] CANDIDATE;

        cout << "quadratic_order::cgsubexp_real() - matrix now has full dimension.  Waiting for HNF routines for sparse matrices...\n" << flush;
        cout << "dimension:  " << size_FB << " x " << smalls << "\n" << flush;
        cout << "matrix file: " << NEWR << "\n" << flush;
        return;
      }

      /* compute HNF and determinant */
      if (info) {
        cout << "\nComputing HNF of " << A.get_no_of_rows() << " x " << A.get_no_of_columns() << " matrix:\n" << flush;
      }

      t.start_timer();

      if (info > 2) {
        cout << "\nrelation matrix:\n" << flush;
        cout << A << "\n" << flush;
        cout << minima << "\n" << flush;
      }

      U = A;
      U.hnf_havas_cont(junk);

      if (info > 2) {
        cout << junk.column_vector(0) << "\n";
        cout << "\nT = \n" << junk << "\n" << flush;
        cout << "size:  " << junk.get_no_of_rows() << " x " << junk.get_no_of_columns() << "\n";
        cout << "test = " << A.row_vector(0)*junk.column_vector(0) << "\n";
        cout << A*junk << "\n";
      }

      A = U;
      U.reset();

      // transform row of minima
      new_minima.set_size(0);
      ii = A.get_no_of_columns();
      for (i=0; i<ii; ++i) {
        rtemp.assign_zero();
        for (j=0; j<ii; ++j) {
          multiply(rtemp2,bigfloat(junk.member(j,i)),minima[j]);
          add(rtemp,rtemp,rtemp2);
        }
        new_minima[i].assign(rtemp);
      }

      if (info > 2) {
        cout << "\nHNF matrix:\n" << flush;
        cout << A << "\n" << flush;
        cout << new_minima << "\n" << flush;
      }
     
      // remove zero columns and create real lattice
      i = 0;
      while (A.is_column_zero(i))
        ++i;

      j = A.get_no_of_columns();
      minima.set_size(j-i);
      for (ii=i; ii<j; ++ii) {
        A.sto_column(A.column(ii),size_FB,ii-i);
        minima[ii-i] = new_minima[ii-i];
      }
      A.resize(size_FB,j-i);
      junk.reset();

//  if (info > 2) {
      // compute regulator approximation
//      unit_lattice.reset();
//      unit_lattice.set_no_of_columns(2);
//      unit_lattice.sto(0,0,abs(new_minima[0]));
//      for (ii=1; ii<i; ++ii) {
//        unit_lattice.sto(0,1,abs(new_minima[ii]));
//        unit_lattice.lll(0.75);
//      }
//      if (R.is_gt_zero()) {
//        unit_lattice.sto(0,1,R);
//        unit_lattice.lll(0.99);
//      }
//      R.assign(unit_lattice.member(0,0));
//  }
//  else
    R.assign_one();

      t.stop_timer();

      if (info) {
        cout << "HNF done...\n" << flush;
        cout << "HNF time:  ";
        MyTime(t.user_time());
        cout << "\n" << flush;
      }

      if (info > 2) {
        cout << "\nMatrix after HNF:  " << A.get_no_of_rows() << " x " << A.get_no_of_columns() << "\n" << flush;
      }

      /* check for zero rows */
      more_rows = A.get_no_of_rows() - A.get_no_of_columns();
      if (more_rows > 0) {
        if (info)
          cout << "\nmatrix does not have full rank:  need more relations\n" << flush;
        used_primes[0] = A.get_no_of_rows() - more_rows;
      }
      else {
        h = A.det();
        if (info) {
          cout << "determinant = " << h << "\n" << flush;
          cout << "hR = " << bigfloat(h)*R << "\n" << flush;
        }

        if (bigfloat(h)*R < hstar)
          break;
        else {
          if (info)
            cout << "\ndeterminant too large:  need more relations\n" << flush;
        }
      }

      U.assign(A);
      A.reset();

      dense_size = size_FB;
      needed = size_FB / 50;
      if (needed < 20)
        needed = 20;
      needed += smalls + more_rows;
      t.start_timer();
    }
  }

  //
  // compute structure
  //

  t.start_timer();

  if (h == 1)
    CL[0] = 1;
  else {
    /* delete all rows and columns with diagonal 1 */
    good_rows.set_size(0);
    ii = A.get_no_of_rows();
    i = 0;
    for (j=0; j<ii; ++j) 
      if (!(A.member(j,j).is_one()))
        good_rows[i++] = j;

    /* update factor base */
    k = i;
    for (j=0; j<k; ++j) {
      fact_base[j] = fact_base[good_rows[j]];
      A.sto_row(A.row(good_rows[j]),ii,j);
      A.sto_column(A.column(good_rows[j]),ii,j);
    }
    fact_base.set_size(i);
    A.resize(i,i);

    A.snf_havas(U,junk);
    ii = 0;
    for (j=0; j<A.get_no_of_columns(); ++j) {
      Bjj.assign(A.member(j,j));
      if (!Bjj.is_one()) {
        CL[ii] = Bjj;
        ++ii;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }

  t.stop_timer();

  if (info) {
    cout << "\nSNF time:  ";
    MyTime(t.user_time());
    cout << "\n" << flush;
  }

  qo_transform_relations(true);  // setze static-Variable zurueck

  unlink(NEWR);
  unlink(FAKM);

  if ( FB )          delete [] FB;
  if ( LOGP )        delete [] LOGP;
  if ( SQRTkN )      delete [] SQRTkN;
  if ( START1 )      delete [] START1;
  if ( START2 )      delete [] START2;
  if ( sieb )        delete [] sieb;
  if ( faktor )      delete [] faktor;
  if ( CANDIDATE )   delete [] CANDIDATE;
}



void
quadratic_order::cgsubexp_imag_more()
{
  int BachBound,needed,more_rows,p,dense_size;
  lidia_size_t ii,j,k;
  bigfloat hstar,temp;
  bigint Bjj,hval;
  bigint_matrix A,junk;
  hash_table <bigint> HT;
  hash_table <quadratic_form> used_polys;
  sort_vector <int> used_primes,Q_primes, Q_exp, D_primes;
  qi_class Fideal;
  quadratic_form F;
  register int tmp, decimal_digits, i;
  FILE *fp;
  int small, smalls=0, size_FB, M, *SQRTkN = NULL, *FB = NULL, *START1 = NULL;
  int *CANDIDATE=NULL;
  int *START2 = NULL, vergleich =10,prozent;
  bool found;
  SIEBTYP *sieb = NULL, *ende, *LOGP = NULL;
  char    *faktor, zeile[150],fname[50],line[1000];
  double  T, LOGMUL, d_wurz, Delta_double, size_A;
  sort_vector <lidia_size_t> good_rows;
  short *bitptr;
  timer t;

  qo_get_name("FAKM");
  qo_get_name("NEWR");

  unlink(NEWR);
  unlink(FAKM);

  t.start_timer();
  qi_class::set_current_order(*this);

  Q_primes.set_mode(EXPAND);
  Q_exp.set_mode(EXPAND);
  D_primes.set_mode(EXPAND);
  good_rows.set_mode(EXPAND);

  decimal_digits = bigint_to_string(-Delta,zeile);

  if (info) {
    cout << "\n\nquadratic order sieve - more relations\n";
    cout << "======================\n\n";
    cout << "discriminant of order: -" << zeile << " (" << decimal_digits << ")\n";
    cout.flush();
  }

  qo_read_par(decimal_digits, T, M, size_FB);

  if (info > 2) {
    cout << "T = " << T << "\n" << flush;
    cout << "Size of sieve interval " << M << "\n" << flush;
    cout << "Requested factor base size " << size_FB << "\n" << flush;
  }

  //
  // compute approximation of h and get factor base */
  // set BachBound = 12 log^2 Delta */
  //

  temp.assign(log(bigfloat(-Delta)));
  square(temp,temp);

  int p_bound;
  temp.intify(p_bound);
//  p_bound = (int) ceil(0.6*((double) p_bound));

  multiply(temp,temp,6);
  temp.intify(BachBound);
  divide(temp,bigfloat(-Delta),3);
  sqrt(temp,temp);
  if (temp < bigfloat(BachBound))
    temp.intify(BachBound);

  hstar = init_subexp(size_FB, &FB, p_bound, used_primes, D_primes);
  hstar *= sqrt(bigfloat(-(Delta << 1))) / Pi();

  M = 4*FB[size_FB+1];

  //
  // initialize sieve interval, etc... 
  //

  if ( !(sieb = new SIEBTYP[M<<1])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate SIEB");
  }

  if (!(faktor = new char[600])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate FAKTOR");
  }

  if (!(LOGP = new SIEBTYP[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate LOGP");
  }

  if (!(SQRTkN = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate SQRTkN");
  }

  if (!(START1 = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate START1");
  }

  if (!(START2 = new int[size_FB + 2])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate START2");
  }

  if (!(CANDIDATE=new int[CAND_NUMBER])) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order", "cgsubexp_imag::allocate CANDIDATE");
  }

  ende = sieb + (M<<1) - 1;

  // use hash tables to avoid duplicate relations and duplicate polynomials
  HT.initialize(size_FB);
  HT.set_key_function(&bigint_key);
  used_polys.initialize(10*size_FB);
  used_polys.set_key_function(&quadratic_form_key);

  Delta_double = dbl(-Delta);
  d_wurz = sqrt( Delta_double );

  LOGMUL=127.0/(2 * (SIEBTYP)(0.5 * log2(Delta_double) + log2((double)M)-T*log2((double) FB[FB[0]+1])));

  // compute log(p) and sqrt(Delta) mod p for all p in the factor base
  tmp = size_FB + 1;
  for (i = 2; i < tmp; i++) {
    p = FB[i];
    LOGP[i] = (SIEBTYP)( LOGMUL*log2((double)p)*2 );
    if ( (SQRTkN[i] = ressol( p + (int) remainder(Delta, p), p)) < 0 )
      SQRTkN[i] += p;
  }

  if ((fp = fopen(FAKM, "w")) == NULL) {
    unlink(FAKM);
    unlink(NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order", "cgsubexp_imag() - can't open \
FAKTORENMATRIX");
  }

  fprintf(fp, "Discriminant = -%s\n", zeile);
  fprintf(fp, "%10d relations\n", NULL);
  fclose(fp);

  // central loop of
  // - computing polynomials and zeros
  // - sieving
  // - testing candidates of the sieve array

  signal(SIGTERM,&qo_stop_it);
  signal(SIGINT, &qo_stop_it);
  signal(SIGHUP, &qo_stop_it);
  signal(SIGQUIT, &qo_stop_it);

  seed(bigint(size_FB));

  // the size of coefficient A should be approximately sqrt(Delta)/M
  size_A = (double) d_wurz/M;
  needed = 1000;

  //
  // compute size of dense part of matrix
  //

  dense_size = size_FB;

  //
  // read old relations
  //

  cout << "Enter name of relations file: ";
  cin >> fname;

  if ((fp = fopen(fname, "r")) == NULL) {
    unlink(FAKM);
    unlink(NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order", "cgsubexp_imag() - can't open \
relation file");
  }

  fgets(line, 1000, fp);
  fgets(line, 1000, fp);
  while (fgets(line, 1000, fp)) {
    string_to_bigint(line,hval);
    cout << hval << "\n";
    HT.hash(hval);
  }

  fclose(fp);

  // 
  // read unused primes
  //

  cout << "Enter name of unused primes file: ";
  cin >> fname;
  if ((fp = fopen(fname, "r")) == NULL) {
    unlink(FAKM);
    unlink(NEWR);
    qo_transform_relations(true);
    lidia_error_handler("quadratic_order", "cgsubexp_imag() - can't open \
unused primes file");
  }

  i = 0;
  used_primes.reset();
  while (fgets(line, 1000, fp)) {
    sscanf(line,"%d\n",&p);
    cout << p << "\n" << flush;
    used_primes[i] = p;
    ++i;
  }

  fclose(fp);

  t.stop_timer();

  if (info > 2) {
    cout << "size of A = " << size_A << "\n" << flush;
    cout << "largest prime in dense = " << FB[dense_size+2] << "\n" << flush;
  }
  if (info) {
    cout << "\ninitializtion time:  ";
    MyTime(t.user_time());
    cout << "\n\n" << flush;
    cout << "unused primes:  " << used_primes.size() << "\n";
  }
  
  if (info)
    cout << "starting sieving:\n\n" << flush;

  t.start_timer();

  do {
    // compute new polynomial 
    new_polynomial_imag(F, size_A, FB, used_polys, Q_primes, Q_exp, dense_size, 0);
    if (info > 2) {
      cout << "sieving with:  " << F << "\n" << flush;
    }

    // process polynomial 
    compute_roots(F, FB, SQRTkN, START1, START2, M, Q_primes, D_primes);

    // sieve using one polynomial
    qo_sieve_interval(FB, LOGP, START1, START2, sieb, ende, M, CANDIDATE);

    // output relations
    small = test_candidates_imag(F, FB, fact_base, START1, START2, faktor, M, Q_primes, Q_exp, D_primes, CANDIDATE, HT, used_primes, needed+1-smalls, 0);

    if (small==-1) { // error has occurred
      unlink(FAKM);
      unlink(NEWR);
      lidia_error_handler("quadratic_order","cgsubexp_imag() - error in \
testing candidates");
    }

    smalls +=small;
    prozent = (int)((double)smalls/(double)size_FB*100);
    if ((prozent >=vergleich) && (smalls <= needed)) {
      if (info) {
        cout << smalls << " (" << prozent << "%) relations.  " << flush;
        cout << used_primes.size() << " primes not represented.  " << flush;
        cout << used_polys.no_of_elements() << " polys.\n" << flush;
      }
      vergleich +=10;
    }

  } while ((used_primes.size()) || (smalls < 20));

  if (info) {
    cout << smalls << " (" << prozent << "%) relations.  " << flush;
    cout << used_primes.size() << " primes not represented.  " << flush;
    cout << used_polys.no_of_elements() << " polys.\n\n" << flush;
  }

  //
  // read relation matrix and compute HNF
  //

  if (qo_transform_relations(false)) {
    unlink(FAKM);
    unlink(NEWR);
    lidia_error_handler("quadratic_order","cgsubexp_imag() - error in \
transforming relations");
  }

  qo_transform_relations(true);  // setze static-Variable zurueck
  if ( FB )          delete [] FB;
  if ( LOGP )        delete [] LOGP;
  if ( SQRTkN )      delete [] SQRTkN;
  if ( START1 )      delete [] START1;
  if ( START2 )      delete [] START2;
  if ( sieb )        delete [] sieb;
  if ( CANDIDATE )   delete [] CANDIDATE;

  cout << "quadratic_order::cgsubexp_imag() - matrix now has full dimension.  Waiting for HNF routines for sparse matrices...\n" << flush;
  cout << "dimension:  " << size_FB << " x " << smalls << "\n" << flush;
  cout << "matrix file: " << NEWR << "\n" << flush;
  return;
}



void
quadratic_order::cgsubexp_imag_end()
{
  int BachBound,p,nitems,M;
  char val[50];
  lidia_size_t i,ii,j,k,rows,cols;
  bigfloat hstar,temp;
  bigint Bjj;
  bigint_matrix A,junk;
  sort_vector <int> used_primes,D_primes;
  base_vector <bigint> cvec;
  base_vector <qi_class> tv;
  register int tmp,decimal_digits;
  FILE *fpin;
  int  size_FB, *FB = NULL;
  char *pp,zeile[1000],mname[20];
  double  T;
  sort_vector <lidia_size_t> good_rows;
  timer t;

  t.start_timer();
  qi_class::set_current_order(*this);

  D_primes.set_mode(EXPAND);
  good_rows.set_mode(EXPAND);
  tv.set_mode(EXPAND);

  decimal_digits = bigint_to_string(-Delta,zeile);
  qo_read_par(decimal_digits, T, M, size_FB);

  if (info > 2) {
    cout << "T = " << T << "\n" << flush;
    cout << "Size of sieve interval " << M << "\n" << flush;
    cout << "Requested factor base size " << size_FB << "\n" << flush;
  }

  //
  // compute approximation of h and get factor base */
  // set BachBound = 12 log^2 Delta */
  //

  temp.assign(log(bigfloat(-Delta)));
  square(temp,temp);

  int p_bound;
  temp.intify(p_bound);
//  p_bound = (int) ceil(0.6*((double) p_bound));

  multiply(temp,temp,6);
  temp.intify(BachBound);
  divide(temp,bigfloat(-Delta),3);
  sqrt(temp,temp);
  if (temp < bigfloat(BachBound))
    temp.intify(BachBound);

  hstar = init_subexp(size_FB, &FB, p_bound, used_primes, D_primes);
  hstar *= sqrt(bigfloat(-(Delta << 1))) / Pi();


  //
  // transform FB
  //

  cout <<  "Enter vector name: ";
  cin >> mname;
  
  fpin = fopen(mname,"r");
  if (!fpin) {
    cout << mname << "\n";
    lidia_error_handler("quadratic_order", "cgsubexp_imag_end() - can't \
open vector");
  }

  i = 0;
  while (fgets(zeile, 1000, fpin)) {
    /* read row number, number of entries, and a zero */
    sscanf(zeile,"%d",&k);
    tv[i] = fact_base[k];
    ++i;
  }
  fact_base.reset();
  fact_base.assign(tv);
  tv.reset();


  //
  // read relation matrix and compute structure
  //

  cout <<  "Enter matrix name: ";
  cin >> mname;

  fpin = fopen(mname,"r");
  if (!fpin) {
    cout << mname << "\n";
    lidia_error_handler("quadratic_order", "cgsubexp_imag_end() - can't \
open hnf matrix");
  }

  A.set_no_of_rows(size_FB);
  cvec.set_mode(EXPAND);

  good_rows.set_size(0);
  rows = size_FB;
  i = cols = 0;
  
  while (fgets(zeile, 1000, fpin)) {
    /* read row number, number of entries, and a zero */
    sscanf(zeile,"%d%d%d",&i,&nitems,&k);
    pp = (char *) strchr(zeile, ' ');
    ++pp;
    ++pp = (char *) strchr(pp,' ');
    ++pp = (char *) strchr(pp,' ');

    cvec.reset();
    cvec.set_size(rows);
    nitems >>= 1;
    for (k=0; k<nitems; ++k) {
      sscanf(pp,"%s%d",&val,&j);
      string_to_bigint(val,cvec[j]);
      ++pp = (char *) strchr(pp,' ');
      ++pp = (char *) strchr(pp,' ');
    }

    if (!(cvec.member(i).is_one())) {
      good_rows[cols] = i;
      A.set_no_of_columns(cols+1);
      A.sto_column_vector(cvec,rows,cols);
      ++cols;
    }
  }

  cout << "Gr = " << good_rows << "\n" << flush;

  /* update factor base */
  for (j=0; j<cols; ++j) {
    fact_base[j] = fact_base[good_rows[j]];
    A.sto_row(A.row(good_rows[j]),cols,j);
  }

  fact_base.set_size(cols);
  A.resize(cols,cols);

  cout << A << "\n";
  h = A.det();
  if (info) {
    cout << "determinant = " << h << "\n" << flush;
  }

  if (bigfloat(h) >= hstar)
      cout << "\ndeterminant too large:  need more relations\n" << flush;

  t.start_timer();
  if (h == 1)
    CL[0] = 1;
  else {
    A.snf_havas(U,junk);
    ii = 0;
    for (j=0; j<A.get_no_of_columns(); ++j) {
      Bjj.assign(A.member(j,j));
      if (!Bjj.is_one()) {
        CL[ii] = Bjj;
        ++ii;
      }
    }
    /* reduce transformation matrix elements and compute inverse */
    set_transformations();
  }

  t.stop_timer();

  if (info) {
    cout << "\nend time:  ";
    MyTime(t.user_time());
    cout << "\n" << flush;
  }

  if ( FB )          delete [] FB;
}



void
quadratic_order::new_polynomial_imag(quadratic_form & F, double size_A, int *FB, hash_table <quadratic_form> & used_polys, sort_vector <int> & Q_primes, sort_vector <int> & Q_exp, int dense_size, int force_prime)
{
  lidia_size_t i,j;
  quadratic_form *oldpoly,FF;
  int p,e,i1,i2,rand_size,attempts,ex;
  bigint temp;
  bigint dbl_tmp; // MM

  oldpoly = (quadratic_form *) true;
  Q_primes.reset();
  Q_exp.reset();
  j = 0;
  if (force_prime) {
    Q_primes[j] = abs(force_prime);
    if (force_prime < 0)
      Q_exp[j] = -1;
    else
      Q_exp[j] = 1;
    ++j;
  }
  else
    F.assign_one(Delta);

  dbl_tmp.assign(size_A); // MM
  rand_size = dense_size;

  while (oldpoly) {
    do {
      attempts = 0;
      do {
        ++attempts;
        if (attempts > 5) {
          rand_size += 10;
          if (rand_size > FB[0])
            rand_size = FB[0];
          attempts = 0;
        }

        temp = randomize(bigint(rand_size));
        temp.intify(i);
        p = FB[i+2];
        if (Q_primes.linear_search(p,i1))
          p = 0;

      } while (!p);

      FF.assign(fact_base[i]);

      temp = randomize(bigint(2));
      temp.intify(e);
      if (e) {
        compose(F,F,FF);
        ex = 1;
      }
      else {
        compose(F,F,get_conjugate(FF));
        ex = -1;
      }

      Q_primes[j] = p;
      Q_exp[j] = ex;
      ++j;
    } while (F.get_a() < dbl_tmp);

    oldpoly = used_polys.search(F);
  }

  used_polys.hash(F);
}



void
quadratic_order::new_polynomial_real(quadratic_form & F, double size_A, int *FB, hash_table <quadratic_form> & used_polys, sort_vector <int> & Q_primes, sort_vector <int> & Q_exp, int dense_size, int force_prime)
{
  lidia_size_t i,j;
  quadratic_form *oldpoly,FF;
  int p,e,i1,i2,rand_size,attempts,ex;
  bigint temp;
  bigint dbl_tmp; // MM

  oldpoly = (quadratic_form *) true;
  Q_primes.reset();
  Q_exp.reset();
  j = 0;
  if (force_prime) {
    Q_primes[j] = abs(force_prime);
    if (force_prime < 0)
      Q_exp[j] = -1;
    else
      Q_exp[j] = 1;
    ++j;
  }
  else
    F.assign_one(Delta);

  dbl_tmp.assign(size_A); // MM
  rand_size = dense_size;
  attempts = 0;

  while (oldpoly) {
    do {
      temp = randomize(bigint(rand_size));
      temp.intify(i);
      p = FB[i+2];
      FF.assign(fact_base[i]);

      temp = randomize(bigint(2));
      temp.intify(e);
      if (e) {
        compose(F,F,FF);
        ex = 1;
      }
      else {
        compose(F,F,get_conjugate(FF));
        ex = -1;
      }

      if (Q_primes.linear_search(p,i1))
        Q_exp[i1] += ex;
      else {
        Q_primes[j] = p;
        Q_exp[j] = ex;
        ++j;
      }

    } while (F.get_a() < dbl_tmp);

    oldpoly = used_polys.search(F);
    ++attempts;
    if (attempts > 5) {
      rand_size += 10;
      if (rand_size > FB[0])
        rand_size = FB[0];
      attempts = 0;
    }
  }

  used_polys.hash(F);
}



void
qo_read_par(int decimal_digits, double &T, int &M, int &p_bound)
{
  int  i = -1;

  while ( (int) qo_params[++i][0] != decimal_digits );

  T = qo_params[i][1];			 // tolerance value
  M = (int) qo_params[i][2];             // length of array is 2*M
  p_bound = (int) qo_params[i][3];       // size of factor base
}



bigfloat
quadratic_order::init_subexp(int & size_FB, int **FB, int p_bound, sort_vector <int> & used_primes, sort_vector <int> & D_primes)
{
  bigfloat temp;
  int kron,Q2,m8,P,QQ;
  short *bitptr;
  double E,C,wt;
  qi_class PID;
  register int i,j,n,*fbb;
  bigint TP;

  used_primes.set_mode(EXPAND);

  QQ = (int) get_optimal_Q();
  Q = QQ;
  n = j = 0;
  D_primes.set_size(0);

  /* compute partial product for p = 2 */
  m8 = (int) remainder(Delta,8);
  if (m8 < 0)
    m8 += 8;
  if (m8 == 1)
    E = log(2.0);
  else if (m8 == 5)
    E = log(2.0 / 3.0);
  else 
    E = 0.0;
  if (prime_ideal(PID,bigint(2))) {
    used_primes[n] = n;
    fact_base[n++] = PID;
    if (remainder(Delta,2) == 0) {
      D_primes[j] = 2;
      ++j;
    }
  }

  /* compute weights */
  Q2 = QQ << 1;
  C = 0.0;
  for (i=QQ; i<=Q2-1; ++i)
    C += (double) i * log((double) i);

  /* generate list of primes */
  PL.PrimeGen(3,Q2+10);

  /* compute partial product for p < Q */
  bitptr = PL.bitarray;
  P = 3;
  while (P < QQ) {
    if (*bitptr) {
      if ((P <= p_bound) || (n < size_FB)) {
        if (prime_ideal(PID,P)) {
          used_primes[n] = n;
          fact_base[n++] = PID;
          if (remainder(Delta,P) == 0) {
            kron = 0;
            D_primes[j] = P;
            ++j;
          }
          else
            kron = 1;
        }
        else {
          if (remainder(Delta,P) == 0)
            kron = 0;
          else 
            kron = -1;
        }
      }
      else
        kron = kronecker(Delta,bigint(P));

      E += log((double) P / (P - kron));
    }
    P += 2;
    ++bitptr;
  }

  /* computed weighted partial products for Q < p < 2Q */
  wt = 1.0;
  for (i=QQ; i<=P; ++i)
    wt -= (double) i*log((double) i) / C;
  while (P < Q2) {
    if (*bitptr) {
      if ((P <= p_bound) || (n < size_FB)) {
        if (prime_ideal(PID,P)) {
          used_primes[n] = n;
          fact_base[n++] = PID;
          if (remainder(Delta,P) == 0) {
            kron = 0;
            D_primes[j] = P;
            ++j;
          }
          else
            kron = 1;
        }
        else {
          if (remainder(Delta,P) == 0)
            kron = 0;
          else 
            kron = -1;
        }
      }
      else
        kron = kronecker(Delta,bigint(P));

      E += wt*log((double) P / (P - kron));
    }
    P += 2;
    ++bitptr;
    wt -= (double) ((P-1.0)*log((double) P-1.0) + P*log((double) P)) / C;
  }

  FI = exp(bigfloat(E));

  if (P < p_bound) {
    PL.PrimeGen(P,p_bound+10);
    bitptr = PL.bitarray;
    while (P <= p_bound) {
      if (*bitptr) {
        if (prime_ideal(PID,P)) {
          used_primes[n] = n;
          fact_base[n++] = PID;
          if (remainder(Delta,P) == 0) {
            D_primes[j] = P;
            ++j;
          }
        }
      }
      P += 2;
      ++bitptr;
    }
  }

  if (n < size_FB) {
    PL.PrimeGen(P,P+10*(size_FB-n));
    bitptr = PL.bitarray;
    while (n < size_FB) {
      if (*bitptr) {
        if (prime_ideal(PID,P)) {
          used_primes[n] = n;
          fact_base[n++] = PID;
          if (remainder(Delta,P) == 0) {
            D_primes[j] = P;
            ++j;
          }
        }
      }
      P += 2;
      ++bitptr;
      if (P > PL.Hbitval) {
        PL.PrimeGen(P,P+100*(size_FB-n));
        bitptr = PL.bitarray;
      }
    }
  }

  size_FB = n;
  if (!(*FB=new int[size_FB+3]))
    lidia_error_handler("quadratic_order", "init_subexp() - can't allocate FB");

  fbb = FB[0];
  *fbb++ = size_FB;
  *fbb++ = -1;
  for (i=0; i<size_FB; ++i) {
    PID.assign(fact_base.member(i));
    TP.assign(PID.get_a());
    TP.intify(P);
    *fbb++ = P;
  }

  *fbb = 0;

  D_primes.sort();

  return FI;
}



void
quadratic_order::factor_imag()
{
  lidia_size_t i,j,numgens,numfacts;
  long pwr;
  bigint curr_fact,val;
  qi_class A;
  base_vector <qi_class> S2gens;
  rational_factorization new_comp;
  sort_vector <bigint> facts;

  qi_class::set_current_order(*this);

  if (disc_fact != Delta) {
    facts.set_mode(EXPAND);
    facts.set_size(0);
    S2gens.set_mode(EXPAND);
    S2gens.set_size(0);
    disc_fact.assign(-1);

    /* compute generators of 2-Sylow subgroup */
    generators();
    numgens = gens.size();
    j = 0;
    for (i=0; i<numgens; ++i) {
      if (CL[i].is_even()) {
        power(S2gens[j],gens[i],CL[i]/2);
        ++j;
      }
    }
    numgens = j;

    if (numgens == 0)
      /* Delta is prime */
      disc_fact.assign(Delta);
    else {
      /* compute divisors of the discriminant */
      numfacts = 0;
      if (Delta.is_even())
        facts[numfacts++] = bigint(2);
      for (i=0; i<numgens; ++i) {
        A = S2gens[i];
        if (A.get_b().is_zero()) {
          /* Delta = -4ac */
          facts[numfacts++] = abs(A.get_a());
          facts[numfacts++] = abs(A.get_c());
        }
        else if (A.get_a() == A.get_b()) {
          /* Delta = b(b-4c) */
          facts[numfacts++] = abs(A.get_b());
          facts[numfacts++] = abs(A.get_b() - (A.get_c() << 2));               
        }
        else {
          /* Delta = (b-2a)(b+2a) */
          facts[numfacts++] = abs(A.get_b() - (A.get_a() << 1));
          facts[numfacts++] = abs(A.get_b() + (A.get_a() << 1));
        }
      }

      /* refine divisors */
      facts.sort();
      for (i=0; i<numfacts-1; ++i) {
        curr_fact = facts[i];
        if (!curr_fact.is_one()) {
          for (j=i+1; j<numfacts; ++j)
            while ((facts[j] % curr_fact) == 0)
              facts[j] /= curr_fact;
        }
      }

      /* construct factorization */
      val.assign(-Delta);
      for (i=0; i<numfacts; ++i) {
        curr_fact = facts[i];
        if (curr_fact > 1) {
          new_comp.assign(curr_fact);

          pwr = 0;
          while ((val % curr_fact) == 0) {
            ++pwr;
            val /= curr_fact;
          }

          for (j=0; j<pwr; ++j)
            disc_fact = disc_fact*new_comp;
        }
      }
      if (!val.is_one()) {
        new_comp.assign(val);
        disc_fact = disc_fact*new_comp;
      }
    }
  }
  if (!disc_fact.is_prime_factorization())
    disc_fact.factor();


  if (disc_fact != Delta) {
    cout << disc_fact << "\n" << flush;
    warning_handler_c("quadratic_order","factor_imag() - factorization not \
equal to the discriminant.  Please report!", cout << "\n" << Delta << " <> " << disc_fact << "\n");
  }
}



/* FIX */
void
quadratic_order::factor_real()
{
  qi_class_real A;
  bigfloat R2;

  warning_handler("quadratic_order","factor_discriminant() - not \
implemented for real orders");

  qi_class::set_current_order(*this);

  if (disc_fact != Delta) {
    generators();

  if (info > 2) {
    R2.assign(regulator());
    R2.divide_by_2();
    cout << "R/2 = " << R2 << "\n" << flush;
    A.assign_one();
    A.assign(nearest(A,R2));
    cout << "at R/2:  " << A << "\n" << flush;
  }

    disc_fact.assign(Delta);
    disc_fact.factor();
  }
}



bigint
quadratic_order::exact_power(const bigint & omult, const qi_class & G, indexed_hash_table <ideal_node> & RT, indexed_hash_table <ideal_node> & QT, lidia_size_t numRpr, long & r, long & q)
{
  lidia_size_t i,j,num,numQ;
  long oldr,oldq;
  bigint ord,p,pwr,ex,oldmult;
  qi_class A,D,E;
  rational_factorization pfact;
  bool found_rel;
  ideal_node *Inode;

  oldr = r;
  oldq = q;
  oldmult = omult;

  numQ = QT.no_of_elements();
  pfact.assign(omult);
  pfact.factor();

  ord.assign_one();
  num = pfact.no_of_comp();
  for (i=0; i<num; ++i) {
    A.assign(G);
    p.assign(pfact.base(i));
    ex = pfact.exponent(i);

    pwr.assign(omult);
    for (j=0; j<ex; ++j)
      divide(pwr,pwr,p);

    if (pwr > 1)
      power_imag(A,A,pwr);

    found_rel = false;
    while (!found_rel) {
      for (j=0; j<numQ; ++j) {
        r = -1;
        D.assign(QT[j].get_A());
        multiply_imag(E,D,A);

        if (E.is_one())
          r = 0;
        else {
          Inode = RT.search(ideal_node(E,0));
          if (Inode)
            r = Inode->get_index();
        }
        if ((r >= 0) && (r < numRpr)) {
          found_rel = true;
          break;
        }
      }
      if (!found_rel) {
        multiply(ord,ord,p);
        power_imag(A,A,p);
      }
    }
  }

  /* ord is the smallest power of G contained in the current subgroup */
  if (ord == oldmult) {
    r = oldr;
    q = oldq;
  }
  else {
    power(A,G,ord);
    r = -1;
    for (j=0; j<numQ; ++j) {
      D.assign(QT[j].get_A());
      multiply_imag(E,D,A);
      if (E.is_one())
        r = 0;
      else {
        Inode = RT.search(ideal_node(E,0));
        if (Inode)
          r = Inode->get_index();
      }
      if ((r >= 0) && (r < numRpr)) {
        q = j;
        break;
      }
      else
        r = -1;
    }
  }

  return ord;
}



bigint
quadratic_order::exact_power_real(const bigint & omult, const qi_class & G, indexed_hash_table <ideal_node> & RT, indexed_hash_table <ideal_node> & QT, lidia_size_t numRpr, long & r, long & q, qi_class_real & Gstep)
{
  lidia_size_t i,j,num,numQ;
  long oldr,oldq;
  bigint ord,p,pwr,ex,oldmult;
  qi_class A,D,E;
  qi_class_real F,*FS;
  rational_factorization pfact;
  bool found_rel;
  ideal_node *Inode;
  bigfloat temp,GStepWidth;

  oldr = r;
  oldq = q;
  oldmult = omult;
  floor(GStepWidth,Gstep.get_distance());

  numQ = QT.no_of_elements();
  pfact.assign(omult);
  pfact.factor();

  ord.assign_one();
  num = pfact.no_of_comp();
  for (i=0; i<num; ++i) {
    A.assign(G);
    p.assign(pfact.base(i));
    ex = pfact.exponent(i);

    pwr.assign(omult);
    for (j=0; j<ex; ++j)
      divide(pwr,pwr,p);

    if (pwr > 1)
      power_real(A,A,pwr);

    found_rel = false;
    while (!found_rel) {
      for (j=0; j<numQ; ++j) {
        r = -1;
        D.assign(QT[j].get_A());
        multiply_real(E,D,A);

        if (Gstep.is_one()) {
          FS = prin_list.search(qi_class_real(E));
          if (FS)
            r = 0;
          else {
            Inode = RT.search(ideal_node(E,0));
            if (Inode)
              r = Inode->get_index();
          }
        }
        else {
          F.assign(E,0.0);
          temp.assign_zero();
          while ((F.get_distance() <= R) && (r < 0)) {
            FS = prin_list.search(F);
            if (FS)
              r = 0;
            else {
              Inode = RT.search(ideal_node(qi_class(F),0));
              if (Inode)
                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) && (r < numRpr)) {
          found_rel = true;
          break;
        }
      }

      if (!found_rel) {
        multiply(ord,ord,p);
        power_real(A,A,p);
      }
    }
  }

  /* ord is the smallest power of G contained in the current subgroup */
  if (ord == oldmult) {
    r = oldr;
    q = oldq;
  }
  else {
    power(A,G,ord);
    r = -1;
    for (j=0; j<numQ; ++j) {
      D.assign(QT[j].get_A());
      multiply_real(E,D,A);

      if (Gstep.is_one()) {
        FS = prin_list.search(qi_class_real(E));
        if (FS)
          r = 0;
        else {
          Inode = RT.search(ideal_node(E,0));
          if (Inode)
            r = Inode->get_index();
        }
      }
      else {
        F.assign(E,0.0);
        temp.assign_zero();
        while ((F.get_distance() <= R) && (r < 0)) {
          FS = prin_list.search(F);
          if (FS)
            r = 0;
          else {
            Inode = RT.search(ideal_node(qi_class(F),0));
            if (Inode)
              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) && (r < numRpr)) {
        q = j;
        break;
      }
      else
        r = -1;
    }
  }

  return ord;
}



void
MyTime(long t)
{
  long d,h,m,s,ms;

  ms = t % 100;
  t /= 100;
  s = t % 60;
  t /= 60;
  m = t % 60;
  t /= 60;
  h = t % 24;
  d = t / 24;

  if (d > 0) {
    cout << setw(3) << d << " day, ";
    cout << setw(3) << h << " hour, ";
    cout << setw(3) << m << " min, ";
    cout << setw(3) << s << " sec, ";
    cout << setw(3) << ms << " hsec";
  }
  else if (h > 0) {
    cout << setw(3) << h << " hour, ";
    cout << setw(3) << m << " min, ";
    cout << setw(3) << s << " sec, ";
    cout << setw(3) << ms << " hsec";
  }
  else if (m > 0) {
    cout << setw(3) << m << " min, ";
    cout << setw(3) << s << " sec, ";
    cout << setw(3) << ms << " hsec";
  }
  else if (s > 0) {
    cout << setw(3) << s << " sec, ";
    cout << setw(3) << ms << " hsec";
  }
  else
    cout << setw(3) << ms << " hsec";

  return;
}



/**********************************
 * prime_lists
 * 
 * to be replaced...
 **********************************/


prime_lists::prime_lists()
{
  Lbitval = 0;
  Hbitval = 0;
  bitarray = NULL;
  primes = NULL;
  already_read = false;
}



prime_lists::~prime_lists()
{
  delete bitarray;
  delete primes;
}


void
prime_lists::GetPrimes()
{
  long P,*primesptr;
  short *bitptr;
  register long x;

  if (!already_read) {
    already_read = true;

    short *temp = new short[MAXBITS];
    bitarray = temp;
    long *temp2 = new long[MPRIMES];
    primes = temp2;

    PrimeGen2();

    primesptr = primes;
    *primesptr = 2;
    ++primesptr;
    x = 1;
    bitptr = bitarray;
    P = 3;
    while (x < MPRIMES) {
      if (*bitptr) {
        *primesptr = P;   
        ++primesptr;
        ++x;
      }
      ++bitptr;
      P += 2;
    }

    PrimeGen(3,MAXHVAL);
  }

  return;
}



void
prime_lists::PrimeGen(long L, long H)
{
  long D,B,p,*primesptr;
  short *bitptr;
  register long i,j,range;
  
  if (!(L % 2))
    ++L;
  Lbitval = L;
  Hbitval = H;
  range = (H - L) >> 1;
  B = (long) floor(sqrt( (double) H));
  /* find all primes in specified range */
  bitptr = bitarray;
  for (i=0; i<=range; i++) {
    *bitptr = 1;
    ++bitptr;
  }

  primesptr = primes;
  ++primesptr;
  p = *primesptr;
  while (p <= B) {
    j = 0;
    bitptr = bitarray;
    D = L;
    while (D % p) {
      ++j; 
      D += 2;
      ++bitptr;
    }
    if (p != D)
      *bitptr = 0;
    while ((j+p) <= range) {
      bitptr += p;
      j += p;
      *bitptr = 0;
    }

    ++primesptr;
    p = *primesptr;
  }

  return;
}



void
prime_lists::PrimeGen2()
{
  long L,H,D,B,p;
  short *bitptr,*pptr;
  register long i,j,range;
  
  L = 3;
  H = 70000;
  Lbitval = L;
  Hbitval = H;
  range = (H - L) >> 1;
  B = (long) floor(sqrt( (double) H));
  /* find all primes in specified range */
  bitptr = bitarray;
  for (i=0; i<=range; i++) {
    *bitptr = 1;
    ++bitptr;
  }

  p = L;
  pptr = bitarray;
  while (p <= B) {
    if (*pptr) {
      j = 0;
      bitptr = bitarray;
      D = L;
      while (D % p) {
        ++j; 
        D += 2;
        ++bitptr;
      }
      if (p != D)
        *bitptr = 0;
      while ((j+p) <= range) {
        bitptr += p;
        j += p;
        *bitptr = 0;
      }
    }

    p += 2;
    ++pptr;
  }

  return;
}


void
prime_lists::MyFactor(long N, long & nfacts, long *FL, long *expL)
{
  long B,*pptr,*FLptr,*expptr;

  nfacts = 0;
  B = (long) floor(sqrt((double) N));
  pptr = primes;
  FLptr = FL;
  expptr = expL;
  while ((N > 1) && (*pptr <= B)) {
    if (!(N % *pptr)) {
      N /= *pptr;
      (*FLptr) = *pptr;
      (*expptr) = 1;
      ++FLptr;
      ++nfacts;
      while (!(N % *pptr) && (N > 1)) {
        ++(*expptr);
        N /= *pptr;
      }
      ++expptr;
    }
    ++pptr;
  }
  if (N > 1) {
    (*FLptr) = N;
    (*expptr) = 1;
    ++nfacts;
  }
}


bigint
int_key(const int & G)
{ return bigint(G); }

bigint
long_key(const long & G)
{ return bigint(G); }

bigint
bigint_key(const bigint & G)
{ return G; }
