//
// LiDIA - a library for computational number theory
//   Copyright (c) 1994, 1995 by the LiDIA Group
//
// File        : bigint_matrix.C
// Author      : Patrick Theobald (PT)
// Last change : PT, Oct 28, 1995, initial version 
//

/*
$Id: bigint_matrix.c,v 1.22 1996/12/18 15:34:19 theobald Exp $
*/

#if defined(HAVE_MAC_DIRS) || defined(__MWERKS__)
#include <LiDIA:bigint_matrix.h>
#include <LiDIA:modular_operations.inl>
#include <LiDIA:modular_functions.inl>
#include <stdlib.h>
#include <LiDIA:xerror.h>
#include <LiDIA:random.h>
#else
#include <LiDIA/bigint_matrix.h>
#include <LiDIA/modular_operations.inl>
#include <LiDIA/modular_functions.inl>
#include <stdlib.h>
#include <LiDIA/xerror.h>
#include <LiDIA/random.h>
#endif

/**
 ** debug defines / error defines
 **/

extern const char *PRT;
extern const char *matrix_error_msg[];

#define DV_BIGM LDBL_MATRIX       /* Debug value   */
#define DM_BIGM "bigint_matrix"   /* Debug message / Error message */
#define LBIGM_ERROR matrix_error_msg

/**
 ** debug level
 **
 **   0 : remainder
 **   1 : 
 **   2 : boolean functions
 **   3 : norms and bounds
 **   4 : randomize
 **   5 : chinrest
 **   6 : get_primes
 **   7 : Linear algebra PART 1
 **   8 : Linear algebra PART 2
 **/

/**
 ** remainder
 **/

