行列指数関数の近似計算精度


### 定義
行列指数関数は次式で定義される．行列指数関数の性質はwikipediaを参照されたい．

```math
e^A = \sum_{k=0}^\infty \frac{1}{k!}A^k
```

定義は上記の通りであり簡単に求まりそうであるが，これを厳密に計算しようとするとかなり煩雑である（計算過程で虚数を扱ったりする）．
$e^A$の要素を初等関数で表したかったり，あるいは組込み制御系で計算機が貧弱だったりする場合，精度はほどほどで良いから簡単に扱いたいという場合がある．

### 途中で打ち切った場合
行列指数関数の最も簡単な近似方法は，定義の式の通りに計算し，計算を途中で打ち切る方法だと考えられる．
これは，マクローリン展開，及び分母多項式を$1$としたときののPade近似と一致する．

この近似方法で問題になるのは近似精度である．
一般論を考えるのは難しいので，実際に値を突っ込んで計算してみる．

一例として，

```math
A = \begin{bmatrix}
0 & 1 \\
-1 & 0 \\
\end{bmatrix}
```

の場合を考える．$e^A$の厳密な値は次の通り．

```math
e^A = \begin{bmatrix}
  0.540302 & 0.841471\\
 -0.841471 & 0.540302 \\
\end{bmatrix}
```

途中で打ち切ったものを近似式とし，$f(A,n) := \sum_{k=0}^n \frac{1}{k!}A^k$と表すことにする．
$k=1$~$4,10$のときの近似値は次のようになる．

```math
f(A, 1) = \begin{bmatrix}
  1.0 & 1.0 \\
 -1.0 & 1.0 \\
\end{bmatrix} \\
f(A, 2) = \begin{bmatrix}
  0.5 & 1.0 \\
 -1.0 & 0.5 \\
\end{bmatrix} \\
f(A, 3) = \begin{bmatrix}
  0.5 & 0.833333 \\
 -0.833333 & 0.5 \\
\end{bmatrix} \\
f(A, 4) = \begin{bmatrix}
  0.541667 & 0.833333 \\
 -0.833333 & 0.541667 \\
\end{bmatrix} \\
f(A, 10) = \begin{bmatrix}
  0.540302 & 0.841471 \\
 -0.841471 & 0.540302 \\
\end{bmatrix}
```

真値と比較すると，上記の$A$では1,2次ぐらいでは十分近似できているとは認めがたい．

近似誤差をフロベニウスノルム$\sqrt{\rm{trace}(AA^{\rm{T}})}=\sqrt{\sum_{i, j} \|A_{i,j}\|^2}$によって評価する．
横軸に$k$を取り，縦軸を誤差$e^A-f(A,k)$のフロベニウスノルムとしたグラフが次である．2つ目のグラフは縦軸を対数表示している．

![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725172/2d79e661-4d45-2db9-7a2b-92ef861b9bbb.png)

![test2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/725172/662e7a63-5db1-ab38-0c29-f32caa227bbc.png)

今回の$A$では，少なくとも4次くらいまでは計算しておきたい．
これは，行列$A$に大きく依存するので，どこまでで近似するかは必要に応じて適宜求める必要がある．

Aの累乗から求められている点から，Aの各要素の値が十分小さければ，低次で打ち切ってもよいと考えられる．

```julia
# Julia 1.6.2

using Plots
using LinearAlgebra

# exp(A)のk次近似関数 (AはSymPy.Symの行列でもOK）
exp_Maclaurin(A::Matrix{T}, k::Int) where T = sum((A^i/factorial(i) for i=0:k))

A = [0. 1.;
     -1. 0.]

p = plot(0:15, [fnorm(exp(A)-exp_approx(A,i)) for i = 0:15], marker=:circle, ms=5, label="")
#p = plot(0:15, [fnorm(exp(A)-exp_approx(A,i)) for i = 0:15], marker=:circle, ms=5, label="", yscale=:log10)
xlabel!("k")
ylabel!("norm(exp(a) - f(a,k))")
```

