Root Finding — 求根アルゴリズム
概要
sangi の求根モジュールは 3 つのカテゴリに分かれる。
- 1D 求根 — $f(x) = 0$ を満たす実数 $x$ を求める。二分法から Brent 法まで 10+ 種類
- 多項式求根 — $p(x) = 0$ の全根 (複素根含む) を求める。低次は公式解、高次は反復法
- N-D 求根 — $\mathbf{F}(\mathbf{x}) = \mathbf{0}$ を満たすベクトル $\mathbf{x}$ を求める
ビルド
// 全モジュールまとめてインクルード
#include <math/roots/root_finding.hpp>
// 個別インクルードも可能
#include <math/roots/root_finding_1d.hpp> // 1D 求根
#include <math/roots/root_finding_nd.hpp> // N-D 求根
#include <math/roots/polynomial_roots.hpp> // 多項式求根
ヘッダオンリー。リンクするライブラリは不要。
結果型と収束判定
RootFindingResult<R>
1D 求根関数、 多項式根反復関数、 N-D 求根関数の 統合戻り値型。 math/roots/root_finding_base.hpp 定義。 テンプレートパラメータ R がスカラー型 T なら 1D 用、 ベクトル型 V なら N-D 用に自動分岐する。
template<typename R>
struct RootFindingResult {
using error_type = /* R がスカラーなら R、 ベクトルなら R::value_type */;
std::optional<R> root; // 成功時: 根の近似値 (スカラーまたはベクトル)、 失敗時: std::nullopt
bool converged; // 収束したかどうか
size_t iterations; // 実際に行った反復回数
error_type error_estimate; // 推定誤差 (絶対値、 収束時は最終ステップ幅 等)
};
1D 求根は RootFindingResult<double> を、 N-D 求根は RootFindingResult<Vector<double>> を返す。
| メンバ | 型 | 意味 |
|---|---|---|
root | std::optional<R> | 収束した場合の根の近似値。 失敗時は std::nullopt |
converged | bool | 収束判定が満たされたか。 部分成功 (max iter 到達だが値はそれなり) でも true になる場合あり |
iterations | size_t | 実行した反復回数 (収束したステップ数、 または上限到達時の max_iterations) |
error_estimate | error_type | 残差・ステップ幅・区間幅 等で推定した最終誤差の絶対値 |
使い方:
auto r = brent_method(f, a, b);
if (r.converged && r.root) {
double x = *r.root; // または r.root.value()
// ... x を使う
} else {
// 失敗 (区間に符号変化なし / max iter 到達 / 数値特異 等)
std::cerr << "no root: iter=" << r.iterations
<< " err=" << r.error_estimate << '\n';
}
ConvergenceCriteria<T>
収束判定の閾値を保持する struct。 全求根関数の最後の引数として渡せる (省略時はデフォルト)。
template<concepts::OrderedField T>
struct ConvergenceCriteria {
T abs_ftol = std::numeric_limits<T>::epsilon() * 100; // |f(x)| 絶対許容誤差
T abs_xtol = std::numeric_limits<T>::epsilon() * 100; // |Δx| 絶対許容誤差
T rel_ftol = std::numeric_limits<T>::epsilon() * 1000; // |f|相対許容誤差
T rel_xtol = std::numeric_limits<T>::epsilon() * 1000; // Δx 相対許容誤差
size_t max_iterations = 100; // 反復上限
};
| メンバ | 意味 | デフォルト (T=double) |
|---|---|---|
abs_ftol | $|f(x)|$ がこれ以下なら収束 (絶対基準) | $\approx 2.2 \times 10^{-14}$ |
abs_xtol | $|\Delta x|$ や区間幅 $|b-a|$ がこれ以下なら収束 | $\approx 2.2 \times 10^{-14}$ |
rel_ftol | $|f_n - f_{n-1}| / |f_{n-1}|$ がこれ以下なら収束 | $\approx 2.2 \times 10^{-13}$ |
rel_xtol | $|x_n - x_{n-1}| / \max(1, |x_{n-1}|)$ がこれ以下なら収束 | $\approx 2.2 \times 10^{-13}$ |
max_iterations | 反復回数の上限。 超えると収束失敗扱い | 100 |
使用例:
// 緩い許容誤差で素早く打ち切る
ConvergenceCriteria<double> loose;
loose.abs_ftol = 1e-6;
loose.max_iterations = 50;
auto r = newton_raphson(f, df, x0, loose);
// 多倍長 (Float) で高精度
ConvergenceCriteria<Float> tight;
tight.abs_ftol = Float("1e-50");
tight.max_iterations = 200;
auto r2 = brent_method<Float>(f, a, b, tight);
1D: 囲い込み法 (Bracketing)
区間 $[a, b]$ 内で $f(a)$ と $f(b)$ が異符号であることを前提とし、確実に収束する。
| 関数 | 収束次数 | 説明 |
|---|---|---|
bisection(f, a, b) | 線形 | 二分法。最も単純で確実 |
regula_falsi(f, a, b) | 超線形〜線形 | 偽位置法 (Regula Falsi, 基本形)。 凸/凹関数では停滞しうる |
illinois_method(f, a, b) | 超線形 | 偽位置法 + Illinois 修正。 停滞回避・実用向き |
ridders_method(f, a, b) | $\sqrt{2}$ | Ridders 法。二分法より速い |
brent_method(f, a, b) | 超線形 | Brent 法。実用上の最良選択 |
alefeld_potra_shi(f, a, b) | $\approx 1.84$ | Alefeld-Potra-Shi 法 (TOMS Algorithm 748)。 Brent の改良版 |
anderson_bjork_method(f, a, b) | 超線形 | Anderson-Björck 法。 Illinois 改良型、 King-Pegasus より単純 |
関数詳細
bisection
template<concepts::OrderedField T>
RootFindingResult<T> bisection(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 区間 $[a, b]$ を毎ステップ半分に絞り、$f$ の符号が変わる側を残す。 線形収束 (毎反復で誤差が 1/2)。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可、 デフォルト構築) |
返り値: RootFindingResult<T>。 区間に符号変化がない場合は converged = false、 root = std::nullopt。
用途: ロバスト性が最優先で速度は二の次のとき。 区間幅が確実に半減するため最悪反復回数を予測可能 (log2((b-a)/tol))。
auto r = bisection<double>([](double x){ return x*x - 2.0; }, 1.0, 2.0);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=42 root=1.41421356237311
// ※ 真値 sqrt(2) = 1.41421356237310 と 12 桁一致
regula_falsi
template<concepts::OrderedField T>
RootFindingResult<T> regula_falsi(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 偽位置法 (Regula Falsi) の基本形。 区間端点を結ぶ直線と $x$ 軸の交点 $c$ を次の試行値とし、 $f(c)$ と同符号の端点を $c$ で置換して符号変化の括り区間を保つ。 常に根を挟むため収束は保証されるが、 $f$ が凸 (または凹) の場合は一方の端点が固定され続ける停滞 (stagnation) が起こり、 収束が線形に劣化しうる。 停滞を回避した実用版は illinois_method を参照。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: 教育・参照用の素の Regula Falsi。 実用では停滞を回避する illinois_method (Illinois)、 king_method (Pegasus 系)、 anderson_bjork_method を推奨。
illinois_method
template<concepts::OrderedField T>
RootFindingResult<T> illinois_method(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 偽位置法 (基本形は regula_falsi) に Illinois 修正を加えた版。 純粋な Regula Falsi は凸/凹な関数で一方の端点が停滞し線形収束に劣化するが、 同側の端点が 2 回連続で更新されなかった時に重み $1/2$ で fa または fb をスケールすることで停滞を回避し、 超線形収束を確保する。 さらに高速な改良版として anderson_bjork_method および king_method (Pegasus 法) を参照。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
ridders_method
template<concepts::OrderedField T>
RootFindingResult<T> ridders_method(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 区間の中点 $m$ と $e^{Q(x-m)}$ で重み付けした false position を組合せる。 1 反復で $f$ を 2 回評価する代わりに $\sqrt{2} \approx 1.414$ 次の超線形収束を得る (実効収束次数は約 1.84)。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: bisection より速く、 Brent より実装が単純。 関数評価が安価な場合の中庸選択。
brent_method
template<concepts::OrderedField T>
RootFindingResult<T> brent_method(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 逆 2 次補間 / 割線法 / 二分法を内部状態に応じて切替える Brent (1973) のハイブリッド。 区間幅が縮まないステップは bisection で強制縮退するため、 速度と保証収束を両立する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: 1D 求根のデフォルト推奨。 SciPy brentq 等の標準アルゴリズム。 区間端点で符号が異なれば常に確実に根を返す。
auto r = brent_method<double>([](double x){ return std::cos(x) - x; }, 0.0, 1.0);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=26 root=0.739085133215161
// ※ Dottie 数 = 0.7390851332151607 と 14 桁一致
alefeld_potra_shi
template<concepts::OrderedField T>
RootFindingResult<T> alefeld_potra_shi(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: Alefeld-Potra-Shi (1995) の TOMS Algorithm 748。 逆 3 次補間と double-length step を組合せて Brent より高い実効収束次数 ($\approx 1.84$ for smooth $f$) を持つ。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: Brent と同じく区間保証付きで、 滑らかな $f$ では Brent より少ない反復で収束。 SciPy scipy.optimize.brentq の代替に推奨。
anderson_bjork_method
template<concepts::OrderedField T>
RootFindingResult<T> anderson_bjork_method(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: Regula Falsi の改良版。 同側端点が 2 回連続で更新されなかったとき、 重み $m = 1 - f_c / f_b$ (もし $m \le 0$ なら $m = 1/2$ にフォールバック) を停滞側の関数値にかけて再補間する。 Illinois 法 (固定重み $1/2$) よりも収束が速く、 King-Pegasus 系と並ぶ実用的な超線形収束を達成する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: regula_falsi より高速な区間保証付き超線形法が欲しい場合の標準選択。 出典: Anderson & Björck, "A new high order method of regula falsi type for computing a root of an equation", BIT (1973)。
1D: 非区間法 (Non-bracketing)
初期値 $x_0$ (と任意で導関数) から反復する。収束は速いが初期値依存。
| 関数 | 収束次数 | 説明 |
|---|---|---|
newton_raphson(f, f', x0) | 2 次 | Newton-Raphson 法。 導関数が必要 |
secant_method(f, x0, x1) | $\approx 1.618$ | 割線法。 導関数不要 (2 点から有限差分近似) |
fixed_point_iteration(g, x0) | 1 次 (一般) | 不動点反復 (Picard)。 $g$ は反復写像で $x = g(x)$ を解く |
steffensen_method(f, x0) | 2 次 | Steffensen 法。 導関数なしで 2 次収束 (Aitken $\Delta^2$ 加速) |
sidi_method(f, x0, x1, criteria, memory) | $\to 2$ (可変) | Sidi の一般化割線法。 直近 $K+1$ 点の補間。 memory=2 で割線法 |
inverse_quadratic_interpolation(f, x0, x1, x2) | $\approx 1.84$ | 逆二次補間 (単独版)。 Brent の内部要素。 安全網との併用が前提 |
muller_method(f, z0, z1, z2) | $\approx 1.84$ | Müller 法。 放物線補間で 実初期点から複素根に到達 |
関数詳細
newton_raphson
template<concepts::OrderedField T>
RootFindingResult<T> newton_raphson(
const std::function<T(T)>& f,
const std::function<T(T)>& df,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 各反復 $x_{n+1} = x_n - f(x_n) / f'(x_n)$。 単根近傍で 2 次収束 ($|x_{n+1} - x^*| \le C |x_n - x^*|^2$)。 重根では線形に劣化。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
df | const std::function<T(T)>& | $f$ の 1 階導関数 |
x0 | T | 初期値 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
注意: $|f'(x)| < \epsilon \cdot 10$ の near-stationary 点で発散判定 → converged=false。 初期値が悪いと別の根に収束、 または発散することがある。 区間保証がほしい場合は brent_method を推奨。
// 例: f(x) = x^2 - 2 の根を Newton 法で求める
auto r = newton_raphson<double>(
[](double x) { return x*x - 2.0; },
[](double x) { return 2.0 * x; },
1.0);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=5 root=1.41421356237310
// ※ 真値 sqrt(2) = 1.41421356237310 と 15 桁一致 (5 反復で収束、 2 次収束の威力)
secant_method
template<concepts::OrderedField T>
RootFindingResult<T> secant_method(
const std::function<T(T)>& f,
T x0, T x1,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 2 点 $(x_{n-1}, x_n)$ を結ぶ割線 (secant) と $x$ 軸の交点を次の試行値: $x_{n+1} = x_n - f(x_n) \frac{x_n - x_{n-1}}{f(x_n) - f(x_{n-1})}$。 導関数を 1 次差分で代用するため Newton よりわずかに遅い ($\approx 1.618$ 次収束、 黄金比)。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
x0 | T | 初期点 1 (根を挟む必要はないが、 x0 と近すぎないこと) |
x1 | T | 初期点 2 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
注意: $|f(x_n) - f(x_{n-1})| < \epsilon$ (傾きほぼゼロ) で停滞検出 → converged=false。
// 例: f(x) = x - cos(x) の根 (= Dottie 数)
auto r = secant_method<double>(
[](double x) { return x - std::cos(x); }, 0.0, 1.0);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=6 root=0.739085133215161
// ※ Dottie 数 0.7390851332151607 と 14 桁一致
fixed_point_iteration
template<concepts::OrderedField T>
RootFindingResult<T> fixed_point_iteration(
const std::function<T(T)>& g,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 反復写像 $g$ に対して $x_{n+1} = g(x_n)$ を繰り返し、 不動点 $x^* = g(x^*)$ (すなわち $x = g(x)$ の解) を求める。 引数 $g$ は反復関数そのものであり、 $f(x)=0$ の $f$ ではない点に注意。 $g$ が $x^*$ 近傍で縮小写像 ($|g'(x^*)| < 1$) なら線形収束し、 特に $g'(x^*) = 0$ なら 2 次以上で収束する。 $|g'(x^*)| > 1$ では発散する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
g | const std::function<T(T)>& | 反復写像 ($x = g(x)$ を解く。 $f(x)=0$ の $f$ ではない) |
x0 | T | 初期値 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可、 $|x_{n+1} - x_n|$ を使用) |
注意: 縮小写像でない ($|g'(x^*)| \ge 1$) 場合は発散し、 最大反復回数に達して converged=false となる (最良近似値は返る)。 newton_raphson は $g(x) = x - f(x)/f'(x)$ とおいた不動点反復の特殊形である。
// 例: g(x) = cos(x) の不動点 (= Dottie 数) を不動点反復で求める
auto r = fixed_point_iteration<double>(
[](double x) { return std::cos(x); }, 0.0);
if (r.converged) std::cout << *r.root;
// g(x) = cos x は |g'(x)| = |sin x| < 1 ゆえ収束、 x* = 0.7390851332151607 (Dottie 数)
steffensen_method
template<concepts::OrderedField T>
RootFindingResult<T> steffensen_method(
const std::function<T(T)>& f,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 更新式 $x_{n+1} = x_n - \frac{f(x_n)^2}{f(x_n + f(x_n)) - f(x_n)}$。 分母 $f(x+f)-f(x)$ が $f(x)\,f'(x)$ を近似するため、 導関数なしで Newton と同じ 2 次収束を得る (Aitken の $\Delta^2$ 加速を不動点反復に適用)。 割線法と違い保持する点は 1 つだけ。 1 反復に $f$ を 2 回評価する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
x0 | T | 初期値 (根の近くから始めること。 遠いと発散しうる) |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
注意: 分母がほぼゼロで停滞検出 → converged=false。 初期値が根から遠いと発散する (大域収束保証はない)。
// 例: f(x) = x^2 - 2 の根 (= √2)、 導関数不要
auto r = steffensen_method<double>(
[](double x) { return x * x - 2.0; }, 1.5);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=4 root=1.414213562373095
sidi_method
template<concepts::OrderedField T>
RootFindingResult<T> sidi_method(
const std::function<T(T)>& f,
T x0, T x1,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>(),
size_t memory = 3);
動作: 直近 $K+1$ 点を通る Newton 補間多項式 $p$ の最新点での微分係数を使って $x_{n+1} = x_n - f(x_n)/p'(x_n)$ と更新する。 memory $=2$ で割線法 ($\varphi \approx 1.618$) に一致し、 増やすほど収束次数が上がる ($\to 2$)。 導関数不要。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
x0, x1 | T | 初期 2 点 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
memory | size_t | 補間に使う点数 $K+1$ ($\geq 2$、 省略可。 既定 3 = 2 次補間。 2 で割線法) |
// 例: f(x) = x^2 - 2 の根 (= √2)、 2 次補間 (memory=3)
auto r = sidi_method<double>(
[](double x) { return x * x - 2.0; }, 1.0, 2.0,
ConvergenceCriteria<double>(), 3);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=6 root=1.414213562373095
inverse_quadratic_interpolation
template<concepts::OrderedField T>
RootFindingResult<T> inverse_quadratic_interpolation(
const std::function<T(T)>& f,
T x0, T x1, T x2,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 3 点を通る逆関数 $x = g(f)$ の二次補間多項式を $f = 0$ で評価して次点を得る ($x = \sum_i x_i \prod_{j \neq i} f_j/(f_i - f_j)$)。 Brent 法の内部要素を独立に使えるようにしたもの。 導関数不要・超 1 次収束 ($\approx 1.84$ 次) だが、 関数値が接近すると不安定なので本来は安全網 (Brent 法) と併用する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
x0, x1, x2 | T | 相異なる 3 初期点 ($f$ 値も相異なること) |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
注意: 3 点の $f$ 値が接近すると補間が破綻 → converged=false。
// 例: f(x) = x^2 - 2 の根 (= √2)
auto r = inverse_quadratic_interpolation<double>(
[](double x) { return x * x - 2.0; }, 1.0, 1.3, 1.7);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=5 root=1.414213562373095
muller_method
template<concepts::OrderedField T>
RootFindingResult<Complex<T>> muller_method(
const std::function<Complex<T>(Complex<T>)>& f,
Complex<T> z0, Complex<T> z1, Complex<T> z2,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 3 点を通る放物線 (2 次多項式) の根のうち最新点に近い方へ進む。 判別式が負でも複素数で進むため、 実数寄りの初期点からでも複素根に到達できる (多項式の複素根探索に有用)。 収束次数 $\approx 1.84$、 導関数不要。 $f$ は複素引数で評価できる必要がある。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<Complex<T>(Complex<T>)>& | 根を求める関数 (複素引数で評価可能であること) |
z0, z1, z2 | Complex<T> | 相異なる 3 初期点 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可、 $|f|$ に abs_ftol・$|\Delta z|$ に abs_xtol を使用) |
注意: 戻り値型は RootFindingResult<Complex<T>> (根が複素数)。 どちらの複素根に収束するかは初期点に依存する。
// 例: z^2 + 1 = 0 → ±i (実数寄りの初期点から複素根へ)
std::function<Complex<double>(Complex<double>)> g =
[](Complex<double> z) { return z * z + Complex<double>(1.0); };
auto r = muller_method<double>(g, {1.0}, {0.5}, {0.0, 0.3});
if (r.converged) std::cout << r.root->re << " + " << r.root->im << "i";
// 実行結果: iter=2 root=0 + 1i (= i)
1D: 高次収束法
| 関数 | 収束次数 | 説明 |
|---|---|---|
halley_method(f, f', f'', x0) | 3 次 | Halley 法。 1 階・2 階導関数が必要 |
schroder_method(f, f', f'', x0) | 2 次 (重根含む) | Schröder 法 (Householder 2 次)。 重根でも 2 次収束を維持 |
king_method(f, a, b) | 超線形 | King-Pegasus 法 (1973、 BIT)。 区間 $[a,b]$ を取る bracketing 法。 Regula Falsi の改良で Illinois より速い |
popovski_method(f, f', x0) | 4 次 | Popovski 法。 1 階導関数のみで 4 次収束 |
householder_method(f, f', f'', f''', x0) | 4 次 | Householder 法 (階数 3)。 Newton ($d{=}1$)・Halley ($d{=}2$) の延長。 1〜3 階導関数が必要 |
関数詳細
halley_method
template<concepts::OrderedField T>
RootFindingResult<T> halley_method(
const std::function<T(T)>& f,
const std::function<T(T)>& df,
const std::function<T(T)>& d2f,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 更新式 $x_{n+1} = x_n - \frac{2 f f'}{2 f'^2 - f f''}$。 Newton の 2 次収束を 3 次に引き上げる Householder 1 次。 2 階導関数が必要、 計算コストは Newton の約 2 倍だが反復回数は減る。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
df | const std::function<T(T)>& | $f$ の 1 階導関数 |
d2f | const std::function<T(T)>& | $f$ の 2 階導関数 |
x0 | T | 初期値 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
注意: 分母 $2 f'^2 - f f''$ がほぼゼロで発散判定。
// 例: f(x) = x^3 - 2 の根 (= 2^(1/3))
auto r = halley_method<double>(
[](double x) { return x*x*x - 2.0; },
[](double x) { return 3.0 * x * x; },
[](double x) { return 6.0 * x; },
1.0);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=3 root=1.25992104989487
// ※ 真値 2^(1/3) = 1.2599210498948732 と 14 桁一致 (3 反復、 3 次収束の威力)
schroder_method
template<concepts::OrderedField T>
RootFindingResult<T> schroder_method(
const std::function<T(T)>& f,
const std::function<T(T)>& df,
const std::function<T(T)>& d2f,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 更新式 $x_{n+1} = x_n - \frac{f f'}{f'^2 - f f''}$。 重根に対して通常の Newton が 1 次に劣化するのに対し、 Schröder (1870) は 重根でも 2 次収束を維持する設計。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
df | const std::function<T(T)>& | $f$ の 1 階導関数 |
d2f | const std::function<T(T)>& | $f$ の 2 階導関数 |
x0 | T | 初期値 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: 多項式の重根を扱う場合、 Halley より安定。 単根では Halley (3 次) の方が速い。
king_method
template<concepts::OrderedField T>
RootFindingResult<T> king_method(
const std::function<T(T)>& f, T a, T b,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: Regula Falsi で補間点を求めた後、 同じ端点が連続停滞した場合に固定端点の関数値を $f_1 \leftarrow f_1 \cdot f_2 / (f_2 + f)$ で縮小して再補間する。 系譜は Illinois 法 (固定重み $1/2$) → Pegasus 法 (Dowell & Jarratt, 1972; 重み $f_2/(f_2+f)$) → King 法 (1973)。 King 法は Pegasus の遅い副ステップを 1〜2 個取り除いて漸近的な収束効率を高めた改良版であり (1 次差分商のみで収束次数 $\approx 1.839$)、 king_method という命名は誤りではなく Pegasus を改良した King の方法を指す。 Illinois 法より速い超線形収束。
出典: R. F. King, "An improved Pegasus method for root finding", BIT 13, 423–427 (1973); M. Dowell & P. Jarratt, "The Pegasus method for computing the root of an equation", BIT 12, 503–508 (1972)。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
a | T | 区間の下限 ($f(a) \cdot f(b) < 0$ 必須) |
b | T | 区間の上限 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
popovski_method
template<concepts::OrderedField T>
RootFindingResult<T> popovski_method(
const std::function<T(T)>& f,
const std::function<T(T)>& df,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 2 ステップ更新 (Newton ステップ + 補正項) で 1 階導関数のみから 4 次収束を実現。 King-Werner / Ostrowski 系の改良。 2 階導関数を計算しない代わり 1 反復あたり $f$ を 2 回評価する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
df | const std::function<T(T)>& | $f$ の 1 階導関数 |
x0 | T | 初期値 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: 2 階導関数が高コスト (e.g. AD で計算) なケースで、 反復回数より関数評価回数の方が問題な時。
householder_method
template<concepts::OrderedField T>
RootFindingResult<T> householder_method(
const std::function<T(T)>& f,
const std::function<T(T)>& df,
const std::function<T(T)>& d2f,
const std::function<T(T)>& d3f,
T x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: Householder 法系列の階数 $d = 3$ のメンバ。 Newton 法 ($d{=}1$)・Halley 法 ($d{=}2$) の延長で 4 次収束。 更新式 $x_{n+1} = x_n - \frac{6 f f'^2 - 3 f^2 f''}{6 f'^3 - 6 f f' f'' + f^2 f'''}$。 1〜3 階導関数を要する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
f | const std::function<T(T)>& | 根を求める 1 変数実関数 |
df, d2f, d3f | const std::function<T(T)>& | $f$ の 1・2・3 階導関数 |
x0 | T | 初期値 |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
注意: 分母 $6 f'^3 - 6 f f' f'' + f^2 f'''$ がほぼゼロで発散判定。 3 階導関数が安く得られる (記号微分・AD) 場合に Newton/Halley より少ない反復で収束する。
// 例: f(x) = x^3 - 2 の根 (= 2^(1/3))
auto r = householder_method<double>(
[](double x) { return x*x*x - 2.0; }, // f
[](double x) { return 3.0*x*x; }, // f'
[](double x) { return 6.0*x; }, // f''
[](double) { return 6.0; }, // f'''
1.5);
if (r.converged) std::cout << *r.root;
// 実行結果: iter=3 root=1.259921049894873 (= 2^(1/3))
多項式求根
全関数は std::vector<Complex<T>> を返す (実根は虚部 = 0)。
根はベクトル空間の元ではなく単なる複素数の集合なので、
sangi::Vector (線形代数用) ではなく STL の std::vector を使う。
入力 Polynomial<T> は係数を 昇べき順で保持 (p[0] = 定数項)。
低次 (公式解)
| 関数 | 説明 |
|---|---|
solveLinear(p) | 1 次: $ax + b = 0$ |
solveQuadratic(p) | 2 次: モニック化 + 大きい絶対値の根を先に求めて Vieta で小さい根 (桁落ち対策) |
solveCubic(p) | 3 次: Cardano の公式 + 判別式 < 0 の場合は三角関数法 |
solveQuartic(p) | 4 次: Ferrari の方法 (解決三次方程式 + 2 つの二次) |
高次 (反復法)
| 関数 | 説明 |
|---|---|
jenkinsTraub(p) | Jenkins-Traub 三段階法。 数値的に最も安定、 5-19 次の標準推奨 |
laguerre(p, eps, maxIter) | Laguerre 法 + デフレーション。 3 次収束、 全根に大域収束保証 |
durandKernerAberth(p, eps, maxIter) | Durand-Kerner-Aberth + Newton polish。 全根同時反復、 20 次以上の推奨 |
bairstow(p, eps, maxIter) | Bairstow 法。 実係数のみで 2 次因子抽出、 複素根は共役ペアとして自然に出る |
weierstrass(p, eps, maxIter) | Durand-Kerner (Weierstrass) 法。 全根同時反復の古典版 (Aberth 補正なし) |
graeffe(p, maxSquarings, eps) | Graeffe 根二乗法。 実根・分離モジュラス向け (歴史的手法) |
lehmerSchur(p, tol, maxDescend) | Lehmer-Schur 法。 複素平面を円板分割し Schur-Cohn 判定で根を局在化 (単位円上の根も可) |
関数詳細
solveLinear / solveQuadratic / solveCubic / solveQuartic
template<typename T> std::vector<Complex<T>> solveLinear(const Polynomial<T>& p);
template<typename T> std::vector<Complex<T>> solveQuadratic(const Polynomial<T>& p);
template<typename T> std::vector<Complex<T>> solveCubic(const Polynomial<T>& p);
template<typename T> std::vector<Complex<T>> solveQuartic(const Polynomial<T>& p);
動作: それぞれ 1/2/3/4 次方程式に対する閉形式解。 次数不一致は assert で検出。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
p | const Polynomial<T>& | 多項式 (係数は昇べき順、 関数ごとに次数 1/2/3/4) |
桁落ち対策: solveQuadratic は判別式が小さい場合に大きい絶対値の根を直接計算し、 もう一方を Vieta の公式で導出 (Numerical Recipes 推奨)。 出典: 奥村「アルゴリズム事典」p205。
// 例: x^2 - 5x + 6 = (x-2)(x-3) = 0
Polynomial<double> p({6.0, -5.0, 1.0}); // 昇べき: c0=6, c1=-5, c2=1
auto roots = solveQuadratic(p);
for (auto& r : roots) std::cout << r.re << ' ';
// 実行結果: "3 2" (真値 {2, 3} と完全一致)
jenkinsTraub
template<typename T>
std::vector<Complex<T>> jenkinsTraub(const Polynomial<T>& poly);
動作: Jenkins-Traub (1970) の RPOLY アルゴリズム (3 段階 shift-shift-shift)。 各根を 1 つずつ求め、 デフレーション (因数除去) で次数を下げる。 同分野で数十年にわたり標準的な「最も信頼できる」多項式求根法。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (係数は昇べき順) |
用途: 中次数 (5-19 次) のデフォルト推奨。 solvePolynomial も 5-19 次でこれを内部呼出。
// 例: x^6 - 1 = 0 (1 の 6 乗根)
Polynomial<double> p({-1.0, 0, 0, 0, 0, 0, 1.0});
auto roots = jenkinsTraub(p);
// 実行結果: 6 根 = {±1, ±exp(±iπ/3)}
// |p(root)| 残差最大 4.97e-16 (16 桁精度)
laguerre
template<typename T>
std::vector<Complex<T>> laguerre(
const Polynomial<T>& poly,
T eps = std::numeric_limits<T>::epsilon() * T(100),
size_t maxIter = 1000);
動作: Laguerre 法 + デフレーション。 各反復で $L(z) = -n / \left( G \pm \sqrt{(n-1)(n H - G^2)} \right)$ 更新 ($G = p'/p$, $H = G^2 - p''/p$)。 任意の初期値から全根に大域収束する珍しい性質、 3 次収束。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (係数は昇べき順) |
eps | T | 収束許容誤差 (省略可、 デフォルト $\epsilon \cdot 100$) |
maxIter | size_t | 反復上限 (省略可、 デフォルト 1000) |
durandKernerAberth
template<typename T>
std::vector<Complex<T>> durandKernerAberth(
const Polynomial<T>& poly,
T eps = std::numeric_limits<T>::epsilon() * T(100),
size_t maxIter = 1000);
動作: Aberth-Ehrlich の全根同時反復 ($z_i \leftarrow z_i - p(z_i)/p'(z_i) / (1 - p(z_i)/p'(z_i) \cdot \sum_{j \ne i} 1/(z_i - z_j))$)。 収束後に元の多項式で各根を Newton polish 精密化する。 全根同時のため デフレーション誤差が蓄積しない。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (係数は昇べき順) |
eps | T | 収束許容誤差 (省略可、 デフォルト $\epsilon \cdot 100$) |
maxIter | size_t | 反復上限 (省略可、 デフォルト 1000) |
用途: 高次 (20 次以上) のデフォルト推奨。 solvePolynomial も 20 次以上で内部呼出。
bairstow
template<typename T>
std::vector<Complex<T>> bairstow(
const Polynomial<T>& poly,
T eps = std::numeric_limits<T>::epsilon() * T(100),
size_t maxIter = 1000);
動作: 試行 2 次因子 $x^2 + rx + s$ で多項式を割り、 Newton 法で $r, s$ を修正して 2 次因子を抽出。 実数演算のみで複素根 (共役ペア) を得る。 出典: Press et al., Numerical Recipes §9.5。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (実係数、 昇べき順) |
eps | T | 収束許容誤差 (省略可、 デフォルト $\epsilon \cdot 100$) |
maxIter | size_t | 反復上限 (省略可、 デフォルト 1000) |
用途: 複素数演算を避けたい組込み環境、 実係数のみで複素根の組をペア単位で扱いたい場合。
weierstrass
template<typename T>
std::vector<Complex<T>> weierstrass(
const Polynomial<T>& poly,
T eps = std::numeric_limits<T>::epsilon() * T(100),
size_t maxIter = 1000);
動作: 全根を同時反復で更新する古典的な Durand-Kerner (Weierstrass) 法。 durandKernerAberth と異なり Aberth 補正を持たない素朴な同時反復 (2 次収束)。 更新式 $z_i \leftarrow z_i - \frac{P(z_i)}{\mathrm{lc}\cdot\prod_{j \neq i}(z_i - z_j)}$。 複素初期値 $(0.4+0.9i)^i$ から始め、 重根が無ければ全根 (複素共役対を含む) へ収束する。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (昇べき順) |
eps | T | 収束許容誤差 (省略可、 デフォルト $\epsilon \cdot 100$) |
maxIter | size_t | 反復上限 (省略可、 デフォルト 1000) |
注意: 重根・近接根では分母 $\prod_{j \neq i}(z_i - z_j)$ がほぼゼロになり収束が遅い。 数値的頑健性を重視するなら durandKernerAberth (Aberth 補正版) を使う。
// 例: (x-2)(x^2+1) = x^3 - 2x^2 + x - 2 → 2, ±i
Polynomial<double> p({-2, 1, -2, 1});
auto roots = weierstrass(p);
// 実行結果: roots = { 2, i, -i }
graeffe
template<typename T>
std::vector<Complex<T>> graeffe(
const Polynomial<T>& poly,
int maxSquarings = 6,
T eps = std::numeric_limits<T>::epsilon() * T(100));
動作: Graeffe 根二乗法。 $p(x)\,p(-x) = s(x^2)$ を繰り返し作って根を $r_i \to r_i^{2^m}$ と二乗し、 モジュラスが分離した根のモニック係数比から $|r_i| \approx \left|\frac{a_{n-i}}{a_{n-i+1}}\right|^{1/2^m}$ でモジュラスを読む。 実根は符号を $P(\pm|r_i|)$ で復元し、 最後に Newton 法で数ステップ磨く。 係数は二重指数的に増大するため、 各二乗でモニック化し overflow 直前で二乗を打ち切る。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (昇べき順) |
maxSquarings | int | 二乗回数 (省略可、 デフォルト 6 = 指数 $2^6 = 64$) |
eps | T | Newton 磨きの許容誤差 (省略可) |
注意 (適用範囲): 実根・モジュラスが分離した場合向け。 等モジュラス根・複素共役対・重根は基本 Graeffe では分離できない (歴史的手法)。 一般の複素根には jenkinsTraub / durandKernerAberth / weierstrass を使う。
// 例: (x-1)(x-2)(x-4) = x^3 - 7x^2 + 14x - 8 → 4, 2, 1
Polynomial<double> p({-8, 14, -7, 1});
auto roots = graeffe(p);
// 実行結果 (モジュラス降順): roots = { 4, 2, 1 }
lehmerSchur
template<typename T>
std::vector<Complex<T>> lehmerSchur(
const Polynomial<T>& poly,
T tol = std::numeric_limits<T>::epsilon() * T(100),
size_t maxDescend = 200);
動作: 全根を含む円板 (Cauchy 限界) を半径半分の小円板 9 枚 (中心 + 周囲 8) で被覆し、 各小円板に Schur-Cohn 判定 (Lehmer の単位円板テスト) を掛けて根を含む小円板へ降下する。 半径が十分小さくなったら中心を種に Newton 磨き → デフレーションし、 次数回繰り返して全根を得る。 判定は厳密な Schur 変換 $T q = \overline{a_0}\,q - a_n\,q^*$ を次数が下がるまで反復する boolean テスト (途中で $|a_0|^2 - |a_n|^2 < 0$ なら内部に根あり)。 種の精度は分割が、 根の精度は Newton 磨きが担うため、 複素根 (単位円上を含む) も求まる。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
poly | const Polynomial<T>& | 多項式 (昇べき順) |
tol | T | 判定・磨きの許容誤差 (省略可、 デフォルト $\epsilon \cdot 100$) |
maxDescend | size_t | 1 根あたりの分割降下回数の上限 (省略可、 デフォルト 200) |
用途: 根の局在化を幾何的に行いたい場合、 単位円上・近接する複素根を確実に分離したい場合。 数値的に最も安定な汎用解には jenkinsTraub を使う。
// 例: x^4 - 1 = 0 → 1, -1, i, -i (単位円上の複素根)
Polynomial<double> p({-1, 0, 0, 0, 1});
auto roots = lehmerSchur(p);
// 実行結果: roots = { 1, -1, i, -i }
統合ディスパッチャ
template<typename T>
std::vector<Complex<T>> solvePolynomial(
const Polynomial<T>& p,
T eps = std::numeric_limits<T>::epsilon() * T(100),
size_t maxIter = 1000);
solvePolynomial は次数に応じて自動的に最適なアルゴリズムを選択する:
- 1 次:
solveLinear(閉形式) - 2 次:
solveQuadratic(閉形式、 桁落ち対策付) - 3 次:
solveCubic(Cardano + 三角関数) - 4 次:
solveQuartic(Ferrari) - 5-19 次:
jenkinsTraub(1 根ずつ + デフレーション、 高精度) - 20 次以上:
durandKernerAberth(全根同時 + Newton polish、 デフレーション誤差回避)
N-D: 多変数求根
$\mathbf{F}: \mathbb{R}^n \to \mathbb{R}^n$ の零点を求める。 戻り値は RootFindingResult<V> (1D 版とテンプレートを共有する統合型、 V はベクトル型)。
| 関数 | 説明 |
|---|---|
newton_raphson_nd(F, J, x0) | 多変数 Newton 法。 Jacobian 関数が必要 |
broyden_method(F, x0) | Broyden 法。 Jacobian 不要 (準 Newton 法、 ランク 1 更新) |
newton_krylov(F, x0) | Newton-Krylov (JFNK)。 F のみ。 Jacobian を作らず J·v を有限差分で近似し GMRES で解く (matrix-free) |
newton_fd(F, x0) | 数値ヤコビアン Newton。 F のみ。 差分で陽な Matrix を組み直接解法で解く (小〜中規模で頑健) |
levenberg_marquardt(F, J, x0) | Levenberg-Marquardt 法。 ダンピング付き、 初期値が悪くても収束しやすい |
powell_hybrid(F, x0) | Powell のハイブリッド法 (信頼領域 + dogleg)。 Jacobian 不要 (内部で差分近似) |
関数詳細
newton_raphson_nd
template<concepts::OrderedField T, typename V, typename M>
requires concepts::VectorOf<V, T> && concepts::MatrixOf<M, T>
RootFindingResult<V> newton_raphson_nd(
const std::function<V(const V&)>& F,
const std::function<M(const V&)>& J,
const V& x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 各反復で $\mathbf{x}_{n+1} = \mathbf{x}_n - J(\mathbf{x}_n)^{-1} F(\mathbf{x}_n)$。 ヤコビアン特異時は対角要素を abs_xtol * 10 で正則化して solve をリトライ。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
F | const std::function<V(const V&)>& | 系を表すベクトル値関数 $\mathbf{F}: \mathbb{R}^n \to \mathbb{R}^n$ |
J | const std::function<M(const V&)>& | ヤコビアン行列を返す関数 |
x0 | const V& | 初期ベクトル |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
返り値: RootFindingResult<V>。 root は std::optional<V>。
収束判定: $\|F(\mathbf{x})\|_2 < \mathrm{abs\_ftol}$ または $\|\Delta\mathbf{x}\|_2$ が許容値以下。
例: 単位円 $x^2 + y^2 = 1$ と直線 $y = x$ の交点を求める。
sangi::Matrix<T>::solve() を経由して concept 要件を満たすため
<math/linalg/solvers.hpp> も include する。
#include <math/roots/root_finding.hpp>
#include <math/linalg/solvers.hpp> // Matrix::solve() の本体定義はここ
using namespace sangi;
auto F = [](const Vector<double>& v) {
Vector<double> r(2);
r[0] = v[0]*v[0] + v[1]*v[1] - 1.0; // x^2 + y^2 - 1
r[1] = v[0] - v[1]; // x - y
return r;
};
auto J = [](const Vector<double>& v) {
Matrix<double> m(2, 2);
m(0,0) = 2*v[0]; m(0,1) = 2*v[1];
m(1,0) = 1.0; m(1,1) = -1.0;
return m;
};
Vector<double> x0(2); x0[0] = 0.5; x0[1] = 0.5;
auto r = newton_raphson_nd<double, Vector<double>, Matrix<double>>(F, J, x0);
if (r.converged && r.root) {
const auto& v = *r.root;
std::cout << v[0] << ", " << v[1];
}
// 実行結果: iter=5 root=(0.707106781186548, 0.707106781186548)
// ※ 真値 1/√2 = 0.7071067811865476 と 15 桁一致
broyden_method
template<concepts::OrderedField T, typename V, typename M>
RootFindingResult<V> broyden_method(
const std::function<V(const V&)>& F,
const V& x0,
std::optional<M> J0 = std::nullopt,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: 「Good Broyden」式の準 Newton 法。 ヤコビアンを毎回計算せず、 ランク 1 更新で近似 ($B_{n+1} = B_n + \frac{(\Delta F - B_n \Delta \mathbf{x}) \Delta \mathbf{x}^\top}{\|\Delta \mathbf{x}\|^2}$)。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
F | const std::function<V(const V&)>& | 系を表すベクトル値関数 |
x0 | const V& | 初期ベクトル |
J0 | std::optional<M> | 初期近似ヤコビアン (省略時は単位行列) |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: ヤコビアンが解析的に書けない / 計算コストが高い場合。 初期 $B_0$ は単位行列、 後は更新で改善。
auto r = broyden_method<double>(F, x0); // J 不要、 F のみ与える
newton_krylov
template<concepts::OrderedField T>
RootFindingResult<Vector<T>> newton_krylov(
const std::function<Vector<T>(const Vector<T>&)>& F,
const Vector<T>& x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>(),
size_t krylov_restart = 30,
T inner_tol = T(1e-3));
動作: Jacobian-free Newton-Krylov (JFNK)。 ユーザは $F$ のみを渡す (解析 Jacobian も数値 Jacobian の陽な構築も不要)。 各 Newton ステップで $\mathbf{J}\,\boldsymbol\delta = -\mathbf{F}$ を GMRES (Krylov) で解くが、 Jacobian を行列として作らず方向微分の有限差分 $\mathbf{J}\,\mathbf{v} \approx (\mathbf{F}(\mathbf{x}+\varepsilon\mathbf{v}) - \mathbf{F}(\mathbf{x}))/\varepsilon$ だけで進める (matrix-free)。 バックトラッキング減衰で大域収束性を補う。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
F | const std::function<Vector<T>(const Vector<T>&)>& | 方程式系 $\mathbf{F}(\mathbf{x})$ |
x0 | const Vector<T>& | 初期ベクトル |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
krylov_restart | size_t | GMRES の最大部分空間次元 (省略可、 デフォルト 30) |
inner_tol | T | GMRES 内部相対残差の停止閾値 (省略可、 デフォルト 1e-3) |
用途: Jacobian を陽に組めない / 大きすぎる大規模非線形系。 broyden_method も $F$ のみで済むが、 こちらは Krylov 部分空間で各ステップの線形系を解くため、 高次元でメモリ効率がよい。
// x² + y² = 1, x - y = 0 → (1/√2, 1/√2) ※ F だけ渡せばよい
using V = Vector<double>;
auto F = [](const V& v) -> V {
return V({v[0]*v[0] + v[1]*v[1] - 1.0, v[0] - v[1]});
};
auto r = newton_krylov<double>(F, V({0.5, 0.5}));
// 実行結果: x = 0.7071067811865476, y = 0.7071067811865476
newton_fd
template<concepts::OrderedField T>
RootFindingResult<Vector<T>> newton_fd(
const std::function<Vector<T>(const Vector<T>&)>& F,
const Vector<T>& x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>(),
T fd_step = std::sqrt(std::numeric_limits<T>::epsilon()));
動作: 数値ヤコビアン Newton 法。 ユーザは $F$ のみを渡す。 各 Newton ステップでヤコビアンを前進差分 $J[:,j] = (F(x + h_j e_j) - F(x))/h_j$ で 陽な n×n 行列 (sangi の Matrix) として構築し、 $\mathbf{J}\,\boldsymbol\delta = -\mathbf{F}$ を直接解法 (LU) で厳密に解く。 中身は newton_raphson_nd に数値ヤコビアンを与える薄いラッパ。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
F | const std::function<Vector<T>(const Vector<T>&)>& | 方程式系 $\mathbf{F}(\mathbf{x})$ |
x0 | const Vector<T>& | 初期ベクトル |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
fd_step | T | 前進差分の基準刻み幅 $h$ (実際は $h(1+|x_j|)$、 省略可) |
newton_krylov との違い: newton_fd は $J$ を陽に持ち (メモリ $O(n^2)$) 線形系を直接解くため小〜中規模で頑健。 大規模で $J$ を組めない場合は newton_krylov (matrix-free) を使う。
// x² + y² = 1, x - y = 0 → (1/√2, 1/√2) ※ F だけ渡せばよい (数値 J を内部構築)
using V = Vector<double>;
auto F = [](const V& v) -> V {
return V({v[0]*v[0] + v[1]*v[1] - 1.0, v[0] - v[1]});
};
auto r = newton_fd<double>(F, V({0.5, 0.5}));
// 実行結果: x = 0.7071067811865476, y = 0.7071067811865476
levenberg_marquardt
template<concepts::OrderedField T, typename V, typename M>
RootFindingResult<V> levenberg_marquardt(
const std::function<V(const V&)>& F,
const std::function<M(const V&)>& J,
const V& x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: $(J^\top J + \lambda I) \Delta\mathbf{x} = -J^\top F$ を解く。 ダンピングパラメータ $\lambda$ を成功時減らし、 失敗時増やす (Marquardt-Levenberg 更新規則)。 Newton と最急降下の補間。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
F | const std::function<V(const V&)>& | 残差を返すベクトル値関数 |
J | const std::function<M(const V&)>& | ヤコビアン行列を返す関数 |
x0 | const V& | 初期ベクトル |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: 初期値が真の根から遠い / ヤコビアンが特異近傍を通る / 最小二乗の感覚で根を探したい場合。 単なる方程式系解法というより、 残差最小化の混成手法。
powell_hybrid
template<concepts::OrderedField T, typename V, typename M>
requires concepts::VectorOf<V, T> && concepts::MatrixOf<M, T>
RootFindingResult<V> powell_hybrid(
const std::function<V(const V&)>& F,
const V& x0,
const ConvergenceCriteria<T>& criteria = ConvergenceCriteria<T>());
動作: Powell (1970) の dogleg-trust-region。 信頼領域内で Newton 方向と最急降下方向を組合せた dogleg 経路をたどる。 ヤコビアンは内部で差分近似するため ユーザは F のみ与えればよい。 MINPACK hybrd ルーチン (SciPy fsolve の標準) と同系統。
パラメータ:
| 引数 | 型 | 説明 |
|---|---|---|
F | const std::function<V(const V&)>& | 系を表すベクトル値関数 |
x0 | const V& | 初期ベクトル |
criteria | const ConvergenceCriteria<T>& | 収束判定 (省略可) |
用途: N-D 求根のデフォルト推奨。 局所収束性 (Newton 並み) と大域収束性 (信頼領域) を兼ね備える。
使用例
#include <math/roots/root_finding.hpp>
#include <iostream>
using namespace sangi;
int main() {
// 1D: f(x) = x^2 - 2 の根を Brent 法で求める
auto result = brent_method<double>(
[](double x) { return x * x - 2.0; },
1.0, 2.0);
if (result.converged && result.root)
std::cout << "sqrt(2) = " << *result.root << std::endl;
// 出力: sqrt(2) = 1.41421...
// 多項式: x^3 - 6x^2 + 11x - 6 = (x-1)(x-2)(x-3) の全根
Polynomial<double> p({-6.0, 11.0, -6.0, 1.0}); // 昇べき
auto roots = solvePolynomial(p);
for (auto& r : roots)
std::cout << r.re << std::endl;
// 出力: 1, 2, 3 (順序は実装依存)
// 5次以上: solvePolynomial が自動的に jenkinsTraub を呼ぶ
Polynomial<double> q({1.0, 0.0, 0.0, 0.0, 0.0, 1.0}); // x^5 + 1
auto roots5 = solvePolynomial(q);
for (auto& r : roots5)
std::cout << r.re << " + " << r.im << "i" << std::endl;
}
関連する数学的背景
以下の記事では、Roots モジュールの基盤となる数学的概念を解説している。
- 二分法 — 最も基本的な求根アルゴリズム
- ニュートン法 — 二次収束する反復法
- 割線法 — 導関数不要の超一次収束法
- 高度な多項式求根法 — Aberth-Ehrlich・companion matrix 法
- N 次根の精度倍増 Newton 法 — 多倍長 N 乗根
- Zimmermann 再帰平方根 — 高速多倍長平方根