//
// LiDIA - a library for computational number theory
//   Copyright (c) 1995 by the LiDIA Group
//
// File        : bi_lattice_gensys.h
// Author      : Werner Backes (WB), Thorsten Lauer (TL) 
// Last change : WB/TL, Feb 10 1995, initial version
//             : WB/TL, Dez 21 1995, second template version
//	       : WB/TL, Jan 08 1996, stabilized template version 	
//	       : WB/TL, Feb 29 1996, some information/function extensions 
//
//

#ifndef _bigint_lattice_gensys_h
#define _bigint_lattice_gensys_h

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:math_matrix.h>
#include <LiDIA:base_vector.h>
#include <LiDIA:bigint.h>
#include <LiDIA:bigfloat.h>
#include <LiDIA:timer.h>
#include <stdlib.h>
#else
#include <LiDIA/math_matrix.h>
#include <LiDIA/base_vector.h>
#include <LiDIA/bigint.h>
#include <LiDIA/bigfloat.h>
#include <LiDIA/timer.h>
#include <stdlib.h>
#endif

#ifndef rint
#define rint(x) (((x)-floor(x))<0.5?floor(x):ceil(x)) 
#endif

//
// Define for gcc 2.7.0 or higher
// !!! new ansi standard !!!
//
#if defined(__GNUC__) && __GNUC__ == 2 && __GNUC_MINOR__ > 6 
#define cl(x)
#define fcl(x) register lidia_size_t x
#else
#define cl(x) lidia_size_t x
#define fcl(x) x
#endif

typedef int (*bin_cmp_func)(const bigint*, const bigint*, long);

//
// you try to reduce  the bigfloat problem to a kind of bigint problem,
// then use the bigint algorithm, and go back to bigfloats
// thats the reason for the friend declaration
// 
class bigfloat_lattice_gensys;
class bigfloat_lattice_basis;

class bigint_lattice_gensys : public math_matrix < bigint > 
{
  protected :
    friend          class bigfloat_lattice_gensys;
    friend          class bigfloat_lattice_basis;
    
//
// Global configuration
//
    sdigit          y_nom;
    sdigit          y_denom;
    double          y_par;
    
    bool            trans_flag;

//
// algorithm - information
//
    sdigit          reduction_steps;
    sdigit          swapping_steps;
    sdigit          correction_steps;

//
// static variables for temporary use to increase speed
//
    static bigint tempmz0;
    static bigint tempmz1;
    static bigint tempmz2;
    static bigint tempmz3;
    static bigint tempmz4;
    static bigint ergmz;
    
    static double   vectdblz;
    static bigint   vectbinz;
    static bigfloat vectbflz;

    static lidia_size_t vectsize;

  public :

//
// Constructor / Destructors 
//
    bigint_lattice_gensys(); 
//    bigint_lattice_gensys(lidia_size_t);
    bigint_lattice_gensys(lidia_size_t, lidia_size_t);
    bigint_lattice_gensys(lidia_size_t, lidia_size_t, const bigint**);
    bigint_lattice_gensys(const math_matrix < bigint >&);
    bigint_lattice_gensys(const bigint_lattice_gensys&);
    ~bigint_lattice_gensys();    

//
//  Assignments
//
    void assign(const bigint_lattice_gensys&);
    bigint_lattice_gensys& operator = (const bigint_lattice_gensys&);
    friend void assign(bigint_lattice_gensys&, const bigint_lattice_gensys&);

//
// Set or get reduction info, paramters
//
    void set_reduction_parameter(double);
    void set_reduction_parameter(sdigit, sdigit);
    double get_reduction_parameter();
    void get_reduction_parameter(sdigit&, sdigit&);

//
// Set representation direction, reduce
// column by column or row by row
//
    void set_representation_columns();
    void set_representation_rows();
    bool get_representation() { return (trans_flag); };

//
// Get detailed information about lll - algorithm
//
    sdigit get_no_of_reduction_steps() { return (reduction_steps); };
    sdigit get_no_of_swaps() { return (swapping_steps); };
    sdigit get_no_of_corrections() { return (correction_steps); };

//
// Algorithms - Interface
//

//
// Special experimantal functions
//
//  randomize = permutation
//  sort by length 
//
   void randomize_vectors();
   void sort_big_vectors();
   void sort_small_vectors();

//
// int bfl_cmp_function(bigint *a, bigint *b, long)
// returns 
// >0     if    a  >  b
// =0     if    a  =  b
// <0     if    a  <  b
//
   void sort_vectors(bin_cmp_func);

//
// Buchmann - Kessler Algorthm for 
// linaer generating systems
//
   void lin_gen_system(math_matrix< bigint >&, lidia_size_t&);