### Pade近似
スカラーの行列指数関数と同様に，Pade近似を使うこともできる．
行列指数関数の定義式の通り，行列指数関数$e^A$はスカラーの指数関数$e^a$のマクローリン展開と見た目が一致している．
よって，Pade近似の係数の導出も同様の議論ができ，スカラーの場合の係数をそのまま流用できる．

近似式は次のようになる．

```math
\begin {align}
e^X \approx & g(X,m,n) \\
:= & [b_0+b_1X+b_2X^2+\cdots+b_nX^n]^{-1} \\
& [a_0+a_1X+a_2X^2+\cdots+a_mX^m]
\end {align}
```

$a_i$, $b_i$は次を満たす指数関数のPade近似の係数である．

```math
e^x \approx \frac{a_0+a_1x+a_2x^2+\cdots+a_mx^m}
{b_0+b_1x+b_2x^2+\cdots+b_nx^n}
```

Pade近似の係数は，手計算するか，ググるか，JuliaのPolynomialsやPythonのmpmathにあるpade関数を使えばよい．

```julia
using Polynomials

m = 2   # 分子の次数
n = 3   # 分母の次数

# Pade近似の多項式(分子, 分母)
function exp_pade(A::Matrix{T}, m::Int, n::Int) where T
    # e^Aのマクローリン展開多項式
    buf = Polynomial([1/factorial(k) for k = 0:m+n])

    # 近似関数
    f = Polynomials.PolyCompat.Pade(buf, m, n)

    return f(A)
end

exp_pade(A, m, n)
```

一般にPade近似はマクローリン展開より良い近似になるようで，その精度はテイラー展開に対して$O(m+n+1)$である．原点から遠い点ではマクロリーン展開より精度は高いだろうが，計算に逆行列が必要になる．

古典制御論におけるむだ時間要素の伝達関数$e^{-Ls}$に対しては，近似した伝達関数をプロパーにしたい＝分子の次数を分母の次数以下にしたいのでPade近似が有用である．とにかく$e^A$の値や式が欲しいという場合には，逆行列があるので使い勝手が悪い．

```math
e^A = \begin{bmatrix}
  0.540302 & 0.841471\\
 -0.841471 & 0.540302 \\
\end{bmatrix} \\

f(A, 5) = \begin{bmatrix}
  0.541667 & 0.841667 \\
 -0.841667 & 0.541667 \\
\end{bmatrix} \\
f(A, 6) = \begin{bmatrix}
  0.540278 & 0.841667 \\
 -0.841667 & 0.540278 \\
\end{bmatrix} \\

g(A, 2, 3) \begin{bmatrix}
  0.540251 & 0.841349 \\
  -0.841349 & 0.540251 \\
\end{bmatrix} \\
g(A, 3, 3) \begin{bmatrix}
  0.54031 & 0.841466 \\
  -0.841466 & 0.54031 \\
\end{bmatrix} \\

\qquad \\

\|f(A,5)-e^A\| = 1.94924 \times 10^{-3} \\
\|f(A,6)-e^A\| = 2.78901 \times 10^{-4} \\
\|g(A,2,3)-e^A\| = 1.87631 \times 10^{-4} \\
\|g(A,3,3)-e^A\| = 1.34915 \times 10^{-5} \\
```

### SymPy(Julia)で行列指数関数を求める
SymPyを用いれば，簡単に求まる場合もある．
上で定義した$A$に非負の定数$t$をかけたものの行列指数関数$e^{At}$は次のように調べられる．
ちなみに，$e^{At}$は制御工学などで頻出し，LTIシステムの状態遷移行列となる．

```julia
# Julia 1.6.2

using SymPy

A = [0. 1.;
     -1. 0.]

# t >= 0
t = symbols("t", positive=true)

exp(A*t)
```

```math
e^{At} = \begin{bmatrix}
  1.0⋅\cos(t) & 1.0⋅\sin(t) \\
 -1.0⋅\sin(t) & 1.0⋅\cos(t) \\
\end{bmatrix}
```

$t=1$を代入してみると，$e^A$と一致する．
（たまにおかしな解が出る？のであまり信用できていない）