🚄

AIと創る鉄道の未来:東海道新幹線運行シミュレーション開発の全貌とAIとのペアプログラミング道

に公開

AIと創る鉄道の未来:東海道新幹線運行シミュレーション開発の全貌とAIとのペアプログラミング道

「AIを使って何か作ってみたいけど、どこから始めればいいの?」「AIとの開発って、指示通りに動かない時どうすればいいんだろう?」

AIエンジニアを目指す皆さん、AIを活用した開発に興味はあっても、実際に使いこなすにはコツが必要だと感じていませんか?今回は、私がAI搭載開発ツールCursorとペアプログラミングを行い、東海道新幹線運行シミュレーションシステムを開発する中で直面した技術的な課題とAIの「癖」、そしてそれを乗り越えるための具体的な対話術についてご紹介します。

AIは万能ではありませんが、その特性を理解し、適切に「リード」することで、あなたの強力なパートナーとなり得ます。さあ、AIとの共同開発の奥深さと、Pythonで動かす鉄道シミュレーションの世界へ飛び込みましょう!

開発の始まり:AIとの理想的なスタート

私のプロジェクトは、東海道新幹線の運行シミュレーションをPythonで実現することでした。最初のステップは、AIアシスタントに「東海道新幹線の運行管理システムをPythonで再現したい。設計戦略を立ててほしい」と依頼することから始まりました。

AIは期待通り、システム全体の目的、アーキテクチャ、クラス設計、実装ステップ、サンプルシナリオ、技術選定まで、非常に網羅的な設計戦略を提示してくれました。特に、StationSectionTrainTimetableOperationStatusといったクラスの骨格を最初に示してくれたことで、プロジェクトの全体像をスムーズに把握することができました。

class Station:
    def __init__(self, name: str):
        self.name = name
        self.trains = []  # 停車中または通過予定の列車リスト

class Section:
    def __init__(self, start_station: 'Station', end_station: 'Station', 
                 distance_km: float, required_time_min: int):
        self.start_station = start_station
        self.end_station = end_station
        self.distance_km = distance_km
        self.required_time_min = required_time_min
        self.trains = []  # 区間内の列車リスト

この初期段階では、AIは非常に優秀な「企画者」であり「設計者」としての能力を発揮してくれました。

1. シミュレーションの舞台設定:駅と区間のモデル化

新幹線の運行シミュレーションを行う上で、まず必要なのは「駅」と「区間」という運行路線の基本要素をプログラム上で表現することです。このシミュレーターでは、それぞれStationクラスとSectionクラスとして定義されています。

1.1. Stationクラス:列車が行き交う拠点

Stationクラスは、駅の名前を持つシンプルなクラスです。将来的には、駅に停車中の列車リストを持つこともできます。



class Station:
    def __init__(self, name: str):
        self.name = name
        self.trains = []  # 停車中または通過予定の列車リスト

このコードは、駅を名前で識別し、その駅に現在いる、または通過する予定の列車を管理するためのリストを持つことを示しています。

1.2. Sectionクラス:列車が走行する路線

Sectionクラスは、2つの駅を結ぶ区間を表します。距離や所要時間の情報に加え、現在その区間を走行中の列車や、追い越し待ちの列車を管理する重要な役割を担います。



class Section:
    def __init__(self, start_station: 'Station', end_station: 'Station', distance_km: float, required_time_min: int):
        self.start_station = start_station
        self.end_station = end_station
        self.distance_km = distance_km
        self.required_time_min = required_time_min
        self.trains = []  # 区間内の列車リスト
        self.waiting_trains = []  # 追い越し待ちの列車リスト

ここで注目すべきは、trainsとwaiting_trainsというリストです。これは、単に列車が区間にいるかどうかだけでなく、列車同士の追い越しや待避といった、より複雑な運行ロジックを実装するための基盤となります。特に、add_trainメソッドでは、列車の種別(のぞみ、ひかり、こだま)に応じた優先度でソートが行われ、高速列車が優先的に区間に入ることができるようになっています.

2. 時刻表と列車のロジック: TimetableとTrainクラス

鉄道運行シミュレーションの核となるのは、列車そのものの挙動と、それを規定する時刻表です。

2.1. Timetableクラス:運行の約束事

Timetableクラスは、特定の列車の各駅における到着時刻と出発時刻を管理します。これは、現実世界のダイヤグラムをシンプルにデータ化したものです.



class Timetable:
    def __init__(self, schedule: dict):
        # schedule: {station_name: {'arrival': 時刻, 'departure': 時刻}}
        self.schedule = schedule

この辞書形式のデータ構造により、各列車の「いつ、どの駅にいるべきか」が明確に定義されます。

2.2. Trainクラス:自律的に動く列車たち

Trainクラスは、列車番号、種別(のぞみ、ひかり、こだま)、そして自身の時刻表を持つ個々の列車を表します。最も重要なのは、update_statusメソッドです。このメソッドが、シミュレーション時間(1分単位)ごとに呼び出され、列車の現在の状態(待機中、運行中、停車中、終了)を更新していきます.



