Return to 高さ h からの斜方投射の最大到達距離を求める準備

SciPy で高さ h からの斜方投射の最大到達距離を求める

地面から高さ $h$ の地点から空気抵抗なしの斜方投射を行うと,初速度の大きさを一定とした場合,水平方向の到達距離が最大となるのは打ち上げ角度(仰角)が何度のときかを Python の SciPy と NumPy を使って(かつ SymPy を使わずに)求める。

運動方程式の解

初期条件を (t = 0) で

$$x(0) = 0, \quad y(0) = h, \quad v_x(0)  = v_0 \cos\theta, \quad v_y(0) = v_0 \sin \theta$$

としたときの解は

\begin{eqnarray}
x(t) &=& v_0 \cos\theta\cdot t \\
y(t) &=& h + v_0 \sin\theta\cdot t – \frac{1}{2} g t^2 \\
v_x(t) &=& v_0 \cos\theta \\
v_y(t) &=& v_0 \sin\theta – g t
\end{eqnarray}

無次元化された解

この系に特徴的な時間 $\displaystyle \tau \equiv \frac{v_0}{g}$ および長さ $\displaystyle \ell \equiv v_0 \tau = \frac{v_0^2}{g}$ で解を無次元化すると,

\begin{eqnarray}
T &\equiv& \frac{t}{\tau} \\
H &\equiv& \frac{h}{\ell} \\
X &\equiv& \frac{x}{\ell} = \cos\theta\cdot T \\
Y&\equiv& \frac{y}{\ell} = H+ \sin\theta\cdot T – \frac{1}{2} T^2 \\
V_x &\equiv& \frac{d\bar{x}}{d\bar{t}} = \cos\theta \\
V_y &\equiv& \frac{d\bar{y}}{d\bar{t}} = \sin\theta – T
\end{eqnarray}

無次元化された滞空時間と水平到達距離

無次元化された滞空時間 $T_1$ は

$$T_1 = \sin\theta + \sqrt{\sin^2\theta + 2 H}$$

この時間での水平方向の無次元化された到達距離 $L$ は

$$L = X(T_1) = \cos\theta\cdot T_1$$

詳細は以下のページ

必要なモジュールの import

必要なモジュールを import します。

In [1]:
# NumPy を使います
import numpy as np

# Matplotlib でグラフを描きます
import matplotlib.pyplot as plt

# 方程式の数値解 
from scipy.optimize import root_scalar

# グラフを SVG で Notebook にインライン表示させる設定
%config InlineBackend.figure_formats = ['svg']

関数の定義

無次元化された解 $X(T), Y(T)$ および滞空時間 $T_1$ と水平到達距離 $L$ を関数として定義します。

In [2]:
# 角度 th は度で 
def X(T, th):
    theta = np.radians(th)
    return np.cos(theta) * T

def Y(T, th, H):
    theta = np.radians(th)
    return H + np.sin(theta)*T - T**2/2

def T1(th, H):
    theta = np.radians(th)
    return np.sin(theta) + np.sqrt(np.sin(theta)**2 + 2*H)

def L(th, H):
    theta = np.radians(th)
    return np.cos(theta) * T1(th, H)

$H = 0$ の場合の軌道

$\theta = 45^{\circ}$ 前後の角度での斜方投射の軌道をグラフにしてみる。

まずは,とりあえずのグラフ:

In [3]:
# θ = 45° のグラフ
th = 45
H = 0

# 滞空時間 T1 を 100 等分
T_list = np.linspace(0, T1(th, H), 100+1)

plt.plot(X(T_list, th), Y(T_list, th, H));

以下のようなオプションを設定して,もう少し体裁を整えます。

  • $\theta$ の値を凡例に
  • グリッド(格子線)をドットに
  • 座標軸のラベルとグラフのタイトル
  • 横軸縦軸の表示範囲
  • 座標軸の目盛の間隔
  • 縦軸横軸のアスペクト比
In [4]:
# θ = 45° のグラフ
th = 45
H = 0

# 滞空時間 T1 を 100 等分
T_list = np.linspace(0, T1(th, H), 100+1)

