// file: $isip/class/math/matrix/MMatrix/MMatrix.h
// version: $Id: MMatrix.h 10636 2007-01-26 22:18:09Z tm334 $
//

// make sure definitions are only made once
//
#ifndef ISIP_MMATRIX
#define ISIP_MMATRIX

// isip include files
//
#ifndef ISIP_MMATRIX_METHODS
#include "MMatrixMethods.h"
#endif

#ifndef ISIP_VECTOR_LONG
#include <VectorLong.h>
#endif

#ifndef ISIP_NAME_MAP
#include <NameMap.h>
#endif

// MMatrix: this is a matrix template class which is inherited by
// other matrix classes. TScalar is a Scalar class, TIntegral is an
// Integral type.
//
// this class supports 6 types of matrices:
//   FULL: a rectangular matrix.
//   DIAGONAL: a diagonal matrix 
//   SYMMETRIC: a symmetric matrix.
//   LOWER_TRIANGULAR: a lower triangular matrix. 
//   UPPER_TRIANGULAR: an upper triangular matrix. 
//   SPARSE: a sparse matrix (has very few non-zero elements).
//
// these are defined by the constant Integral::MTYPE. this important
// constant is located in Integral.h so that the notion of a matrix
// storage type is available to both MMatrix.h and MMatrixMethods.h,
// as well as the specific instantiations of this template.
//
template <class TScalar, class TIntegral>
class MMatrix {

  //---------------------------------------------------------------------------
  //
  // public constants
  //
  //---------------------------------------------------------------------------
public:

  // define the class name
  //
  static const String CLASS_NAME;

  //----------------------------------------
  //
  // i/o related constants
  //
  //----------------------------------------  
  
  static const String DEF_PARAM;
  static const String PARAM_DATA;
  static const String PARAM_TYPE;
  static const String PARAM_NROWS;
  static const String PARAM_NCOLS;
  static const String PARAM_ROW_IND;
  static const String PARAM_COL_IND;

  //----------------------------------------
  //
  // matrix type-related constants
  //
  //----------------------------------------  
  
  static const NameMap TYPE_MAP;
  
  //----------------------------------------
  //
  // other numeric constants
  //
  //----------------------------------------  

  // define a conditioning factor to be used to determine singularity
  //
  static const double THRESH_SINGULAR = 1e-10;

  // define a conditioning factor to be used to determine threshhold
  //  for rank method
  //
  static const double THRESH_SINGULAR_DOUBLE = 2.22e-16; // for double type
  static const double THRESH_SINGULAR_FLOAT = 1.49e-08; // for float type
  
  // define a threshold used to determine sparseness
  //
  static const double THRESH_SPARSE = 0.9;

  // define a threshold used to determine balance
  //
  static const double THRESH_BALANCE = 0.95;

  // define a threshold used to stabilize matrices
  //
  static const double THRESH_STABLE = 1e-20;

  //----------------------------------------
  //
  // default values and arguments
  //
  //----------------------------------------  

  // default arguments to methods
  //
  static const unichar DEF_ROW_DELIM = L',';
  static const unichar DEF_DELIM = DEF_ROW_DELIM;
  
  // default value(s) of the class data
  //
  static const TIntegral DEF_VALUE;
  static const long DEF_SIZE = 0;

  //----------------------------------------
  //
  // error codes
  //
  //----------------------------------------
  
  static const long ERR = 24000;
  static const long ERR_OPTYPE = 24001;
  static const long ERR_DIM = 24002;
  static const long ERR_UNKTYP = 24003;
  static const long ERR_SINGLR = 24004;
  static const long ERR_POSDEF = 24005;    
  
  //---------------------------------------------------------------------------
  //
  // protected data
  //
  //---------------------------------------------------------------------------
protected:

  typedef MVector<TScalar, TIntegral> TVector;

  // define the type
  //
  Integral::MTYPE type_d;

  // number of rows and columns in the matrix
  //
  Long nrows_d;
  Long ncols_d;
  
  // the values of this matrix are stored in a vector for two reasons.
  // first, we want to make use of all the math operations supported
  // in the vector classes. second, we want to make access and manipulation
  // of the matrix fast.
  //
  TVector m_d;

  // for the sparse matrix implementation, in addition to the values,
  // we need two vectors to hold row and column indices.
  //
  MVector<Long, long> row_index_d;
  MVector<Long, long> col_index_d;
  
  // declare a static debug level
  //
  static Integral::DEBUG debug_level_d;

  //---------------------------------------------------------------------------
  //
  // required public methods
  //
  //---------------------------------------------------------------------------
public:

  // method: name
  //
  static const String& name() {
    return CLASS_NAME;
  }

  // method: diagnose
  //  
  static boolean diagnose(Integral::DEBUG debug_level) {
    return MMatrixMethods::diagnose<MMatrix<TScalar,TIntegral>,
      TScalar, TIntegral>(debug_level);
  }

  // method: setDebug
  //
  boolean setDebug(Integral::DEBUG level) {
    debug_level_d = level;
    return true;
  }
    
  // method: debug
  //
  boolean debug(const unichar* message) const {
    return debug(name(), message);
  }
  
  // method: destructor
  //  note that since there are no pointers in the protected data,
  //  we don't need to clean up anything.
  //
  ~MMatrix() {}

  // method: default constructor
  //
  MMatrix();

  // method: copy constructor
  //
  MMatrix(const MMatrix& arg);

  // method: assign
  //  this method is templatized so it can also do conversions without
  //  generating class-specific code.
  //
  template <class TAScalar, class TAIntegral>
  boolean assign(const MMatrix<TAScalar, TAIntegral>& arg) {
    return MMatrixMethods::assign(*this, arg);
  }

  // operator= methods:
  //  these methods are omitted because they are defined in the
  //  classes that instantiate this template
  //
  
  // i/o methods
  //
  long sofSize() const {
    return MMatrixMethods::sofSize<TScalar,TIntegral>(*this);
  }
  
