Matrix デモ — 行列演算の活用

Matrix<T> は動的サイズの行列クラスである。 LU 分解、SVD、ガウス消去法などの線形代数アルゴリズムと組み合わせて、 連立方程式の求解や低ランク近似を行える。 テンプレート引数に Rational を指定すれば、丸め誤差のない厳密計算も可能である。

このページでは 3 つのデモを通じて、行列演算の実用的な応用を紹介する。 以下の出力はすべて calx で実際に計算したものである。 ソースコードはページ末尾に掲載しており、 calx をビルドすれば手元で同じ結果を確認できる。

Demo 1: 連立方程式を解く — LU 分解 vs ガウス消去法

LU 分解とガウスの消去法、2 つの方法で同じ連立方程式を解く。 部分ピボット選択と完全ピボット選択の結果も比較する。

$$\begin{pmatrix} 2 & 1 & -1 \\ -3 & -1 & 2 \\ -2 & 1 & 2 \end{pmatrix} \begin{pmatrix} x_1 \\ x_2 \\ x_3 \end{pmatrix} = \begin{pmatrix} 8 \\ -11 \\ -3 \end{pmatrix}$$
LU solve: x = {2, 3, -1} Gauss partial: x = {2, 3, -1} Gauss full: x = {2, 3, -1} Residual norm: 0
3 つの方法すべてで同じ解 $x = (2, 3, -1)$ が得られる。完全ピボットは計算コストが高いが、悪条件行列での数値安定性に優れる。

Demo 2: SVD で低ランク近似

特異値分解 (SVD) $A = U \Sigma V^\top$ を使って行列を低ランク近似する。 上位 $k$ 個の特異値だけを残した $A_k = U_k \Sigma_k V_k^\top$ で元の行列を近似する。 画像圧縮や次元削減 (PCA) の基礎になる手法である。

以下の $5 \times 4$ 行列(ランク 2 の構造 + 微小ノイズ)を分解する:

$$A = \begin{pmatrix} 3.01 & 0 & 6 & 3 \\ 10 & 2 & 11.98 & 8 \\ 4 & 2 & 0 & 2 \\ 13 & 2.02 & 18 & 11 \\ 11 & 4 & 6 & 6.99 \end{pmatrix}$$
Singular values: sigma[0] = 34.4036 sigma[1] = 6.7744 sigma[2] = 0.0130 sigma[3] = 0.0088 Rank-2 approximation error (Frobenius): 1.57e-02
$\sigma_0 = 34.4$, $\sigma_1 = 6.77$ の 2 つが大きく、$\sigma_2, \sigma_3$ は $0.01$ 程度と微小。ランク 2 近似の Frobenius 誤差は $1.57 \times 10^{-2}$ であり、この行列の情報がほぼ上位 2 つの特異値に集中していることがわかる。

Demo 3: Precision Showdown — double vs Float vs Rational

ヒルベルト行列 $H_{ij} = 1/(i+j+1)$ は最も悪条件な行列の一つとして知られる。 真の解を $\mathbf{x}_1 = (1, 1, \ldots, 1)$ とし、右辺ベクトル $\mathbf{b} = H \mathbf{x}_1$ を構成する。 $H\mathbf{x} = \mathbf{b}$ を doubleFloat(200 桁)、Rational で解き、 誤差 $\max_i |x_i - (x_1)_i|$ を比較する。

ndoubleFloat(200桁)Rational
1000 (exact)
26.7e-1600 (exact)
31.0e-141.8e-2010 (exact)
46.1e-131.9e-1990 (exact)
56.2e-136.7e-1990 (exact)
65.3e-106.4e-1970 (exact)
72.6e-082.9e-1950 (exact)
84.2e-073.0e-1940 (exact)
92.0e-051.1e-1920 (exact)
103.2e-042.1e-1910 (exact)
119.7e-039.0e-1900 (exact)
120.362.1e-1880 (exact)
13FAILED5.7e-1870 (exact)
14FAILED5.2e-1850 (exact)
15FAILED8.2e-1840 (exact)
16FAILED2.9e-1820 (exact)
17FAILED2.6e-1810 (exact)
18FAILED3.1e-1790 (exact)
19FAILED7.6e-1780 (exact)
20FAILED4.6e-1760 (exact)
  • double: n が増えるごとに約 2 桁ずつ精度が悪化。n=12 で全桁不正確 (誤差 0.36)、n=13 で特異と判定される
  • Float(200 桁): n=20 でも誤差 4.6×10-176(176 桁正確)
  • Rational: 常に厳密解(誤差ゼロ)