void bigint_matrix::
remainder(const bigint_matrix & M, const bigint & mod)
{
  /**
   ** DESCRIPTION: RES.remainder(M,mod);
   **              => RES = M % mod,
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "remainder(const bigint_matrix &, const bigint &)", DV_BIGM);

  if (rows != M.rows)
    set_no_of_rows(M.rows);
  if (columns != M.columns)
    set_no_of_columns(M.columns);
  
  register lidia_size_t i, j;    
  register bigint *REStmp, *Mtmp;	

  for (i = 0; i < M.rows; i++)
    {
      REStmp = value[i];
      Mtmp = M.value[i];
      for (j = 0; j < M.columns; j++)
	::best_remainder(REStmp[j], Mtmp[j], mod);
    }	
}

/**
 ** boolean functions
 **/

bool bigint_matrix::
is_column_zero(lidia_size_t c) const
{
  /**
   ** DESCRIPTION: 1 == A.is_column_zero(c) <=> A.value[x][c] == 0, 
   **              x=0,...,rows-1
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "is_column_zero(lidia_size_t)", DV_BIGM + 2);
  
  if (c < 0 || c >= columns)
    lidia_error_handler_para(c, "c", "0 <= c < columns",
			     columns , "columns", "0 <= c < columns",
			     "bool bigint_matrix::"
			     "is_column_zero(lidia_size_t c) const",
			     DM_BIGM, LBIGM_ERROR[3]);

  register lidia_size_t i;

  for (i = 0; i < rows ; i++)
    {
      if (!value[i][c].is_zero())
	return false;
    }
  return true;
}

bool bigint_matrix::
is_row_zero(lidia_size_t r) const
{
  /**
   ** DESCRIPTION: 1 == A.is_row_zero(r) <=> A.value[r][x] == 0, 
   **              x=0,...,columns-1
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "is_row_zero(lidia_size_t)", DV_BIGM + 2);
  
  if (r < 0 || r >= rows)
    lidia_error_handler_para(r, "r", "0 <= r < rows",
			     rows, "rows", "0 <= r < rows",
			     "bool bigint_matrix::"
			     "is_row_zero(lidia_size_t r) const",
			     DM_BIGM, LBIGM_ERROR[3]);

  register lidia_size_t i;
  register bigint *tmp = value[r];

  for (i = 0; i < columns; i++)
    {
      if (!tmp[i].is_zero())
	return false;
    }
  return true;
}

bool bigint_matrix::
is_matrix_zero() const
{
  /**
   ** DESCRIPTION: 1 == A.is_matrix_zero() <=> A.value[x][y] == 0, 
   **              x=0,...,rows-1 and y=0,...,columns-1
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "is_matrix_zero()", DV_BIGM + 2);
  
  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (!tmp[j].is_zero())
	  return false;
    }
  return true;
}

/**
 ** norms and bounds
 **/

void bigint_matrix::
max(bigint &MAX) const
{ 
  /**
   ** DESCRIPTION: A.max(res);
   **              => res = maximum of all members of matrix A
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "max(bigint &)", DV_BIGM + 3);
  
  MAX.assign(value[0][0]);
  
  register lidia_size_t i, j;
  register bigint *tmp;
  
  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MAX < tmp[j])
	  MAX.assign(tmp[j]);
    }
}

void bigint_matrix::
max_abs(bigint &MAX) const
{
  /**
   ** DESCRIPTION: A.max_abs(res); 
   **              => res = absolute maximum of all members of matrix A
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "max_abs(bigint &)", DV_BIGM + 3);
  
  MAX.assign(abs(value[0][0]));

  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MAX < abs(tmp[j]))
	  MAX.assign(abs(tmp[j]));
    }
}

void bigint_matrix::
max_pos(bigint & MAX, lidia_size_t & x, lidia_size_t & y) const
{ 
  /**
   ** DESCRIPTION: A.max_pos(res, x, y);
   **              => res = maximum of all members of matrix A
   **              => res = A.value[x][y]
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "max_pos(bigint &, lidia_size_t &, lidia_size_t &)", DV_BIGM + 3);
  
  MAX.assign(value[0][0]);
  x = 0;
  y = 0;
  
  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MAX < tmp[j])
	  {
	    MAX.assign(tmp[j]);
	    x = i;
	    y = j;
	  }
    }
}

void bigint_matrix::
max_abs_pos(bigint & MAX, lidia_size_t & x, lidia_size_t & y) const
{
  /**
   ** DESCRIPTION: A.max_abs_pos(res, x, y); 
   **              => res = absolute maximum of all members of matrix A
   ++              => res = A.value[x][y]
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "max_abs_pos(bigint &, lidia_size_t &, lidia_size_t &)", DV_BIGM + 3);
  
  MAX.assign(abs(value[0][0]));
  x = 0;
  y = 0;

  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MAX < abs(tmp[j]))
	  {
	    MAX.assign(abs(tmp[j]));
	    x = i;
	    y = j;
	  }
    }
}

void bigint_matrix::
min(bigint &MIN) const
{
  /**
   ** DESCRIPTION: A.min(MIN); 
   **              => MIN = minimum of all members of matrix A
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "min(bigint &)", DV_BIGM + 3);
  
  MIN.assign(value[0][0]);

  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MIN > tmp[j])
	  MIN.assign(tmp[j]);
    }
}

void bigint_matrix::
min_abs(bigint &MIN) const
{
  /**
   ** DESCRIPTION: A.min_abs(MIN); 
   **              => MIN = absolute minimum of all members of matrix A
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "min_abs(bigint &)", DV_BIGM + 3);
  
  MIN.assign(abs(value[0][0]));

  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MIN > abs(tmp[j]))
	  MIN.assign(abs(tmp[j]));
    }
}

void bigint_matrix::
min_pos(bigint & MIN, lidia_size_t & x, lidia_size_t & y) const
{
  /**
   ** DESCRIPTION: A.min_pos(MIN, x, y); 
   **              => MIN = minimum of all members of matrix A
   **              => MIN = A.value[x][y] 
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "min_pos(bigint &, lidia_size_t &, lidia_size_t &)", DV_BIGM + 3);
  
  MIN.assign(value[0][0]);
  x = 0;
  y = 0;

  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MIN > tmp[j])
	  {
	    MIN.assign(tmp[j]);
	    x = i;
	    y = j;
	  }
    }
}

void bigint_matrix::
min_abs_pos(bigint & MIN, lidia_size_t & x, lidia_size_t & y) const
{
  /**
   ** DESCRIPTION: A.min_abs_pos(MIN, x, y); 
   **              => MIN = absolute minimum of all members of matrix A
   **              => MIN = A.value[x][y]
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "min_abs_pos(bigint &, lidia_size_t &, lidia_size_t &)", DV_BIGM + 3);
  
  MIN.assign(abs(value[0][0]));
  x = 0;
  y = 0;

  register lidia_size_t i, j;
  register bigint *tmp;

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	if (MIN > abs(tmp[j]))
	  {
	    MIN.assign(abs(tmp[j]));
	    x = i;
	    y = j;
	  }
    }
}

void bigint_matrix::
hadamard(bigint & H) const
{
  /**
   ** DESCRIPTION: A.hadamard(H);
   **              => H = hadamard bound of matrix A
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hadamard(bigint &)", DV_BIGM + 3);
  
  register lidia_size_t min, max, i, j;

  if (columns < rows)
    {
      min = columns;
      max = rows;
    }
  else
    {	
      min = rows;
      max = columns;
    }

  register bigint *tmp;
  bigint TMP;
  
  /* Step 1 - 11 */
  register bigint *h = new bigint[max];
  memory_handler(h, DM_BIGM, "hadamard :: "
		 "Error in memory allocation (h)");
  
  for (j = 0; j < columns; j++)
    {
      h[j].assign_zero();
      for (i = 0; i < rows; i++)
	{
	  ::multiply(TMP, value[i][j], value[i][j]);
	  ::add(h[j], h[j], TMP);
	}
    }
  
  /* Step 12 - 15 */
  j = 0;
  if (rows < columns)
    while (j < columns - 1)
      if (h[j] < h[j + 1])	
	{
	  ::swap(h[j], h[j + 1]);
	  j = 0;
	}				// h.sort(DOWN);
      else
	j++;
  
  /* Step 16 - 20 */
  bigint COLUMNS = 1;
  for (j = 0; j < min; j++)
    ::multiply(COLUMNS, COLUMNS, h[j]);
  
  /* Step 21 - 34 */
  for (i = 0; i < rows; i++)
    {
      h[i].assign_zero();
      tmp = value[i];
      for (j = 0; j < columns; j++)
	{
	  ::multiply(TMP, tmp[j], tmp[j]);
	  ::add(h[i], h[i], TMP);
	}
    }
  
  j = 0;
  if (rows > columns)
    while (j < rows - 1)
      if (h[j] < h[j + 1])   
	{
	  ::swap(h[j], h[j + 1]);
	  j = 0;
	}				// h.sort(DOWN);
      else
	j++;
  
  /* Step 35 - 39 */
  bigint ROWS;
  ROWS.assign_one();
  for (i = 0; i < min; i++)
    ::multiply(ROWS, ROWS, h[i]);
  
  delete[] h;
  
  /* Step 40 - 45 */
  bigint M;
  if (COLUMNS < ROWS)
    M.assign(COLUMNS);
  else
    M.assign(ROWS);
  
  register lidia_size_t B = M.bit_length() - 1;
  bigint E = ((bigint)B / bigint(2)) + bigint(2);
  power(H, bigint(2), E);
  dec(H);
}

void bigint_matrix::
hadamard2(bigint & H) const
{
  /**
   ** DESCRIPTION: A.hadamard2(H);
   **              => H = hadamard bound of matrix A
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hadamard2(bigint &)", DV_BIGM + 3);
  
  register lidia_size_t min = (columns < rows) ? columns : rows;
  register lidia_size_t i, j;

  /* Computation of row and column norms */
  register bigint *Wrows = new bigint[rows];
  memory_handler(Wrows, DM_BIGM, "hadamard :: "
		 "Error in memory allocation (Wrows)");
  
  register bigint *Wcolumns = new bigint[columns];
  memory_handler(Wcolumns, DM_BIGM, "hadamard :: "
		 "Error in memory allocation (Wcolumns)");

  register bigint *tmp;
  bigint TMP;	

  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	{
	  ::multiply(TMP, tmp[j], tmp[j]);
	  ::add(Wrows[i], Wrows[i], TMP);
	  ::add(Wcolumns[j], Wcolumns[j], TMP);
	}
    }
  
  /* Column evaluation */
  j = 0;
  if (rows < columns)
    while (j < columns - 1)
      if (Wcolumns[j] < Wcolumns[j + 1])	
	{
	  ::swap(Wcolumns[j], Wcolumns[j + 1]);
	  j = 0;
	}				// h.sort(DOWN);
      else
	j++;
  
  bigint COLUMNS;
  COLUMNS.assign_one();
  for (j = 0; j < min; j++)
    ::multiply(COLUMNS, COLUMNS, Wcolumns[j]);
  delete[] Wcolumns;
  
  /* Row evaluation */
  j = 0;
  if (rows > columns)
    while (j < rows - 1)
      if (Wrows[j] < Wrows[j + 1])   
	{
	  ::swap(Wrows[j], Wrows[j + 1]);
	  j = 0;
	}				// h.sort(DOWN);
      else
	j++;

  bigint ROWS;
  ROWS.assign_one();
  for (i = 0; i < min; i++)
    ::multiply(ROWS, ROWS, Wrows[i]);
  delete[] Wrows;
  
  /* Hadamard */  
  register lidia_size_t B = ((COLUMNS < ROWS) ? COLUMNS.bit_length() : ROWS.bit_length()) - 1;
  power(H, bigint(2), (B / (lidia_size_t) 2) + (lidia_size_t) 2);
  dec(H);
}

void bigint_matrix::
row_norm(bigint & RES, lidia_size_t pos, long art) const
{
  debug_handler_l(DM_BIGM, "in member - function "
		  "row_norm(bigint &, lidia_size_t, long)", DV_BIGM + 3);

  RES.assign_zero();

  bigint TMP;
  register bigint *tmp = value[pos];

  if (pos < 0 || pos >= rows)
    lidia_error_handler_para(pos, "pos", "0 <= pos < rows",
			     rows, "rows", "",
			     "void bigint_matrix::"
                             "row_norm(bigint & RES, lidia_size_t pos, long art) const",
			     DM_BIGM, LBIGM_ERROR[1]);

  register lidia_size_t i;

  if (art != 0)
    for (i = 0; i < columns; i++)
      {
	power(TMP, tmp[i], art);
	::add(RES, RES, abs(TMP));
      }
  else
     for (i = 0; i < columns; i++)
      {
	if (!tmp[i].is_zero())
	  RES++;
      }
}

void bigint_matrix::
column_norm(bigint & RES, lidia_size_t pos, long art) const
{
  debug_handler_l(DM_BIGM, "in member - function "
		  "column_norm(bigint &, lidia_size_t, long)", DV_BIGM + 3);
  
  RES.assign_zero();

  bigint TMP;

  if (pos < 0 || pos >= columns)
    lidia_error_handler_para(pos, "pos", "0 <= pos < columns",
			     columns, "columns", "",
			     "void bigint_matrix::"
                             "column_norm(bigint & RES, lidia_size_t pos, long art) const",
			     DM_BIGM, LBIGM_ERROR[1]);

  register lidia_size_t i;
  if ( art != 0)
    for (i = 0; i < rows; i++)
      {
	power(TMP, value[i][pos], art);
	::add(RES, RES, abs(TMP));
      }
  else
    for (i = 0; i < rows; i++)
      {
	if (!value[i][pos].is_zero())
	  RES++;	
      }
}

/**
 ** randomize
 **/

void bigint_matrix::
randomize(const bigint & S)
{
  /**
   ** DESCRIPTION: RES.randomize(S);
   **              => 0 <= RES.value[i][j] <= S, i=0,...,RES.rows-1,
   **                 j=0,...,RES.columns-1; random values
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "randomize(const bigint &)", DV_BIGM + 4);
  
  register lidia_size_t i, j;
  register bigint *tmp;
  
  seed(S);
  
  for (i = 0; i < rows; i++)
    {
      tmp = value[i];
      for (j = 0; j < columns; j++)
	tmp[j].assign(::randomize(S));
    }
}

void bigint_matrix::
randomize_with_det(const bigint & S, const bigint & DET)
{
  /**
   ** DESCRIPTION: RES.randomize_with_det(S, DET);
   **              => 0 <= RES.value[i][j] <= S, i=0,...,RES.rows-1,
   **                 j=0,...,RES.columns-1; random values
   **              => det(RES) == DET
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "randomize_with_det(const bigint &, const bigint &)", DV_BIGM + 4);
  
  if (rows != columns)
    lidia_error_handler_para(rows, "rows", "rows == columns",
			     columns, "columns", "rows == columns",
			     "void bigint_matrix::"
			     "randomize_with_det(const bigint & S, const bigint & DET)",
			     DM_BIGM, LBIGM_ERROR[7]);
  
  register lidia_size_t i, j;
  
  bigint_matrix T(rows, columns), T1(rows, columns);
  seed(S);
  
  register bigint *tmp = NULL, *tmp1;
  for (i = 0; i < rows; i++)
    {
      tmp = T.value[i];
      tmp1 = T1.value[i];
      for (j = i+1; j < columns; j++)
	tmp[j].assign(::randomize(S));
      for (j = 0; j < i; j++)
	tmp1[j].assign(::randomize(S));
      tmp[i].assign_one();
      tmp1[i].assign_one();
    }
  i = (lidia_size_t) random() % rows;
  T.value[i][i].assign(DET);
  multiply(T, T1);
}

/**
 ** Chinese remaindering theorem
 **/

void
chinrest(bigint & RES, const bigint * values, const bigint * prim)
{
  /**
   ** DESCRIPTION: chinrest(Res,v,prim);
   **              => Res = solution of the Chinese remaindering theorem
   **                 with parameters v and prim
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in function "
		  "chinrest(bigint &, const bigint *, const bigint *)", DV_BIGM + 5);
  
  register lidia_size_t i;
  bigint M, mod;
  bigint TMP, TMP0, TMP1, TMP2;
  
  long len;
  prim[0].longify(len);
  
  register bigint e;
  
  /* Step 1 */
  bigint L = 1;
  for (i = 0; i < len; i++)
    ::multiply(L, L, prim[i + 1]);

  bigint X;
  for (i = 0; i < len; i++)
    {
      mod.assign(prim[i + 1]);
      div_rem(M, TMP, L, mod);
      best_remainder(TMP, M, mod);
      xgcd(TMP0, TMP1, mod, TMP);
      ::multiply(e, TMP1, M);
      ::multiply(TMP, e, values[i]);
      ::add(X, X, TMP);
    }

  best_remainder(X, X, L);
  ::subtract(TMP0, L, bigint(1));
  shift_left(TMP1, X, 1);
  shift_left(TMP2, L, 1);

  if (TMP1 >= -TMP0 && TMP1 <= TMP0)
    RES.assign(X);
  else if (TMP1 - TMP2 <= TMP0 || TMP1 - TMP2 >= -TMP0)
    ::subtract(RES, X, L);
  else
    lidia_error_handler_para("void chinrest(bigint & RES, const bigint * values, const bigint * prim)", 
			     DM_BIGM, LBIGM_ERROR[8]);
}

void bigint_matrix::
chinrest(const bigint_matrix * v, const bigint * prim)
{
  debug_handler_l(DM_BIGM, "in member - function "
		  "chinrest(const bigint_matrix *, const bigint *)", DV_BIGM + 5);
  
  register lidia_size_t i, j, l;

  long len;
  prim[0].longify(len);
  bigint M, X, mod;
  bigint TMP, TMP0, TMP1, TMP2;
  bigint dummy;
  
  register bigint *e = new bigint[len];
  memory_handler(e, DM_BIGM, "chinrest :: "
		 "Error in memory allocation (e)");
  lidia_size_t r = v[0].rows;
  lidia_size_t c = v[0].columns;

  /* new dimensions */
  if (columns != c)
    set_no_of_columns(c);
  if (rows != r)
    set_no_of_rows(r);

  register bigint *m = new bigint[len];
  memory_handler(m, DM_BIGM, "chinrest :: "
		 "Error in memory allocation (m)");

  /* Step 1 */
  bigint L = 1;
  for (i = 0; i < len; i++)
    ::multiply(L, L, prim[i + 1]);

  for (i = 0; i < len; i++)
  {
    mod.assign(prim[i + 1]);
    ::divide(M, L, mod);
    best_remainder(TMP, M, mod);
    dummy = xgcd(TMP0, TMP1, mod, TMP);
    ::multiply(e[i], TMP1, M);
  }

  for (i = 0; i < r; i++)
    for (j = 0; j < c; j++)
      {
	X.assign_zero();

	for (l = 0; l < len; l++)
	  m[l].assign(v[l].value[i][j]);

	for (l = 0; l < len; l++)
	  {
	    ::multiply(TMP, e[l], m[l]);
	    ::add(X, X, TMP);
	    }
	best_remainder(X, X, L);
	::subtract(TMP0, L, bigint(1));
	shift_left(TMP1, X, 1);
	shift_left(TMP2, L, 1);
	if (!(TMP1 >= -TMP0 && TMP1 <= TMP0))
	  if (TMP1 - TMP2 <= TMP0 || TMP1 - TMP2 >= -TMP0)
	    ::subtract(X, X, L);
	  else
	    lidia_error_handler_para("void bigint_matrix::"
				     "chinrest(const bigint_matrix * v, const bigint * prim)", 
				     DM_BIGM, LBIGM_ERROR[8]);
	value[i][j].assign(X);
      }
  delete[] e;
  delete[] m;
}

/**
 ** List of primes
 **/

bigint *
get_primes(const bigint & C, const bigint & m)
{
  /**
   ** DESCRIPTION: get_primes(C,m) = v;
   **              => v = List of primes
   **              => !(m | v[i]) for all i=1,...,v[0]
   **              => v[1]*...*v[v[0]] > C
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in function "
		  "get_primes(bigint, bigint)", DV_BIGM + 6);
  
  register bigint *List = NULL;	
  
  /* GET ENV Variable */
  static lidia_size_t ANZAHL = 0;
  static char *LIDIA_PRIMES_NAME = NULL;
  
  if (ANZAHL == 0)
    {
      char *LIDIA_PRIMES_SIZE = getenv("LIDIA_PRIMES_SIZE");
      if (LIDIA_PRIMES_SIZE == NULL)
	ANZAHL = LIDIA_PRIMELIST_SIZE;
      else
	{
	  ANZAHL = atoi(LIDIA_PRIMES_SIZE);
	  if (LIDIA_PRIMES_SIZE != getenv("LIDIA_PRIMES_SIZE"))
	    delete[] LIDIA_PRIMES_SIZE;
	}
    }
  
  if (LIDIA_PRIMES_NAME == NULL)
    {
      LIDIA_PRIMES_NAME = getenv("LIDIA_PRIMES_NAME");
      if (LIDIA_PRIMES_NAME == NULL)
	{
	  LIDIA_PRIMES_NAME = new char[256];
	  strcpy(LIDIA_PRIMES_NAME, LIDIA_PRIMELIST_NAME);
	}
    }
  
  lidia_size_t i = 0, j, PRObits = 0;
  lidia_size_t Hbits = C.bit_length();
  
  register bigint *p = new bigint[ANZAHL];
  memory_handler(p, DM_BIGM, "get_primes :: "
		 "Error in memory allocation (p)");
  
  ifstream in(LIDIA_PRIMES_NAME);
  if (in.fail() || in.bad())
    lidia_error_handler_para(LIDIA_PRIMES_NAME, "LIDIA_PRIMES_NAME", "",
			     "bigint *get_primes(const bigint & C, const bigint & m)", 
			     DM_BIGM, LBIGM_ERROR[9]);
  
  while (PRObits < Hbits && i < ANZAHL)
    {
      in >> p[i];
      if (m % p[i] != 0)
	{
	  PRObits += p[i].bit_length();	    /* multiply(PRO, PRO, bigint(p[i])) */
	  i++;
	}
    }
  if (PRObits < Hbits)
    {
      delete[] p;
      
      if (ANZAHL == LIDIA_PRIMELIST_MAXSIZE)
	lidia_error_handler_para(DM_BIGM, "get_primes :: Primlist too small !!");
      
      ANZAHL *= 2;
      if (ANZAHL > LIDIA_PRIMELIST_MAXSIZE)
 	ANZAHL = LIDIA_PRIMELIST_MAXSIZE;
      cout << DM_BIGM << ":: get_primes :: " <<
	"Size of primelist increased ! " << endl << flush;
      cout << "new size: " << ANZAHL << endl << flush;
      
      List = get_primes(C, m);
    }
  else
    {
      List = new bigint[i + 1];
      memory_handler(List, DM_BIGM, "get_primes :: "
		     "Error in memory allocation (List)");
      List[0].assign(i);
      for (j = 0; j < i; j++)
	List[j + 1].assign(p[j]);
      delete[] p;
    }
  return List;
}

/**
 ** BEGIN: Linear algebra
 ** PART 1
 **/

/**
 ** rank
 **/

lidia_size_t bigint_matrix::
rank() const
{
  /**
   ** DESCRIPTION: A.rank() = Rank of matrix A.
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "rank()", DV_BIGM + 7);
  
  register lidia_size_t i;
  
  /* Step 1 */
  bigint H;
  hadamard(H);
  
  /* Step 2 */
  register bigint *PRIM = get_primes((bigint)2 * H, (bigint)1);
  long Number_of_primes;
  PRIM[0].longify(Number_of_primes);
  
  lidia_size_t RANG = 0;
  
  /* Step 3,4 */
  lidia_size_t v;
  for (i = 1; i <= Number_of_primes; i++)
    {
      v = rank_intern(value, rows, columns, PRIM[i]);
      if (RANG < v)
	RANG = v;
    }
  delete[] PRIM;
  
  /* Step 5,6 */
  return RANG;
}

/**
 ** rank and linearly independent rows
 **/

lidia_size_t *bigint_matrix::
lininr() const
{
  /**
   ** DESCRIPTION: RES[0] = Rank of matrix (Avalue,r,c).
   **              RES[1],...,RES[RES[0]], such that
   **              row(RES[1]),...,row(RES[RES[0]])
   **              of matrix *this are linearly independent.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "lininr()", DV_BIGM + 7);
  
  register lidia_size_t i, j;
  lidia_size_t *tmp;
  
  /* Step 1 */
  bigint H;
  hadamard(H);
  
  /* Step 2 */
  register bigint *PRIM = get_primes((bigint)2 * H, (bigint)1);
  long Number_of_primes;
  PRIM[0].longify(Number_of_primes);
  lidia_size_t RANG = 0;
  
  /* Step 3,4 */
  lidia_size_t **A = new lidia_size_t *[Number_of_primes];
  memory_handler(A, DM_BIGM, "lininr :: "
		 "Error in memory allocation (A)");
  
  for (i = 0; i < Number_of_primes; i++)
    {
      tmp = new lidia_size_t[rows + 1];
      memory_handler(tmp, DM_BIGM, "lininr :: "
		     "Error in memory allocation (tmp)");
      for (j = 0; j <= rows; j++)
	tmp[j] = 0;
      A[i] = tmp;
    }
  lidia_size_t *v = NULL;
  
  for (i = 1; i <= Number_of_primes; i++)
    {
      v = lininr_intern(value, rows, columns, PRIM[i]);
      if (RANG < v[0])
	RANG = v[0];
      if (RANG == columns || RANG == rows)
	Number_of_primes = i;
      for (j = 0; j < v[0]; j++)
	A[i - 1][j] = v[j + 1];
      delete[] v;
    }
  delete[] PRIM;
  
  /* Step 5,6 */
  lidia_size_t *RES = new lidia_size_t[RANG + 1];
  memory_handler(RES, DM_BIGM, "lininr :: "
		 "Error in memory allocation (RES)");
  RES[0] = RANG;
  lidia_size_t TMP1;
  for (i = 0; i < RANG; i++)
    {
      TMP1 = 0;
      for (j = 0; j < Number_of_primes; j++)
	if (TMP1 < A[j][i])
	  TMP1 = A[j][i];
      RES[i + 1] = TMP1;
    }
  for (i = 0; i < Number_of_primes; i++)
    delete[] A[i];
  delete[] A;
  return RES;
}

void bigint_matrix::
lininr(base_vector < lidia_size_t > &RES) const
{
  /**
   ** DESCRIPTION: RES[0] = Rank of matrix (Avalue,r,c).
   **              RES[1],...,RES[RES[0]], such that
   **              row(RES[1]),...,row(RES[RES[0]])
   **              of matrix *this are linearly independent.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "lininr(base_vector < lidia_size_t > &)", DV_BIGM + 7);
  
  register lidia_size_t i, j;
  lidia_size_t *tmp;
  
  /* Step 1 */
  bigint H;
  hadamard(H);
  
  /* Step 2 */
  register bigint *PRIM = get_primes((bigint)2 * H, (bigint)1);
  long Number_of_primes;
  PRIM[0].longify(Number_of_primes);
  lidia_size_t RANG = 0;
  
  /* Step 3,4 */
  lidia_size_t **A = new lidia_size_t *[Number_of_primes];
  memory_handler(A, DM_BIGM, "lininr :: "
		 "Error in memory allocation (A)");
  
  for (i = 0; i < Number_of_primes; i++)
    {
      tmp = new lidia_size_t[rows + 1];
      memory_handler(tmp, DM_BIGM, "lininr :: "
		     "Error in memory allocation (tmp)");
      for (j = 0; j <= rows; j++)
	tmp[j] = 0;
      A[i] = tmp;
    }
  lidia_size_t *v = NULL;
  
  for (i = 1; i <= Number_of_primes; i++)
    {
      v = lininr_intern(value, rows, columns, PRIM[i]);
      if (RANG < v[0])
	RANG = v[0];
      if (RANG == columns || RANG == rows)
	Number_of_primes = i;
      for (j = 0; j < v[0]; j++)
	A[i - 1][j] = v[j + 1];
      delete[] v;
    }
  delete[] PRIM;
  
  /* Step 5,6 */
  if (RES.capacity() < RANG + 1)
    RES.set_capacity(RANG + 1);
  if (RES.size() != RANG + 1)
    RES.set_size(RANG + 1);

  RES[0] = RANG;
  lidia_size_t TMP1;
  for (i = 0; i < RANG; i++)
    {
      TMP1 = 0;
      for (j = 0; j < Number_of_primes; j++)
	if (TMP1 < A[j][i])
	  TMP1 = A[j][i];
      RES[i + 1] = TMP1;
    }
  for (i = 0; i < Number_of_primes; i++)
    delete[] A[i];
  delete[] A;
}

lidia_size_t *bigint_matrix::
lininr2() const
{
  /**
   ** DESCRIPTION: RES[0] = Rank of matrix (Avalue,r,c).
   **              RES[1],...,RES[RES[0]], such that
   **              row(RES[1]),...,row(RES[RES[0]])
   **              of matrix *this are linearly independent.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "lininr2()", DV_BIGM + 7);
  
  register lidia_size_t i, j;
  
  /* Step 1 */
  bigint H;
  hadamard(H);
  
  /* Step 2 */
  register bigint *PRIM = get_primes((bigint)2 * H, (bigint)1);
  long Number_of_primes;
  PRIM[0].longify(Number_of_primes);
  
  /* Step 3,4 */
  lidia_size_t *res_vector = new lidia_size_t[rows + 1];
  memory_handler(res_vector, DM_BIGM, "lininr :: "
		 "Error in memory allocation (res_vector)");
  for (i = 0; i<= rows; res_vector[i] = 0, i++);
  
  lidia_size_t *v = NULL;
  
  for (i = 1; i <= Number_of_primes; i++)
    {
      v = lininr_intern(value, rows, columns, PRIM[i]);
      if (res_vector[0] < v[0])
	res_vector[0] = v[0];
      for (j = 1; j <= v[0]; j++)
	if (res_vector[j] < v[j])
	  res_vector[j] = v[j];
      delete[] v;
    }
  delete[] PRIM;
  
  /* Step 5,6 */
  lidia_size_t *RES = new lidia_size_t[res_vector[0] + 1];
  memory_handler(RES, DM_BIGM, "lininr :: "
		 "Error in memory allocation (RES)");

  for (i = 0; i < res_vector[0]; RES[i] = res_vector[i], i++);
  delete[] res_vector;
  return RES;
}

/**
 ** rank linearly independent columns
 **/

lidia_size_t *bigint_matrix::
lininc() const
{
  /**
   ** DESCRIPTION: RES[0] = Rank of matrix (Avalue,r,c).
   **              RES[1],...,RES[RES[0]], such that
   **              column(RES[1]),...,column(RES[RES[0]])
   **              of matrix *this are linearly independent.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "lininc()", DV_BIGM + 7);
  
  register lidia_size_t i, j;
  lidia_size_t *tmp;

  /* Step 1 */
  bigint H;
  hadamard(H);
  
  /* Step 2 */
  register bigint *PRIM = get_primes((bigint)2 * H, 1);
  long Number_of_primes;
  PRIM[0].longify(Number_of_primes);

  lidia_size_t RANG = 0;

  /* Step 3,4 */
  lidia_size_t **A = new lidia_size_t *[Number_of_primes];
  memory_handler(A, DM_BIGM, "lininc :: "
		 "Error in memory allocation (A)");
  
  for (i = 0; i < Number_of_primes; i++)
    {
      tmp = new lidia_size_t[columns + 1];
      memory_handler(tmp, DM_BIGM, "lininc :: "
		     "Error in memory allocation (tmp)");
      for (j = 0; j <= columns; j++)
	tmp[j] = 0;
      A[i] = tmp;
    }
  
  lidia_size_t *v = NULL;
  for (i = 1; i <= Number_of_primes; i++)
    {
      v = lininc_intern(value, rows, columns, PRIM[i]);
      if (RANG < v[0])
	RANG = v[0];
      if (RANG == rows || RANG == columns)
	Number_of_primes = i;
      for (j = 0; j < v[0]; j++)
	A[i - 1][j] = v[j + 1];
    }
  delete[] v;
  delete[] PRIM;
  
  /* Step 5,6 */
  lidia_size_t *RES = new lidia_size_t[RANG + 1];
  memory_handler(RES, DM_BIGM, "lininc :: "
		 "Error in memory allocation (RES)");
  RES[0] = RANG;
  lidia_size_t TMP1;
  for (i = 0; i < RANG; i++)
    {
      TMP1 = 0;
      for (j = 0; j < Number_of_primes; j++)
	if (TMP1 < A[j][i])
	  TMP1 = A[j][i];
      RES[i + 1] = TMP1;
    }
  for (i = 0; i < Number_of_primes; i++)
    delete[] A[i];
  delete[] A;
  return RES;
}

void bigint_matrix::
lininc(base_vector < lidia_size_t > &RES) const
{
  /**
   ** DESCRIPTION: RES[0] = Rank of matrix (Avalue,r,c).
   **              RES[1],...,RES[RES[0]], such that
   **              column(RES[1]),...,column(RES[RES[0]])
   **              of matrix *this are linearly independent.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "lininc(base_vector < lidia_size_t > &)", DV_BIGM + 7);
  
  register lidia_size_t i, j;
  lidia_size_t *tmp;

  /* Step 1 */
  bigint H;
  hadamard(H);
  
  /* Step 2 */
  register bigint *PRIM = get_primes((bigint)2 * H, 1);
  long Number_of_primes;
  PRIM[0].longify(Number_of_primes);

  lidia_size_t RANG = 0;

  /* Step 3,4 */
  lidia_size_t **A = new lidia_size_t *[Number_of_primes];
  memory_handler(A, DM_BIGM, "lininc :: "
		 "Error in memory allocation (A)");
  
  for (i = 0; i < Number_of_primes; i++)
    {
      tmp = new lidia_size_t[columns + 1];
      memory_handler(tmp, DM_BIGM, "lininc :: "
		     "Error in memory allocation (tmp)");
      for (j = 0; j <= columns; j++)
	tmp[j] = 0;
      A[i] = tmp;
    }
  
  lidia_size_t *v = NULL;
  for (i = 1; i <= Number_of_primes; i++)
    {
      v = lininc_intern(value, rows, columns, PRIM[i]);
      if (RANG < v[0])
	RANG = v[0];
      if (RANG == rows || RANG == columns)
	Number_of_primes = i;
      for (j = 0; j < v[0]; j++)
	A[i - 1][j] = v[j + 1];
    }
  delete[] v;
  delete[] PRIM;
  
  /* Step 5,6 */
  if (RES.capacity() < RANG + 1)
    RES.set_capacity(RANG + 1);
  if (RES.size() != RANG + 1)
    RES.set_size(RANG + 1);

  RES[0] = RANG;
  lidia_size_t TMP1;
  for (i = 0; i < RANG; i++)
    {
      TMP1 = 0;
      for (j = 0; j < Number_of_primes; j++)
	if (TMP1 < A[j][i])
	  TMP1 = A[j][i];
      RES[i + 1] = TMP1;
    }
  for (i = 0; i < Number_of_primes; i++)
    delete[] A[i];
  delete[] A;
}

/**
 ** regular expansion
 **/

void bigint_matrix::
regexpansion(const lidia_size_t * v)
{
  /**
   ** DESCRIPTION: A.regexpansion(v);
   **              => A = Regular Expansion of the old matrix A relative v.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "regexpansion(const lidia_size_t *)", DV_BIGM + 7);

  register lidia_size_t k = v[0];
  if (columns > k)
    {
      register lidia_size_t i=0, j=0;
      lidia_size_t diff = columns - rows;
      bigint_matrix A(*this);
      set_no_of_rows(columns);
      set_no_of_columns(columns);
      bigint_matrix ERG(diff, columns);
      while (i < columns && k > 0 && j < diff)
	{
	  if (i != v[k])
	    {
	      ERG.value[j][i].assign_one();
	      j++;
	    }
	  else
	    k--;
	  i++;
	}
      compose_v(ERG, A);
    }
}

/**
 ** adjoint matrix
 **/

void bigint_matrix::
adj(const bigint_matrix & A)
{
  /**
   ** DESCRIPTION: B.adj(A);
   **              => B = adjoint matrix of matrix A
   ** ERROR: A.columns != A.rows
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in function "
		  "adj(const bigint_matrix &)", DV_BIGM + 7);
  
  register lidia_size_t i, j, z1, z2;
  
  if (A.columns != A.rows)
    lidia_error_handler_para(A.columns, "A.columns", "A.columns == A.rows",
			     A.rows, "A.rows", "A.columns == A.rows",
			     "void bigint_matrix::"
			     "adj(const bigint_matrix & A)", DM_BIGM, LBIGM_ERROR[7]);

  register bigint **Bbigint = new bigint *[A.rows];
  long **Blong = new long *[A.rows];
  
  memory_handler(Bbigint, DM_BIGM, "adj :: "
		 "Error in memory allocation (Bbigint)");
  memory_handler(Blong, DM_BIGM, "adj :: "
		 "Error in memory allocation (Blong)");
  
  register bigint *tmp, *Atmp;
  long *tmp1;
  
  for (i = 0; i < A.rows; i++)
    {
      tmp = new bigint[A.rows];
      tmp1 = new long[A.rows];
      memory_handler(tmp, DM_BIGM, "adj :: "
		     "Error in memory allocation (tmp)");
      memory_handler(tmp1, DM_BIGM, "adj :: "
		     "Error in memory allocation (tmp1)");
      for (j = 0; j < A.rows; j++)
	{
	  tmp[j].assign_zero();
	  tmp1[j] = 0;
	}
      
      Bbigint[i] = tmp;
      Blong[i] = tmp1;
    }

  /* Step 1 */
  bigint H = A.hadamard();

  /* Step 2,3 */
  register bigint *PRIM = get_primes((bigint)2*H, A.det());
  long n;
  PRIM[0].longify(n);

  /* Step 4 */
  bigint MOD;
  long Modlong;
  bigint_matrix *chininput = new bigint_matrix[n];
  memory_handler(chininput, DM_BIGM, "adj :: "
		 "Error in memory allocation (chininput)");
  for (i = 1; i <= n; i++)
    {
      MOD.assign(PRIM[i]);
      if (MOD.bit_length() > bigint::bits_per_digit())
	{
	  for (z1 = 0; z1 < A.rows; z1++)
	    {
	      tmp = Bbigint[z1];
	      Atmp = A.value[z1];
	      for (z2 = 0; z2 < A.columns; z2++)
		best_remainder(tmp[z2], Atmp[z2], MOD);
	    }
	  
	  adj_intern(Bbigint, A.rows, MOD);
	  
	  chininput[i - 1].set_no_of_columns(A.columns);
	  chininput[i - 1].set_no_of_rows(A.rows);
	  
	  for (z1 = 0; z1 < A.rows; z1++)
	    {
	      tmp = Bbigint[z1];
	      Atmp = chininput[i-1].value[z1];
	      for (z2 = 0; z2 < A.columns; z2++)
		Atmp[z2].assign(tmp[z2]);
	    }
	}
      else
	{
	  MOD.longify(Modlong);
	  for (z1 = 0; z1 < A.rows; z1++)
	    {
	      tmp1 = Blong[z1];
	      Atmp = A.value[z1];
	      for (z2 = 0; z2 < A.columns; z2++)
		best_remainder_long(tmp1[z2], Atmp[z2], Modlong);
	    }
	  
	  adj_intern(Blong, A.rows, Modlong);
	  
	  chininput[i - 1].set_no_of_columns(A.columns);
	  chininput[i - 1].set_no_of_rows(A.rows);
	  
	  for (z1 = 0; z1 < A.rows; z1++)
	    {
	      tmp1 = Blong[z1];
	      Atmp = chininput[i-1].value[z1];
	      for (z2 = 0; z2 < A.columns; z2++)
		Atmp[z2].assign(tmp1[z2]);
	    }
	}
    }
  
  /* Step 5 */
  chinrest(chininput, PRIM);
  for (i = 0; i < A.rows; i++)
    {
      delete[] Bbigint[i];
      delete[] Blong[i];
    }
  delete[] PRIM;
  delete[] Bbigint;
  delete[] Blong;
  delete[] chininput;
}

/**
 ** lattice determinant
 **/

void bigint_matrix::
latticedet1(bigint & DET) const
{
  /**
   ** INPUT: *this, DET
   ** DESCRIPTION: A.latticedet1(DET);
   **              => DET = lattice determinant
   **              of the lattice formed by the columns of matrix A
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "latticedet1(bigint &)", DV_BIGM + 7);
  
  register lidia_size_t i, j;
  register bigint *tmp, *tmp1;
  
  /* Step 1 */
  bigint_matrix B(*this);
  lidia_size_t *linuz = B.lininr();
  lidia_size_t r = linuz[0];
  
  /* Step 2 */
  bigint_matrix C(r, columns);
  for (i = 0; i < r; i++)
    {
      tmp = C.value[i];
      tmp1 = value[linuz[i+1]];
      for (j = 0; j < columns; j++)
	tmp[j].assign(tmp1[j]);
    }
  
  /* Step 3 */
  if (r == columns)
    C.det(DET);
  else
    {
      bigint_matrix H1 = C.trans();
      
      lidia_size_t *linuz1 = H1.lininr();
      
      bigint_matrix D(r, r);
      for (i = 0; i < r; i++)
	for (j = 0; j < r; j++)
	  D.value[j][i].assign(C.value[j][linuz1[i + 1]]);
      D.det(DET);
      delete[] linuz1;
    }
  delete[] linuz;
  if (DET.is_lt_zero())
    DET.negate();
}

void bigint_matrix::
latticedet2(bigint & DET) const
{
  /**
   ** INPUT: *this,DET
   ** DESCRIPTION: A.latticedet2(DET);
   **              => DET = lattice determinant
   **              of the lattice formed by the columns of matrix A
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "latticedet2(bigint &)", DV_BIGM + 7);

  register lidia_size_t i, j;
  bigint TMP, TMP1, *tmp, *tmp1, *tmp2;
  
  /* Step 1 */
  lidia_size_t *linu = lininr();
  lidia_size_t r = linu[0];
  
  /* Step 2 */
  bigint_matrix C(r, columns);
  bigint_matrix C1(r, columns);
  
  for (i = 0; i < r; i++)
    {
      tmp = C.value[i];
      tmp1 = value[linu[i+1]];
      tmp2 = C1. value[i];
      for (j = 0; j < columns; j++)
	{
	  tmp[j].assign(tmp1[j]);
	  tmp2[columns - 1 - j].assign(tmp1[j]);
	}
    }
  delete[] linu;
  
  /* Step 3 */
  if (r == columns)
    C.det(DET);
  else
    {
      linu = C.lininc();
      bigint_matrix D(r, r);
      for (i = 0; i < r; i++)
	for (j = 0; j < r; j++)
	  D.value[j][i].assign(C.value[j][linu[i + 1]]);
      D.det(TMP);
      delete[] linu;
      
      linu = C1.lininc();
      for (i = 0; i < r; i++)
	for (j = 0; j < r; j++)
	  D.value[j][i].assign(C1.value[j][linu[i + 1]]);
      D.det(TMP1);
      delete[] linu;
      DET = gcd(TMP, TMP1);
    }
  if (DET.is_lt_zero())
    DET.negate();
}

void bigint_matrix::
latticedet3(bigint & DET) const
{
  /**
   ** INPUT: *this,DET
   ** DESCRIPTION: A.latticedet3(DET);
   **              => DET = lattice determinant
   **              of the lattice formed by the columns of matrix A
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "latticedet3(bigint &)", DV_BIGM + 7);

  register lidia_size_t i, j;
  register bigint *tmp, *tmp1;

  /* Step 1 */
  lidia_size_t *linuz = lininr();
  lidia_size_t r = linuz[0];

  /* Step 2 */
  if (r == columns)
    {
      bigint_matrix C(r, columns);
      for (i = 0; i < r; i++)
	{
	  tmp = C.value[i];
	  tmp1 = value[linuz[i+1]];
	  for (j = 0; j < columns; j++)
	    tmp[j].assign(tmp1[j]);
	}
      C.det(DET);
    }
  else
    { 
      lidia_size_t *linuz1 = lininc();
      
      bigint_matrix D(r, r);
      for (i = 0; i < r; i++)
	for (j = 0; j < r; j++)
	  D.value[j][i].assign(value[linuz[j+1]][linuz1[i + 1]]);
      D.det(DET);
      delete[] linuz1;
    }
  delete[] linuz;
  if (DET.is_lt_zero())
    DET.negate();
}

/**
 ** determinant
 **/

void bigint_matrix::
det(bigint & DET) const
{
  /**
   ** DESCRIPTION: A.det(DET)
   **              => DET = determinant of matrix A
   ** ERROR: columns != rows
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "det(bigint &)", DV_BIGM + 7);

  if (rows != columns)
    lidia_error_handler_para(rows, "rows", "rows == columns",
			     columns, "columns", "rows == columns",
			     "void bigint_matrix::"
			     "det(bigint & DET) const",
			     DM_BIGM, LBIGM_ERROR[7]);
  
  register lidia_size_t i, j, z1, z2;
  register bigint **Bbigint;
  long **Blong;
  
  Bbigint = new bigint *[rows];
  Blong = new long *[rows];
  memory_handler(Bbigint, DM_BIGM, "det :: "
		 "Error in memory allocation (Bbigint)");
  memory_handler(Blong, DM_BIGM, "det :: "
		 "Error in memory allocation (Blong)");

  const bigint *consttmp;
  bigint *tmp;
  long *tmp1;
  for (i = 0; i < rows; i++)
  {
    Bbigint[i] = new bigint[columns];
    Blong[i] = new long[columns];
    memory_handler(Bbigint[i], DM_BIGM, "det :: "
		   "Error in memory allocation (Bbigint[i])");
    memory_handler(Blong[i], DM_BIGM, "det :: "
		   "Error in memory allocation (Blong[i])");
  }

  /* Step 1 */
  bigint H = hadamard();
  shift_left(H, H, 1);

  /* Step 2 */
  bigint *PRIM = get_primes(H, bigint(1));
  long n;
  PRIM[0].longify(n);

  bigint MOD;
  long Modlong, Detlong;

  /* Step 3 */
  bigint *chininput = new bigint[n];
  memory_handler(chininput, DM_BIGM, "det :: "
		 "Error in memory allocation (chininput)");
  for (i = 1; i <= n; i++)
    {
      MOD.assign(PRIM[i]);
      if (MOD.bit_length() >= bigint::bits_per_digit())
	{
	  for (z1 = 0; z1 < rows; z1++)
	    {
	      consttmp = value[z1];
	      tmp = Bbigint[z1];
	      for (z2 = 0; z2 < columns; z2++)
		best_remainder(tmp[z2], consttmp[z2], MOD);
	    }
	  det_intern(chininput[i - 1], Bbigint, rows, MOD);
	}
      else
	{
	  MOD.longify(Modlong);
	  for (z1 = 0; z1 < rows; z1++)
	    {
	      consttmp = value[z1];
	      tmp1 = Blong[z1];
	      for (z2 = 0; z2 < columns; z2++)
		best_remainder_long(tmp1[z2], consttmp[z2], Modlong);
	    }
	  Detlong = det_intern(Blong, rows, Modlong);
	  chininput[i - 1].assign(Detlong);
	}
    }
  for (j = 0; j < rows; j++)
    {
      delete[] Bbigint[j];
      delete[] Blong[j];
    }
  delete[] Bbigint;
  delete[] Blong;
  
  /* Step 4 */
  ::chinrest(DET, chininput, PRIM);
  delete[] chininput;
  delete[] PRIM;
}

/**
 ** characteristic polynomial
 **/

bigint *bigint_matrix::
charpoly() const
{
  /**
   ** DESCRIPTION: RES = A.charpoly();
   **              => RES[0],...,RES[r] are the coefficients of
   **                 the characteristic polynomial of matrix A
   ** ERROR: rows != columns
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "charpoly()", DV_BIGM + 7);

  if (columns != rows)
    lidia_error_handler_para(rows, "rows", "rows == columns",
			     columns, "columns", "rows == columns",
			     "bigint *bigint_matrix::"
			     "charpoly() const",
			     DM_BIGM, LBIGM_ERROR[7]); 
  
  register lidia_size_t i, j, z1, z2;
  bigint TMP, TMP1;
  bigint **Bbigint, *tmp, *tmp2;
  long len, **Blong, *tmp1;

  bigint *RES = new bigint[columns + 1];
  memory_handler(RES, DM_BIGM, "charpoly :: "
		 "Error in memory allocation (RES)");

  /* Step 1 */
  j = (lidia_size_t) columns / 2;
  hadamard(TMP);
  TMP1.assign_one();
  for (i = columns; i > columns - j; i--)
    ::multiply(TMP, TMP, (i*i));
  for (i = j; i > 0; i--)
    ::multiply(TMP1, TMP1, (i*i));
  ::divide(TMP,TMP,TMP1);

  if (TMP.is_zero())
    {
      RES[columns].assign((columns % 2 == 0) ? 1 : -1 );
      return RES;
    }
          
  /* Step 2 */
  shift_left(TMP, TMP, 1);
  bigint *PRIM = get_primes(TMP, (bigint) 1);
  PRIM[0].longify(len);
  
  bigint *zwbigint;
  long *zwlong;
  
  /* Step 3 */
  bigint_matrix U(columns + 1, (int)len);
  Bbigint = new bigint *[rows];
  Blong = new long *[rows];
  memory_handler(Bbigint, DM_BIGM, "charpoly :: "
		 "Error in memory allocation (Bbigint)");
  memory_handler(Blong, DM_BIGM, "charpoly :: "
		 "Error in memory allocation (Blong)");  
  for (i = 0; i < rows; i++)
    {
      tmp = new bigint[columns];
      tmp1 = new long[columns];
      memory_handler(tmp, DM_BIGM, "charpoly :: "
		     "Error in memory allocation (tmp)");
      memory_handler(tmp1, DM_BIGM, "charpoly :: "
		     "Error in memory allocation (tmp1)");
      for (j = 0; j < columns; j++)
	{
	  tmp[j].assign_zero();
	  tmp1[j] = 0;
	}
      
      Bbigint[i] = tmp;
      Blong[i] = tmp1;
    }
  
  bigint MOD;
  long Modlong;
  
  /* Step 3 */
  for (i = 1; i <= len; i++)
    {
      MOD.assign(PRIM[i]);
      if (MOD.bit_length() > bigint::bits_per_digit())
	{
	  for (z1 = 0; z1 < rows; z1++)
	    {
	      tmp = Bbigint[z1];
	      tmp2 = value[z1];
	      for (z2 = 0; z2 < columns; z2++)
		best_remainder(tmp[z2], tmp2[z2], MOD);
	    }
	  
	  zwbigint = charpoly_intern(Bbigint, rows, MOD);
	  for (j = 0; j < columns + 1; j++)
	    U.value[j][i - 1].assign(zwbigint[j]);
	  delete[] zwbigint;
	}
      else
	{
	  MOD.longify(Modlong);
	  for (z1 = 0; z1 < rows; z1++)
	    {
	      tmp1 = Blong[z1];
	      tmp2 = value[z1];
	      for (z2 = 0; z2 < columns; z2++)
		best_remainder_long(tmp1[z2], tmp2[z2], Modlong);
	    }
	  
	  zwlong = charpoly_intern(Blong, rows, Modlong);
	  for (j = 0; j < columns + 1; j++)
	    U.value[j][i - 1].assign(zwlong[j]);
	  delete[] zwlong;
	}
    }
  for (j = 0; j < rows; j++)
    {
      delete[] Bbigint[j];
      delete[] Blong[j];
    }
  delete[] Bbigint;
  delete[] Blong;
  
  /* Step 4,5 */
  for (i = 0; i < columns + 1; i++)
    {
      ::chinrest(RES[i], tmp = U.row(i), PRIM);
	delete[] tmp;
    }
  
  delete[] PRIM;
  return RES;
}

void bigint_matrix::
charpoly(base_vector < bigint > &RES) const
{
  /**
   ** DESCRIPTION: A.charpoly(RES);
   **              => RES[0],...,RES[r] are the coefficients of
   **                 the characteristic polynomial of matrix A
   ** ERROR: rows != columns
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "charpoly(base_vector < bigint > &)", DV_BIGM + 7);

  if (columns != rows)
    lidia_error_handler_para(rows, "rows", "rows == columns",
			     columns, "columns", "rows == columns",
			     "void bigint_matrix::"
			     "charpoly(base_vector < bigint > &) const",
			     DM_BIGM, LBIGM_ERROR[7]); 
  
  register lidia_size_t i, j, z1, z2;
  bigint TMP;
  bigint **Bbigint, *tmp, *tmp2;
  long len, **Blong, *tmp1;

  if (RES.capacity() < columns + 1)
    RES.set_capacity(columns +1);
  if (RES.size() != columns + 1)
    RES.set_size(columns + 1);

  /* Step 1 */
  power(TMP, (bigint) columns * max_abs(), (bigint) columns);
  if (TMP.is_zero())
    {
      RES[columns].assign((columns % 2 == 0) ? 1 : -1 );
      return;
    }

  /* Step 2 */
  shift_left(TMP, TMP, 1);
  bigint *PRIM = get_primes(TMP, 1);
  PRIM[0].longify(len);
  
  bigint *zwbigint;
  long *zwlong;
  
  /* Step 3 */
  bigint_matrix U(columns + 1, (int)len);
  Bbigint = new bigint *[rows];
  Blong = new long *[rows];
  memory_handler(Bbigint, DM_BIGM, "charpoly :: "
		 "Error in memory allocation (Bbigint)");
  memory_handler(Blong, DM_BIGM, "charpoly :: "
		 "Error in memory allocation (Blong)");  
  for (i = 0; i < rows; i++)
    {
      tmp = new bigint[columns];
      tmp1 = new long[columns];
      memory_handler(tmp, DM_BIGM, "charpoly :: "
		     "Error in memory allocation (tmp)");
      memory_handler(tmp1, DM_BIGM, "charpoly :: "
		     "Error in memory allocation (tmp1)");
      for (j = 0; j < columns; j++)
	{
	  tmp[j].assign_zero();
	  tmp1[j] = 0;
	}
      
      Bbigint[i] = tmp;
      Blong[i] = tmp1;
    }
  
  bigint MOD;
  long Modlong;
  
  /* Step 3 */
  for (i = 1; i <= len; i++)
    {
      MOD.assign(PRIM[i]);
      if (MOD.bit_length() > bigint::bits_per_digit())
	{
	  for (z1 = 0; z1 < rows; z1++)
	    {
	      tmp = Bbigint[z1];
	      tmp2 = value[z1];
	      for (z2 = 0; z2 < columns; z2++)
		best_remainder(tmp[z2], tmp2[z2], MOD);
	    }
	  
	  zwbigint = charpoly_intern(Bbigint, rows, MOD);
	  for (j = 0; j < columns + 1; j++)
	    U.value[j][i - 1].assign(zwbigint[j]);
	  delete[] zwbigint;
	}
      else
	{
	  MOD.longify(Modlong);
	  for (z1 = 0; z1 < rows; z1++)
	    {
	      tmp1 = Blong[z1];
	      tmp2 = value[z1];
	      for (z2 = 0; z2 < columns; z2++)
		best_remainder_long(tmp1[z2], tmp2[z2], Modlong);
	    }
	  
	  zwlong = charpoly_intern(Blong, rows, Modlong);
	  for (j = 0; j < columns + 1; j++)
	    U.value[j][i - 1].assign(zwlong[j]);
	  delete[] zwlong;
	}
    }
  for (j = 0; j < rows; j++)
    {
      delete[] Bbigint[j];
      delete[] Blong[j];
    }
  delete[] Bbigint;
  delete[] Blong;
  
  /* Step 4,5 */
  for (i = 0; i < columns + 1; i++)
    {
      ::chinrest(RES[i], tmp = U.row(i), PRIM);
	delete[] tmp;
    }
  
  delete[] PRIM;
}

/**
 ** END: Linear algebra
 ** PART 1
 **/

/**
 ** BEGIN: Linear algebra
 ** PART 2
 **/

/**
 ** Hermite normal form
 **/

void bigint_matrix::
hnfmod_dkt(const bigint &mod)
{
  /**
   ** DESCRIPTION: A.hnfmod_dkt(mod);
   **              => A in Hermite normal form
   **              => h = lattice determinant of lattice formed
   **              by the columns of matrix A
   ** ERROR: rank != rows
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "hnfmod_dkt(const bigint &)", DV_BIGM + 8);
  
  if (rank() != rows)
    lidia_error_handler_para(rank(), "rank", "rank == rows",
			     rows, "rows", "rank == rows",
			     "void bigint_matrix::"
			     "hnfmod_dkt(const bigint &mod)",
			     DM_BIGM, LBIGM_ERROR[10]);
  
  /* bigint part */
  register long i, j, z, diff = columns - rows;
  
  bigint RES0, RES1, RES2, RES3;  /* 0=lggT,1=rggt,2=ggt */
  bigint x, y;
  bigint TMP, TMP1, TMP2, TMP3;
  bigint *Atmp, *Atmp1 = NULL;
  
  /* Step 1,2 */
  for (i = 0; i < rows; i++)
    {
      Atmp = value[i];
      for (j = 0; j < columns; j++)
	best_remainder(Atmp[j], Atmp[j], mod);
    }
  
  /* Step 3 - 5 */
  for (i = rows - 1; i >= 0; i--)
    {
      Atmp = value[i];
      
      /* Step 6 -8 */
      for (j = diff + i - 1; j >= 0; j--)
	{
	  if (!Atmp[j].is_zero())
	    {
	      /* Step 9 - 12 */
	      if (Atmp[diff + i].is_zero())
		Atmp[diff + i].assign(mod);
	      
	      /* Step 13 - 18 */
	      RES2 = xgcd(RES0, RES1, Atmp[j], Atmp[diff + i]);
	      div_rem(x, RES3, Atmp[diff + i], RES2);
	      div_rem(y, RES3, Atmp[j], RES2);
	      
	      /* Step 19 - 28 */
	      for (z = 0; z <= i; z++)
		{
		  Atmp1 = value[z];
		  TMP.assign(Atmp1[j]);
		  TMP1.assign(Atmp1[diff + i]);
		  
		  /* Atmp1[j] = ((TMP * x) + (TMP1 * y)) % h; */
		  mult_mod(TMP2, TMP, x, mod);
		  mult_mod(TMP3, TMP1, y, mod);
		  sub_mod(Atmp1[j], TMP2, TMP3, mod);
		  
		  /* Atmp1[n-m+i] = ((TMP * RES0) + (TMP1 * RES1)) % h; */
		  mult_mod(TMP2, TMP, RES0, mod);
		  mult_mod(TMP3, TMP1, RES1, mod);
		  add_mod(Atmp1[diff+i], TMP2, TMP3, mod);
		}
	    }
	}
    }
  
  /* Step 29 - 32 */
  bigint D = mod;
  for (i = rows - 1; i >= 0; i--)
    {
      Atmp = value[i];
      
      /* Step 33 - 36 */
      if (Atmp[diff + i].is_zero())
	Atmp[diff + i].assign(D);
      
      /* Step 37 - 39 */
      RES2 = xgcd(RES0, RES1, Atmp[diff + i], D);
      
      /* Step 40 - 46 */
      for (z = 0; z < rows; z++)
	{
	  Atmp1 = value[z];
	  mult_mod(Atmp1[diff + i], Atmp1[diff + i], RES0, D);
	  if (Atmp1[diff+i].is_lt_zero())
	    ::add(Atmp1[diff+i], Atmp1[diff+i], D);
	}
      Atmp[columns - rows + i].assign(RES2);
      
      /* Step 47 - 49 */
      div_rem(D, TMP, D, RES2);
    }
  
  /* Step 50 - 52 */
  for (i = rows - 1; i >= 0; i--)
    {
      Atmp = value[i];
      
      /* Step 53 - 56 */
      if (Atmp[diff + i].is_zero())
	Atmp[diff + i].assign(mod);
      
      /* Step 57 - 59 */
      for (j = diff + i + 1; j < columns; j++)
	{
	  pos_div_rem(TMP, TMP1, Atmp[j], Atmp[diff + i]);
	  
	  /* Step 60 - 66 */
	  for (z = 0; z <= i; z++)
	    {
	      Atmp1 = value[z];
	      ::multiply(TMP1, Atmp1[diff + i], TMP);
	      ::subtract(Atmp1[j], Atmp1[j], TMP1);
	    }
	}
    }
}

void bigint_matrix::
hnfmod_cohen(const bigint & D)
{
  debug_handler_l(DM_BIGM, "in member - function "
		  "hnfmod_cohen(const bigint &)", DV_BIGM + 8);
  
  if (rank() != rows)
    lidia_error_handler_para(rank(), "rank", "rows == rank",
			     rows, "rows", "rows == rank", 
			     "void bigint_matrix::"
			     "hnfmod_cohen(const bigint & D)",
			     DM_BIGM, LBIGM_ERROR[10]);
  
  bigint_matrix W(rows, columns);
  long i = rows - 1;
  long j = columns - 1;
  long k = columns - 1;
  long z;

  bigint R = D;
  bigint u, v, d, q;
  bigint TMP, TMP1, TMP2;
  bigint *tmp, *tmp1;

  for (i = rows - 1; i >= 0; i--)
  {
    tmp = value[i];
    if (tmp[k].is_zero())
      tmp[k].assign(R);
    while (j != 0)
    {
      j--;
      if (!tmp[j].is_zero())
      {
	d = xgcd(u, v, tmp[k], tmp[j]);
	div_rem(TMP1, TMP, tmp[j], d);
	div_rem(TMP2, TMP, tmp[k], d);
	for (z = 0; z < rows; z++)
	{
	  tmp1 = value[z];
	  TMP.assign(u * tmp1[k] + v * tmp1[j]);
	  best_remainder(tmp1[j], TMP2 * tmp1[j] - TMP1 * tmp1[k], R);
	  best_remainder(tmp1[k], TMP, R);
	}
      }
    }
    d = xgcd(u, v, tmp[k], R);
    for (z = 0; z < rows; z++)
    {
      best_remainder(W.value[z][k], u * value[z][k], R);
      if (W.value[z][k] < 0)
	::add(W.value[z][k], W.value[z][k], R);
    }
    if (W.value[i][k].is_zero())
      W.value[i][k].assign(R);
    for (j = k + 1; j < columns; j++)
    {
      pos_div_rem(q, TMP, W.value[i][j], W.value[i][k]);
      for (z = 0; z < rows; z++)
	::subtract(W.value[z][j], W.value[z][j], q * W.value[z][k]);
    }
    div_rem(R, TMP, R, d);
    k--;
    j = k;
  }
  assign(W);
}

void bigint_matrix::
hnfmod_mueller(bigint_matrix & TRANS)
{
  /**
   ** DESCRIPTION: A.hnfmod_mueller(TRANS);
   **              => A in Hermite normal form
   **              => TRANS = transformtion matrix A*TRANS = HNFmod(A)
   ** ERROR: TRANS.rows != TRANS.columns or TRANS.rows != columns or
   **        rank != rows
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hnfmod_mueller(bigint_matrix &, const bigint &)", DV_BIGM + 8);
  
  register long i, j;
  bigint TMP, *TRANStmp;
  
  /* Step 1 */
  lidia_size_t *linuz = lininc();
  if (linuz[0] != rows)
    lidia_error_handler_para(linuz[0], "rank", "rank == rows",
			     rows, "rows", "rank == rows", 
			     "void bigint_matrix::"
			     "hnfmod_mueller(bigint_matrix & TRANS)",
			     DM_BIGM, LBIGM_ERROR[10]);
  
  /* Step 2,3 */
  if (rows == columns)
    {
      bigint_matrix A1(*this);
      bigint DET = det();
      if (DET.is_lt_zero())
	hnfmod_dkt(-DET);
      else
	hnfmod_dkt(DET);
      ::multiply(TRANS,::adj(A1), *this);
      
      for (i = 0; i < rows; i++)
	{
	  TRANStmp = TRANS.value[i];
	  for (j = 0; j < rows; j++)
	    div_rem(TRANStmp[j], TMP, TRANStmp[j], DET);
	}
    }
  else
    {
      lidia_size_t diff = columns - rows;
      
      bigint_matrix EAR(*this);
      EAR.regexpansion(linuz);
      bigint_matrix R(rows, rows), F(rows, diff);
      bigint_matrix E(columns, columns), P(columns, columns);
      E.diag(bigint(1), bigint(0));
      
      long k = linuz[0], l1 = 0, l2 = 0;
      for (i = 0; i < columns; i++)
	{
	  if (linuz[k] == i)
	    {
	      k--;
	      // R.sto_column(column(i), rows, l1);
              for (j=0;j<rows;j++)
		R.value[j][l1].assign(value[j][i]);
	      // P.sto_column(E.column(i), columns, l1);
              for (j=0;j<columns;j++)
		P.value[j][l1].assign(E.value[j][i]);
	      l1++;
	    }
	  else
	    {
	      //F.sto_column(column(i), rows, l2);
              for (j=0;j<rows;j++)
		F.value[j][l2].assign(value[j][i]);
	      //P.sto_column(E.column(i), columns, rows + l2);
              for (j=0;j<columns;j++)
		P.value[j][rows+l2].assign(E.value[j][i]);
	      l2++;
	    }
	}
      delete[] linuz;
      
      /* Step 4,5 */
      EAR.hnfmod_dkt();
      bigint_matrix ADJ =::adj(R);
      bigint DET = R.det();
      bigint_matrix U(diff, diff), V(diff, rows);
      bigint_matrix EHNF(rows, rows), Null(rows, diff);
      
      EAR.split_t(U, V, Null, EHNF);
      
      bigint_matrix M(columns, columns);
      
      /* Step 6 */
      bigint_matrix T1 = (-ADJ) * (F * U);
      bigint_matrix T2 = (-ADJ) * (F * V) + (ADJ * EHNF);
      bigint_matrix T3 = (U * DET);
      bigint_matrix T4 = (V * DET);
      M.compose_t(T1, T2, T3, T4);
      
      /* Step 7 */
      ::multiply(TRANS, P, M);
      for (i = 0; i < columns; i++)
	{
	  TRANStmp = TRANS.value[i];
	  for (j = 0; j < columns; j++)
	    div_rem(TRANStmp[j], TMP, TRANStmp[j], DET);
	}
      ::multiply(*this, *this, TRANS);
    }
}

void bigint_matrix::
hnf_simple()
{
  /**
   ** DESCRIPTION: HNF Computation
   ** ALGORITHM: Gauss with reduction
   ** IMPROVEMENTS: Theory of Havas / best reaminder
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hnf_simple()", DV_BIGM + 8);
  
  bigint_matrix TR(columns, columns);
  bigint *REM;
  register lidia_size_t startr = 0, startc = 0, i, j;
  
  for (startc=columns-1,startr=rows-1;startr>=0 && startc>=0;startr--,startc--)
    {
      bigint *ZU = row(startr);
      for (i = startc + 1; i < columns; ZU[i].assign_zero(), i++);
      REM = TR.mgcd2(ZU, columns);
      delete[] REM;
      delete[] ZU;
      
      TR = TR.trans();
      ::multiply(*this, *this, TR);
      for (i = 0; value[startr][i].is_zero() && i <= startc; i++);
      if (i > startc)
	return;
      swap_columns(startc, i);
    }
  
  bigint TMP, q;
  bigint *tmp, *tmp1;
  for (startc=columns-2,startr=rows-2;startr>=0 && startc>=0;startr--,startc--)
    {
      tmp = value[startr];
      for (i = startc + 1; i < columns; i++)
	{
	  pos_div_rem(q, TMP, tmp[i], tmp[startc]);
	  for (j = 0; j <= startr; j++)
	    {
	      tmp1 = value[j];
	      ::multiply(TMP,q,tmp1[startc]);
	      ::subtract(tmp1[i], tmp1[i], TMP);
	    }
	}
    }
}

void bigint_matrix::
hnf_simple(bigint_matrix & T)
{
  /**
   ** DESCRIPTION: HNF Computation
   ** ALGORITHM: Gauss with reduction
   ** IMPROVEMENTS: Theory of Havas / best reaminder
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hnf_simple(bigint_matrix &)", DV_BIGM + 8);
  
  if (T.columns != columns)
    T.set_no_of_columns(columns);
  if (T.rows != columns)
    T.set_no_of_rows(columns);
  
  T.diag(1, 0);
  
  bigint *REM;
  bigint_matrix TR = T;
  register lidia_size_t startr, startc, i, j;
  
  for (startc = columns - 1, startr = rows - 1; startr >= 0 && startc >= 0; startr--, startc--)
    {
      bigint *ZU = row(startr);
      for (i = startc + 1; i < columns; ZU[i].assign_zero(), i++);
      REM = TR.mgcd2(ZU, columns);
      delete[] REM;
      delete[] ZU;
      TR.assign(TR.trans());
      ::multiply(*this, *this, TR);
      ::multiply(T, T, TR);
      for (i = 0; value[startr][i].is_zero() && i <= startc; i++);
      if (i > startc)
	return;
      swap_columns(startc, i);
      T.swap_columns(startc, i);
    }
  
  bigint TMP, q;
  bigint *tmp, *tmp1;
  for (startc = columns - 2, startr = rows - 2; startr >= 0 && startc >= 0; startr--, startc--)
    {
      tmp = value[startr];
      for (i = startc + 1; i < columns; i++)
	{
	  pos_div_rem(q, TMP, tmp[i], tmp[startc]);
	  for (j = 0; j <= startr; j++)
	    {
	      tmp1 = value[j];
	      ::multiply(TMP,q,tmp1[startc]);
	      ::subtract(tmp1[i], tmp1[i], TMP);
	    }
	  for (j = 0; j < columns; j++)
	    {
	      tmp1 = T.value[j];
	      ::multiply(TMP,q,tmp1[startc]);
	      ::subtract(tmp1[i], tmp1[i], TMP);
	    }
	}
    }
}

void bigint_matrix::
hnf_havas()
{
   /**
   ** DESCRIPTION: HNF Computation
   ** ALGORITHM: Gauss with reduction
   ** IMPROVEMENTS: Theory of Havas / best reaminder
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "hnf_havas()", DV_BIGM + 8);
  
  bigint q, res, TMP;
  bigint *tmp, *tmp1;
  
  register lidia_size_t startr, startc, i, j, SW;
  lidia_size_t index;
  
  /* Elimination */
  for (startc = columns - 1, startr = rows - 1; startr >= 0 && startc >= 0; startc--, startr--)
    {
      tmp = value[startr];
      
      /* init */
      for (index=0; index<=startc && tmp[index].is_zero();index++);
      
      if (index > startc)
	return;
      
      do
	{
	  SW = 0;
	  for (i = 0; i <= startc; i++)
	    if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
	      index = i;
	  
	  for (i = 0; i <= startc; i++)
	    if ((i != index) && !tmp[i].is_zero())
	      {
		SW = 1;
		div_rem(q, res, tmp[i], tmp[index]);
		for (j = 0; j < rows; j++)
		  {
		    tmp1 = value[j];
		    ::multiply(TMP,q,tmp1[index]);
		    ::subtract(tmp1[i], tmp1[i], TMP);
		  }
	      }
	}
      while (SW == 1);

      if (tmp[index].is_lt_zero())
	for (i=0;i<=startr;i++)
	  value[i][index].negate();
      if (index != startc)
	swap_columns(startc, index);
    } 

  for (startc = columns - 2, startr = rows - 2; startr >= 0 && startc >= 0; startr--, startc--)
    {
      tmp = value[startr];
      for (i = startc + 1; i < columns; i++)
	{
	  pos_div_rem(q, TMP, tmp[i], tmp[startc]);
	  for (j = 0; j <= startr; j++)
	    {
	      tmp1 = value[j];
	      ::multiply(TMP,q,tmp1[startc]);
	      ::subtract(tmp1[i], tmp1[i], TMP);
	    }
	}
      
    }
}

void bigint_matrix::
hnf_havas(bigint_matrix & T)
{
  /**
   ** DESCRIPTION: HNF Computation
   ** ALGORITHM: Gauss with reduction
   ** IMPROVEMENTS: Theory of Havas / best reaminder
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hnf_havas(bigint_matrix &)", DV_BIGM + 8);
  
  bigint q, res, TMP;
  bigint *tmp, *tmp1;
  
  if (T.columns != columns)
    T.set_no_of_columns(columns);
  if (T.rows != columns)
    T.set_no_of_rows(columns);
  
  T.diag(1, 0);
  
  lidia_size_t startr, startc;
  lidia_size_t i, j, index, SW;

  /* Elimination */
  for (startc = columns - 1, startr = rows - 1; startr >= 0 && startc >= 0; startc--, startr--)
    {      
      tmp = value[startr];
      for (index=0; index<=startc && tmp[index].is_zero();index++);
      
      if (index > startc)
	return;
      
      do
	{
	  SW=0;
	  for (i = 0; i <= startc; i++)
	    if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
	      index = i;
	  
	  for (i = 0; i <= startc; i++)
	    if ((i != index) && !tmp[i].is_zero())
	      {
		SW=1;
		div_rem(q, res, tmp[i], tmp[index]);
		for (j = 0; j < rows; j++)
		  {
		    tmp1 = value[j];
		    ::multiply(TMP,q,tmp1[index]);
		    ::subtract(tmp1[i], tmp1[i], TMP);
		  }
		for (j = 0; j < columns; j++)
		  {
		    tmp1 = T.value[j];
		    ::multiply(TMP,q,tmp1[index]);
		    ::subtract(tmp1[i], tmp1[i], TMP);
		  }
	      }
	}
      while (SW==1);
      
      if (tmp[index].is_lt_zero())
	{
	  for (i=0;i<=startr;i++)
	    value[i][index].negate();
	  for (i=0;i<columns;i++)
	    T.value[i][index].negate();
	}
      if (index !=startc)
	{
	  swap_columns(startc, index);
	  T.swap_columns(startc, index);
	}
    }
  
  /* column reduction */
  for (startc = columns - 2, startr = rows - 2; startr >= 0 && startc >= 0; startr--, startc--)
    {
      tmp = value[startr];
      for (i = startc + 1; i < columns; i++)
	{
	  pos_div_rem(q, TMP, tmp[i], tmp[startc]);
	  for (j = 0; j <= startr; j++)
	    {
	      tmp1 = value[j];
	      ::multiply(TMP,q,tmp1[startc]);
	      ::subtract(tmp1[i], tmp1[i], TMP);
	    }
	  for (j = 0; j < columns; j++)
	    {
	      tmp1 = T.value[j];
	      ::multiply(TMP,q,tmp1[startc]);
	      ::subtract(tmp1[i], tmp1[i], TMP);
	    }
	}
    }
}

void bigint_matrix::
hnf_havas_cont()
{
  /**
   ** DESCRIPTION: HNF Computation
   ** ALGORITHM: Gauss with reduction
   ** IMPROVEMENTS: Theory of Havas / best reaminder
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "hnf_havas_cont()", DV_BIGM + 8);

  bigint q, res, TMP;
  bigint *tmp, *tmp1;

  register lidia_size_t startr, startc, i, j, SW;
  register lidia_size_t index2, index;
  
  /* Elimination */
  for (startc=columns-1,startr=rows-1;startr>=0 && startc>=0;startc--,startr--)
    {
      tmp=value[startr];
      for (index2=0;index2<=startc && tmp[index2].is_zero();index2++);
      
      if (index2 > startc)
	{
	  startc++;
	  continue;
	}
      
      index = index2;       
      
      do
	{
	  SW=0;
	  for (i = index2; i <= startc; i++)
	    if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
	      index = i;
	  
	  for (i = index2; i <= startc; i++)
	    {
	      if ((i != index) && !tmp[i].is_zero())
		{
		  SW=1;
		  div_rem(q, res, tmp[i], tmp[index]);
		  for (j = 0; j <= startr; j++)
		    {
		      tmp1 = value[j];
		      ::multiply(TMP,q,tmp1[index]);
		      ::subtract(tmp1[i], tmp1[i], TMP);
		    }
		}
	    }
	}
      while (SW==1);
      
      if (tmp[index].is_lt_zero())
	for (i=0;i<=startr;i++)
	  value[i][index].negate();
      if (index != startc)
	swap_columns(startc, index);
      
      /* column reduction */
      for (i = startc+1;i<columns;i++)
	if (tmp[i] >= tmp[startc] || tmp[i] < 0)
	  {
	    pos_div_rem(q, TMP, tmp[i], tmp[startc]);
	    for (j=0; j <= startr; j++)
	      {
		tmp1 = value[j];
		::multiply(TMP,q,tmp1[startc]);
		::subtract(tmp1[i],tmp1[i],TMP);
	      }
	  }
    }     
}

void bigint_matrix::
hnf_havas_cont(bigint_matrix & T)
{
  /**
   ** DESCRIPTION: HNF Computation
   ** ALGORITHM: Gauss with reduction
   ** IMPROVEMENTS: Theory of Havas / best reaminder
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "hnf_havas_cont(bigint_matrix &)", DV_BIGM + 8);
  
  bigint q, res, TMP;
  bigint *tmp, *tmp1;
  
  if (T.columns != columns)
    T.set_no_of_columns(columns);
  if (T.rows != columns)
    T.set_no_of_rows(columns);
  
  T.diag(1, 0);
  
  lidia_size_t startr, startc;
  lidia_size_t i, j, index, SW, index2;

  /* Elimination */
  for (startc = columns - 1, startr = rows - 1; startr >= 0 && startc >= 0; startc--, startr--)
    {      
      tmp = value[startr];
      for (index2=0; index2<=startc && tmp[index2].is_zero();index2++);
      
      if (index2 > startc)
	{
	  startc++;
	  continue;
	}

      index = index2;
      
      do
	{
	  SW=0;
	  for (i = index2; i <= startc; i++)
	    if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
	      index = i;
	  
	  for (i = index2; i <= startc; i++)
	    if ((i != index) && !tmp[i].is_zero())
	      {
		SW=1;
		div_rem(q, res, tmp[i], tmp[index]);
		for (j = 0; j < rows; j++)
		  {
		    tmp1 = value[j];
		    ::multiply(TMP,q,tmp1[index]);
		    ::subtract(tmp1[i], tmp1[i], TMP);
		  }
		for (j = 0; j < columns; j++)
		  {
		    tmp1 = T.value[j];
		    ::multiply(TMP,q,tmp1[index]);
		    ::subtract(tmp1[i], tmp1[i], TMP);
		  }
	      }
	}
      while (SW==1);
      
      if (tmp[index].is_lt_zero())
	{
	  for (i=0;i<=startr;i++)
	    value[i][index].negate();
	  for (i=0;i<columns;i++)
	    T.value[i][index].negate();
	}
      if (index !=startc)
	{
	  swap_columns(startc, index);
	  T.swap_columns(startc, index);
	}
  
      /* column reduction */
      for (i = startc + 1; i < columns; i++)
	if (tmp[i] >= tmp[startc] || tmp[i] < 0)
	  {
	    pos_div_rem(q, TMP, tmp[i], tmp[startc]);
	    for (j = 0; j <= startr; j++)
	      {
		tmp1 = value[j];
		::multiply(TMP,q,tmp1[startc]);
		::subtract(tmp1[i], tmp1[i], TMP);
	      }
	    for (j = 0; j < columns; j++)
	      {
		tmp1 = T.value[j];
		::multiply(TMP,q,tmp1[startc]);
		::subtract(tmp1[i], tmp1[i], TMP);
	      }
	  }
    }
}

/**
 ** Kernel
 **/

void bigint_matrix::
kernel1(const bigint_matrix & A)
{
  /**
   ** DESCRIPTION: B.kernel1(A);
   **              => The columns of matrix B form a basis
   **              of the kernel of matrix A.
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "kernel1(const bigint_matrix &)", DV_BIGM + 8);
  
  bigint *ZBAtmp, *Atmp;
  register long i, j;
  lidia_size_t c = A.columns;

  /* Step 1 */
  lidia_size_t *linuz = A.lininr();
  lidia_size_t r = linuz[0];
  if (r == c)
  {
    bigint_matrix RES(c, 1);
    assign(RES);
    delete[] linuz;
    return;
  }
  
  /* Step 2 */
  bigint_matrix ZBA(r, c);
  for (i = 1; i <= r; i++)
  {
    ZBAtmp = ZBA.value[i - 1];
    Atmp = A.value[linuz[r - i + 1]];
    for (j = 0; j < c; j++)
      ZBAtmp[j].assign(Atmp[j]);
  }
  delete[] linuz;
  
  /* Step 3 */
  bigint_matrix TRANS(c, c);
  ZBA.hnf(TRANS);

  /* Step 4 */
  bigint_matrix PART2(c, r);
  if (rows != c)
    set_no_of_rows(c);
  if (columns != c - r)
    set_no_of_columns(c - r);
  TRANS.split_h(*this, PART2);
}

void bigint_matrix::
kernel2(const bigint_matrix & A)
{
  /**
   ** DESCRIPTION: B.kernel2(A);
   **              => The columns of matrix B form a basis
   **              of the kernel of matrix A.
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "kernel2(const bigint_matrix &)", DV_BIGM + 8);

  register lidia_size_t i;
  bigint_matrix B = A;
  B.hnf_havas_cont(*this);

  for (i=0;i<A.columns && B.is_column_zero(i);i++);
  
  if (i==0)
    {
      bigint_matrix C(A.columns,1);
      assign(C);
    }
  else
    set_no_of_columns(i);
}

/**
 ** regular InvImage
 **/

void bigint_matrix::
reginvimage1(const bigint_matrix & A, const bigint_matrix & B)
{
  /**
   ** DESCRIPTION: C.reginvimage1(A,B);
   **              => A * C.column(j) = g(j)*B.column(j), j=0,...,B.columns
   **              => g(j) minimal
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "reginvimage1(const bigint_matrix &, const bigint_matrix &", DV_BIGM + 8);
  
  register long i, j;
  bigint TMP, TMP1;

  /* Step 1 */
  bigint DET = A.det();
  if (DET == 0)
  {
    lidia_error_handler_para(DET, "det(A)", "det(A) != 0",
			     "void bigint_matrix::"
			     "reginvimage1(const bigint_matrix & A, const bigint_matrix & B)",
			     DM_BIGM, LBIGM_ERROR[11]);
    return;
  }
  bigint_matrix ADJ = ::adj(A);

  /* Step 2 */
  bigint_matrix PROD = ADJ * B;
  bigint *u = new bigint[B.rows];
  memory_handler(u,DM_BIGM,"reginvimage1 :: "
		 "Error in memory allocation (u)");
  bigint *g, phi;

  /* Step 3 */
  if (rows != B.rows + 1)
    set_no_of_rows(B.rows + 1);
  if (columns != B.columns)
    set_no_of_columns(B.columns);
  for (i = 0; i < B.columns; i++)
  {
    for (j=0;j<PROD.rows;j++)
      u[j].assign(PROD.value[j][i]);
    g = ::mgcd2(u, PROD.rows);
    div_rem(phi, TMP, DET, gcd(g[0], DET));
    if (phi.is_lt_zero())
      phi.negate();

    /* Step 4 */
    for (j = 0; j < PROD.rows; j++)
    {
      ::multiply(TMP, phi, u[j]);
      div_rem(value[j][i], TMP1, TMP, DET);
    }
    value[PROD.rows][i].assign(phi);
    delete[] g;
  }
 delete[] u;
}

void bigint_matrix::
reginvimage2(const bigint_matrix & A, const bigint_matrix & B)
{
  /**
   ** DESCRIPTION: C.reginvimage2(A,B);
   **              => A * C.column(j) = g(j)*B.column(j), j=0,...,B.columns
   **              => g(j) minimal
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "reginvimage2(const bigint_matrix &, const bigint_matrix &", DV_BIGM + 8);
  
  register lidia_size_t i, j, len, oldlen;
  bigint TMP, TMP1;
  
  /* Step 1 */
  bigint DET = A.det();
  if (DET == 0)
    {
      lidia_error_handler_para(DET, "det(A)", "det(A) != 0",
			       "void bigint_matrix::"
			       "reginvimage2(const bigint_matrix & A, const bigint_matrix & B)",
			       DM_BIGM, LBIGM_ERROR[11]);
      return;
    }
  
  /* Step 2 */
  oldlen = B.rows;
  len = B.rows + 1;
  ::multiply(*this,::adj(A),B);

  bigint *u = new bigint[len];
  memory_handler(u,DM_BIGM,"reginvimage :: "
		 "Error in memory allocation (u)");
  bigint phi;
  
  /* Step 3 */
  set_no_of_rows(len);
  for (i = 0; i < B.columns; i++)
    {
      for (j=0;j<oldlen;j++)
	u[j].assign(value[j][i]);
      u[oldlen].assign(DET);
      ::mgcd2(TMP1, u, len);
      div_rem(phi, TMP, DET, TMP1);
      if (phi.is_lt_zero())
	phi.negate();
      
      /* Step 4 */
      for (j = 0; j < oldlen; j++)
	{
	  ::multiply(TMP, phi, u[j]);
	  div_rem(value[j][i], TMP1, TMP, DET);
	}
      value[oldlen][i].assign(phi);
    }
  delete[] u;
}

/**
 ** Image
 **/

void bigint_matrix::
image1(const bigint_matrix & A)
{
  /**
   ** DESCRIPTION: B.image1(A);
   **              => The columns of matrix B form a basis
   **                 of the image of matrix A.
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "image1(const bigint_matrix &)", DV_BIGM + 8);
  
  bigint *ZBAtmp, *Atmp;
  register long i, j;

  /* Step 1 */
  lidia_size_t *v = A.lininr();
  lidia_size_t RANG = v[0];

  /* Step 2,3 */
  bigint_matrix ZBA(RANG, A.columns);
  for (i = 1; i <= RANG; i++)
    {
      ZBAtmp = ZBA.value[i - 1];
      Atmp = A.value[v[RANG - i + 1]];
      for (j = 0; j < A.columns; j++)
	ZBAtmp[j].assign(Atmp[j]);
    }
  delete[] v;

  /* Step 4 */
  bigint_matrix TRANS(A.columns, A.columns);
  ZBA.hnf(TRANS);

  /* Step 5 */
  if (rows != A.rows)
    set_no_of_rows(A.rows);
  if (columns != RANG)
    set_no_of_columns(RANG);

  if (A.columns == RANG)
    ::multiply(*this, A, TRANS);
  else
    {
      bigint_matrix M(A.rows, A.columns);
      ::multiply(M, A, TRANS);
      bigint_matrix PART1(RANG, A.columns - RANG);
      M.split_h(PART1, *this);
    }
}

void bigint_matrix::
image2(const bigint_matrix & A)
{
  /**
   ** DESCRIPTION: B.image2(A);
   **              => The columns of matrix B form a basis
   **                 of the image of matrix A.
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "image2(const bigint_matrix &)", DV_BIGM + 8);
  
  register lidia_size_t i;
  assign(A);
  hnf_havas_cont();
  
  for (i=0;i<columns && is_column_zero(i); i++);
  
  if (i != 0)
    if (i == columns)
      set_no_of_columns(1);
    else
      {
	bigint_matrix M(*this);
	bigint_matrix PART1(rows,i);
	set_no_of_columns(columns-i);
	M.split_h(PART1, *this);
      }
}

/**
 ** InvImage
 **/

void bigint_matrix::
invimage(const bigint_matrix & B, const bigint * b)
{
  /**
   ** DESCRIPTION: v = invimage(B,b);
   **              => A is a basis of the solution of B*x=b
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "invimage(const bigint_matrix &, const bigint *)", DV_BIGM + 8);

  if (b == NULL)
    lidia_error_handler_para(PRT, "b", "b != NULL",
			     "void bigint_matrix::"
			     "invimage(const bigint_matrix & B, const bigint * b)",
			     DM_BIGM, LBIGM_ERROR[1]);
  
  register long i;
  bigint *tmp;

  /* Step 1 */
  bigint_matrix A = B;
  A.set_no_of_columns(B.columns + 1);
  for (i = 0; i < B.rows; i++)
    A.value[i][B.columns].assign(-b[i]);
  kernel(A);

  /* Step 2 */
  if (is_column_zero(0) || is_row_zero(B.columns))
  {
    bigint_matrix C(B.rows, 1);
    set_no_of_columns(1);
    assign(C);
    return;
  }

  bigint *g = ::mgcd2(tmp=row(B.columns), columns);
  delete[] tmp;
  if (g[0] > 1)
  {
    bigint_matrix C(B.rows, 1);
    set_no_of_columns(1);
    assign(C);
    return;
  }

  /* Step 3,4  */
  bigint *x = (*this) * &(g[1]);
  delete[] g;

  /* Step 5 */
  kernel(B);
  set_no_of_columns(columns + 1);
  for (i = 0; i < rows; i++)
    value[i][columns-1].assign(x[i]);
  delete[] x;
}

void bigint_matrix::
invimage(const bigint_matrix & B, const math_vector < bigint > &b)
{
  /**
   ** DESCRIPTION: v = invimage(B,b);
   **              => A is a basis of the solution of B*x=b
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "invimage(const bigint_matrix &, const math_vector < bigint > &)", DV_BIGM + 8);
  
  register long i;
  /* Step 1 */
  bigint_matrix A = B;
  A.set_no_of_columns(B.columns + 1);

  if (b.size() != B.rows)
    lidia_error_handler_para(b.size(), "b.size", "b.size == B.rows",
			     B.rows, "B.rows", "b.size == B.rows",
			     "void bigint_matrix::"
			     "invimage(const bigint_matrix & B, const math_vector < bigint > &b)",
			     DM_BIGM, LBIGM_ERROR[1]);

  bigint *tmp = b.get_data_address();
  for (i = 0; i < B.rows; i++)
    A.value[i][B.columns].assign(-tmp[i]);
  kernel(A);

  /* Step 2 */
  if (is_column_zero(0) || is_row_zero(B.columns))
  {
    bigint_matrix C(B.rows, 1);
    set_no_of_columns(1);
    assign(C);
    return;
  }
  
  bigint *g = ::mgcd2(tmp=row(B.columns), columns);

  delete[] tmp;
  if (g[0] > 1)
    {
      bigint_matrix C(B.rows, 1);
      set_no_of_columns(1);
      assign(C);
      return;
    }

  /* Step 3,4  */
  bigint *x = (*this) * &(g[1]);
  delete[] g;

  /* Step 5 */
  kernel(B);
  set_no_of_columns(columns + 1);
  for (i = 0; i < rows; i++)
    value[i][columns-1].assign(x[i]);
  delete[] x;
}

/**
 ** Smith normal form
 **/

void bigint_matrix::
snf_hartley()
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** IMPROVEMENTS: Havas, Majewski
   ** PAPER: Recognizing badly represented Z-modules, Havas
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_hartley()", DV_BIGM + 8);
  
  register lidia_size_t startr, startc, TEILBARKEIT;
  bigint TMP1, TMP2;
  bigint *tmp;
  register lidia_size_t xpivot, ypivot, i, j, z;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
    {

      /* pivot: first non zero */

      xpivot = -1;
      ypivot = -1;
      for (i = startr; i < rows; i++)
	{
	  tmp = value[i];
	  for (j = startc; j < columns; j++)
	  if (!tmp[j].is_zero())
	    {
	      xpivot = i;
	      ypivot = j;
	      i = rows;
	      j = columns;
	    }
	}

      if (xpivot != -1)
	{
	  /* swap to diagonalposition */
	  swap_rows(startr, xpivot);
	  swap_columns(startc, ypivot);
	  
	  TEILBARKEIT=0;
	  
	  while(TEILBARKEIT==0)
	    {
	      TEILBARKEIT=1;
	      
	      /* mgcd computation for row */
	      for (i = startc + 1; i < columns; i++)
		if (value[startr][i] % value[startr][startc] != 0)
		  {
		    div_rem(TMP1, TMP2, value[startr][i], value[startr][startc]);
		    for (j = startr; j < rows; j++)
		      {
			tmp = value[j];
			::multiply(TMP2, tmp[startc], TMP1);
			::subtract(tmp[i], tmp[i], TMP2);
		      }
		    swap_columns(startc, i);
		    i = startc;
		  }
	      
	      /* mgcd computation for column */
	      for (i = startr + 1; i < rows; i++)
		if (value[i][startc] % value[startr][startc] != 0)
		  {
		    div_rem(TMP1, TMP2, value[i][startc], value[startr][startc]);
		    for (j = startc; j < columns; j++)
		      {
			::multiply(TMP2, value[startr][j], TMP1);
			::subtract(value[i][j], value[i][j], TMP2);
		      }
		    TEILBARKEIT=0; //perhaps
		      swap_rows(i, startr);
		    i = startr;
		  }
	    }

	  /* row elimination */
	  for (i = startc + 1; i < columns; i++)
	    {
	      div_rem(TMP1, TMP2, value[startr][i], value[startr][startc]);
	      for (j = startr; j < rows; j++)
		{
		  tmp = value[j];
		  ::multiply(TMP2, tmp[startc], TMP1);
		  ::subtract(tmp[i], tmp[i], TMP2);
		}
	    }

	  /* column elimination */
	  for (i = startr + 1; i < rows; i++)
	    {
	      div_rem(TMP1, TMP2, value[i][startc], value[startr][startc]);
	      for (j = startc; j < columns; j++)
		{
		  ::multiply(TMP2, value[startr][j], TMP1);
		  ::subtract(value[i][j], value[i][j], TMP2);
		}
	    }
      
	  /* modulo test */
	  for (i = startr + 1; i < rows; i++)
	    for (j = startc + 1; j < columns; j++)
	      if (value[i][j] % value[startr][startc] != 0)
		{
		  for (z = 0; z < columns; z++)
		    ::add(value[startr][z], value[startr][z], value[i][z]);
		  i = rows;
		  j = columns;
		  startc = 0;
		  startr = 0;
		}
	}
    }
  
  /* diagonal >= 0 */
  for (i = 0; i < columns && i < rows; i++)
    if (value[i][i].is_lt_zero())
      value[i][i].negate();
}

void bigint_matrix::
snf_hartley(bigint_matrix & T1, bigint_matrix & T2)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_hartley(bigint_matrix &, bigint_matrix &)", DV_BIGM + 8);
  
  if (T1.columns != rows)
    T1.set_no_of_columns(rows);
  if (T1.rows != rows)
    T1.set_no_of_rows(rows);
  
  if (T2.columns != columns)
    T2.set_no_of_columns(columns);
  if (T2.rows != columns)
    T2.set_no_of_rows(columns);
  
  T1.diag(1, 0);
  T2.diag(1, 0);
  
  register lidia_size_t startr, startc, TEILBARKEIT;
  bigint TMP1, TMP2;
  lidia_size_t xpivot, ypivot;
  register lidia_size_t i, j, z;
  
  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
    {
      
      /* pivot: first non zero */
      xpivot = -1;
      ypivot = -1;
      for (i = startr; i < rows; i++)
	for (j = startc; j < columns; j++)
	  if (value[i][j] != 0)
	    {
	      xpivot = i;
	      ypivot = j;
	      i = rows;
	      j = columns;
	    }
      if (xpivot != -1)
	{
	  /* swap to diagonalposition */
	  swap_rows(startr, xpivot);
	  T1.swap_rows(startr, xpivot);
	  swap_columns(startc, ypivot);
	  T2.swap_columns(startc, ypivot);

	  TEILBARKEIT=0;
	  
	  while(TEILBARKEIT==0)
	    {
	      TEILBARKEIT=1;
	      
	      /* mgcd computation for row */
	      for (i = startc + 1; i < columns; i++)
		if (value[startr][i] % value[startr][startc] != 0)
		  {
		    div_rem(TMP1, TMP2, value[startr][i], value[startr][startc]);
		    for (j = startr; j < rows; j++)
		      {
			::multiply(TMP2, value[j][startc], TMP1);
			::subtract(value[j][i], value[j][i], TMP2);
		      }
		    for (j = 0; j < columns; j++)
		      {
			::multiply(TMP2, T2.value[j][startc], TMP1);
			::subtract(T2.value[j][i], T2.value[j][i], TMP2);
		      }
		    swap_columns(startc, i);
		    T2.swap_columns(startc, i);
		    i = startc;
		  }
	      
	      /* mgcd computation for column */
	      for (i = startr + 1; i < rows; i++)
		if (value[i][startc] % value[startr][startc] != 0)
		  {
		    div_rem(TMP1, TMP2, value[i][startc], value[startr][startc]);
		    for (j = startc; j < columns; j++)
		      {
			::multiply(TMP2, value[startr][j], TMP1);
			::subtract(value[i][j], value[i][j], TMP2);
		      }
		    for (j = 0; j < rows; j++)
		      {
			::multiply(TMP2, T1.value[startr][j], TMP1);
			::subtract(T1.value[i][j], T1.value[i][j], TMP2);
		      }
		    TEILBARKEIT=0;
		    swap_rows(i, startr);
		    T1.swap_rows(i, startr);
		    i = startr;
		  }
	    }
	  
	  /* row elimination */
	  for (i = startc + 1; i < columns; i++)
	    {
	      div_rem(TMP1, TMP2, value[startr][i], value[startr][startc]);
	      for (j = startr; j < rows; j++)
		{
		  ::multiply(TMP2, value[j][startc], TMP1);
		  ::subtract(value[j][i], value[j][i], TMP2);
		}
	      for (j = 0; j < columns; j++)
		{
		  ::multiply(TMP2, T2.value[j][startc], TMP1);
		  ::subtract(T2.value[j][i], T2.value[j][i], TMP2);
		}
	    }
	  
	  /* column elimination */
	  for (i = startr + 1; i < rows; i++)
	    {
	      div_rem(TMP1, TMP2, value[i][startc], value[startr][startc]);
	      for (j = startc; j < columns; j++)
		{
		  ::multiply(TMP2, value[startr][j], TMP1);
		  ::subtract(value[i][j], value[i][j], TMP2);
		}
	      for (j = 0; j < rows; j++)
		{
		  ::multiply(TMP2, T1.value[startr][j], TMP1);
		  ::subtract(T1.value[i][j], T1.value[i][j], TMP2);
		}
	    }
	  
	  /* modulo test */
	  for (i = startr + 1; i < rows; i++)
	    for (j = startc + 1; j < columns; j++)
	      if (value[i][j] % value[startr][startc] != 0)
		{
		  for (z = 0; z < columns; z++)
		    ::add(value[startr][z], value[startr][z], value[i][z]);
		  for (z = 0; z < rows; z++)
		    ::add(T1.value[startr][z], T1.value[startr][z], T1.value[i][z]);
		  i = rows;
		  j = columns;
		  startc = 0;
		  startr = 0;
		}
	}
    }
  
  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      {
	value[i][i].negate();
	for (z = 0; z < columns; z++)
	  T2.value[z][i].negate();
      }
}

void bigint_matrix::
snf_simple()
{
  /**
   ** DESCRIPTION: SNF Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** IMPROVEMENT: Havas
   ** PAPER: Recognizing badly represented Z-modules
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		"snf_simple()", DV_BIGM + 8);
  bigint PIVOT, TMP1, TMP2;
  bigint *tmp=NULL, *deltmp;

  bigint_matrix TR1(rows,rows);
  bigint_matrix TR2(columns,columns);
  bigint *REM;
  register lidia_size_t startr, startc, pivot, i, j, z, TEILBARKEIT;
  
  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
    {

      /* pivot: first non zero */
      pivot = -1;
      for (i = startr; i < rows; i++)
	for (j = startc; j < columns; j++)
	  if (value[i][j] != 0)
	    {
	      pivot = i;
	      i = rows;
	      j = columns;
	    }

      if (pivot != -1)
	{
	  /* swap pivot in actual row */
	  swap_rows(startr, pivot);
	      
	  TEILBARKEIT=0;

	  while(TEILBARKEIT==0)
	    {
	      TEILBARKEIT=1;
	      
	      /* mgcd computation and row elimination */
	      REM=TR2.mgcd2(deltmp=row(startr), columns);
  	      delete[] deltmp;

              delete[] REM;
	      TR2 = TR2.trans();
	      ::multiply(*this, *this, TR2);
	   	      
	      tmp = value[startr];
	      for (i = 0; tmp[i].is_zero() && i < columns; i++);
	      swap_columns(startc, i);
	   	  
	      /* mgcd computation and column elimination */
	      REM=TR1.mgcd2(deltmp=column(startc), rows);
              delete[] deltmp;

              delete[] REM;
	      ::multiply(*this, TR1, *this);
	   	      
	      for (i = 0; value[i][startc].is_zero() && i < rows; i++);
	      swap_rows(startr, i);
	   
	      /* control: row == 0 */
	      tmp = value[startr];
	      for (i = startc+1; tmp[i].is_zero() && i < columns; i++);
	      if (i!=columns)
		TEILBARKEIT=0;
	    } 
	  
	  /* modulo test */
	  for (i = startr; i < rows; i++)
	    for (j = startc + 1; j < columns; j++)
	      if (value[i][j] % value[startr][startc] != 0)
		{
		  if (i != startr)
		    for (z = 0; z < columns; z++)
		      ::add(tmp[z], tmp[z], value[i][z]);
		  i = rows;
		  j = columns;
		  startc--;
		  startr--;
		}
	}
    }
  
  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      value[i][i].negate();
}

void bigint_matrix::
snf_simple(bigint_matrix & T1, bigint_matrix & T2)
{
  /**
   ** DESCRIPTION: SNF Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** IMPROVEMENT: Havas
   ** PAPER: Recognizing badly represented Z-modules
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_simple(bigint_matrix &, bigint_matrix &)", DV_BIGM + 8);
  bigint PIVOT, TMP1, TMP2;
  bigint *tmp=NULL, *deltmp;

  if (T1.columns != rows)
    T1.set_no_of_columns(rows);
  if (T1.rows != rows)
    T1.set_no_of_rows(rows);

  if (T2.columns != columns)
    T2.set_no_of_columns(columns);
  if (T2.rows != columns)
    T2.set_no_of_rows(columns);

  T1.diag(1, 0);
  T2.diag(1, 0);

  bigint_matrix TR1 = T1;
  bigint_matrix TR2 = T2;
  bigint *REM;
  register lidia_size_t startr, startc, pivot, i, j, z, TEILBARKEIT;
  
  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
    {

      /* pivot: first non zero */
      pivot = -1;
      for (i = startr; i < rows; i++)
	for (j = startc; j < columns; j++)
	  if (value[i][j] != 0)
	    {
	      pivot = i;
	      i = rows;
	      j = columns;
	    }

      if (pivot != -1)
	{
	  /* swap pivot in actual row */
	  swap_rows(startr, pivot);
	  T1.swap_rows(startr, pivot);
      
	  TEILBARKEIT=0;

	  while(TEILBARKEIT==0)
	    {
	      TEILBARKEIT=1;
	      
	      /* mgcd computation and row elimination */
	      REM=TR2.mgcd2(deltmp=row(startr), columns);
              delete[] deltmp;

              delete[] REM;
	      TR2 = TR2.trans();
	      ::multiply(*this, *this, TR2);
	      ::multiply(T2, T2, TR2);
	      
	      tmp = value[startr];
	      for (i = 0; tmp[i].is_zero() && i < columns; i++);
	      swap_columns(startc, i);
	      T2.swap_columns(startc, i);
	  
	      /* mgcd computation and column elimination */
	      REM=TR1.mgcd2(deltmp=column(startc), rows);
              delete[] deltmp;

              delete[] REM;
	      ::multiply(*this, TR1, *this);
	      ::multiply(T1, TR1, T1);
	      
	      for (i = 0; value[i][startc].is_zero() && i < rows; i++);
	      swap_rows(startr, i);
	      T1.swap_rows(startr, i);

	      /* control: row == 0 */
	      tmp = value[startr];
	      for (i = startc+1; tmp[i].is_zero() && i < columns; i++);
	      if (i!=columns)
		TEILBARKEIT=0;
	    } 
	  
	  /* modulo test */
	  for (i = startr; i < rows; i++)
	    for (j = startc + 1; j < columns; j++)
	      if (value[i][j] % value[startr][startc] != 0)
		{
		  if (i != startr)
		    {
		      for (z = 0; z < columns; z++)
			::add(tmp[z], tmp[z], value[i][z]);
		      for (z = 0; z < rows; z++)
			::add(T1.value[startr][z], T1.value[startr][z], T1.value[i][z]);
		    }
		  i = rows;
		  j = columns;
		  startc--;
		  startr--;
		}
	}
    }
  
  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      {
	value[i][i].negate();
	for (z = 0; z < columns; z++)
	  T2.value[z][i].negate();
      }
}

void bigint_matrix::
snf_havas()
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules
   ** IMPROVEMENTS: Havas, best reaminder include mgcd
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_havas()", DV_BIGM + 8);
  register lidia_size_t i, j, z, index;
  bigint PIVOT;
  bigint *tmp=NULL;

  register lidia_size_t startr, startc, xpivot, ypivot, SW, TEILBARKEIT;
  bigint TMP1, TMP2;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
    {
      
      /* pivot: first non zero */
      xpivot = -1;
      ypivot = -1;
      for (i = startr; i < rows; i++)
	for (j = startc; j < columns; j++)
	  if (!value[i][j].is_zero())
	    {
	      xpivot = i;
	      ypivot = j;
	      i = rows;
	      j = columns;
	    }
      
      if (xpivot != -1)
	{
	  /* swap to actual row */
	  swap_rows(startr, xpivot);

	  index = ypivot;

	  TEILBARKEIT=0;

	  while(TEILBARKEIT==0)
	    {
	      TEILBARKEIT=1;

	      /* gcd2(row(startr),columns,TR2); */
	      tmp = value[startr];
	      do
		{
		  SW=0;
		  for (i = 0; i < columns; i++)
		    if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		      index = i;
	      
		  for (i = 0; i < columns; i++)
		    if (i != index && !tmp[i].is_zero())
		      {
			SW=1;
			div_rem(TMP1, TMP2, tmp[i], tmp[index]);
			for (j = 0; j < rows; j++)
			  {
			    ::multiply(TMP2, value[j][index], TMP1);
			    ::subtract(value[j][i], value[j][i], TMP2);
			  }
		      }
		}
	      while (SW== 1);
	  
	      for (i = 0; value[startr][i].is_zero() && i < columns; i++);
	      swap_columns(startc, i);
      
	      /* mgcd2(column(startc),rows,TR1); */
	      index = startr; /* no index search */
	      do
		{
		  SW=0;
		  for (i = 0; i < rows; i++)
		    if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		      index = i;
		  
		  for (i = 0; i < rows; i++)
		    if ((i != index) && !value[i][startc].is_zero())
		      {
			SW=1;
			tmp = value[i];
			div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
			for (j = 0; j < columns; j++)
			  {
			    ::multiply(TMP2, value[index][j], TMP1);
			    ::subtract(tmp[j], tmp[j], TMP2);
			  }
		      }
		}
	      while (SW==1);

	      for (i = 0; value[i][startc].is_zero() && i < rows; i++);
	      swap_rows(startr, i);

	      for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	      if(index!=columns)
		TEILBARKEIT=0;
	      index = startc;
	    }
	  
	  /* modulo test */
	  for (i = startr; i < rows; i++)
	    for (j = startc + 1; j < columns; j++)
	      if (value[i][j] % value[startr][startc] != 0)
		{
		  if (i != startr)
		    for (z = 0; z < columns; z++)
		      ::add(tmp[z], tmp[z], value[i][z]);
		  i = rows;
		  j = columns;
		  startc--;
		  startr--;
		}
	}
    }
  
  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      value[i][i].negate();
}