  boolean read(Sof& sof, long tag, const String& name) {
    return MMatrixMethods::read<TScalar,TIntegral>(*this, sof, tag, name);
  }
  
  boolean write(Sof& sof, long tag, const String& name) const {
    return MMatrixMethods::write<TScalar,TIntegral>(*this, sof, tag, name);
  }
  
  boolean readData(Sof& sof, const String& pname,
		   long size, boolean param, boolean nested) {
    return MMatrixMethods::readData<TScalar,TIntegral>(*this, sof, pname,
						       size, param, nested);
  }
  
  boolean writeData(Sof& sof, const String& name) const {
    return MMatrixMethods::writeData<TScalar,TIntegral>(*this, sof, name);
  }
  
  // equality methods
  //
  boolean eq(const MMatrix& arg) const;

  // memory management methods:
  //  the new and delete methods are omitted because they are defined in the
  //  classes that instantiate this template
  //
  boolean clear(Integral::CMODE cmode = Integral::DEF_CMODE);

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  extensions to required methods
  //
  //---------------------------------------------------------------------------

  // debug methods:
  //  this second debug method is used so that the template
  //  instantiations can use a common debug method.
  //
  boolean debug(const String& name, const unichar* message) const;

  // constructor(s)
  //
  MMatrix(long nrows, long ncols = DEF_SIZE,
	  Integral::MTYPE type = Integral::DEF_MTYPE);

  // method: eq
  //
  boolean eq(TIntegral value) const {
    return MMatrixMethods::eq<TScalar,TIntegral>(*this, value);
  }
  
  // method: assign
  //  this method is an overload of the required assign method that
  //  allows the user to set the type. note that we do not use a default
  //  value for the type argument so we avoid a conflict with the standard
  //  assign method. also 
  //
  template <class TAScalar, class TAIntegral>
  boolean assign(const MMatrix<TAScalar, TAIntegral>& arg,
		 Integral::MTYPE type) {
    return ((((void*)&arg == (void*)this) && changeType(type)) ||
	    (setDimensions(0, 0, false, type) && copy(arg)));
  }

  // other scalar assign methods
  //
  boolean assign(TIntegral value);

  // method: assign
  //  array conversion methods
  //
  template<class TAIntegral>
  boolean assign(long num_rows, long num_cols, const TAIntegral* arg, 
		 Integral::MTYPE type = Integral::DEF_MTYPE) {
    return MMatrixMethods::assign<TScalar,TIntegral>(*this, num_rows,
						     num_cols, arg, type);
  }    

  // method: assign
  //
  boolean assign(long num_rows, long num_cols, const String* arg, 
		 Integral::MTYPE type = Integral::DEF_MTYPE) {
    return MMatrixMethods::assign<TScalar,TIntegral>(*this, num_rows,
						     num_cols, arg, type);
  }
  
  // method: assign
  //  create a matrix from a string with given delimiter
  //
  boolean assign(long nrows, long ncols, const String& arg,
		 Integral::MTYPE type = Integral::DEF_MTYPE,  
		 const Char delim = DEF_DELIM) {
    return MMatrixMethods::assign<TScalar,TIntegral>(*this, nrows, ncols,
						     arg, type, delim);
  }

  // method: assign
  //  create a matrix from an array of unichars with given delimiter
  //  
  boolean assign(long nrows, long ncols, const unichar* arg,
		 Integral::MTYPE type = Integral::DEF_MTYPE,
		 const Char delim = DEF_DELIM) {
    String temp((unichar*)arg);
    return MMatrixMethods::assign<TScalar,TIntegral>(*this, nrows, ncols,
						     temp, type, delim);
  }  

  // method: copy
  //  copy the values from arg to "this" without changing the type of "this"
  //
  template <class TAScalar, class TAIntegral>
  boolean copy(const MMatrix<TAScalar, TAIntegral>& arg) {
    return MMatrixMethods::copy(*this, arg);
  }

  // method: swap
  //
  boolean swap(MMatrix& arg) {
    return MMatrixMethods::swap<TScalar,TIntegral>(*this, arg);
  }

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  operator overloads, get, set, find, resize methods
  //
  //---------------------------------------------------------------------------

  // method: operator()
  //
  TIntegral operator()(long i, long j) const {
    return getValue(i, j);
  }

  // method: getType
  //
  Integral::MTYPE getType() const {
    return type_d;
  }  

  // method: getValue
  //
  TIntegral getValue(long row_index, long col_index) const {
    return MMatrixMethods::getValue<TScalar,TIntegral>(*this, row_index,
						       col_index);
  }

  // method: getValue
  //
  boolean getValue(TScalar& value, long row_index, long col_index) const {
    return MMatrixMethods::getValue<TScalar,TIntegral>(*this, value,
						       row_index, col_index);
  }

  // method: getNumRows
  //
  long getNumRows() const {
    return nrows_d;
  }
  
  // method: getNumColumns
  //
  long getNumColumns() const {
    return ncols_d;
  }

  // method: getRow
  //
  boolean getRow(TVector& vector, long row_index) const {
    return MMatrixMethods::getRow<TScalar,TIntegral>(*this, vector,
						     row_index);
  }

  // method: getColumn
  //
  boolean getColumn(TVector& vector, long col_index) const {
    return MMatrixMethods::getColumn<TScalar,TIntegral>(*this, vector,
							col_index);
  }
  
  // method: getDiagonal
  //
  boolean getDiagonal(TVector& vector) const {
    return MMatrixMethods::getDiagonal<TScalar,TIntegral>(*this, vector);
  }
  
  // method: getLower
  //
  boolean getLower(MMatrix& arg) const {
    return MMatrixMethods::getLower<TScalar,TIntegral>(*this, arg);
  }
  
  // method: getUpper
  //
  boolean getUpper(MMatrix& arg) const {
    return MMatrixMethods::getUpper<TScalar,TIntegral>(*this, arg);
  }
  
