見出し画像

Pythonで高校数学:2次関数のグラフ(3)

Pythonで高校数学:2次関数のグラフ(1) では,基本のグラフを,スライダで係数を変えて描けるようにした。
Pythonで高校数学:2次関数のグラフ(2) では,定義域が t ≦ x ≦ t+2 のときの最大値・最小値を考えるために,スライダで t の値を変えられるようにした。
 次は,一般形で係数が文字のとき,制限された定義域での最大・最小を考える。
これも,係数の変化とグラフの位置の変化がイメージできないために,生徒はかなり苦労する。

まず,スタートは次の図。0 ≦ x ≦ 2 における最大値と最小値。

画像1

aの値が変わるとこれがどう変わるか。計算で解くには,標準形に変形して,軸の位置と f(0) , f(2) の値で分類するのだが,なぜその3つで分類するかを理解するのが大変なのだ。
下にスライダがあって,aの値を変えられるようになっている。少し動かすと次のようになる。

画像2

この状態では,頂点のところで最小,f(0) が最大になる。
その手前,a=1 のときは f(0)とf(2)が同じ高さ(値)になって,2つのxの値に対して最大値が( )になる,というのが想像できるとよい。a=1 のときの式は
     f(x) = x²-2x+3 
だから,最大値は 3 ,と計算できればよい。

画像3

a の値を変化させたとき,頂点の軌跡は y=-x²+x+2 になるのだが,2次関数を学ぶ数学Ⅰの段階では軌跡は未習だ。(軌跡は数学Ⅱ)
しかし,頂点の x 座標が a , y 座標が -a²+a+2 だから   y=-x²+x+2 となる,くらいは説明してもよいのではないか。
Locus ON/OFF のボタンをクリックすると,その軌跡の表示を ON/OFF できる。

画像4

これでスライダを動かすと,頂点が緑の放物線上を動いていくことがわかる。このようにすると,頂点の軌跡の方程式を求めることで,問題の放物線がどう動くかがイメージしやすくなるはずだ。1年次で理解が困難なら,2年次で軌跡を学んだときにもう一度これをやってもいいだろう。

 さて,プログラム。
前の2回と異なるのは,軸の目盛の表示位置だ。前の2回では,枠の外側に目盛がついていた。これがデフォルト。しかし,数学で関数のグラフというなら,やはり上の図のように,軸に目盛を付けたい。そのために枠につく目盛は非表示にして(すると,plt.grid() を書いても方眼は非表示になる)目盛をつけるコードを書く。

plt.xticks([]) # 目盛を非表示にする 
plt.yticks([])
# 目盛りの表示とタイトルの表示
for n in range(-4, 5): # x軸目盛
   if n != 0:
       x = [n, n]
       y = [-0.2, 0.1]
       plt.plot(x, y, color='k', lw=0.5)
       plt.text(n-0.07, -0.6, str(n))
for n in range(-2, 7): # y軸目盛 
   if n != 0:
       x = [-0.1, 0.1]
       y = [n, n]
       plt.plot(x, y, color='k', lw=0.5)
       plt.text(-0.4, n-0.1, str(n))
plt.text(-0.4, -0.4, 'O', fontsize=14) # 原点のオー
plt.title(functext+"$ \ (0 \leqq \ x \ \leqq \ 2)$", fontsize = 14) 

目盛の表示位置に細かい座標設定が必要になるが,一度作っておいてひな形とすればよい。

 次にグラフを表示するが,次のものが必要になる。
(1) 頂点の軌跡の放物線
 ボタンで表示・非表示にするには,色を変えるのが簡便。白にすれば結果的に非表示になる。ただし,最背面に描いておく。というのは,描いた順に,レイヤー状になるので,後から描くと,線が重なったところが白抜けしてしまうからだ。
(2) 問題の関数のグラフの全体を破線で
(3) 定義域の部分を実線で
(4) 定義域をx軸上に赤で
(5) 定義域の左(x=2)から,放物線までの線。定義域に対するグラフの範囲を明確にするため。

 以上を描いたら,スライダとボタンを作り,クリックすると実行されるコールバック関数を定義する。
 ボタンは,ON/OFF でトグルにした。その切り替えのためにフラグを用意する。はじめは flag = TRUE としておいて,関数が呼ばれたら flag = not flag とすれば TRUE/FALSE が切り替わる。トグルにせずに,ONとOFF の2つを用意してもよい。
  次が全ソース。先頭の %matplotlib notebook は Jupyter Notebook のときに必要。いろいろ盛り込んだので,空行を含めて100行を超えている。(ただし,ここにコピーして code 表示にすると空行は削除されてしまう)

