// Copyright (C) 2001-2009 Vivien Mallet
//
// This file is part of the linear-algebra library Seldon,
// http://seldon.sourceforge.net/.
//
// Seldon is free software; you can redistribute it and/or modify it under the
// terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 2.1 of the License, or (at your option)
// any later version.
//
// Seldon is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
// FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for
// more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Seldon. If not, see http://www.gnu.org/licenses/.


#ifndef SELDON_FILE_FUNCTIONS_MATVECT_CXX

/*
  Functions defined in this file:

  M*X -> Y
  Mlt(M, X, Y)

  alpha.M*X -> Y
  Mlt(alpha, M, X, Y)

  alpha.M*X + beta.Y -> Y
  MltAdd(alpha, M, X, beta, Y)
*/

namespace Seldon
{


  /////////
  // MLT //


  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2>
  void Mlt(const Matrix<T0, Prop0, Storage0, Allocator0>& M,
	   const Vector<T1, Storage1, Allocator1>& X,
	   Vector<T2, Storage2, Allocator2>& Y)
  {
    Y.Fill(T2(0));
    MltAdd(T2(1), M, X, T2(0), Y);
  }


  template <class T0,
	    class T1, class Prop1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3, class Storage3, class Allocator3>
  void Mlt(const T0 alpha,
	   const Matrix<T1, Prop1, Storage1, Allocator1>& M,
	   const Vector<T2, Storage2, Allocator2>& X,
	   Vector<T3, Storage3, Allocator3>& Y)
  {
    Y.Fill(T2(0));
    MltAdd(alpha, M, X, T3(0), Y);
  }


  // MLT //
  /////////



  ////////////
  // MltAdd //


  /*** Sparse matrices ***/


  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const Matrix<T1, Prop1, RowSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(M, X, Y, "MltAdd(alpha, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    T4 zero(0);
    T4 temp;

    int* ptr = M.GetPtr();
    int* ind = M.GetInd();
    typename Matrix<T1, Prop1, RowSparse, Allocator1>::pointer
      data = M.GetData();

    for (int i = 0; i < ma; i++)
      {
	temp = zero;
	for (int j = ptr[i]; j < ptr[i+1]; j++)
	  temp += data[j] * X(ind[j]);
	Y(i) += alpha * temp;
      }
  }


