1 // This file is part of Eigen, a lightweight C++ template library 2 // for linear algebra. 3 // 4 // Copyright (C) 2009-2011 Jitse Niesen <jitse (at) maths.leeds.ac.uk> 5 // 6 // This Source Code Form is subject to the terms of the Mozilla 7 // Public License v. 2.0. If a copy of the MPL was not distributed 8 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 10 #ifndef EIGEN_MATRIX_FUNCTION 11 #define EIGEN_MATRIX_FUNCTION 12 13 #include "StemFunction.h" 14 #include "MatrixFunctionAtomic.h" 15 16 17 namespace Eigen { 18 19 /** \ingroup MatrixFunctions_Module 20 * \brief Class for computing matrix functions. 21 * \tparam MatrixType type of the argument of the matrix function, 22 * expected to be an instantiation of the Matrix class template. 23 * \tparam AtomicType type for computing matrix function of atomic blocks. 24 * \tparam IsComplex used internally to select correct specialization. 25 * 26 * This class implements the Schur-Parlett algorithm for computing matrix functions. The spectrum of the 27 * matrix is divided in clustered of eigenvalues that lies close together. This class delegates the 28 * computation of the matrix function on every block corresponding to these clusters to an object of type 29 * \p AtomicType and uses these results to compute the matrix function of the whole matrix. The class 30 * \p AtomicType should have a \p compute() member function for computing the matrix function of a block. 31 * 32 * \sa class MatrixFunctionAtomic, class MatrixLogarithmAtomic 33 */ 34 template <typename MatrixType, 35 typename AtomicType, 36 int IsComplex = NumTraits<typename internal::traits<MatrixType>::Scalar>::IsComplex> 37 class MatrixFunction 38 { 39 public: 40 41 /** \brief Constructor. 42 * 43 * \param[in] A argument of matrix function, should be a square matrix. 44 * \param[in] atomic class for computing matrix function of atomic blocks. 45 * 46 * The class stores references to \p A and \p atomic, so they should not be 47 * changed (or destroyed) before compute() is called. 48 */ 49 MatrixFunction(const MatrixType& A, AtomicType& atomic); 50 51 /** \brief Compute the matrix function. 52 * 53 * \param[out] result the function \p f applied to \p A, as 54 * specified in the constructor. 55 * 56 * See MatrixBase::matrixFunction() for details on how this computation 57 * is implemented. 58 */ 59 template <typename ResultType> 60 void compute(ResultType &result); 61 }; 62 63 64 /** \internal \ingroup MatrixFunctions_Module 65 * \brief Partial specialization of MatrixFunction for real matrices 66 */ 67 template <typename MatrixType, typename AtomicType> 68 class MatrixFunction<MatrixType, AtomicType, 0> 69 { 70 private: 71 72 typedef internal::traits<MatrixType> Traits; 73 typedef typename Traits::Scalar Scalar; 74 static const int Rows = Traits::RowsAtCompileTime; 75 static const int Cols = Traits::ColsAtCompileTime; 76 static const int Options = MatrixType::Options; 77 static const int MaxRows = Traits::MaxRowsAtCompileTime; 78 static const int MaxCols = Traits::MaxColsAtCompileTime; 79 80 typedef std::complex<Scalar> ComplexScalar; 81 typedef Matrix<ComplexScalar, Rows, Cols, Options, MaxRows, MaxCols> ComplexMatrix; 82 83 public: 84 85 /** \brief Constructor. 86 * 87 * \param[in] A argument of matrix function, should be a square matrix. 88 * \param[in] atomic class for computing matrix function of atomic blocks. 89 */ 90 MatrixFunction(const MatrixType& A, AtomicType& atomic) : m_A(A), m_atomic(atomic) { } 91 92 /** \brief Compute the matrix function. 93 * 94 * \param[out] result the function \p f applied to \p A, as 95 * specified in the constructor. 96 * 97 * This function converts the real matrix \c A to a complex matrix, 98 * uses MatrixFunction<MatrixType,1> and then converts the result back to 99 * a real matrix. 100 */ 101 template <typename ResultType> 102 void compute(ResultType& result) 103 { 104 ComplexMatrix CA = m_A.template cast<ComplexScalar>(); 105 ComplexMatrix Cresult; 106 MatrixFunction<ComplexMatrix, AtomicType> mf(CA, m_atomic); 107 mf.compute(Cresult); 108 result = Cresult.real(); 109 } 110 111 private: 112 typename internal::nested<MatrixType>::type m_A; /**< \brief Reference to argument of matrix function. */ 113 AtomicType& m_atomic; /**< \brief Class for computing matrix function of atomic blocks. */ 114 115 MatrixFunction& operator=(const MatrixFunction&); 116 }; 117 118 119 /** \internal \ingroup MatrixFunctions_Module 120 * \brief Partial specialization of MatrixFunction for complex matrices 121 */ 122 template <typename MatrixType, typename AtomicType> 123 class MatrixFunction<MatrixType, AtomicType, 1> 124 { 125 private: 126 127 typedef internal::traits<MatrixType> Traits; 128 typedef typename MatrixType::Scalar Scalar; 129 typedef typename MatrixType::Index Index; 130 static const int RowsAtCompileTime = Traits::RowsAtCompileTime; 131 static const int ColsAtCompileTime = Traits::ColsAtCompileTime; 132 static const int Options = MatrixType::Options; 133 typedef typename NumTraits<Scalar>::Real RealScalar; 134 typedef Matrix<Scalar, Traits::RowsAtCompileTime, 1> VectorType; 135 typedef Matrix<Index, Traits::RowsAtCompileTime, 1> IntVectorType; 136 typedef Matrix<Index, Dynamic, 1> DynamicIntVectorType; 137 typedef std::list<Scalar> Cluster; 138 typedef std::list<Cluster> ListOfClusters; 139 typedef Matrix<Scalar, Dynamic, Dynamic, Options, RowsAtCompileTime, ColsAtCompileTime> DynMatrixType; 140 141 public: 142 143 MatrixFunction(const MatrixType& A, AtomicType& atomic); 144 template <typename ResultType> void compute(ResultType& result); 145 146 private: 147 148 void computeSchurDecomposition(); 149 void partitionEigenvalues(); 150 typename ListOfClusters::iterator findCluster(Scalar key); 151 void computeClusterSize(); 152 void computeBlockStart(); 153 void constructPermutation(); 154 void permuteSchur(); 155 void swapEntriesInSchur(Index index); 156 void computeBlockAtomic(); 157 Block<MatrixType> block(MatrixType& A, Index i, Index j); 158 void computeOffDiagonal(); 159 DynMatrixType solveTriangularSylvester(const DynMatrixType& A, const DynMatrixType& B, const DynMatrixType& C); 160 161 typename internal::nested<MatrixType>::type m_A; /**< \brief Reference to argument of matrix function. */ 162 AtomicType& m_atomic; /**< \brief Class for computing matrix function of atomic blocks. */ 163 MatrixType m_T; /**< \brief Triangular part of Schur decomposition */ 164 MatrixType m_U; /**< \brief Unitary part of Schur decomposition */ 165 MatrixType m_fT; /**< \brief %Matrix function applied to #m_T */ 166 ListOfClusters m_clusters; /**< \brief Partition of eigenvalues into clusters of ei'vals "close" to each other */ 167 DynamicIntVectorType m_eivalToCluster; /**< \brief m_eivalToCluster[i] = j means i-th ei'val is in j-th cluster */ 168 DynamicIntVectorType m_clusterSize; /**< \brief Number of eigenvalues in each clusters */ 169 DynamicIntVectorType m_blockStart; /**< \brief Row index at which block corresponding to i-th cluster starts */ 170 IntVectorType m_permutation; /**< \brief Permutation which groups ei'vals in the same cluster together */ 171 172 /** \brief Maximum distance allowed between eigenvalues to be considered "close". 173 * 174 * This is morally a \c static \c const \c Scalar, but only 175 * integers can be static constant class members in C++. The 176 * separation constant is set to 0.1, a value taken from the 177 * paper by Davies and Higham. */ 178 static const RealScalar separation() { return static_cast<RealScalar>(0.1); } 179 180 MatrixFunction& operator=(const MatrixFunction&); 181 }; 182 183 /** \brief Constructor. 184 * 185 * \param[in] A argument of matrix function, should be a square matrix. 186 * \param[in] atomic class for computing matrix function of atomic blocks. 187 */ 188 template <typename MatrixType, typename AtomicType> 189 MatrixFunction<MatrixType,AtomicType,1>::MatrixFunction(const MatrixType& A, AtomicType& atomic) 190 : m_A(A), m_atomic(atomic) 191 { 192 /* empty body */ 193 } 194 195 /** \brief Compute the matrix function. 196 * 197 * \param[out] result the function \p f applied to \p A, as 198 * specified in the constructor. 199 */ 200 template <typename MatrixType, typename AtomicType> 201 template <typename ResultType> 202 void MatrixFunction<MatrixType,AtomicType,1>::compute(ResultType& result) 203 { 204 computeSchurDecomposition(); 205 partitionEigenvalues(); 206 computeClusterSize(); 207 computeBlockStart(); 208 constructPermutation(); 209 permuteSchur(); 210 computeBlockAtomic(); 211 computeOffDiagonal(); 212 result = m_U * m_fT * m_U.adjoint(); 213 } 214 215 /** \brief Store the Schur decomposition of #m_A in #m_T and #m_U */ 216 template <typename MatrixType, typename AtomicType> 217 void MatrixFunction<MatrixType,AtomicType,1>::computeSchurDecomposition() 218 { 219 const ComplexSchur<MatrixType> schurOfA(m_A); 220 m_T = schurOfA.matrixT(); 221 m_U = schurOfA.matrixU(); 222 } 223 224 /** \brief Partition eigenvalues in clusters of ei'vals close to each other 225 * 226 * This function computes #m_clusters. This is a partition of the 227 * eigenvalues of #m_T in clusters, such that 228 * # Any eigenvalue in a certain cluster is at most separation() away 229 * from another eigenvalue in the same cluster. 230 * # The distance between two eigenvalues in different clusters is 231 * more than separation(). 232 * The implementation follows Algorithm 4.1 in the paper of Davies 233 * and Higham. 234 */ 235 template <typename MatrixType, typename AtomicType> 236 void MatrixFunction<MatrixType,AtomicType,1>::partitionEigenvalues() 237 { 238 const Index rows = m_T.rows(); 239 VectorType diag = m_T.diagonal(); // contains eigenvalues of A 240 241 for (Index i=0; i<rows; ++i) { 242 // Find set containing diag(i), adding a new set if necessary 243 typename ListOfClusters::iterator qi = findCluster(diag(i)); 244 if (qi == m_clusters.end()) { 245 Cluster l; 246 l.push_back(diag(i)); 247 m_clusters.push_back(l); 248 qi = m_clusters.end(); 249 --qi; 250 } 251 252 // Look for other element to add to the set 253 for (Index j=i+1; j<rows; ++j) { 254 if (internal::abs(diag(j) - diag(i)) <= separation() && std::find(qi->begin(), qi->end(), diag(j)) == qi->end()) { 255 typename ListOfClusters::iterator qj = findCluster(diag(j)); 256 if (qj == m_clusters.end()) { 257 qi->push_back(diag(j)); 258 } else { 259 qi->insert(qi->end(), qj->begin(), qj->end()); 260 m_clusters.erase(qj); 261 } 262 } 263 } 264 } 265 } 266 267 /** \brief Find cluster in #m_clusters containing some value 268 * \param[in] key Value to find 269 * \returns Iterator to cluster containing \c key, or 270 * \c m_clusters.end() if no cluster in m_clusters contains \c key. 271 */ 272 template <typename MatrixType, typename AtomicType> 273 typename MatrixFunction<MatrixType,AtomicType,1>::ListOfClusters::iterator MatrixFunction<MatrixType,AtomicType,1>::findCluster(Scalar key) 274 { 275 typename Cluster::iterator j; 276 for (typename ListOfClusters::iterator i = m_clusters.begin(); i != m_clusters.end(); ++i) { 277 j = std::find(i->begin(), i->end(), key); 278 if (j != i->end()) 279 return i; 280 } 281 return m_clusters.end(); 282 } 283 284 /** \brief Compute #m_clusterSize and #m_eivalToCluster using #m_clusters */ 285 template <typename MatrixType, typename AtomicType> 286 void MatrixFunction<MatrixType,AtomicType,1>::computeClusterSize() 287 { 288 const Index rows = m_T.rows(); 289 VectorType diag = m_T.diagonal(); 290 const Index numClusters = static_cast<Index>(m_clusters.size()); 291 292 m_clusterSize.setZero(numClusters); 293 m_eivalToCluster.resize(rows); 294 Index clusterIndex = 0; 295 for (typename ListOfClusters::const_iterator cluster = m_clusters.begin(); cluster != m_clusters.end(); ++cluster) { 296 for (Index i = 0; i < diag.rows(); ++i) { 297 if (std::find(cluster->begin(), cluster->end(), diag(i)) != cluster->end()) { 298 ++m_clusterSize[clusterIndex]; 299 m_eivalToCluster[i] = clusterIndex; 300 } 301 } 302 ++clusterIndex; 303 } 304 } 305 306 /** \brief Compute #m_blockStart using #m_clusterSize */ 307 template <typename MatrixType, typename AtomicType> 308 void MatrixFunction<MatrixType,AtomicType,1>::computeBlockStart() 309 { 310 m_blockStart.resize(m_clusterSize.rows()); 311 m_blockStart(0) = 0; 312 for (Index i = 1; i < m_clusterSize.rows(); i++) { 313 m_blockStart(i) = m_blockStart(i-1) + m_clusterSize(i-1); 314 } 315 } 316 317 /** \brief Compute #m_permutation using #m_eivalToCluster and #m_blockStart */ 318 template <typename MatrixType, typename AtomicType> 319 void MatrixFunction<MatrixType,AtomicType,1>::constructPermutation() 320 { 321 DynamicIntVectorType indexNextEntry = m_blockStart; 322 m_permutation.resize(m_T.rows()); 323 for (Index i = 0; i < m_T.rows(); i++) { 324 Index cluster = m_eivalToCluster[i]; 325 m_permutation[i] = indexNextEntry[cluster]; 326 ++indexNextEntry[cluster]; 327 } 328 } 329 330 /** \brief Permute Schur decomposition in #m_U and #m_T according to #m_permutation */ 331 template <typename MatrixType, typename AtomicType> 332 void MatrixFunction<MatrixType,AtomicType,1>::permuteSchur() 333 { 334 IntVectorType p = m_permutation; 335 for (Index i = 0; i < p.rows() - 1; i++) { 336 Index j; 337 for (j = i; j < p.rows(); j++) { 338 if (p(j) == i) break; 339 } 340 eigen_assert(p(j) == i); 341 for (Index k = j-1; k >= i; k--) { 342 swapEntriesInSchur(k); 343 std::swap(p.coeffRef(k), p.coeffRef(k+1)); 344 } 345 } 346 } 347 348 /** \brief Swap rows \a index and \a index+1 in Schur decomposition in #m_U and #m_T */ 349 template <typename MatrixType, typename AtomicType> 350 void MatrixFunction<MatrixType,AtomicType,1>::swapEntriesInSchur(Index index) 351 { 352 JacobiRotation<Scalar> rotation; 353 rotation.makeGivens(m_T(index, index+1), m_T(index+1, index+1) - m_T(index, index)); 354 m_T.applyOnTheLeft(index, index+1, rotation.adjoint()); 355 m_T.applyOnTheRight(index, index+1, rotation); 356 m_U.applyOnTheRight(index, index+1, rotation); 357 } 358 359 /** \brief Compute block diagonal part of #m_fT. 360 * 361 * This routine computes the matrix function applied to the block diagonal part of #m_T, with the blocking 362 * given by #m_blockStart. The matrix function of each diagonal block is computed by #m_atomic. The 363 * off-diagonal parts of #m_fT are set to zero. 364 */ 365 template <typename MatrixType, typename AtomicType> 366 void MatrixFunction<MatrixType,AtomicType,1>::computeBlockAtomic() 367 { 368 m_fT.resize(m_T.rows(), m_T.cols()); 369 m_fT.setZero(); 370 for (Index i = 0; i < m_clusterSize.rows(); ++i) { 371 block(m_fT, i, i) = m_atomic.compute(block(m_T, i, i)); 372 } 373 } 374 375 /** \brief Return block of matrix according to blocking given by #m_blockStart */ 376 template <typename MatrixType, typename AtomicType> 377 Block<MatrixType> MatrixFunction<MatrixType,AtomicType,1>::block(MatrixType& A, Index i, Index j) 378 { 379 return A.block(m_blockStart(i), m_blockStart(j), m_clusterSize(i), m_clusterSize(j)); 380 } 381 382 /** \brief Compute part of #m_fT above block diagonal. 383 * 384 * This routine assumes that the block diagonal part of #m_fT (which 385 * equals the matrix function applied to #m_T) has already been computed and computes 386 * the part above the block diagonal. The part below the diagonal is 387 * zero, because #m_T is upper triangular. 388 */ 389 template <typename MatrixType, typename AtomicType> 390 void MatrixFunction<MatrixType,AtomicType,1>::computeOffDiagonal() 391 { 392 for (Index diagIndex = 1; diagIndex < m_clusterSize.rows(); diagIndex++) { 393 for (Index blockIndex = 0; blockIndex < m_clusterSize.rows() - diagIndex; blockIndex++) { 394 // compute (blockIndex, blockIndex+diagIndex) block 395 DynMatrixType A = block(m_T, blockIndex, blockIndex); 396 DynMatrixType B = -block(m_T, blockIndex+diagIndex, blockIndex+diagIndex); 397 DynMatrixType C = block(m_fT, blockIndex, blockIndex) * block(m_T, blockIndex, blockIndex+diagIndex); 398 C -= block(m_T, blockIndex, blockIndex+diagIndex) * block(m_fT, blockIndex+diagIndex, blockIndex+diagIndex); 399 for (Index k = blockIndex + 1; k < blockIndex + diagIndex; k++) { 400 C += block(m_fT, blockIndex, k) * block(m_T, k, blockIndex+diagIndex); 401 C -= block(m_T, blockIndex, k) * block(m_fT, k, blockIndex+diagIndex); 402 } 403 block(m_fT, blockIndex, blockIndex+diagIndex) = solveTriangularSylvester(A, B, C); 404 } 405 } 406 } 407 408 /** \brief Solve a triangular Sylvester equation AX + XB = C 409 * 410 * \param[in] A the matrix A; should be square and upper triangular 411 * \param[in] B the matrix B; should be square and upper triangular 412 * \param[in] C the matrix C; should have correct size. 413 * 414 * \returns the solution X. 415 * 416 * If A is m-by-m and B is n-by-n, then both C and X are m-by-n. 417 * The (i,j)-th component of the Sylvester equation is 418 * \f[ 419 * \sum_{k=i}^m A_{ik} X_{kj} + \sum_{k=1}^j X_{ik} B_{kj} = C_{ij}. 420 * \f] 421 * This can be re-arranged to yield: 422 * \f[ 423 * X_{ij} = \frac{1}{A_{ii} + B_{jj}} \Bigl( C_{ij} 424 * - \sum_{k=i+1}^m A_{ik} X_{kj} - \sum_{k=1}^{j-1} X_{ik} B_{kj} \Bigr). 425 * \f] 426 * It is assumed that A and B are such that the numerator is never 427 * zero (otherwise the Sylvester equation does not have a unique 428 * solution). In that case, these equations can be evaluated in the 429 * order \f$ i=m,\ldots,1 \f$ and \f$ j=1,\ldots,n \f$. 430 */ 431 template <typename MatrixType, typename AtomicType> 432 typename MatrixFunction<MatrixType,AtomicType,1>::DynMatrixType MatrixFunction<MatrixType,AtomicType,1>::solveTriangularSylvester( 433 const DynMatrixType& A, 434 const DynMatrixType& B, 435 const DynMatrixType& C) 436 { 437 eigen_assert(A.rows() == A.cols()); 438 eigen_assert(A.isUpperTriangular()); 439 eigen_assert(B.rows() == B.cols()); 440 eigen_assert(B.isUpperTriangular()); 441 eigen_assert(C.rows() == A.rows()); 442 eigen_assert(C.cols() == B.rows()); 443 444 Index m = A.rows(); 445 Index n = B.rows(); 446 DynMatrixType X(m, n); 447 448 for (Index i = m - 1; i >= 0; --i) { 449 for (Index j = 0; j < n; ++j) { 450 451 // Compute AX = \sum_{k=i+1}^m A_{ik} X_{kj} 452 Scalar AX; 453 if (i == m - 1) { 454 AX = 0; 455 } else { 456 Matrix<Scalar,1,1> AXmatrix = A.row(i).tail(m-1-i) * X.col(j).tail(m-1-i); 457 AX = AXmatrix(0,0); 458 } 459 460 // Compute XB = \sum_{k=1}^{j-1} X_{ik} B_{kj} 461 Scalar XB; 462 if (j == 0) { 463 XB = 0; 464 } else { 465 Matrix<Scalar,1,1> XBmatrix = X.row(i).head(j) * B.col(j).head(j); 466 XB = XBmatrix(0,0); 467 } 468 469 X(i,j) = (C(i,j) - AX - XB) / (A(i,i) + B(j,j)); 470 } 471 } 472 return X; 473 } 474 475 /** \ingroup MatrixFunctions_Module 476 * 477 * \brief Proxy for the matrix function of some matrix (expression). 478 * 479 * \tparam Derived Type of the argument to the matrix function. 480 * 481 * This class holds the argument to the matrix function until it is 482 * assigned or evaluated for some other reason (so the argument 483 * should not be changed in the meantime). It is the return type of 484 * matrixBase::matrixFunction() and related functions and most of the 485 * time this is the only way it is used. 486 */ 487 template<typename Derived> class MatrixFunctionReturnValue 488 : public ReturnByValue<MatrixFunctionReturnValue<Derived> > 489 { 490 public: 491 492 typedef typename Derived::Scalar Scalar; 493 typedef typename Derived::Index Index; 494 typedef typename internal::stem_function<Scalar>::type StemFunction; 495 496 /** \brief Constructor. 497 * 498 * \param[in] A %Matrix (expression) forming the argument of the 499 * matrix function. 500 * \param[in] f Stem function for matrix function under consideration. 501 */ 502 MatrixFunctionReturnValue(const Derived& A, StemFunction f) : m_A(A), m_f(f) { } 503 504 /** \brief Compute the matrix function. 505 * 506 * \param[out] result \p f applied to \p A, where \p f and \p A 507 * are as in the constructor. 508 */ 509 template <typename ResultType> 510 inline void evalTo(ResultType& result) const 511 { 512 typedef typename Derived::PlainObject PlainObject; 513 typedef internal::traits<PlainObject> Traits; 514 static const int RowsAtCompileTime = Traits::RowsAtCompileTime; 515 static const int ColsAtCompileTime = Traits::ColsAtCompileTime; 516 static const int Options = PlainObject::Options; 517 typedef std::complex<typename NumTraits<Scalar>::Real> ComplexScalar; 518 typedef Matrix<ComplexScalar, Dynamic, Dynamic, Options, RowsAtCompileTime, ColsAtCompileTime> DynMatrixType; 519 typedef MatrixFunctionAtomic<DynMatrixType> AtomicType; 520 AtomicType atomic(m_f); 521 522 const PlainObject Aevaluated = m_A.eval(); 523 MatrixFunction<PlainObject, AtomicType> mf(Aevaluated, atomic); 524 mf.compute(result); 525 } 526 527 Index rows() const { return m_A.rows(); } 528 Index cols() const { return m_A.cols(); } 529 530 private: 531 typename internal::nested<Derived>::type m_A; 532 StemFunction *m_f; 533 534 MatrixFunctionReturnValue& operator=(const MatrixFunctionReturnValue&); 535 }; 536 537 namespace internal { 538 template<typename Derived> 539 struct traits<MatrixFunctionReturnValue<Derived> > 540 { 541 typedef typename Derived::PlainObject ReturnType; 542 }; 543 } 544 545 546 /********** MatrixBase methods **********/ 547 548 549 template <typename Derived> 550 const MatrixFunctionReturnValue<Derived> MatrixBase<Derived>::matrixFunction(typename internal::stem_function<typename internal::traits<Derived>::Scalar>::type f) const 551 { 552 eigen_assert(rows() == cols()); 553 return MatrixFunctionReturnValue<Derived>(derived(), f); 554 } 555 556 template <typename Derived> 557 const MatrixFunctionReturnValue<Derived> MatrixBase<Derived>::sin() const 558 { 559 eigen_assert(rows() == cols()); 560 typedef typename internal::stem_function<Scalar>::ComplexScalar ComplexScalar; 561 return MatrixFunctionReturnValue<Derived>(derived(), StdStemFunctions<ComplexScalar>::sin); 562 } 563 564 template <typename Derived> 565 const MatrixFunctionReturnValue<Derived> MatrixBase<Derived>::cos() const 566 { 567 eigen_assert(rows() == cols()); 568 typedef typename internal::stem_function<Scalar>::ComplexScalar ComplexScalar; 569 return MatrixFunctionReturnValue<Derived>(derived(), StdStemFunctions<ComplexScalar>::cos); 570 } 571 572 template <typename Derived> 573 const MatrixFunctionReturnValue<Derived> MatrixBase<Derived>::sinh() const 574 { 575 eigen_assert(rows() == cols()); 576 typedef typename internal::stem_function<Scalar>::ComplexScalar ComplexScalar; 577 return MatrixFunctionReturnValue<Derived>(derived(), StdStemFunctions<ComplexScalar>::sinh); 578 } 579 580 template <typename Derived> 581 const MatrixFunctionReturnValue<Derived> MatrixBase<Derived>::cosh() const 582 { 583 eigen_assert(rows() == cols()); 584 typedef typename internal::stem_function<Scalar>::ComplexScalar ComplexScalar; 585 return MatrixFunctionReturnValue<Derived>(derived(), StdStemFunctions<ComplexScalar>::cosh); 586 } 587 588 } // end namespace Eigen 589 590 #endif // EIGEN_MATRIX_FUNCTION 591