![**WebAssembly向けにRustで固有値ソルバーを実装する**
- **目的:** Rust を用いて堅牢な固有値ソルバーを実装し、WebAssembly(WASM)へコンパイルしてブラウザ上で効率的に動作させること。
- **主要手順:**
1. 数値アルゴリズムの選定または実装(例:QR アルゴリズム、べき乗法など)。
2. 必要に応じて `no_std` を使用しつつ Rust でコアロジックを記述。
3. `wasm-pack` または `cargo build --target wasm32-unknown-unknown` を用いて WebAssembly にコンパイル。
4. JavaScript との相互運用のために `#[wasm_bindgen]` を使って関数をエクスポート。
5. ブラウザ環境でテストとベンチマークを実施。](/_next/image?url=%2Fscreenshots%2F2026-01-07%2F1767738616038.webp&w=3840&q=75)
2026/01/02 4:31
**WebAssembly向けにRustで固有値ソルバーを実装する** - **目的:** Rust を用いて堅牢な固有値ソルバーを実装し、WebAssembly(WASM)へコンパイルしてブラウザ上で効率的に動作させること。 - **主要手順:** 1. 数値アルゴリズムの選定または実装(例:QR アルゴリズム、べき乗法など)。 2. 必要に応じて `no_std` を使用しつつ Rust でコアロジックを記述。 3. `wasm-pack` または `cargo build --target wasm32-unknown-unknown` を用いて WebAssembly にコンパイル。 4. JavaScript との相互運用のために `#[wasm_bindgen]` を使って関数をエクスポート。 5. ブラウザ環境でテストとベンチマークを実施。
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
記事では、著者が完全に手動で作成した固有値ソルバーをRustで構築し、それをWebAssembly(WASM)にコンパイルしてJavaScriptから直接使用できるようにする方法について説明しています。
Matrix と Complex のカスタム型を作成し、Householder QR分解、Gershgorin円検証、ヘッセンベルグ変換、および完全なQRアルゴリズムなどのコア線形代数ルーチンを実装することで、このライブラリは wasm-bindgen 以外に外部依存関係を持たず自己完結型となります。Rust の機能(Option、パターンマッチング(let-else)、デバッグマクロ、単体テスト、および Clippy ライント)を利用してコード品質を維持しています。クレートは wasm-pack でパッケージ化され、約84 kBのWASMバイナリとJavaScript バインディングが生成され、著者はそれらを iframe ショートカットを介して Hugo ベースのブログに埋め込んでいます。将来的には、さらに多くの行列型や対話型デモ、Rust フロントエンドフレームワークとの深い統合を追加し、WebAssembly/DOM 互換性の問題にも対処する予定です。この軽量かつ高性能な数値ツールは、ウェブページやアプリケーションに簡単に埋め込むことができ、重い外部ライブラリなしで JavaScript における線形代数タスクに高速な代替手段を開発者にもたらします。本文
2025年12月31日 – RustでWebAssembly向け固有値ソルバーを実装する
最近、ゲーシュゴリン円定理(Gershgorin Circle Theorem)に出会い、インタラクティブな可視化に最適だと感じました。
しかし私はウェブ開発者ではなく、JavaScriptもあまり好きではありません。そのため、上記のインタラクティブコンポーネントで行われる固有値計算は Rust で書き、WebAssembly にコンパイルし、HTML と JavaScript でラップしています。
免責事項 – 本投稿と Rust コードは私自身が作成したもので、講義ノートや他言語のリファレンス実装を参照しながら書きました。HTML・CSS・JavaScript は LLM が生成しました。すべてのミスは私にあります。
1. ゲーシュゴリン円定理
この定理は、行列の固有値が複素平面上でどこに存在するかを、その行列だけから簡単に推測できることを示します。
(A) を (n \times n) の複素行列とします。
各行 (i) に対して:
- 中心 (c_i = a_{ii})
- 半径 (r_i = \sum_{j \neq i} |a_{ij}|)
ゲーシュゴリン円は [ D(c_i,r_i)={z\in\mathbb C : |z-c_i| \le r_i}. ]
ゲーシュゴリン円定理
(A) のすべての固有値は、集合 (\bigcup_{i=1}^n D(c_i,r_i)) 内に入ります。
もし (A) が実対称行列ならば、全固有値は実数であり、円は実軸上の閉区間へと縮約します。
2. 例
[ [ 2 , -1 , 0 ], [ 1 , 1 , 0 ], [ 0 , 0 , 5 ] ]
固有値: ({\frac{3\pm i\sqrt{3}}{2},,5})。
ゲーシュゴリン円: (D(2,1), D(1,1), D(5,0))。
複素共役対は最初の二つの円の境界に位置し、実固有値は 5 における退化円と一致します。
別例 – 4次ハダマード行列:
[ [ 1 , -1 , 1 , -1 ], [ 1 , -1 , -1 , 1 ], [ 1 , 1 , -1 , -1 ], [ 1 , 1 , 1 , 1 ] ]
固有値: ({\pm\sqrt2 \pm i\sqrt2})。
3. QR 固有値アルゴリズム
QR アルゴリズムは、類似変換を QR 分解で反復適用し、収束するまで続けます。
(A_k) が与えられたとき、 [ A_{k+1}=R_k Q_k,\quad A_k=Q_k R_k. ]
この列は、対角成分が固有値となる上三角行列へ収束します。
単純実装では収束が遅く、非対称行列(例えば置換行列)に対して失敗します。
4. Rustでの実装
4.1 複素数
Rust には組み込みの複素型がないため、自前で定義します:
#[wasm_bindgen] #[derive(PartialEq, Clone, Copy)] pub struct Complex { pub re: f64, pub im: f64, }
加算・減算・乗算・除算・ノルム(
f64::hypot を用いて数値安定性を確保)などの演算とヘルパー関数を実装します。
4.2 円型
#[wasm_bindgen] #[derive(PartialEq, Debug)] pub struct Circle { pub centre: Complex, pub radius: f64, }
contains(&self, z: Complex) -> bool メソッドで点が円内にあるか判定します。
4.3 行列型
行列は row-major 配列で保存します:
#[wasm_bindgen] #[derive(Clone)] pub struct Matrix { order: usize, entries: Vec<Complex>, }
主なメソッド:
– コンストラクタ。new(order, entries)
,get(row, col)set(row, col, val)
,zero(order)eye(order)- 演算は演算子オーバーロード(
,Add
,Sub
)で実装。Mul
はgershgorin_circles()
を返す。Vec<Circle>
は Gram–Schmidt を実装。qr_decomposition()
は Householder 反射を使用。hessenberg_reduction()
4.4 シフト付き QR アルゴリズムと減算法
最終的な実装は以下のようになります:
#[wasm_bindgen] pub fn qr_algorithm(&self, max_iter: u64, tol: f64) -> Vec<Complex> { let mut n = self.order; let mut eigvals = vec![Complex::zero(); n]; let mut a_k = self.clone().hessenberg_reduction(); for _ in 0..max_iter { if a_k.get(n-1, n-2).norm() <= tol { n -= 1; eigvals[n] = a_k.get(n, n); a_k = a_k.deflate(n); } if n == 1 { eigvals[0] = a_k.get(0,0); break; } // trailing 2x2 ブロックの固有値 let (mu_1, mu_2) = { let (a,b,c,d) = ( a_k.get(n-2,n-2), a_k.get(n-2,n-1), a_k.get(n-1,n-2), a_k.get(n-1,n-1) ); let trace = a + d; let det = a*d - b*c; let disc = (trace*trace)/Complex::real(4.0) - det; ( trace/Complex::real(2.0) + disc.sqrt(), trace/Complex::real(2.0) - disc.sqrt() ) }; if n == 2 { eigvals[0] = mu_1; eigvals[1] = mu_2; break; } let a_nn = a_k.get(n-1,n-1); let s_k = if (mu_1-a_nn).norm() < (mu_2-a_nn).norm() { mu_1 } else { mu_2 }; let shift = Matrix::eye(n).scalar_mul(s_k); let (q,r) = (&a_k - &shift).qr_decomposition(); a_k = &(&r * &q) + &shift; } eigvals }
5. 補足事項
- デバッグ –
,dbg!
を活用すると簡単に検証できます。#[test] - ゼロコスト抽象化 – クローンは多くの場合不要です。借用チェッカーとイテレータで割り当てを減らせます。
- デフォルト引数 – Rust にはないため、明示的にパラメータを渡すことでテストが容易になります。
- Clippy & LSP – バグ検出やインラインドキュメント生成に不可欠です。
6. WebAssembly へのコンパイル
wasm-pack を使用:
cargo new --lib gershgorin-circle-theorem wasm-pack build --target web --no-typescript --no-pack
生成される
.wasm と .js は pkg/ ディレクトリに配置されます。#[wasm_bindgen] により、JavaScript のグルーコードは最小限です:
<script type="module"> import init, { Complex, Matrix } from "./pkg/gershgorin_circle_theorem.js"; init().then(() => { const matrix = new Matrix(order, values); const circles = matrix.gershgorin_circles(); const eigvals = matrix.qr_algorithm(10n, 1e-8); }); </script>
7. Hugo での公開
このコンポーネントはブログ投稿内に iframe として埋め込みます。
publish-html-as-resource.html というカスタムショートコードを用い、.html ファイルをページではなくリソースとして扱います。
{{< iframe src="component/component.html" width="100%" height="820" >}}
8. 結論
Rust で固有値ソルバーを書き、WebAssembly にコンパイルする作業は非常にやりがいのある経験でした。
借用チェッカーの壁や複素型の欠如といった初期の障害を
wasm-bindgen と Rust エコシステムのおかげで乗り越えることができました。
次なる挑戦
- 他の行列族(確率行列、正定値行列)を対象にインタラクティブデモを作成。
- Brauer の Cassini 楕円などより厳密な境界を調査。
- JavaScript グルーコードをさらに削減 —
や Leptos/Dioxus などのフレームワークを検討。web-sys
投稿終了。