  // method: getMinor
  //
  boolean getMinor(MMatrix& arg, long row_index, long col_index) const {
    return MMatrixMethods::getMinor<TScalar, TIntegral>(*this, arg, row_index,
							col_index);
  }

  // method: setValue
  //
  boolean setValue(long row_index, long col_index,
		   const TScalar& value) {
    return MMatrixMethods::setValue<TScalar,TIntegral>(*this, row_index,
						       col_index,
						       (TIntegral)value);
  }

  // method: setRow
  //
  boolean setRow(long row_index, const TVector& vector) {
    return MMatrixMethods::setRow<TScalar,TIntegral>(*this,
						     row_index, vector);
  }    

  // method: setColumn
  //
  boolean setColumn(long col_index, const TVector& vector) {
    return MMatrixMethods::setColumn<TScalar,TIntegral>(*this,
							col_index, vector);
  }    
  
  // method: setDiagonal
  //
  boolean setDiagonal(const TVector& values) {
    return MMatrixMethods::setDiagonal<TScalar,TIntegral>(*this, values);
  }    

  // method: setDiagonal
  //
  boolean setDiagonal(const MMatrix& source) {
    TVector tmp;
    source.getDiagonal(tmp);
    return MMatrixMethods::setDiagonal<TScalar,TIntegral>(*this, tmp);
  }
  
  // method: setDiagonal
  //
  boolean setDiagonal(TIntegral value) {
    TVector tmp(nrows_d);
    tmp.assign(value);
    return MMatrixMethods::setDiagonal<TScalar,TIntegral>(*this, tmp);
  }

  // method: setLower
  //
  boolean setLower(const MMatrix& source) {
    return MMatrixMethods::setLower<TScalar,TIntegral>(*this, source);
  }    
 
  // method: setUpper
  //
  boolean setUpper(const MMatrix& source) {
    return MMatrixMethods::setUpper<TScalar,TIntegral>(*this, source);
  }

  // method: setBlock
  //
  boolean setBlock(long start_row, long start_col,
		   long num_rows, long num_cols, TIntegral value) {
    return MMatrixMethods::setBlock<TScalar, TIntegral>(*this,
							start_row, start_col,
							num_rows, num_cols,
							value);
  }

  // method: makeDiagonal
  //
  boolean makeDiagonal(const TVector& values,
		       Integral::MTYPE type = Integral::DIAGONAL) {
    return MMatrixMethods::makeDiagonal<TScalar,TIntegral>(*this,
							   values, type);
  }    
  
  // method: makeDiagonal
  //
  boolean makeDiagonal(TIntegral value, long dim,
		       Integral::MTYPE type = Integral::DIAGONAL) {
    TVector tmp(dim);
    tmp.assign(value);
    return MMatrixMethods::makeDiagonal<TScalar,TIntegral>(*this, tmp, type);
  }
  
  // method: makeIdentity
  //
  boolean makeIdentity(long dim, Integral::MTYPE type = Integral::DIAGONAL) {
    TVector tmp(dim);
    tmp.assign((TIntegral)1.0);
    return MMatrixMethods::makeDiagonal<TScalar,TIntegral>(*this, tmp, type);
  }

  // method: makeLower
  //
  boolean makeLower(const MMatrix& source,
		    Integral::MTYPE type = Integral::LOWER_TRIANGULAR) {
    return MMatrixMethods::makeLower<TScalar,TIntegral>(*this, source, type);
  }
  
  // method: makeUpper
  //
  boolean makeUpper(const MMatrix& source,
		    Integral::MTYPE type = Integral::UPPER_TRIANGULAR) {
    return MMatrixMethods::makeUpper<TScalar,TIntegral>(*this, source, type);
  }
  
  // method: nextZero
  //
  boolean nextZero(long& row_index, long& col_index,
		   long row_start, long col_start) const {
    return MMatrixMethods::nextZero(*this, row_index, col_index,
				    row_start, col_start);
  }

  // method: nextNonZero
  //
  boolean nextNonZero(TIntegral& value,
		      long& row_index, long& col_index,
		      long row_start, long col_start) const {
    return MMatrixMethods::nextNonZero(*this, value, row_index, col_index,
				       row_start, col_start);
  }

  // method: findRow
  //
  long findRow(const TVector& vector) const {
    return MMatrixMethods::findRow<TScalar,TIntegral>(*this, vector);
  }
  
  // resize methods
  //
  boolean setCapacity(long nrows, long ncols = DEF_SIZE,
		      boolean preserve_values = true,
		      Integral::MTYPE type = Integral::UNCHANGED);
  boolean setCapacity(const MMatrix& prototype_matrix,
		      boolean preserve_values = true,
		      Integral::MTYPE type = Integral::UNCHANGED);

  boolean setDimensions(long nrows, long ncols = DEF_SIZE,
			boolean preserve_values = true,
			Integral::MTYPE type = Integral::UNCHANGED);
  boolean setDimensions(const MMatrix& prototype_matrix,
			boolean preserve_values = true,
			Integral::MTYPE type = Integral::UNCHANGED);

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  property checking methods
  //
  //---------------------------------------------------------------------------
  
  // method: checkDimensions
  //  check if the current matrix has the same dimension as the input matrix
  //
  template <class TAScalar, class TAIntegral>
  boolean checkDimensions(const MMatrix<TAScalar, TAIntegral>& matrix) const {
    return (getNumRows() == matrix.getNumRows() &&
	    getNumColumns() == matrix.getNumColumns());    
  }

  // method: changeType
  //  change the type of the current matrix
  //
  boolean changeType(Integral::MTYPE type);

  // method: isTypePossible
  //  check if it is possible to change the current matrix into a
  //  matrix of the requested type
  //
  boolean isTypePossible(Integral::MTYPE type) const {
    return MMatrixMethods::isTypePossible<TScalar,TIntegral>(*this, type);
  }

