|
| 1 | +#pragma once |
| 2 | +#if defined(__GNUC__) || defined(__clang__) |
| 3 | +#pragma GCC optimize("O3", "fast-math") |
| 4 | +#endif |
| 5 | +#include "numerical/math/CompilerOptimizations.hpp" |
| 6 | +#include "numerical/math/Matrix.hpp" |
| 7 | +#include "numerical/math/MatrixNorms.hpp" |
| 8 | +#include "numerical/math/TriangularSolve.hpp" |
| 9 | +#include <array> |
| 10 | +#include <cmath> |
| 11 | +#include <cstddef> |
| 12 | +#include <type_traits> |
| 13 | + |
| 14 | +namespace math |
| 15 | +{ |
| 16 | + template<typename T, std::size_t N> |
| 17 | + class MatrixExponential |
| 18 | + { |
| 19 | + static_assert(std::is_floating_point_v<T>, "MatrixExponential supports floating-point types"); |
| 20 | + |
| 21 | + public: |
| 22 | + MatrixExponential() = default; |
| 23 | + |
| 24 | + OPTIMIZE_FOR_SPEED SquareMatrix<T, N> Compute(const SquareMatrix<T, N>& a); |
| 25 | + OPTIMIZE_FOR_SPEED SquareMatrix<T, N> Compute(const SquareMatrix<T, N>& a, T dt); |
| 26 | + |
| 27 | + private: |
| 28 | + static constexpr T c0{ T{ 1 } }; |
| 29 | + static constexpr T c1{ T{ 1 } / T{ 2 } }; |
| 30 | + static constexpr T c2{ T{ 5 } / T{ 44 } }; |
| 31 | + static constexpr T c3{ T{ 1 } / T{ 66 } }; |
| 32 | + static constexpr T c4{ T{ 1 } / T{ 792 } }; |
| 33 | + static constexpr T c5{ T{ 1 } / T{ 15840 } }; |
| 34 | + static constexpr T c6{ T{ 1 } / T{ 665280 } }; |
| 35 | + |
| 36 | + void PadeNumeratorDenominator(const SquareMatrix<T, N>& as, |
| 37 | + SquareMatrix<T, N>& num, |
| 38 | + SquareMatrix<T, N>& den); |
| 39 | + |
| 40 | + SquareMatrix<T, N> SolvePade(const SquareMatrix<T, N>& den, const SquareMatrix<T, N>& num); |
| 41 | + }; |
| 42 | + |
| 43 | + template<typename T, std::size_t N> |
| 44 | + void MatrixExponential<T, N>::PadeNumeratorDenominator(const SquareMatrix<T, N>& as, |
| 45 | + SquareMatrix<T, N>& num, |
| 46 | + SquareMatrix<T, N>& den) |
| 47 | + { |
| 48 | + const auto identity = SquareMatrix<T, N>::Identity(); |
| 49 | + const auto a2 = as * as; |
| 50 | + const auto a4 = a2 * a2; |
| 51 | + const auto a6 = a4 * a2; |
| 52 | + |
| 53 | + const auto vEven = identity * c0 + a2 * c2 + a4 * c4 + a6 * c6; |
| 54 | + const auto uOdd = as * (identity * c1 + a2 * c3 + a4 * c5); |
| 55 | + |
| 56 | + num = vEven + uOdd; |
| 57 | + den = vEven - uOdd; |
| 58 | + } |
| 59 | + |
| 60 | + template<typename T, std::size_t N> |
| 61 | + SquareMatrix<T, N> MatrixExponential<T, N>::SolvePade(const SquareMatrix<T, N>& den, const SquareMatrix<T, N>& num) |
| 62 | + { |
| 63 | + SquareMatrix<T, N> lu = den; |
| 64 | + std::array<std::size_t, N> piv{}; |
| 65 | + for (std::size_t i = 0; i < N; ++i) |
| 66 | + piv[i] = i; |
| 67 | + |
| 68 | + for (std::size_t k = 0; k < N; ++k) |
| 69 | + { |
| 70 | + std::size_t p = k; |
| 71 | + T maxVal = std::abs(lu.at(k, k)); |
| 72 | + for (std::size_t i = k + 1; i < N; ++i) |
| 73 | + { |
| 74 | + T candidate = std::abs(lu.at(i, k)); |
| 75 | + if (candidate > maxVal) |
| 76 | + { |
| 77 | + maxVal = candidate; |
| 78 | + p = i; |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + if (p != k) |
| 83 | + { |
| 84 | + for (std::size_t j = 0; j < N; ++j) |
| 85 | + { |
| 86 | + T tmp = lu.at(p, j); |
| 87 | + lu.at(p, j) = lu.at(k, j); |
| 88 | + lu.at(k, j) = tmp; |
| 89 | + } |
| 90 | + std::size_t tmpIdx = piv[p]; |
| 91 | + piv[p] = piv[k]; |
| 92 | + piv[k] = tmpIdx; |
| 93 | + } |
| 94 | + |
| 95 | + for (std::size_t i = k + 1; i < N; ++i) |
| 96 | + { |
| 97 | + lu.at(i, k) /= lu.at(k, k); |
| 98 | + for (std::size_t j = k + 1; j < N; ++j) |
| 99 | + lu.at(i, j) -= lu.at(i, k) * lu.at(k, j); |
| 100 | + } |
| 101 | + } |
| 102 | + |
| 103 | + SquareMatrix<T, N> result{}; |
| 104 | + for (std::size_t col = 0; col < N; ++col) |
| 105 | + { |
| 106 | + Vector<T, N> b{}; |
| 107 | + for (std::size_t i = 0; i < N; ++i) |
| 108 | + b.at(i, 0) = num.at(piv[i], col); |
| 109 | + |
| 110 | + Vector<T, N> y = SolveUnitLowerTriangular(lu, b); |
| 111 | + Vector<T, N> x = SolveUpperTriangular(lu, y); |
| 112 | + |
| 113 | + for (std::size_t i = 0; i < N; ++i) |
| 114 | + result.at(i, col) = x.at(i, 0); |
| 115 | + } |
| 116 | + |
| 117 | + return result; |
| 118 | + } |
| 119 | + |
| 120 | + template<typename T, std::size_t N> |
| 121 | + OPTIMIZE_FOR_SPEED SquareMatrix<T, N> MatrixExponential<T, N>::Compute(const SquareMatrix<T, N>& a) |
| 122 | + { |
| 123 | + const T norm = InfinityNorm(a); |
| 124 | + int s{ 0 }; |
| 125 | + if (norm > T{ 1 }) |
| 126 | + { |
| 127 | + const T lg = std::log2(norm); |
| 128 | + s = static_cast<int>(std::ceil(lg)); |
| 129 | + if (s < 0) |
| 130 | + s = 0; |
| 131 | + } |
| 132 | + |
| 133 | + const T scale = T{ 1 } / static_cast<T>(1 << s); |
| 134 | + const auto as = a * scale; |
| 135 | + |
| 136 | + SquareMatrix<T, N> num{}; |
| 137 | + SquareMatrix<T, N> den{}; |
| 138 | + PadeNumeratorDenominator(as, num, den); |
| 139 | + |
| 140 | + auto r = SolvePade(den, num); |
| 141 | + |
| 142 | + for (int k = 0; k < s; ++k) |
| 143 | + r = r * r; |
| 144 | + |
| 145 | + return r; |
| 146 | + } |
| 147 | + |
| 148 | + template<typename T, std::size_t N> |
| 149 | + OPTIMIZE_FOR_SPEED SquareMatrix<T, N> MatrixExponential<T, N>::Compute(const SquareMatrix<T, N>& a, T dt) |
| 150 | + { |
| 151 | + const auto adt = a * dt; |
| 152 | + return Compute(adt); |
| 153 | + } |
| 154 | + |
| 155 | +#ifdef NUMERICAL_TOOLBOX_COVERAGE_BUILD |
| 156 | + extern template class MatrixExponential<float, 2>; |
| 157 | + extern template class MatrixExponential<float, 3>; |
| 158 | +#endif |
| 159 | +} |
0 commit comments