void bigint_matrix::
snf_havas(bigint_matrix & T1, bigint_matrix & T2)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules
   ** IMPROVEMENTS: Havas, best reaminder include mgcd
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_havas(bigint_matrix &, bigint_matrix &)", DV_BIGM + 8);
  register lidia_size_t i, j, z, index;
  bigint PIVOT;
  bigint *tmp=NULL;

  if (T1.columns != rows)
    T1.set_no_of_columns(rows);
  if (T1.rows != rows)
    T1.set_no_of_rows(rows);

  if (T2.columns != columns)
    T2.set_no_of_columns(columns);
  if (T2.rows != columns)
    T2.set_no_of_rows(columns);

  T1.diag(1, 0);
  T2.diag(1, 0);

  register lidia_size_t startr, startc, xpivot, ypivot, SW, TEILBARKEIT;
  bigint TMP1, TMP2;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
    {
      
      /* pivot: first non zero */
      xpivot = -1;
      ypivot = -1;
      for (i = startr; i < rows; i++)
	for (j = startc; j < columns; j++)
	  if (!value[i][j].is_zero())
	    {
	      xpivot = i;
	      ypivot = j;
	      i = rows;
	      j = columns;
	    }
      
      if (xpivot != -1)
	{
	  /* swap to actual row */
	  swap_rows(startr, xpivot);
	  T1.swap_rows(startr, xpivot);

	  index = ypivot;

	  TEILBARKEIT=0;

	  while(TEILBARKEIT==0)
	    {
	      TEILBARKEIT=1;

	      /* gcd2(row(startr),columns,TR2); */
	      tmp = value[startr];
	      do
		{
		  SW=0;
		  for (i = 0; i < columns; i++)
		    if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		      index = i;
	      
		  for (i = 0; i < columns; i++)
		    if (i != index && !tmp[i].is_zero())
		      {
			SW=1;
			div_rem(TMP1, TMP2, tmp[i], tmp[index]);
			for (j = 0; j < rows; j++)
			  {
			    ::multiply(TMP2, value[j][index], TMP1);
			    ::subtract(value[j][i], value[j][i], TMP2);
			  }
			for (j = 0; j < columns; j++)
			  {
			    ::multiply(TMP2, T2.value[j][index], TMP1);
			    ::subtract(T2.value[j][i], T2.value[j][i], TMP2);
			  }
		      }
		}
	      while (SW== 1);
	  
	      for (i = 0; value[startr][i].is_zero() && i < columns; i++);
	      swap_columns(startc, i);
	      T2.swap_columns(startc, i);
      
	      /* mgcd2(column(startc),rows,TR1); */
	      index = startr; /* no index search */
	      do
		{
		  SW=0;
		  for (i = 0; i < rows; i++)
		    if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		      index = i;
		  
		  for (i = 0; i < rows; i++)
		    if ((i != index) && !value[i][startc].is_zero())
		      {
			SW=1;
			tmp = value[i];
			div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
			for (j = 0; j < columns; j++)
			  {
			    ::multiply(TMP2, value[index][j], TMP1);
			    ::subtract(tmp[j], tmp[j], TMP2);
			  }
			for (j = 0; j < rows; j++)
			  {
			    ::multiply(TMP2, T1.value[index][j], TMP1);
			    ::subtract(T1.value[i][j], T1.value[i][j], TMP2);
			  }
		      }
		}
	      while (SW==1);

	      for (i = 0; value[i][startc].is_zero() && i < rows; i++);
	      swap_rows(startr, i);
	      T1.swap_rows(startr, i);

	      for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	      if(index!=columns)
		TEILBARKEIT=0;
	      index = startc;
	    }
	  
	  /* modulo test */
	  for (i = startr; i < rows; i++)
	    for (j = startc + 1; j < columns; j++)
	      if (value[i][j] % value[startr][startc] != 0)
		{
		  if (i != startr)
		    {
		      for (z = 0; z < columns; z++)
			::add(tmp[z], tmp[z], value[i][z]);
		      for (z = 0; z < rows; z++)
			::add(T1.value[startr][z], T1.value[startr][z], T1.value[i][z]);
		    }
		  i = rows;
		  j = columns;
		  startc--;
		  startr--;
		}
	}
    }
  
  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      {
	value[i][i].negate();
	for (z = 0; z < columns; z++)
	  T2.value[z][i].negate();
      }
}