# 凡例
key = 'θ = %2d°' % th

# plot でグラフを描く
plt.plot(X(T_list, th), Y(T_list, th, H), label = key)

# グラフのオプション設定例
# グリッドをドットで
plt.grid(linestyle='dotted')

# 座標軸のラベル
plt.xlabel("X")
plt.ylabel("Y")

# グラフのタイトル
plt.title("H = 0 の場合の斜方投射")

# 横軸縦軸の表示範囲
plt.xlim(0, 1.1)
plt.ylim(0, 0.5)

# 座標軸の目盛の間隔を手動で設定する場合
# 間隔を指定して np.arange() を使う例
# 0 から 0.1 間隔で 1.1 未満(つまり 1.0)まで
plt.xticks(np.arange(0, 1.1, 0.1))

# 要素数を指定して np.linspace() を使う例
# (0, 0.5) 区間を 5 等分
plt.yticks(np.linspace(0, 0.5, 5+1))

# 45°が45°に見えるように
# 縦軸横軸のアスペクト比を 'equal' に。
plt.gca().set_aspect('equal')

# 凡例を表示
plt.legend();

$\theta$ の値を $43^{\circ}$ から $47^{\circ}$ まで $1^{\circ}$ 刻みで変えて,複数の曲線を一度に描く例。

for で繰り返し処理させてみます。

なお,細かいことですが,グラフの最高点は $\theta$ の値が大きいときですので,先に $47^{\circ}$ から描きはじめて,$1^{\circ}$ ずつ減らしてみます。

In [5]:
H = 0

# 複数のグラフを一度に描く場合,
# for ループで繰り返し処理
for th in range(47, 42, -1):

    # 滞空時間 T1 を 100 等分
    T_list = np.linspace(0, T1(th, H), 100+1)

    # 凡例
    key = 'θ = %2d°' % th

    # plot でグラフを描く
    plt.plot(X(T_list, th), Y(T_list, th, H), label = key)

plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("X")
plt.ylabel("Y")
plt.title("H = 0 の場合の斜方投射")

# 横軸縦軸の表示範囲と目盛
plt.xlim(0, 1.1)
plt.ylim(0, 0.5)
plt.xticks(np.arange(0, 1.1, 0.1))

plt.gca().set_aspect('equal')

# 凡例を表示
plt.legend();

最大到達距離となる角度 $\theta$ を調べるために,着地点付近を拡大表示します。

In [6]:
H = 0

# 複数のグラフを一度に描く場合,
# for ループで繰り返し処理
for th in range(47, 42, -1):

    # 滞空時間 T1 を 100 等分
    T_list = np.linspace(0, T1(th, H), 100+1)

    # 凡例
    key = 'θ = %2d°' % th

    # plot でグラフを描く
    plt.plot(X(T_list, th), Y(T_list, th, H), label = key)

plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("X")
plt.ylabel("Y")
plt.title("H = 0 の場合の斜方投射")

# 着地点付近を拡大表示
plt.xlim(0.99, 1.0)
plt.ylim(0, 0.01)

# 凡例を表示
plt.legend();
plt.savefig("Nhtosha04.svg");

上のグラフから,$H=0$ の場合には確かに $\theta=45^{\circ}$ の時に最大到達距離になっていることがわかります。

$H = 0.2$ の場合の軌道

今度は,$H=0.2$ としてやってみます。

In [7]:
H = 0.2
for th in range(47, 37, -1):
    T_list = np.linspace(0, T1(th, H), 100+1)
    key = 'θ = %2d°' % th
    plt.plot(X(T_list, th), Y(T_list, th, H), label = key)

plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("X")
plt.ylabel("Y")
plt.title("H = 0.2 の場合の斜方投射")

# 横軸縦軸の表示範囲を調整する
plt.xlim(0, 1.5)
plt.ylim(0, 0.7)
plt.xticks(np.arange(0, 1.4, 0.1))
plt.gca().set_aspect('equal')

# 凡例を表示
plt.legend();

最大到達距離となる角度 $\theta$ を調べるために,着地点付近を拡大表示します。