%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.widgets as wg
# 2次関数の定義と表示用文字列
def f(x, a):
   return(x**2 - 2*a*x + a + 2)
functext = "$f(x)=x^2-2ax+a+2$"
# 頂点の軌跡の式の定義
def f2(x):
   return(-x**2 + x + 2)
locustext = "$y=-x^2+x+2$"
#== 以下は変更しなくてよい =============
# ただし関数の式を変えた場合,式の表示位置を変える必要があるかもしれない
plt.figure(figsize=(6, 6))
plt.axis([-5, 5, -3, 7])
# 余白設定
plt.subplots_adjust(left=0.2, bottom=0.2)
plt.grid()
plt.axhline(0, lw=1.5, color='k')
plt.axvline(0, lw=1.5, color='k')
plt.xticks([]) # 目盛を非表示にする 
plt.yticks([])
# 目盛りの表示とタイトルの表示
for n in range(-4, 5): # x軸目盛
   if n != 0:
       x = [n, n]
       y = [-0.2, 0.1]
       plt.plot(x, y, color='k', lw=0.5)
       plt.text(n-0.07, -0.6, str(n))
for n in range(-2, 7): # y軸目盛 
   if n != 0:
       x = [-0.1, 0.1]
       y = [n, n]
       plt.plot(x, y, color='k', lw=0.5)
       plt.text(-0.4, n-0.1, str(n))
plt.text(-0.4, -0.4, 'O', fontsize=14) # 原点のオー
plt.title(functext+"$ \ (0 \leqq \ x \ \leqq \ 2)$", fontsize = 14) 
# ボタンのフラグ と a の初期値
flag = True
a = 0
# 頂点の軌跡 はじめは白で最背面に描いておく
# 関数の式を変えた場合,式の表示位置を変える必要があるかもしれない
x = np.linspace(-5, 5, 100)
y = f2(x)
gl, = plt.plot(x, y, lw=1 , ls='--', color='w')
tl = plt.text(-4, 1, locustext, color='w', size=13) #定義域を赤で表示 
plt.plot([0, 2], [0, 0], lw=2, color='r')
# 定義域の x = 2 の縦線
g3, = plt.plot([2, 2], [0, f(2, a)], ls='--', lw=0.5, color='r')
# 全体を破線で表示
x = np.linspace(-5, 5, 100)
y = f(x, a)
g1, = plt.plot(x, y, ls='--')
# 定義域の分だけ表示
x = np.linspace(0, 2, 50)
y = f(x, a)
g2, = plt.plot(x, y, lw=2, color='b')

# スライダの設定
ax_a = plt.axes([0.2, 0.05, 0.7, 0.04])
slider_a = wg.Slider(ax_a, 'a', -5, 5, valinit=0, valstep=0.1)
# グラフを再描画する
def update(val):
   a = slider_a.val
   x = np.linspace(-5, 5, 100)
   g1.set_xdata(x)
   g1.set_ydata(f(x, a))
   x = np.linspace(0, 2, 50)
   g2.set_xdata(x)
   g2.set_ydata(f(x, a))
   g3.set_ydata([0, f(2, a)])
   
# ボタンの設定
ax1 = plt.axes([0.2, 0.1, 0.2, 0.05])
btn = wg.Button(ax1, 'Locus ON/OFF')
def locus(event):
   global flag
   if flag == True:
       gl.set_color('g')
       tl.set_color('g')
   else:
       gl.set_color('w')
       tl.set_color('w')
   flag = not flag
slider_a.on_changed(update)
btn.on_clicked(locus)
plt.show() 

なお,Jupyter Notebook でなく,IDLE または,コマンドラインでファイル渡しで実行すると,ボタンをクリック後,ボタンからマウスを移動しないと軌跡はON/OFF にならない。

操作している様子を動画にした。

https://youtu.be/533jLzHwaY4