void bigint_matrix::
snf_mult(long art)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules + pivot selection
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_mult(bigint_matrix &, bigint_matrix &)", DV_BIGM + 8);
  register lidia_size_t i, j, z, index, SW;
  bigint TMP1, TMP2;
  bigint *tmp=NULL;

  register lidia_size_t startr, startc, xpivot, ypivot, TEILBARKEIT;
  bigint ROW, COLUMN, PIVOT, NORM;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
  {
    
    /* pivotselection: minimale C * R norm */
    xpivot = -1;
    ypivot = -1;
    PIVOT.assign_zero();
    for (i = startr; i < rows; i++)
      for (j = startc; j < columns; j++)
	{
	  if (PIVOT == abs(value[i][j]))
	    {
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::multiply(TMP1, ROW, COLUMN);
	      if (TMP1 < NORM)
		{
		  NORM.assign(TMP1);
		  PIVOT.assign(abs(value[i][j]));
		  xpivot = i;
		  ypivot = j;
		}
	    }

	  if ((PIVOT > abs(value[i][j]) && !value[i][j].is_zero()) || PIVOT.is_zero())
	    {
	      PIVOT.assign(abs(value[i][j]));
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::multiply(NORM, ROW, COLUMN);
	      xpivot = i;
	      ypivot = j;
	    }
	}

    if (!PIVOT.is_zero())
      {
	
	/* swap to actual row */
	swap_rows(startr, xpivot);
	
	index = ypivot;

	TEILBARKEIT=0;

	while(TEILBARKEIT==0)
	  {
	    TEILBARKEIT=1;
	    
	    /* gcd2(row(startr),columns,TR2); */
	    tmp = value[startr];
	    do
	      {
		SW=0;
		for (i = 0; i < columns; i++)
		  if ((i != index) && !tmp[i].is_zero())
		    {
		      SW=1;
		      div_rem(TMP1, TMP2, tmp[i], tmp[index]);
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, value[j][index], TMP1);
			  ::subtract(value[j][i], value[j][i], TMP2);
			}
		    }
		
		for (i = 0; i < columns; i++)
		  if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		    index = i;
	      }
	    while (SW==1);

	    for (i = 0; value[startr][i].is_zero(); i++);
	    swap_columns(startc, i);
	
	    /* mgcd2(column(startc),rows,TR1); */
	    index = startr;
	    do
	      {
		SW=0;
		for (i = 0; i < rows; i++)
		  if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		    index = i;
		
		for (i = 0; i < rows; i++)
		  if ((i != index) && !value[i][startc].is_zero())
		    {
		      SW=1;
		      tmp = value[i];
		      div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, value[index][j], TMP1);
			  ::subtract(tmp[j], tmp[j], TMP2);
			}
		    }
	      }
	    while (SW==1);

	    for (i = 0; value[i][startc].is_zero(); i++);
	    swap_rows(startr, i);

	    tmp = value[startr];
	    for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	    if (index!=columns)
	      TEILBARKEIT=0;

	    index=startr;
	  }

	/* modulo test */
	for (i = startr; i < rows; i++)
	  for (j = startc + 1; j < columns; j++)
	    if (value[i][j] % value[startr][startc] != 0)
	      {
		if (i != startr)
		  for (z = 0; z < columns; z++)
		    ::add(tmp[z], tmp[z], value[i][z]);
		i = rows;
		j = columns;
		startc--;
		startr--;
	      }
      }
  }

  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      value[i][i].negate();
}

