Enable saving intermidiate (Schur decomposition) but disable unstable specialization for matrix power-matrix product.

This commit is contained in:
Chen-Pang He 2012-09-21 23:24:28 +08:00
parent d5d99dd1f0
commit 87afd99433
5 changed files with 463 additions and 555 deletions

View File

@ -211,8 +211,9 @@ documentation of \ref matrixbase_exp "exp()".
\include MatrixLogarithm.cpp
Output: \verbinclude MatrixLogarithm.out
\note \p M has to be a matrix of \c float, \c double, \c long double
\c complex<float>, \c complex<double>, or \c complex<long double> .
\note \p M has to be a matrix of \c float, \c double, <tt>long
double</tt>, \c complex<float>, \c complex<double>, or \c complex<long
double> .
\sa MatrixBase::exp(), MatrixBase::matrixFunction(),
class MatrixLogarithmAtomic, MatrixBase::sqrt().
@ -234,27 +235,14 @@ where exp denotes the matrix exponential, and log denotes the matrix
logarithm.
The matrix \f$ M \f$ should meet the conditions to be an argument of
matrix logarithm. If \p p is neither an integer nor the real scalar
type of \p M, it is casted into the real scalar type of \p M.
matrix logarithm. If \p p is not of the real scalar type of \p M, it
is casted into the real scalar type of \p M.
This function computes the matrix logarithm using the
Schur-Pad&eacute; algorithm as implemented by MatrixBase::pow().
The exponent is split into integral part and fractional part, where
the fractional part is in the interval \f$ (-1, 1) \f$. The main
diagonal and the first super-diagonal is directly computed.
The actual work is done by the MatrixPower class, which can compute
\f$ M^p v \f$, where \p v is another matrix with the same rows as
\p M. The matrix \p v is set to be the identity matrix by default.
Therefore, the expression <tt>M.pow(p) * v</tt> is specialized for
this. No temporary storage is created for the result. The code below
directly evaluates R-values into L-values without aliasing issue. Do
\b NOT try to \a optimize with noalias(). It won't compile.
\code
v = m.pow(p) * v;
m = m.pow(q);
// v2.noalias() = m.pow(p) * v1; Won't compile!
\endcode
This function computes the matrix power using the Schur-Pad&eacute;
algorithm as implemented by class MatrixPower. The exponent is split
into integral part and fractional part, where the fractional part is
in the interval \f$ (-1, 1) \f$. The main diagonal and the first
super-diagonal is directly computed.
Details of the algorithm can be found in: Nicholas J. Higham and
Lijing Lin, "A Schur-Pad&eacute; algorithm for fractional powers of a
@ -277,8 +265,18 @@ the z-axis.
\include MatrixPower.cpp
Output: \verbinclude MatrixPower.out
\note \p M has to be a matrix of \c float, \c double, \c long double
\c complex<float>, \c complex<double>, or \c complex<long double> .
MatrixBase::pow() is user-friendly. However, there are some
circumstances under which you should use class MatrixPower directly.
MatrixPower can save the result of Schur decomposition, so it's
better for computing various powers for the same matrix.
Example:
\include MatrixPower_optimal.cpp
Output: \verbinclude MatrixPower_optimal.out
\note \p M has to be a matrix of \c float, \c double, <tt>long
double</tt>, \c complex<float>, \c complex<double>, or \c complex<long
double> .
\sa MatrixBase::exp(), MatrixBase::log(), class MatrixPower.

View File