  // method: isFull
  //
  boolean isFull() const {
    return MMatrixMethods::isTypePossible<TScalar,TIntegral>
      (*this, Integral::FULL);
  }

  // method: isDiagonal
  //
  boolean isDiagonal() const {
    return MMatrixMethods::isTypePossible<TScalar,TIntegral>
      (*this, Integral::DIAGONAL);
  }

  // method: isSymmetric
  //
  boolean isSymmetric() const {
    return MMatrixMethods::isTypePossible<TScalar,TIntegral>
      (*this, Integral::SYMMETRIC);
  }
  
  // method: isLowerTriangular
  //
  boolean isLowerTriangular() const {
    return MMatrixMethods::isTypePossible<TScalar,TIntegral>
      (*this, Integral::LOWER_TRIANGULAR);
  }

  // method: isUpperTriangular
  //
  boolean isUpperTriangular() const {
    return MMatrixMethods::isTypePossible<TScalar,TIntegral>
      (*this, Integral::UPPER_TRIANGULAR);
  }

  // method: isSparse
  //
  boolean isSparse() const {
    return (numEqual((TIntegral)0) > (nrows_d * ncols_d * THRESH_SPARSE));
  }

  // method: isSquare
  //  check if the current matrix is a square matrix
  //
  boolean isSquare() const {
    return isSquare(*this);
  }

  // method: isSquare
  //
  boolean isSquare(const MMatrix& matrix) const {
    return (matrix.getNumRows() == matrix.getNumColumns());
  }

  // method: isSingular
  //  check if the current matrix is singular
  //
  boolean isSingular(double thresh = 0) const {
    return isSingular(*this, thresh);
  }

  // method: isSingular
  //
  boolean isSingular(const MMatrix& matrix, double thresh = 0) const;
  
  // method: isOrthogonal()
  //
  boolean isOrthogonal() const {
    return MMatrixMethods::isOrthogonal<TScalar,TIntegral>(*this);
  }
    
  // method: isIdentity
  //
  boolean isIdentity() const {
    return MMatrixMethods::isIdentity<TScalar,TIntegral>(*this);
  }
  
  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  relational and logical methods
  //
  //---------------------------------------------------------------------------

  // method: ne
  //
  boolean ne(TIntegral value) const {
    return (!eq(value));
  }

  // method: ne
  //  
  boolean ne(const MMatrix& matrix) const {
    return (!eq(matrix));
  } 

  // method: lt
  //
  boolean lt(TIntegral value) const {
    return MMatrixMethods::lt<TScalar,TIntegral>(*this, value);
  }
  
  // method: le
  //
  boolean le(TIntegral value) const {
    return MMatrixMethods::le<TScalar,TIntegral>(*this, value);
  }

  // method: gt
  //
  boolean gt(TIntegral value) const {
    return MMatrixMethods::gt<TScalar,TIntegral>(*this, value);
  }
  
  // method: ge
  //
  boolean ge(TIntegral value) const {
    return MMatrixMethods::ge<TScalar,TIntegral>(*this, value);
  }
  
  // method: operator ==
  //
  boolean operator == (TIntegral arg) const {
    return eq(arg);
  }

  // method: operator !=
  //
  boolean operator != (TIntegral arg) const {
    return ne(arg);
  }

  // method: operator <
  //
  boolean operator < (TIntegral arg) const {
    return lt(arg);
  }

  // method: operator <=
  //
  boolean operator <= (TIntegral arg) const {
    return le(arg);
  }

  // method: operator >
  //
  boolean operator > (TIntegral arg) const {
    return gt(arg);
  }

  // method: operator >=
  //
  boolean operator >= (TIntegral arg) const {
    return ge(arg);
  }
  
  // method: numEqual
  //
  long numEqual(TIntegral value) const {
    return MMatrixMethods::numEqual<TScalar,TIntegral>(*this, value);
  }

  // method: numNotEqual
  //
  long numNotEqual(TIntegral value) const {
    long nrows = getNumRows();
    long ncols = getNumColumns();
    return (nrows * ncols - numEqual(value));        
  }
  
  // almostEqual methods
  //
  boolean almostEqual(TIntegral value,
		      double percent = Integral::DEF_PERCENTAGE,
		      double bound = Integral::DEF_BOUND) const;
  boolean almostEqual(const MMatrix& matrix,
		      double percent = Integral::DEF_PERCENTAGE,
		      double bound = Integral::DEF_BOUND) const;

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  concatenation methods 
  //
  //--------------------------------------------------------------------------
  
  // method: concatByRow
  //
  boolean concatByRow(const MMatrix& additional_rows) {
    return MMatrixMethods::concatByRow<TScalar,TIntegral>(*this,
							  additional_rows);
  }

  // method: concatByRow
  //  
  boolean concatByRow(const MMatrix& matrix1, const MMatrix& matrix2) {
    Integral::MTYPE old_type = type_d;
    return (assign(matrix1) && concatByRow(matrix2) && changeType(old_type));
  }
  
  // method: concatByColumn
  //
  boolean concatByColumn(const MMatrix& additional_cols) {
    return MMatrixMethods::concatByColumn<TScalar,TIntegral>(*this,
							     additional_cols);
  }

  // method: concatByColumn
  //  
  boolean concatByColumn(const MMatrix& matrix1,
			 const MMatrix& matrix2) {
    Integral::MTYPE old_type = type_d;
    return (assign(matrix1) && concatByColumn(matrix2) &&
	    changeType(old_type));
  }

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  ordering methods 
  //
  //--------------------------------------------------------------------------

  // method: reorderRows
  //
  boolean reorderRows(const MVector<Long,long>& indexes) {
    return MMatrixMethods::reorderRows<TScalar,TIntegral>(*this, indexes);
  }
  