  /*** Complex sparse matrices ***/


  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const Matrix<T1, Prop1, RowComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int i, j;

    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(M, X, Y, "MltAdd(alpha, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    complex<T1> zero(0);
    complex<T1> temp;

    int* real_ptr = M.GetRealPtr();
    int* imag_ptr = M.GetImagPtr();
    int* real_ind = M.GetRealInd();
    int* imag_ind = M.GetImagInd();
    typename Matrix<T1, Prop1, RowComplexSparse, Allocator1>::pointer
      real_data = M.GetRealData();
    typename Matrix<T1, Prop1, RowComplexSparse, Allocator1>::pointer
      imag_data = M.GetImagData();

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	  temp += real_data[j] * X(real_ind[j]);
	Y(i) += alpha * temp;
      }

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	  temp += complex<T1>(T1(0), imag_data[j]) * X(imag_ind[j]);
	Y(i) += alpha * temp;
      }
  }


  /*** Symmetric sparse matrices ***/


  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const Matrix<T1, Prop1, RowSymSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(M, X, Y, "MltAdd(alpha, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int i, j;
    T4 zero(0);
    T4 temp;

    int* ptr = M.GetPtr();
    int* ind = M.GetInd();
    typename Matrix<T1, Prop1, RowSymSparse, Allocator1>::pointer
      data = M.GetData();

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = ptr[i]; j < ptr[i + 1]; j++)
	  temp += data[j] * X(ind[j]);
	Y(i) += alpha * temp;
      }
    for (i = 0; i < ma-1; i++)
      for (j = ptr[i]; j < ptr[i + 1]; j++)
	if (ind[j] != i)
	  Y(ind[j]) += alpha * data[j] * X(i);
  }


  /*** Symmetric complex sparse matrices ***/


  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(M, X, Y, "MltAdd(alpha, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int i, j;
    complex<T1> zero(0);
    complex<T1> temp;

    int* real_ptr = M.GetRealPtr();
    int* imag_ptr = M.GetImagPtr();
    int* real_ind = M.GetRealInd();
    int* imag_ind = M.GetImagInd();
    typename Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>::pointer
      real_data = M.GetRealData();
    typename Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>::pointer
      imag_data = M.GetImagData();

    for (i = 0; i<ma; i++)
      {
	temp = zero;
	for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	  temp += real_data[j] * X(real_ind[j]);
	Y(i) += alpha * temp;
      }
    for (i = 0; i<ma-1; i++)
      for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	if (real_ind[j] != i)
	  Y(real_ind[j]) += alpha * real_data[j] * X(i);

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	  temp += complex<T1>(T1(0), imag_data[j]) * X(imag_ind[j]);
	Y(i) += alpha * temp;
      }
    for (i = 0; i<ma-1; i++)
      for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	if (imag_ind[j] != i)
	  Y(imag_ind[j]) += alpha * complex<T1>(T1(0), imag_data[j]) * X(i);
  }


  /*** Sparse matrices, *Trans ***/


  // NoTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonNoTrans& Trans,
	      const Matrix<T1, Prop1, RowSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    MltAdd(alpha, M, X, beta, Y);
  }


  // Trans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonTrans& Trans,
	      const Matrix<T1, Prop1, RowSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int i, j;

    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, SeldonTrans, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int* ptr = M.GetPtr();
    int* ind = M.GetInd();
    typename Matrix<T1, Prop1, RowSparse, Allocator1>::pointer
      data = M.GetData();

    for (i = 0; i < ma; i++)
      for (j = ptr[i]; j < ptr[i + 1]; j++)
	Y(ind[j]) += alpha * data[j] * X(i);
  }


  // ConjTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonConjTrans& Trans,
	      const Matrix<complex<T1>, Prop1, RowSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int i, j;

    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, SeldonConjTrans, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int* ptr = M.GetPtr();
    int* ind = M.GetInd();
    typename Matrix<complex<T1>, Prop1, RowSparse, Allocator1>::pointer
      data = M.GetData();

    for (i = 0; i < ma; i++)
      for (j = ptr[i]; j < ptr[i + 1]; j++)
	Y(ind[j]) += alpha * conj(data[j]) * X(i);
  }


  /*** Complex sparse matrices, *Trans ***/


  // NoTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonNoTrans& Trans,
	      const Matrix<T1, Prop1, RowComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    MltAdd(alpha, M, X, beta, Y);
  }


  // Trans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonTrans& Trans,
	      const Matrix<T1, Prop1, RowComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int i, j;

    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, SeldonTrans, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int* real_ptr = M.GetRealPtr();
    int* imag_ptr = M.GetImagPtr();
    int* real_ind = M.GetRealInd();
    int* imag_ind = M.GetImagInd();
    typename Matrix<T1, Prop1, RowComplexSparse, Allocator1>::pointer
      real_data = M.GetRealData();
    typename Matrix<T1, Prop1, RowComplexSparse, Allocator1>::pointer
      imag_data = M.GetImagData();

    for (i = 0; i < ma; i++)
      for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	Y(real_ind[j]) += alpha * real_data[j] * X(i);

    for (i = 0; i < ma; i++)
      for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	Y(imag_ind[j]) += alpha * complex<T1>(T1(0), imag_data[j]) * X(i);
  }


  // ConjTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonConjTrans& Trans,
	      const Matrix<T1, Prop1, RowComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int i, j;

    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, SeldonConjTrans, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int* real_ptr = M.GetRealPtr();
    int* imag_ptr = M.GetImagPtr();
    int* real_ind = M.GetRealInd();
    int* imag_ind = M.GetImagInd();
    typename Matrix<T1, Prop1, RowComplexSparse, Allocator1>::pointer
      real_data = M.GetRealData();
    typename Matrix<T1, Prop1, RowComplexSparse, Allocator1>::pointer
      imag_data = M.GetImagData();

    for (i = 0; i < ma; i++)
      for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	Y(real_ind[j]) += alpha * real_data[j] * X(i);

    for (i = 0; i < ma; i++)
      for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	Y(imag_ind[j]) += alpha * complex<T1>(T1(0), - imag_data[j]) * X(i);
  }


  /*** Symmetric sparse matrices, *Trans ***/


  // NoTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonNoTrans& Trans,
	      const Matrix<T1, Prop1, RowSymSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    MltAdd(alpha, M, X, beta, Y);
  }


  // Trans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonTrans& Trans,
	      const Matrix<T1, Prop1, RowSymSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    MltAdd(alpha, M, X, beta, Y);
  }


  // ConjTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonConjTrans& Trans,
	      const Matrix<complex<T1>, Prop1, RowSymSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, SeldonConjTrans, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int i, j;
    complex<T1> zero(0);
    complex<T1> temp;

    int* ptr = M.GetPtr();
    int* ind = M.GetInd();
    typename Matrix<complex<T1>, Prop1, RowSymSparse, Allocator1>::pointer
      data = M.GetData();

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = ptr[i]; j < ptr[i + 1]; j++)
	  temp += conj(data[j]) * X(ind[j]);
	Y(i) += temp;
      }
    for (i = 0; i < ma - 1; i++)
      for (j = ptr[i]; j < ptr[i + 1]; j++)
	if (ind[j] != i)
	  Y(ind[j]) += conj(data[j]) * X(i);
  }


  /*** Symmetric complex sparse matrices, *Trans ***/


  // NoTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonNoTrans& Trans,
	      const Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    MltAdd(alpha, M, X, beta, Y);
  }


  // Trans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonTrans& Trans,
	      const Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    MltAdd(alpha, M, X, beta, Y);
  }


  // ConjTrans.
  template <class T0,
	    class T1, class Prop1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const class_SeldonConjTrans& Trans,
	      const Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta, Vector<T4, Storage4, Allocator4>& Y)
  {
    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, SeldonConjTrans, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    int i, j;
    complex<T1> zero(0);
    complex<T1> temp;

    int* real_ptr = M.GetRealPtr();
    int* imag_ptr = M.GetImagPtr();
    int* real_ind = M.GetRealInd();
    int* imag_ind = M.GetImagInd();
    typename Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>::pointer
      real_data = M.GetRealData();
    typename Matrix<T1, Prop1, RowSymComplexSparse, Allocator1>::pointer
      imag_data = M.GetImagData();

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	  temp += real_data[j] * X(real_ind[j]);
	Y(i) += temp;
      }
    for (i = 0; i < ma - 1; i++)
      for (j = real_ptr[i]; j < real_ptr[i + 1]; j++)
	if (real_ind[j] != i)
	  Y(real_ind[j]) += real_data[j] * X(i);

    for (i = 0; i < ma; i++)
      {
	temp = zero;
	for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	  temp += complex<T1>(T1(0), - imag_data[j]) * X(imag_ind[j]);
	Y(i) += temp;
      }
    for (i = 0; i < ma - 1; i++)
      for (j = imag_ptr[i]; j < imag_ptr[i + 1]; j++)
	if (imag_ind[j] != i)
	  Y(imag_ind[j]) += complex<T1>(T1(0), - imag_data[j]) * X(i);
  }


  // MltAdd //
  ////////////



  ////////////
  // MltAdd //


  template <class T0,
	    class T1, class Prop1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const Matrix<T1, Prop1, Storage1, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta,
	      Vector<T4, Storage4, Allocator4>& Y)
  {
    int ma = M.GetM();
    int na = M.GetN();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(M, X, Y, "MltAdd(alpha, M, X, beta, Y)");
#endif

    Mlt(beta, Y);

    T4 zero(0);
    T4 temp;
    T4 alpha_(alpha);

    for (int i = 0; i < ma; i++)
      {
	temp = zero;
	for (int j = 0; j < na; j++)
	  temp += M(i, j) * X(j);
	Y(i) += alpha_ * temp;
      }
  }
  
  
  template <class T0,
	    class T1, class Prop1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3,
	    class T4, class Storage4, class Allocator4>
  void MltAdd(const T0 alpha,
	      const SeldonTranspose& Trans,
	      const Matrix<T1, Prop1, Storage1, Allocator1>& M,
	      const Vector<T2, Storage2, Allocator2>& X,
	      const T3 beta,
	      Vector<T4, Storage4, Allocator4>& Y)
  {
    if (Trans.NoTrans())
      {
        MltAdd(alpha, M, X, beta, Y);
        return;
      }
    else if (Trans.ConjTrans())
      throw WrongArgument("MltAdd(alpha, trans, M, X, beta, Y)",
                          "Complex conjugation not supported.");

    int ma = M.GetM();
    int na = M.GetN();

#ifdef SELDON_CHECK_BOUNDS
    CheckDim(Trans, M, X, Y, "MltAdd(alpha, trans, M, X, beta, Y)");
#endif

    if (beta == T3(0))
      Y.Fill(T4(0));
    else
      Mlt(beta, Y);

    T4 zero(0);
    T4 temp;
    T4 alpha_(alpha);

    for (int i = 0; i < na; i++)
      {
	temp = zero;
	for (int j = 0; j < ma; j++)
	  temp += M(j, i) * X(j);
	Y(i) += alpha_ * temp;
      }
  }
  

  // MltAdd //
  ////////////



  ///////////
  // Gauss //


  // Solve X = M*Y with Gauss method.
  // Warning: M is modified. The results are stored in X.
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1>
  inline void Gauss(Matrix<T0, Prop0, Storage0, Allocator0>& M,
		    Vector<T1, Storage1, Allocator1>& X)
  {
    int i, j, k;
    T1 r, S;

    int ma = M.GetM();
    int na = M.GetN();

#ifdef SELDON_CHECK_BOUNDS
    if (na != ma)
      throw WrongDim("Gauss(M, X)",
		     "The matrix must be squared.");

    CheckDim(M, X, "Gauss(M, X)");
#endif

    for (k = 0; k < ma - 1; k++)
      for (i = k + 1; i < ma; i++)
	{
	  r = M(i, k) / M(k, k);
	  for (j = k + 1; j < ma; j++)
	    M(i, j) -= r * M(k, j);
	  X(i) -= r *= X(k);
	}

    X(ma - 1) = X(ma - 1) / M(ma - 1, ma - 1);
    for (k = ma - 2; k > -1; k--)
      {
	S = X(k);
	for (j = k + 1; j < ma; j++)
	  S -= M(k, j) * X(j);
	X(k) = S / M(k, k);
      }
  }


  // Gauss //
  ///////////



  ////////////////////
  // Gauss - Seidel //


  // Solve X = M*Y with Gauss-Seidel method.
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2>
  inline void GaussSeidel(const Matrix<T0, Prop0, Storage0, Allocator0>& M,
			  const Vector<T1, Storage1, Allocator1>& X,
			  Vector<T2, Storage2, Allocator2>& Y,
			  int iter)
  {
    int i, j, k;
    T1 temp;

    int ma = M.GetM();
    int na = M.GetN();

#ifdef SELDON_CHECK_BOUNDS
    if (na != ma)
      throw WrongDim("GaussSeidel(M, X, Y, iter)",
		     "The matrix must be squared.");

    CheckDim(M, X, Y, "GaussSeidel(M, X, Y, iter)");
#endif
    
    for (i = 0; i < iter; i++)
      for (j = 0; j < na; j++)
	{
	  temp = 0;
	  for (k = 0; k < j; k++)
	    temp -= M(j, k) * Y(k);
	  for (k = j + 1; k < na; k++)
	    temp -= M(j, k) * Y(k);
	  Y(j) = (X(j) + temp) / M(j, j);
	}
  }


  // Gauss-Seidel //
  //////////////////



  ///////////////////
  // S.O.R. method //


  // Solve X = M*Y with S.O.R. method.
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2,
	    class T3>
  inline void SOR(const Matrix<T0, Prop0, Storage0, Allocator0>& M,
		  const Vector<T1, Storage1, Allocator1>& X,
		  Vector<T2, Storage2, Allocator2>& Y,
		  T3 omega,
		  int iter)
  {
    int i, j, k;
    T1 temp;

    int ma = M.GetM();
    int na = M.GetN();

#ifdef SELDON_CHECK_BOUNDS
    if (na != ma)
      throw WrongDim("SOR(M, X, Y, omega, iter)",
		     "The matrix must be squared.");

    CheckDim(M, X, Y, "SOR(M, X, Y, omega, iter)");
#endif
    
    for (i = 0; i < iter; i++)
      for (j = 0; j < na; j++)
	{
	  temp = 0;
	  for (k = 0; k < j; k++)
	    temp -= M(j, k) * Y(k);
	  for (k = j + 1; k < na; k++)
	    temp -= M(j, k) * Y(k);
	  Y(j) = (T3(1) - omega) * Y(j) + omega * (X(j) + temp) / M(j, j);
	}
  }


  // Gauss-Seidel //
  //////////////////



  /////////////
  // SolveLU //


  // Solves M.X = Y where A has been decomposed in a LU form.
  // Y is overwritten (Y <- X).
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1>
  void SolveLU(const Matrix<T0, Prop0, Storage0, Allocator0>& M,
	       Vector<T1, Storage1, Allocator1>& Y)
  {
    int i, k;
    T1 temp;

    int ma = M.GetM();

#ifdef SELDON_CHECK_BOUNDS
    int na = M.GetN();
    if (na != ma)
      throw WrongDim("SolveLU(M, Y)",
		     "The matrix must be squared.");

    CheckDim(M, Y, "SolveLU(M, Y)");
#endif

    // Forward substitution.
    for (i = 0; i < ma; i++)
      {
	temp = 0;
	for (k = 0; k < i; k++)
	  temp += M(i, k) * Y(k);
	Y(i) = (Y(i) - temp) / M(i, i);
      }
    // Back substitution.
    for (i = ma - 2; i > -1; i--)
      {
	temp = 0;
	for (k = i + 1; k < ma; k++)
	  temp += M(i, k) * Y(k);
	Y(i) -= temp;
      }
  }


  // SolveLU //
  /////////////



  //////////////
  // CHECKDIM //


  //! Checks the compatibility of the dimensions.
  /*! Checks that M.X + Y -> Y is possible according to the dimensions of
    the matrix M and the vectors X and Y. If the dimensions are incompatible,
    an exception is raised (a WrongDim object is thrown).
    \param M matrix.
    \param X vector.
    \param Y vector.
    \param function (optional) function in which the compatibility is checked.
    Default: "".
  */
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2>
  void CheckDim(const Matrix<T0, Prop0, Storage0, Allocator0>& M,
		const Vector<T1, Storage1, Allocator1>& X,
		const Vector<T2, Storage2, Allocator2>& Y,
		string function = "")
  {
    if (X.GetLength() != M.GetN() || Y.GetLength() != M.GetM())
      throw WrongDim(function, string("Operation M.X + Y -> Y not permitted:")
		     + string("\n     M (") + to_str(&M) + string(") is a ")
		     + to_str(M.GetM()) + string(" x ") + to_str(M.GetN())
		     + string(" matrix;\n     X (") + to_str(&X)
		     + string(") is vector of length ")
		     + to_str(X.GetLength()) + string(";\n     Y (")
		     + to_str(&Y) + string(") is vector of length ")
		     + to_str(Y.GetLength()) + string("."));
  }


  //! Checks the compatibility of the dimensions.
  /*! Checks that M.X + Y -> Y is possible according to the dimensions of
    the matrix M and the vectors X and Y. If the dimensions are incompatible,
    an exception is raised (a WrongDim object is thrown).
    \param status status of the matrix M, e.g. transposed.
    \param M matrix.
    \param X vector.
    \param Y vector.
    \param function (optional) function in which the compatibility is checked.
    Default: "".
    \param op (optional) operation to be performed on the vectors.
    Default: "M.X + Y -> Y".
  */
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2>
  void CheckDim(const SeldonTranspose& status,
		const Matrix<T0, Prop0, Storage0, Allocator0>& M,
		const Vector<T1, Storage1, Allocator1>& X,
		const Vector<T2, Storage2, Allocator2>& Y,
		string function = "", string op = "M.X + Y -> Y")
  {
    if (op == "M.X + Y -> Y")
      if (status.Trans())
	op = string("Operation M'.X + Y -> Y not permitted:");
      else if (status.ConjTrans())
	op = string("Operation M*.X + Y -> Y not permitted:");
      else
	op = string("Operation M.X + Y -> Y not permitted:");
    else
      op = string("Operation ") + op + string(" not permitted:");
    if (X.GetLength() != M.GetN(status) || Y.GetLength() != M.GetM(status))
      throw WrongDim(function, op + string("\n     M (") + to_str(&M)
		     + string(") is a ") + to_str(M.GetM()) + string(" x ")
		     + to_str(M.GetN()) + string(" matrix;\n     X (")
		     + to_str(&X) + string(") is vector of length ")
		     + to_str(X.GetLength()) + string(";\n     Y (")
		     + to_str(&Y) + string(") is vector of length ")
		     + to_str(Y.GetLength()) + string("."));
  }


