からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

【Python】円関数(三角関数)の定義の可視化

はじめに

 円関数(三角関数)の定義や性質、公式などを可視化して理解しようシリーズです。

 この記事では、円関数のグラフを作成します。

【前の内容】

www.anarchive-beta.com

【他の内容】

www.anarchive-beta.com

【この記事の内容】

円関数の定義の可視化

 円関数(circular functions)・三角関数(trigonometric functions)の4つの関数(sin関数・cos関数・tan関数・cot関数)の定義をグラフで確認します。
 各関数や作図処理の詳細についてはそれぞれの記事を参照してください。

 利用するライブラリを読み込みます。

# 利用ライブラリ
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation


単位円の作図

 まずは、角度(ラジアン)軸を追加した3次元の曲線として、単位円の円周を可視化します。
 円周については「円周の作図【Matplotlib】 - からっぽのしょこ」を参照してください。

グラフの作成

 変数を固定して、円周上の点のグラフを作成します。

 変数の範囲を指定して、曲線の座標を作成します。

# 曲線用のラジアンの範囲を指定
theta_vec = np.linspace(start=0*np.pi, stop=2*np.pi, num=1001)
print(theta_vec[:5])

# 曲線の座標を計算
x_vec = np.cos(theta_vec)
y_vec = np.sin(theta_vec)
print(x_vec[:5])
print(y_vec[:5])
[0.         0.00628319 0.01256637 0.01884956 0.02513274]
[1.         0.99998026 0.99992104 0.99982235 0.99968419]
[0.         0.00628314 0.01256604 0.01884844 0.0251301 ]

 変数(ラジアン)  \theta の範囲を指定して、数値ベクトルを作成します。範囲を  2 \pi の倍数にすると周期性を確認しやすくなります。円周率  \pinp.pi で扱えます。
 cos関数をx座標  x = \cos \theta、sin関数をy座標  y = \sin \theta とします。sin関数は np.sin()、cos関数は np.cos() で計算できます。

 変数を指定して、曲線上の点の座標を作成します。

# 点用のラジアンを指定
theta_val = 5/6 * np.pi
print(theta_val)

# 点の座標を計算
x_val = np.cos(theta_val)
y_val = np.sin(theta_val)
print(x_val)
print(y_val)
2.6179938779914944
-0.8660254037844387
0.49999999999999994

 変数(ラジアン)  \theta を指定して、曲線の座標と同様にして計算します。

・装飾用の作図コード(クリックで展開)

 角マークの座標を作成します。

# 角マークの座標を作成
d  = 0.2
ds = 0.005
u_vec = np.linspace(start=0, stop=theta_val, num=600)
angle_mark_x_vec = (d + ds*u_vec) * np.cos(u_vec)
angle_mark_y_vec = (d + ds*u_vec) * np.sin(u_vec)
print(angle_mark_x_vec[:5])
print(angle_mark_y_vec[:5])
[0.2        0.20001994 0.20003606 0.20004836 0.20005684]
[0.         0.00087421 0.0017486  0.00262315 0.00349784]

 2つの線分のなす角(偏角)  \theta を示す角マークとして、 0 \leq u \leq \theta のラジアンを作成して、半径が  d の円弧の座標  (x, y) = (d \cos u, d \sin u) を計算します。半径 d でサイズを調整します。
 偏角が1周を超える(  |\theta| \gt 2 \pi の)場合は、係数が  d の螺旋の座標  (x, y) = (d u \cos u, d u \sin u) を使うと明示できます。この例では、ノルムの基準値 d と間隔用の係数 ds でサイズを調整します。

 角ラベルの座標を作成します。

# 角ラベルの座標を作成
d = 0.4
u_val = 0.5 * theta_val
angle_label_x_val = d * np.cos(u_val)
angle_label_y_val = d * np.sin(u_val)
print(angle_label_x_val)
print(angle_label_y_val)
0.1035276180410083
0.38637033051562736

 角マークの中点に角ラベルを配置することにします。 u = \frac{\theta}{2} のラジアンを作成して、円弧上の点の座標を計算します。原点からのノルム d で表示位置を調整します。

 ラジアン軸目盛の座標を作成します。

# 半周期(範囲π)における目盛数(分母の値)を指定
tick_num = 6

# 目盛番号(分子の値)の範囲を設定
tick_min = np.floor(theta_vec.min() / np.pi) * tick_num
tick_max = np.ceil(theta_vec.max() / np.pi) * tick_num

# 目盛番号(分子の値)を作成
tick_vec = np.arange(start=tick_min, stop=tick_max+1)
print(tick_vec[:5])

# 目盛位置を作成
rad_break_vec = tick_vec/tick_num * np.pi
print(rad_break_vec[:5])

# 目盛ラベルを作成
rad_label_vec = [f'$\\frac{{{i:.0f}}}{{{tick_num}}} \pi$' for i in tick_vec]
print(rad_label_vec[:5])
rad_label_vec = [f'${t/np.pi:.2f} \pi$' for t in rad_break_vec]
print(rad_label_vec[:5])
[0. 1. 2. 3. 4.]
[0.         0.52359878 1.04719755 1.57079633 2.0943951 ]
['$\\frac{0}{6} \\pi$', '$\\frac{1}{6} \\pi$', '$\\frac{2}{6} \\pi$', '$\\frac{3}{6} \\pi$', '$\\frac{4}{6} \\pi$']
['$0.00 \\pi$', '$0.17 \\pi$', '$0.33 \\pi$', '$0.50 \\pi$', '$0.67 \\pi$']

 ラジアン  \theta に関する軸目盛ラベルを  \frac{i}{n} \pi の形または  \frac{i}{n} を小数にした形で表示することにします。 n は半周期(  \pi 間隔の範囲)における目盛数、 i は(負の数を含む)目盛番号に対応します。
 theta_vec の最小値・最大値に対して、 \theta = \frac{i}{n} \pi i について整理した  i = \frac{\theta}{\pi} n を計算して、最小番号から最大番号までの整数を作成します。整数にするために、小数部分に関して最小値は floor() で切り捨て、最大値は ceiling() で切り上げておきます。
 作成した目盛番号に対応する目盛位置(ラジアン)を  \theta = \frac{i}{n} \pi で計算します。

 3次元の単位円の円周のグラフを作成します。

