// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later // matrix_functions.hpp // // 行列関数: expm (行列指数), sqrtm (行列平方根), logm (行列対数) // // アルゴリズム: // expm — Scaling-and-squaring + Padé [13/13] (Higham 2005) // sqrtm — Schur 分解 + Parlett 再帰 (Björck-Hammarling 1983) // logm — Schur 分解 + Parlett 再帰 #ifndef CALX_MATRIX_FUNCTIONS_HPP #define CALX_MATRIX_FUNCTIONS_HPP #include #include #include #include #include #include #include #include #include #include namespace calx { namespace algorithms { //===================================================================== // ヘルパー関数 (内部用) //===================================================================== namespace detail_matfun { // 行列 1-ノルム: max_j Σ_i |A_{ij}| (最大列絶対値和) template T matrix_one_norm(const Matrix& A) { const auto rows = A.rows(); const auto cols = A.cols(); T max_col_sum = T(0); for (std::size_t j = 0; j < cols; ++j) { T col_sum = T(0); for (std::size_t i = 0; i < rows; ++i) { col_sum += std::abs(A(i, j)); } if (col_sum > max_col_sum) { max_col_sum = col_sum; } } return max_col_sum; } // 準上三角行列のブロック構造を検出 // block_starts[k] = k 番目のブロックの開始行 // block_sizes[k] = 1 or 2 template void detect_blocks(const Matrix& T_mat, std::size_t n, std::vector& block_starts, std::vector& block_sizes) { block_starts.clear(); block_sizes.clear(); std::size_t i = 0; while (i < n) { if (i + 1 < n && T_mat(i + 1, i) != T(0)) { // 2×2 ブロック block_starts.push_back(i); block_sizes.push_back(2); i += 2; } else { // 1×1 ブロック block_starts.push_back(i); block_sizes.push_back(1); i += 1; } } } // 2×2 ブロック B の固有値 α±iβ を計算 // B = [[a,b],[c,d]], α = (a+d)/2, β = sqrt(|bc + ((a-d)/2)²|) // ただし複素共役対の場合のみ (disc < 0) template void block2_eigenvalues(const Matrix& T_mat, std::size_t i, T& alpha, T& beta) { T a = T_mat(i, i), b = T_mat(i, i + 1); T c = T_mat(i + 1, i), d = T_mat(i + 1, i + 1); alpha = (a + d) / T(2); T half_diff = (a - d) / T(2); T disc = half_diff * half_diff + b * c; if (disc < T(0)) { beta = std::sqrt(-disc); } else { // 実固有値のケース (α ± sqrt(disc)) beta = T(0); } } // 2×2 ブロック B に対する f(B) の計算 // f(B) = u·I + (v/β)·(B - α·I) // func_re, func_im: f(α+iβ) = func_re + i·func_im template void apply_block2_function( const Matrix& T_mat, std::size_t i, Matrix& F, T alpha, T beta, T func_re, T func_im) { if (beta == T(0)) { // 実固有値ケース: 2×2 ブロックだが β=0 // 対角要素に f(α) を設定 F(i, i) = func_re; F(i + 1, i + 1) = func_re; // 上三角要素は f'(α) * T_ij で近似 // (実際にはこのケースは稀) F(i, i + 1) = T(0); F(i + 1, i) = T(0); return; } T u = func_re; T v_over_beta = func_im / beta; // F の (i,i), (i,i+1), (i+1,i), (i+1,i+1) を設定 F(i, i) = u + v_over_beta * (T_mat(i, i) - alpha); F(i, i + 1) = v_over_beta * T_mat(i, i + 1); F(i + 1, i) = v_over_beta * T_mat(i + 1, i); F(i + 1, i + 1) = u + v_over_beta * (T_mat(i + 1, i + 1) - alpha); } // 小ブロック Sylvester 方程式ソルバー: A·X - X·B = C // A: p×p, B: q×q, C: p×q, X: p×q (p,q ∈ {1,2}) // F の部分行列 F[ri:ri+p, ci:ci+q] に解を書き込む template void solve_sylvester_block( const Matrix& F, // F 行列 (A_block と B_block の読み出し元) const Matrix& T_mat, // 元の準上三角行列 Matrix& result, // 解を書き込む先 std::size_t ri, std::size_t p, // 行ブロック (開始, サイズ) std::size_t ci, std::size_t q, // 列ブロック (開始, サイズ) std::size_t n) { // RHS を計算: C = (F_ii · T_ij - T_ij · F_jj) + Σ_k (F_ik · T_kj - T_ik · F_kj) // ここで、F_ii = result[ri:ri+p, ri:ri+p] (対角ブロック) // F_jj = result[ci:ci+q, ci:ci+q] (対角ブロック) // T_ij = T_mat[ri:ri+p, ci:ci+q] (上三角ブロック) // まず T_ij · (F_ii - F_jj) 項を計算 // 次に中間ブロックの cross terms を加算 // C = F_ii · T_ij T C[2][2] = {}; for (std::size_t a = 0; a < p; ++a) { for (std::size_t b = 0; b < q; ++b) { T sum = T(0); for (std::size_t k = 0; k < p; ++k) { sum += result(ri + a, ri + k) * T_mat(ri + k, ci + b); } C[a][b] = sum; } } // C -= T_ij · F_jj for (std::size_t a = 0; a < p; ++a) { for (std::size_t b = 0; b < q; ++b) { T sum = T(0); for (std::size_t k = 0; k < q; ++k) { sum += T_mat(ri + a, ci + k) * result(ci + k, ci + b); } C[a][b] -= sum; } } // 中間ブロックの cross terms を加算 // Σ_{k: ri+p ≤ k < ci} (F[ri:,k:] · T[k:,ci:] - T[ri:,k:] · F[k:,ci:]) for (std::size_t k = ri + p; k < ci; ++k) { for (std::size_t a = 0; a < p; ++a) { for (std::size_t b = 0; b < q; ++b) { C[a][b] += result(ri + a, k) * T_mat(k, ci + b) - T_mat(ri + a, k) * result(k, ci + b); } } } // Sylvester 方程式 A·X - X·B = C を解く // A = T_mat[ri:ri+p, ri:ri+p] (T の対角ブロック、F_ii ではない!) // 注意: TF=FT の関係から導出すると、 // T_ii · F_ij - F_ij · T_jj = RHS // なので A = T_ii, B = T_jj if (p == 1 && q == 1) { // スカラー: x = c / (t_ii - t_jj) T denom = T_mat(ri, ri) - T_mat(ci, ci); if (std::abs(denom) < std::numeric_limits::epsilon() * T(100)) { result(ri, ci) = T(0); // 縮退ケース } else { result(ri, ci) = C[0][0] / denom; } } else if (p == 1 && q == 2) { // 1×2 Sylvester: a·X - X·B = C // [a*x0 - x0*b00 - x1*b10, a*x1 - x0*b01 - x1*b11] = [c0, c1] T a = T_mat(ri, ri); T b00 = T_mat(ci, ci), b01 = T_mat(ci, ci + 1); T b10 = T_mat(ci + 1, ci), b11 = T_mat(ci + 1, ci + 1); // M · [x0, x1]^T = [c0, c1]^T T m00 = a - b00, m01 = -b10; T m10 = -b01, m11 = a - b11; T det = m00 * m11 - m01 * m10; if (std::abs(det) < std::numeric_limits::epsilon() * T(100)) { result(ri, ci) = T(0); result(ri, ci + 1) = T(0); } else { result(ri, ci) = ( m11 * C[0][0] - m01 * C[0][1]) / det; result(ri, ci + 1) = (-m10 * C[0][0] + m00 * C[0][1]) / det; } } else if (p == 2 && q == 1) { // 2×1 Sylvester: A·X - X·b = C T b = T_mat(ci, ci); T a00 = T_mat(ri, ri), a01 = T_mat(ri, ri + 1); T a10 = T_mat(ri + 1, ri), a11 = T_mat(ri + 1, ri + 1); // [a00-b, a01; a10, a11-b] · [x0; x1] = [c0; c1] T m00 = a00 - b, m01 = a01; T m10 = a10, m11 = a11 - b; T det = m00 * m11 - m01 * m10; if (std::abs(det) < std::numeric_limits::epsilon() * T(100)) { result(ri, ci) = T(0); result(ri + 1, ci) = T(0); } else { result(ri, ci) = ( m11 * C[0][0] - m01 * C[1][0]) / det; result(ri + 1, ci) = (-m10 * C[0][0] + m00 * C[1][0]) / det; } } else { // 2×2 Sylvester: A·X - X·B = C → 4×4 線形系 T a00 = T_mat(ri, ri), a01 = T_mat(ri, ri + 1); T a10 = T_mat(ri + 1, ri), a11 = T_mat(ri + 1, ri + 1); T b00 = T_mat(ci, ci), b01 = T_mat(ci, ci + 1); T b10 = T_mat(ci + 1, ci), b11 = T_mat(ci + 1, ci + 1); // vec(X) = [x00, x10, x01, x11]^T (列優先) // M · vec(X) = vec(C) where M = I⊗A - B^T⊗I // M = [[a00-b00, a01, -b10, 0 ], // [a10, a11-b00, 0, -b10 ], // [-b01, 0, a00-b11, a01 ], // [0, -b01, a10, a11-b11]] T M[4][4] = { {a00 - b00, a01, -b10, T(0)}, {a10, a11 - b00, T(0), -b10}, {-b01, T(0), a00 - b11, a01}, {T(0), -b01, a10, a11 - b11} }; T rhs[4] = {C[0][0], C[1][0], C[0][1], C[1][1]}; // 4×4 ガウス消去法 (部分ピボット) int piv[4] = {0, 1, 2, 3}; for (int col = 0; col < 4; ++col) { // ピボット選択 int max_row = col; T max_val = std::abs(M[piv[col]][col]); for (int row = col + 1; row < 4; ++row) { T v = std::abs(M[piv[row]][col]); if (v > max_val) { max_val = v; max_row = row; } } std::swap(piv[col], piv[max_row]); if (max_val < std::numeric_limits::epsilon() * T(100)) { // 特異ケース result(ri, ci) = T(0); result(ri + 1, ci) = T(0); result(ri, ci + 1) = T(0); result(ri + 1, ci + 1) = T(0); return; } // 消去 for (int row = col + 1; row < 4; ++row) { T factor = M[piv[row]][col] / M[piv[col]][col]; for (int k = col + 1; k < 4; ++k) { M[piv[row]][k] -= factor * M[piv[col]][k]; } rhs[piv[row]] -= factor * rhs[piv[col]]; } } // 後退代入 T x[4]; for (int i = 3; i >= 0; --i) { T sum = rhs[piv[i]]; for (int k = i + 1; k < 4; ++k) { sum -= M[piv[i]][k] * x[k]; } x[i] = sum / M[piv[i]][i]; } result(ri, ci) = x[0]; result(ri + 1, ci) = x[1]; result(ri, ci + 1) = x[2]; result(ri + 1, ci + 1) = x[3]; } } } // namespace detail_matfun //===================================================================== // expm — 行列指数関数 //===================================================================== /** * @brief 行列指数関数 exp(A) * * Scaling-and-squaring 法 + Padé [13/13] 近似。 * Higham (2005) "The Scaling and Squaring Method for * the Matrix Exponential Revisited" に基づく。 * * @tparam T 要素の型 (float, double) * @param A 入力行列 (n×n 正方行列) * @return exp(A) * @throws DimensionError A が正方行列でない場合 */ template Matrix expm(const Matrix& A) { const auto n = A.rows(); if (n != A.cols()) { throw DimensionError("expm: matrix must be square"); } if (n == 0) { return A; } if (n == 1) { Matrix result(1, 1); result(0, 0) = std::exp(A(0, 0)); return result; } auto I = Matrix::identity(n); // Padé 近似の θ 値 (Higham Table 10.2) constexpr T theta3 = T(1.495585217958292e-02); constexpr T theta5 = T(2.539398330063230e-01); constexpr T theta7 = T(9.504178996162932e-01); constexpr T theta9 = T(2.097847961257068e+00); constexpr T theta13 = T(5.371920351148152e+00); T norm1 = detail_matfun::matrix_one_norm(A); // A² を事前計算 (全次数で使用) Matrix A2 = A * A; // 低次 Padé が使える場合 (||A||₁ が十分小さい) auto pade_3 = [&]() -> Matrix { // b₀=120, b₁=60, b₂=12, b₃=1 constexpr T b0 = T(120), b1 = T(60), b2 = T(12), b3 = T(1); Matrix U = A * (b3 * A2 + b1 * I); Matrix V = b2 * A2 + b0 * I; return solve_multi(Matrix(V - U), Matrix(V + U)); }; auto pade_5 = [&]() -> Matrix { constexpr T b0 = T(30240), b1 = T(15120), b2 = T(3360); constexpr T b3 = T(420), b4 = T(30), b5 = T(1); Matrix A4 = A2 * A2; Matrix U = A * (b5 * A4 + b3 * A2 + b1 * I); Matrix V = b4 * A4 + b2 * A2 + b0 * I; return solve_multi(Matrix(V - U), Matrix(V + U)); }; auto pade_7 = [&]() -> Matrix { constexpr T b0 = T(17297280), b1 = T(8648640), b2 = T(1995840); constexpr T b3 = T(277200), b4 = T(25200), b5 = T(1512); constexpr T b6 = T(56), b7 = T(1); Matrix A4 = A2 * A2; Matrix A6 = A4 * A2; Matrix U = A * (b7 * A6 + b5 * A4 + b3 * A2 + b1 * I); Matrix V = b6 * A6 + b4 * A4 + b2 * A2 + b0 * I; return solve_multi(Matrix(V - U), Matrix(V + U)); }; auto pade_9 = [&]() -> Matrix { constexpr T b0 = T(17643225600.0), b1 = T(8821612800.0); constexpr T b2 = T(2075673600.0), b3 = T(302702400.0); constexpr T b4 = T(30270240.0), b5 = T(2162160.0); constexpr T b6 = T(110880.0), b7 = T(3960.0); constexpr T b8 = T(90.0), b9 = T(1); Matrix A4 = A2 * A2; Matrix A6 = A4 * A2; Matrix A8 = A4 * A4; Matrix U = A * (b9 * A8 + b7 * A6 + b5 * A4 + b3 * A2 + b1 * I); Matrix V = b8 * A8 + b6 * A6 + b4 * A4 + b2 * A2 + b0 * I; return solve_multi(Matrix(V - U), Matrix(V + U)); }; if (norm1 <= theta3) return pade_3(); if (norm1 <= theta5) return pade_5(); if (norm1 <= theta7) return pade_7(); if (norm1 <= theta9) return pade_9(); // Padé [13/13] with scaling int s = std::max(0, static_cast(std::ceil(std::log2(norm1 / theta13)))); T scale = std::ldexp(T(1), -s); Matrix As = A * scale; Matrix As2 = As * As; Matrix As4 = As2 * As2; Matrix As6 = As4 * As2; // Padé [13/13] 係数 constexpr T b0 = T(64764752532480000.0); constexpr T b1 = T(32382376266240000.0); constexpr T b2 = T(7771770303897600.0); constexpr T b3 = T(1187353796428800.0); constexpr T b4 = T(129060195264000.0); constexpr T b5 = T(10559470521600.0); constexpr T b6 = T(670442572800.0); constexpr T b7 = T(33522128640.0); constexpr T b8 = T(1323241920.0); constexpr T b9 = T(40840800.0); constexpr T b10 = T(960960.0); constexpr T b11 = T(16380.0); constexpr T b12 = T(182.0); constexpr T b13 = T(1.0); // U = As · (As6·(b13·As6 + b11·As4 + b9·As2) + b7·As6 + b5·As4 + b3·As2 + b1·I) Matrix inner_u = b13 * As6 + b11 * As4 + b9 * As2; Matrix U = As * (As6 * inner_u + b7 * As6 + b5 * As4 + b3 * As2 + b1 * I); // V = As6·(b12·As6 + b10·As4 + b8·As2) + b6·As6 + b4·As4 + b2·As2 + b0·I Matrix inner_v = b12 * As6 + b10 * As4 + b8 * As2; Matrix V = As6 * inner_v + b6 * As6 + b4 * As4 + b2 * As2 + b0 * I; // (V - U) · F = V + U を解く Matrix F = solve_multi(Matrix(V - U), Matrix(V + U)); // s 回自乗 for (int i = 0; i < s; ++i) { F = F * F; } return F; } //===================================================================== // sqrtm — 行列平方根 //===================================================================== /** * @brief 行列平方根 A^{1/2} (主値) * * Schur 分解 + Parlett 再帰 (Björck-Hammarling 1983)。 * A の全固有値が負の実軸上にないとき、主値平方根が存在する。 * * @tparam T 要素の型 (float, double) * @param A 入力行列 (n×n 正方行列) * @return A^{1/2} * @throws DimensionError A が正方行列でない場合 * @throws MathError 負の実固有値が存在する場合 */ template Matrix sqrtm(const Matrix& A) { const auto n = A.rows(); if (n != A.cols()) { throw DimensionError("sqrtm: matrix must be square"); } if (n == 0) { return A; } if (n == 1) { if (A(0, 0) < T(0)) { throw MathError("sqrtm: matrix has negative real eigenvalue"); } Matrix result(1, 1); result(0, 0) = std::sqrt(A(0, 0)); return result; } // Schur 分解: A = Q·T·Qᵀ auto [T_mat, Q] = schur_decomposition(A); // ブロック構造を検出 std::vector block_starts, block_sizes; detail_matfun::detect_blocks(T_mat, n, block_starts, block_sizes); // 結果行列 F = sqrtm(T) を初期化 Matrix F(n, n, T(0)); // Step 1: 対角ブロックの計算 for (std::size_t bi = 0; bi < block_starts.size(); ++bi) { std::size_t i = block_starts[bi]; std::size_t sz = block_sizes[bi]; if (sz == 1) { // 1×1 ブロック: sqrt(T_ii) if (T_mat(i, i) < T(0)) { throw MathError("sqrtm: matrix has negative real eigenvalue"); } F(i, i) = std::sqrt(T_mat(i, i)); } else { // 2×2 ブロック: 複素共役固有値 α±iβ // √(α+iβ) = √r · (cos(θ/2) + i·sin(θ/2)) // r = |λ| = √(α²+β²), θ = arg(λ) = atan2(β, α) T alpha, beta; detail_matfun::block2_eigenvalues(T_mat, i, alpha, beta); T r = std::sqrt(alpha * alpha + beta * beta); T theta = std::atan2(beta, alpha); T sqrt_r = std::sqrt(r); T u = sqrt_r * std::cos(theta / T(2)); T v = sqrt_r * std::sin(theta / T(2)); detail_matfun::apply_block2_function(T_mat, i, F, alpha, beta, u, v); } } // Step 2: 上対角ブロックの計算 (Parlett 再帰) // 対角から離れる方向にブロック列を処理 for (std::size_t d = 1; d < block_starts.size(); ++d) { for (std::size_t bi = 0; bi + d < block_starts.size(); ++bi) { std::size_t bj = bi + d; std::size_t ri = block_starts[bi]; std::size_t p = block_sizes[bi]; std::size_t ci = block_starts[bj]; std::size_t q = block_sizes[bj]; detail_matfun::solve_sylvester_block(F, T_mat, F, ri, p, ci, q, n); } } // Step 3: 復元 sqrtm(A) = Q·F·Qᵀ Matrix Qt = Q.transpose(); return Q * F * Qt; } //===================================================================== // logm — 行列対数関数 //===================================================================== /** * @brief 行列対数 log(A) (主値) * * Schur 分解 + Parlett 再帰。 * A の全固有値が負の実軸上にないとき、主値対数が存在する。 * * @tparam T 要素の型 (float, double) * @param A 入力行列 (n×n 正方行列) * @return log(A) * @throws DimensionError A が正方行列でない場合 * @throws MathError 非正の実固有値が存在する場合 */ template Matrix logm(const Matrix& A) { const auto n = A.rows(); if (n != A.cols()) { throw DimensionError("logm: matrix must be square"); } if (n == 0) { return A; } if (n == 1) { if (A(0, 0) <= T(0)) { throw MathError("logm: matrix has non-positive real eigenvalue"); } Matrix result(1, 1); result(0, 0) = std::log(A(0, 0)); return result; } // Schur 分解: A = Q·T·Qᵀ auto [T_mat, Q] = schur_decomposition(A); // ブロック構造を検出 std::vector block_starts, block_sizes; detail_matfun::detect_blocks(T_mat, n, block_starts, block_sizes); // 結果行列 F = logm(T) を初期化 Matrix F(n, n, T(0)); // Step 1: 対角ブロックの計算 for (std::size_t bi = 0; bi < block_starts.size(); ++bi) { std::size_t i = block_starts[bi]; std::size_t sz = block_sizes[bi]; if (sz == 1) { // 1×1 ブロック: log(T_ii) if (T_mat(i, i) <= T(0)) { throw MathError("logm: matrix has non-positive real eigenvalue"); } F(i, i) = std::log(T_mat(i, i)); } else { // 2×2 ブロック: 複素共役固有値 α±iβ T alpha, beta; detail_matfun::block2_eigenvalues(T_mat, i, alpha, beta); T r2 = alpha * alpha + beta * beta; T u = std::log(r2) / T(2); // ½·log(|λ|²) T v = std::atan2(beta, alpha); detail_matfun::apply_block2_function(T_mat, i, F, alpha, beta, u, v); } } // Step 2: 上対角ブロックの計算 (Parlett 再帰) for (std::size_t d = 1; d < block_starts.size(); ++d) { for (std::size_t bi = 0; bi + d < block_starts.size(); ++bi) { std::size_t bj = bi + d; std::size_t ri = block_starts[bi]; std::size_t p = block_sizes[bi]; std::size_t ci = block_starts[bj]; std::size_t q = block_sizes[bj]; detail_matfun::solve_sylvester_block(F, T_mat, F, ri, p, ci, q, n); } } // Step 3: 復元 logm(A) = Q·F·Qᵀ Matrix Qt = Q.transpose(); return Q * F * Qt; } } // namespace algorithms } // namespace calx #endif // CALX_MATRIX_FUNCTIONS_HPP