Bonus: H(5) の厳密な逆行列

ヒルベルト行列の逆行列は全要素が整数になることが知られている。

H^{-1}: 25 -300 1050 -1400 630 -300 4800 -18900 26880 -12600 1050 -18900 79380 -117600 56700 -1400 26880 -117600 179200 -88200 630 -12600 56700 -88200 44100 H * H^{-1} == I (exact): true

ソースコードと実行方法

このページの全出力は以下の C++ プログラムから生成された。 calx をビルドすれば、手元で同じ結果を確認できる。

Demo 1: 連立方程式 (クリックで展開)
#include <iostream>
#include <math/core/matrix.hpp>
#include <math/core/vector.hpp>
#include <math/linalg/decomposition.hpp>

using namespace calx;
using namespace calx::algorithms;

int main() {
    // 3×3 連立方程式  Ax = b
    Matrix<double> A = {{2, 1, -1}, {-3, -1, 2}, {-2, 1, 2}};
    Vector<double> b = {8, -11, -3};

    // 方法1: LU 分解
    auto x1 = lu_solve(A, b);
    std::cout << "LU solve:       x = {";
    for (std::size_t i = 0; i < x1.size(); ++i)
        std::cout << (i ? ", " : "") << x1[i];
    std::cout << "}" << std::endl;

    // 方法2: ガウス消去法 (部分ピボット)
    auto x2 = gaussian_elimination(A, b, PivotStrategy::Partial);
    std::cout << "Gauss partial:  x = {";
    for (std::size_t i = 0; i < x2.size(); ++i)
        std::cout << (i ? ", " : "") << x2[i];
    std::cout << "}" << std::endl;

    // 方法3: ガウス消去法 (完全ピボット)
    auto x3 = gaussian_elimination(A, b, PivotStrategy::Full);
    std::cout << "Gauss full:     x = {";
    for (std::size_t i = 0; i < x3.size(); ++i)
        std::cout << (i ? ", " : "") << x3[i];
    std::cout << "}" << std::endl;

    // 検算: Ax = b
    Vector<double> r = A * x1 - b;
    std::cout << "Residual norm:  " << r.norm() << std::endl;
}
Demo 2: SVD 低ランク近似 (クリックで展開)
#include <iostream>
#include <iomanip>
#include <cmath>
#include <math/core/matrix.hpp>
#include <math/linalg/decomposition.hpp>

using namespace calx;
using namespace calx::algorithms;

int main() {
    // 5x4 行列 (ランク 2 構造 + 微小ノイズ)
    // A ≈ 3*u1*v1^T + 2*u2*v2^T
    Matrix<double> A = {
        { 3.01,  0.00,  6.00,  3.00},
        {10.00,  2.00, 11.98,  8.00},
        { 4.00,  2.00,  0.00,  2.00},
        {13.00,  2.02, 18.00, 11.00},
        {11.00,  4.00,  6.00,  6.99}
    };

    auto [U, sigma, Vt] = svd_decomposition(A);

    std::cout << "Singular values:" << std::endl;
    for (std::size_t i = 0; i < sigma.size(); i++)
        std::cout << "  sigma[" << i << "] = " << std::fixed
                  << std::setprecision(4) << sigma[i] << std::endl;

    // ランク 2 近似: 上位 2 個の特異値のみ残す
    std::size_t k = 2;
    Vector<double> s_trunc(sigma.size(), 0.0);
    for (std::size_t i = 0; i < k; i++) s_trunc[i] = sigma[i];
    auto A_approx = reconstruct_matrix_from_svd(U, s_trunc, Vt);

    // 近似誤差
    double err = 0;
    for (std::size_t i = 0; i < A.rows(); i++)
        for (std::size_t j = 0; j < A.cols(); j++)
            err += (A(i,j) - A_approx(i,j)) * (A(i,j) - A_approx(i,j));
    std::cout << "\nRank-2 approximation error (Frobenius): "
              << std::scientific << std::setprecision(2) << std::sqrt(err) << std::endl;
    std::cout << "Compression: " << A.rows() * A.cols() << " -> "
              << k * (A.rows() + A.cols() + 1) << " values" << std::endl;
}
Demo 3: Precision Showdown (クリックで展開)
// example_precision_showdown.cpp
// Build:
//   cd build && cmake --build . --config Release --target example-precision-showdown

