diff options
author | Jitse Niesen <jitse@maths.leeds.ac.uk> | 2011-06-07 14:44:43 +0100 |
---|---|---|
committer | Jitse Niesen <jitse@maths.leeds.ac.uk> | 2011-06-07 14:44:43 +0100 |
commit | 8c8ab9ae103930e21da76ed6dcbaa585e8af7ff4 (patch) | |
tree | 3bef2b3707b0e0efa6135e791fa7e060cc83debf | |
parent | a6d42e28fe445dca105552e8f2a54254102f4c4a (diff) |
Implement matrix logarithm + test + docs.
Currently, test matrix_function_1 fails due to bug #288.
-rw-r--r-- | Eigen/src/Core/MatrixBase.h | 1 | ||||
-rw-r--r-- | Eigen/src/Core/util/ForwardDeclarations.h | 1 | ||||
-rw-r--r-- | unsupported/Eigen/MatrixFunctions | 56 | ||||
-rw-r--r-- | unsupported/Eigen/src/MatrixFunctions/MatrixLogarithm.h | 340 | ||||
-rw-r--r-- | unsupported/doc/examples/MatrixLogarithm.cpp | 15 | ||||
-rw-r--r-- | unsupported/test/matrix_function.cpp | 21 |
6 files changed, 434 insertions, 0 deletions
diff --git a/Eigen/src/Core/MatrixBase.h b/Eigen/src/Core/MatrixBase.h index bc5766f04..e2d34d257 100644 --- a/Eigen/src/Core/MatrixBase.h +++ b/Eigen/src/Core/MatrixBase.h @@ -466,6 +466,7 @@ template<typename Derived> class MatrixBase const MatrixFunctionReturnValue<Derived> cos() const; const MatrixFunctionReturnValue<Derived> sin() const; const MatrixSquareRootReturnValue<Derived> sqrt() const; + const MatrixLogarithmReturnValue<Derived> log() const; #ifdef EIGEN2_SUPPORT template<typename ProductDerived, typename Lhs, typename Rhs> diff --git a/Eigen/src/Core/util/ForwardDeclarations.h b/Eigen/src/Core/util/ForwardDeclarations.h index 74be25216..9f5fbc9f6 100644 --- a/Eigen/src/Core/util/ForwardDeclarations.h +++ b/Eigen/src/Core/util/ForwardDeclarations.h @@ -284,6 +284,7 @@ template<typename MatrixType,int Direction> class Homogeneous; template<typename Derived> struct MatrixExponentialReturnValue; template<typename Derived> class MatrixFunctionReturnValue; template<typename Derived> class MatrixSquareRootReturnValue; +template<typename Derived> class MatrixLogarithmReturnValue; namespace internal { template <typename Scalar> diff --git a/unsupported/Eigen/MatrixFunctions b/unsupported/Eigen/MatrixFunctions index ecdf23cef..43955d352 100644 --- a/unsupported/Eigen/MatrixFunctions +++ b/unsupported/Eigen/MatrixFunctions @@ -50,6 +50,7 @@ namespace Eigen { * - \ref matrixbase_cos "MatrixBase::cos()", for computing the matrix cosine * - \ref matrixbase_cosh "MatrixBase::cosh()", for computing the matrix hyperbolic cosine * - \ref matrixbase_exp "MatrixBase::exp()", for computing the matrix exponential + * - \ref matrixbase_log "MatrixBase::log()", for computing the matrix logarithm * - \ref matrixbase_matrixfunction "MatrixBase::matrixFunction()", for computing general matrix functions * - \ref matrixbase_sin "MatrixBase::sin()", for computing the matrix sine * - \ref matrixbase_sinh "MatrixBase::sinh()", for computing the matrix hyperbolic sine @@ -71,6 +72,7 @@ namespace Eigen { #include "src/MatrixFunctions/MatrixExponential.h" #include "src/MatrixFunctions/MatrixFunction.h" #include "src/MatrixFunctions/MatrixSquareRoot.h" +#include "src/MatrixFunctions/MatrixLogarithm.h" @@ -172,6 +174,60 @@ Output: \verbinclude MatrixExponential.out \c complex<float> or \c complex<double> . +\section matrixbase_log MatrixBase::log() + +Compute the matrix logarithm. + +\code +const MatrixLogarithmReturnValue<Derived> MatrixBase<Derived>::log() const +\endcode + +\param[in] M invertible matrix whose logarithm is to be computed. +\returns expression representing the matrix logarithm root of \p M. + +The matrix logarithm of \f$ M \f$ is a matrix \f$ X \f$ such that +\f$ \exp(X) = M \f$ where exp denotes the matrix exponential. As for +the scalar logarithm, the equation \f$ \exp(X) = M \f$ may have +multiple solutions; this function returns a matrix whose eigenvalues +have imaginary part in the interval \f$ (-\pi,\pi] \f$. + +In the real case, the matrix \f$ M \f$ should be invertible and +it should have no eigenvalues which are real and negative (pairs of +complex conjugate eigenvalues are allowed). In the complex case, it +only needs to be invertible. + +This function computes the matrix logarithm using the Schur-Parlett +algorithm as implemented by MatrixBase::matrixFunction(). The +logarithm of an atomic block is computed by MatrixLogarithmAtomic, +which uses direct computation for 1-by-1 and 2-by-2 blocks and an +inverse scaling-and-squaring algorithm for bigger blocks, with the +square roots computed by MatrixBase::sqrt(). + +Details of the algorithm can be found in Section 11.6.2 of: +Nicholas J. Higham, +<em>Functions of Matrices: Theory and Computation</em>, +SIAM 2008. ISBN 978-0-898716-46-7. + +Example: The following program checks that +\f[ \log \left[ \begin{array}{ccc} + \frac12\sqrt2 & -\frac12\sqrt2 & 0 \\ + \frac12\sqrt2 & \frac12\sqrt2 & 0 \\ + 0 & 0 & 1 + \end{array} \right] = \left[ \begin{array}{ccc} + 0 & \frac14\pi & 0 \\ + -\frac14\pi & 0 & 0 \\ + 0 & 0 & 0 + \end{array} \right]. \f] +This corresponds to a rotation of \f$ \frac14\pi \f$ radians around +the z-axis. This is the inverse of the example used in the +documentation of \ref matrixbase_exp "exp()". + +\include MatrixLogarithm.cpp +Output: \verbinclude MatrixLogarithm.out + +\sa MatrixBase::exp(), MatrixBase::matrixFunction(), + class MatrixLogarithmAtomic, MatrixBase::sqrt(). + \section matrixbase_matrixfunction MatrixBase::matrixFunction() diff --git a/unsupported/Eigen/src/MatrixFunctions/MatrixLogarithm.h b/unsupported/Eigen/src/MatrixFunctions/MatrixLogarithm.h new file mode 100644 index 000000000..8cdcca659 --- /dev/null +++ b/unsupported/Eigen/src/MatrixFunctions/MatrixLogarithm.h @@ -0,0 +1,340 @@ +// This file is part of Eigen, a lightweight C++ template library +// for linear algebra. +// +// Copyright (C) 2011 Jitse Niesen <jitse@maths.leeds.ac.uk> +// +// Eigen 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 3 of the License, or (at your option) any later version. +// +// Alternatively, you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of +// the License, or (at your option) any later version. +// +// Eigen 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 or the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License and a copy of the GNU General Public License along with +// Eigen. If not, see <http://www.gnu.org/licenses/>. + +#ifndef EIGEN_MATRIX_LOGARITHM +#define EIGEN_MATRIX_LOGARITHM + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +/** \ingroup MatrixFunctions_Module + * \class MatrixLogarithmAtomic + * \brief Helper class for computing matrix logarithm of atomic matrices. + * + * \internal + * Here, an atomic matrix is a triangular matrix whose diagonal + * entries are close to each other. + * + * \sa class MatrixFunctionAtomic, MatrixBase::log() + */ +template <typename MatrixType> +class MatrixLogarithmAtomic +{ +public: + + typedef typename MatrixType::Scalar Scalar; + // typedef typename MatrixType::Index Index; + typedef typename NumTraits<Scalar>::Real RealScalar; + // typedef typename internal::stem_function<Scalar>::type StemFunction; + // typedef Matrix<Scalar, MatrixType::RowsAtCompileTime, 1> VectorType; + + /** \brief Constructor. */ + MatrixLogarithmAtomic() { } + + /** \brief Compute matrix logarithm of atomic matrix + * \param[in] A argument of matrix logarithm, should be upper triangular and atomic + * \returns The logarithm of \p A. + */ + MatrixType compute(const MatrixType& A); + +private: + + void compute2x2(const MatrixType& A, MatrixType& result); + void computeBig(const MatrixType& A, MatrixType& result); + static Scalar atanh(Scalar x); + int getPadeDegree(typename MatrixType::RealScalar normTminusI); + void computePade(MatrixType& result, const MatrixType& T, int degree); + void computePade3(MatrixType& result, const MatrixType& T); + void computePade4(MatrixType& result, const MatrixType& T); + void computePade5(MatrixType& result, const MatrixType& T); + void computePade6(MatrixType& result, const MatrixType& T); + void computePade7(MatrixType& result, const MatrixType& T); + + static const double maxNormForPade[]; + static const int minPadeDegree = 3; + static const int maxPadeDegree = 7; + + // Prevent copying + MatrixLogarithmAtomic(const MatrixLogarithmAtomic&); + MatrixLogarithmAtomic& operator=(const MatrixLogarithmAtomic&); +}; + +template <typename MatrixType> +const double MatrixLogarithmAtomic<MatrixType>::maxNormForPade[] = { 0.0162 /* degree = 3 */, 0.0539, 0.114, 0.187, 0.264 }; + +/** \brief Compute logarithm of triangular matrix with clustered eigenvalues. */ +template <typename MatrixType> +MatrixType MatrixLogarithmAtomic<MatrixType>::compute(const MatrixType& A) +{ + using std::log; + MatrixType result(A.rows(), A.rows()); + if (A.rows() == 1) + result(0,0) = log(A(0,0)); + else if (A.rows() == 2) + compute2x2(A, result); + else + computeBig(A, result); + return result; +} + +/** \brief Compute atanh (inverse hyperbolic tangent). */ +template <typename MatrixType> +typename MatrixType::Scalar MatrixLogarithmAtomic<MatrixType>::atanh(typename MatrixType::Scalar x) +{ + using std::abs; + using std::sqrt; + if (abs(x) > sqrt(NumTraits<Scalar>::epsilon())) + return Scalar(0.5) * log((Scalar(1) + x) / (Scalar(1) - x)); + else + return x + x*x*x / Scalar(3); +} + +/** \brief Compute logarithm of 2x2 triangular matrix. */ +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::compute2x2(const MatrixType& A, MatrixType& result) +{ + using std::abs; + using std::ceil; + using std::imag; + using std::log; + + Scalar logA00 = log(A(0,0)); + Scalar logA11 = log(A(1,1)); + + result(0,0) = logA00; + result(1,0) = Scalar(0); + result(1,1) = logA11; + + if (A(0,0) == A(1,1)) { + result(0,1) = A(0,1) / A(0,0); + } else if ((abs(A(0,0)) < 0.5*abs(A(1,1))) || (abs(A(0,0)) > 2*abs(A(1,1)))) { + result(0,1) = A(0,1) * (logA11 - logA00) / (A(1,1) - A(0,0)); + } else { + // computation in previous branch is inaccurate if A(1,1) \approx A(0,0) + int unwindingNumber = ceil((imag(logA11 - logA00) - M_PI) / (2*M_PI)); + Scalar z = (A(1,1) - A(0,0)) / (A(1,1) + A(0,0)); + result(0,1) = A(0,1) * (Scalar(2) * atanh(z) + Scalar(0,2*M_PI*unwindingNumber)) / (A(1,1) - A(0,0)); + } +} + +/** \brief Compute logarithm of triangular matrices with size > 2. + * \details This uses a inverse scale-and-square algorithm. */ +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computeBig(const MatrixType& A, MatrixType& result) +{ + int numberOfSquareRoots = 0; + int numberOfExtraSquareRoots = 0; + int degree; + MatrixType T = A; + + while (true) { + RealScalar normTminusI = (T - MatrixType::Identity(T.rows(), T.rows())).cwiseAbs().colwise().sum().maxCoeff(); + if (normTminusI < maxNormForPade[maxPadeDegree - minPadeDegree]) { + degree = getPadeDegree(normTminusI); + int degree2 = getPadeDegree(normTminusI / RealScalar(2)); + if ((degree - degree2 <= 1) || (numberOfExtraSquareRoots == 1)) + break; + ++numberOfExtraSquareRoots; + } + T = T.sqrt(); + ++numberOfSquareRoots; + } + + computePade(result, T, degree); + result *= pow(RealScalar(2), numberOfSquareRoots); +} + +/* \brief Get suitable degree for Pade approximation. */ +template <typename MatrixType> +int MatrixLogarithmAtomic<MatrixType>::getPadeDegree(typename MatrixType::RealScalar normTminusI) +{ + for (int degree = 3; degree <= maxPadeDegree; ++degree) + if (normTminusI <= maxNormForPade[degree - minPadeDegree]) + return degree; + assert(false); // this line should never be reached +} + +/* \brief Compute Pade approximation to matrix logarithm */ +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computePade(MatrixType& result, const MatrixType& T, int degree) +{ + switch (degree) { + case 3: computePade3(result, T); break; + case 4: computePade4(result, T); break; + case 5: computePade5(result, T); break; + case 6: computePade6(result, T); break; + case 7: computePade7(result, T); break; + default: assert(false); // should never happen + } +} + +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computePade3(MatrixType& result, const MatrixType& T) +{ + const int degree = 3; + double nodes[] = { 0.112701665379258, 0.500000000000000, 0.887298334620742 }; + double weights[] = { 0.277777777777778, 0.444444444444444, 0.277777777777778 }; + MatrixType TminusI = T - MatrixType::Identity(T.rows(), T.rows()); + result.setZero(T.rows(), T.rows()); + for (int k = 0; k < degree; ++k) + result += weights[k] * (MatrixType::Identity(T.rows(), T.rows()) + nodes[k] * TminusI) + .template triangularView<Upper>().solve(TminusI); +} + +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computePade4(MatrixType& result, const MatrixType& T) +{ + const int degree = 4; + double nodes[] = { 0.069431844202974, 0.330009478207572, 0.669990521792428, 0.930568155797026 }; + double weights[] = { 0.173927422568727, 0.326072577431273, 0.326072577431273, 0.173927422568727 }; + MatrixType TminusI = T - MatrixType::Identity(T.rows(), T.rows()); + result.setZero(T.rows(), T.rows()); + for (int k = 0; k < degree; ++k) + result += weights[k] * (MatrixType::Identity(T.rows(), T.rows()) + nodes[k] * TminusI) + .template triangularView<Upper>().solve(TminusI); +} + +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computePade5(MatrixType& result, const MatrixType& T) +{ + const int degree = 5; + double nodes[] = { 0.046910077030668, 0.230765344947158, 0.500000000000000, + 0.769234655052841, 0.953089922969332 }; + double weights[] = { 0.118463442528095, 0.239314335249683, 0.284444444444444, + 0.239314335249683, 0.118463442528094 }; + MatrixType TminusI = T - MatrixType::Identity(T.rows(), T.rows()); + result.setZero(T.rows(), T.rows()); + for (int k = 0; k < degree; ++k) + result += weights[k] * (MatrixType::Identity(T.rows(), T.rows()) + nodes[k] * TminusI) + .template triangularView<Upper>().solve(TminusI); +} + +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computePade6(MatrixType& result, const MatrixType& T) +{ + const int degree = 6; + double nodes[] = { 0.033765242898424, 0.169395306766868, 0.380690406958402, + 0.619309593041598, 0.830604693233132, 0.966234757101576 }; + double weights[] = { 0.085662246189585, 0.180380786524069, 0.233956967286345, + 0.233956967286346, 0.180380786524069, 0.085662246189585 }; + MatrixType TminusI = T - MatrixType::Identity(T.rows(), T.rows()); + result.setZero(T.rows(), T.rows()); + for (int k = 0; k < degree; ++k) + result += weights[k] * (MatrixType::Identity(T.rows(), T.rows()) + nodes[k] * TminusI) + .template triangularView<Upper>().solve(TminusI); +} + +template <typename MatrixType> +void MatrixLogarithmAtomic<MatrixType>::computePade7(MatrixType& result, const MatrixType& T) +{ + const int degree = 7; + double nodes[] = { 0.025446043828621, 0.129234407200303, 0.297077424311301, 0.500000000000000, + 0.702922575688699, 0.870765592799697, 0.974553956171379 }; + double weights[] = { 0.064742483084435, 0.139852695744638, 0.190915025252559, 0.208979591836734, + 0.190915025252560, 0.139852695744638, 0.064742483084435 }; + MatrixType TminusI = T - MatrixType::Identity(T.rows(), T.rows()); + result.setZero(T.rows(), T.rows()); + for (int k = 0; k < degree; ++k) + result += weights[k] * (MatrixType::Identity(T.rows(), T.rows()) + nodes[k] * TminusI) + .template triangularView<Upper>().solve(TminusI); +} + +/** \ingroup MatrixFunctions_Module + * + * \brief Proxy for the matrix logarithm of some matrix (expression). + * + * \tparam Derived Type of the argument to the matrix function. + * + * This class holds the argument to the matrix function until it is + * assigned or evaluated for some other reason (so the argument + * should not be changed in the meantime). It is the return type of + * matrixBase::matrixLogarithm() and most of the time this is the + * only way it is used. + */ +template<typename Derived> class MatrixLogarithmReturnValue +: public ReturnByValue<MatrixLogarithmReturnValue<Derived> > +{ +public: + + typedef typename Derived::Scalar Scalar; + typedef typename Derived::Index Index; + + /** \brief Constructor. + * + * \param[in] A %Matrix (expression) forming the argument of the matrix logarithm. + */ + MatrixLogarithmReturnValue(const Derived& A) : m_A(A) { } + + /** \brief Compute the matrix logarithm. + * + * \param[out] result Logarithm of \p A, where \A is as specified in the constructor. + */ + template <typename ResultType> + inline void evalTo(ResultType& result) const + { + typedef typename Derived::PlainObject PlainObject; + typedef internal::traits<PlainObject> Traits; + static const int RowsAtCompileTime = Traits::RowsAtCompileTime; + static const int ColsAtCompileTime = Traits::ColsAtCompileTime; + static const int Options = PlainObject::Options; + typedef std::complex<typename NumTraits<Scalar>::Real> ComplexScalar; + typedef Matrix<ComplexScalar, Dynamic, Dynamic, Options, RowsAtCompileTime, ColsAtCompileTime> DynMatrixType; + typedef MatrixLogarithmAtomic<DynMatrixType> AtomicType; + AtomicType atomic; + + const PlainObject Aevaluated = m_A.eval(); + MatrixFunction<PlainObject, AtomicType> mf(Aevaluated, atomic); + mf.compute(result); + } + + Index rows() const { return m_A.rows(); } + Index cols() const { return m_A.cols(); } + +private: + typename internal::nested<Derived>::type m_A; + + MatrixLogarithmReturnValue& operator=(const MatrixLogarithmReturnValue&); +}; + +namespace internal { + template<typename Derived> + struct traits<MatrixLogarithmReturnValue<Derived> > + { + typedef typename Derived::PlainObject ReturnType; + }; +} + + +/********** MatrixBase method **********/ + + +template <typename Derived> +const MatrixLogarithmReturnValue<Derived> MatrixBase<Derived>::log() const +{ + eigen_assert(rows() == cols()); + return MatrixLogarithmReturnValue<Derived>(derived()); +} + +#endif // EIGEN_MATRIX_LOGARITHM diff --git a/unsupported/doc/examples/MatrixLogarithm.cpp b/unsupported/doc/examples/MatrixLogarithm.cpp new file mode 100644 index 000000000..8c5d97054 --- /dev/null +++ b/unsupported/doc/examples/MatrixLogarithm.cpp @@ -0,0 +1,15 @@ +#include <unsupported/Eigen/MatrixFunctions> +#include <iostream> + +using namespace Eigen; + +int main() +{ + using std::sqrt; + MatrixXd A(3,3); + A << 0.5*sqrt(2), -0.5*sqrt(2), 0, + 0.5*sqrt(2), 0.5*sqrt(2), 0, + 0, 0, 1; + std::cout << "The matrix A is:\n" << A << "\n\n"; + std::cout << "The matrix logarithm of A is:\n" << A.log() << "\n"; +} diff --git a/unsupported/test/matrix_function.cpp b/unsupported/test/matrix_function.cpp index 04167abfb..c2ca5d5f1 100644 --- a/unsupported/test/matrix_function.cpp +++ b/unsupported/test/matrix_function.cpp @@ -121,6 +121,26 @@ void testMatrixExponential(const MatrixType& A) } template<typename MatrixType> +void testMatrixLogarithm(const MatrixType& A) +{ + typedef typename internal::traits<MatrixType>::Scalar Scalar; + typedef typename NumTraits<Scalar>::Real RealScalar; + typedef std::complex<RealScalar> ComplexScalar; + + MatrixType scaledA; + RealScalar maxImagPartOfSpectrum = A.eigenvalues().imag().cwiseAbs().maxCoeff(); + if (maxImagPartOfSpectrum >= 0.9 * M_PI) + scaledA = A * 0.9 * M_PI / maxImagPartOfSpectrum; + else + scaledA = A; + + // identity X.exp().log() = X only holds if Im(lambda) < pi for all eigenvalues of X + MatrixType expA = scaledA.exp(); + MatrixType logExpA = expA.log(); + VERIFY_IS_APPROX(logExpA, scaledA); +} + +template<typename MatrixType> void testHyperbolicFunctions(const MatrixType& A) { // Need to use absolute error because of possible cancellation when @@ -157,6 +177,7 @@ template<typename MatrixType> void testMatrix(const MatrixType& A) { testMatrixExponential(A); + testMatrixLogarithm(A); testHyperbolicFunctions(A); testGonioFunctions(A); } |