ぱたへね

はてなダイアリーはrustの色分けができないのでこっちに来た

数値積分の様子をインターラクティブに見よう

Pythonで数値積分 〜フーリエ級数展開を例に、てかscipy.integrate.quad()かわいいよscipy.integrate.quad()に先を越された気がしたので、PyplotにGUIをつけてみました。とっても簡単とは行きませんが、Tkinterと組み合わせることでグラフのパラメータをインターラクティブに変更することができます。
どうもaxis()が効いていないようで、グラフに追加するtextが微妙に変なところにでてしまいます。

ソースはこちらです。

# -*- coding: utf-8 -*-
from pylab import *
from scipy.integrate import quad

from numpy import sin, pi
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg
from matplotlib.figure import Figure
import Tkinter as Tk
from Pmw import ComboBox

# global variable
x_min = -4.0
x_max = 8.0
xs = linspace(x_min, x_max, 256)
space = (x_max - x_min) / 80

class func_info():
    def __init__(self, fn, name, text, prefix):
        self.fn = fn
        self.name = name
        self.text = text
        self.prefix = prefix

def fourier(fun, n_max):
    a = []
    b = []
    for n in xrange(0, 1+n_max):
        res, err = quad(lambda x:fun(x)*cos(n*x), -pi, pi)
        a.append(res/pi)
        res, err = quad(lambda x:fun(x)*sin(n*x), -pi, pi)
        b.append(res/pi)
    def fn(x):
        sum = a[0] / 2
        for n in xrange(1, 1+n_max):
            sum += a[n]*cos(n*x) + b[n]*sin(n*x)
        return sum
    return fn

def yrange(fn):
    y_min = 100000.0
    y_max = -100000.0
    for x in xs:
        y = float(fn(x))
        if y < y_min:
            y_min = y
        if y > y_max:
            y_max = y
    return (y_min, y_max)

def draw(fn, txt, prefix):
    y_min, y_max = yrange(fn)
    baselineskip = (y_max - y_min) / 18
    y_max += baselineskip*2.75
    y_min -= baselineskip*2.75
    for til in xrange(0,20):
        axis([x_min, x_max, y_min, y_max])
        text(x_max-space, y_max-baselineskip*1.25, '$f(x)='+txt+'$', fontsize=13, horizontalalignment='right')
        txt_n = '$n\leq %d$' % til
        text(x_max-space, y_max-baselineskip*2.25, txt_n, fontsize=13, horizontalalignment='right')
        f_fn = fourier(fn, til)
        plot(xs, amap(fn,xs), 'b:', lw=1)
        plot(xs, amap(f_fn,xs), 'r-', lw=1)
        savefig("%s%02d" % (prefix,til))
        clf()

def display(window, fi, til):
    """ display graph
    """
    fn = fi.fn
    txt = fi.text
    prefix = fi.prefix
    y_min, y_max = yrange(fn)
    baselineskip = (y_max - y_min) / 18
    y_max += baselineskip*2.75
    y_min -= baselineskip*2.75
    axis([x_min, x_max, y_min, y_max]) # axis 効いてない
    window.text(x_max-space, y_max-baselineskip*1.25, '$f(x)='+txt+'$', fontsize=13, horizontalalignment='right')
    txt_n = '$n\leq %d$' % til
    window.text(x_max-space, y_max-baselineskip*2.25, txt_n, fontsize=13, horizontalalignment='right')
    f_fn = fourier(fn, til)
    window.plot(xs, amap(fn,xs), 'b:', lw=1)
    window.plot(xs, amap(f_fn,xs), 'r-', lw=1)
    clf()


def regularize(x):
    return (x + pi) % (pi * 2) - pi

def f1(x):
    # 1 | 0
    x = regularize(x)
    if x >= 0:
        return 1
    else:
        return 0

def f2(x):
    # x
    x = regularize(x)
    return x

def f3(x):
    # |x|
    x = regularize(x)
    return abs(x)

def f4(x):
    # x^2
    x = regularize(x)
    return x*x

def f5(x):
    # x | 0
    x = regularize(x)
    if x >= 0:
        return x
    else:
        return 0

def f6(x):
    # {3,0,4,1,2}
    x = regularize(x)
    ix = int(floor((pi + x) / (pi*2) * 5))
    ys = [3,0,4,1,2]
    return ys[ix]

def init_functions():
    """ initialize function list
    """
    funcs = []
    # 2つめの引数の2文字目を、indexの代わりにに使っているので、fn・・・の順番や文字を変えると死ぬ。
    funcs.append(func_info(f1, 'f1', '1 (0\leq x<\pi), 0 (-\pi\leq x<0)', 'f1_'))
    funcs.append(func_info(f2, 'f2', 'x (-\pi\leq x<\pi)', 'f2_'))
    funcs.append(func_info(f3, 'f3', '|x| (-\pi\leq x<\pi)', 'f3_'))
    funcs.append(func_info(f4, 'f4', 'x^2 (-\pi\leq x<\pi)', 'f4_'))
    funcs.append(func_info(f5, 'f5', 'x (0\leq x<\pi), 0 (-\pi\leq x<0)', 'f5_'))
    funcs.append(func_info(f6, 'f6', '{3,0,4,1,2}', 'f6_'))

    return funcs

# ここから
root = Tk.Tk()
root.option_add('*font', 'FixedSys 14')
root.wm_title("Graph")

fig = Figure(figsize=(5,4), dpi=100)
win = fig.add_subplot(111)

functions = init_functions()
selected = 0 # default

canvas = FigureCanvasTkAgg(fig, master=root)
canvas.show()
canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)


toolbar = NavigationToolbar2TkAgg( canvas, root )
toolbar.update()
canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)

def quit_cb():
    """ callback function for quit button.
    """
    root.quit()     # stops mainloop
    root.destroy()  # this is necessary on Windows to prevent
    # Fatal Python Error: PyEval_RestoreThread: NULL tstate

def scale_cb(h):
    """ callback function for scale, It draw the graph with new parameter.
    """
    win.clear()
    display(win, functions[selected], int(h))
    canvas.draw()

def select_function_cb(entry):
    """ callback function for combobox
    """
    global selected
    # indexが欲しかった
    # see init_functions()
    index = int(entry[1]) -1
    selected = index

    n = scale_n.get()
    scale_cb(n)


# GUI用フレーム
frame = Tk.Frame()

cb_sel = ComboBox(frame,
                    label_text = 'function', labelpos = 'w', # labepos が無いとエラー
                    scrolledlist_items = map((lambda x:x.name), functions),
                    selectioncommand = select_function_cb
                    )
cb_sel.selectitem(0)
cb_sel.pack(side = Tk.LEFT, padx = 0)

label_scale = Tk.Label(frame, text = "n")
label_scale.pack(side = Tk.LEFT, padx = 10)

scale_n = Tk.Scale(frame, orient = Tk.HORIZONTAL, from_ = 1, to = 20, command = scale_cb)
scale_n.pack(side = Tk.LEFT)

button_quit = Tk.Button(frame, text='Quit', command = quit_cb)
button_quit.pack(side = Tk.LEFT, padx = 20)

frame.pack(side = Tk.BOTTOM)

Tk.mainloop()