  // method: reorderRows
  //
  boolean reorderRows(const MMatrix& matrix,
		      const MVector<Long,long>& indices) {
    Integral::MTYPE old_type =
      (type_d == Integral::SPARSE) ? Integral::SPARSE : Integral::FULL;
    return (assign(matrix) && reorderRows(indices) && changeType(old_type));
  }

  // method: reorderColumns
  //
  boolean reorderColumns(const MVector<Long,long>& indexes) {
    return MMatrixMethods::reorderColumns<TScalar,TIntegral>(*this, indexes);
  }
  
  // method: reorderColumns
  //
  boolean reorderColumns(const MMatrix& matrix,
			 const MVector<Long,long>& indices) {
    Integral::MTYPE old_type =
      (type_d == Integral::SPARSE) ? Integral::SPARSE : Integral::FULL;
    return (assign(matrix) && reorderColumns(indices) && changeType(old_type));
  }

  // method: swapRows
  //
  boolean swapRows(const MMatrix& arg, long row1, long row2) {
    return MMatrixMethods::swapRows<TScalar, TIntegral>(*this, arg,
							row1, row2);
  }

  // method: swapRows
  //
  boolean swapRows(long row1, long row2) {
    return MMatrixMethods::swapRows<TScalar, TIntegral>(*this, *this,
							row1, row2);
  }

  // method: swapColumns
  //
  boolean swapColumns(const MMatrix& arg, long col1, long col2) {
    return MMatrixMethods::swapColumns<TScalar, TIntegral>(*this, arg,
							   col1, col2);
  }

  // method: swapColumns
  //
  boolean swapColumns(long col1, long col2) {
    return MMatrixMethods::swapColumns<TScalar, TIntegral>(*this, *this,
							   col1, col2);
  }

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  basic mathematical methods
  //
  //---------------------------------------------------------------------------

  // method: add
  //
  template<class TAScalar, class TAIntegral>  
  boolean add(const MMatrix<TAScalar, TAIntegral>& m1) {
    return MMatrixMethods::add<TScalar,TIntegral>(*this, m1);
  }
  
  // method: add
  //
  template<class TAScalar, class TAIntegral>
  boolean add(const MMatrix& m1, const MMatrix<TAScalar, TAIntegral>& m2) {
    return MMatrixMethods::add<TScalar,TIntegral>(*this, m1, m2);
  }
  
  // method: add
  //
  boolean add(TIntegral value) {
    return MMatrixMethods::add<TScalar,TIntegral>(*this, value);
  }
  
  // method: add
  //
  boolean add(const MMatrix& m1, TIntegral value) {
    Integral::MTYPE old_type = type_d;
    return (assign(m1) && add(value) && changeType(old_type));
  }

  // method: sub
  //
  template<class TAScalar, class TAIntegral>  
  boolean sub(const MMatrix<TAScalar, TAIntegral>& m1) {
    return MMatrixMethods::sub<TScalar,TIntegral>(*this, m1);
  }
   
  // method: sub
  //
  template<class TAScalar, class TAIntegral>  
  boolean sub(const MMatrix& m1, const MMatrix<TAScalar, TAIntegral>& m2) {
    return MMatrixMethods::sub<TScalar,TIntegral>(*this, m1, m2);
  }
  
  // method: sub
  //
  boolean sub(TIntegral value) {
    return MMatrixMethods::sub<TScalar,TIntegral>(*this, value);
  }
  
  // method: sub
  //
  boolean sub(const MMatrix& m1, TIntegral value) {
    Integral::MTYPE old_type = type_d;
    return (assign(m1) && sub(value) && changeType(old_type));
  }

  // method: mult
  //
  template<class TAScalar, class TAIntegral>    
  boolean mult(const MMatrix<TAScalar, TAIntegral>& m1) {
    return MMatrixMethods::mult<TScalar,TIntegral>(*this, *this, m1);
  }
  
  // method: mult
  //
  template<class TAScalar, class TAIntegral>    
  boolean mult(const MMatrix& m1, const MMatrix<TAScalar, TAIntegral>& m2) {
    return MMatrixMethods::mult<TScalar,TIntegral>(*this, m1, m2);
  }
  
  // method: mult
  //
  boolean mult(TIntegral value) {
    return m_d.mult(value);
  }

  // method: div
  //
  boolean div(TIntegral value) {
    return m_d.div(value);
  }
  
  // method: neg
  //
  boolean neg() {
    return m_d.neg();
  }
  
  // method: neg
  //
  boolean neg(const MMatrix& m1) {
    Integral::MTYPE old_type = type_d;
    return (assign(m1) && neg() && changeType(old_type));
  }

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  other mathematical methods
  //
  //--------------------------------------------------------------------------
  
  // method: min
  //  find the minimum value
  //
  TIntegral min() const {
    static long row_pos;
    static long col_pos;
    return MMatrixMethods::min<TScalar,TIntegral>(*this, row_pos, col_pos);
  }
  
  // method: min
  //  find the element with the minimum value
  //
  TIntegral min(long& row_index, long& col_index) const {
    return MMatrixMethods::min<TScalar,TIntegral>(*this, row_index, col_index);
  } 

  // method: max
  //  find the maximum value
  //  
  TIntegral max() const {
    static long row_pos;
    static long col_pos;
    return MMatrixMethods::max<TScalar,TIntegral>(*this, row_pos, col_pos);
  }
  
  // method: max
  //  find the element with the maximum value
  //  
  TIntegral max(long& row_index, long& col_index) const {
    return MMatrixMethods::max<TScalar,TIntegral>(*this, row_index, col_index);
  }

  // method: minMag
  //  find the minimum magnitude
  //
  double minMag() const {
    static long row_pos;
    static long col_pos;
    return MMatrixMethods::minMag<TScalar,TIntegral>(*this, row_pos, col_pos);
  }
  
  // method: minMag
  //  find the element with the minimum magnitude
  //
  double minMag(long& row_index, long& col_index) const {
    return MMatrixMethods::minMag<TScalar,TIntegral>(*this, row_index,
						     col_index);
  }

