(続き)Bokeh をつかって行列演算を可視化する(with animation)

本日は

先日の Bokeh をつかって行列演算を可視化する に引き続いて Bokeh の練習として回転行列

R(\theta)=\begin{bmatrix}
\cos\theta& -\sin\theta\\
\sin\theta& \cos\theta
\end{bmatrix}

のパラメータ $\theta$ を変えた時に像がどのように変化するのを見ていきましょう.
とはいえ,回転行列というぐらいですからぐるぐる回りますよね.
当たり前かもしれませんが,実際に実装して確かめて見ましょう.

実装例

ベタがきで書いています.
本当はクラス化した方が再利用性の観点から良いのですが,それは読者の演習問題とします.よくあるパターンですね.

anim_rot.py

"""
Reference:
https://github.com/bokeh/bokeh/blob/master/examples/plotting/server/animated.py
"""

import numpy as np
from bokeh.plotting import figure, output_file, show, curdoc
from bokeh.models import ColumnDataSource, Div
from bokeh.layouts import gridplot, column
from bokeh.client import push_session

init_degree = 30


def rot_mat(degree):
    theta = np.deg2rad(degree)
    mat = np.array([[np.cos(theta), -np.sin(theta)],
                    [np.sin(theta), np.cos(theta)]])
    return mat


def main():
    xs = np.linspace(-np.pi, np.pi, 11)
    ys = xs
    Xs, Ys = np.meshgrid(xs, ys)
    Xs, Ys = Xs.flatten(), Ys.flatten()

    mat = rot_mat(init_degree)

    transXs, transYs = mat @ np.array([Xs, Ys])

    output_file("lasso_selector.html")
    TOOLS = "pan,lasso_select,save,reset"
    source = ColumnDataSource(data=dict(Xs=Xs,
                                        Ys=Ys,
                                        transXs=transXs,
                                        transYs=transYs))

    f = figure(tools=TOOLS, title="target",
               x_range=(-np.pi*np.sqrt(2)-1, np.pi*np.sqrt(2)+1),
               y_range=(-np.pi*np.sqrt(2)-1, np.pi*np.sqrt(2)+1))
    f.circle('Xs', 'Ys', source=source)

    transf = figure(x_range=f.x_range,
                    y_range=f.y_range,
                    tools=TOOLS,
                    title="trans")
    transf.circle('transXs', 'transYs', source=source, size=6)

    degree = init_degree
    def update():
        nonlocal degree
        degree += 1
        new_mat = rot_mat(degree)
        transXs, transYs = new_mat @ np.array([Xs, Ys])
        source.data = dict(Xs=Xs,
                           Ys=Ys,
                           transXs=transXs,
                           transYs=transYs)
        transf.title.text = "theta={}".format(degree % 360)

    grid = gridplot([[f, transf]])
    plot = column(Div(text="<h2>Transform before and after</h2>"),
                  grid)
    document = curdoc()
    document.add_root(plot)
    document.add_periodic_callback(update, 60)


main()

add_periodic_callbackupdate 関数を突っ込むことで周期的に update を呼び出して $\theta$ を1度ずつずらしたときの像を描画します.

ここで注意なのは

if __name__ == '__main__':
      main() 

のようにせず, そのまま main() を書いておくことです.そうしておかないと実行しても空のページが出力されてしまいます. Referenceとして挙げている方法で session を用いるのも可能なのですが,なぜか起動に時間がかかるのでそのようにはしていません.

https://github.com/bokeh/bokeh/blob/master/examples/plotting/server/animated.py

実行例

動かして見ましょう.bokeh パッケージをインストールすると bokeh コマンドが使えます.

$ bokeh serve --show anim_rot.py

そうするとブラウザが立ち上がり下画面のように動くはずです.
GIFでキャプチャーするツールで動画化しています.

animrot.gif

想像通り回転している様子がわかります.そして思っていたよりもカクカクせずに動いてくれますね.