   inline friend bigint_lattice_gensys lin_gen_system(const bigint_lattice_gensys& L,
                                                      lidia_size_t& rank)
   {
     bigint_lattice_gensys TL(L);
     bigint_lattice_gensys T;
     TL.lin_gen_system(T, rank);
     return (T);
   }

//
// Variation of the Schnorr - Euchner - lllfp using doubles for
// paramter=1 and x*doubleprecision for parameter=x
//
   void lll_gensys(lidia_size_t&, sdigit=1);
   void lll_gensys(const math_matrix< bigint >&, lidia_size_t&, sdigit=1);
   inline friend bigint_lattice_gensys lll_gensys(const bigint_lattice_gensys& L,
                                                  lidia_size_t& rank,
                                                  sdigit factor=1)
   {
     bigint_lattice_gensys TL(L);
     TL.lll_gensys(rank, factor);
     return (TL);
   }

   
   void lll_trans_gensys(math_matrix< bigint >&, lidia_size_t&, sdigit=1);
   inline friend bigint_lattice_gensys lll_trans_gensys(const bigint_lattice_gensys& L,
                                                        math_matrix< bigint >& T, 
                                                        lidia_size_t& rank,
                                                        sdigit factor=1)
   {
     bigint_lattice_gensys LT(L);
     LT.lll_trans_gensys(T, rank, factor);
     return (LT);
   }
   
   
//
// Modified lll for genertating systems 
// of the form n x n+1
//
// Returns vector of dimension n 
// where you can find the relations 
//
   void mlll(base_vector< bigint >&);
   void mlll(const math_matrix< bigint >&, base_vector< bigint >&);
   inline friend bigint_lattice_gensys mlll(const bigint_lattice_gensys& L, 
                                            base_vector< bigint >& v)
   {
     bigint_lattice_gensys TL(L);
     TL.mlll(v);
     return(TL);
   }

   void mlll(bigint *&);
   void mlll(const math_matrix< bigint >&, bigint *&);
   inline friend bigint_lattice_gensys mlll(const bigint_lattice_gensys& L, 
                                            bigint*& v)
   {
     bigint_lattice_gensys TL(L);
     TL.mlll(v);
     return(TL);
   }


//
// Compute a basis from lattice A and Transformation lattice T
//
   void compute_basis(const math_matrix< bigint >& A, const math_matrix< bigint >& T) 
   {
     debug_handler("bigint_lattice_gensys","compute_basis(A, T)");
     if (trans_flag)
       ::multiply(*this, A, T);
     else
       ::multiply(*this, T, A);
   }
   
  protected :

//
// Functions needed by every constructor 
//
    void init_parameters();
    void do_some_other_stuff() {};
    void assign_the_rest(const bigint_lattice_gensys&);

    sdigit compute_read_precision();
    sdigit compute_precision();

    void alpha_compute(bigfloat&);
    void gamma_compute(bigfloat&, sdigit);
    void zwei_pot_q_compute(bigfloat&, sdigit&, bigfloat&);

//
// very funny, but helpfull, speedup
//
    void Tr_trans_swap(bigint_lattice_gensys&);
    void bigintify(sdigit&, const math_matrix< bigfloat >&);

//
// Dimension checking
//
    inline bool Tr_check_mlll()
    {
      if (trans_flag == true)
        {
          if (rows+1 != columns)
            return(false);
          else
            return(true);
        }
      else
        {
          if (columns+1 != rows)
            return(false);
          else
            return(true);
        }
    }


//
// Algorithms, real Implementaion
//
    void Tr_randomize_vectors();
    void Tr_sort_vectors(sdigit);
    void Tr_sort_vectors(bin_cmp_func);