void bigint_matrix::
snf_mult(bigint_matrix & T1, bigint_matrix & T2, long art)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules + pivot selection
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_mult(bigint_matrix &, bigint_matrix &)", DV_BIGM + 8);
  register lidia_size_t i, j, z, index, SW;
  bigint TMP1, TMP2;
  bigint *tmp=NULL;

  if (T1.columns != rows)
    T1.set_no_of_columns(rows);
  if (T1.rows != rows)
    T1.set_no_of_rows(rows);

  if (T2.columns != columns)
    T2.set_no_of_columns(columns);
  if (T2.rows != columns)
    T2.set_no_of_rows(columns);

  T1.diag(1, 0);
  T2.diag(1, 0);

  register lidia_size_t startr, startc, xpivot, ypivot, TEILBARKEIT;
  bigint ROW, COLUMN, PIVOT, NORM;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
  {
    
    /* pivotselection: minimale C * R norm */
    xpivot = -1;
    ypivot = -1;
    PIVOT.assign_zero();
    for (i = startr; i < rows; i++)
      for (j = startc; j < columns; j++)
	{
	  if (PIVOT == abs(value[i][j]))
	    {
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::multiply(TMP1, ROW, COLUMN);
	      if (TMP1 < NORM)
		{
		  NORM.assign(TMP1);
		  PIVOT.assign(abs(value[i][j]));
		  xpivot = i;
		  ypivot = j;
		}
	    }

	  if ((PIVOT > abs(value[i][j]) && !value[i][j].is_zero()) || PIVOT.is_zero())
	    {
	      PIVOT.assign(abs(value[i][j]));
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::multiply(NORM, ROW, COLUMN);
	      xpivot = i;
	      ypivot = j;
	    }
	}

    if (!PIVOT.is_zero())
      {
	
	/* swap to actual row */
	swap_rows(startr, xpivot);
	T1.swap_rows(startr, xpivot);
	
	index = ypivot;

	TEILBARKEIT=0;

	while(TEILBARKEIT==0)
	  {
	    TEILBARKEIT=1;
	    
	    /* gcd2(row(startr),columns,TR2); */
	    tmp = value[startr];
	    do
	      {
		SW=0;
		for (i = 0; i < columns; i++)
		  if ((i != index) && !tmp[i].is_zero())
		    {
		      SW=1;
		      div_rem(TMP1, TMP2, tmp[i], tmp[index]);
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, value[j][index], TMP1);
			  ::subtract(value[j][i], value[j][i], TMP2);
			}
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, T2.value[j][index], TMP1);
			  ::subtract(T2.value[j][i], T2.value[j][i], TMP2);
			}
		    }
		
		for (i = 0; i < columns; i++)
		  if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		    index = i;
	      }
	    while (SW==1);

	    for (i = 0; value[startr][i].is_zero(); i++);
	    swap_columns(startc, i);
	    T2.swap_columns(startc, i);
	
	    /* mgcd2(column(startc),rows,TR1); */
	    index = startr;
	    do
	      {
		SW=0;
		for (i = 0; i < rows; i++)
		  if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		    index = i;
		
		for (i = 0; i < rows; i++)
		  if ((i != index) && !value[i][startc].is_zero())
		    {
		      SW=1;
		      tmp = value[i];
		      div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, value[index][j], TMP1);
			  ::subtract(tmp[j], tmp[j], TMP2);
			}
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, T1.value[index][j], TMP1);
			  ::subtract(T1.value[i][j], T1.value[i][j], TMP2);
			}
		    }
	      }
	    while (SW==1);

	    for (i = 0; value[i][startc].is_zero(); i++);
	    swap_rows(startr, i);
	    T1.swap_rows(startr, i);

	    tmp = value[startr];
	    for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	    if (index!=columns)
	      TEILBARKEIT=0;

	    index=startr;
	  }

	/* modulo test */
	for (i = startr; i < rows; i++)
	  for (j = startc + 1; j < columns; j++)
	    if (value[i][j] % value[startr][startc] != 0)
	      {
		if (i != startr)
		  {
		    for (z = 0; z < columns; z++)
		      ::add(tmp[z], tmp[z], value[i][z]);
		    for (z = 0; z < rows; z++)
		      ::add(T1.value[startr][z], T1.value[startr][z], T1.value[i][z]);
		  }
		i = rows;
		j = columns;
		startc--;
		startr--;
	      }
      }
  }

  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      {
	value[i][i].negate();
	for (z = 0; z < columns; z++)
	  T2.value[z][i].negate();
      }
}

