Edited at

jupyter notebook + matplotlibで非同期アニメーション

以下、 jupyter notebooknotebook と表記します。


TL;DR

notebookmatplotlib の描画coroutineを非同期に回してアニメーションする。

(下のgifはfpsすごい出ている感がありますが、こんなんじゃないです…)

sin.mov.gif


モチベーション



  • matplotlib のアニメーションの使い方を思い出すのが、面倒。

  • 描画の動きを大雑把に確認したいだけなので、フレームレートは求めない。(私の環境ではfps10くらいでCPUリソースがかわいそうになるくらい)


    • フレームレートを求めるなら、このブログ記事などをご参考に。

    • ファイル等に書き出すなら、matplotlibのアニメーション機能を使うべき。



  • ブロッキングな処理(while True: sleep)をやると、 kernel の処理がブロックされてしまうので、おそらく描画されない。


環境



  • python


    • 3.6.5

    • 2.7.15 (「python2は小学生にもバカにされる」は割とマジ)



  • ライブラリ


    • pip install jupyter numpy matplotlib




notebook は tornado

notebook のコアは 非同期処理ウェブフレームワークの tornado

ここtornadoioloop が起動されている。


描画coroutineをioloopに処理させる


add_callback版

※以下、 notebook.ipynb です。

%matplotlib notebook

import matplotlib.pyplot as plt
import numpy as np

from tornado.ioloop import IOLoop
from tornado import gen

@gen.coroutine
def coro_anim():
fig, ax = plt.subplots()
x = np.arange(0, 2 * np.pi, 0.01)
f = lambda x: np.sin(3 * x)
line, = ax.plot(x, f(x))

while True:
x += 0.1
line.set_data(x, f(x))
ax.set_xlim((min(x), max(x)))
plt.show()
yield gen.sleep(1.0)

IOLoop.current().add_callback(coro_anim)

一見、良さそうに見えますが、何回かセルを走らせていると、coroutineが複数走って、CPUリソースを食っていきます。

再実行時に、 Kernel Restart しても良いと思いますが、ここでは少し粘って、他に使えそうなのを探ります。


PeriodicCallback版

とは言っても、ioloopに入れたcoroutineのkillの仕方が分からなかったので、

初期描画処理をcoroutineの外に出して、 再描画処理だけをcoroutineのまま残して、

PeriodicCallbackinterval=1000ms で回しました。

以下も、 .ipynb です。

%matplotlib notebook

import matplotlib.pyplot as plt
import numpy as np

from tornado.ioloop import PeriodicCallback
from tornado import gen
cb = None

fig, ax = plt.subplots()
x = np.arange(0, 2 * np.pi, 0.01)
f = lambda x: np.sin(3 * x)
line, = ax.plot(x, f(x))

@gen.coroutine
def coro_anim():
global x
x += 0.1
line.set_data(x, f(x))
ax.set_xlim((min(x), max(x)))
plt.show()

if cb and cb.is_running():
cb.stop()
cb = PeriodicCallback(coro_anim, 1000)
cb.start()

これでセルを何回走らせたとしても、coroutineを stop してからインスタンスし直しているので、複数のcoroutineが走ってCPUリソースを食いつぶすことはなさそうです。

(実のところ、 Run Cells を魂を込めて連打すると、coroutineが複数できます。)

最悪、coroutineが複数できてしまっても、Kernel Restart で、その kernel で回っているioloopを初期化できるので、問題はなさそうです。


coroutineが複数走っているかの判断

notebook のノートの右上の方に python バージョンが出ているのですが、

その横に があります。 coroutine が処理されているときはここが になるので、

これがcoroutineのインターバルより頻繁に点滅していれば、coroutineが複数走っている可能性があります。


描画の焼付きみたいなもの

描画させながらいじってると、下図のような焼付きみたいなものがたまに出ますが、グラフの右下のところをドラッグしてグラフサイズを変更すると、

描画しなおしてくれるので、coroutineを再起動しなくても直せます。

2018-05-15-08-12-42.png


asyncioでの動作

私は標準ライブラリの asycnio の方が馴染みがあり、先に asyncio で書いて動作確認しているので、

asyncio でも書けば普通に動きます。もちろん、 python のバージョンに対してのコード依存性が出てきます。

今回は、 event_loop のことを書き始めると、本題からズレていきそうなので、tornado ということもあり、こちらにしました。