    void Tr_lll();                    
    void Tr_lll_dbl();                   
    void Tr_lll_bfl(sdigit);

    void Tr_lll_deep_insert_dbl(sdigit);          // not working yet
    void Tr_lll_deep_insert_bfl(sdigit, sdigit);  // not yet implemented (s.o.)
    
    void Tr_lll_trans(math_matrix< bigint >&);   
    void Tr_lll_trans_dbl(math_matrix< bigint >&);
    void Tr_lll_trans_bfl(math_matrix< bigint >&, sdigit);

    void Tr_lll_rand();   

    void Tr_mlll_dbl(bigint*);                    // not yet implemented
    void Tr_mlll_bfl(bigint*);

    void Tr_lin_gen_system(math_matrix< bigint >&, lidia_size_t&);

    void Tr_lll_dbl_gensys(lidia_size_t&);
    void Tr_lll_bfl_gensys(lidia_size_t&, sdigit=2);

    void Tr_lll_trans_dbl_gensys(math_matrix< bigint >&, lidia_size_t&);
    void Tr_lll_trans_bfl_gensys(math_matrix< bigint >&, lidia_size_t&, sdigit=2);

    
//
// "Vector" - Operations
//

#ifdef __GNUC__ 

//
// for double vectors
//
    inline friend void bin_assign_dbl(double* a, double* b)
    {
      debug_handler("bigint_lattice_gensys","void bin_assign_dbl(a, b)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        a[i]=b[i];
    }
    
    inline friend void bin_assign_zero_dbl(double* a)
    {
      debug_handler("bigint_lattice_gensys","void bin_assign_zero_dbl(a)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        a[i]=0;
    }
    
    inline friend void bin_add_dbl(double* c, double* a, double* b)
    {
      debug_handler("bigint_lattice_gensys","void bin_add_dbl(c, a, b)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        c[i]=a[i]+b[i];
    }
    
    inline friend void bin_subtract_dbl(double* c, double* a, double* b)
    {
      debug_handler("bigint_lattice_gensys","void bin_subtract_dbl(c, a, b)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        c[i]=a[i]-b[i];
    }
    
    inline friend void bin_scalmul_dbl(double* b, const double& d, double* a)
    {
      debug_handler("bigint_lattice_gensys","void bin_scalmul_dbl(b, d, a)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        b[i]=a[i]*d;
    }
    
    inline friend void bin_scalprod_dbl(double& res, double* a, double* b)
    {
      debug_handler("bigint_lattice_gensys","void bin_scalprod_dbl(res, a, b)");
      cl(i);
      res=0;
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        res+=a[i]*b[i];
    }
    
    inline friend void bin_scalquad_dbl(double& res, double* a)
    {
      debug_handler("bigint_lattice_gensys","void bin_scalquad_dbl(res, a, b)");
      cl(i);
      res=0;
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        res+=a[i]*a[i];
    }
    
    inline friend void bin_l2_norm_dbl(double& norm, double* v)
    {
      debug_handler("bigint_lattice_gensys","void bin_l2_norm_dbl(norm, v)");
      cl(i);
      norm=0;		// Initialisation
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        norm+=v[i]*v[i];
      norm=sqrt(norm);
    }
    
    inline friend void bin_l1_norm_dbl(double& norm, double* v)
    {
      debug_handler("bigint_lattice_gensys","void bin_l1_norm_dbl(norm, v)");
      cl(i);
      norm=0;	 // Initialisation
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        norm+=fabs(v[i]);
    }

//
// for bigfloat vectors
//
    inline friend void bin_assign_bfl(bigfloat* a, bigfloat* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_assign_bfl(a, b)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        a[i].assign(b[i]);
    }
    
    inline friend void bin_assign_zero_bfl(bigfloat* a)
    {
      debug_handler("bigint_lattice_gensys", "bin_assign_zero_bfl(a)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        a[i].assign_zero();
    }
    
    inline friend void bin_add_bfl(bigfloat* c, bigfloat* a, bigfloat* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_add_bfl(c, a, b)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        ::add(c[i],a[i],b[i]);
    }
    
    inline friend void bin_subtract_bfl(bigfloat* c, bigfloat* a, bigfloat* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_subtract_bfl(c, a, b)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        ::subtract(c[i],a[i],b[i]);
    }
    
    inline friend void bin_scalmul_bfl(bigfloat* b, const bigfloat& d, bigfloat* a)
    {
      debug_handler("bigint_lattice_gensys", "bin_scalmul_bfl(b, d, a)");
      cl(i);
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        ::multiply(b[i], a[i], d);
    }
    
    inline friend void bin_scalprod_bfl(bigfloat& res, bigfloat* a, bigfloat* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_scalprod_bfl(res, a, b)");
      cl(i);
      res.assign_zero();
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {    
          ::multiply(bigint_lattice_gensys::vectbflz, a[i], b[i]);
          ::add(res, res, bigint_lattice_gensys::vectbflz);
        }
    }
    
    inline friend void bin_scalquad_bfl(bigfloat& res, bigfloat* a)
    {
      debug_handler("bigint_lattice_gensys", "bin_scalquad_bfl(res, a)");
      cl(i);
      res.assign_zero();
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {    
          ::square(bigint_lattice_gensys::vectbflz, a[i]);
          ::add(res, res, bigint_lattice_gensys::vectbflz);
        }
    }
    
    inline friend void bin_l1_norm_bfl(bigfloat& norm, bigfloat* v)
    {
      debug_handler("bigint_lattice_gensys", "bin_l1_norm_bfl(norm, v)");
      cl(i);
      norm.assign_zero();	 // Initialisation
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {
          bigint_lattice_gensys::vectbflz.assign(abs(v[i]));
          ::add(norm, norm, bigint_lattice_gensys::vectbflz);
        }
    }
    
    inline friend void bin_l2_norm_bfl(bigfloat& norm, bigfloat* v)
    {
      debug_handler("bigint_lattice_gensys", "bin_l2_norm_bfl(norm, v)");
      cl(i);
      norm.assign_zero();		// Initialisation
      for (fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {
          ::square(bigint_lattice_gensys::vectbflz, v[i]);
          ::add(norm, norm, bigint_lattice_gensys::vectbflz);
        }
      sqrt(norm, norm);
    }

//
// for bigint vectors
//
    inline friend void bin_assign_bin(bigint* a, bigint* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_assign_bin(a, b)");
      cl(i);
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        a[i].assign(b[i]);
    }
    
    inline friend void bin_assign_zero_bin(bigint* a)
    {
      debug_handler("bigint_lattice_gensys", "bin_assign_zero_bin(a)");
      cl(i);
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        a[i].assign_zero();
    }
    
    inline friend void bin_add_bin(bigint* c, bigint* a, bigint* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_add_bin(c, a, b)");
      cl(i);
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        ::add(c[i],a[i],b[i]);
    }
    
    inline friend void bin_subtract_bin(bigint* c, bigint* a, bigint* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_subtract_bin(c, a, b)");
      cl(i);
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        ::subtract(c[i],a[i],b[i]);
    }
    
    inline friend void bin_scalmul_bin(bigint* b, const bigint& d, bigint* a)
    {
      debug_handler("bigint_lattice_gensys", "bin_scalmul_bin(b, d, a)");
      cl(i);
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--)
        ::multiply(b[i], a[i], d);
    }
    
    inline friend void bin_scalprod_bin(bigint& res, bigint* a, bigint* b)
    {
      debug_handler("bigint_lattice_gensys", "bin_scalprod_bin(res, a, b)");
      cl(i);
      res.assign_zero();
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {    
          ::multiply(bigint_lattice_gensys::vectbinz, a[i], b[i]);
          ::add(res, res, bigint_lattice_gensys::vectbinz);
        }
    }
    
    inline friend void bin_scalquad_bin(bigint& res, bigint* a)
    {
      debug_handler("bigint_lattice_gensys", "bin_scalquad_bin(res, a)");
      cl(i);
      res.assign_zero();
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {    
          ::square(bigint_lattice_gensys::vectbinz, a[i]);
          ::add(res, res, bigint_lattice_gensys::vectbinz);
        }
    }
    
    inline friend void bin_l2_norm_bin(bigint& norm, bigint* v)
    {
      debug_handler("bigint_lattice_gensys", "bin_l2_norm_bin(norm, v)");
      cl(i);
      norm.assign_zero();		// Initialisation
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {
          ::square(bigint_lattice_gensys::vectbinz, v[i]);
          ::add(norm, norm, bigint_lattice_gensys::vectbinz);
        }
    //  sqrt(norm, norm);
    }
    
    inline friend void bin_l1_norm_bin(bigint& norm, bigint* v)
    {
      debug_handler("bigint_lattice_gensys", "bin_l1_norm_bin(norm, v)");
      cl(i);
      norm.assign_zero();	 // Initialisation
      for(fcl(i)=bigint_lattice_gensys::vectsize-1;i>=0;i--) 
        {
          bigint_lattice_gensys::vectbinz.assign(abs(v[i]));
          ::add(norm, norm, bigint_lattice_gensys::vectbinz);
        }
    }
    
#else

//
// For double vectors
//
    friend void bin_assign_dbl(double*, double*); 
    friend void bin_assign_zero_dbl(double*);
    friend void bin_add_dbl(double*, double*, double*);
    friend void bin_subtract_dbl(double*, double*, double*);

    friend void bin_scalmul_dbl(double*, const double&, double*);
    friend void bin_scalprod_dbl(double&, double*, double*);
    friend void bin_scalquad_dbl(double&, double*);
    
    friend void bin_l1_norm_dbl(double&, double*);
    friend void bin_l2_norm_dbl(double&, double*);

//
// for bigfloat vectors
//
    friend void bin_assign_bfl(bigfloat*, bigfloat*); 
    friend void bin_assign_zero_bfl(bigfloat*);
    friend void bin_add_bfl(bigfloat*, bigfloat*, bigfloat*);
    friend void bin_subtract_bfl(bigfloat*, bigfloat*, bigfloat*);

    friend void bin_scalmul_bfl(bigfloat*, const bigfloat&, bigfloat*);
    friend void bin_scalprod_bfl(bigfloat&, bigfloat*, bigfloat*);
    friend void bin_scalquad_bfl(bigfloat&, bigfloat*);
    
    friend void bin_l1_norm_bfl(bigfloat&, bigfloat*);
    friend void bin_l2_norm_bfl(bigfloat&, bigfloat*);


//
// for bigint vectors
//
    friend void bin_assign_bin(bigint*, bigint*); 
    friend void bin_assign_zero_bin(bigint*);
    friend void bin_add_bin(bigint*, bigint*, bigint*);
    friend void bin_subtract_bin(bigint*, bigint*, bigint*);

    friend void bin_scalmul_bin(bigint*, const bigint&, bigint*);
    friend void bin_scalprod_bin(bigint&, bigint*, bigint*);
    friend void bin_scalquad_bin(bigint&, bigint*);
    
    friend void bin_l1_norm_bin(bigint&, bigint*);
    friend void bin_l2_norm_bin(bigint&, bigint*);

#endif

    inline friend void bin_swap_dbl(double*& a, double*& b)
    {
      debug_handler("bigint_lattice_gensys","void bin_swap_dbl(a,b)");
      register double* temp=a;
      a=b;
      b=temp;
    }

    inline friend void bin_swap_bfl(bigfloat*& a, bigfloat*& b)
    {
      debug_handler("bigint_lattice_gensys","bin_swap_bfl(a,b)");
      register bigfloat* temp=a;
      a=b;
      b=temp;
    }
    
    inline friend void bin_swap_bin(bigint*& a, bigint*& b)
    {
      debug_handler("bigint_lattice_gensys","bin_swap_bin(a, b)");
      register bigint* temp=a;
      a=b;
      b=temp;
    }
    

};

#endif
