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}$$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}$$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}$ を double、Float(200 桁)、Rational で解き、
誤差 $\max_i |x_i - (x_1)_i|$ を比較する。
| n | double | Float(200桁) | Rational |
|---|---|---|---|
| 1 | 0 | 0 | 0 (exact) |
| 2 | 6.7e-16 | 0 | 0 (exact) |
| 3 | 1.0e-14 | 1.8e-201 | 0 (exact) |
| 4 | 6.1e-13 | 1.9e-199 | 0 (exact) |
| 5 | 6.2e-13 | 6.7e-199 | 0 (exact) |
| 6 | 5.3e-10 | 6.4e-197 | 0 (exact) |
| 7 | 2.6e-08 | 2.9e-195 | 0 (exact) |
| 8 | 4.2e-07 | 3.0e-194 | 0 (exact) |
| 9 | 2.0e-05 | 1.1e-192 | 0 (exact) |
| 10 | 3.2e-04 | 2.1e-191 | 0 (exact) |
| 11 | 9.7e-03 | 9.0e-190 | 0 (exact) |
| 12 | 0.36 | 2.1e-188 | 0 (exact) |
| 13 | FAILED | 5.7e-187 | 0 (exact) |
| 14 | FAILED | 5.2e-185 | 0 (exact) |
| 15 | FAILED | 8.2e-184 | 0 (exact) |
| 16 | FAILED | 2.9e-182 | 0 (exact) |
| 17 | FAILED | 2.6e-181 | 0 (exact) |
| 18 | FAILED | 3.1e-179 | 0 (exact) |
| 19 | FAILED | 7.6e-178 | 0 (exact) |
| 20 | FAILED | 4.6e-176 | 0 (exact) |
double: n が増えるごとに約 2 桁ずつ精度が悪化。n=12 で全桁不正確 (誤差 0.36)、n=13 で特異と判定されるFloat(200 桁): n=20 でも誤差 4.6×10-176(176 桁正確)Rational: 常に厳密解(誤差ゼロ)
Bonus: H(5) の厳密な逆行列
ヒルベルト行列の逆行列は全要素が整数になることが知られている。
ソースコードと実行方法
このページの全出力は以下の 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