root_scalarから数値が返っても、それだけで根が求まったとは判断できない。brentqでは連続な関数と符号が異なる端点を持つ区間を用意し、収束状態、探索範囲、元の式へ代入した残差を確認する。特に不連続な関数の符号変化は、根の存在を保証しない。
最小例で確かめる
以下は説明用に作った小さなデータである。実測データや実行速度の測定結果ではない。コード全体をexample.pyとして保存すれば、入力ファイルを別途用意せずに実行できる。assertは、この例で成り立つべき形や値を確認するために入れてある。
import numpy as np
from scipy.optimize import root_scalar
def f(x):
return x**3 - x - 2
a, b = 1., 2.
assert np.isfinite([f(a), f(b)]).all()
assert f(a) < 0 < f(b)
sol = root_scalar(f, bracket=(a, b), method="brentq",
xtol=1e-12, rtol=1e-12)
residual = abs(f(sol.root))
print("converged / flag:", sol.converged, sol.flag)
print("root:", f"{sol.root:.12f}")
print("residual below 1e-10:", residual < 1e-10)
try:
root_scalar(f, bracket=(2., 3.), method="brentq")
except ValueError:
print("expected: ValueError for invalid bracket")
else:
raise AssertionError("invalid bracket was accepted")
# 意図的な反例:符号は変わるが、値は決して0にならない。
def discontinuous(x):
return -1. if x < 0.3 else 1.
false_success = root_scalar(discontinuous, bracket=(0., 1.),
method="brentq", xtol=1e-10)
print("discontinuous converged:", false_success.converged)
print("discontinuous residual:", abs(discontinuous(false_success.root)))
assert sol.converged and a <= sol.root <= b and residual < 1e-10
np.testing.assert_allclose(sol.root, 1.5213797068045676, rtol=0, atol=1e-10)
assert false_success.converged
assert abs(discontinuous(false_success.root)) == 1.
実行結果
converged / flag: True converged
root: 1.521379706805
residual below 1e-10: True
expected: ValueError for invalid bracket
discontinuous converged: True
discontinuous residual: 1.0
bracketを選ぶ前に式の定義域を見る
f(x)=x**3-x-2は多項式なので、実数上で連続である。区間[1, 2]の端点では値が負から正へ変わるため、この区間内に少なくとも一つの根がある。例ではbrentqを明示し、初期区間と許容誤差を記録した。端点の符号だけでなく、式そのものがその区間で連続か確認することが前提になる。
対数、平方根、分母を含む式なら、探索区間に定義域外や極がないかを調べる。数点を評価してすべて有限だったとしても、その間に不連続点がないことの証明にはならない。物理モデルの切替条件など、式を定義した側の情報を使って区間を分ける必要がある。
結果オブジェクトと残差を合わせて見る
convergedはアルゴリズムの収束判定、flagは終了理由である。rootだけを取り出す前にこの二つを確認し、必要ならiterationsやfunction_callsも記録する。例では得られた根が元の区間内にあり、元の関数値の絶対値が1e-10より小さいことも検査している。
xtolやrtolは主に根の位置に関する停止精度であり、関数値の残差と同じ単位ではない。傾きが大きい関数では小さな位置誤差でも残差が大きくなり得る。逆に傾きが小さければ残差が小さくても位置が不確かな場合がある。何をどの精度まで必要とするか、変数と関数値の尺度に応じて決めよう。
不連続な反例で成功判定の限界を知る
discontinuousは0.3の前後で-1から1へ飛び、どこにも0を持たない。それでも区間を狭める手続きが停止し、convergedがTrueになる例を掲載した。この入力はbrentqの連続性の前提を満たしていないため、収束フラグを根の存在証明として扱ってはいけない。
この反例では根の候補へ代入した残差が1のままであり、式を満たしていないことが分かる。残差検査は重要な防波堤だが、それだけで数学的な正しさを全て保証するものでもない。定義域、連続性、単位、モデルの分岐を含めて検査する。意図した根の枝を探しているかも確認したい。
符号が変わらないときの考え方
[2, 3]では端点が同符号なので、このbrentq呼び出しはValueErrorとなる。だからといって関数全体に根がないとは言えない。また、二重根のように根をまたいでも符号が変わらない場合は、この区間の選び方では検出できない。探索手法と問題の性質を合わせる必要がある。
複数の根がある式でも、一つのbracketからすべての根が列挙されるわけではない。意味のある範囲を複数区間へ分け、重複や境界を検査するなど、別の設計が必要になる。数値が期待値に近いという理由だけで採用せず、どの条件を確認して受け入れた結果なのかを残しておこう。
動作確認環境と参考資料
Linux・CPython 3.12.14、NumPy 2.3.5、pandas 2.2.3、SciPy 1.17.0、Matplotlib 3.10.8の環境で掲載コードを実行した。使用するライブラリはコード冒頭のimportを参照してほしい。公式資料の最新版と、この実行確認版は区別している。数値の末尾や表の表示幅は環境によって変わることがある。