@ -10,395 +10,69 @@
#ifndef EIGEN_MATRIX_POWER
#define EIGEN_MATRIX_POWER
#ifndef M_PI
#define M_PI 3.141592653589793238462643383279503L
#endif
namespace Eigen {
/**
* \ingroup MatrixFunctions_Module
*
* \brief Class for computing matrix powers.
*
* \tparam MatrixType type of the base, expected to be an instantiation
* of the Matrix class template.
* \tparam PlainObject type of the multiplier.
*/
template<typename MatrixType, typename PlainObject = MatrixType>
class MatrixPower
namespace internal {
template<int IsComplex>
struct recompose_complex_schur
{
private:
typedef internal::traits<MatrixType> Traits;
static const int Rows = Traits::RowsAtCompileTime;
static const int Cols = Traits::ColsAtCompileTime;
static const int Options = Traits::Options;
static const int MaxRows = Traits::MaxRowsAtCompileTime;
static const int MaxCols = Traits::MaxColsAtCompileTime;
typedef typename MatrixType::Scalar Scalar;
typedef typename MatrixType::RealScalar RealScalar;
typedef std::complex<RealScalar> ComplexScalar;
typedef typename MatrixType::Index Index;
typedef Matrix<ComplexScalar, Rows, Cols, Options, MaxRows, MaxCols> ComplexMatrix;
typedef Array<ComplexScalar, Rows, 1, ColMajor, MaxRows> ComplexArray;
public:
/**
* \brief Constructor.
*
* \param[in] A the base of the matrix power.
* \param[in] p the exponent of the matrix power.
* \param[in] b the multiplier.
*/
MatrixPower(const MatrixType& A, RealScalar p, const PlainObject& b) :
m_A(A),
m_p(p),
m_b(b),
m_dimA(A.cols()),
m_dimb(b.cols())
{ /* empty body */ }
/**
* \brief Compute the matrix power.
*
* \param[out] result \f$ A^p b \f$, as specified in the constructor.
*/
template<typename ResultType> void compute(ResultType& result);
private:
/**
* \brief Compute the matrix to integral power.
*
* If \p b is \em fatter than \p A, it computes \f$ A^{p_{\textrm int}}
* \f$ first, and then multiplies it with \p b. Otherwise,
* #computeChainProduct optimizes the expression.
*
* \sa computeChainProduct(ResultType&);
*/
template<typename ResultType>
void computeIntPower(ResultType& result);
/**
* \brief Convert integral power of the matrix into chain product.
*
* This optimizes the matrix expression. It automatically chooses binary
* powering or matrix chain multiplication or solving the linear system
* repetitively, according to which algorithm costs less.
*/
template<typename ResultType>
void computeChainProduct(ResultType&);
/** \brief Compute the cost of binary powering. */
static int computeCost(RealScalar);
/** \brief Solve the linear system repetitively. */
template<typename ResultType>
void partialPivLuSolve(ResultType&, RealScalar);
/** \brief Compute Schur decomposition of #m_A. */
void computeSchurDecomposition();
/**
* \brief Split #m_p into integral part and fractional part.
*
* This method stores the integral part \f$ p_{\textrm int} \f$ into
* #m_pInt and the fractional part \f$ p_{\textrm frac} \f$ into
* #m_pFrac, where #m_pFrac is in the interval \f$ (-1,1) \f$. To
* choose between the possibilities below, it considers the computation
* of \f$ A^{p_1} \f$ and \f$ A^{p_2} \f$ and determines which of these
* computations is the better conditioned.
*/
void getFractionalExponent();
/** \brief Compute power of 2x2 triangular matrix. */
void compute2x2(RealScalar p);
/**
* \brief Compute power of triangular matrices with size > 2.
* \details This uses a Schur-Pad&eacute; algorithm.
*/
void computeBig();
/** \brief Get suitable degree for Pade approximation. (specialized for RealScalar = double) */
inline int getPadeDegree(double);
/** \brief Get suitable degree for Pade approximation. (specialized for RealScalar = float) */
inline int getPadeDegree(float);
/** \brief Get suitable degree for Pade approximation. (specialized for RealScalar = long double) */
inline int getPadeDegree(long double);
/** \brief Compute Pad&eacute; approximation to matrix fractional power. */
void computePade(const int& degree, const ComplexMatrix& IminusT);
/** \brief Get a certain coefficient of the Pad&eacute; approximation. */
inline RealScalar coeff(const int& degree);
/**
* \brief Store the fractional power into #m_tmp.
*
* This intended for complex matrices.
*/
void computeTmp(ComplexScalar);
/**
* \brief Store the fractional power into #m_tmp.
*
* This is intended for real matrices. It takes the real part of
* \f$ U T^{p_{\textrm frac}} U^H \f$.
*
* \sa computeTmp(ComplexScalar);
*/
void computeTmp(RealScalar);
const MatrixType& m_A; ///< \brief Reference to the matrix base.
const RealScalar m_p; ///< \brief The real exponent.
const PlainObject& m_b; ///< \brief Reference to the multiplier.
const Index m_dimA; ///< \brief The dimension of #m_A, equivalent to %m_A.cols().
const Index m_dimb; ///< \brief The dimension of #m_b, equivalent to %m_b.cols().
MatrixType m_tmp; ///< \brief Used for temporary storage.
RealScalar m_pInt; ///< \brief Integral part of #m_p.
RealScalar m_pFrac; ///< \brief Fractional part of #m_p.
ComplexMatrix m_T; ///< \brief Triangular part of Schur decomposition.
ComplexMatrix m_U; ///< \brief Unitary part of Schur decomposition.
ComplexMatrix m_fT; ///< \brief #m_T to the power of #m_pFrac.
ComplexArray m_logTdiag; ///< \brief Logarithm of the main diagonal of #m_T.
template<typename ResultType, typename MatrixType>
static inline void run(ResultType& res, const MatrixType& T, const MatrixType& U)
{ res = U * (T.template triangularView<Upper>() * U.adjoint()); }
};
template<typename MatrixType, typename PlainObject>
template<typename ResultType>
void MatrixPower<MatrixType,PlainObject>::compute(ResultType& result)
template<>
struct recompose_complex_schur<0>
{
using std::floor;
using std::pow;
template<typename ResultType, typename MatrixType>
static inline void run(ResultType& res, const MatrixType& T, const MatrixType& U)
{ res = (U * (T.template triangularView<Upper>() * U.adjoint())).real(); }
};
m_pInt = floor(m_p + RealScalar(0.5));
m_pFrac = m_p - m_pInt;
if (!m_pFrac) {
computeIntPower(result);
} else if (m_dimA == 1)
result = pow(m_A(0,0), m_p) * m_b;
else {
computeSchurDecomposition();
getFractionalExponent();
computeIntPower(result);
if (m_dimA == 2)
compute2x2(m_pFrac);
else
computeBig();
computeTmp(Scalar());
result = m_tmp * result;
}
}
template<typename MatrixType, typename PlainObject>
template<typename ResultType>
void MatrixPower<MatrixType,PlainObject>::computeIntPower(ResultType& result)
template<typename T>
inline int binary_powering_cost(T p)
{
MatrixType tmp;
if (m_dimb > m_dimA) {
tmp = MatrixType::Identity(m_dimA, m_dimA);
computeChainProduct(tmp);
result.noalias() = tmp * m_b;
} else {
result = m_b;
computeChainProduct(result);
}
}
template<typename MatrixType, typename PlainObject>
template<typename ResultType>
void MatrixPower<MatrixType,PlainObject>::computeChainProduct(ResultType& result)
{
using std::abs;
using std::fmod;
using std::ldexp;
RealScalar p = abs(m_pInt);
int cost = computeCost(p);
if (m_pInt < RealScalar(0)) {
if (p * m_dimb <= cost * m_dimA && m_dimA > 2) {
partialPivLuSolve(result, p);
return;
} else {
m_tmp = m_A.inverse();
}
} else {
m_tmp = m_A;
}
while (p * m_dimb > cost * m_dimA) {
if (fmod(p, RealScalar(2)) >= RealScalar(1)) {
result = m_tmp * result;
cost--;
}
m_tmp *= m_tmp;
cost--;
p = ldexp(p, -1);
}
for (; p >= RealScalar(1); p--)
result = m_tmp * result;
}
template<typename MatrixType, typename PlainObject>
int MatrixPower<MatrixType,PlainObject>::computeCost(RealScalar p)
{
using std::frexp;
using std::ldexp;
int cost, tmp;
frexp(p, &cost);
while (frexp(p, &tmp), tmp > 0) {
p -= ldexp(RealScalar(0.5), tmp);
cost++;
while (std::frexp(p, &tmp), tmp > 0) {
p -= std::ldexp(static_cast<T>(0.5), tmp);
++cost;
}
return cost;
}
template<typename MatrixType, typename PlainObject>
template<typename ResultType>
void MatrixPower<MatrixType,PlainObject>::partialPivLuSolve(ResultType& result, RealScalar p)
{
const PartialPivLU<MatrixType> Asolver(m_A);
for (; p >= RealScalar(1); p--)
result = Asolver.solve(result);
}
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::computeSchurDecomposition()
{
const ComplexSchur<MatrixType> schurOfA(m_A);
m_T = schurOfA.matrixT();
m_U = schurOfA.matrixU();
}
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::getFractionalExponent()
{
using std::pow;
typedef Array<RealScalar, Rows, 1, ColMajor, MaxRows> RealArray;
const ComplexArray Tdiag = m_T.diagonal();
const RealArray absTdiag = Tdiag.abs();
const RealScalar maxAbsEival = absTdiag.maxCoeff();
const RealScalar minAbsEival = absTdiag.minCoeff();
m_logTdiag = Tdiag.log();
if (m_pFrac > RealScalar(0.5) && // This is just a shortcut.
m_pFrac > (RealScalar(1) - m_pFrac) * pow(maxAbsEival/minAbsEival, m_pFrac)) {
m_pFrac--;
m_pInt++;
}
}
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::compute2x2(RealScalar p)
{
using std::abs;
using std::ceil;
using std::exp;
using std::imag;
using std::ldexp;
using std::pow;
using std::sinh;
int i, j, unwindingNumber;
ComplexScalar w;
m_fT(0,0) = pow(m_T(0,0), p);
for (j = 1; j < m_dimA; j++) {
i = j - 1;
m_fT(j,j) = pow(m_T(j,j), p);
if (m_T(i,i) == m_T(j,j)) {
m_fT(i,j) = p * pow(m_T(i,j), p - RealScalar(1));
} else if (abs(m_T(i,i)) < ldexp(abs(m_T(j,j)), -1) || abs(m_T(j,j)) < ldexp(abs(m_T(i,i)), -1)) {
m_fT(i,j) = m_T(i,j) * (m_fT(j,j) - m_fT(i,i)) / (m_T(j,j) - m_T(i,i));
} else {
// computation in previous branch is inaccurate if abs(m_T(j,j)) \approx abs(m_T(i,i))
unwindingNumber = ceil((imag(m_logTdiag[j] - m_logTdiag[i]) - M_PI) / (2 * M_PI));
w = internal::atanh2(m_T(j,j) - m_T(i,i), m_T(j,j) + m_T(i,i)) + ComplexScalar(0, M_PI * unwindingNumber);
m_fT(i,j) = m_T(i,j) * RealScalar(2) * exp(RealScalar(0.5) * p * (m_logTdiag[j] + m_logTdiag[i])) *
sinh(p * w) / (m_T(j,j) - m_T(i,i));
}
}
}
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::computeBig()
{
using std::ldexp;
const int digits = std::numeric_limits<RealScalar>::digits;
const RealScalar maxNormForPade = digits <= 24? 4.3386528e-1f: // sigle precision
digits <= 53? 2.789358995219730e-1: // double precision
digits <= 64? 2.4471944416607995472e-1L: // extended precision
digits <= 106? 1.1016843812851143391275867258512e-01: // double-double
9.134603732914548552537150753385375e-02; // quadruple precision
int degree, degree2, numberOfSquareRoots = 0, numberOfExtraSquareRoots = 0;
ComplexMatrix IminusT, sqrtT, T = m_T;
RealScalar normIminusT;
while (true) {
IminusT = ComplexMatrix::Identity(m_dimA, m_dimA) - T;
normIminusT = IminusT.cwiseAbs().colwise().sum().maxCoeff();
if (normIminusT < maxNormForPade) {
degree = getPadeDegree(normIminusT);
degree2 = getPadeDegree(normIminusT * RealScalar(0.5));
if (degree - degree2 <= 1 || numberOfExtraSquareRoots)
break;
numberOfExtraSquareRoots++;
}
MatrixSquareRootTriangular<ComplexMatrix>(T).compute(sqrtT);
T = sqrtT;
numberOfSquareRoots++;
}
computePade(degree, IminusT);
for (; numberOfSquareRoots; numberOfSquareRoots--) {
compute2x2(ldexp(m_pFrac, -numberOfSquareRoots));
m_fT *= m_fT;
}
compute2x2(m_pFrac);
}
template<typename MatrixType, typename PlainObject>
inline int MatrixPower<MatrixType,PlainObject>::getPadeDegree(float normIminusT)
inline int matrix_power_get_pade_degree(float normIminusT)
{
const float maxNormForPade[] = { 2.8064004e-1f /* degree = 3 */ , 4.3386528e-1f };
int degree = 3;
for (; degree <= 4; degree++)
for (; degree <= 4; ++degree)
if (normIminusT <= maxNormForPade[degree - 3])
break;
return degree;
}
template<typename MatrixType, typename PlainObject>
inline int MatrixPower<MatrixType,PlainObject>::getPadeDegree(double normIminusT)
inline int matrix_power_get_pade_degree(double normIminusT)
{
const double maxNormForPade[] = { 1.884160592658218e-2 /* degree = 3 */ , 6.038881904059573e-2,
1.239917516308172e-1, 1.999045567181744e-1, 2.789358995219730e-1 };
const double maxNormForPade[] = { 1.884160592658218e-2 /* degree = 3 */ , 6.038881904059573e-2, 1.239917516308172e-1,
1.999045567181744e-1, 2.789358995219730e-1 };
int degree = 3;
for (; degree <= 7; degree++)
for (; degree <= 7; ++degree)
if (normIminusT <= maxNormForPade[degree - 3])
break;
return degree;
}
template<typename MatrixType, typename PlainObject>
inline int MatrixPower<MatrixType,PlainObject>::getPadeDegree(long double normIminusT)
inline int matrix_power_get_pade_degree(long double normIminusT)
{
#if LDBL_MANT_DIG == 53
#if LDBL_MANT_DIG == 53
const int maxPadeDegree = 7;
const double maxNormForPade[] = { 1.884160592658218e-2L /* degree = 3 */ , 6.038881904059573e-2L,
1.239917516308172e-1L, 1.999045567181744e-1L, 2.789358995219730e-1L };
const double maxNormForPade[] = { 1.884160592658218e-2L /* degree = 3 */ , 6.038881904059573e-2L, 1.239917516308172e-1L,
1.999045567181744e-1L, 2.789358995219730e-1L };
#elif LDBL_MANT_DIG <= 64
const int maxPadeDegree = 8;
const double maxNormForPade[] = { 6.3854693117491799460e-3L /* degree = 3 */ , 2.6394893435456973676e-2L,
6.4216043030404063729e-2L, 1.1701165502926694307e-1L, 1.7904284231268670284e-1L, 2.4471944416607995472e-1L };
#elif LDBL_MANT_DIG <= 106
const int maxPadeDegree = 10;
const double maxNormForPade[] = { 1.0007161601787493236741409687186e-4L /* degree = 3 */ ,
@ -414,101 +88,369 @@ inline int MatrixPower<MatrixType,PlainObject>::getPadeDegree(long double normIm
9.134603732914548552537150753385375e-2L };
#endif
int degree = 3;
for (; degree <= maxPadeDegree; degree++)
for (; degree <= maxPadeDegree; ++degree)
if (normIminusT <= maxNormForPade[degree - 3])
break;
return degree;
}
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::computePade(const int& degree, const ComplexMatrix& IminusT)
} // namespace internal
/* (non-doc)
* \brief Class for computing triangular matrices to fractional power.
*
* \tparam MatrixType type of the base.
*/
template<typename MatrixType, int UpLo = Upper> class MatrixPowerTriangularAtomic
{
int i = degree << 1;
m_fT = coeff(i) * IminusT;
for (i--; i; i--) {
m_fT = (ComplexMatrix::Identity(m_dimA, m_dimA) + m_fT).template triangularView<Upper>()
.solve(coeff(i) * IminusT).eval();
private:
typedef typename MatrixType::Scalar Scalar;
typedef typename MatrixType::RealScalar RealScalar;
typedef Array<Scalar,
EIGEN_SIZE_MIN_PREFER_FIXED(MatrixType::RowsAtCompileTime,MatrixType::ColsAtCompileTime),
1,ColMajor,
EIGEN_SIZE_MIN_PREFER_FIXED(MatrixType::MaxRowsAtCompileTime,MatrixType::MaxColsAtCompileTime)> ArrayType;
const MatrixType& m_T;
void computePade(int degree, const MatrixType& IminusT, MatrixType& res, RealScalar p) const;
void compute2x2(MatrixType& res, RealScalar p) const;
void computeBig(MatrixType& res, RealScalar p) const;
public:
explicit MatrixPowerTriangularAtomic(const MatrixType& T);
void compute(MatrixType& res, RealScalar p) const;
};
template<typename MatrixType, int UpLo>
MatrixPowerTriangularAtomic<MatrixType,UpLo>::MatrixPowerTriangularAtomic(const MatrixType& T) :
m_T(T)
{ eigen_assert(T.rows() == T.cols()); }
template<typename MatrixType, int UpLo>
void MatrixPowerTriangularAtomic<MatrixType,UpLo>::compute(MatrixType& res, RealScalar p) const
{
switch (m_T.rows()) {
case 0:
break;
case 1:
res(0,0) = std::pow(m_T(0,0), p);
break;
case 2:
compute2x2(res, p);
break;
default:
computeBig(res, p);
}
m_fT += ComplexMatrix::Identity(m_dimA, m_dimA);
}
template<typename MatrixType, typename PlainObject>
inline typename MatrixType::RealScalar MatrixPower<MatrixType,PlainObject>::coeff(const int& i)
template<typename MatrixType, int UpLo>
void MatrixPowerTriangularAtomic<MatrixType,UpLo>::computePade(int degree, const MatrixType& IminusT, MatrixType& res,
RealScalar p) const
{
if (i == 1)
return -m_pFrac;
else if (i & 1)
return (-m_pFrac - RealScalar(i >> 1)) / RealScalar(i << 1);
else
return (m_pFrac - RealScalar(i >> 1)) / RealScalar((i - 1) << 1);
int i = degree<<1;
res = (p-(i>>1)) / ((i-1)<<1) * IminusT;
for (--i; i; --i) {
res = (MatrixType::Identity(m_T.rows(), m_T.cols()) + res).template triangularView<UpLo>()
.solve((i==1 ? -p : i&1 ? (-p-(i>>1))/(i<<1) : (p-(i>>1))/((i-1)<<1)) * IminusT).eval();
}
res += MatrixType::Identity(m_T.rows(), m_T.cols());
}
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::computeTmp(RealScalar)
{ m_tmp = (m_U * m_fT * m_U.adjoint()).real(); }
template<typename MatrixType, int UpLo>
void MatrixPowerTriangularAtomic<MatrixType,UpLo>::compute2x2(MatrixType& res, RealScalar p) const
{
using std::abs;
using std::pow;
ArrayType logTdiag = m_T.diagonal().array().log();
res(0,0) = pow(m_T(0,0), p);
template<typename MatrixType, typename PlainObject>
void MatrixPower<MatrixType,PlainObject>::computeTmp(ComplexScalar)
{ m_tmp = m_U * m_fT * m_U.adjoint(); }
for (int i=1; i < m_T.cols(); ++i) {
res(i,i) = pow(m_T(i,i), p);
if (m_T(i-1,i-1) == m_T(i,i)) {
res(i-1,i) = p * pow(m_T(i-1,i), p-1);
} else if (2*abs(m_T(i-1,i-1)) < abs(m_T(i,i)) || 2*abs(m_T(i,i)) < abs(m_T(i-1,i-1))) {
res(i-1,i) = m_T(i-1,i) * (res(i,i)-res(i-1,i-1)) / (m_T(i,i)-m_T(i-1,i-1));
} else {
// computation in previous branch is inaccurate if abs(m_T(i,i)) \approx abs(m_T(i-1,i-1))
int unwindingNumber = std::ceil(((logTdiag[i]-logTdiag[i-1]).imag() - M_PI) / (2*M_PI));
Scalar w = internal::atanh2(m_T(i,i)-m_T(i-1,i-1), m_T(i,i)+m_T(i-1,i-1)) + Scalar(0, M_PI*unwindingNumber);
res(i-1,i) = m_T(i-1,i) * RealScalar(2) * std::exp(RealScalar(0.5) * p * (logTdiag[i]+logTdiag[i-1])) *
std::sinh(p * w) / (m_T(i,i) - m_T(i-1,i-1));
}
}
}
template<typename MatrixType, int UpLo>
void MatrixPowerTriangularAtomic<MatrixType,UpLo>::computeBig(MatrixType& res, RealScalar p) const
{
const int digits = std::numeric_limits<RealScalar>::digits;
const RealScalar maxNormForPade = digits <= 24? 4.3386528e-1f: // sigle precision
digits <= 53? 2.789358995219730e-1: // double precision
digits <= 64? 2.4471944416607995472e-1L: // extended precision
digits <= 106? 1.1016843812851143391275867258512e-01: // double-double
9.134603732914548552537150753385375e-02; // quadruple precision
int degree, degree2, numberOfSquareRoots=0, numberOfExtraSquareRoots=0;
MatrixType IminusT, sqrtT, T=m_T;
RealScalar normIminusT;
while (true) {
IminusT = MatrixType::Identity(m_T.rows(), m_T.cols()) - T;
normIminusT = IminusT.cwiseAbs().colwise().sum().maxCoeff();
if (normIminusT < maxNormForPade) {
degree = internal::matrix_power_get_pade_degree(normIminusT);
degree2 = internal::matrix_power_get_pade_degree(normIminusT/2);
if (degree - degree2 <= 1 || numberOfExtraSquareRoots)
break;
++numberOfExtraSquareRoots;
}
MatrixSquareRootTriangular<MatrixType>(T).compute(sqrtT);
T = sqrtT;
++numberOfSquareRoots;
}
computePade(degree, IminusT, res, p);
for (; numberOfSquareRoots; --numberOfSquareRoots) {
compute2x2(res, std::ldexp(p,-numberOfSquareRoots));
res *= res;
}
compute2x2(res, p);
}
/**
* \ingroup MatrixFunctions_Module
*
* \brief Proxy for the matrix power multiplied by other matrix.
* \brief Class for computing matrix powers.
*
* \tparam Derived type of the base, a matrix (expression).
* \tparam RhsDerived type of the multiplier.
* \tparam MatrixType type of the base, expected to be an instantiation
* of the Matrix class template.
*
* This class holds the arguments to the matrix power 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
* MatrixPowerReturnValue::operator*() and related functions and most
* of the time this is the only way it is used.
* This class is capable of computing real/complex matrices raised to
* an arbitrary real power. Meanwhile, it saves the result of Schur
* decomposition if an non-integral power has even been calculated.
* Therefore, if you want to compute multiple (>= 2) matrix powers
* for the same matrix, using the class directly is more efficient than
* calling MatrixBase::pow().
*
* Example:
* \include MatrixPower_optimal.cpp
* Output: \verbinclude MatrixPower_optimal.out
*/
template<typename Derived, typename RhsDerived>
class MatrixPowerProductReturnValue : public ReturnByValue<MatrixPowerProductReturnValue<Derived,RhsDerived> >
template<typename MatrixType> class MatrixPower
{
private:
typedef typename Derived::PlainObject MatrixType;
typedef typename RhsDerived::PlainObject PlainObject;
typedef typename RhsDerived::RealScalar RealScalar;
typedef typename RhsDerived::Index Index;
static const int Rows = MatrixType::RowsAtCompileTime;
static const int Cols = MatrixType::ColsAtCompileTime;
static const int Options = MatrixType::Options;
static const int MaxRows = MatrixType::MaxRowsAtCompileTime;
static const int MaxCols = MatrixType::MaxColsAtCompileTime;
typedef typename MatrixType::Scalar Scalar;
typedef typename MatrixType::RealScalar RealScalar;
typedef typename MatrixType::Index Index;
typedef Matrix<std::complex<RealScalar>,Rows,Cols,Options,MaxRows,MaxCols> ComplexMatrix;
const MatrixType& m_A;
MatrixType m_tmp1, m_tmp2;
ComplexMatrix m_T, m_U, m_fT;
bool m_init;
RealScalar modfAndInit(RealScalar, RealScalar*);
template<typename PlainObject, typename ResultType>
void apply(const PlainObject&, ResultType&, bool&);
template<typename ResultType>
void computeIntPower(ResultType&, RealScalar);
template<typename PlainObject, typename ResultType>
void computeIntPower(const PlainObject&, ResultType&, RealScalar);
template<typename ResultType>
void computeFracPower(ResultType&, RealScalar);
public:
/**
* \brief Constructor.
*
* \param[in] A %Matrix (expression), the base of the matrix power.
* \param[in] p scalar, the exponent of the matrix power.
* \prarm[in] b %Matrix (expression), the multiplier.
* \param[in] A the base of the matrix power.
*/
MatrixPowerProductReturnValue(const Derived& A, RealScalar p, const RhsDerived& b)
: m_A(A), m_p(p), m_b(b) { }
explicit MatrixPower(const MatrixType& A);
/**
* \brief Compute the expression.
* \brief Return the expression \f$ A^p \f$.
*
* \param[out] result \f$ A^p b \f$ where \p A, \p p and \p bare as
* in the constructor.
* \param[in] p exponent, a real scalar.
*/
template<typename ResultType>
inline void evalTo(ResultType& result) const
{
const MatrixType A = m_A;
const PlainObject b = m_b;
MatrixPower<MatrixType, PlainObject> mp(A, m_p, b);
mp.compute(result);
}
const MatrixPowerReturnValue<MatrixPower<MatrixType> > operator()(RealScalar p)
{ return MatrixPowerReturnValue<MatrixPower<MatrixType> >(*this, p); }
Index rows() const { return m_b.rows(); }
Index cols() const { return m_b.cols(); }
/**
* \brief Compute the matrix power.
*
* \param[in] p exponent, a real scalar.
* \param[out] res \f$ A^p \f$ where A is specified in the
* constructor.
*/
void compute(MatrixType& res, RealScalar p);
private:
const Derived& m_A;
const RealScalar m_p;
const RhsDerived& m_b;
MatrixPowerProductReturnValue& operator=(const MatrixPowerProductReturnValue&);
/**
* \brief Compute the matrix power multiplied by another matrix.
*
* \param[in] b a matrix with the same rows as A.
* \param[in] p exponent, a real scalar.
* \param[in] noalias
* \param[out] res \f$ A^p b \f$, where A is specified in the
* constructor.
*/
template<typename PlainObject, typename ResultType>
void compute(const PlainObject& b, ResultType& res, RealScalar p);
Index rows() const { return m_A.rows(); }
Index cols() const { return m_A.cols(); }
};
template<typename MatrixType>
MatrixPower<MatrixType>::MatrixPower(const MatrixType& A) :
m_A(A),
m_init(false)
{ /* empty body */ }
template<typename MatrixType>
void MatrixPower<MatrixType>::compute(MatrixType& res, RealScalar p)
{
switch (m_A.cols()) {
case 0:
break;
case 1:
res(0,0) = std::pow(m_A(0,0), p);
break;
default:
RealScalar intpart;
RealScalar x = modfAndInit(p, &intpart);
res = MatrixType::Identity(m_A.rows(),m_A.cols());
computeIntPower(res, intpart);
computeFracPower(res, x);
}
}
template<typename MatrixType>
template<typename PlainObject, typename ResultType>
void MatrixPower<MatrixType>::compute(const PlainObject& b, ResultType& res, RealScalar p)
{
switch (m_A.cols()) {
case 0:
break;
case 1:
res = std::pow(m_A(0,0), p) * b;
break;
default:
RealScalar intpart;
RealScalar x = modfAndInit(p, &intpart);
computeIntPower(b, res, intpart);
computeFracPower(res, x);
}
}
template<typename MatrixType>
typename MatrixType::RealScalar MatrixPower<MatrixType>::modfAndInit(RealScalar x, RealScalar* intpart)
{
static RealScalar maxAbsEival, minAbsEival;
*intpart = std::floor(x);
RealScalar res = x - *intpart;
if (!m_init && res) { // !init && res
const ComplexSchur<MatrixType> schurOfA(m_A);
m_T = schurOfA.matrixT();
m_U = schurOfA.matrixU();
m_init = true;
const Array<RealScalar,EIGEN_SIZE_MIN_PREFER_FIXED(Rows,Cols),1,ColMajor,EIGEN_SIZE_MIN_PREFER_FIXED(MaxRows,MaxCols)>
absTdiag = m_T.diagonal().array().abs();
maxAbsEival = absTdiag.maxCoeff();
minAbsEival = absTdiag.minCoeff();
}
if (res > RealScalar(0.5) && res > (1-res) * std::pow(maxAbsEival/minAbsEival, res)) {
--res;
++*intpart;
}
return res;
}
template<typename MatrixType>
template<typename PlainObject, typename ResultType>
void MatrixPower<MatrixType>::apply(const PlainObject& b, ResultType& res, bool& init)
{
if (init)
res = m_tmp1 * res;
else {
init = true;
res.noalias() = m_tmp1 * b;
}
}
template<typename MatrixType>
template<typename ResultType>
void MatrixPower<MatrixType>::computeIntPower(ResultType& res, RealScalar p)
{
RealScalar pp = std::abs(p);
if (p<0) m_tmp1 = m_A.inverse();
else m_tmp1 = m_A;
while (pp >= 1) {
if (std::fmod(pp, 2) >= 1)
res = m_tmp1 * res;
m_tmp1 *= m_tmp1;
pp /= 2;
}
}
template<typename MatrixType>
template<typename PlainObject, typename ResultType>
void MatrixPower<MatrixType>::computeIntPower(const PlainObject& b, ResultType& res, RealScalar p)
{
if (b.cols() > m_A.cols()) {
m_tmp2 = MatrixType::Identity(m_A.rows(),m_A.cols());
computeIntPower(m_tmp2, p);
res.noalias() = m_tmp2 * b;
} else {
RealScalar pp = std::abs(p);
int cost = internal::binary_powering_cost(pp);
bool init = false;
if (p==0) {
res = b;
return;
}
if (p<0) m_tmp1 = m_A.inverse();
else m_tmp1 = m_A;
while (b.cols()*pp > m_A.cols()*cost) {
if (std::fmod(pp, 2) >= 1) {
apply(b, res, init);
--cost;
}
m_tmp1 *= m_tmp1;
--cost;
pp /= 2;
}
for (; pp >= 1; --pp)
apply(b, res, init);
}
}
template<typename MatrixType>
template<typename ResultType>
void MatrixPower<MatrixType>::computeFracPower(ResultType& res, RealScalar p)
{
if (p) {
MatrixPowerTriangularAtomic<ComplexMatrix>(m_T).compute(m_fT, p);
internal::recompose_complex_schur<NumTraits<Scalar>::IsComplex>::run(m_tmp1, m_fT, m_U);
res = m_tmp1 * res;
}
}
/**
* \ingroup MatrixFunctions_Module
*
@ -525,11 +467,10 @@ class MatrixPowerProductReturnValue : public ReturnByValue<MatrixPowerProductRet
template<typename Derived>
class MatrixPowerReturnValue : public ReturnByValue<MatrixPowerReturnValue<Derived> >
{
private:
public:
typedef typename Derived::RealScalar RealScalar;
typedef typename Derived::Index Index;
public:
/**
* \brief Constructor.
*
@ -539,26 +480,6 @@ class MatrixPowerReturnValue : public ReturnByValue<MatrixPowerReturnValue<Deriv
MatrixPowerReturnValue(const Derived& A, RealScalar p)
: m_A(A), m_p(p) { }
/**
* \brief Return the matrix power multiplied by %Matrix \p b.
*
* The %MatrixPower class can optimize \f$ A^p b \f$ computing, and
* this method provides an elegant way to call it.
*
* Unlike general matrix-matrix / matrix-vector product, this does
* \b NOT produce a temporary storage for the result. Therefore,
* the code below is \a already optimal:
* \code
* v = A.pow(p) * b;
* // v.noalias() = A.pow(p) * b; Won't compile!
* \endcode
*
* \param[in] b %Matrix (expression), the multiplier.
*/
template<typename RhsDerived>
const MatrixPowerProductReturnValue<Derived,RhsDerived> operator*(const MatrixBase<RhsDerived>& b) const
{ return MatrixPowerProductReturnValue<Derived,RhsDerived>(m_A, m_p, b.derived()); }
/**
* \brief Compute the matrix power.
*
@ -566,14 +487,8 @@ class MatrixPowerReturnValue : public ReturnByValue<MatrixPowerReturnValue<Deriv
* constructor.
*/
template<typename ResultType>
inline void evalTo(ResultType& result) const
{
typedef typename Derived::PlainObject PlainObject;
const PlainObject A = m_A;
const PlainObject Identity = PlainObject::Identity(m_A.rows(), m_A.cols());
MatrixPower<PlainObject> mp(A, m_p, Identity);
mp.compute(result);
}
inline void evalTo(ResultType& res) const
{ MatrixPower<typename Derived::PlainObject>(m_A).compute(res, m_p); }
Index rows() const { return m_A.rows(); }
Index cols() const { return m_A.cols(); }
@ -584,18 +499,42 @@ class MatrixPowerReturnValue : public ReturnByValue<MatrixPowerReturnValue<Deriv
MatrixPowerReturnValue& operator=(const MatrixPowerReturnValue&);
};
namespace internal {
template<typename Derived>
struct traits<MatrixPowerReturnValue<Derived> >
{
typedef typename Derived::PlainObject ReturnType;
};
template<typename MatrixType>
class MatrixPowerReturnValue<MatrixPower<MatrixType> >
: public ReturnByValue<MatrixPowerReturnValue<MatrixPower<MatrixType> > >
{
public:
typedef typename MatrixType::RealScalar RealScalar;
typedef typename MatrixType::Index Index;
template<typename Derived, typename RhsDerived>
struct traits<MatrixPowerProductReturnValue<Derived,RhsDerived> >
{
typedef typename RhsDerived::PlainObject ReturnType;
};
MatrixPowerReturnValue(MatrixPower<MatrixType>& ref, RealScalar p)
: m_pow(ref), m_p(p) { }
template<typename ResultType>
inline void evalTo(ResultType& res) const
{ m_pow.compute(res, m_p); }
Index rows() const { return m_pow.rows(); }
Index cols() const { return m_pow.cols(); }
private:
MatrixPower<MatrixType>& m_pow;
const RealScalar m_p;
MatrixPowerReturnValue& operator=(const MatrixPowerReturnValue&);
};
namespace internal {
template<typename Derived>
struct traits<MatrixPowerReturnValue<Derived> >
{ typedef typename Derived::PlainObject ReturnType; };
template<typename MatrixType>
struct traits<MatrixPowerReturnValue<MatrixPower<MatrixType> > >
{ typedef MatrixType ReturnType; };
template<typename Derived>
struct traits<MatrixPowerProductBase<Derived> >
{ typedef typename traits<Derived>::ReturnType ReturnType; };
}
template<typename Derived>

View File

@ -11,6 +11,6 @@ int main()
sin(1), cos(1), 0,
0 , 0 , 1;
std::cout << "The matrix A is:\n" << A << "\n\n"
<< "The matrix power A^(pi/4) is:\n" << A.pow(pi/4) << std::endl;
"The matrix power A^(pi/4) is:\n" << A.pow(pi/4) << std::endl;
return 0;
}

View File

@ -0,0 +1,17 @@
#include <unsupported/Eigen/MatrixFunctions>
#include <iostream>
using namespace Eigen;
int main()
{
Matrix4cd A = Matrix4cd::Random();
MatrixPower<Matrix4cd> Apow(A);
std::cout << "The matrix A is:\n" << A << "\n\n"
"A^3.1 is:\n" << Apow(3.1) << "\n\n"
"A^3.3 is:\n" << Apow(3.3) << "\n\n"
"A^3.7 is:\n" << Apow(3.7) << "\n\n"
"A^3.9 is:\n" << Apow(3.9) << std::endl;
return 0;
}

View File

@ -16,15 +16,17 @@ void test2dRotation(double tol)
T angle, c, s;
A << 0, 1, -1, 0;
for (int i = 0; i <= 20; i++) {
MatrixPower<Matrix<T,2,2> > Apow(A);
for (int i=0; i<=20; ++i) {
angle = pow(10, (i-10) / 5.);
c = std::cos(angle);
s = std::sin(angle);
B << c, s, -s, c;
C = A.pow(std::ldexp(angle, 1) / M_PI);
std::cout << "test2dRotation: i = " << i << " error powerm = " << relerr(C, B) << '\n';
VERIFY(C.isApprox(B, T(tol)));
C = Apow(std::ldexp(angle,1) / M_PI);
std::cout << "test2dRotation: i = " << i << " error powerm = " << relerr(C,B) << '\n';
VERIFY(C.isApprox(B, static_cast<T>(tol)));
}
}
@ -36,15 +38,17 @@ void test2dHyperbolicRotation(double tol)
std::complex<T> ish(0, std::sinh(1));
A << ch, ish, -ish, ch;
for (int i = 0; i <= 20; i++) {
angle = std::ldexp(T(i-10), -1);
MatrixPower<Matrix<std::complex<T>,2,2> > Apow(A);
for (int i=0; i<=20; ++i) {
angle = std::ldexp(static_cast<T>(i-10), -1);
ch = std::cosh(angle);
ish = std::complex<T>(0, std::sinh(angle));
B << ch, ish, -ish, ch;
C = A.pow(angle);
std::cout << "test2dHyperbolicRotation: i = " << i << " error powerm = " << relerr(C, B) << '\n';
VERIFY(C.isApprox(B, T(tol)));
C = Apow(angle);
std::cout << "test2dHyperbolicRotation: i = " << i << " error powerm = " << relerr(C,B) << '\n';
VERIFY(C.isApprox(B, static_cast<T>(tol)));
}
}
@ -55,71 +59,37 @@ void testExponentLaws(const MatrixType& m, double tol)
MatrixType m1, m2, m3, m4, m5;
RealScalar x, y;
for (int i = 0; i < g_repeat; i++) {
for (int i=0; i<g_repeat; ++i) {
generateTestMatrix<MatrixType>::run(m1, m.rows());
MatrixPower<MatrixType> mpow(m1);
x = internal::random<RealScalar>();
y = internal::random<RealScalar>();
m2 = m1.pow(x);
m3 = m1.pow(y);
m2 = mpow(x);
m3 = mpow(y);
m4 = m1.pow(x + y);
m4 = mpow(x+y);
m5.noalias() = m2 * m3;
std::cout << "testExponentLaws: error powerm = " << relerr(m4, m5);
VERIFY(m4.isApprox(m5, RealScalar(tol)));
if (!NumTraits<typename MatrixType::Scalar>::IsComplex) {
m4 = m1.pow(x * y);
m5 = m2.pow(y);
std::cout << " " << relerr(m4, m5);
VERIFY(m4.isApprox(m5, RealScalar(tol)));
}
std::cout << "testExponentLaws: error powerm = " << relerr(m4, m5);
VERIFY(m4.isApprox(m5, static_cast<RealScalar>(tol)));
m4 = mpow(x*y);
m5 = m2.pow(y);
std::cout << " " << relerr(m4, m5);
VERIFY(m4.isApprox(m5, static_cast<RealScalar>(tol)));
m4 = (std::abs(x) * m1).pow(y);
m5 = std::pow(std::abs(x), y) * m3;
std::cout << " " << relerr(m4, m5) << '\n';
VERIFY(m4.isApprox(m5, RealScalar(tol)));
}
}
template<typename MatrixType, typename VectorType>
void testMatrixVectorProduct(const MatrixType& m, const VectorType& v, double tol)
{
typedef typename MatrixType::RealScalar RealScalar;
MatrixType m1;
VectorType v1, v2, v3;
RealScalar p;
for (int i = 0; i < g_repeat; i++) {
generateTestMatrix<MatrixType>::run(m1, m.rows());
v1 = VectorType::Random(v.rows(), v.cols());
p = internal::random<RealScalar>();
v2.noalias() = m1.pow(p).eval() * v1;
v1 = m1.pow(p) * v1;
std::cout << "testMatrixVectorProduct: error powerm = " << relerr(v2, v1) << '\n';
VERIFY(v2.isApprox(v1, RealScalar(tol)));
}
}
template<typename MatrixType>
void testAliasing(const MatrixType& m)
{
typedef typename MatrixType::RealScalar RealScalar;
MatrixType m1, m2;
RealScalar p;
for (int i = 0; i < g_repeat; i++) {
generateTestMatrix<MatrixType>::run(m1, m.rows());
p = internal::random<RealScalar>();
m2 = m1.pow(p);
m1 = m1.pow(p);
VERIFY(m1 == m2);
VERIFY(m4.isApprox(m5, static_cast<RealScalar>(tol)));
}
}
void test_matrix_power()
{
typedef Matrix<long double,Dynamic,Dynamic> MatrixXe;
CALL_SUBTEST_2(test2dRotation<double>(1e-13));
CALL_SUBTEST_1(test2dRotation<float>(2e-5)); // was 1e-5, relaxed for clang 2.8 / linux / x86-64
CALL_SUBTEST_9(test2dRotation<long double>(1e-13));
@ -135,20 +105,4 @@ void test_matrix_power()
CALL_SUBTEST_5(testExponentLaws(Matrix3cf(), 1e-4));
CALL_SUBTEST_8(testExponentLaws(Matrix4f(), 1e-4));
CALL_SUBTEST_6(testExponentLaws(MatrixXf(8,8), 1e-4));
CALL_SUBTEST_2(testMatrixVectorProduct(Matrix2d(), Vector2d(), 1e-13));
CALL_SUBTEST_7(testMatrixVectorProduct(Matrix<double,3,3,RowMajor>(), Vector3d(), 1e-13));
CALL_SUBTEST_3(testMatrixVectorProduct(Matrix4cd(), Vector4cd(), 1e-13));
CALL_SUBTEST_4(testMatrixVectorProduct(MatrixXd(8,8), MatrixXd(8,2), 1e-13));
CALL_SUBTEST_1(testMatrixVectorProduct(Matrix2f(), Vector2f(), 1e-4));
CALL_SUBTEST_5(testMatrixVectorProduct(Matrix3cf(), Vector3cf(), 1e-4));
CALL_SUBTEST_8(testMatrixVectorProduct(Matrix4f(), Vector4f(), 1e-4));
CALL_SUBTEST_6(testMatrixVectorProduct(MatrixXf(8,8), VectorXf(8), 1e-4));
CALL_SUBTEST_9(testMatrixVectorProduct(Matrix<long double,Dynamic,Dynamic>(7,7), Matrix<long double,7,9>(), 1e-13));
CALL_SUBTEST_7(testAliasing(Matrix<double,3,3,RowMajor>()));
CALL_SUBTEST_3(testAliasing(Matrix4cd()));
CALL_SUBTEST_4(testAliasing(MatrixXd(8,8)));
CALL_SUBTEST_5(testAliasing(Matrix3cf()));
CALL_SUBTEST_6(testAliasing(MatrixXf(8,8)));
}