void bigint_matrix::
snf_add(long art)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules + pivot selection
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_add(long)", DV_BIGM + 8);
  
  register lidia_size_t i, j, z, index, SW;
  bigint TMP1, TMP2;
  bigint *tmp=NULL;

  register lidia_size_t startr, startc, xpivot, ypivot, TEILBARKEIT;
  bigint ROW, COLUMN, PIVOT, NORM;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
  {
    
    /* pivotselection: minimale C * R norm */
    xpivot = -1;
    ypivot = -1;
    PIVOT.assign_zero();
    for (i = startr; i < rows; i++)
      for (j = startc; j < columns; j++)
	{
	  if (PIVOT == abs(value[i][j]))
	    {
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::add(TMP1, ROW, COLUMN);
	      if (TMP1 < NORM)
		{
		  NORM.assign(TMP1);
		  PIVOT.assign(abs(value[i][j]));
		  xpivot = i;
		  ypivot = j;
		}
	    }

	  if ((PIVOT > abs(value[i][j]) && !value[i][j].is_zero()) || PIVOT.is_zero())
	    {
	      PIVOT.assign(abs(value[i][j]));
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::add(NORM, ROW, COLUMN);
	      xpivot = i;
	      ypivot = j;
	    }
	}

    if (!PIVOT.is_zero())
      {
	
	/* swap to actual row */
	swap_rows(startr, xpivot);
	
	index = ypivot;

	TEILBARKEIT=0;

	while(TEILBARKEIT==0)
	  {
	    TEILBARKEIT=1;
	    
	    /* gcd2(row(startr),columns,TR2); */
	    tmp = value[startr];
	    do
	      {
		SW=0;
		for (i = 0; i < columns; i++)
		  if ((i != index) && !tmp[i].is_zero())
		    {
		      SW=1;
		      div_rem(TMP1, TMP2, tmp[i], tmp[index]);
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, value[j][index], TMP1);
			  ::subtract(value[j][i], value[j][i], TMP2);
			}
		    }
		
		for (i = 0; i < columns; i++)
		  if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		    index = i;
	      }
	    while (SW==1);

	    for (i = 0; value[startr][i].is_zero(); i++);
	    swap_columns(startc, i);
	
	    /* mgcd2(column(startc),rows,TR1); */
	    index = startr;
	    do
	      {
		SW=0;
		for (i = 0; i < rows; i++)
		  if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		    index = i;
		
		for (i = 0; i < rows; i++)
		  if ((i != index) && !value[i][startc].is_zero())
		    {
		      SW=1;
		      tmp = value[i];
		      div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, value[index][j], TMP1);
			  ::subtract(tmp[j], tmp[j], TMP2);
			}
		    }
	      }
	    while (SW==1);

	    for (i = 0; value[i][startc].is_zero(); i++);
	    swap_rows(startr, i);

	    tmp = value[startr];
	    for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	    if (index!=columns)
	      TEILBARKEIT=0;

	    index=startr;
	  }

	/* modulo test */
	for (i = startr; i < rows; i++)
	  for (j = startc + 1; j < columns; j++)
	    if (value[i][j] % value[startr][startc] != 0)
	      {
		if (i != startr)
		  for (z = 0; z < columns; z++)
		    ::add(tmp[z], tmp[z], value[i][z]);
		i = rows;
		j = columns;
		startc--;
		startr--;
	      }
      }
  }

  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      value[i][i].negate();
}

