SIRモデル計算プログラムをGUI化する

#はじめに
前回の記事に、感染症の拡散を説明するSIRモデルについて説明しました。その時に、SIRモデルを解く数値積分のPythonプログラムも掲載しました。今回の記事には、そのPythonプログラムをGUI化してみます。

 前回の記事：感染病の数学予測モデル　(SIRモデル）：事例紹介(1) https://qiita.com/kotai2003/items/d74583b588841e6427a2

#GUIの説明
自分が作ったプログラムをGUI化する理由は、「他人に使ってもらいたいから」です。将来的には、GUIのPyコードだけではなくて、setup.exeの形で配布したいと思います。

まず、今回に作成したGUIの画面構成です。SIRモデルの感染率（Beta)と除去率(Gamma)を右側の空欄に入力し、その下のDrawボタンを押すと、SIRモデルの計算結果が中央の画面にプロットされます。

```math

\begin{align}

\frac{dS}{dt} &= -\beta SI \\
\frac{dI}{dt} &=  \beta SI -\gamma I \\
\frac{dR}{dt} &=  \gamma I \\

\end{align} 
```

```math
\begin{align}
S &: 感染可能者 \quad \text{(Susceptible)}　\\
I &: 感染者 \quad \text{(Infectious)}　\\
R &: 感染後死亡者、もしくは免疫を獲得した者 \quad \text{(Removed)}　\\
\beta &: 感染率\quad \text{(The infectious rate)} \quad [1/day] \\
\gamma &:除去率\quad \text{(The Recovery rate)} \quad [1/day]　\\
\end{align}
```

初期の画面に、Typical Condition~と書いてありますが、これは感染率（Beta)と除去率(Gamma)を入力する際に参考にするためです。

![スクリーンショット 2020-02-22 20.38.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/2e60067f-144e-a0f0-3b3c-ae77d3948b9c.png)

次の画面が計算結果を示します。SIRモデルの数値積分結果がLine Plotで表示されます。新しく入力した感染率（Beta)と除去率(Gamma)の値が、タイトルに更新されます。

![スクリーンショット 2020-02-22 20.38.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/fd245427-c2b8-1cda-e679-8df2228d8f50.png)

GUIの終了は、右下のQuitボタンを押すことで実行されます。

![スクリーンショット 2020-02-22 20.40.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/208980/fb2393e6-643b-0101-e220-3cad92ae368b.png)


#プログラムの説明
GUIのライブラリは、Tkinterを利用しました。
簡単にプログラムの動作を説明します。

(1) TkinterでCanvas Widgetを用意する。 TkinterのEntry Widgetで、感染率（Beta)と除去率(Gamma)を入力するテキストボックスを用意する。
(2) scipyでSIRモデルを計算し、その結果をmatplotlibでプロットする。
(3)　matplotlibのFigureを Canvas WidgetでDrawする。
 


必要なライブラリをインポートします。

```python
import tkinter
import tkinter.messagebox as tkmsg
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.backends.backend_tkagg import NavigationToolbar2Tk

import numpy as np
from functools import partial
from scipy.integrate import odeint

```

SIRモデルの数値積分に必要な関数を用意します。

```python

# SIR Differential Equation
def SIR_EQ(v, t, beta, gamma):
    '''
    dS/dt = -beta * S * I
    dI/dt = beta * S * I - gamma * I
    dR/dt = gamma * I

    [v[0], v[1], v[2]]=[S, I, R]

    dv[0]/dt = -beta * v[0] * v[1]
    dv[1]/dt = beta * v[0] * v[1] - gamma * v[1]
    dv[2]/dt = gamma * v[1]

    '''
    return [-beta*v[0]*v[1], beta * v[0] * v[1] - gamma * v[1], gamma * v[1]]

# Solving SIR Equation
def Calc_SIR(var_b, var_r):
    # parameters
    t_max = 14
    dt = 0.01
    #beta_const = 0.0026
    #gamma_const = 0.5

    beta_const = var_b
    gamma_const = var_r

    # initial_state
    S_0 = 762
    I_0 = 1
    R_0 = 0
    ini_state = [S_0, I_0, R_0]  # [S[0], I[0], R[0]]

    # numerical integration
    times = np.arange(1, t_max, dt)
    args = (beta_const, gamma_const)
    # Solver SIR model
    result = odeint(SIR_EQ, ini_state, times, args)
    return times,result

```