#include <math/core/matrix.hpp>
#include <math/core/vector.hpp>
#include <math/core/mp/Float.hpp>
#include <math/core/mp/Rational.hpp>
#include <math/linalg/decomposition.hpp>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <limits>

using namespace calx;
using namespace calx::algorithms;

// Hilbert matrix H(i,j) = 1/(i+j+1)
template<typename T>
Matrix<T> hilbert(int n) {
    Matrix<T> H(n, n);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            H(i, j) = T(1) / T(i + j + 1);
    return H;
}

Matrix<Rational> hilbert_rational(int n) {
    Matrix<Rational> H(n, n);
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            H(i, j) = Rational(1, i + j + 1);
    return H;
}

int main() {
    std::cout << std::setw(4) << "n"
              << std::setw(18) << "double"
              << std::setw(18) << "Float(200d)"
              << std::setw(18) << "Rational" << std::endl;

    for (int n = 1; n <= 20; n++) {
        // true solution: x1 = (1, 1, ..., 1)

        // --- double ---
        double err_d = 0;
        {
            auto H = hilbert<double>(n);
            Vector<double> x1(n, 1.0);
            Vector<double> b = H * x1;      // b = H * x1
            try {
                auto x = lu_solve(H, b);     // solve H x = b
                for (int i = 0; i < n; i++)
                    err_d = std::max(err_d, std::abs(x[i] - 1.0));
            } catch (...) { err_d = std::numeric_limits<double>::infinity(); }
        }

        // --- Float (200 digits) ---
        double err_f = 0;
        {
            Float::setDefaultPrecision(200);
            auto H = hilbert<Float>(n);
            Vector<Float> x1(n, Float(1));
            Vector<Float> b = H * x1;       // b = H * x1
            try {
                auto x = lu_solve(H, b);     // solve H x = b
                for (int i = 0; i < n; i++) {
                    Float diff = calx::abs(x[i] - Float(1));
                    err_f = std::max(err_f, diff.toDouble());
                }
            } catch (...) { err_f = std::numeric_limits<double>::infinity(); }
        }

        // --- Rational (exact) ---
        double err_r = 0;
        {
            auto H = hilbert_rational(n);
            Vector<Rational> x1(n, Rational(1));
            Vector<Rational> b = H * x1;    // b = H * x1
            auto x = gaussian_elimination(H, b, PivotStrategy::Partial);
            for (int i = 0; i < n; i++)
                if (x[i] != Rational(1)) err_r = 1.0;
        }

        // output
        auto fmt = [](double e) -> std::string {
            char buf[32];
            if (e == 0) return "0 (exact)";
            if (e >= 1.0) return "FAILED";
            if (std::isinf(e)) return "SINGULAR";
            snprintf(buf, sizeof(buf), "%.2e", e);
            return buf;
        };
        std::cout << std::setw(4) << n
                  << std::setw(18) << fmt(err_d)
                  << std::setw(18) << fmt(err_f)
                  << std::setw(18) << fmt(err_r) << std::endl;
    }
}

API の詳細は Matrix API リファレンスLinAlg API リファレンス を参照のこと。

ビルドと実行

cl /std:c++latest /EHsc /O2 /utf-8 /I<calx>/include demo_matrix.cpp ^
   /link calx_int.lib calx_float.lib calx_rational.lib