In [8]:
H = 0.2
for th in range(47, 37, -1):
    T_list = np.linspace(0, T1(th, H), 100+1)
    key = 'θ = %2d°' % th
    plt.plot(X(T_list, th), Y(T_list, th, H), label = key)

plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("X")
plt.ylabel("Y")
plt.title("H = 0.2 の場合の斜方投射")

# 着地点付近を拡大表示
plt.xlim(1.15, 1.19)
plt.ylim(0, 0.02)

# 凡例を表示
plt.legend();

水平到達距離が最大となるのは $\theta=45^{\circ}$ のときではない!ことがわかるだろう。

水平到達距離 $L$

定量的に調べるために,水平到達距離 L(th, H) をグラフにしてみる。

In [9]:
H = 0.2
th_list = np.linspace(38, 48, 101)

plt.plot(th_list, L(th_list, H))
plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("打ち出し角度 (°)")
plt.ylabel("規格化された水平到達距離")
plt.title("H = 0.2 の場合の打ち出し角度と水平到達距離")

# 横軸縦軸の表示範囲を調整する
plt.xlim(38, 48)
plt.xticks(np.arange(38, 48, 1));

上のグラフをみると,$H=0.2$ の場合に水平到達距離が最大となるのは $\theta=45^{\circ}$ のときではなく… どのくらい?

L が最大となる角度を数値微分で求める

数値微分して $\displaystyle \frac{dL(\theta, H)}{d\theta} = 0$ となる角度 $\theta$ を求めてみる。

$\theta \rightarrow x$ として,

$$\frac{dL(x, H)}{dx} \simeq \frac{L(x + \delta) – L(x – \delta)}{2 \delta}, \quad |\delta| << 1$$

In [10]:
# とりあえず,以下のように定義しておく。
# 数値微分: delta は小さいほどよい,というわけではない 
# scipy.optimize.root_scalar を使うときは
# x の関数として定義
def dL(x, H, delta):
    return (L(x+delta, H) - L(x-delta, H))/(2*delta)

$dL(x) = 0$ の解を $x$ について数値的に求めるには,scipy.optimize.root_scalar を使います。

In [11]:
H = 0.2
# delta を変えてみる
for delta in [1.e-3, 1.e-4, 1.e-5, 1.e-6, 1.e-7]:
    # x 以外の変数は args に
    print(root_scalar(dL, bracket = [38, 48], args=(H, delta)).root)
40.2029658845539
40.20296588569512
40.20296588835251
40.20296588448801
40.20296551268753

上の結果から,delta = 1.e-5 にして以下のような関数を定義します。

In [12]:
def thmax(H):
    """ 高さ H からの斜方投射で最大到達距離になる角度(°)
    """
    delta = 1.e-5
    ans = root_scalar(dL, bracket = [1, 89], args=(H, delta)).root    
    return ans
In [13]:
thmax(0.2)
Out[13]:
40.20296588400588

定義した thmax(H) を使って,H = 0.2 のときの最大到達距離となる軌道のグラフを描きます。

In [14]:
# 上の結果を使ってグラフを描く

H = 0.2

# 時間 T の範囲。滞空時間 T1 まで。
T_list = np.linspace(0, T1(thmax(H), H), 100)

# 凡例の数値の桁数を揃える
key = 'H=%3.1f, θ=%6.3f, Lmax=%6.3f' % (H, thmax(H), L(thmax(H), H))

plt.plot(X(T_list, thmax(H)), Y(T_list, thmax(H), H), label = key)

# グラフのオプション設定例
# グリッドをドットで
plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("X")
plt.ylabel("Y")
plt.title("高さ H からの斜方投射の最大水平到達距離")

# 横軸縦軸の表示範囲
plt.xlim(0, 1.2)
plt.ylim(0, 0.5)
plt.xticks(np.arange(0, 1.3, 0.1))
plt.gca().set_aspect('equal')

# 凡例を表示
plt.legend();

問題 1

高さ $H$ からの斜方投射の水平到達距離が最大となる打ち出し角度と,そのときの最大水平到達距離がわかるようなグラフを次の $H$ についてまとめて描け。

