[研究レポート] 回転式探査レーダー

[研究レポート] 回転式探査レーダー

回転式探査レーダー付勾配降下法

 前回の続きです。今回は 2 変数関数 $z=f(x, y)$ のレーダー勾配降下(←なんかもう名称を略してる)をやります。基本的な考え方自体は 1 変数のときと同じですが、3 次元空間を探査する必要があるので、搭載するレーダーもそれなりに工夫を凝らさなくてはなりません。

回転式探査レーダー

 今回紹介する 回転式レーダー は下図のようなイメージです。

 ビームの方角変更

 $1$ ステップごとに $4$ 本のビームが同時照射されます。
 それぞれのビームの射出方向は $90^\circ$ の角度をなしていて、ステップごとに照射軸が反時計回りに回転しつつ、さらに動径方向のビームの長さ $r(t)$ も周期的に変化させます ($t$ はステップ数)。個々のビームの長さと方角 $\boldsymbol{R}(t)$ はベクトルを使って
 
\[\boldsymbol{R}(t)=\begin{bmatrix}
r(t)\cos \theta(t)\\ r(t)\sin \theta(t)\end{bmatrix}\]
のように表されます。$r(t)$ の設定方法は色々考えられますが、今回は三角関数を使って
 
\[r(t)=X\sin a(t)\]
のように設定しておきます。$X$ は $x$ の最大値 $x_{\mathrm{max}}$ と最小値 $x_{\mathrm{min}}$ の差
 
\[X=x_{\mathrm{max}}-x_{\mathrm{min}}\]
によって定義されます。これが動径方向の振幅となります。
 

テストコード

 今回はテスト関数として
\[z=2\sin x+\sin\sqrt{|x|}+\sin\frac{y}{2}\]
を使うことにします。この関数を等高線で図示すると次のようになります。

 Python テストに使用する関数の等高線図

 点線で描かれた部分が $z$ が負の値をとる領域です。浅い窪地に捕まらないように、目的地にできるだけ早く達することを試みます。

# https://python.atelierkobato.com/rotation/
# リストR2-A-1
# 回転式レーダー付勾配降下法

# 必要なモジュールをインポート
import numpy as np
import matplotlib.pyplot as plt

# テスト関数を定義
def func(x ,y):
    z = 2 * np.sin(x) + np.sin(np.sqrt(np.abs(x))) + np.sin(y/2)
    return z

# テスト関数の勾配(グラディエント)
def grad(x, y):
    gx = 2 * np.cos(x) + 0.5 * np.cos(np.sqrt(x)) / np.sqrt(x)
    gy = 0.5 * np.cos(0.5 * y)
    return np.array([gx, gy])

# ★★★★★★★★★★

# 等高線を描画
n = 129
x = np.linspace(0, 12, n)
y = np.linspace(0, 12, n)
x_max = np.max(x)
x_min = np.min(x)
y_max = np.max(y)
y_min = np.min(y)
X, Y = np.meshgrid(x, y)
Z = func(X, Y)

# FigureとAxesの設定
fig = plt.figure(figsize = (6, 6))
ax = fig.add_subplot(111)
ax.set_xlabel("x", size = 16)
ax.set_ylabel("y", size = 16)
ax.set_xlim([x_min, x_max])
ax.set_ylim([y_min, y_max])

# 等高線を描画
ct = ax.contour(X, Y, Z, 12, colors = "black")
ax.clabel(ct, fmt = "%0.1f", fontsize = 10)

# ★★★★★★★★★★

# 回転式探査レーダー勾配降下法による最小値の探索

# 初期値の設定
x_init, y_init = (2, 10)

# x,yに初期値を代入
x, y = (x_init, y_init)

# 学習率α
alpha = 0.01

# 収束判定条件
eps = 0.01

# ループの最大数と最小数
t_max = 2000
t_min = 500

# 回転レーダーの各種設定
a = 0.2
u = 1.0
du = 1.0
angle_0 = np.arange(0, np.pi, np.pi/4)
d_angle = np.pi / 16
angle = angle_0

xs_list = np.zeros(4)
ys_list = np.zeros(4)

for t in range(1, t_max):
    u += du
    angle += d_angle
    x -= alpha * grad(x, y)[0]
    y -= alpha * grad(x, y)[1]
    
    r = (x_max - x_min) * np.sin(a * u)
    xs_list = x + r * np.cos(angle)
    ys_list = y + r * np.sin(angle)
    zs_list = func(xs_list, ys_list)
    zs_min = np.min(zs_list)
    xs = xs_list[zs_list == zs_min]
    ys = ys_list[zs_list == zs_min]

    # 転移条件
    tf_1 = func(xs, ys) < func(x, y)
    tf_2 = x_min < xs[0] < x_max
    tf_3 = y_min < ys[0] < y_max

    # 条件をすべて満たせば転移して、ビーム長さを初期化
    if tf_1 and tf_2 and tf_3:
        u = 0
        x = xs[0]
        y = ys[0]

    # (x, y)をプロット
    ax.plot(x, y, color = "red",
            marker = "o", markersize = 1,
            zorder = 1, linestyle = "none")

    # 勾配の大きさの計算
    d = np.sqrt(grad(x, y)[0]**2 + grad(x, y)[1]**2)
    
    # 条件を満たせばループ終了
    if t > t_min and d < eps:
        break

print("t = {}".format(t))
print("(x, y, z) = ({0:.3f}, {1:.3f}, {2:.3f})".format(x, y, func(x, y)))
t = 1080
(x, y, z) = (11.000, 9.440, -3.174)

 窪地のレーダー探査(等高線)

 無事に目的地に到達することができました。勾配法ってゲームみたいで面白いですよね。難しい関数を用意して、それを攻略するようなレーダーを開発するみたいなことを繰り返せば、楽しみながらコードを改良できます。
 

ステップ数短縮効果と処理時間増加のトレードオフ

 図の赤い軌跡を見ると、窪地を見つけるまでのステップ数は多くなさそうです(レーダーによって素早く補足されています)が、その窪みの斜面を下ること(つまり普通の勾配降下)にステップ数の大半を費やしています。前回記事の最後で「ビームが近い場所を探査するときにショートカットすることがある」と言いましたが、これを意図的に実行するために、非常に短いビームを照射するショートレンジレーダーを搭載することも考えられます。とはいえ、レーダーの搭載数を増やすと負荷がかかって処理速度が遅くなることがあるので、慎重にステップ短縮効果と処理時間増加の兼ね合いを見極める必要があります(Jupyter Notebook における処理時間の計測方法については、こちらの記事を参照してください)。でもそんな細かい事をやる前に、もっと派手なレーダーを試してみたいのですよね?(←ただ自分がやりたいだけ)。というわけで次回記事では「拡散式パワーレーダー」を紹介する予定です。