Pythonで高校数学:ベクトルの一次結合,終点の存在範囲
高校の数学の教科書には「一次結合」という用語は出てこない。「ベクトルの終点の存在範囲」となっている。見出し画像で,s,t が条件 s+t=1 を満たしながら変化するときのベクトルpの終点の存在する範囲が直線になる,という話。
これは,「直線のベクトル方程式」につながるのだが,生徒が苦手とするところだ。ここはやはり実際に動かして,イメージを持つほうがいいだろう。
こんな感じで動かす。はじめは見出し図の状態。スライダを動かすと
いろいろ動かしていると,ベクトルpの終点が直線上にありそうだとわかる。そこで,軌跡ON/OFF ボタンをクリックすると,軌跡:直線が表示される。
ということで,Tkinter で作成。matplotlib では,スライダで s を動かしたときの図の再描画がうまくいかなかった。
矢線は matplotlib でも,Tkinter でも描ける。しかし,描いた図を操作するのに,Tkinter は,座標も属性も変更できるが,matlinplot ではどうすればいいかわからない。2次関数のグラフのときは set_xdata でできたのだが。
ともかく,Tkinter ならできる。
とはいえ結構多くの図を描くことになる。
(1) ベクトルa,b
(2) ベクトルc :あとで位置が変わるので,タグを付けておく。
(3) a,b に係数 s, 1-s をかけたベクトル
(4) ベクトルを表す文字表示
(5) ベクトルの和を表すときの補助線
(6) 存在範囲の直線
(7) スライダと,そのコールバック関数
(8) ボタン都,そのコールバック関数
いくつか要点を。
・ベクトルの計算をするので,成分はリストではなく,numpy の配列にする。
たとえば,
a = np.array([3, 1])
b = np.array([1, 2])
s = 0.5
c = s*a + (wa-s)*b
のように計算する。wa は s+t の値。初期値は1。
・Tkinter は左上が原点 (0,0) なので,上下を逆転する。また,50dpi が座標平面の1に相当するように位置を計算する。
・(4) で,Tkinter では TeX書式が使えないようなので,ベクトルの矢印は別で描く。
このくらいか。あとは,ひたすらコーディングする。たとえば,ベクトルaに係数sをかけたベクトルは
canvas.create_line(
orgx, orgy, orgx+s*a[0]*50, orgy-s*a[1]*50,
arrow=tk.LAST,
width=3,
fill='blue',
tag="aa"
)
org は原点。arrow=tk.LAST で矢線にする。fill は描画色。
ベクトル a, b の成分と,s+t の値は簡単にかえられるようにしておく。これを 2 にすると
2分の1にすると
といった調子。実際に動かしている様子を動画にした。
全ソースコードを示す。130行近くある。
import numpy as np
import tkinter as tk
root = tk.Tk()
root.title('ベクトルの終点の存在領域')
root.geometry("450x450")
canvas = tk.Canvas(root, width=400, height=300, bg="white")
a = np.array([3, 1])
b = np.array([1, 2])
s = 0.5
# s+t=wa 初期値は 1
wa = 1
#原点
orgx = 130
orgy = 280
# === 以下は特に変えなくてもよい =====
c = s*a + (wa-s)*b
locus1 = 10*a + (wa-10)*b
locus2 = -9*a + (wa+9)*b
#式の表示
canvas.create_text(155, 35, text="→ → → ", font=('', 24))
canvas.create_text(200, 50, text="p=s a+t b (s+t = "+str(wa)+")", font=('', 24))
# a, b の表示
canvas.create_line(
orgx, orgy, orgx+a[0]*50, orgy-a[1]*50,
arrow=tk.LAST,
)
canvas.create_line(
orgx, orgy, orgx+b[0]*50, orgy-b[1]*50,
arrow=tk.LAST,
)
canvas.create_text(orgx+a[0]*50, orgy-a[1]*50+10, text="→", font=('', 20))
canvas.create_text(orgx+a[0]*50, orgy-a[1]*50+20, text="a", font=('', 20))
canvas.create_text(orgx+b[0]*50-30, orgy-b[1]*50+5, text="→", font=('', 20))
canvas.create_text(orgx+b[0]*50-30, orgy-b[1]*50+20, text="b", font=('', 20))
# c などの表示。タグ付き
canvas.create_line(
orgx, orgy, orgx+s*a[0]*50, orgy-s*a[1]*50,
arrow=tk.LAST,
width=3,
fill='blue',
tag="aa"
)
canvas.create_line(
orgx, orgy, orgx+(wa-s)*b[0]*50, orgy-(wa-s)*b[1]*50,
arrow=tk.LAST,
width=3,
fill='blue',
tag="bb"
)
canvas.create_line(
orgx, orgy, orgx+c[0]*50, orgy-c[1]*50,
arrow=tk.LAST,
tag="vc"
)
canvas.create_text(orgx+c[0]*50+10, orgy-c[1]*50-10,
text="→",
font=('', 20),
tag="vcarrow"
)
canvas.create_text(orgx+c[0]*50+10, orgy-c[1]*50,
text="p",
font=('', 20),
tag="vcstr"
)
canvas.create_line(
orgx+s*a[0]*50, orgy-s*a[1]*50,
orgx+c[0]*50, orgy-c[1]*50,
orgx+(wa-s)*b[0]*50, orgy-(wa-s)*b[1]*50,
dash=(5, 5),
fill='blue',
tag="guide"
)
canvas.create_line(
orgx+locus1[0]*50, orgy-locus1[1]*50,
orgx+locus2[0]*50, orgy-locus2[1]*50,
dash=(5, 5),
fill='white',
tag="locus"
)
# ベクトルなどの書き換え
def drawvec(n):
s = float(n)
c = s*a + (wa-s)*b
x0 = orgx
y0 = orgy
xa = orgx + s*a[0]*50
ya = orgy - s*a[1]*50
xb = orgx + (wa-s)*b[0]*50
yb = orgy - (wa-s)*b[1]*50
xc = orgx + c[0]*50
yc = orgy - c[1]*50
canvas.coords("aa", x0, y0, xa, ya)
canvas.coords("bb", x0, y0, xb, yb)
canvas.coords("vc", x0, y0, xc, yc)
canvas.coords("vcarrow", xc+10, yc-10)
canvas.coords("vcstr", xc+10, yc)
canvas.coords("guide", xa, ya, xc, yc, xb, yb)
# 軌跡 ON/OFF
flag = True
def locus(enent):
global flag
if flag:
canvas.itemconfig("locus", fill='red')
else:
canvas.itemconfig("locus", fill='white')
flag = not flag
# スライダを作る
sc = tk.Scale(from_=-3, to=3,
length= 200,
orient="horizontal",
variable=tk.DoubleVar(value=0.5),
resolution=0.01,
label="sの値",
command=drawvec
)
# ボタンを作る
btn = tk.Button(text="軌跡 ON/OFF")
#配置
canvas.place(x=20, y=20)
sc.place(x=90, y=350)
btn.place(x=300, y=350)
btn.bind("<ButtonPress>", locus)
root.mainloop()