$H = 0.2, 0.4, 0.6, 0.8, 1.0$

解答例

In [15]:
Hl = np.linspace(0.2, 1, 5)
Hl
Out[15]:
array([0.2, 0.4, 0.6, 0.8, 1. ])
In [16]:
for H in reversed(Hl):
    # 時間 T の範囲。滞空時間 T1 まで。
    T_list = np.linspace(0, T1(thmax(H), H), 100)

    # 凡例の数値の桁数を揃える
    key = 'H=%3.1f, θ=%6.3f, Lmax=%6.3f' % (H, thmax(H), L(thmax(H), H))

    plt.plot(X(T_list, thmax(H)), Y(T_list, thmax(H), H), label = key)

# グラフのオプション設定例
# グリッドをドットで
plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("X")
plt.ylabel("Y")
plt.title("高さ H からの斜方投射の最大水平到達距離")

# 横軸縦軸の表示範囲
plt.xlim(0, 2.4)
plt.ylim(0, 1.5)
#plt.xticks(np.arange(0, 1.3, 0.1))
plt.gca().set_aspect('equal')

# 凡例を表示
plt.legend();

問題 2

高さ $H$ を横軸に,そのときの水平到達距離が最大となる打ち出し角度 $\theta$ を縦軸にしたグラフを描け。

解答例

In [17]:
# 横軸は H
# (0, 1) 区間を 100等分
Hlist = np.linspace(0, 1, 101)

# 縦軸は thmax(H)
thmaxlist = []
for H in Hlist:
    thmaxlist += [thmax(H)]

plt.plot(Hlist, thmaxlist)

# グラフのオプション設定例
# グリッドをドットで
plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("高さ H")
plt.ylabel("θmax (°)")
plt.title("高さ H からの斜方投射の最大水平到達距離を与える角度");

問題 3

高さ $H$ を横軸に,そのときの最大水平到達距離を縦軸にしたグラフを描け。

解答例

In [18]:
# 横軸は H
# (0, 1) 区間を 100等分
Hlist = np.linspace(0, 1, 101)

# 縦軸は L(thmax(H), H)
thmaxlist = []
for H in Hlist:
    thmaxlist += [thmax(H)]

plt.plot(Hlist, L(thmaxlist, Hlist))

# グラフのオプション設定例
# グリッドをドットで
plt.grid(linestyle='dotted')

# 座標軸のラベルとタイトル
plt.xlabel("高さ H")
plt.ylabel("最大水平到達距離 L")
plt.title("高さ H からの斜方投射の最大水平到達距離");

問題 4

$h = 5\,\mbox{(m)}$ の高さから速さ $v_0 = 10\,\mbox{(m/s)}$ で斜方投射したときの最大水平到達距離は何 $\mbox{m}$ か。またそのときの打出し角度は何度か。

ヒント:

$h = 5\,\mbox{(m)}$ は規格化された高さ $H$ ではいくらか,また規格化された最大到達距離 $L_{\rm max}(H)$ を次元をもった量に直すといくらか,ということ。

\begin{eqnarray}
\ell &=& \frac{v_0^2}{g} \\
H &=& \frac{h}{\ell} = \frac{g h}{v_0^2} \\
x &=& \ell X = \frac{v_0^2}{g} X \\
x_{\rm max} &=& \frac{v_0^2}{g} L_{\rm max}(H)
\end{eqnarray}

重力加速度 $g = 9.80665 \,(\mbox{m}/\mbox{s}^2)$

解答例

In [19]:
h = 5
v0 = 10
g = 9.80665

# 系に特徴的な長さ
ell = v0**2/g

# 規格化された高さ H
H = h/ell

# ell をかけて次元をもった量に変換
xmax = ell * L(thmax(H), H)
print('高さ h = %2d m のときの最大水平到達距離は' % h, end=' ')
print('%9.6f m' % xmax)

print('そのときの打ち出し角度は %9.6f°' % thmax(H))
高さ h =  5 m のときの最大水平到達距離は 14.351088 m
そのときの打ち出し角度は 35.395688°