# グラフサイズを設定
axis_size  = 2
axis_t_min = rad_break_vec.min()
axis_t_max = rad_break_vec.max()

# 矢サイズを指定
alr = 0.25

# 3D単位円周を作図
fig, ax = plt.subplots(figsize=(12, 12), dpi=100, facecolor='white', 
                       subplot_kw={'projection': '3d'})
ax.quiver(axis_t_min, [0, 0, axis_size], [0, -axis_size, 0], 
          axis_t_max-axis_t_min, 0, 0, 
          color='black', linewidth=1, arrow_length_ratio=alr/(axis_t_max-axis_t_min), 
          linestyle=['solid', 'dashed', 'dashed']+['solid']*6) # θ軸線
ax.quiver(axis_t_min, [-axis_size, 0], [0, -axis_size], 
          0, [2*axis_size, 0], [0, 2*axis_size], 
          color='black', linewidth=1, arrow_length_ratio=alr*0.5/axis_size, 
          linestyle=['dashed']*2+['solid']*4) # x・y軸線
ax.plot(x_vec, y_vec, zs=axis_t_min, zdir='x', 
        color='C0', linestyle='dashed') # 円周
ax.plot(theta_vec, x_vec, zs=-axis_size, zdir='z', 
        color='navy', linestyle='dashed') # cos曲線
ax.plot(theta_vec, y_vec, zs=axis_size, zdir='y', 
        color='crimson', linestyle='dashed') # sin曲線
ax.quiver([theta_val, axis_t_min], 0 ,0, 
          0, x_val, y_val, 
          color='black', arrow_length_ratio=0) # 動径
ax.plot(axis_t_min.repeat(600), angle_mark_x_vec, angle_mark_y_vec, 
        color='black', linewidth=1) # 角マーク
ax.text(axis_t_min, angle_label_x_val, angle_label_y_val, 
        s='$\\theta$', size=15, ha='center', va='center') # 角ラベル
ax.plot(theta_vec, x_vec, y_vec, linewidth=2) # 関数曲線
ax.scatter([theta_val, axis_t_min, theta_val, theta_val], 
           [x_val, x_val, x_val, axis_size], 
           [y_val, y_val, -axis_size, y_val], 
           s=100) # 関数点
ax.quiver(theta_val, x_val, y_val, 
          [axis_t_min-theta_val, 0, 0], [0, 0, axis_size-x_val], [0, -axis_size-y_val, 0], 
          arrow_length_ratio=0, color='gray', linestyle='dotted') # 点の目盛線
ax.quiver([theta_val, axis_t_min, theta_val], 0, [0, 0, -axis_size], 
          0, x_val, 0, 
          arrow_length_ratio=0, color='navy', label='$\cos \\theta$') # cos線分
ax.quiver([theta_val, axis_t_min, theta_val], [x_val, x_val, axis_size], 0, 
          0, 0, y_val, 
          arrow_length_ratio=0, color='crimson', label='$\sin \\theta$') # sin線分
ax.set_xticks(ticks=rad_break_vec, labels=rad_label_vec) # θ軸目盛
ax.set_xlim(xmin=axis_t_min, xmax=axis_t_max)
ax.set_ylim(ymin=-axis_size, ymax=axis_size)
ax.set_zlim(zmin=-axis_size, zmax=axis_size)
ax.set_xlabel('$\\theta$')
ax.set_ylabel('$x = r\ \cos \\theta$')
ax.set_zlabel('$y = r\ \sin \\theta$')
ax.set_title(f'$r = 1, \\theta = {theta_val/np.pi:.2f} \pi, (x, y) = ({x_val:.2f}, {y_val:.2f})$', loc='left')
fig.suptitle('unit circle', fontsize=20)
ax.legend()
ax.set_box_aspect((axis_t_max-axis_t_min, 2*axis_size, 2*axis_size))
#ax.view_init(elev=0, azim=0) # 円周
#ax.view_init(elev=0, azim=270) # sin曲線
#ax.view_init(elev=90, azim=270) # cos曲線
plt.show()

単位円の円周とsin関数・cos関数の曲線の関係

単位円の円周とsin関数・cos関数の曲線の関係

 原点を中心とする半径  r の円周の座標は  (x, y) = (r \cos \theta, r \sin \theta) です。よって、単位円(半径が  r = 1 の円)の円周のx座標がcos関数、y座標がsin関数に対応するのを確認できます。

アニメーションの作成

 変数を変化させて、円周上の点のアニメーションを作成します。
 作図コードについては「circular_definition.py at anemptyarchive/Mathematics · GitHub」を参照してください。

 単位円の円周上の点とsin関数・cos関数それぞれの曲線上の点が対応しているのを確認できます。

単位円と関数の関係

 次は、単位円における偏角(単位円上の点)と円関数(sin・cos・tan・cot)の関係を確認します。
 平面上での関係については「【R】円関数(三角関数)の可視化 - からっぽのしょこ」を参照してください。

 執筆中です...

おわりに

 3月14日なので円周率ネタの記事を書きたかったのですが、想定の半分しか間に合いませんでした。3月中には完成させたいです…
 この円周率ソングを聴きながら待っててください。

 仮!K・A・R・I!

【次の内容】

つづく