void bigint_matrix::
snf_add(bigint_matrix & T1, bigint_matrix & T2, long art)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules + pivot selection
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_add(bigint_matrix &, bigint_matrix &, long)", DV_BIGM + 8);

  register lidia_size_t i, j, z, index, SW;
  bigint TMP1, TMP2;
  bigint *tmp=NULL;

  if (T1.columns != rows)
    T1.set_no_of_columns(rows);
  if (T1.rows != rows)
    T1.set_no_of_rows(rows);

  if (T2.columns != columns)
    T2.set_no_of_columns(columns);
  if (T2.rows != columns)
    T2.set_no_of_rows(columns);

  T1.diag(1, 0);
  T2.diag(1, 0);

  register lidia_size_t startr, startc, xpivot, ypivot, TEILBARKEIT;
  bigint ROW, COLUMN, PIVOT, NORM;

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
  {
    
    /* pivotselection: minimale C * R norm */
    xpivot = -1;
    ypivot = -1;
    PIVOT.assign_zero();
    for (i = startr; i < rows; i++)
      for (j = startc; j < columns; j++)
	{
	  if (PIVOT == abs(value[i][j]))
	    {
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::add(TMP1, ROW, COLUMN);
	      if (TMP1 < NORM)
		{
		  NORM.assign(TMP1);
		  PIVOT.assign(abs(value[i][j]));
		  xpivot = i;
		  ypivot = j;
		}
	    }

	  if ((PIVOT > abs(value[i][j]) && !value[i][j].is_zero()) || PIVOT.is_zero())
	    {
	      PIVOT.assign(abs(value[i][j]));
	      row_norm(ROW, i, art);
	      column_norm(COLUMN, j, art);
	      ::add(NORM, ROW, COLUMN);
	      xpivot = i;
	      ypivot = j;
	    }
	}

    if (!PIVOT.is_zero())
      {
	
	/* swap to actual row */
	swap_rows(startr, xpivot);
	T1.swap_rows(startr, xpivot);
	
	index = ypivot;

	TEILBARKEIT=0;

	while(TEILBARKEIT==0)
	  {
	    TEILBARKEIT=1;
	    
	    /* gcd2(row(startr),columns,TR2); */
	    tmp = value[startr];
	    do
	      {
		SW=0;
		for (i = 0; i < columns; i++)
		  if ((i != index) && !tmp[i].is_zero())
		    {
		      SW=1;
		      div_rem(TMP1, TMP2, tmp[i], tmp[index]);
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, value[j][index], TMP1);
			  ::subtract(value[j][i], value[j][i], TMP2);
			}
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, T2.value[j][index], TMP1);
			  ::subtract(T2.value[j][i], T2.value[j][i], TMP2);
			}
		    }
		
		for (i = 0; i < columns; i++)
		  if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		    index = i;
	      }
	    while (SW==1);

	    for (i = 0; value[startr][i].is_zero(); i++);
	    swap_columns(startc, i);
	    T2.swap_columns(startc, i);
	
	    /* mgcd2(column(startc),rows,TR1); */
	    index = startr;
	    do
	      {
		SW=0;
		for (i = 0; i < rows; i++)
		  if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		    index = i;
		
		for (i = 0; i < rows; i++)
		  if ((i != index) && !value[i][startc].is_zero())
		    {
		      SW=1;
		      tmp = value[i];
		      div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, value[index][j], TMP1);
			  ::subtract(tmp[j], tmp[j], TMP2);
			}
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, T1.value[index][j], TMP1);
			  ::subtract(T1.value[i][j], T1.value[i][j], TMP2);
			}
		    }
	      }
	    while (SW==1);

	    for (i = 0; value[i][startc].is_zero(); i++);
	    swap_rows(startr, i);
	    T1.swap_rows(startr, i);

	    tmp = value[startr];
	    for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	    if (index!=columns)
	      TEILBARKEIT=0;

	    index=startr;
	  }

	/* modulo test */
	for (i = startr; i < rows; i++)
	  for (j = startc + 1; j < columns; j++)
	    if (value[i][j] % value[startr][startc] != 0)
	      {
		if (i != startr)
		  {
		    for (z = 0; z < columns; z++)
		      ::add(tmp[z], tmp[z], value[i][z]);
		    for (z = 0; z < rows; z++)
		      ::add(T1.value[startr][z], T1.value[startr][z], T1.value[i][z]);
		  }
		i = rows;
		j = columns;
		startc--;
		startr--;
	      }
      }
  }

  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      {
	value[i][i].negate();
	for (z = 0; z < columns; z++)
	  T2.value[z][i].negate();
      }
}

