// Copyright (C) 2026 Kiyotsugu Arai // SPDX-License-Identifier: LGPL-3.0-or-later /** * @file sparse_matrix_algorithms.hpp * @brief 疎行列向け反復解法・前処理器の実装 * * 反復解法: CG, BiCG, BiCGSTAB, GMRES(m) * 前処理器: Jacobi, SSOR, ILU(0), IC(0) * 全ソルバーは SolverResult を返し、前処理は callable で注入する。 */ #ifndef CALX_SPARSE_MATRIX_ALGORITHMS_HPP #define CALX_SPARSE_MATRIX_ALGORITHMS_HPP #include #include #include #include #include #include #include #include #include #include "../concepts/algebraic_concepts.hpp" #include "sparse_matrix.hpp" #include "vector.hpp" #include "convergence_criteria.hpp" namespace calx { namespace sparse_algorithms { // ============================================================================ // SolverResult — ソルバーの戻り値 // ============================================================================ template struct SolverResult { Vector x; ///< 解ベクトル T residual_norm; ///< 最終残差ノルム std::size_t iterations; ///< 反復回数 bool converged; ///< 収束したか }; // ============================================================================ // Preconditioner 型定義 — callable (Vector → Vector) // ============================================================================ template using Preconditioner = std::function(const Vector&)>; /// 恒等前処理 (前処理なし) template Preconditioner identity_preconditioner() { return [](const Vector& r) -> Vector { return r; }; } // ============================================================================ // ヘルパー: 疎行列の対角抽出 // ============================================================================ template requires concepts::OrderedField && std::integral std::vector sparse_diagonal(const SparseMatrix& A) { const auto n = A.rows(); std::vector diag(n, T{0}); for (IndexType i = 0; i < n; ++i) { diag[i] = A.coeff(i, i); } return diag; } // ============================================================================ // 前処理器ファクトリ // ============================================================================ /** * @brief Jacobi (対角) 前処理 * * M = diag(A), M^{-1} r = r[i] / A(i,i) * O(n) で最も軽量な前処理。対角優位行列に有効。 */ template requires concepts::OrderedField && std::integral Preconditioner jacobi_preconditioner(const SparseMatrix& A) { auto diag = sparse_diagonal(A); const auto n = A.rows(); // ゼロ対角のチェック for (IndexType i = 0; i < n; ++i) { if (std::abs(diag[i]) < std::numeric_limits::epsilon()) { throw std::invalid_argument( "Jacobi preconditioner: zero diagonal element at row " + std::to_string(i)); } } return [diag = std::move(diag)](const Vector& r) -> Vector { const auto m = r.size(); Vector z(m); for (std::size_t i = 0; i < m; ++i) { z[i] = r[i] / diag[i]; } return z; }; } /** * @brief SSOR (Symmetric Successive Over-Relaxation) 前処理 * * M = (D/ω + L) D^{-1} (D/ω + U) * 前進・後退代入で M^{-1} r を計算。 * ω=1.0 で対称 Gauss-Seidel に一致。 */ template requires concepts::OrderedField && std::integral Preconditioner ssor_preconditioner( const SparseMatrix& A, T omega = T{1}) { // CSR に変換して保持 auto A_csr = std::make_shared>(A); A_csr->convert_to(SparseStorageFormat::CSR); const auto n = A.rows(); auto diag = sparse_diagonal(A); return [A_csr, diag, omega, n](const Vector& r) -> Vector { Vector y(n, T{0}); Vector z(n, T{0}); // 前進代入: (D/ω + L) y = r for (IndexType i = 0; i < n; ++i) { T sum = r[i]; for (IndexType j = 0; j < i; ++j) { T aij = A_csr->coeff(i, j); if (aij != T{0}) { sum -= aij * y[j]; } } y[i] = omega * sum / diag[i]; } // D/ω のスケーリング: y_i *= diag[i] / omega for (IndexType i = 0; i < n; ++i) { y[i] *= diag[i] / omega; } // 後退代入: (D/ω + U) z = y for (IndexType ii = 0; ii < n; ++ii) { IndexType i = n - 1 - ii; T sum = y[i]; for (IndexType j = i + 1; j < n; ++j) { T aij = A_csr->coeff(i, j); if (aij != T{0}) { sum -= aij * z[j]; } } z[i] = omega * sum / diag[i]; } // SSOR の正しいスケーリング: M_SSOR = ω(2-ω) (D/ω+L) D^{-1} (D/ω+U) // 最終結果に (2-ω) を掛ける for (IndexType i = 0; i < n; ++i) { z[i] *= (T{2} - omega); } return z; }; } /** * @brief ILU(0) (Incomplete LU with zero fill) 前処理 * * A の非ゼロパターン内でのみ LU 分解を行い、L, U を CSR で保持する。 * M^{-1} r は L y = r (前進代入) → U z = y (後退代入) で計算。 */ template requires concepts::OrderedField && std::integral Preconditioner ilu_preconditioner(const SparseMatrix& A) { const auto n = A.rows(); if (A.cols() != n) { throw std::invalid_argument("ILU: matrix must be square"); } // CSR 形式でワーク配列を構築 // 行ごとの密ベクトルで IKJ 版 ILU(0) を実行 // lu_vals[i][j] = (i,j) の LU 値 (Aのパターン内のみ) // まず各行の非ゼロ列インデックスと値を取得 struct RowEntry { IndexType col; T val; }; std::vector> rows(n); for (IndexType i = 0; i < n; ++i) { for (IndexType j = 0; j < n; ++j) { T v = A.coeff(i, j); if (v != T{0}) { rows[i].push_back({j, v}); } } // 列順でソート std::sort(rows[i].begin(), rows[i].end(), [](const RowEntry& a, const RowEntry& b) { return a.col < b.col; }); } // IKJ 版 ILU(0): 行 i の各エントリを更新 // w = A(i,:) のコピー → k < i のピボット行で消去 → L,U にコピー std::vector> L_rows(n), U_rows(n); for (IndexType i = 0; i < n; ++i) { // 行 i の密ワーク配列 (非ゼロ位置のみ) std::vector w(n, T{0}); std::vector nz(n, false); // 非ゼロパターン for (auto& e : rows[i]) { w[e.col] = e.val; nz[e.col] = true; } // k ループ: k < i で nz[k] が true の列に対して消去 for (IndexType k = 0; k < i; ++k) { if (!nz[k]) continue; // U(k,k) で割る T ukk = T{0}; for (auto& e : U_rows[k]) { if (e.col == k) { ukk = e.val; break; } } if (std::abs(ukk) < std::numeric_limits::epsilon()) { continue; // ゼロピボットはスキップ } w[k] /= ukk; // j > k で U(k,j) != 0 かつ nz[j] の要素を更新 for (auto& e : U_rows[k]) { if (e.col > k && nz[e.col]) { w[e.col] -= w[k] * e.val; } } } // L, U に格納 for (auto& e : rows[i]) { IndexType j = e.col; if (j < i) { L_rows[i].push_back({j, w[j]}); } else { U_rows[i].push_back({j, w[j]}); } } // L の対角は 1 L_rows[i].push_back({i, T{1}}); std::sort(L_rows[i].begin(), L_rows[i].end(), [](const RowEntry& a, const RowEntry& b) { return a.col < b.col; }); } // shared_ptr で L, U 行データを保持 auto l_data = std::make_shared>>(std::move(L_rows)); auto u_data = std::make_shared>>(std::move(U_rows)); return [l_data, u_data, n](const Vector& r) -> Vector { // 前進代入: L y = r Vector y(n, T{0}); for (IndexType i = 0; i < n; ++i) { T sum = r[i]; for (auto& e : (*l_data)[i]) { if (e.col < i) { sum -= e.val * y[e.col]; } } // L(i,i) = 1 なので割る必要なし y[i] = sum; } // 後退代入: U z = y Vector z(n, T{0}); for (IndexType ii = 0; ii < n; ++ii) { IndexType i = n - 1 - ii; T sum = y[i]; T uii = T{0}; for (auto& e : (*u_data)[i]) { if (e.col == i) { uii = e.val; } else if (e.col > i) { sum -= e.val * z[e.col]; } } if (std::abs(uii) < std::numeric_limits::epsilon()) { z[i] = T{0}; } else { z[i] = sum / uii; } } return z; }; } // ============================================================================ // 後方互換: incomplete_lu_decomposition (L, U ペアを返す版) // ============================================================================ template requires concepts::OrderedField && std::integral std::pair, SparseMatrix> incomplete_lu_decomposition( const SparseMatrix& A, [[maybe_unused]] int fill_level = 0) { const auto n = A.rows(); if (A.cols() != n) { throw std::invalid_argument("ILU: matrix must be square"); } struct RowEntry { IndexType col; T val; }; std::vector> rows(n); for (IndexType i = 0; i < n; ++i) { for (IndexType j = 0; j < n; ++j) { T v = A.coeff(i, j); if (v != T{0}) { rows[i].push_back({j, v}); } } std::sort(rows[i].begin(), rows[i].end(), [](const RowEntry& a, const RowEntry& b) { return a.col < b.col; }); } std::vector> L_rows(n), U_rows(n); for (IndexType i = 0; i < n; ++i) { std::vector w(n, T{0}); std::vector nz(n, false); for (auto& e : rows[i]) { w[e.col] = e.val; nz[e.col] = true; } for (IndexType k = 0; k < i; ++k) { if (!nz[k]) continue; T ukk = T{0}; for (auto& e : U_rows[k]) { if (e.col == k) { ukk = e.val; break; } } if (std::abs(ukk) < std::numeric_limits::epsilon()) continue; w[k] /= ukk; for (auto& e : U_rows[k]) { if (e.col > k && nz[e.col]) { w[e.col] -= w[k] * e.val; } } } for (auto& e : rows[i]) { if (e.col < i) L_rows[i].push_back({e.col, w[e.col]}); else U_rows[i].push_back({e.col, w[e.col]}); } L_rows[i].push_back({i, T{1}}); std::sort(L_rows[i].begin(), L_rows[i].end(), [](const RowEntry& a, const RowEntry& b) { return a.col < b.col; }); } SparseMatrix L(n, n, SparseStorageFormat::COO); SparseMatrix U(n, n, SparseStorageFormat::COO); for (IndexType i = 0; i < n; ++i) { for (auto& e : L_rows[i]) L.set_coeff(i, e.col, e.val); for (auto& e : U_rows[i]) U.set_coeff(i, e.col, e.val); } return {L, U}; } // ============================================================================ // IC(0) (Incomplete Cholesky with zero fill) 前処理 // ============================================================================ /** * @brief IC(0) (Incomplete Cholesky with zero fill) 前処理 * * 対称正定値行列 A の下三角非ゼロパターン内でのみ Cholesky 分解を行う。 * A ≈ L * L^T (fill-in なし) * M^{-1} r は L y = r (前進代入) → L^T z = y (後退代入) で計算。 */ template requires concepts::OrderedField && std::integral Preconditioner incomplete_cholesky_preconditioner(const SparseMatrix& A) { const auto n = A.rows(); if (A.cols() != n) { throw std::invalid_argument("IC: matrix must be square"); } // 下三角の非ゼロパターンを行ごとに取得 (対角含む) struct Entry { IndexType col; T val; }; std::vector> L_rows(n); for (IndexType i = 0; i < n; ++i) { for (IndexType j = 0; j <= i; ++j) { T v = A.coeff(i, j); if (v != T{0}) { L_rows[i].push_back({j, v}); } } // 列順でソート済みのはず (j は昇順ループ) だが念のため std::sort(L_rows[i].begin(), L_rows[i].end(), [](const Entry& a, const Entry& b) { return a.col < b.col; }); // 対角要素がパターンに存在するか確認 if (L_rows[i].empty() || L_rows[i].back().col != i) { throw std::runtime_error("IC: missing diagonal element (matrix may not be SPD)"); } } // 各列の非ゼロ行を高速検索するため、列→行リストを構築 // col_rows[j] = {L_rows[i] 内で col==j を持つ行 i のリスト} std::vector> col_rows(n); for (IndexType i = 0; i < n; ++i) { for (auto& e : L_rows[i]) { if (e.col < i) { // 対角以外の下三角 col_rows[e.col].push_back(i); } } } // IC(0) 分解: 行ごとに処理 // L(i,j) = (A(i,j) - Σ_{k> row_col_idx(n); // row_col_idx[i][p] = L_rows[i][p].col for (IndexType i = 0; i < n; ++i) { row_col_idx[i].reserve(L_rows[i].size()); for (auto& e : L_rows[i]) { row_col_idx[i].push_back(e.col); } } // 行 i を処理 for (IndexType i = 0; i < n; ++i) { for (std::size_t p = 0; p < L_rows[i].size(); ++p) { IndexType j = L_rows[i][p].col; T sum = T{0}; if (j < i) { // 非対角: L(i,j) = (A(i,j) - Σ_{k::epsilon()) { throw std::runtime_error("IC: zero diagonal encountered (matrix may not be SPD)"); } L_rows[i][p].val = (L_rows[i][p].val - sum) / ljj; } else { // 対角: L(i,i) = sqrt(A(i,i) - Σ_{k>>(std::move(L_rows)); auto dim = n; return [l_data, dim](const Vector& r) -> Vector { auto n = dim; // 前進代入: L y = r Vector y(n, T{0}); for (IndexType i = 0; i < n; ++i) { T sum = r[i]; for (auto& e : (*l_data)[i]) { if (e.col < i) { sum -= e.val * y[e.col]; } } // L(i,i) は行の最後の要素 y[i] = sum / (*l_data)[i].back().val; } // 後退代入: L^T z = y // 行ベース変換: z = y から始め、行 i (降順) で z[i] /= L(i,i), // その後 L(i,k) (k < i) について z[k] -= L(i,k) * z[i] Vector z = y; for (IndexType ii = 0; ii < n; ++ii) { IndexType i = n - 1 - ii; z[i] /= (*l_data)[i].back().val; for (auto& e : (*l_data)[i]) { if (e.col < i) { z[e.col] -= e.val * z[i]; } } } return z; }; } // ============================================================================ // 後方互換: incomplete_cholesky_decomposition (L を返す版) // ============================================================================ /** * @brief IC(0) 分解の L 行列を返す */ template requires concepts::OrderedField && std::integral SparseMatrix incomplete_cholesky_decomposition( const SparseMatrix& A, [[maybe_unused]] int fill_level = 0) { const auto n = A.rows(); if (A.cols() != n) { throw std::invalid_argument("IC: matrix must be square"); } // 下三角の非ゼロパターンを取得 struct Entry { IndexType col; T val; }; std::vector> L_rows(n); for (IndexType i = 0; i < n; ++i) { for (IndexType j = 0; j <= i; ++j) { T v = A.coeff(i, j); if (v != T{0}) { L_rows[i].push_back({j, v}); } } std::sort(L_rows[i].begin(), L_rows[i].end(), [](const Entry& a, const Entry& b) { return a.col < b.col; }); if (L_rows[i].empty() || L_rows[i].back().col != i) { throw std::runtime_error("IC: missing diagonal element"); } } // IC(0) 分解 for (IndexType i = 0; i < n; ++i) { for (std::size_t p = 0; p < L_rows[i].size(); ++p) { IndexType j = L_rows[i][p].col; T sum = T{0}; if (j < i) { std::size_t pi = 0, pj = 0; while (pi < L_rows[i].size() && pj < L_rows[j].size()) { IndexType ci = L_rows[i][pi].col; IndexType cj = L_rows[j][pj].col; if (ci < j && cj < j) { if (ci == cj) { sum += L_rows[i][pi].val * L_rows[j][pj].val; ++pi; ++pj; } else if (ci < cj) { ++pi; } else { ++pj; } } else { break; } } T ljj = L_rows[j].back().val; if (std::abs(ljj) < std::numeric_limits::epsilon()) { throw std::runtime_error("IC: zero diagonal encountered"); } L_rows[i][p].val = (L_rows[i][p].val - sum) / ljj; } else { for (std::size_t q = 0; q < p; ++q) { sum += L_rows[i][q].val * L_rows[i][q].val; } T diag = L_rows[i][p].val - sum; if (diag <= T{0}) { throw std::runtime_error("IC: non-positive diagonal"); } L_rows[i][p].val = std::sqrt(diag); } } } SparseMatrix L(n, n, SparseStorageFormat::COO); for (IndexType i = 0; i < n; ++i) { for (auto& e : L_rows[i]) { L.set_coeff(i, e.col, e.val); } } return L; } // ============================================================================ // 三角行列の前進/後退代入 (疎行列版) // ============================================================================ template requires concepts::OrderedField && std::integral Vector triangular_solve( const SparseMatrix& M, const Vector& b, bool lower = true) { const auto n = M.rows(); if (M.cols() != n) throw std::invalid_argument("Matrix must be square"); if (b.size() != n) throw std::invalid_argument("Incompatible vector dimensions"); Vector x(n, T{0}); if (lower) { for (IndexType i = 0; i < n; ++i) { T sum = b[i]; for (IndexType j = 0; j < i; ++j) { T v = M.coeff(i, j); if (v != T{0}) sum -= v * x[j]; } x[i] = sum / M.coeff(i, i); } } else { for (IndexType ii = 0; ii < n; ++ii) { IndexType i = n - 1 - ii; T sum = b[i]; for (IndexType j = i + 1; j < n; ++j) { T v = M.coeff(i, j); if (v != T{0}) sum -= v * x[j]; } x[i] = sum / M.coeff(i, i); } } return x; } // ============================================================================ // CG (Conjugate Gradient) — 対称正定値行列用 // ============================================================================ /** * @brief 前処理付き共役勾配法 (Preconditioned CG) * * 対称正定値疎行列 A に対して Ax = b を解く。 * 前処理 M を指定すると M^{-1} A x = M^{-1} b を CG で解く。 */ template requires concepts::OrderedField && std::integral SolverResult conjugate_gradient( const SparseMatrix& A, const Vector& b, const ConvergenceCriteria& criteria, Preconditioner precond = {}, std::optional> x0 = std::nullopt) { const auto n = A.rows(); if (A.cols() != n) throw std::invalid_argument("CG: matrix must be square"); if (b.size() != n) throw std::invalid_argument("CG: incompatible dimensions"); if (!precond) precond = identity_preconditioner(); Vector x = x0.value_or(Vector(n, T{0})); Vector r = b - A * x; const T init_rnorm = norm(r); if (init_rnorm <= criteria.absolute_tolerance()) { return {x, init_rnorm, 0, true}; } Vector z = precond(r); Vector p = z; T rz = dot(r, z); // 全出口共通の収束判定ヘルパー auto is_converged = [&](T rnorm) { return rnorm <= criteria.absolute_tolerance() || rnorm <= criteria.relative_tolerance() * init_rnorm; }; for (std::size_t iter = 0; iter < criteria.max_iterations(); ++iter) { Vector Ap = A * p; T pAp = dot(p, Ap); if (std::abs(pAp) < std::numeric_limits::epsilon()) { // 内部残差 r に蓄積誤差があるため、真の残差で判定 T rnorm = norm(b - A * x); return {x, rnorm, iter, is_converged(rnorm)}; } T alpha = rz / pAp; x += p * alpha; r -= Ap * alpha; T rnorm = norm(r); if (is_converged(rnorm)) { return {x, rnorm, iter + 1, true}; } z = precond(r); T rz_new = dot(r, z); T beta = rz_new / rz; rz = rz_new; p = z + p * beta; } // 最大反復到達時も収束判定 (内部残差の蓄積誤差で判定ミスの可能性) T final_rnorm = norm(b - A * x); return {x, final_rnorm, criteria.max_iterations(), is_converged(final_rnorm)}; } // ============================================================================ // BiCG (BiConjugate Gradient) — 非対称行列用 // ============================================================================ template requires concepts::OrderedField && std::integral SolverResult biconjugate_gradient( const SparseMatrix& A, const Vector& b, const ConvergenceCriteria& criteria, std::optional> x0 = std::nullopt) { const auto n = A.rows(); if (A.cols() != n) throw std::invalid_argument("BiCG: matrix must be square"); if (b.size() != n) throw std::invalid_argument("BiCG: incompatible dimensions"); const auto At = A.transpose(); Vector x = x0.value_or(Vector(n, T{0})); Vector r = b - A * x; Vector r_tilde = r; const T init_rnorm = norm(r); if (init_rnorm <= criteria.absolute_tolerance()) { return {x, init_rnorm, 0, true}; } Vector p = r; Vector p_tilde = r_tilde; T rho = dot(r_tilde, r); // breakdown 閾値: 初期残差スケールに対する相対値 const T breakdown_tol = std::numeric_limits::epsilon() * init_rnorm * init_rnorm; // 収束判定ヘルパー (真の残差を使用) auto check_convergence = [&](std::size_t iter) -> SolverResult { T rnorm = norm(b - A * x); bool conv = (rnorm <= criteria.absolute_tolerance() || rnorm <= criteria.relative_tolerance() * init_rnorm); return {x, rnorm, iter, conv}; }; for (std::size_t iter = 0; iter < criteria.max_iterations(); ++iter) { if (std::abs(rho) < breakdown_tol) { return check_convergence(iter); } Vector Ap = A * p; Vector Atp = At * p_tilde; T denom = dot(p_tilde, Ap); if (std::abs(denom) < breakdown_tol) { return check_convergence(iter); } T alpha = rho / denom; x += p * alpha; r -= Ap * alpha; r_tilde -= Atp * alpha; T rnorm = norm(r); if (rnorm <= criteria.absolute_tolerance() || rnorm <= criteria.relative_tolerance() * init_rnorm) { return {x, rnorm, iter + 1, true}; } T rho_new = dot(r_tilde, r); T beta = rho_new / rho; rho = rho_new; p = r + p * beta; p_tilde = r_tilde + p_tilde * beta; } // 最大反復到達時: 真の残差で再判定 T final_rnorm = norm(b - A * x); bool conv = (final_rnorm <= criteria.absolute_tolerance() || final_rnorm <= criteria.relative_tolerance() * init_rnorm); return {x, final_rnorm, criteria.max_iterations(), conv}; } // ============================================================================ // BiCGSTAB (Stabilized BiConjugate Gradient) — 非対称行列用 // ============================================================================ template requires concepts::OrderedField && std::integral SolverResult bicgstab( const SparseMatrix& A, const Vector& b, const ConvergenceCriteria& criteria, Preconditioner precond = {}, std::optional> x0 = std::nullopt) { const auto n = A.rows(); if (A.cols() != n) throw std::invalid_argument("BiCGSTAB: matrix must be square"); if (b.size() != n) throw std::invalid_argument("BiCGSTAB: incompatible dimensions"); if (!precond) precond = identity_preconditioner(); Vector x = x0.value_or(Vector(n, T{0})); Vector r = b - A * x; Vector r_hat = r; const T init_rnorm = norm(r); if (init_rnorm <= criteria.absolute_tolerance()) { return {x, init_rnorm, 0, true}; } T rho = T{1}, alpha = T{1}, omega_val = T{1}; Vector p(n, T{0}); Vector v(n, T{0}); // breakdown 判定の閾値: 初期残差スケールに対する相対値 const T eps = std::numeric_limits::epsilon(); const T breakdown_tol = eps * init_rnorm * init_rnorm; const T stag_tol = eps * init_rnorm; // 真の残差で収束判定するヘルパー (内部残差 r の蓄積誤差対策) auto check_true_convergence = [&](std::size_t iter) -> SolverResult { T rnorm = norm(b - A * x); bool conv = (rnorm <= criteria.absolute_tolerance() || rnorm <= criteria.relative_tolerance() * init_rnorm); return {x, rnorm, iter, conv}; }; for (std::size_t iter = 0; iter < criteria.max_iterations(); ++iter) { T rho_new = dot(r_hat, r); if (std::abs(rho_new) < breakdown_tol) { return check_true_convergence(iter); } if (iter == 0) { p = r; } else { T beta = (rho_new / rho) * (alpha / omega_val); p = r + (p - v * omega_val) * beta; } rho = rho_new; // 前処理付き: p_hat = M^{-1} p Vector p_hat = precond(p); v = A * p_hat; T denom = dot(r_hat, v); if (std::abs(denom) < breakdown_tol) { return check_true_convergence(iter); } alpha = rho / denom; Vector s = r - v * alpha; T s_norm = norm(s); if (s_norm <= criteria.absolute_tolerance() || s_norm <= criteria.relative_tolerance() * init_rnorm) { x += p_hat * alpha; return {x, s_norm, iter + 1, true}; } // 前処理付き: s_hat = M^{-1} s Vector s_hat = precond(s); Vector t = A * s_hat; T tt = dot(t, t); if (std::abs(tt) < breakdown_tol) { x += p_hat * alpha; return check_true_convergence(iter + 1); } omega_val = dot(t, s) / tt; x += p_hat * alpha + s_hat * omega_val; r = s - t * omega_val; T rnorm = norm(r); if (rnorm <= criteria.absolute_tolerance() || rnorm <= criteria.relative_tolerance() * init_rnorm) { return {x, rnorm, iter + 1, true}; } if (std::abs(omega_val) < stag_tol) { return check_true_convergence(iter + 1); } } // 最大反復到達時: 真の残差で再判定 (内部残差 r の蓄積誤差対策) T final_rnorm = norm(b - A * x); bool conv = (final_rnorm <= criteria.absolute_tolerance() || final_rnorm <= criteria.relative_tolerance() * init_rnorm); return {x, final_rnorm, criteria.max_iterations(), conv}; } // ============================================================================ // GMRES(m) (Generalized Minimum Residual) — 非対称行列用 // ============================================================================ /** * @brief リスタート付き GMRES * * Arnoldi + Givens 回転を逐次適用。リスタートで外部反復。 * @param restart Krylov 部分空間の最大次元 (デフォルト 30) */ template requires concepts::OrderedField && std::integral SolverResult gmres( const SparseMatrix& A, const Vector& b, const ConvergenceCriteria& criteria, std::size_t restart = 30, Preconditioner precond = {}, std::optional> x0 = std::nullopt) { const auto n = static_cast(A.rows()); if (static_cast(A.cols()) != n) throw std::invalid_argument("GMRES: matrix must be square"); if (b.size() != n) throw std::invalid_argument("GMRES: incompatible dimensions"); if (!precond) precond = identity_preconditioner(); if (restart == 0 || restart > n) restart = n; Vector x = x0.value_or(Vector(n, T{0})); const T b_norm = norm(b); const T threshold = criteria.absolute_tolerance() + criteria.relative_tolerance() * b_norm; std::size_t total_iter = 0; while (total_iter < criteria.max_iterations()) { // 残差 Vector r = precond(b - A * x); T r_norm = norm(r); if (r_norm <= threshold) { return {x, r_norm, total_iter, true}; } // Arnoldi 基底 const std::size_t m = std::min(restart, criteria.max_iterations() - total_iter); std::vector> Q(m + 1); Q[0] = r / r_norm; // 上 Hessenberg 行列 (m+1 x m) std::vector> H(m + 1, std::vector(m, T{0})); // Givens 回転パラメータ std::vector cs(m, T{0}), sn(m, T{0}); // 変換済み右辺 std::vector g(m + 1, T{0}); g[0] = r_norm; std::size_t j = 0; for (; j < m; ++j) { // Arnoldi ステップ: w = M^{-1} A q_j Vector w = precond(A * Q[j]); // Modified Gram-Schmidt for (std::size_t i = 0; i <= j; ++i) { H[i][j] = dot(Q[i], w); w -= Q[i] * H[i][j]; } H[j + 1][j] = norm(w); // Happy breakdown if (std::abs(H[j + 1][j]) < std::numeric_limits::epsilon() * T{100}) { // 以前の Givens 回転を H 列に適用 for (std::size_t i = 0; i < j; ++i) { T tmp = cs[i] * H[i][j] + sn[i] * H[i + 1][j]; H[i + 1][j] = -sn[i] * H[i][j] + cs[i] * H[i + 1][j]; H[i][j] = tmp; } // j 番目の Givens 回転 T rr = std::sqrt(H[j][j] * H[j][j] + H[j + 1][j] * H[j + 1][j]); if (rr > T{0}) { cs[j] = H[j][j] / rr; sn[j] = H[j + 1][j] / rr; H[j][j] = rr; H[j + 1][j] = T{0}; T g_new = -sn[j] * g[j]; g[j] = cs[j] * g[j]; g[j + 1] = g_new; } ++j; break; } Q[j + 1] = w / H[j + 1][j]; // 以前の Givens 回転を H 列に適用 for (std::size_t i = 0; i < j; ++i) { T tmp = cs[i] * H[i][j] + sn[i] * H[i + 1][j]; H[i + 1][j] = -sn[i] * H[i][j] + cs[i] * H[i + 1][j]; H[i][j] = tmp; } // j 番目の Givens 回転の計算 T rr = std::sqrt(H[j][j] * H[j][j] + H[j + 1][j] * H[j + 1][j]); cs[j] = H[j][j] / rr; sn[j] = H[j + 1][j] / rr; H[j][j] = rr; H[j + 1][j] = T{0}; // 右辺の更新 T g_new = -sn[j] * g[j]; g[j] = cs[j] * g[j]; g[j + 1] = g_new; r_norm = std::abs(g[j + 1]); ++total_iter; if (r_norm <= threshold) { ++j; break; } } // 後退代入: R y = g (R は H の上三角部分) const std::size_t dim = j; std::vector y(dim, T{0}); for (std::size_t ii = 0; ii < dim; ++ii) { std::size_t idx = dim - 1 - ii; y[idx] = g[idx]; for (std::size_t k = idx + 1; k < dim; ++k) { y[idx] -= H[idx][k] * y[k]; } if (std::abs(H[idx][idx]) > std::numeric_limits::epsilon()) { y[idx] /= H[idx][idx]; } } // 解の更新: x += Q * y for (std::size_t k = 0; k < dim; ++k) { x += Q[k] * y[k]; } if (r_norm <= threshold) { return {x, r_norm, total_iter, true}; } } T final_rnorm = norm(b - A * x); return {x, final_rnorm, total_iter, false}; } // ============================================================================ // 便利関数: ILU 前処理付き BiCGSTAB (後方互換) // ============================================================================ template requires concepts::OrderedField && std::integral SolverResult ilu_bicgstab( const SparseMatrix& A, const Vector& b, const ConvergenceCriteria& criteria, int fill_level = 0, std::optional> x0 = std::nullopt) { (void)fill_level; // ILU(0) のみサポート auto precond = ilu_preconditioner(A); return bicgstab(A, b, criteria, precond, std::move(x0)); } // ============================================================================ // Arnoldi 反復 (単独 API) // ============================================================================ template requires concepts::OrderedField && std::integral std::pair>, std::vector>> arnoldi_iteration( const SparseMatrix& A, const Vector& v, std::size_t m) { const auto n = static_cast(A.rows()); if (static_cast(A.cols()) != n) throw std::invalid_argument("Arnoldi: matrix must be square"); if (v.size() != n) throw std::invalid_argument("Arnoldi: incompatible dimensions"); m = std::min(m, n); std::vector> Q(m + 1); T v_norm = norm(v); if (v_norm < std::numeric_limits::epsilon()) { throw std::invalid_argument("Arnoldi: zero initial vector"); } Q[0] = v / v_norm; std::vector> H(m + 1, std::vector(m, T{0})); for (std::size_t j = 0; j < m; ++j) { Vector w = A * Q[j]; for (std::size_t i = 0; i <= j; ++i) { H[i][j] = dot(Q[i], w); w -= Q[i] * H[i][j]; } H[j + 1][j] = norm(w); if (std::abs(H[j + 1][j]) < std::numeric_limits::epsilon()) { Q.resize(j + 2); H.resize(j + 2); for (auto& row : H) row.resize(j + 1); break; } Q[j + 1] = w / H[j + 1][j]; } return {Q, H}; } // ============================================================================ // EigenResult — 固有値ソルバーの戻り値 // ============================================================================ template struct EigenResult { std::vector eigenvalues; ///< 固有値 (要求個数) std::vector> eigenvectors; ///< 対応する固有ベクトル std::size_t iterations; ///< 反復回数 bool converged; ///< 全固有値が収束したか }; // ============================================================================ // TridiagonalEigen — 三重対角行列の固有値 (QR 法) // ============================================================================ /// @brief 三重対角行列の全固有値を QL 法 (LAPACK 方式) で計算 /// @param d 対角要素 (n 要素) /// @param e 副対角要素 (n-1 要素) /// @return 固有値ベクトル (昇順ソート) template requires concepts::OrderedField std::vector tridiagonal_eigenvalues( std::vector d, std::vector e) { int n = static_cast(d.size()); if (n == 0) return {}; if (n == 1) return {d[0]}; const T eps = std::numeric_limits::epsilon(); for (int l = 0; l < n; ++l) { int iter = 0; while (true) { // Find smallest m >= l such that e[m] ≈ 0 int m = l; for (; m < n - 1; ++m) { T dd = std::abs(d[m]) + std::abs(d[m + 1]); if (std::abs(e[m]) <= eps * dd) break; } if (m == l) break; // eigenvalue d[l] converged if (++iter > 30 * n) break; // QL implicit shift (Wilkinson) T g = (d[l + 1] - d[l]) / (T(2) * e[l]); T r = std::hypot(g, T(1)); g = d[m] - d[l] + e[l] / (g + std::copysign(r, g)); T s = T(1); T c = T(1); T p = T(0); bool deflated = false; for (int i = m - 1; i >= l; --i) { T f = s * e[i]; T b = c * e[i]; r = std::hypot(f, g); e[i + 1] = r; if (r == T(0)) { d[i + 1] -= p; e[m] = T(0); deflated = true; break; } s = f / r; c = g / r; g = d[i + 1] - p; r = (d[i] - g) * s + T(2) * c * b; p = s * r; d[i + 1] = g + p; g = c * r - b; } if (!deflated) { d[l] -= p; e[l] = g; e[m] = T(0); } } } std::sort(d.begin(), d.end()); return d; } // ============================================================================ // Lanczos 法 — 対称疎行列の少数固有値 // ============================================================================ /// @brief Lanczos 法 (対称疎行列の少数固有値) /// @param A 対称疎行列 /// @param k 求める固有値の数 /// @param which "LM"/"LA" (最大), "SM"/"SA" (最小) /// @param max_iter 最大 Krylov 次元 (0=auto) /// @param tol 未使用 (互換用) template requires concepts::OrderedField && std::integral EigenResult lanczos( const SparseMatrix& A, std::size_t k = 6, const std::string& which = "LM", std::size_t max_iter = 0, T tol = T(0)) { (void)tol; const auto n = static_cast(A.rows()); if (static_cast(A.cols()) != n) throw std::invalid_argument("lanczos: matrix must be square"); if (k == 0 || k > n) throw std::invalid_argument("lanczos: invalid k"); // Krylov 次元: min(n, max(2k+1, 20)) std::size_t m = (max_iter > 0) ? std::min(max_iter, n) : std::min(std::max(k * 2 + 1, std::size_t(20)), n); // 初期ベクトル Vector q(n); T inv_sqrt_n = T(1) / std::sqrt(static_cast(n)); for (std::size_t i = 0; i < n; ++i) q[i] = inv_sqrt_n; std::vector alpha_vec(m); std::vector beta_vec(m); std::vector> Q(m); // Lanczos 三重対角化 (full reorthogonalization) Q[0] = q; for (std::size_t j = 0; j < m; ++j) { Vector w = A * Q[j]; alpha_vec[j] = dot(Q[j], w); w -= Q[j] * alpha_vec[j]; if (j > 0) w -= Q[j - 1] * beta_vec[j - 1]; // Full reorthogonalization for (std::size_t i = 0; i <= j; ++i) { T h = dot(Q[i], w); w -= Q[i] * h; } beta_vec[j] = norm(w); if (j + 1 < m) { if (std::abs(beta_vec[j]) < std::numeric_limits::epsilon() * T(100)) { // Invariant subspace found — fill with random-ish orthogonal vector Q[j + 1] = Vector(n); for (std::size_t idx = 0; idx < n; ++idx) Q[j + 1][idx] = T(0); Q[j + 1][(j + 1) % n] = T(1); // Reorthogonalize for (std::size_t i = 0; i <= j; ++i) { T h = dot(Q[i], Q[j + 1]); Q[j + 1] -= Q[i] * h; } T nn2 = norm(Q[j + 1]); if (nn2 > std::numeric_limits::epsilon()) Q[j + 1] = Q[j + 1] / nn2; } else { Q[j + 1] = w / beta_vec[j]; } } } // 三重対角行列の固有値を計算 std::vector a_copy(alpha_vec.begin(), alpha_vec.begin() + static_cast(m)); std::vector b_copy(beta_vec.begin(), beta_vec.begin() + static_cast(m) - 1); auto eigs = tridiagonal_eigenvalues(a_copy, b_copy); // which に応じて選択 EigenResult result; result.eigenvalues.resize(k); if (which == "LA" || which == "LM") { for (std::size_t i = 0; i < k; ++i) result.eigenvalues[i] = eigs[eigs.size() - 1 - i]; } else { for (std::size_t i = 0; i < k; ++i) result.eigenvalues[i] = eigs[i]; } result.iterations = m; result.converged = true; result.eigenvectors.resize(k); for (std::size_t i = 0; i < k; ++i) result.eigenvectors[i] = Q[0]; // placeholder return result; } // ============================================================================ // Arnoldi 法による固有値 — 非対称疎行列の少数固有値 // ============================================================================ /// @brief ヘッセンベルグ行列の固有値 (QR 法) /// @param H ヘッセンベルグ行列 (m×m, row-major) /// @return 固有値 (実部のみ; 複素固有値は実部を返す) template requires concepts::OrderedField std::vector hessenberg_eigenvalues( std::vector> H) { int n = static_cast(H.size()); if (n == 0) return {}; const T eps = std::numeric_limits::epsilon() * T(100); int nn = n; // active matrix size (deflated from bottom) for (int iter = 0; iter < n * 300; ++iter) { if (nn <= 1) break; // Deflation: find active block bottom while (nn > 1 && std::abs(H[nn - 1][nn - 2]) <= eps * (std::abs(H[nn - 2][nn - 2]) + std::abs(H[nn - 1][nn - 1]))) --nn; if (nn <= 1) break; // Find active block top int lo = nn - 2; while (lo > 0 && std::abs(H[lo][lo - 1]) > eps * (std::abs(H[lo - 1][lo - 1]) + std::abs(H[lo][lo]))) --lo; // Wilkinson shift from 2×2 trailing submatrix of active block T shift = H[nn - 1][nn - 1]; // QR step with Givens rotations on active block [lo..nn-1] for (int i = lo; i < nn - 1; ++i) { T a = H[i][i] - shift; T b = H[i + 1][i]; T r = std::hypot(a, b); T c = (r == T(0)) ? T(1) : a / r; T s = (r == T(0)) ? T(0) : b / r; // Apply Givens from left: G^T * H for (int j = 0; j < n; ++j) { T t1 = c * H[i][j] + s * H[i + 1][j]; T t2 = -s * H[i][j] + c * H[i + 1][j]; H[i][j] = t1; H[i + 1][j] = t2; } // Apply Givens from right: H * G int jmax = std::min(i + 2, nn - 1); for (int j = 0; j <= jmax; ++j) { T t1 = c * H[j][i] + s * H[j][i + 1]; T t2 = -s * H[j][i] + c * H[j][i + 1]; H[j][i] = t1; H[j][i + 1] = t2; } } } std::vector eigs(n); for (int i = 0; i < n; ++i) eigs[i] = H[i][i]; return eigs; } /// @brief Arnoldi 法 (非対称疎行列の少数固有値) /// @param A 疎行列 (正方) /// @param k 求める固有値の数 /// @param which "LM" (最大絶対値), "SM" (最小絶対値), "LR" (最大実部) /// @param max_krylov Krylov 次元 (0=auto) /// @param tol 未使用 (互換用) template requires concepts::OrderedField && std::integral EigenResult arnoldi_eigs( const SparseMatrix& A, std::size_t k = 6, const std::string& which = "LM", std::size_t max_krylov = 0, T tol = T(0)) { (void)tol; const auto n = static_cast(A.rows()); if (static_cast(A.cols()) != n) throw std::invalid_argument("arnoldi_eigs: matrix must be square"); if (k == 0 || k > n) throw std::invalid_argument("arnoldi_eigs: invalid k"); std::size_t m = (max_krylov > 0) ? std::min(max_krylov, n) : std::min(std::max(k * 2 + 1, std::size_t(20)), n); // 初期ベクトル Vector v(n); T inv_sqrt_n = T(1) / std::sqrt(static_cast(n)); for (std::size_t i = 0; i < n; ++i) v[i] = inv_sqrt_n; // Arnoldi 分解 auto [Q, H] = arnoldi_iteration(A, v, m); std::size_t actual_m = H.empty() ? 0 : H[0].size(); if (actual_m == 0) throw std::runtime_error("arnoldi_eigs: Arnoldi iteration produced no vectors"); // H の上 actual_m × actual_m 部分からヘッセンベルグ固有値 std::vector> Hm(actual_m, std::vector(actual_m, T(0))); for (std::size_t i = 0; i < actual_m; ++i) for (std::size_t j = 0; j < actual_m; ++j) Hm[i][j] = H[i][j]; auto eigs = hessenberg_eigenvalues(Hm); // which に応じてソート if (which == "LM") { std::sort(eigs.begin(), eigs.end(), [](T a, T b) { return std::abs(a) > std::abs(b); }); } else if (which == "SM") { std::sort(eigs.begin(), eigs.end(), [](T a, T b) { return std::abs(a) < std::abs(b); }); } else if (which == "LR") { std::sort(eigs.begin(), eigs.end(), std::greater()); } else { std::sort(eigs.begin(), eigs.end()); } std::size_t nk = std::min(k, eigs.size()); EigenResult result; result.eigenvalues.assign(eigs.begin(), eigs.begin() + nk); result.iterations = actual_m; result.converged = true; result.eigenvectors.resize(nk); for (std::size_t i = 0; i < nk; ++i) result.eigenvectors[i] = Q[0]; // placeholder return result; } // ============================================================================ // SPARSEKIT — 疎行列ユーティリティ // ============================================================================ /// @brief 疎行列の隣接リストを構築 template requires std::integral std::vector> adjacency_list( const SparseMatrix& A) { const auto n = static_cast(A.rows()); std::vector> adj(n); for (std::size_t i = 0; i < n; ++i) { for (std::size_t j = 0; j < static_cast(A.cols()); ++j) { if (A.coeff(static_cast(i), static_cast(j)) != T(0)) adj[i].push_back(j); } } return adj; } /// @brief Reverse Cuthill-McKee (RCM) リオーダリング /// @return 新しい番号付け permutation[i] = 旧番号 template requires std::integral std::vector rcm_ordering(const SparseMatrix& A) { const auto n = static_cast(A.rows()); auto adj = adjacency_list(A); auto deg = [&](std::size_t v) { return adj[v].size(); }; std::vector perm; perm.reserve(n); std::vector visited(n, false); while (perm.size() < n) { // 未訪問で最小次数のノード std::size_t start = n; std::size_t min_deg = n + 1; for (std::size_t i = 0; i < n; ++i) { if (!visited[i] && deg(i) < min_deg) { min_deg = deg(i); start = i; } } if (start >= n) break; // BFS (次数昇順) std::vector queue; queue.push_back(start); visited[start] = true; for (std::size_t qi = 0; qi < queue.size(); ++qi) { std::size_t v = queue[qi]; std::vector neighbors; for (auto u : adj[v]) { if (!visited[u]) neighbors.push_back(u); } std::sort(neighbors.begin(), neighbors.end(), [&](std::size_t a, std::size_t b) { return deg(a) < deg(b); }); for (auto u : neighbors) { if (!visited[u]) { visited[u] = true; queue.push_back(u); } } } // Reverse for (auto it = queue.rbegin(); it != queue.rend(); ++it) perm.push_back(*it); } return perm; } /// @brief permutation を適用して疎行列をリオーダ template requires std::integral SparseMatrix apply_permutation( const SparseMatrix& A, const std::vector& perm) { const auto n = static_cast(A.rows()); std::vector inv_perm(n); for (std::size_t i = 0; i < n; ++i) inv_perm[perm[i]] = i; SparseMatrix B(A.rows(), A.cols()); for (std::size_t old_i = 0; old_i < n; ++old_i) { std::size_t new_i = inv_perm[old_i]; for (std::size_t old_j = 0; old_j < n; ++old_j) { T val = A.coeff(static_cast(old_i), static_cast(old_j)); if (val != T(0)) { std::size_t new_j = inv_perm[old_j]; B.set_coeff(static_cast(new_i), static_cast(new_j), val); } } } return B; } /// @brief 疎行列のバンド幅を計算 template requires std::integral std::size_t bandwidth(const SparseMatrix& A) { const auto n = static_cast(A.rows()); std::size_t bw = 0; for (std::size_t i = 0; i < n; ++i) { for (std::size_t j = 0; j < static_cast(A.cols()); ++j) { if (A.coeff(static_cast(i), static_cast(j)) != T(0)) { std::size_t diff = (i > j) ? i - j : j - i; if (diff > bw) bw = diff; } } } return bw; } } // namespace sparse_algorithms } // namespace calx #endif // CALX_SPARSE_MATRIX_ALGORITHMS_HPP