Juliaで非線型方程式の解の精度保証付き数値計算¶

非線形方程式

$$ f(x) = 0,\quad f:D\subset\mathbb{R}^n \rightarrow \mathbb{R}^n $$

の解を精度保証することを考える。$D$ は $f$ の定義域とし、$f$ は一階連続微分可能な関数とする。

今回は、区間解析の標準的な手法となっているR. Krawczykによる解の検証手法、ならびに区間ニュートン法による解の検証方法を紹介する。

Krawczyk(クラフチック)法¶

Krawczykの主張は、以下の定理で表される。

定理 $X\in \mathbb{R}^n$を区間ベクトル(候補者集合ともいう)、$c = \mathrm{mid}(X)$、$R\simeq Df(c)^{-1}=J(c)^{-1}$、$E$ を単位行列とし、

\begin{equation} K(X) = c-Rf(c)+(E-RDf(X))(X-c) \end{equation}

としたとき、$K(X)\subset \mathrm{int}(X)$ ($\mathrm{int}(X)$:$X$ の内部)ならば $X$ に $f(x)=0$ の解が唯一存在する。

この定理の証明は、簡易ニュートン写像 $g(x)=x-Rf(x)$ に対して、縮小写像の原理が成立することを確認する。

縮小写像の原理¶

定義 $X\subseteq \mathbb{R}^n$ として、写像 $T:X\rightarrow X$ を考える。このとき、全ての $x,y\in X$ において、距離 $d(x,y)$ を $d(x,y):=\|x-y\|_E$ で定義し、

$$ d(T(x),T(y))\le \alpha d(x,y) $$

が成り立つ $\alpha\in [0,1)$ が存在するとき、このような $T$ を縮小写像という。

定理(縮小写像の原理) $X\subseteq \mathbb{R}^n$ が閉集合、写像 $T:X\rightarrow X$ が縮小写像とする。このとき、$T(\tilde{x}) = \tilde{x}$ を満たす点 $\tilde{x}$ が $X$ に唯一存在する。($\tilde{x}$ を不動点という)

Krawczyk写像と簡易ニュートン写像の関係¶

$X\in \mathbb{IR}^n$ を区間ベクトルとし、写像 $f$ に対する簡易ニュートン写像 $g:\mathbb{R}^n\rightarrow \mathbb{R}^n$ を次で定義する。

$$ g(x)=x-Rf(x). $$

