Pythonで航空機を飛ばす!AI時代のフライト制御入門
Pythonで航空機を飛ばす!AI時代のフライト制御入門[非AIエンジニアによるAI時代のコーディング]
はじめに:AIとPythonで航空機のフライト制御に挑戦!
皆さん、こんにちは!この記事では、「非AIエンジニアによるAI時代のコーディング」シリーズとして、Pythonを使って航空機のフライト制御の基礎を体験します。今回は、実際の航空機の経路設定でよく用いられる、あらかじめ設定された経路を自動で飛行する「ウェイポイント追従」というガイダンス手法にPID制御を組み合わせ、そのシミュレーションをPythonで実装します。今回も、CursorのAgent機能を主に使用しました。
今回のプロダクト:航空機の2次元ウェイポイント追従シミュレーション
今回開発するのは、2次元平面上を飛行する航空機が、あらかじめ設定されたウェイポイント(経由地点)を順番に辿っていくシミュレーションプログラムです。
このプログラムを使うことで、
- 航空機がどのように目標地点に向かって飛行するのか
- どのようなアルゴリズムで制御すれば、正確に経路を追従できるのか
といった、フライト制御の基本的な考え方を学ぶことができます。
シミュレーション結果は、アニメーションで表示されるので、航空機の動きを目で見て確認できるのも面白いところです。
プログラム解説:Pythonで航空機を制御してみよう!
それでは、Cursorが実際にPythonで書いたプログラムを見ていきましょう。
プログラム全体構成
このプログラムは、大きく分けて以下の要素で構成されています。
-
Vehicle
クラス: 航空機の状態(位置、速度、方位など)を管理する -
Controller
クラス: 航空機を目標地点に導くための制御アルゴリズム -
PIDController
クラス: PID制御を行うためのクラス - シミュレーション: 航空機の動きを時間経過とともに計算し、結果をアニメーションで表示する
Vehicle
クラス
まずは、航空機の状態を管理するVehicle
クラスを見てみましょう。
class Vehicle:
def __init__(self):
# 初期位置を最初のウェイポイントに設定
self.x = 0 # 後で更新
self.y = 0 # 後で更新
self.v = 0 # 速度 [m/s]
self.theta = 0 # 機首方位 [rad]
def update(self, dt, acc, alpha):
# 運動方程式に基づいて状態を更新
self.v += acc * dt
self.theta += alpha * dt
self.x += self.v * np.cos(self.theta) * dt
self.y += self.v * np.sin(self.theta) * dt
このクラスは、航空機の位置(x、y)、速度(v)、機首方位(theta)などの情報を保持し、時間経過に伴うこれらの変化を計算します。
Controller
クラス
次に、航空機を目標地点に導くための制御アルゴリズムを実装したController
クラスを見てみましょう。
class Controller:
def __init__(self):
# PIDコントローラのゲイン設定
self.pid_v = PIDController(0.5, 0.01, 0.1) # 速度制御用PID
self.pid_theta = PIDController(2.0, 0.01, 0.2) # 方位制御用PID
self.target_v = 30 # 目標速度 [m/s]
self.waypoint_index = 0
self.arrival_radius = 5.0 # ウェイポイント到達判定半径 [m]
self.slowdown_radius = 15.0 # 減速開始半径 [m]
self.max_angle_error = np.pi/4 # 許容最大方位誤差 [rad]
self.final_approach = False
self.stop_decel = 1.0 # 停止時の減速度を緩和 [m/s²]
def compute_control(self, vehicle, waypoints):
if self.waypoint_index >= len(waypoints):
# 最終地点での停止(緩やかに)
if abs(vehicle.v) < 0.01:
vehicle.v = 0
return 0, 0
# 方位制御を維持しながら緩やかに減速
target = waypoints[-1] # 最終ウェイポイント
dx = target[0] - vehicle.x
dy = target[1] - vehicle.y
desired_theta = np.arctan2(dy, dx)
# 方位誤差の計算
theta_error = desired_theta - vehicle.theta
theta_error = np.arctan2(np.sin(theta_error), np.cos(theta_error))
# 方位制御は維持しながら減速
alpha = self.pid_theta.update(theta_error)
decel = -self.stop_decel if vehicle.v > 0 else self.stop_decel
return decel, alpha
# 現在の目標ウェイポイント
target = waypoints[self.waypoint_index]
# ウェイポイントまでの距離と方位を計算
dx = target[0] - vehicle.x
dy = target[1] - vehicle.y
distance = np.sqrt(dx**2 + dy**2)
desired_theta = np.arctan2(dy, dx)
# 方位誤差の計算
theta_error = desired_theta - vehicle.theta
theta_error = np.arctan2(np.sin(theta_error), np.cos(theta_error))
# 速度制御(方位誤差が大きい場合は減速)
target_speed = self.target_v
angle_factor = 1.0 - min(abs(theta_error) / self.max_angle_error, 1.0)
if distance < self.slowdown_radius:
# 距離に応じて線形に減速
target_speed = max(5.0, self.target_v * (distance / self.slowdown_radius))
# 方位誤差が大きい場合は速度を抑制
target_speed *= max(0.3, angle_factor)
# ウェイポイント到達判定
if distance < self.arrival_radius:
self.waypoint_index += 1
print(f"Waypoint {self.waypoint_index} に到達")
if self.waypoint_index >= len(waypoints):
print("最終目的地に到達、停止します")
self.final_approach = True
# 最終接近時は一定の減速度で停止
decel = -self.stop_decel if vehicle.v > 0 else self.stop_decel
return decel, 0
# PID制御による制御入力の計算
v_error = target_speed - vehicle.v
acc = self.pid_v.update(v_error)
alpha = self.pid_theta.update(theta_error)
# 制御入力の制限
acc = np.clip(acc, -5, 5)
alpha = np.clip(alpha, -1, 1)
return acc, alpha
このクラスは、PID制御を用いて、航空機への制御入力(加速度 acc、方位変化率 alpha)を計算します。
-
PIDController
: 速度と方位の制御にPID制御を使用。 -
target_v
: 目標速度 -
waypoint_index
: 現在目標としているウェイポイントのインデックス -
arrival_radius
: ウェイポイント到達判定半径 -
slowdown_radius
: 減速開始半径 -
max_angle_error
: 許容最大方位誤差 -
final_approach
: 最終接近フラグ -
stop_decel
: 停止時の減速度
PIDController
クラス
Controllerクラスで使用されているPID制御を行うPIDController
クラスを見てみましょう。
class PIDController:
def __init__(self, Kp, Ki, Kd):
self.Kp = Kp
self.Ki = Ki
self.Kd = Kd
self.integral = 0
self.prev_error = 0
def update(self, error):
self.integral += error
derivative = error - self.prev_error
self.prev_error = error
return self.Kp * error + self.Ki * self.integral + self.Kd * derivative
このクラスは、PID制御のゲイン(Kp、Ki、Kd)を設定し、誤差に基づいて制御量を計算します。
シミュレーション
シミュレーション部分では、航空機の状態を時間経過とともに更新し、その結果をアニメーションで表示します。
def simulate_waypoint_following():
# 初期化
vehicle = Vehicle()
controller = Controller()
# 日本の主要空港の座標と名前
R = 6371000
origin = [135.0, 35.0]
airports = [
([139.7819, 35.5489], "羽田空港"),
([136.9647, 35.2550], "名古屋空港"),
([135.2440, 34.7850], "関西空港"),
([132.9194, 34.4361], "広島空港"),
([130.4514, 33.5847], "福岡空港"),
([130.7189, 31.8034], "鹿児島空港")
]
waypoints = np.array([p[0] for p in airports])
# 緯度経度を平面直角座標に変換
waypoints = np.array([
[R * np.cos(np.radians(p[1])) * np.radians(p[0] - origin[0]),
R * np.radians(p[1] - origin[1])]
for p in waypoints
]) * 0.0002
# 初期位置を最初のウェイポイントに設定
vehicle.x = waypoints[0][0]
vehicle.y = waypoints[0][1]
controller.waypoint_index = 1 # 2番目のウェイポイントから目指す
# アニメーションの設定
plt.rcParams['font.family'] = 'Arial' # フォントをArialに設定
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_xlim(-100, 100)
ax.set_ylim(-100, 100)
ax.grid(True)
ax.set_xlabel('X [m]')
ax.set_ylabel('Y [m]')
ax.set_title('航空機のウェイポイント追従シミュレーション', fontname="MS Gothic") # 日本語フォントを指定
# ウェイポイントのプロットと名前の表示
waypoint_markers = [] # ウェイポイントのマーカーを保存
radius_circles = [] # 到達判定半径の円を保存
# 初期状態でのウェイポイントと到達半径の表示
for i, (coord, name) in enumerate(zip(waypoints, [a[1] for a in airports])):
# ウェイポイントのマーカー
marker, = ax.plot(coord[0], coord[1], 'r*', markersize=10, label='Waypoints' if i == 0 else "")
waypoint_markers.append(marker)
# 到達判定半径の円
circle = plt.Circle((coord[0], coord[1]), controller.arrival_radius,
fill=False, linestyle='--', color='g', alpha=0.5)
ax.add_patch(circle)
radius_circles.append(circle)
# ウェイポイント名の表示
ax.annotate(name, (coord[0], coord[1]),
xytext=(5, 5), textcoords='offset points',
fontname="MS Gothic", fontsize=8)
# ウェイポイント間を結ぶ線
ax.plot(waypoints[:,0], waypoints[:,1], 'r--', alpha=0.3)
# 機体の軌跡と現在位置
trajectory, = ax.plot([], [], 'b-', label='Trajectory')
position, = ax.plot([], [], 'ko', label='Aircraft')
# 情報表示用のテキスト(位置を調整)
info_text = ax.text(-95, 85, '', fontsize=10, fontname="MS Gothic",
bbox=dict(facecolor='white', alpha=0.7)) # 背景を追加して見やすく
# 凡例の表示
ax.legend()
def init():
trajectory.set_data([], [])
position.set_data([], [])
return trajectory, position, info_text, *waypoint_markers, *radius_circles
trajectory_x = []
trajectory_y = []
def animate(frame):
if frame >= 200:
anim.event_source.stop()
return trajectory, position, info_text, *waypoint_markers, *radius_circles
dt = 0.1
acc, alpha = controller.compute_control(vehicle, waypoints)
vehicle.update(dt, acc, alpha)
# 軌跡データの更新
trajectory_x.append(vehicle.x)
trajectory_y.append(vehicle.y)
# プロットの更新
trajectory.set_data(trajectory_x, trajectory_y)
position.set_data([vehicle.x], [vehicle.y])
# 到達済みウェイポイントの色を変更
for i in range(controller.waypoint_index):
if i < len(waypoint_markers):
waypoint_markers[i].set_color('blue') # 到達済みは青色に
radius_circles[i].set_color('gray') # 円も灰色に
# 情報表示の更新
elapsed_time = frame * dt
current_waypoint = "到着済" if controller.waypoint_index >= len(airports) else airports[controller.waypoint_index][1]
# 方向を0-360度で表示(負の角度を正の角度に変換)
direction = np.degrees(vehicle.theta) % 360
info = f'時間: {elapsed_time:.1f}秒\n'
info += f'位置: ({vehicle.x:.1f}, {vehicle.y:.1f})\n'
info += f'速度: {vehicle.v:.1f} m/s\n'
info += f'方向: {direction:.1f}°\n'
info += f'次の目的地: {current_waypoint}'
if controller.final_approach:
if abs(vehicle.v) < 0.01:
info += '\n状態: 停止完了'
else:
info += '\n状態: 停止中...'
info_text.set_text(info)
info_text.set_fontname("MS Gothic")
return trajectory, position, info_text, *waypoint_markers, *radius_circles
# アニメーションの実行
anim = FuncAnimation(fig, animate, init_func=init,
frames=None, interval=50, blit=True)
plt.show()
matplotlibのFuncAnimation
を使用することで、航空機の動きを滑らかなアニメーションとして表示することができます。
シミュレーションの結果
今回のプログラムを実行すると、以下のようなシミュレーション結果が出力されました。
経路追従はほぼ再現できていますね。
惜しいのは、終着地点で減速し切れていないところです。ここは改善の余地がありそうです。
AI活用のポイント:PID制御と経路計画
今回のプログラムでは、航空機の制御にPID制御という古典的な制御手法を使用しています。
PID制御は、
- P制御 (比例制御): 目標値との誤差に比例した制御を行う
- I制御 (積分制御): 定常偏差を解消する
- D制御 (微分制御): 変化の速度を抑制する
という3つの要素を組み合わせることで、安定した制御を実現する手法です。
AI技術を活用することで、
- PID制御のゲインを自動的に調整する
- より複雑な経路計画アルゴリズムを実装する
- 気象データなどの外部環境を考慮した制御を行う
といった、さらに高度なフライト制御が可能になります。
今後の可能性
今回のプログラムは、シンプルな2次元空間でのシミュレーションですが、実際の航空機制御では、
- 3次元空間での飛行
- 乱気流などの外乱への対応
- 他の航空機との衝突回避
など、さまざまな要素を考慮する必要があります。
AI技術を活用することで、これらの複雑な課題を解決し、より安全で効率的な航空交通システムを構築することが可能です。
それにしても、AIだけでここまでのプログラムが開発できるのは、シビれますね。
Discussion