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

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

bigint * bigint_matrix::
mgcd(const bigint * a, lidia_size_t n)
{
  /**
   ** DESCRIPTION: RES = mgcd(a,n);
   **              => RES[0] = RES[1]*a[0] + ... + RES[n]*a[n-1]
   **              => RES[0] = gcd(a[0],...,a[n-1])
   ** ALGORITHM: Bradley
   ** VERSION: 1.8
   **/
  
  debug_handler_l("multiple_gcd", "in function "
		  "mgcd(const bigint *, lidia_size_t)", LDBL_MATRIX);
  
  bigint *RES = NULL;
  register lidia_size_t i;	
  
  if (n <= 0)
    lidia_error_handler("multiple_gcd", "mgcd :: Error in parameter !!");
  
  if (n > 1)
    {
      /* Step 1 - 8 */
      bigint TMP, TMP1, TMP2;
      
      bigint *tmp = new bigint[n];
      memory_handler(tmp, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (tmp)");
      bigint *y = new bigint[n];
      memory_handler(y, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (y)");
      bigint *z = new bigint[n];
      memory_handler(z, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (z)");
      
      tmp[0].assign(a[0]);
      for (i = 1; i < n; i++)
	tmp[i] = xgcd(y[i], z[i], tmp[i - 1], a[i]);
      
      /* Step 9 - 19 */
      bigint *x = new bigint[n];
      memory_handler(x, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (x)");
      bigint *y1 = new bigint[n];
      memory_handler(y1, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (y1)");
      
      x[n-1].assign(z[n-1]);
      y1[n-1].assign(y[n-1]);
      
      bigint *G = new bigint[n];
      memory_handler(G, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (G)");
      bigint *S = new bigint[n];
      memory_handler(S, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (S)");
      for (i = n - 2; i >= 1; i--)
	{
	  div_rem(G[i], TMP, tmp[i - 1], tmp[i]);
	  if (G[i].is_zero())
	    S[i].assign_zero();
	  else
	    nearest(S[i], y1[i + 1] * z[i], G[i]);

	  /* y1[i] = y1[i+1]*y[i] + S[i]*(a[i]/tmp[i]); */
	  div_rem(TMP1, TMP, a[i], tmp[i]);
	  ::multiply(TMP1, S[i], TMP1);
	  ::multiply(TMP2, y1[i + 1], y[i]);
	  ::add(y1[i], TMP2, TMP1);
	  
	  /* x[i] = y1[i+1]*z[i] - S[i]*G[i]; */
	  ::multiply(TMP1, S[i], G[i]);
	  ::multiply(TMP2, y1[i + 1], z[i]);
	  ::subtract(x[i], TMP2, TMP1);
	}
      x[0].assign(y1[1]);
      
      /* Step 20,21 */
      RES = new bigint[n + 1];
      memory_handler(RES, "matrix_bigint", "mgcd :: "
		     "Error in memory allocation (RES)");
      RES[0].assign(tmp[n - 1]);
      for (i = 1; i <= n; i++)
	RES[i].assign(x[i - 1]);
      
      delete[] tmp;
      delete[] y;
      delete[] z;
      delete[] x;
      delete[] y1;
      delete[] G;
      delete[] S;
    }	
  if (n == 1)
    {		
      RES = new bigint [2];
      RES[0].assign(a[0]);
      RES[1].assign_one();
    }
  if (RES[0].is_lt_zero())
     for (i=0;i<=n;i++)
       RES[i].negate();
  return RES;
}

bigint *bigint_matrix::
mgcd1(const bigint * aconst, lidia_size_t n)
{
  /**
   ** DESCRIPTION: RES = T.mgcd1(a,n);
   **              => RES[0] = RES[1]*a[0] + ... + RES[n]*a[n-1]
   **              => RES[0] = gcd(a[0],...,a[n-1])
   **              => T*a = RES
   ** ALGORITHM: Blankinship, PIVOT: MINIMUM
   ** VERSION: 1.8
   **/
  
  debug_handler("multiple_gcd", "in member - function "
		"mgcd1(const bigint *, lidia_size_t)");

  register long i, j, index, bound;
  bigint MIN, TMP, q, r, *Ttmp1, *Ttmp2 = NULL;
  
  if (columns != n)
    set_no_of_columns(n);
  if (rows != n)
    set_no_of_rows(n);
  diag(1, 0);
  
  bigint *a = new bigint[n + 1];
  memory_handler(a, "multiple_gcd", "mgcd1 :: "
		 "Error in memory allocation (a)");

  for (i = 0; i < n; i++)
    a[i].assign(aconst[i]);

  /* Init */
  for (index=0; index<n && a[index].is_zero();index++);

  if (index==n)
    {
      delete[] a;
      return new bigint[n];
    }
  else
    bound = index;

  do
    {      
      /* Pivot search: MINIMUM */
      MIN.assign(a[index]);
      
      for (i = bound; i < n; i++)
	if ((abs(MIN) > abs(a[i])) && !a[i].is_zero())
	  {
	    MIN.assign(a[i]);
	    index = i;
	  }
      
      /* first element != 0 */
      for (i = bound; i < n && (a[i].is_zero() || i == index); i++);
      if (i < n)
	{
	  div_rem(q, r, a[i], MIN);
	  a[i].assign(r);
	  Ttmp1 = value[i];
	  Ttmp2 = value[index];
	  for (j = 0; j < n; j++)
	    {
	      ::multiply(TMP, q, Ttmp2[j]);
	      ::subtract(Ttmp1[j], Ttmp1[j], TMP);
	    }
	}
    }
  while (i<n);

  Ttmp2 = value[index];
  
  /* gcd <0 ? */
  if (a[index] < 0)
    {
      a[index].negate();
      for (i = 0; i < n; i++)
	Ttmp2[i].negate();
    }
  
  if (index != 0)
    a[0].assign(a[index]);
  for (i = 1; i <= n; i++)
    a[i].assign(Ttmp2[i - 1]);
  return a;
}

void 
mgcd2(bigint & RES, const bigint * aconst, lidia_size_t n)
{
  /**
   ** DESCRIPTION: mgcd2(Res,a,n);
   **              => RES = gcd(a[0],...,a[n-1])
   ** ALGORITHM: Blankinship
   ** IMPROVEMENTS: Havas, Majewski, reduction of all elements, MIN assignments
   ** PAPER: Hermite normal form computation for integer matrices, Havas
   ** VERSION: 1.8
   **/

  debug_handler("multiple_gcd", "in member - function "
		"mgcd2(bigint &, const bigint *, lidia_size_t)");

  register lidia_size_t i, index, SW, bound;
  bigint MIN, TMP, q, r;

  bigint *a = new bigint[n + 1];
  memory_handler(a, "multiple_gcd", "mgcd2 :: "
		 "Error in memory allocation (a)");

  for (i = 0; i < n; i++)
    a[i].assign(aconst[i]);
  
  /* init */
  for (index=0; index<n && a[index].is_zero();index++);
  
  if (index==n)
    {
      RES.assign_zero();
      return;
    }
  else
    bound = index;
  
  do
    {
      MIN.assign(a[index]);
      
      /* Pivot search: MINIMUM */
      for (i = bound; i < n; i++)
	if ((abs(MIN) > abs(a[i])) && !a[i].is_zero())
	  {
	    MIN.assign(a[i]);
	    index = i;
	  }
      
      /* all elements */
      SW=0;
      
      for (i = bound; i < n; i++)
	if ((i != index) && !a[i].is_zero())
	  {
	    SW=1;
	    div_rem(q, r, a[i], MIN);
	    a[i].assign(r);
	  }
    }
  while (SW==1);

  /* gcd < 0 ? */
  if (a[index] < 0)
    ::negate(RES,a[index]);
  else
    RES.assign(a[index]);
}

bigint *
mgcd2(const bigint * aconst, lidia_size_t n)
{
  /**
   ** DESCRIPTION: RES = mgcd2(a,n);
   **              => RES[0] = RES[1]*a[0] + ... + RES[n]*a[n-1]
   **              => RES[0] = gcd(a[0],...,a[n-1])
   ** ALGORITHM: Blankinship
   ** IMPROVEMENTS: Havas, Majewski, reduction of all elements, MIN assignments
   ** PAPER: Hermite normal form computation for integer matrices, Havas
   ** VERSION: 1.8
   **/

  debug_handler("multiple_gcd", "in function "
		"mgcd2(const bigint *, lidia_size_t)");

  register lidia_size_t i, j, index, SW, bound;
  bigint MIN, TMP, q, r, *Ttmp1, *Ttmp2 = NULL;

  bigint_matrix T(n,n);
  T.diag(1,0);

  bigint *a = new bigint[n + 1];
  memory_handler(a, "multiple_gcd", "mgcd2 :: "
		 "Error in memory allocation (a)");

  for (i = 0; i < n; i++)
    a[i].assign(aconst[i]);


  for (index=0; index<n && a[index].is_zero();index++);

  if (index==n)
    {
      delete[] a;
      return new bigint[n];
    }
  else
    bound = index;

  do
    {
      MIN.assign(a[index]);
      

      for (i = bound; i < n; i++)
	if ((abs(MIN) > abs(a[i])) && !a[i].is_zero())
	  {
	    MIN.assign(a[i]);
	    index = i;
	  }
      

      SW=0;

      Ttmp2 = T.value[index];
      for (i = bound; i < n; i++)
	if ((i != index) && !a[i].is_zero())
	  {
	    SW=1;
	    Ttmp1 = T.value[i];
	    div_rem(q, r, a[i], MIN);
	    a[i].assign(r);
	    for (j = 0; j < n; j++)
	      {
		::multiply(TMP, q, Ttmp2[j]);
		::subtract(Ttmp1[j], Ttmp1[j], TMP);
	      }
	  }
    }
  while (SW==1);

  Ttmp2 = T.value[index];
  

  if (a[index] < 0)
    {
      a[index].negate();
      for (i = 0; i < n; i++)
	Ttmp2[i].negate();
    }
  
  if (index != 0)
    a[0].assign(a[index]);
  for (i = 1; i <= n; i++)
    a[i].assign(Ttmp2[i - 1]);
  
  return a;
}

bigint *bigint_matrix::
mgcd2(const bigint * aconst, lidia_size_t n)
{
  /**
   ** DESCRIPTION: RES = T.mgcd2(a,n);
   **              => RES[0] = RES[1]*a[0] + ... + RES[n]*a[n-1]
   **              => RES[0] = gcd(a[0],...,a[n-1])
   **              => T*a = RES
   ** ALGORITHM: Blankinship
   ** IMPROVEMENTS: Havas, Majewski, reduction of all elements, MIN assignments
   ** PAPER: Hermite normal form computation for integer matrices, Havas
   ** VERSION: 1.8
   **/

  debug_handler("multiple_gcd", "in member - function "
		"mgcd2(const bigint *, lidia_size_t)");

  register lidia_size_t i, j, index, bound, SW;
  bigint MIN, TMP, q, r, *Ttmp1, *Ttmp2 = NULL;

  if (columns != n)
    set_no_of_columns(n);
  if (rows != n)
    set_no_of_rows(n);
  diag(1, 0);

  bigint *a = new bigint[n + 1];
  memory_handler(a, "multiple_gcd", "mgcd2 :: "
		 "Error in memory allocation (a)");

  for (i = 0; i < n; i++)
    a[i].assign(aconst[i]);

  /* init */
  for (index=0; index<n && a[index].is_zero();index++);

  if (index==n)
    {
       delete[] a;
       return new bigint[n];
    }
  else
    bound = index;

  do
    {
      MIN.assign(a[index]);
      
      /* Pivot search: MINIMUM */
      for (i = bound; i < n; i++)
	if ((abs(MIN) > abs(a[i])) && !a[i].is_zero())
	  {
	    MIN.assign(a[i]);
	    index = i;
	  }
      
      /* all elements */
      SW=0;
      Ttmp2 = value[index];
      for (i = bound; i < n; i++)
	if ((i != index) && !a[i].is_zero())
	  {
	    SW=1;
	    Ttmp1 = value[i];
	    div_rem(q, r, a[i], MIN);
	    a[i].assign(r);
	    for (j = 0; j < n; j++)
	      {
		::multiply(TMP, q, Ttmp2[j]);
		::subtract(Ttmp1[j], Ttmp1[j], TMP);
	      }
	  }
    }
  while (SW==1);

  Ttmp2 = value[index];
  
  /* gcd < 0 ? */
  if (a[index] < 0)
    {
      a[index].negate();
      for (i = 0; i < n; i++)
	Ttmp2[i].negate();
    }
  
  if (index != 0)
    a[0].assign(a[index]);
  for (i = 1; i <= n; i++)
    a[i].assign(Ttmp2[i - 1]);
  
  return a;
}

bigint *bigint_matrix::
mgcd3(const bigint * aconst, lidia_size_t n)
{
  /**
   ** DESCRIPTION: RES = mgcd3(a,n,A);
   **              => RES[0] = RES[1]*a[0] + ... + RES[n]*a[n-1]
   **              => RES[0] = gcd(a[0],...,a[n-1])
   **              => T*a = RES
   ** ALGORITHM: Blankinship
   ** IMPROVEMENTS: Havas, Majewski, reduction of all elements, 
   **               index assignments
   ** PAPER: Hermite normal form computation for lidia_size_teger matrices, Havas
   ** VERSION: 1.8
   **/

  debug_handler("multiple_gcd", "in member - function "
		"mgcd3(const bigint *, lidia_size_t)");

  register lidia_size_t i, j, index, bound, SW;
  bigint MIN, TMP, q, r, *Ttmp2 = NULL, *Ttmp1;

  if (columns != n)
    set_no_of_columns(n);
  if (rows != n)
    set_no_of_rows(n);
  diag(1, 0);

  bigint *a = new bigint[n + 1];
  memory_handler(a, "multiple_gcd", "mgcd3 :: "
		 "Error in memory allocation (a)");
  
  for (i = 0; i < n; i++)
    a[i].assign(aconst[i]);
  
  /* init */
  for (index=0; index<n && a[index].is_zero();index++);
  
  if (index==n)
    {
       delete[] a;
       return new bigint[n];
    }
  else
    bound = index;
  
  do
    {
      /* Pivot search: MINIMUM */
      for (i = bound; i < n; i++)
	if ((abs(a[index]) > abs(a[i])) && !a[i].is_zero())
	  index = i;

      MIN.assign(a[index]);
      
      /* all elements */
      SW=0;
      Ttmp2 = value[index];
      
      for (i = bound; i < n; i++)
	if ((i != index) && !a[i].is_zero())
	  {
	    SW=1;
	    Ttmp1 = value[i];
	    div_rem(q, r, a[i], MIN);
	    a[i].assign(r);
	    for (j = 0; j < n; j++)
	      {
		::multiply(TMP, q, Ttmp2[j]);
		::subtract(Ttmp1[j], Ttmp1[j], TMP);
	      }
	  }
    }
  while (SW==1);

  Ttmp2 = value[index];
  
  if (a[index] < 0)
    {
      a[index].negate();
      for (i = 0; i < n; i++)
	Ttmp2[i].negate();
    }
  
  if (index != 0)
    a[0].assign(a[index]);
  for (i = 1; i <= n; i++)
    a[i].assign(Ttmp2[i - 1]);
  
  return a;
}

bigint *bigint_matrix::
mgcd4(const bigint * aconst, lidia_size_t n)
{
  /**
   ** DESCRIPTION: RES = mgcd4(a,n,A);
   **              => RES[0] = RES[1]*a[0] + ... + RES[n]*a[n-1]
   **              => RES[0] = gcd(a[0],...,a[n-1])
   **              => T*a = RES
   ** ALGORITHM: Blankinship
   ** IMPROVEMENTS: Havas, Majewski, reduction of all elements, 
   **               MIN assignments, best remainder
   ** PAPER: Hermite normal form computation for lidia_size_teger matrices, 
   **        Havas + best remainder
   ** VERSION: 1.8
   **/

  debug_handler("multiple_gcd", "in member - function "
		"mgcd4(const bigint *, lidia_size_t)");

  register lidia_size_t i, j, index, bound, SW;
  bigint MIN, TMP, q, r, *Ttmp1, *Ttmp2 = NULL;

  if (columns != n)
    set_no_of_columns(n);
  if (rows != n)
    set_no_of_rows(n);
  diag(1, 0);

  bigint *a = new bigint[n + 1];
  memory_handler(a, "multiple_gcd", "in mgcd4 :: Error in memory allocation (a) !!");

  for (i = 0; i < n; i++)
    a[i].assign(aconst[i]);

  /* init */
  for (index=0; index<n && a[index].is_zero();index++);
  
  if (index==n)
    {
       delete[] a;
       return new bigint[n];
    }
  else
    bound = index;

  do
    {
      
      MIN.assign(a[index]);
      
      /* Pivot search: MINIMUM */
      for (i = bound; i < n; i++)
	if ((abs(MIN) > abs(a[i])) && !a[i].is_zero())
	  {
	    MIN.assign(a[i]);
	    index = i;
	  }
      
      /* all elements */
      SW = 0;
      Ttmp2 = value[index];
      for (i = bound; i < n; i++)
	if ((i != index) && !a[i].is_zero())
	  {
	    SW = 1;

	    Ttmp1 = value[i];
	    pos_div_rem(q, r, a[i], MIN);
	    if (r * (bigint)2 > abs(MIN))
	      {
		if (!MIN.is_lt_zero())
		  {
		    inc(q);
		    ::subtract(r, r, MIN);
		  }
		else
		  {
		    dec(q);
		    ::add(r,r,MIN);
		  }
	      }
	    
	    a[i].assign(r);
	    for (j = 0; j < n; j++)
	      {
		::multiply(TMP, q, Ttmp2[j]);
		::subtract(Ttmp1[j], Ttmp1[j], TMP);
	      }
	  }
    }
  while (SW==1);
  
  Ttmp2 = value[index];
  
  if (a[index] < 0)
    {
      a[index].negate();
      for (i = 0; i < n; i++)
	Ttmp2[i].negate();
    }
  
  if (index != 0)
    a[0].assign(a[index]);
  for (i = 1; i <= n; i++)
    a[i].assign(Ttmp2[i - 1]);

  return a;
}