ここで $R\in \mathbb{R}^{n\times n}$ は、$n$ 次元正方行列として、$c\in X$ におけるヤコビ行列 $J(c)$ の逆近似行列 $(R\simeq f'(c)^{-1})$ とする。

このとき、$R$ が正則であるならば、

$$ f(x)=0\Longleftrightarrow g(x)=x $$

が成り立つ。

そして、写像 $g$ が、区間ベクトル $X$ から $X$ への縮小写像となれば、縮小写像の原理から、$f(x)=0$ の真の解 $\tilde{x}$ が $X$ 内に一意存在することが示せる。しかし、写像 $g$ の区間 $X$ における区間拡張 $g_{[~]}(X)$ を考えると、常に

$$g_{[~]}(X)=X-Rf_{[~]}(X)\not\subset X$$

となり、縮小写像の原理を示すことができない。ここで

$$ g(X):=\{g(x)\mid x\in X\} (\mbox{写像 $g$ の値域}) $$$$ g_{[~]}(X)\supset g(X) $$

である。以下に、具体的な例を示す。

$$ F(x,y) = \left(\begin{array}{c}f(x,y)\\ g(x,y)\end{array}\right) = \left(\begin{array}{c}x^2 + y^2 - 1\\ x - y\end{array}\right) $$

とし、候補区間 $X$ を $ (0.707107 ± 1.0\times 10^{-8}, 0.707107 ± 1.0\times 10^{-8})^T$ とする。

区間拡張 $g_{[~]}(X)$ を計算すると、

$$ g_{[~]}(X)=X-Rf_{[~]}(X) = ([0.707106, 0.707107], [0.707106, 0.707107])^T \not\subset X $$

となり、候補区間 $X$ に含まれないことがわかる。

In [1]:
using LinearAlgebra,IntervalArithmetic, ForwardDiff
X = [0.707107 ± 1e-8, 0.707107 ± 1e-8]
c = mid.(X)
f(x, y) = x^2 + y^2 - 1
g(x, y) = x - y
F( (x, y) ) = [f(x, y); g(x, y)]
DF = ForwardDiff.jacobian(F,c)
R = inv(DF)
s = X - R*F(X)
@show s
@show s .⊂ X;
s = Interval{Float64}[[0.707106, 0.707107], [0.707106, 0.707107]]
s .⊂ X = Bool[0, 0]

この困難を解決するために平均値形式を導入する。平均値形式は区間演算における区間の増大を抑制するための基本手法である。

平均値形式¶

定義 写像 $f: D \rightarrow \mathbb{R}^{n}$ が区間 $X\subset D$ ($D$ は $f$ の定義域)において、1階連続微分可能とする。 このとき、$x,~\tilde{x} \in X$に対して、

$$ f(x) \in f(\tilde{x})+Df_{[~]}(X)(x-\tilde{x}) $$

が成立する。($Df_{[~]}(x)$ は、写像 $f$ のヤコビ行列 $J(x)$ の区間 $X$ における区間拡張)上記の右辺を写像 $f$ の平均値形式と呼ぶ。

今回は、簡易ニュートン写像 $g$ の点 $c\in X$における平均値形式によって、Krawczyk写像 $K(X):\mathbb{IR}^n \rightarrow \mathbb{IR}^n$が以下で定義することができる。

$$ K(X)=c-Rf_{[~]}(c)+(I-RDf_{[~]}(X))(X-c) $$

ここでは、 $I\in \mathbb{R}^{n\times n}$ 単位行列、$Df_{[~]}(X)$ は写像 $f$ のヤコビ行列 $J(x)$ の区間 $X$ における区間拡張とする。

自動微分を利用したヤコビ行列の計算¶

Kwawczyk法を使う際には、区間拡張 $Df_{[~]}(X)$ を計算する必要がある。計算の方法として、最も標準的な実装方法は、自動微分を利用することである。

Juliaで自動微分を適用する場合には、ForwardDiffパッケージを利用する。使用例は以下の通りである。以下では $f(x,y) = x^2+y^2-1$, $g(x,y) = x^3 + y^4$ として

$$ h(x,y) = \left(\begin{array}{c}f(x,y)\\ g(x,y)\end{array}\right) $$

のヤコビ行列

$$ J(x) = \left(\begin{array}{cc}f_x(x,y) & f_y(x,y)\\ g_x(x,y) & g_y(x,y)\end{array}\right)= \left(\begin{array}{cc} 2x & 2y\\ 3x^2 & 4y^3\end{array}\right) $$

の区間 $(x,y) = ([0.8,0.9], [-1,-0.5])$ における値を求める。

In [2]:
using LinearAlgebra,IntervalArithmetic, ForwardDiff
X = [(0.8..0.9),(-1..(-0.5))]
f(x, y) = x^2 + y^2 - 1
g(x, y) = x^3 + y^4
h( (x, y) ) = [f(x, y); g(x, y)]
# ForwardDiff.jacobian(g, X::IntervalBox) = ForwardDiff.jacobian(g, X.v)
J = ForwardDiff.jacobian(h,X)
Out[2]:
2×2 Matrix{Interval{Float64}}:
 [1.59999, 1.80001]  [-2, -1]
 [1.91999, 2.43001]   [-4, -0.5]

関数 $h(x,y)$ の区間 $X = ([0.8,0.9], [-1,-0.5])$ における区間拡張 $h_{[~]}(X)$ は

In [3]:
h(X)
Out[3]:
2-element Vector{Interval{Float64}}:
 [-0.110001, 0.810001]
  [0.574499, 1.72901]

Krawczyk法の実装¶

ここから、Juliaを使ったKrawczyk法の実装を行う。以下では、 $f(x,y) = x^2+y^2-1$, $g(x,y) = x - y$ とする。

In [4]:
using LinearAlgebra,IntervalArithmetic, ForwardDiff
# 区間Xを定義
X = [(0.6..0.8),(0.6..0.8)]
# 計算対象となる方程式を定義
f(x, y) = x^2 + y^2 - 1
g(x, y) = x - y
F( (x, y) ) = [f(x, y); g(x, y)]
# ForwardDiff.jacobian(g, X::IntervalBox) = ForwardDiff.jacobian(g, X.v)
# 区間Xにおけるヤコビ行列を計算
iDF = ForwardDiff.jacobian(F,X)
Out[4]:
2×2 Matrix{Interval{Float64}}:
     [1.19999, 1.60001]        [1.19999, 1.60001]
 [1, 1]                  [-1, -1]
In [5]:
c = mid.(X)
ic = map(Interval,c)
# Rを計算するためのf'(c)を求める
DF = ForwardDiff.jacobian(F,c) # 区間演算なし
Out[5]:
2×2 Matrix{Float64}:
 1.4   1.4
 1.0  -1.0
In [6]:
# 区間Xにおけるヤコビ行列Jの逆行列Rを定義する
R = inv(DF) # 区間演算なし
Out[6]:
2×2 Matrix{Float64}:
 0.357143   0.5
 0.357143  -0.5
In [7]:
M = Matrix{Float64}(I,size(R)) - R*iDF
Out[7]:
2×2 Matrix{Interval{Float64}}:
 [-0.0714286, 0.0714286]  [-0.0714286, 0.0714286]
 [-0.0714286, 0.0714286]  [-0.0714286, 0.0714286]
In [8]:
R*F(ic)
Out[8]:
2-element Vector{Interval{Float64}}:
 [-0.00714286, -0.00714285]
 [-0.00714286, -0.00714285]
In [9]:
# Krawczyk写像を計算
K = c - R*F(ic) + M*(X - c) # 区間演算必要
Out[9]:
2-element Vector{Interval{Float64}}:
 [0.692857, 0.721429]
 [0.692857, 0.721429]
In [10]:
# 収束判定
K .⊂ X
Out[10]:
2-element BitVector:
 1
 1

Krawczyk写像 $K$ が候補区間 $X$ に含まれているため、この方程式の解は $X$の中に一意に存在することが示される。 以下では、上記の計算をまとめ、1つの関数として定義する。

In [11]:
function krawczyk(F,X)
    iDF = ForwardDiff.jacobian(F,X)
    c = mid.(X); ic = map(Interval,c)
    DF = ForwardDiff.jacobian(F,c)
    R = inv(DF)
    M = Matrix{Float64}(I,size(R)) - R*iDF
    return c - R*F(ic) + M*(X - c)
end
Out[11]:
krawczyk (generic function with 1 method)

また、候補区間の範囲をできる限り絞るために、Krawczyk法を条件の範囲内で繰り返し行う。 以下は、その計算を行った結果である。

In [12]:
K = krawczyk(F,X)
tol = 5e-10
while maximum(radius,K) >= tol
    K = krawczyk(F,K)
   @show radius.(K)
end
K
radius.(K) = [0.00028860028860044906, 0.00028860028860044906]
radius.(K) = [1.1779002662137827e-7, 1.1779002662137827e-7]
radius.(K) = [1.9761969838327786e-14, 1.9761969838327786e-14]
Out[12]:
2-element Vector{Interval{Float64}}:
 [0.707106, 0.707107]
 [0.707106, 0.707107]

以下の例のように候補区間 $X$ の選び方次第では、$K(X)\subset \mathrm{int}(X)$ が成立しない場合もある。

In [13]:
err = 0.7;
X = [0.7 ± err , 0.7 ± err ];
c = mid.(X);
K = krawczyk(F,X);
K .⊂ X
Out[13]:
2-element BitVector:
 0
 0

この時は、柏木の方法によって、候補区間 $X$ を変更すると有効であることがある。以下に、その手順を示す。いま $c\in \mathbb{F}^m$ を非線形方程式の数値計算で得られた近似解とする。 ここで、$r=|Rf(c)|\in \mathbb{F}^m$ をベクトルとして考え、候補区間 $X$ を

$$ X=\left(\begin{array}{c} {\left[c_{1}-u_{1}, c_{1}+u_{1}\right]} \\ {\left[c_{2}-u_{2}, c_{2}+u_{2}\right]} \\ \vdots \\ {\left[c_{m}-u_{m}, c_{m}+u_{m}\right]} \end{array}\right),\quad u_{i}=r_{i}+\frac{1}{n} \Sigma_{k} r_{k} $$

とする。そうすることで、$K(X)\subset \mathrm{int}(X)$ がより成立するようになる。以下に実装方法を示す。

In [14]:
# rを計算
r = abs.(R*F(c))
Out[14]:
2-element Vector{Float64}:
 0.00714285714285707
 0.00714285714285707
In [15]:
# uを計算
u = r .+ (sum(r)/length(r))
Out[15]:
2-element Vector{Float64}:
 0.01428571428571414
 0.01428571428571414
In [16]:
# 候補区間Xを新たに定める
X_new = c .± u;
X = X_new;
K = krawczyk(F,X);
K .⊂ X
Out[16]:
2-element BitVector:
 1
 1

今までの結果を集約して、最終的なKrawczyk法のアルゴリズムを実装する。以下では $f(x,y) = x^2+y^2-1$, $g(x,y) = x^2 - y^4$ として計算を行った。

In [17]:
using LinearAlgebra, IntervalArithmetic, ForwardDiff

# 候補区間Xを定義
X = [(0.6..0.7),(0.6..0.8)]

# 計算対象となる方程式を定義
f(x, y) = x^2 + y^2 - 1
g(x, y) = x^2 - y^4
F( (x, y) ) = [f(x, y); g(x, y)]
Out[17]:
F (generic function with 1 method)
In [18]:
#ニュートン法で近似解を計算する
function newton(F,x0)
    #初期値を設定
    tol = 5e-10; count = 0;
    x = x0;
    Fx = F(x);
    #条件の範囲内で計算を回す
    while maximum(abs,Fx) ≥ tol && count ≤ 20
        DF = ForwardDiff.jacobian(F,x);
        x -= DF\Fx;
        Fx = F(x);
        count += 1;
    end
    return x
end

#クラフチック法を計算する
function krawczyk(F,X)
    iDF = ForwardDiff.jacobian(F,X);
    c = mid.(X); ic = map(Interval,c);
    DF = ForwardDiff.jacobian(F,c);
    R = inv(DF);
    M = Matrix{Float64}(I,size(R)) - R*iDF;
    #クラフチック写像の値を返す
    return c - R*F(ic) + M*(X - c)
end

#最終的に完成した関数
function verifynlss_krawczyk(F,c)
    DF = ForwardDiff.jacobian(F,c)
    R = inv(DF)
    r = abs.(R*F(c))
    u = r .+ (sum(r)/length(r))
    X = c .± u
    K = krawczyk(F,X)
    #範囲内に入っていたら、さらに範囲の精度をあげていく
    if all(K .⊂ X)
        tol = 5e-10
        count = 0
        while maximum(radius,K) >= tol && count ≤ 100
            K = krawczyk(F,K)
            count += 1
        end
        success = 1
        return success, K
    end
    println("Oh my way, verification is failed...return a improved approximate solution") # cをNewton法で改善しても良い。
    success = 0
    return success, newton(F,c)
end

success, X = verifynlss_krawczyk(F,[0.61,0.78])
if success == 0
    success, X = verifynlss_krawczyk(F,X)
end
success, X
Out[18]:
(1, Interval{Float64}[[0.618033, 0.618034], [0.786151, 0.786152]])

区間ニュートン法¶

次に、もう1つの手法である区間ニュートン法について説明する。区間ニュートン法は、G.Alefeldによって提案された手法である。主張は以下の通りである。

与えられた区間ベクトル $X\in \mathbb{IR}^n$ に対して、$f:X\rightarrow \mathbb{R}^n$ が1階連続微分可能な関数とする。$M\in Df_{[~]}(X)$ を満たす任意の行列 $M$ が正則であると仮定し、ある $c\in X$ に対して、集合 $N(c,X)$ を

$$ N(c,X) := \{c - M^{-1}f(c)|M\in Df_{[~]}(X)\} $$

と定義する。この時、$N(c,X)\subset X$ が成立するならば、非線形方程式の真の解 $\tilde{x}$ が区間ベクトル $X$ 内に一意存在する。また、$N(c,X)\cap X=\emptyset$ ならば、非線形方程式の解は $X$ 内に存在しない。さらに、$\tilde{x}\in N(c,X)$ である。

Krawczyk法との違い¶

Krawczyk法と比較すると、解の存在検証部分は同一のプログラムで動作する。 大きく違う部分は、区間連立1次方程式を解く必要がある点である。Krawczyk法は、近似解 $c$ におけるヤコビ行列 $J(c)$ の逆近似行列 $R$ だけを利用する手法である。一方で区間ニュートン法は要素数が大きくなると区間連立1次方程式の計算速度が遅くなり、さらに精度が悪くなるという問題点がある。 したがって、区間ニュートン法とKrawczyk法は問題によって使い分けるのがよい。 以下は、Juliaで区間連立1次方程式を計算するアルゴリズムである。

In [19]:
# include("IntervalLinearAlgebra.jl");

#区間連立1次方程式を解く関数
function verifylss_iAib(iA,ib) 
    A = mid.(iA)
    b = mid.(ib)
    x̄ = A\b
    n = length(x̄)
    R = inv(A)
    #########
    G = Matrix{Float64}(I, n, n) - R*iA
    α = opnorm(G,Inf)# Interval arithmetic
    #########
    if α < 1
        x̄ = map(Interval,x̄)
        r = iA*x̄ - ib # Interval arithmetic
        Rr = R*r
        err = abs.(Rr) + supremum(norm(Rr,Inf))/(1-α)*(abs.(G)*ones(n)) # Interval arithmetic
    else
        println("Oh my way, verification is failed...")
        err = nan
    end
    return x̄ .± supremum.(err)
end
Out[19]:
verifylss_iAib (generic function with 1 method)

区間ニュートン法の実装¶

以下では、上記の関数を用いて区間ニュートン法の手順を紹介する。ここでは $f(x,y) = x^2+y^2-1$, $g(x,y) = x^2 - y^4$ として問題を解く。

In [20]:
using LinearAlgebra,IntervalArithmetic, ForwardDiff
# 区間Xを定義
X = [(0.6.. 0.7),(0.7.. 0.8)]
c = mid.(X)
ic = map(Interval,c)

# 計算対象となる方程式を定義
f(x, y) = x^2 + y^2 - 1
g(x, y) = x^2 - y^4
F( (x, y) ) = [f(x, y); g(x, y)]

# 区間Xにおけるヤコビ行列を計算
iM  = ForwardDiff.jacobian(F,X) # 区間演算必要
ifc = F(ic)
Out[20]:
2-element Vector{Interval{Float64}}:
 [-0.0150001, -0.0149999]
  [0.106093, 0.106094]
In [21]:
#区間連立1次方程式を計算
verifylss_iAib(iM,ifc)
Out[21]:
2-element Vector{Interval{Float64}}:
  [0.0206957, 0.0432826]
 [-0.047109, -0.0283388]
In [22]:
#N(c,X)を計算
N = ic - verifylss_iAib(iM,ifc)
Out[22]:
2-element Vector{Interval{Float64}}:
 [0.606717, 0.629305]
 [0.778338, 0.797109]
In [23]:
#収束判定
N .⊂ X
Out[23]:
2-element BitVector:
 1
 1

Krawczyk方と同じく、区間ニュートン法の最終的なアルゴリズムを実装する。

In [24]:
#ニュートン法を計算
function newton(F,x0)
    #初期値を設定
    tol = 5e-10; count = 0;
    x = x0;
    Fx = F(x);
    #条件によってニュートン法をまわす
    while maximum(abs,Fx) ≥ tol && count ≤ 20
        DF = ForwardDiff.jacobian(F,x);
        x -= DF\Fx;
        Fx = F(x);
        count += 1;
    end
    return x
end

#N(c,X)を計算する関数
function IntervalNewton(F,X)
    c = mid.(X);
    ic = map(Interval,c);
    iM = ForwardDiff.jacobian(F,X);
    ib = F(ic);
    #N(c,X)の値を返す
    return ic - verifylss_iAib(iM,ib)
#     return ic - iM\ib
end

#最終的に構築した関数
function verifynlss_IntervalNewton(F, c)
    DF = ForwardDiff.jacobian(F,c);
    R = inv(DF);
    r = abs.(R*F(c));
    u = r .+ (sum(r)/length(r));
    X = c .± u;
    K = IntervalNewton(F,X);
    #範囲内に入っていたら、さらに解の精度をあげる
    if all(K .⊂ X)
        tol = 5e-10;
        while maximum(radius,K) >= tol
            K = IntervalNewton(F,K)
        end
        success = 1
        return success, K
    end
    println("Oh my way, verification is failed...return a improved approximate solution") 
    success = 0
    return success, newton(F,c)
end
Out[24]:
verifynlss_IntervalNewton (generic function with 1 method)
In [25]:
verifynlss_IntervalNewton(F,[0.7,0.8])
Out[25]:
(1, Interval{Float64}[[0.618033, 0.618034], [0.786151, 0.786152]])

最後に、区間ニュートン法で3次方程式の解を求めてみる。ここでは、ロジスティック写像の3周期解を導く方程式を用いて計算する。解く式は以下のとおりである。

$$ \left\{\begin{array}{l} x_{1}-\lambda x_{3}\left(1-x_{3}\right)=0 \\ x_{2}-\lambda x_{1}\left(1-x_{1}\right)=0 \\ x_{3}-\lambda x_{2}\left(1-x_{2}\right)=0 \end{array}\right. $$

今回は、 $\lambda=3.82843$として問題を解く。

In [26]:
using LinearAlgebra,IntervalArithmetic, ForwardDiff

#\lambdaを設定
lam = 3.82843

#解く方程式を設定
f(x, y, z) = x - lam*z*(1-z) 
g(x, y, z) = y - lam*x*(1-x)
h(x, y, z) = z - lam*y*(1-y)
F( (x, y, z) ) = [f(x, y, z); g(x, y, z); h(x, y, z)]

#候補区間を設定
X = [(0.9.. 1.0),(0.1.. 0.2),(0.5.. 0.6)]
Out[26]:
3-element Vector{Interval{Float64}}:
 [0.899999, 1]
 [0.0999999, 0.200001]
 [0.5, 0.600001]
In [27]:
x0 = newton(F,mid.(X)) # 区間演算なし
Out[27]:
3-element Vector{Float64}:
 0.9562724713863567
 0.16008745377675246
 0.5147686339721098
In [28]:
using BenchmarkTools
@btime verifynlss_IntervalNewton($F,$x0)
  30.000 μs (142 allocations: 12.19 KiB)
Out[28]:
(1, Interval{Float64}[[0.956272, 0.956273], [0.160087, 0.160088], [0.514768, 0.514769]])
In [29]:
@btime verifynlss_krawczyk($F,$x0)
  23.541 μs (144 allocations: 12.14 KiB)
Out[29]:
(1, Interval{Float64}[[0.956272, 0.956273], [0.160087, 0.160088], [0.514768, 0.514769]])

参考文献¶

  1. 大石進一編著, 精度保証付き数値計算の基礎, コロナ社, 2018.

(精度保証付き数値計算の教科書. 浮動小数点数および区間演算に詳しい. 今回は6章を参考にした)

  1. 柏木雅英, 非線形方程式の解の精度保証 (+ 自動微分), 数値解析特論C (2020年度)講義資料

http://www.kashi.info.waseda.ac.jp/~kashi/lec2020/nac/krawczyk.pdf%E3%80%80%EF%BC%88%E6%9C%80%E7%B5%82%E9%96%B2%E8%A6%A7%E6%97%A5%EF%BC%9A2020%E5%B9%B412%E6%9C%8824%E6%97%A5%EF%BC%89.

大谷俊輔, 高安亮紀,2020年12月24日日(最終更新:2023年5月21日)