  // method: maxMag
  //  find the maximum magnitude
  //  
  double maxMag() const {
    static long row_pos;
    static long col_pos;
    return MMatrixMethods::maxMag<TScalar,TIntegral>(*this, row_pos, col_pos);
  }
 
  // method: maxMag
  //  find the element with the maximum magnitude
  //
  double maxMag(long& row_index, long& col_index) const {
    return MMatrixMethods::maxMag<TScalar,TIntegral>(*this, row_index,
						     col_index);
  }

  // random number generation methods  
  //
  boolean rand(Random& generator = Random::GLOBAL_UNIFORM) {
    return MMatrixMethods::rand<TScalar, TIntegral>(*this, generator);
  }
  
  boolean rand(TIntegral min_val, TIntegral max_val,
	       Random& generator = Random::GLOBAL_UNIFORM) {
    return MMatrixMethods::rand<TScalar, TIntegral>(*this, min_val,
						   max_val, generator);
  }
  
  boolean grand(TIntegral mean, TIntegral stdev,
		Random& generator = Random::GLOBAL_GAUSSIAN) {
    return MMatrixMethods::grand<TScalar, TIntegral>(*this, mean,
						    stdev, generator);
  }
  
  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  linear algebra related methods 
  //
  //--------------------------------------------------------------------------
  
  // method: determinant
  //
  TIntegral determinant() const {
    return MMatrixMethods::determinant<TScalar,TIntegral>(*this);
  }
  
  // method: inverse
  //
  boolean inverse() {
    return MMatrixMethods::inverse<TScalar,TIntegral>(*this, *this);
  }
  
  // method: inverse
  //
  boolean inverse(const MMatrix& matrix) {
    return MMatrixMethods::inverse<TScalar,TIntegral>(*this, matrix);
  }

  // method: transpose
  //
  boolean transpose() {
    return MMatrixMethods::transpose<TScalar,TIntegral>(*this, *this);
  }
  
  // method: transpose
  //
  boolean transpose(const MMatrix& matrix) {
    return MMatrixMethods::transpose<TScalar,TIntegral>(*this, matrix);
  }

  // method: rank
  //
  long rank() const {
    return MMatrixMethods::rank<TScalar,TIntegral>(*this);
  }

  // method: trace
  //
  TIntegral trace() const {
    return MMatrixMethods::trace<TScalar,TIntegral>(*this);
  }

  // method: decompositionLU
  //
  boolean decompositionLU(MMatrix& lower_trig, MMatrix& upper_trig) const {
    MVector<Long, long> vec;
    long sign;
    return MMatrixMethods::decompositionLU<TScalar, TIntegral>(*this,
							       lower_trig,
							       upper_trig,
							       vec, sign,
							       THRESH_STABLE);
  }

  // method: decompositionLU
  //
  boolean decompositionLU(MMatrix& lower_trig, MMatrix& upper_trig,
			  MVector<Long, long>& index, long& sign,
			  double stabilize = THRESH_STABLE) const {
    return MMatrixMethods::decompositionLU(*this, lower_trig, upper_trig,
					   index, sign, stabilize);
  }

  // method: decompositionCholesky
  //
  boolean decompositionCholesky(MMatrix& ltri) const {
    return MMatrixMethods::decompositionCholesky(*this, ltri);
  }

  // method: decompositionSVD
  //
  boolean decompositionSVD(MMatrix& u, MMatrix& w, MMatrix& v, boolean economy_mode = true) const {
    return MMatrixMethods::decompositionSVD(*this, u, w, v, economy_mode);
  }

    //method decompositionQR
  //
  boolean decompositionQR(MMatrix& q, MMatrix& r) const {
    return MMatrixMethods::decompositionQR(*this, q, r);
  }

  // method: eigen
  //  find the eigenvalues and vectors for a matrix 
  //
  #ifdef ISIP_TEMPLATE_unsigned
    boolean eigen(TVector& eigvals, MMatrix& eigvects) const {
      return false;
    }
  #else
    boolean eigen(TVector& eigvals, MMatrix& eigvects) const {
      return MMatrixMethods::eigen(*this, eigvals, eigvects);
    }
  #endif

  // method: luSolve
  //
  boolean luSolve(MVector<TScalar, TIntegral>& out_vec,
		  const MMatrix& l,
		  const MMatrix& u,
		  const MVector<TScalar, TIntegral>& in_vec) const {
    VectorLong index(l.getNumRows());
    index.ramp(0, 1);
    return MMatrixMethods::luSolve(out_vec, l, u, index, in_vec);
  }

  // method: luSolve
  //
  boolean luSolve(MVector<TScalar, TIntegral>& out_vec,
		  const MMatrix& l,
		  const MMatrix& u,
		  const VectorLong& index,
		  const MVector<TScalar, TIntegral>& in_vec) const {
    return MMatrixMethods::luSolve(out_vec, l, u, index, in_vec);
  }

  // method: choleskySolve
  //
  boolean choleskySolve(MVector<TScalar, TIntegral>& out_vec,
			const MMatrix& l,
			const MVector<TScalar, TIntegral>& in_vec) const {
    return MMatrixMethods::choleskySolve(out_vec, l, in_vec);
  }

  // method: svdSolve
  //
  boolean svdSolve(MVector<TScalar, TIntegral>& out_vec,
		   const MMatrix& u,
		   const MMatrix& w,
		   const MMatrix& v,
		   const MVector<TScalar, TIntegral>& in_vec,
		   boolean zero_singulars = false) const {
    return MMatrixMethods::svdSolve(out_vec, u, w, v, in_vec, zero_singulars);
  }

  // method: matrix-vector multiplication
  //
  boolean multv(TVector& output_vector, const TVector& input_vector) const {
    return MMatrixMethods::multv<TScalar,TIntegral>(*this, output_vector,
						    input_vector);
  }