void bigint_matrix::
snf_new(long art)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules + pivot selection
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_new(long)", DV_BIGM + 8);
  register lidia_size_t i, j, z, index, SW;
  bigint TMP1, TMP2;
  bigint *tmp=NULL;

  register lidia_size_t startr, startc, xpivot, ypivot, TEILBARKEIT;
  bigint ROW, COLUMN, PIVOT, NORM;

  bigint *RO = new bigint[rows];
  bigint *CO = new bigint[columns];

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
  {
    /* norm computation */
    for(i=0;i<rows;i++)
      row_norm(RO[i],i,art);
    for(i=0;i<columns;i++)
      column_norm(CO[i],i,art);

    /* pivotselection: new minimale C * R norm */
    xpivot = -1;
    ypivot = -1;
    PIVOT.assign_zero();
    NORM.assign_zero();
    for (i = startr; i < rows; i++)
      for (j = startc; j < columns; j++)
	{
	  ::multiply(TMP1,RO[i],CO[j]);
	  
	  if (!value[i][j].is_zero() && (NORM > TMP1 || PIVOT.is_zero()))
	    {
	      NORM.assign(TMP1);
	      PIVOT.assign(abs(value[i][j]));
	      xpivot = i;
	      ypivot = j;
	    }
	    
	  if (NORM==TMP1 && !PIVOT.is_zero() && !value[i][j].is_zero())
	    {
	      if (PIVOT > abs(value[i][j]))
		{
		  PIVOT.assign(abs(value[i][j]));
		  NORM.assign(TMP1);
		  xpivot = i;
		  ypivot = j;
		}
	    }
	}

    if (!PIVOT.is_zero())
      {
	
	/* swap to actual row */
	swap_rows(startr, xpivot);
	
	index = ypivot;

	TEILBARKEIT=0;

	while(TEILBARKEIT==0)
	  {
	    TEILBARKEIT=1;
	    
	    /* gcd2(row(startr),columns,TR2); */
	    tmp = value[startr];
	    do
	      {
		SW=0;
		for (i = 0; i < columns; i++)
		  if ((i != index) && !tmp[i].is_zero())
		    {
		      SW=1;
		      div_rem(TMP1, TMP2, tmp[i], tmp[index]);
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, value[j][index], TMP1);
			  ::subtract(value[j][i], value[j][i], TMP2);
			}
		    }
		
		for (i = 0; i < columns; i++)
		  if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		    index = i;
	      }
	    while (SW==1);

	    for (i = 0; value[startr][i].is_zero(); i++);
	    swap_columns(startc, i);
	
	    /* mgcd2(column(startc),rows,TR1); */
	    index = startr;
	    do
	      {
		SW=0;
		for (i = 0; i < rows; i++)
		  if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		    index = i;
		
		for (i = 0; i < rows; i++)
		  if ((i != index) && !value[i][startc].is_zero())
		    {
		      SW=1;
		      tmp = value[i];
		      div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, value[index][j], TMP1);
			  ::subtract(tmp[j], tmp[j], TMP2);
			}
		    }
	      }
	    while (SW==1);

	    for (i = 0; value[i][startc].is_zero(); i++);
	    swap_rows(startr, i);

	    tmp = value[startr];
	    for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	    if (index!=columns)
	      TEILBARKEIT=0;

	    index=startr;
	  }

	/* modulo test */
	for (i = startr; i < rows; i++)
	  for (j = startc + 1; j < columns; j++)
	    if (value[i][j] % value[startr][startc] != 0)
	      {
		if (i != startr)
		  for (z = 0; z < columns; z++)
		    ::add(tmp[z], tmp[z], value[i][z]);
		i = rows;
		j = columns;
		startc--;
		startr--;
	      }
      }
  }

  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      value[i][i].negate();
}

void bigint_matrix::
snf_new(bigint_matrix & T1, bigint_matrix & T2, long art)
{
  /**
   ** DESCRIPTION: snf Computation
   ** ALGORITHM: given in Hartley and Hawkes
   ** PAPER: Recognizing badly represented Z-modules + pivot selection
   ** VERSION: 2.0
   **/
  
  debug_handler_l(DM_BIGM, "in member - function "
		  "snf_new(bigint_matrix &, bigint_matrix &, long)", DV_BIGM + 8);
  register lidia_size_t i, j, z, index, SW;
  bigint TMP1, TMP2;
  bigint *tmp=NULL;

  if (T1.columns != rows)
    T1.set_no_of_columns(rows);
  if (T1.rows != rows)
    T1.set_no_of_rows(rows);

  if (T2.columns != columns)
    T2.set_no_of_columns(columns);
  if (T2.rows != columns)
    T2.set_no_of_rows(columns);

  T1.diag(1, 0);
  T2.diag(1, 0);

  register lidia_size_t startr, startc, xpivot, ypivot, TEILBARKEIT;
  bigint ROW, COLUMN, PIVOT, NORM;

  bigint *RO = new bigint[rows];
  bigint *CO = new bigint[columns];

  for (startc = 0, startr = 0; startr < rows && startc < columns; startr++, startc++)
  {
    /* norm computation */
    for(i=0;i<rows;i++)
      row_norm(RO[i],i,art);
    for(i=0;i<columns;i++)
      column_norm(CO[i],i,art);

    /* pivotselection: new minimale C * R norm */
    xpivot = -1;
    ypivot = -1;
    PIVOT.assign_zero();
    NORM.assign_zero();
    for (i = startr; i < rows; i++)
      for (j = startc; j < columns; j++)
	{
	  /*row_norm(ROW,i,art);
	  column_norm(COLUMN,j,art);*/
	  ::multiply(TMP1,RO[i],CO[j]);
	  
	  if (!value[i][j].is_zero() && (NORM > TMP1 || PIVOT.is_zero()))
	    {
	      NORM.assign(TMP1);
	      PIVOT.assign(abs(value[i][j]));
	      xpivot = i;
	      ypivot = j;
	    }
	    
	  if (NORM==TMP1 && !PIVOT.is_zero() && !value[i][j].is_zero())
	    {
	      if (PIVOT > abs(value[i][j]))
		{
		  PIVOT.assign(abs(value[i][j]));
		  NORM.assign(TMP1);
		  xpivot = i;
		  ypivot = j;
		}
	    }
	}

    if (!PIVOT.is_zero())
      {
	
	/* swap to actual row */
	swap_rows(startr, xpivot);
	T1.swap_rows(startr, xpivot);
	
	index = ypivot;

	TEILBARKEIT=0;

	while(TEILBARKEIT==0)
	  {
	    TEILBARKEIT=1;
	    
	    /* gcd2(row(startr),columns,TR2); */
	    tmp = value[startr];
	    do
	      {
		SW=0;
		for (i = 0; i < columns; i++)
		  if ((i != index) && !tmp[i].is_zero())
		    {
		      SW=1;
		      div_rem(TMP1, TMP2, tmp[i], tmp[index]);
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, value[j][index], TMP1);
			  ::subtract(value[j][i], value[j][i], TMP2);
			}
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, T2.value[j][index], TMP1);
			  ::subtract(T2.value[j][i], T2.value[j][i], TMP2);
			}
		    }
		
		for (i = 0; i < columns; i++)
		  if ((abs(tmp[index]) > abs(tmp[i])) && !tmp[i].is_zero())
		    index = i;
	      }
	    while (SW==1);

	    for (i = 0; value[startr][i].is_zero(); i++);
	    swap_columns(startc, i);
	    T2.swap_columns(startc, i);
	
	    /* mgcd2(column(startc),rows,TR1); */
	    index = startr;
	    do
	      {
		SW=0;
		for (i = 0; i < rows; i++)
		  if ((abs(value[index][startc]) > abs(value[i][startc])) && !value[i][startc].is_zero())
		    index = i;
		
		for (i = 0; i < rows; i++)
		  if ((i != index) && !value[i][startc].is_zero())
		    {
		      SW=1;
		      tmp = value[i];
		      div_rem(TMP1, TMP2, tmp[startc], value[index][startc]);
		      for (j = 0; j < columns; j++)
			{
			  ::multiply(TMP2, value[index][j], TMP1);
			  ::subtract(tmp[j], tmp[j], TMP2);
			}
		      for (j = 0; j < rows; j++)
			{
			  ::multiply(TMP2, T1.value[index][j], TMP1);
			  ::subtract(T1.value[i][j], T1.value[i][j], TMP2);
			}
		    }
	      }
	    while (SW==1);

	    for (i = 0; value[i][startc].is_zero(); i++);
	    swap_rows(startr, i);
	    T1.swap_rows(startr, i);

	    tmp = value[startr];
	    for (index=startc+1;index<columns && tmp[index].is_zero();index++);
	    if (index!=columns)
	      TEILBARKEIT=0;

	    index=startr;
	  }

	/* modulo test */
	for (i = startr; i < rows; i++)
	  for (j = startc + 1; j < columns; j++)
	    if (value[i][j] % value[startr][startc] != 0)
	      {
		if (i != startr)
		  {
		    for (z = 0; z < columns; z++)
		      ::add(tmp[z], tmp[z], value[i][z]);
		    for (z = 0; z < rows; z++)
		      ::add(T1.value[startr][z], T1.value[startr][z], T1.value[i][z]);
		  }
		i = rows;
		j = columns;
		startc--;
		startr--;
	      }
      }
  }

  /* diagonal >= 0 */
  for (i = 0; i < rows && i < columns; i++)
    if (value[i][i] < 0)
      {
	value[i][i].negate();
	for (z = 0; z < columns; z++)
	  T2.value[z][i].negate();
      }
}

void bigint_matrix::
snfmod_dkt(const bigint &mod)
{
  /**
   ** DESCRIPTION: A.snfmod_dkt(mod);
   **              => A in Smith normal form
   **              => h = lattice determinant of lattice formed
   **              by the columns of matrix A
   ** PAPER: Asymptotically fast triangulazation of matrices over rings
   ** IMPROVEMENT: Hafner, McCurly
   ** ERROR: rank != rows
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "snfmod_dkt(const bigint &)", DV_BIGM + 8);
  
  if (rank() != rows)
    lidia_error_handler_para(rank(), "rank", "rows == rank",
			     rows, "rows", "rows == rank",
			     "void bigint_matrix::"
			     "snfmod_dkt(const bigint &mod)", 
			     DM_BIGM, LBIGM_ERROR[10]);
  
  register lidia_size_t diagindex, j, z, l;
  
  bigint RES0, RES1, RES2, RES3;  /* 0=lggT,1=rggt,2=ggt */
  bigint x, y;
  bigint TMP, TMP1, TMP2, TMP3;
  bigint *Atmp, *Atmp1 = NULL;
  
  /* A = A % mod */
  for (z = 0; z < rows; z++)
    {
      Atmp = value[z];
      for (j = 0; j < columns; j++)
	best_remainder(Atmp[j], Atmp[j], mod);
    }
  
  /* Step 3 - 5 */
  for (diagindex = 0; diagindex < rows; diagindex++)
    {
      Atmp = value[diagindex];
      
      /* if diagonalelement == 0 then diagonalelement = mod */
      if (Atmp[diagindex].is_zero())
	Atmp[diagindex].assign(mod);
      
      /* Step 6 -8 */
      for (j = diagindex+1; j < columns; j++)
	if (!Atmp[j].is_zero())
	  {
	    /* columns reduction */
	    RES2 = xgcd(RES0, RES1, Atmp[j], Atmp[diagindex]);
	    div_rem(x, RES3, Atmp[diagindex], RES2);
	    div_rem(y, RES3, Atmp[j], RES2);
	    
	    for (z = 0; z < rows; z++)
	      {
		Atmp1 = value[z];
		TMP.assign(Atmp1[j]);
		TMP1.assign(Atmp1[diagindex]);
		
		mult_mod(TMP2,TMP,x,mod);
		mult_mod(TMP3,TMP1,y,mod);
		sub_mod(Atmp1[j],TMP2,TMP3,mod);
		
		mult_mod(TMP2,TMP,RES0,mod);
		mult_mod(TMP3,TMP1,RES1,mod);
		add_mod(Atmp1[diagindex],TMP2,TMP3,mod);
	      }
	  }
      
      for (j = diagindex+1; j < rows; j++)
	if (!value[j][diagindex].is_zero())
	  {
	    /* row reduction */
	    RES2 = xgcd(RES0, RES1, value[j][diagindex], Atmp[diagindex]);
	    div_rem(x, RES3, Atmp[diagindex], RES2);
	    div_rem(y, RES3, value[j][diagindex], RES2);
	    
	    for (z = 0; z < columns; z++)
	      {
		TMP.assign(value[j][z]);
		TMP1.assign(value[diagindex][z]);
		
		mult_mod(TMP2,TMP,x,mod);
		mult_mod(TMP3,TMP1,y,mod);
		sub_mod(value[j][z],TMP2,TMP3,mod);
		
		mult_mod(TMP2,TMP,RES0,mod);
		mult_mod(TMP3,TMP1,RES1,mod);
		add_mod(value[diagindex][z],TMP2,TMP3,mod);
	      }
	  }
      
      /* value[diagindex][diagindex] | value[i][j] ??? */
      TMP = Atmp[diagindex];
      for (j=diagindex+1;j<rows;j++)
	for (z=diagindex+1;z<columns;z++)
	  {
	    if (value[j][z] % TMP != 0)
	      {
		if (j != diagindex)
		  for (l = diagindex; l<columns; l++)
		    add_mod(Atmp[l],Atmp[l],value[j][l],mod);
		j = rows;
		z = columns;
	      }
	  }     
      
      for (z=diagindex+1; z<columns && Atmp[z].is_zero(); z++);
      if (z != columns)
	diagindex--;     
    }
  
  /* Step 29 - 32 */
  bigint D = mod;
  for (j = 0; j < rows; j++)
    {
      Atmp = value[j];
      Atmp[j].assign(xgcd(RES0, RES1, Atmp[j], D));       
      div_rem(D, TMP, D, Atmp[j]);
    }
}

void bigint_matrix::
snfmod_cohen(const bigint & mod)
{
  /**
   ** DESCRIPTION: A.snfmod_cohen(mod);
   **              => A in Smith normal form
   **              => mod = multiple of lattice determinant of lattice formed
   **              by the columns of matrix A
   ** ERROR: rank != rows
   ** VERSION: 2.0
   **/

  debug_handler_l(DM_BIGM, "in member - function "
		  "snfmod_cohen(const bigint &)", DV_BIGM + 8);
  if (rank() != rows )
    lidia_error_handler_para(rank(), "rank", "rank == rows",
			     rows, "rows", "rank == rows",
			     "void bigint_matrix::"
			     "snfmod_cohen(const bigint & mod)",
			     DM_BIGM, LBIGM_ERROR[10]);
  
  register lidia_size_t diagindex, j, z, l;
  
  bigint RES0, RES1, RES2, RES3;  /* 0=lggT,1=rggt,2=ggt */
  bigint x, y;
  bigint TMP, TMP1, TMP2, TMP3;
  bigint *Atmp, *Atmp1 = NULL;
  bigint D = mod;

  /* Step 1 */
  for (diagindex = 0; diagindex < rows; diagindex++)
    {
      Atmp = value[diagindex];
      
      if (Atmp[diagindex].is_zero())
	Atmp[diagindex].assign(mod);
      
      /* Step 2 - 4 */
      for (j = diagindex+1; j < columns; j++)
	if (!Atmp[j].is_zero())
	  {
	    /* columns reduction */
	    RES2 = xgcd(RES0, RES1, Atmp[j], Atmp[diagindex]);
	    div_rem(x, RES3, Atmp[diagindex], RES2);
	    div_rem(y, RES3, Atmp[j], RES2);
	    
	    for (z = 0; z < rows; z++)
	      {
		Atmp1 = value[z];
		TMP.assign(Atmp1[j]);
		TMP1.assign(Atmp1[diagindex]);
		
		mult_mod(TMP2,TMP,x,mod);
		mult_mod(TMP3,TMP1,y,mod);
		sub_mod(Atmp1[j],TMP2,TMP3,mod);
		
		mult_mod(TMP2,TMP,RES0,mod);
		mult_mod(TMP3,TMP1,RES1,mod);
		add_mod(Atmp1[diagindex],TMP2,TMP3,mod);
	      }
	  }
      
      /* Step 5 - 7 */
      for (j = diagindex+1; j < rows; j++)
	if (!value[j][diagindex].is_zero())
	  {
	    /* row reduction */
	    RES2 = xgcd(RES0, RES1, value[j][diagindex], Atmp[diagindex]);
	    div_rem(x, RES3, Atmp[diagindex], RES2);
	    div_rem(y, RES3, value[j][diagindex], RES2);
	    
	    for (z = 0; z < columns; z++)
	      {
		TMP.assign(value[j][z]);
		TMP1.assign(value[diagindex][z]);
		
		mult_mod(TMP2,TMP,x,mod);
		mult_mod(TMP3,TMP1,y,mod);
		sub_mod(value[j][z],TMP2,TMP3,mod);
		
		mult_mod(TMP2,TMP,RES0,mod);
		mult_mod(TMP3,TMP1,RES1,mod);
		add_mod(value[diagindex][z],TMP2,TMP3,mod);
	      }
	  }
      
      /* Step 8,9 */
      TMP = Atmp[diagindex];
      
      for (j=diagindex+1;j<rows;j++)
	for (z=diagindex+1;z<columns;z++)
	  {
	    if (value[j][z] % TMP != 0)
	      {
		if (j != diagindex)
		  for (l = diagindex; l<columns; l++)
		    add_mod(Atmp[l],Atmp[l],value[j][l],mod);
		j = rows;
		z = columns;
	      }
	  }     
      
      for (z=diagindex+1; z<columns && Atmp[z].is_zero(); z++);
      if (z != columns)
	diagindex--;
      else
	{
	  /* Step 10 */
	  Atmp[diagindex]=xgcd(TMP1,TMP2,TMP,D);
	  div_rem(D,TMP1,D,Atmp[diagindex]);
	}
    }
}

/**
 ** END: Linear algebra
 ** PART 2
 **/

void bigint_matrix::
gauss()
{
  debug_handler(DM_BIGM, "in member - function gauss()");

  bigint_matrix TR(columns, columns);
  bigint *REM;
  register lidia_size_t startr = 0, startc = 0, i;
  
  for (startc = columns - 1, startr = rows - 1; startr >= 0 && startc >= 0; startr--, startc--)
  {
    bigint *ZU = row(startr);
    for (i = startc + 1; i < columns; ZU[i].assign_zero(), i++);
    REM=TR.mgcd1(ZU, columns);
    delete[] REM;
    delete[] ZU;
    
    TR = TR.trans();
    ::multiply(*this, *this, TR);
    for (i = 0; value[startr][i].is_zero() && i <= startc; i++);
    if (i > startc)
      return;
    swap_columns(startc, i);

  }
}

#undef DV_BIGM 
#undef DM_BIGM 
#undef LBIGM_ERROR 
