/* * Portable Agile C++ Classes (PACC) * Copyright (C) 2001-2004 by Marc Parizeau * http://manitou.gel.ulaval.ca/~parizeau/PACC * * This library 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. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Contact: * Laboratoire de Vision et Systemes Numeriques * Departement de genie electrique et de genie informatique * Universite Laval, Quebec, Canada, G1K 7P4 * http://vision.gel.ulaval.ca * */ /*! * \file PACC/Math/Matrix.cpp * \brief Method definitions for class Matrix. * \author Marc Parizeau and Christian Gagné, Laboratoire de vision et systèmes numériques, Université Laval * $Revision: 1.9.2.1 $ * $Date: 2007/09/10 18:24:08 $ */ #include "Math/Matrix.hpp" #include "Math/Vector.hpp" #include "Util/StringFunc.hpp" #include #include #include using namespace std; using namespace PACC; /*! This method also returns a reference to the result. */ Matrix& Matrix::add(Matrix& outMatrix, double inScalar) const { PACC_AssertM(mRows > 0 && mCols > 0, "add() invalid matrix!"); outMatrix.setRowsCols(mRows, mCols); for(unsigned int i = 0; i < size(); ++i) outMatrix[i] = (*this)[i] + inScalar; return outMatrix; } /*! This method also returns a reference to the result. */ Matrix& Matrix::add(Matrix& outMatrix, const Matrix& inMatrix) const { PACC_AssertM(mRows > 0 && mCols > 0, "add() invalid matrix!"); PACC_AssertM(mRows == inMatrix.mRows && mCols == inMatrix.mCols, "add() matrix mismatch!"); outMatrix.setRowsCols(mRows, mCols); for(unsigned int i = 0; i < size(); ++i) outMatrix[i] = (*this)[i] + inMatrix[i]; return outMatrix; } /*! */ void Matrix::computeBackSubLU(const vector& inIndexes, Matrix& ioMatrix) const { unsigned int lII = UINT_MAX; for(unsigned int i = 0; i < mRows; ++i) { double lSum = ioMatrix(inIndexes[i], 0); ioMatrix(inIndexes[i], 0) = ioMatrix(i, 0); if(lII != UINT_MAX) { for(unsigned int j = lII; j < i; ++j) lSum -= (*this)(i,j) * ioMatrix(j, 0); } else if(lSum != 0.0) lII = i; ioMatrix(i, 0) = lSum; } for(unsigned int i = 0; i < mRows; ++i) { const unsigned int lR = mRows-i-1; double lSum = ioMatrix(lR,0); for(unsigned int j = lR+1; j < mCols; ++j) lSum -= (*this)(lR,j) * ioMatrix(j,0); ioMatrix(lR,0) = lSum / (*this)(lR,lR); } } /*! */ double Matrix::computeDeterminant(void) const { PACC_AssertM(mRows > 0 && mCols > 0, "computeDeterminant() invalid matrix!"); PACC_AssertM(mRows == mCols, "computeDeterminant() matrix not square!"); Matrix lTmp = *this; vector lIndexes(mRows); int lD; lTmp.decomposeLU(lIndexes, lD); double lResult = lD; for(unsigned int i = 0; i < mRows; ++i) lResult *= lTmp(i,i); return lResult; } /*! */ void Matrix::computeEigens(Vector& outValues, Matrix& outVectors) const { PACC_AssertM(mRows > 0 && mCols > 0, "computeEigens() invalid matrix!"); PACC_AssertM(mRows == mCols, "computeEigens() matrix not square!"); outValues.resize(mRows); outVectors.resize(mRows, mCols); // Computer eigenvectors/eigenvalues using Triagonal QL method Vector lE(mRows); tred2(outValues, lE, outVectors); tql2(outValues, lE, outVectors); // Sort by eigenvalues. for(unsigned int j = 0; j < outValues.size(); ++j) { double lMax=outValues[j]; unsigned int lMaxArg=j; for(unsigned int l = j+1; l lMax) { lMax=outValues[l]; lMaxArg=l; } } if(lMaxArg != j) { for(unsigned int r = 0; r < outVectors.mRows; ++r) { double lTmp = outVectors(r,j); outVectors(r,j) = outVectors(r,lMaxArg); outVectors(r,lMaxArg) = lTmp; } double lTmp = outValues[j]; outValues[j] = outValues[lMaxArg]; outValues[lMaxArg] = lTmp; } } } /*! */ void Matrix::decomposeLU(vector& outIndexes, int& outD) { outD = 1; vector lScales; scaleLU(lScales); for(unsigned int j = 0; j < mCols; ++j) { for(unsigned int i = 0; i < j; ++i) { double lSum = (*this)(i, j); for(unsigned int k = 0; k < i; ++k) lSum -= (*this)(i,k) * (*this)(k,j); (*this)(i, j) = lSum; } double lMax = 0; unsigned int l = j; for(unsigned int i = j; i < mRows; ++i) { double lSum = (*this)(i,j); for(unsigned int k = 0; k < j; ++k) lSum -= (*this)(i,k) * (*this)(k,j); (*this)(i, j) = lSum; double lTmp = lScales[i] * fabs(lSum); if(lTmp >= lMax) { l = i; lMax = lTmp; } } if(j != l) { for(unsigned int k = 0; k < (*this).mCols; ++k) { double lTmp = (*this)(l,k); (*this)(l,k) = (*this)(j,k); (*this)(j,k) = lTmp; } outD = -outD; lScales[l] = lScales[j]; } outIndexes[j] = l; if((*this)(j,j) == 0.0) (*this)(j,j) = 1e-20; if(j != (mCols-1)) { double lDummy = 1.0 / (*this)(j,j); for(unsigned int i = j+1; i < mRows; ++i) (*this)(i,j) *= lDummy; } } } /*! This method also returns a reference to the result. */ Matrix& Matrix::extract(Matrix& outMatrix, unsigned int inRow1, unsigned int inRow2, unsigned int inCol1, unsigned int inCol2) const { PACC_AssertM(inRow1 <= inRow2 && inCol1 <= inCol2 && inRow2 < mRows && inCol2 < mCols, "extract() invalid indexes!"); if(&outMatrix != this) { // output matrix is not self assigning outMatrix.setRowsCols(inRow2-inRow1+1, inCol2-inCol1+1); for(unsigned int i = inRow1; i <= inRow2; ++i) { for(unsigned int j = inCol1; j <= inCol2; ++j) { outMatrix(i-inRow1,j-inCol1) = (*this)(i,j); } } } else { // use temporary matrix to self assign Matrix lMatrix(*this); outMatrix.setRowsCols(inRow2-inRow1+1, inCol2-inCol1+1); for(unsigned int i = inRow1; i <= inRow2; ++i) { for(unsigned int j = inCol1; j <= inCol2; ++j) { outMatrix(i-inRow1,j-inCol1) = lMatrix(i,j); } } } return outMatrix; } /*! This method also returns a reference to the result. */ Matrix& Matrix::extractColumn(Matrix& outMatrix, unsigned int inCol) const { return extract(outMatrix, 0, mRows-1, inCol, inCol); } /*! This method also returns a reference to the result. */ Matrix& Matrix::extractRow(Matrix& outMatrix, unsigned int inRow) const { return extract(outMatrix, inRow, inRow, 0, mCols-1); } /*! */ double Matrix::hypot(double a, double b) const { double r; if(abs(a) > abs(b)) { r = b/a; r = abs(a)*sqrt(1+r*r); } else if(b != 0) { r = a/b; r = abs(b)*sqrt(1+r*r); } else { r = 0.0; } return r; } /*! */ Matrix Matrix::invert(void) const { Matrix lMatrix; return invert(lMatrix); } /*! This method also returns a reference to the result. */ Matrix& Matrix::invert(Matrix& outMatrix) const { PACC_AssertM(mRows == mCols, "invert() matrix not square!"); Matrix lTmp = *this; vector lIndexes(mRows); int lD; lTmp.decomposeLU(lIndexes, lD); outMatrix.setIdentity(mRows); Matrix lB(mRows, 1); for(unsigned int j = 0; j < mCols; ++j) { for(unsigned int i = 0; i < mRows; ++i) lB(i,0) = outMatrix(i,j); lTmp.computeBackSubLU(lIndexes, lB); for(unsigned int i = 0; i < mRows; ++i) outMatrix(i,j) = lB(i,0); } return outMatrix; } /*! This method also returns a reference to the result. */ Matrix& Matrix::multiply(Matrix& outMatrix, double inScalar) const { PACC_AssertM(mRows > 0 && mCols > 0, "multiply() invalid matrix!"); outMatrix.setRowsCols(mRows, mCols); for(unsigned int i = 0; i < size(); ++i) outMatrix[i] = (*this)[i] * inScalar; return outMatrix; } /*! This method also returns a reference to the result. */ Matrix& Matrix::multiply(Matrix& outMatrix, const Matrix& inMatrix) const { PACC_AssertM(mCols == inMatrix.mRows, "multiply() matrix mismatch!"); if(&outMatrix != this && &outMatrix != &inMatrix) { // output matrix is neither left or right matrix (no self assigment) outMatrix.setRowsCols(mRows, inMatrix.mCols); for(unsigned int i = 0; i < outMatrix.mRows; ++i) { for(unsigned int j = 0; j < outMatrix.mCols; ++j) { outMatrix(i,j) = 0; for(unsigned int k = 0; k < mCols; ++k) { outMatrix(i,j) += (*this)(i,k) * inMatrix(k,j); } } } } else if(&outMatrix == this && &outMatrix != &inMatrix) { // use temporary matrix to self assign with left matrix Matrix lMatrix(*this); outMatrix.setRowsCols(mRows, inMatrix.mCols); for(unsigned int i = 0; i < outMatrix.mRows; ++i) { for(unsigned int j = 0; j < outMatrix.mCols; ++j) { outMatrix(i,j) = 0; for(unsigned int k = 0; k < mCols; ++k) { outMatrix(i,j) += lMatrix(i,k) * inMatrix(k,j); } } } } else if(&outMatrix != this && &outMatrix == &inMatrix) { // use temporary matrix to self assign with right matrix Matrix lMatrix(inMatrix); outMatrix.setRowsCols(mRows, inMatrix.mCols); for(unsigned int i = 0; i < outMatrix.mRows; ++i) { for(unsigned int j = 0; j < outMatrix.mCols; ++j) { outMatrix(i,j) = 0; for(unsigned int k = 0; k < mCols; ++k) { outMatrix(i,j) += (*this)(i,k) * lMatrix(k,j); } } } } else { // use temporary matrix to self assign with both left and right matrices Matrix lMatrix(*this); outMatrix.setRowsCols(mRows, inMatrix.mCols); for(unsigned int i = 0; i < outMatrix.mRows; ++i) { for(unsigned int j = 0; j < outMatrix.mCols; ++j) { outMatrix(i,j) = 0; for(unsigned int k = 0; k < mCols; ++k) { outMatrix(i,j) += lMatrix(i,k) * lMatrix(k,j); } } } } return outMatrix; } /*! Matrix elements must be enumerated in row order and delimited by either commas (','), semi-columns (';'), or white space. The recommended style is to seperate elements with comas, and rows with semi-columns. For example: \verbatim 1,2,3,4;5,6,7,8;9,10,11,12 \endverbatim The number of elements must match the product of the "rows" and "cols" attributes. */ string Matrix::read(const XML::Iterator& inNode) { if(!inNode) throw runtime_error("Matrix::read() nothing to read!"); clear(); for(XML::Iterator lChild = inNode->getFirstChild(); lChild; ++lChild) { if(lChild->getType() == XML::eString) { istringstream lStream(lChild->getValue()); Tokenizer lTokenizer(lStream); lTokenizer.setDelimiters(" \n\r\t,;", ""); string lToken; while(lTokenizer.getNextToken(lToken)) push_back(String::convertToFloat(lToken)); } } mRows = String::convertToInteger(inNode->getAttribute("rows")); mCols = String::convertToInteger(inNode->getAttribute("cols")); if(vector::size() != mRows*mCols) { throwError("Matrix::read() number of elements does not match the rows x cols attributes", inNode); } string lName = inNode->getAttribute("name"); if(lName != "") mName = lName; return lName; } /*! */ void Matrix::resize(unsigned int inRows, unsigned int inCols) { Matrix lMat(*this); setRowsCols(inRows, inCols); for(unsigned int i = 0; i < mRows; ++i) { for(unsigned int j = 0; j < mCols; ++j) { (*this)(i,j) = (i < lMat.mRows && j < lMat.mCols ? lMat(i,j) : 0.); } } } /*! */ void Matrix::scaleLU(vector& outScales) const { outScales.resize(mCols); for(unsigned int i = 0; i < mRows; ++i) { double lMax = 0.; for(unsigned int j = 0; j < mCols; ++j) { const double lTmp = fabs((*this)(i,j)); if(lTmp > lMax) lMax=lTmp; } if(lMax == 0.) throw runtime_error(" matrix is singular!"); outScales[i] = 1./lMax; } } /*! */ void Matrix::setIdentity(unsigned int inSize) { setRowsCols(inSize, inSize); for(unsigned int j = 0; j < mCols; ++j) { for(unsigned int i = 0; i < mRows; ++i) { (*this)(i,j) = (i == j ? 1 : 0); } } } /*! This method also returns a reference to the result. */ Matrix& Matrix::subtract(Matrix& outMatrix, double inScalar) const { PACC_AssertM(mRows > 0 && mCols > 0, "subtract() invalid matrix!"); outMatrix.setRowsCols(mRows, mCols); for(unsigned int i = 0; i < size(); ++i) outMatrix[i] = (*this)[i] - inScalar; return outMatrix; } /*! This method also returns a reference to the result. */ Matrix& Matrix::subtract(Matrix& outMatrix, const Matrix& inMatrix) const { PACC_AssertM(mRows > 0 && mCols > 0, "subtract() invalid matrix!"); PACC_AssertM(mRows == inMatrix.mRows && mCols == inMatrix.mCols, "subtract() matrix mismatch!"); outMatrix.setRowsCols(mRows, mCols); for(unsigned int i = 0; i < size(); ++i) outMatrix[i] = (*this)[i] - inMatrix[i]; return outMatrix; } /*! * \param d Real part of eigenvalues computed from the matrix. * \param e Imaginary part of eigenvalues computed from the matrix. * \param V Eigenvectors computed from the matrix. * * This method is derived from procedure tql2 of the Java package JAMA, * which is itself derived from the Algol procedures tql2, by * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding * Fortran subroutine in EISPACK. */ void Matrix::tql2(Vector& d, Vector& e, Matrix& V) const { const unsigned int n=mRows; for(unsigned int i = 1; i < n; i++) e[i-1] = e[i]; e[n-1] = 0.0; double f = 0.0; double tst1 = 0.0; double eps = std::pow(2.0,-52.0); for(unsigned int l = 0; l < n; l++) { // Find small subdiagonal element tst1 = max(tst1, abs(d[l]) + abs(e[l])); unsigned int m=l; while((m+1) < n) { if(std::abs(e[m]) <= eps*tst1) break; m++; } // If m == l, d[l] is an eigenvalue, // otherwise, iterate. if(m > l) { unsigned int iter = 0; do { iter = iter + 1; // (Could check iteration count here.) // Compute implicit shift double g = d[l]; double p = (d[l+1] - g) / (2.0 * e[l]); double r = hypot(p,1.0); if(p < 0) r = -r; d[l] = e[l] / (p + r); d[l+1] = e[l] * (p + r); double dl1 = d[l+1]; double h = g - d[l]; for(unsigned int i = l+2; i < n; i++) d[i] -= h; f = f + h; // Implicit QL transformation. p = d[m]; double c = 1.0; double c2 = c; double c3 = c; double el1 = e[l+1]; double s = 0.0; double s2 = 0.0; for(unsigned int i = m-1; i >= l; i--) { c3 = c2; c2 = c; s2 = s; g = c * e[i]; h = c * p; r = hypot(p,e[i]); e[i+1] = s * r; s = e[i] / r; c = p / r; p = c * d[i] - s * g; d[i+1] = h + s * (c * g + s * d[i]); // Accumulate transformation. for(unsigned int k = 0; k < n; k++) { h = V(k,i+1); V(k,i+1) = s * V(k,i) + c * h; V(k,i) = c * V(k,i) - s * h; } if(i == 0) break; } p = -s * s2 * c3 * el1 * e[l] / dl1; e[l] = s * p; d[l] = c * p; // Check for convergence. } while (std::abs(e[l]) > eps*tst1); } d[l] = d[l] + f; e[l] = 0.0; } } /*! * \param d Real part of eigenvalues computed from the matrix. * \param e Imaginary part of eigenvalues computed from the matrix. * \param V Eigenvectors computed from the matrix. * * This method is derived from procedure tred2 of the Java package JAMA, * which is itself derived from the Algol procedures tred2, by * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding * Fortran subroutine in EISPACK. */ void Matrix::tred2(Vector& d, Vector& e, Matrix& V) const { const unsigned int n=mRows; V = *this; for(unsigned int j = 0; j < n; ++j) d[j] = V(n-1,j); // Householder reduction to tridiagonal form. for(unsigned int i = n-1; i > 0; --i) { // Scale to avoid under/overflow. double scale = 0.0; double h = 0.0; for(unsigned int k = 0; k < i; ++k) scale += abs(d[k]); if(scale == 0.0) { e[i] = d[i-1]; for(unsigned int j = 0; j < i; ++j) { d[j] = V(i-1,j); V(i,j) = 0.0; V(j,i) = 0.0; } } else { // Generate Householder vector. for(unsigned int k=0; k 0.0) g = -g; e[i] = scale * g; h = h - f * g; d[i-1] = f - g; for(unsigned int j = 0; j < i; j++) e[j] = 0.0; // Apply similarity transformation to remaining columns. for(unsigned int j = 0; j < i; j++) { f = d[j]; V(j,i) = f; g = e[j] + V(j,j) * f; for(unsigned int k = j+1; k <= i-1; k++) { g += V(k,j) * d[k]; e[k] += V(k,j) * f; } e[j] = g; } f = 0.0; for(unsigned int j = 0; j < i; j++) { e[j] /= h; f += e[j] * d[j]; } double hh = f / (h + h); for(unsigned int j=0; j 0 && mCols > 0, "transpose() invalid matrix!"); if(&outMatrix != this) { // output matrix is not self assigning outMatrix.setRowsCols(mCols,mRows); // transpose elements for(unsigned int i = 0; i< mRows; ++i) { for(unsigned int j = 0; j < mCols; ++j) { outMatrix(j,i) = (*this)(i,j); } } } else { // use temporary matrix to self assign Matrix lMatrix(*this); outMatrix.setRowsCols(mCols,mRows); // transpose elements for(unsigned int i = 0; i< mRows; ++i) { for(unsigned int j = 0; j < mCols; ++j) { outMatrix(j,i) = lMatrix(i,j); } } } return outMatrix; } /*! */ void Matrix::throwError(const string& inMessage, const XML::Iterator& inNode) const { ostringstream lStream; lStream << inMessage << " for markup:\n"; XML::Streamer lStreamer(lStream); inNode->serialize(lStreamer); throw runtime_error(lStream.str()); } /*! See Matrix::read for a description of the write format. By default, the precision of the output is set to 15 digits. This value can be changed using method Matrix::setPrecision. */ void Matrix::write(XML::Streamer& outStream, const string& inTag) const { outStream.openTag(inTag, false); if(mName != "") outStream.insertAttribute("name", mName); outStream.insertAttribute("rows", mRows); outStream.insertAttribute("cols", mCols); ostringstream lContent; lContent.precision(mPrec); for(unsigned int i = 0; i < size(); ++i) { if(i != 0 && i % mCols == 0) lContent << ";"; else if(i != 0) lContent << ","; lContent << (*this)[i]; } outStream.insertStringContent(lContent.str()); outStream.closeTag(); } /*! */ ostream& PACC::operator<<(ostream &outStream, const Matrix& inMatrix) { XML::Streamer lStream(outStream); inMatrix.write(lStream); return outStream; } /*! This method uses the first data tag of the parse tree to read the matrix. The corresponding tree root is then erased. Any read error throws a std::runtime_error. */ XML::Document& PACC::operator>>(XML::Document& inDocument, Matrix& outMatrix) { XML::Iterator lNode = inDocument.getFirstDataTag(); outMatrix.read(lNode); inDocument.erase(lNode); return inDocument; }