  // method: vector-matrix multiplication
  //  
  boolean vmult(TVector& output_vector, const TVector& input_vector) const {
    return MMatrixMethods::vmult<TScalar,TIntegral>(*this, output_vector,
						    input_vector);
  } 

  // method: quadratic
  //  compute (input) * (*this) * (transpose(input))
  //
  boolean quadratic(MMatrix& output, const MMatrix& input) const {
    return MMatrixMethods::quadratic<TScalar,TIntegral>(output, *this, input);
  }

  // method: quadratic
  //  compute (in_vec) * (*this) * (transpose(in_vec))
  //
  boolean quadratic(TIntegral& output, const TVector& in_vec) const {
    return MMatrixMethods::quadratic<TScalar,TIntegral>(output, *this, in_vec);
  }

  // method: outerProduct
  //  compute (*this) * transpose(*this)
  //
  boolean outerProduct() {
    return MMatrixMethods::outerProduct<TScalar,TIntegral>(*this, *this,
							   *this);
  }

  // method: outerProduct
  //  compute (in_mat) * transpose(in_mat)
  //
  template<class TAScalar, class TAIntegral>    
  boolean outerProduct(const MMatrix<TAScalar, TAIntegral>& in_mat) {
    return MMatrixMethods::outerProduct<TScalar,TIntegral>(*this, in_mat,
							   in_mat);
  }

  // method: outerProduct
  //  compute (in_mat1) * transpose(in_mat2)
  //
  template<class TAScalar, class TAIntegral>    
  boolean outerProduct(const MMatrix& in_mat1,
		       const MMatrix<TAScalar, TAIntegral>& in_mat2) {
    return MMatrixMethods::outerProduct<TScalar,TIntegral>(*this, in_mat1,
							   in_mat2);
  }

  // method: outerProduct
  //  compute (in_vec) * transpose(in_vec)
  //
  boolean outerProduct(const TVector& in_vec) {
    return MMatrixMethods::outerProduct<TScalar,TIntegral>(*this, in_vec,
							   in_vec);
  }

  // method: outerProduct
  //  compute (in_vec1) * transpose(in_vec2)
  //
  template<class TAScalar, class TAIntegral>    
  boolean outerProduct(const TVector& in_vec1,
		       const MVector<TAScalar, TAIntegral>& in_vec2) {
    return MMatrixMethods::outerProduct<TScalar,TIntegral>(*this, in_vec1,
							   in_vec2);
  }

  //---------------------------------------------------------------------------
  //
  // class-specific public methods:
  //  other matrix to scalar mathematical methods 
  //
  //--------------------------------------------------------------------------

  // method: sum
  //  calculate the sum of all the elements in the matrix
  //
  TIntegral sum() const {
    return MMatrixMethods::sum<TScalar,TIntegral>(*this);
  }

  // method: sum of square
  //  calculate the sum of squares of all the elements in the matrix
  //  
  TIntegral sumSquare() const {
    return MMatrixMethods::sumSquare<TScalar,TIntegral>(*this);
  }

  // method: sumColumn
  //  calculate the sum of all elements in the given column
  //
  TIntegral sumColumn(long col_index) const {
    return MMatrixMethods::sumColumn<TScalar,TIntegral>(*this, col_index);
  }
  
  // method: sumRow
  //  calculate the sum of all elements in the given row
  //  
  TIntegral sumRow(long row_index) const {
    return MMatrixMethods::sumRow<TScalar,TIntegral>(*this, row_index);
  }
  
  // method: mean
  //  calculate the mean of all the elements in the matrix
  //  
  TIntegral mean() const {
    return sum() / (nrows_d * ncols_d);
  }

  // method: rms
  //  calculate the rms of all the elements in the matrix
  //  
  TIntegral rms() const {
    return (TIntegral)Integral::sqrt(var());
  }

  // method: variance
  //  calculate the variance of all elements as a 1-D array
  //
  TIntegral var() const {
    return MMatrixMethods::var<TScalar,TIntegral>(*this);
  }

  //---------------------------------------------------------------------------
  //
  // private methods
  //
  //---------------------------------------------------------------------------
private:

  //---------------------------------------------------------------------------
  //
  // private methods:
  //  indexing methods
  //
  //--------------------------------------------------------------------------

  // method: index
  //  this method gives the index of an element in the vector for storage
  //
  long index(long row_index, long col_index) const {
    return index(row_index, col_index, nrows_d, ncols_d);
  }

  // method: index
  //
  long index(long row_index, long col_index, long num_rows, long num_cols,
	     Integral::MTYPE type = Integral::UNCHANGED) const;  
  
  // method: reverseIndex
  //  get the row index and column index of the element in the matrix
  //  from the vector index
  //
  boolean reverseIndex(long& row_index, long& col_index,
		       long vec_index) const {
    return MMatrixMethods::reverseIndex(*this, row_index, col_index,
					vec_index);
  }
  
  // method: startRow
  //  get the row index of first available element of a particular column
  //
  long startRow(long row_index,	Integral::MTYPE type_1,
		Integral::MTYPE type_2 = Integral::UNCHANGED) const;
  
  // method: stopRow
  //  get the row index of last available element of a particular column
  //
  long stopRow(long row_index,
	       Integral::MTYPE type_1,
	       Integral::MTYPE type_2 = Integral::UNCHANGED) const;
  
  // method: startColumn
  //  get the column index of first available element of a particular row
  //
  long startColumn(long row_index, Integral::MTYPE type_1,
		   Integral::MTYPE type_2 = Integral::UNCHANGED) const;
  
  // method: stopColumn
  //  get the column index of last available element of a particular row
  //
  long stopColumn(long row_index, Integral::MTYPE type_1,
		  Integral::MTYPE type_2 = Integral::UNCHANGED) const;
  
  // method: vecLength
  //  this method gives the length of the storage vector for a given
  //  dimension and type of the matrix
  //
  long vecLength() const {
    return vecLength(nrows_d, ncols_d, type_d);
  }
  