#ifdef SELDON_WITH_CBLAS
  //! Checks the compatibility of the dimensions.
  /*! Checks that M.X + Y -> Y is possible according to the dimensions of
    the matrix M and the vectors X and Y. If the dimensions are incompatible,
    an exception is raised (a WrongDim object is thrown).
    \param trans status of the matrix M, e.g. transposed.
    \param M matrix.
    \param X vector.
    \param Y vector.
    \param function (optional) function in which the compatibility is checked.
    Default: "".
    \param op (optional) operation to be performed on the vectors.
    Default: "M.X + Y -> Y".
  */
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1,
	    class T2, class Storage2, class Allocator2>
  void CheckDim(const enum CBLAS_TRANSPOSE trans,
		const Matrix<T0, Prop0, Storage0, Allocator0>& M,
		const Vector<T1, Storage1, Allocator1>& X,
		const Vector<T2, Storage2, Allocator2>& Y,
		string function = "", string op = "M.X + Y -> Y")
  {
    SeldonTranspose status(trans);
    if (op == "M.X + Y -> Y")
      if (status.Trans())
	op = string("Operation M'.X + Y -> Y not permitted:");
      else if (status.ConjTrans())
	op = string("Operation M*.X + Y -> Y not permitted:");
      else
	op = string("Operation M.X + Y -> Y not permitted:");
    else
      op = string("Operation ") + op + string(" not permitted:");
    if (X.GetLength() != M.GetN(status) || Y.GetLength() != M.GetM(status))
      throw WrongDim(function, op + string("\n     M (") + to_str(&M)
		     + string(") is a ") + to_str(M.GetM()) + string(" x ")
		     + to_str(M.GetN()) + string(" matrix;\n     X (")
		     + to_str(&X) + string(") is vector of length ")
		     + to_str(X.GetLength()) + string(";\n     Y (")
		     + to_str(&Y) + string(") is vector of length ")
		     + to_str(Y.GetLength()) + string("."));
  }