TkinterのボタンのEventの時に実行される関数を用意します。
DrawCanvas関数に、matplotlibのaxesインスタンス(=ax)を用意し、そのインスタンスをプロットする仕組みです。最後のcanvas.draw()で、matplotlibのaxesインスタンスが、canvas widgetに表示されることになります。

```python
def Quit():
    tkmsg.showinfo("Tomomi Research Inc.","Thank you for running this program!")
    root.quit()
    root.destroy()

#Draw Button
def DrawCanvas(canvas, ax):
    value_beta = EditBox_beta.get()
    value_gamma = EditBox_gamma.get()
    if value_beta != '':
        EditBox_beta.delete(0, tkinter.END)
        EditBox_gamma.delete(0, tkinter.END)
        ax.cla()
        beta = float(value_beta)
        gamma = float(value_gamma)
        t_r, res_r = Calc_SIR(beta,gamma)

        ax.plot(t_r, res_r)
        ax.legend(['Susceptible', 'Infectious', 'Removed'])
        ax.set_title('Beta='+str(beta)+', Gamma='+str(gamma) )
        ax.set_xlabel('time(days)')
        ax.set_ylabel('population')
    canvas.draw()
```
この部分はメインのプログラムです。
Tkinterの各Widgetの設定は、この部分に書いてあります。

```python
if __name__ == '__main__':
    try:
        # GUI generate
        root = tkinter.Tk()
        root.title("SIR model")

        # Graph setting
        fig, ax1 = plt.subplots()
        #fig.gca().set_aspect('equal', adjustable='box')  # グラフ領域の調整 #get current axes

        ax1.set_title('Typical Condition:  beta=0.0026, gamma=0.5, $S_0$=762, $I_0$=1')


        # Generate Canvas
        Canvas = FigureCanvasTkAgg(fig, master=root)
        Canvas.get_tk_widget().grid(row=0, column=0, rowspan=10)

        #Beta
        EditBox_beta = tkinter.Entry(width=5)  # テキストボックスの生成
        EditBox_beta.grid(row=1, column=2)

        GridLabel_beta = tkinter.Label(text="Beta")
        GridLabel_beta.grid(row=1, column=1)

        # Gamma
        EditBox_gamma = tkinter.Entry(width=5)  # テキストボックスの生成
        EditBox_gamma.grid(row=4, column=2)

        GridLabel_gamma = tkinter.Label(text="Gamma")
        GridLabel_gamma.grid(row=4, column=1)

        # ボタンに関する諸々の設定
        ReDrawButton = tkinter.Button(text="Draw", width=15, command=partial(DrawCanvas, Canvas, ax1))  # ボタンの生成
        ReDrawButton.grid(row=5, column=1, columnspan=2)  # 描画位置(テキトー)

        QuitButton = tkinter.Button(text="Quit", width=15, command=Quit)  # ボタンの生成
        QuitButton.grid(row=7, column=1, columnspan=2)  # 描画位置(テキトー)

        DrawCanvas(Canvas, ax1)
        root.mainloop()
    except:
        import traceback

        traceback.print_exc()
    finally:
        input(">>")  # エラー吐き出したときの表示待ち
```

#まとめ

Python GUIのライブラリには、Tkinterと PyQTが有名だそうですが、Tkiknterが初心者にもプログラムしやすいと聞いたため、Tkinterを選んでみました。未だにもPyQTに興味がありますので、両方使ったことがある方は、コメントを残していただければと思います。

PythonのGUIのメリットは、Cross Platformであることだと思います。プログラムを一つ作成すれば、変更なしにWindows, Mac OS, Linuxに使えることが可能です。現在、WindowsでDeep Learningのプログラムを開発し、Nvidia製のJetson系(Linux）に実装することを考えています。その時、Python GUIが大変役に立つと思います。



#参考資料
1. MatplotlibにGUI(Tkinter)を組み合わせる話 https://www.shtsno24.tokyo/2018/12/matplotlibguitkinter.html
2. Matplotlib&Seaborn実装ハンドブック https://amzn.to/2ujQ2CL
3. 感染病の数学予測モデルの紹介　(SIRモデル）https://qiita.com/kotai2003/items/3078f4095c3e94e5325c
4. 感染病の数学予測モデル　(SIRモデル）：事例紹介(1) https://qiita.com/kotai2003/items/d74583b588841e6427a2
5. Embedding in Tk https://matplotlib.org/gallery/user_interfaces/embedding_in_tk_canvas_sgskip.html