class Train:
    def __init__(self, train_no: str, train_type: str, timetable: 'Timetable'):
        self.train_no = train_no
        self.train_type = train_type
        self.timetable = timetable
        self.current_station = None
        self.current_section = None
        self.delay_min = 0
        self.status = "待機"
        self.priority = {"のぞみ": 3, "ひかり": 2, "こだま": 1}[train_type] # 優先度設定
        self.waiting_for_overtake = False
        self.history = [] # 列車の移動履歴

    def update_status(self, current_time: int, operation: 'OperationStatus'):
        # ... (中略)
        # 駅間移動の履歴記録
        if self.status == "運行中" and self.current_section:
            # ... (中略)
            self.history.append({
                'from': self.current_section.start_station.name,
                'to': self.current_section.end_station.name,
                'progress': progress,
                'time': current_time
            })

このupdate_statusメソッド内では、列車が次の駅に到着したかどうかの判定や、区間に入れるかどうかのチェック (_can_enter_section)、そして追い越し待ち状態の管理など、鉄道運行シミュレーション特有のロジックが組み込まれています。列車の移動履歴を正確に記録し、表示するためにhistory属性とlast_update_time属性が追加されました.

3. AIとのペアプログラミング道:指示解釈のずれと根本原因の特定

開発を進める中で、AIの指示解釈に「ずれ」が生じることがありました。AIはユーザーの抽象的な指示を「一般的な解釈」で補完しようとするAIの「癖」の現れです。

3.1. リアルタイム可視化の壁:期待と現実のギャップ

私が「リアルタイムに可視化すること」を指示した際、AIはmatplotlibを使ってダイヤグラムを描画するコードを生成してくれました。しかし、最初の実装では、列車が単なる矩形(四角いブロック)で表示されたり、時刻軸の表示範囲が12時までに限定されていたりしました。
これは、AIが「リアルタイム可視化」という抽象的な指示を、「動的なグラフ表示」として一般的な解釈をした結果です。私の意図する「鉄道ダイヤグラムとしての視覚的な正確さ」までは汲み取れていませんでした。

【克服策】具体的な指示と段階的なフィードバック

このギャップを埋めるため、私は以下のように段階的に指示を深掘りしました。
「1次元の列車運行管理ダイヤグラムを東京駅から新大阪駅まで表示し、間に各駅を表示し、列車をそのダイヤに合わせて1分毎に駅間を移動していくような可視化」
「横軸の時間軸だけではなく、縦軸の駅間も移動するように変更して。また、その現在位置までの移動履歴も、線分で表示するようにして」
「駅間移動中は斜め線分、停車中は横線分で、現在位置を点で表示し、一度表示した線分は消えないように履歴として表示し続けてほしい」
このように、具体的な表示形式(斜め線分、横線分、点の表示)と、表示の持続性(履歴として残す)を明確に指示することで、AIの理解を深め、最終的な理想のダイヤグラムへと近づけることができました。

3.2. 履歴の記録と描画の根本問題:AIの「現状維持バイアス」を乗り越える

特に難航したのが「駅間移動中の斜め線分が一切表示されない」という問題でした。AIは当初、履歴の記録が不十分だったり、描画ロジックが履歴の内容を正しく解釈できていなかったりするケースがありました。

【克服策】履歴の中身を「可視化」し、根本原因を特定させる

この問題に対し、私はAIに「履歴の中身はどうなっているか?」をprint出力で確認させ、履歴が from == to のデータしか記録されていないという根本原因を突き止めました。
AIが一度実装したロジックを保持しようとする「現状維持バイアス」によって、問題が修正されきらない場合があります。そのため、私はAIに「表示ロジックそのものに問題があるのではないでしょうか。一つずつ丁寧に検討してください」と、AI自身に自己分析と丁寧な検証を促すことで、根本的な原因にたどり着かせました。
そして、「駅間移動開始時に必ず『始点(進行率0)』と『現在位置(進行率>0)』の履歴を記録すること」と、「update_visualizationでfrom != toの履歴ペアがあれば必ず斜め線を描画すること」という具体的な修正方針を明確に指示しました。

4. 運行全体の管理と可視化:OperationStatusクラス

OperationStatusクラスは、シミュレーション全体の進行を管理する中核となるクラスです。全ての列車、駅、区間の情報が集約され、シミュレーション時間を進めるたびに、各列車の状態を更新し、最終的にその結果を視覚化します。

4.1. シミュレーションの進行

updateメソッドが1分ごとに呼び出されることで、シミュレーション内の時間が進み、それに伴い全ての列車の状態が更新されます。



class OperationStatus:
    # ... (中略)
    def update(self):
        """シミュレーションを1分進める"""
        self.time += 1
        # 運行中の列車のみ更新
        self.active_trains = [train for train in self.trains if train.status != "終了"]
        for train in self.active_trains:
            train.update_status(self.time, self) # 各列車の状態を更新

4.2. 東海道新幹線ダイヤグラムの視覚化

このシミュレーターの最大の見どころは、matplotlibを使ったダイヤグラムのリアルタイムアニメーションです。initialize_visualizationメソッドでグラフの基本設定を行い、update_visualizationメソッドがアニメーションの各フレームを更新します.