#endif


  //! Checks the compatibility of the dimensions.
  /*! Checks that M.X is possible according to the dimensions of
    the matrix M and the vector X. If the dimensions are incompatible,
    an exception is raised (a WrongDim object is thrown).
    \param M matrix.
    \param X vector.
    \param function (optional) function in which the compatibility is checked.
    Default: "".
    \param op (optional) operation to be performed. Default: "M.X".
  */
  template <class T0, class Prop0, class Storage0, class Allocator0,
	    class T1, class Storage1, class Allocator1>
  void CheckDim(const Matrix<T0, Prop0, Storage0, Allocator0>& M,
		const Vector<T1, Storage1, Allocator1>& X,
		string function = "", string op = "M.X")
  {
    if (X.GetLength() != M.GetN())
      throw WrongDim(function, string("Operation ") + op + " not permitted:"
		     + string("\n     M (") + to_str(&M) + string(") is a ")
		     + to_str(M.GetM()) + string(" x ") + to_str(M.GetN())
		     + string(" matrix;\n     X (") + to_str(&X)
		     + string(") is vector of length ")
		     + to_str(X.GetLength()) + string("."));
  }


  // CHECKDIM //
  //////////////


}  // namespace Seldon.

#define SELDON_FUNCTIONS_MATVECT_CXX
#endif