  // other resizing methods:
  //  these methods facilitate setDimensions, and other such methods,
  //  that require matrix type-specific operations and computations.
  //
  long vecLength(long nrows, long ncols, Integral::MTYPE type) const;
  boolean vecResetCapacity(long nrows, long ncols, Integral::MTYPE type);
  boolean vecResizeCapacity(long nrows, long ncols);
  boolean vecCreateLength(long nrows, long ncols, Integral::MTYPE type);
  boolean vecDimensionLength(long nrows, long ncols, boolean preserve_values);

  // method: randIndicesSparse
  //  a method that generates random non-zero values in a sparse matrix.
  //
  boolean randIndicesSparse(Random& generator = Random::GLOBAL_UNIFORM) {
    return MMatrixMethods::randIndicesSparse<TScalar, TIntegral>(*this,
								 generator);
  }

  // other type-specific indexing methods
  //
  boolean sortSparse();
  
  //---------------------------------------------------------------------------
  //
  // private methods:
  //  linear algebra-related methods
  //
  //--------------------------------------------------------------------------

  // method: multiplyRowByColumn
  //  multiply the specified row of one matrix with the specified
  //  column of the other matrix. this is used in the multiply methods
  //
  template <class TAScalar, class TAIntegral>  
  TIntegral multiplyRowByColumn(const MMatrix& matrix_for_row, const
				MMatrix<TAScalar, TAIntegral>& matrix_for_col,
				long row_index, long col_index) const {
    return MMatrixMethods::multiplyRowByColumn<TScalar, TIntegral>
      (*this, matrix_for_row, matrix_for_col, row_index, col_index);
  }

  // method: multiplyRowByRow
  //  multiply the specified row of one matrix with the specified
  //  row of the other matrix. this is used in the outerProduct methods
  //
  template <class TAScalar, class TAIntegral>  
  TIntegral multiplyRowByRow(const MMatrix& m1,
			     const MMatrix<TAScalar, TAIntegral>& m2,
			     long row_m1, long row_m2) const {
    return MMatrixMethods::multiplyRowByRow<TScalar, TIntegral>
      (*this, m1, m2, row_m1, row_m2);
  }

  // method: determinantLU
  //
  TIntegral determinantLU() const {
    return MMatrixMethods::determinantLU<TScalar,TIntegral>(*this);
  }

  // method: determinantLU
  //
  TIntegral determinantLU(const MMatrix& matrix) const {
    return MMatrixMethods::determinantLU<TScalar,TIntegral>(matrix);
  }

  // method: determinantMinor
  //
  TIntegral determinantMinor() const {
    return MMatrixMethods::determinantMinor<TScalar,TIntegral>(*this);
  }

  // method: determinantMinor
  //
  TIntegral determinantMinor(const MMatrix& matrix) const {
    return MMatrixMethods::determinantMinor<TScalar,TIntegral>(matrix);
  }

  #ifdef ISIP_TEMPLATE_fp
    // method: eigenComputeVector
    //
    boolean eigenComputeVector(MVector<TScalar, TIntegral>& eigvect,
			     TIntegral eigval) {
      return MMatrixMethods::eigenComputeVector(*this, eigvect, eigval);
    }

    // method: eigenBalance
    //
    boolean eigenBalance() {
      return MMatrixMethods::eigenBalance(*this);
    }

    // method: eigenEliminateHessenberg
    //
    boolean eigenEliminateHessenberg() {
      return MMatrixMethods::eigenEliminateHessenberg(*this);
    }

    // method: eigenHessenbergQR
    //
    boolean eigenHessenbergQR(MVector<TScalar, TIntegral>& eigval_real,
  			    MVector<TScalar, TIntegral>& eigval_imag) {
      return MMatrixMethods::eigenHessenbergQR(*this, eigval_real, eigval_imag);;
    }
  #else
    // method: eigenComputeVector
    //
    boolean eigenComputeVector(MVector<TScalar, TIntegral>& eigvect,
			     TIntegral eigval) {
      return false;
    }

    // method: eigenBalance
    //
    boolean eigenBalance() {
      return false;
    }

    // method: eigenEliminateHessenberg
    //
    boolean eigenEliminateHessenberg() {
      return false;
    }

    // method: eigenHessenbergQR
    //
    boolean eigenHessenbergQR(MVector<TScalar, TIntegral>& eigval_real,
  			    MVector<TScalar, TIntegral>& eigval_imag) {
      return false;
    }
  #endif

  //---------------------------------------------------------------------------
  //
  // private methods:
  //  type conversion and streaming
  //
  //--------------------------------------------------------------------------

  // check methods: these methods check properties of the matrix
  //
  boolean checkSquare(long nrows, long ncols, Integral::MTYPE type) const;
  Integral::MTYPE checkType(Integral::MTYPE type) const;
  
  // method: assignStream
  //  assign the matrix from a vector containing the stream of data
  //
  boolean assignStream(long nrows, long ncols,
		       const TVector& vec, Integral::MTYPE type) {
    return MMatrixMethods::assignStream<TScalar, TIntegral>(*this, nrows,
							    ncols, vec, type);
  }

  // method: assign
  //  array conversion methods
  //  reads complex numbers from array of real numbers
  //    - imaginary components are at the end of the array
  //  this method is used only in diagnoese method.
  //
  boolean assignComplexDiagnose(long num_rows, long num_cols,
				  const double* arg,
				  Integral::MTYPE type = Integral::DEF_MTYPE) {

    return MMatrixMethods::assignComplexDiagnose<TScalar, TIntegral>
      (*this, num_rows, num_cols, arg, type);
  }    

  //---------------------------------------------------------------------------
  //
  // friend classes:
  //  while friend classes and functions are generally discouraged,
  //  they are used in the MMatrix class to circumvent the
  //  template delayed compilation problem. 
  //
  //--------------------------------------------------------------------------

  friend class MMatrixMethods;
};

// end of include file
//
#endif