class OperationStatus:
    # ... (中略)
    def initialize_visualization(self):
        """可視化の初期化"""
        set_japanese_font() # 日本語フォントの設定
        self.fig, self.ax = plt.subplots(figsize=(15, 8))
        self.ax.set_title('東海道新幹線 運行ダイヤグラム', fontsize=14, pad=20)
        self.ax.set_xlabel('時刻', fontsize=12)
        self.ax.set_ylabel('駅', fontsize=12)
        # ... (軸の設定など)
        self.train_styles = { # 列車種別ごとのスタイル定義
            "のぞみ": {'color': '#FF0000', 'linewidth': 2.0},
            "ひかり": {'color': '#0000FF', 'linewidth': 1.5},
            "こだま": {'color': '#008000', 'linewidth': 1.0}
        }

    def update_visualization(self, frame):
        set_japanese_font()  # フォント設定を確実に行う
        self.ax.clear()
        self.time = 6 * 60 + frame  # 6:00から開始
        # ... (中略)
        for train in self.trains:
            style = self.train_styles.get(train.train_type, {'color': 'gray', 'linewidth': 1.0})
            history = train.history
            n = len(history)
            for i in range(n - 1):
                h0 = history[i]
                h1 = history[i+1]
                from_idx = station_names.index(h0['from'])
                to_idx = station_names.index(h0['to'])
                t0 = h0['time']
                t1 = h1['time']
                if h0['from'] == h0['to']:
                    # 停車中:到着時刻から現在時刻までの水平線
                    self.ax.plot([t0, t1], [from_idx, from_idx], color=style['color'], linewidth=style['linewidth']*1.5)
                else:
                    # 駅間移動:出発時刻・出発駅から現在時刻・現在位置まで
                    # 現在位置の進行率
                    progress = h1['progress']
                    y_pos = from_idx + (to_idx - from_idx) * progress
                    self.ax.plot([t0, t1], [from_idx, y_pos], color=style['color'], linewidth=style['linewidth'])

この実装では、駅間移動中は出発駅・出発時刻から現在位置・現在時刻までの斜め線分を、停車中は到着時刻・停車駅から現在時刻・停車駅までの水平線分を描画し、これらが一度表示されたら消えない履歴として残るように工夫されています。

5. シミュレーション結果:動的なダイヤグラムで運行を体験!

このシミュレーションを実行すると、6:00から始まる東海道新幹線の運行が、美しいダイヤグラムとしてアニメーションで目の前に現れます。
あなたは、秒単位で変化する列車の位置、駅での停車、そして区間内での追い越しや待避(優先度の低い列車が区間に入るのを待つ様子)といった、現実の鉄道運行で起こりうる様々な状況を視覚的に追体験することができます。特に、高速列車である「のぞみ」が「ひかり」や「こだま」を追い抜いていく様子は、ダイヤグラム上で交差する線として鮮やかに表現され、運行管理の妙を感じられるでしょう。
最終的に、このシステムは13本の列車(のぞみ6本、ひかり4本、こだま3本)の運行をシミュレートし、東京から新大阪までの全駅間の運行を可視化することに成功しました。列車種別ごとの色分け表示も、視認性の向上に大きく貢献しています。

6. AIを活用した開発のメリットと今後の展望

今回の開発を通じて、AIアシスタントとの共同作業が非常に効果的であることを実感しました。CursorのようなAI搭載開発ツールを活用することで、以下のようなメリットがあります。

  • 迅速なコード生成: 基本的なクラス構造や関数の実装をAIが提案してくれます。
  • デバッグの効率化: エラーメッセージの解析や問題の特定をAIがサポートします。
  • コードの最適化: より効率的な実装方法をAIが提案してくれます。

このプロジェクトを発展させることで、以下のような機能追加が可能です。

  • 遅延シミュレーション機能の追加
  • 列車の追い越し機能の実装(今回は優先度による待避のみ)
  • より詳細な運行データの表示
  • ユーザーインターフェースの改善

将来的には、実際の運行データと連携させたり、AIによる運行最適化を試みたりすることで、さらに実用的なシステムへと進化させることができるでしょう。

まとめ:AIを「リード」する開発の醍醐味

AIアシスタントCursorとの新幹線運行シミュレーション開発は、まさにAIとの対話そのものでした。初期の設計から始まり、リアルタイム可視化の壁、日本語表示の課題、そして複雑な運行ロジックの表現まで、多くの試行錯誤がありました。
しかし、AIの「癖」を理解し、具体的な指示と粘り強いフィードバックを繰り返すことで、最終的には理想とする東海道新幹線運行シミュレーションシステムを完成させることができました。AIは、私たちエンジニアの強力な味方ですが、その力を最大限に引き出すには、AIを「リード」するスキルが求められます。
AIの「指示解釈のずれ」には具体性と反復で対応し、「現状維持バイアス」には徹底した確認と根本原因の特定で乗り越える。そして、時にはAIに自己分析を促すことで、その真価を引き出す。
ぜひ皆さんも、AIとのペアプログラミングに挑戦し、この新しい開発の形を体験してみてください。AIの可能性は、あなたの対話スキルにかかっています!

Discussion