軌道上組み立て — ロボットによる大型宇宙構造物の自動構築

100メートルを超える大型宇宙望遠鏡で系外惑星の大気を直接撮像する。1キロメートル四方の太陽電池パネルで太陽光を集め、マイクロ波で地上に送電する。こうした壮大な構想は、ある根本的な物理的制約に突き当たります。ロケットのフェアリング(先端の格納部)に収まらないのです。

現在世界最大のフェアリングを持つロケットは SpaceX の Starship で、直径約 9 m、長さ約 22 m です。これは十分に大きく聞こえますが、口径 100 m の宇宙望遠鏡や 1 km 四方の太陽電池アレイを一度に打ち上げることは物理的に不可能です。地上で完成品を作ってロケットに載せるという従来のパラダイムでは、構造物のサイズにロケットのフェアリングという絶対的な上限が課されます。

この制約を根本的に突破する手段が軌道上組み立て(On-Orbit Assembly: OOA)です。複数回の打ち上げで部品を軌道に送り込み、宇宙空間でロボットが自動的に組み上げる――この技術は、次世代の宇宙インフラを実現する鍵として急速に注目されています。

軌道上組み立ての技術を理解すると、以下のような応用が見えてきます。

  • 次世代大型宇宙望遠鏡: JWST の 6.5 m を超える口径 20〜100 m 級の望遠鏡を軌道上で構築し、系外惑星の大気成分を直接分光分析する
  • 宇宙太陽光発電所(SBSP): 1 km 規模の太陽電池アレイを軌道上で組み立て、24 時間安定した電力を地上に送る
  • 深宇宙探査の大型船: 火星や外惑星への有人ミッションに必要な大型宇宙船を、LEO で組み立ててから出発させる
  • 宇宙デブリ対策インフラ: 大型のデブリ回収プラットフォームを軌道上で建造する

本記事の内容

  • 打ち上げサイズ制限 — なぜ軌道上で組み立てる必要があるのか
  • ISS の組立歴史と得られた教訓
  • NASA ISAM / Archinaut — 次世代の自動組立プログラム
  • モジュラー組立戦略 — 分割・結合の設計思想
  • 組立順序最適化アルゴリズム — DAG とトポロジカルソート
  • 精度要求と公差管理 — 微小重力下の位置合わせ
  • 大型望遠鏡・宇宙太陽光発電所への応用
  • Python で組立順序最適化を実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

打ち上げサイズの壁 — フェアリング制約の物理学

ロケットフェアリングという「箱」

宇宙に物を運ぶには、ロケットのフェアリング内に収めなければなりません。フェアリングは大気中を飛行する際にペイロードを空力加熱や音響振動から守る「殻」であり、その寸法はロケットの直径に制約されます。代表的なロケットのフェアリング寸法を見てみましょう。

ロケット フェアリング直径 フェアリング長さ LEO 投入質量
Falcon 9 5.2 m 13.1 m 22,800 kg
Ariane 6 5.4 m 20.0 m 21,650 kg
H3 5.2 m 16.0 m 16,500 kg
SLS Block 1 5.0 m / 8.4 m 19.1 m / 27.4 m 95,000 kg
Starship ~9 m ~22 m ~150,000 kg

Starship のような超大型ロケットでも、フェアリング直径は 9 m 程度です。口径 25 m の宇宙望遠鏡を一度に打ち上げることはできません。

折り畳みと展開 — 従来のアプローチの限界

フェアリングの制約を回避する伝統的な方法は、構造物を折り畳んで打ち上げ、軌道上で展開する(deploy)ことです。JWST(ジェイムズ・ウェッブ宇宙望遠鏡)はこの戦略の最も精巧な例です。6.5 m の主鏡を 18 枚の六角形セグメントに分割し、折り畳んで Ariane 5 のフェアリング(直径 4.57 m)に収めました。

しかし、展開方式にはいくつかの本質的な限界があります。

機械的複雑性: 折り畳み/展開機構は、単一障害点(Single Point of Failure)の塊です。JWST には 344 の単一障害点がありました。どれか1つでも動作しなければミッション全体が失敗します。

サイズのスケーリング限界: 展開比(収納時と展開後のサイズ比)には構造力学的な限界があります。構造物のサイズが大きくなると、展開機構に必要な剛性と強度が急増し、質量が急激に増加します。JWST の展開比は約 3:1(収納 4.5 m → 展開 6.5 m の主鏡直径)でしたが、口径 25 m 以上の望遠鏡ではこの比を 5:1 以上にする必要があり、機構の信頼性確保が極めて困難になります。

精度の制約: 展開後の構造精度は、ヒンジやラッチの機械精度に依存します。光学系の場合、波面誤差を $\lambda / 20$ 以下に抑える必要があり($\lambda$ は観測波長)、可視光($\lambda \approx 500 \, \text{nm}$)で 25 nm 以下の精度が要求されます。この精度を展開機構だけで保証することは、サイズが大きくなるほど困難です。

これらの限界が、折り畳み・展開ではなく「軌道上組み立て」という異なるパラダイムへの移行を促しています。では、軌道上組み立ては本当に実現可能なのでしょうか? 実は、人類はすでに 40 回以上の打ち上げを重ねて、サッカー場ほどの構造物を軌道上で組み立てた経験があります。

ISS の組立歴史と教訓 — 人類最大の宇宙建設プロジェクト

ISS 組立の全体像

国際宇宙ステーション(ISS)は、軌道上組み立ての最大にして最も成功した実例です。1998 年の最初のモジュール「ザーリャ」(Zarya)から 2011 年の最後の与圧モジュール取り付けまで、約 13 年の歳月をかけて組み上げられました。

ISS の主要な数値は以下のとおりです。

項目
総質量 約 420,000 kg
トラス構造の長さ 約 109 m
太陽電池パネルの幅 約 73 m
与圧容積 約 916 m$^3$
打ち上げ回数 40 回以上(スペースシャトル + ロシアロケット)
EVA(船外活動)回数 組立のために 160 回以上
組立期間 1998〜2011 年(約 13 年)

ISS は人類史上最大の国際共同建設プロジェクトであり、軌道上組み立ての技術的な可能性を実証しました。しかし同時に、多くの課題も明らかにしました。

ISS 組立から得られた 5 つの教訓

教訓 1: EVA への依存は持続不可能である

ISS の組立では、宇宙飛行士が宇宙服を着て船外に出る船外活動(EVA)が不可欠でした。160 回以上の EVA は、それぞれ約 6 時間を要し、宇宙飛行士の体力と精神力を大きく消耗させます。EVA の準備には減圧に約 4 時間が必要で、宇宙服のメンテナンスにも膨大な時間がかかります。

さらに、EVA はリスクが高い活動です。宇宙服の不具合(ヘルメット内への水漏れなど)や、工具の紛失(ISS 周辺には宇宙飛行士が落とした工具が数個漂っています)、微小デブリとの衝突リスクなど、多くの危険が伴います。

将来の大型構造物の組み立てでは、EVA 回数を大幅に削減するか、完全に排除する自動化が必要です。

教訓 2: モジュール間インターフェースの標準化が重要

ISS は米国、ロシア、ESA、JAXA、CSA の 5 つの宇宙機関が開発したモジュールで構成されています。各モジュールの結合には、共通結合機構(Common Berthing Mechanism: CBM)やロシアのプローブ・コーン式ドッキング機構が使われましたが、異なるインターフェースの共存は設計と運用を複雑にしました。

将来の軌道上組立では、すべてのモジュールが共通の機械的・電気的インターフェースを持つ標準化が不可欠です。

教訓 3: 組立順序がミッション全体の成否を左右する

ISS の組立では、各モジュールの打ち上げ・取り付け順序が極めて重要でした。最初に打ち上げたザーリャ(FGB)は電力と推進を提供し、次のユニティ(Node 1)は結合ノードとして機能しました。電力系統が稼働する前に実験モジュールを取り付けても意味がありません。

この「依存関係に基づく順序制約」は、数学的にはトポロジカルソートの問題であり、モジュール数が増えると計画の複雑性が急増します。

教訓 4: 熱膨張への対処が不可欠

ISS のトラス構造は、太陽光が当たる面と影の面で 100°C 以上の温度差が生じます。この温度勾配による熱膨張は、構造物の変形を引き起こし、太陽電池パネルの指向精度やロボットアーム操作に影響します。

具体的には、アルミニウム合金(線膨張係数 $\alpha \approx 23 \times 10^{-6} \, \text{K}^{-1}$)でできた長さ $L = 50 \, \text{m}$ のトラスが温度差 $\Delta T = 150 \, \text{K}$ を受けた場合、熱変形量は

$$ \delta = \alpha \cdot L \cdot \Delta T = 23 \times 10^{-6} \times 50 \times 150 \approx 0.17 \, \text{m} $$

となります。17 cm もの変形は、精密な組立作業において無視できません。軌道上組立では、熱環境を考慮した作業タイミングの計画と、熱変形に対してロバストな結合機構の設計が求められます。

教訓 5: ロボットアームは組立の必須要素

ISS の組立において、Canadarm2(SSRMS)は主役級の活躍をしました。大型モジュールの配置転換、太陽電池パネルの展開支援、EVA 時のプラットフォーム提供など、ほぼすべての組立作業に関与しました。Canadarm2 はエンドエフェクタ同士を付け替えることでトラス上を「歩く」ことができ、ISS のどの部分にもアクセスできる柔軟性を持っています。

この経験は、将来の軌道上組立ではロボットアームが中心的な役割を果たすことを明確に示しています。ただし、ISS の Canadarm2 は地上オペレータがリアルタイムで操作しており、自律性は限定的でした。次世代の組立ではより高度な自律性が求められます。

ISS の組立が多くの教訓を残した一方で、次の疑問が浮かびます。では、人間の介在をさらに減らし、ロボットが自律的に組み立てる技術はどこまで進んでいるのでしょうか? 次のセクションでは、NASA を中心に進められている最先端の自動組立プログラムを見ていきましょう。

NASA ISAM と Archinaut — 次世代の自動構築プログラム

ISAM 戦略 — In-Space Servicing, Assembly, and Manufacturing

NASA は 2022 年に ISAM(In-Space Servicing, Assembly, and Manufacturing)国家戦略を発表しました。これは、軌道上でのサービス・組立・製造を国家レベルの優先技術として位置づける包括的な戦略です。

ISAM 戦略の核心は、宇宙インフラの運用パラダイムを「使い捨て」から「持続可能なメンテナンス・拡張」へ転換することです。具体的には、以下の 3 つの柱で構成されています。

Servicing(サービス): 運用中の衛星に対する検査、燃料補給、修理、アップグレード。前回の記事で詳しく解説した 軌道上サービス(OOS) がこの柱に該当します。

Assembly(組立): 複数回の打ち上げで軌道に送り込んだ部品を結合して、フェアリングに収まらない大型構造物を構築する技術。本記事の主題です。

Manufacturing(製造): 宇宙空間でゼロから構造物を製造する技術。3D プリンティング、溶接、リサイクルなどが含まれます。組立が「地上で作った部品を宇宙で結合する」のに対し、製造は「宇宙で原材料から部品を作る」点で異なります。

Archinaut プログラム — 宇宙でロボットが構造物を「作る」

ISAM 戦略の中でも特に注目を集めたプロジェクトが、Made In Space 社(現 Redwire 社)が NASA と共同で進めた Archinaut プログラムです。

Archinaut One: Archinaut プログラムの最初の軌道実証ミッションとして計画されました。小型衛星に搭載されたロボットアームが、軌道上で 3D プリンティングにより太陽電池パネルの支持構造(ビーム)を製造・展開するデモンストレーションです。地上から折り畳んで打ち上げるのではなく、軌道上でビームを「印刷」して伸ばすという点が革新的です。

この方式のメリットは大きく 2 つあります。

  1. 展開機構が不要: 折り畳み/展開のためのヒンジ、スプリング、ラッチといった機構が不要になり、単一障害点が大幅に減少します
  2. サイズの制約が緩和: 原材料(フィラメント)はコンパクトに収納できるため、製造後の構造物は原材料の体積をはるかに超えるサイズにできます

ISS での先行実験: Archinaut に先立つ技術実証として、Made In Space 社は ISS 上で AMF(Additive Manufacturing Facility) を運用し、微小重力環境での 3D プリンティングの実現可能性を実証しました。ポリマー材料を用いた部品製造に成功し、微小重力下でも層間接着が地上と同等以上の品質で行えることを確認しました。

OSAM-1 — ロボット組立のフラッグシッププログラム

NASA が計画していた OSAM-1(On-orbit Servicing, Assembly, and Manufacturing 1) は、自律ロボットアームによる衛星の燃料補給と、3D プリンティングによるビーム製造を同時に実証する野心的なミッションでした。OSAM-1 に搭載される予定だった技術要素は、軌道上組立の将来像を具体的に示しています。

  • SPIDER(Space Infrastructure Dexterous Robot): 多関節ロボットアームで、3D プリントされたビーム上にソーラーブランケットを配置・接合する
  • Trusselator: 炭素繊維複合材のトラスビームを連続的に製造する装置

OSAM-1 は 2024 年にプログラムの見直しが行われ、コスト超過の問題から当初の形での実施は見直されましたが、その技術要素は後続プログラムに引き継がれています。

これらの技術開発プログラムの共通点は、「軌道上組立を自動化する」という方向性です。では、自動化を実現するための設計思想として、どのようにして大型構造物をロボットが扱いやすい「部品」に分解すればよいのでしょうか? 次に、モジュラー組立の設計戦略を見ていきましょう。

モジュラー組立戦略 — 分割と結合の設計思想

なぜモジュラーにするのか

軌道上組立の核心的な設計判断は、「完成した構造物をどのように部品(モジュール)に分割するか」です。この分割の仕方が、打ち上げコスト、組立の複雑性、故障時の修理容易性、そして将来の拡張性を大きく左右します。

モジュラー設計のメリットを日常的な例で理解しましょう。LEGO ブロックを思い浮かべてください。LEGO が世界中で愛されている理由の 1 つは、限られた種類のブロック(モジュール)を組み合わせることで、ほぼ無限の構造物を作れることです。しかも、1 つのブロックが壊れたら、構造全体を捨てることなくそのブロックだけを交換できます。

軌道上組立のモジュラー設計も同じ発想です。大型構造物を少数の標準モジュールタイプの繰り返しで構成することで、以下の利点が得られます。

  1. 打ち上げ効率: 各モジュールをフェアリングに収まるサイズに設計できる
  2. 量産効果: 同一設計のモジュールを多数生産するため、学習曲線効果で単価が下がる
  3. 修理容易性: 故障モジュールだけを交換できる(Orbital Replacement Unit: ORU 思想)
  4. 段階的拡張: 初期段階で一部のモジュールだけ組み立てて運用開始し、後から機能を追加できる

モジュラー設計の 3 つの階層

大型宇宙構造物のモジュラー設計は、一般に以下の 3 階層で考えます。

レベル 1 — セグメント(Segment): 構造全体を大きく分割した単位。ISS でいえば「米国軌道セグメント」と「ロシア軌道セグメント」に相当します。それぞれが独立したサブシステム(電力、熱制御、通信)を持ち、単独でも最低限の機能を発揮できます。

レベル 2 — モジュール(Module): 1 回の打ち上げで軌道に送り込む単位。ISS でいえば「デスティニー実験棟」や「P3/P4 トラスセグメント」が該当します。フェアリングに収まるサイズ・質量であることが設計制約です。

レベル 3 — ユニット(Unit): モジュール内で交換可能な最小単位。ISS の ORU(Orbital Replacement Unit)がこれに該当し、バッテリーユニット、ジャイロスコープ、ポンプモジュールなどがあります。EVA やロボットアームで交換できるように、標準的なボルト・コネクタ設計が適用されています。

結合インターフェースの設計

モジュール同士を軌道上で結合するためのインターフェースは、軌道上組立の成否を左右する最も重要な技術要素の 1 つです。結合インターフェースに求められる機能を整理します。

機械的結合: 2 つのモジュールを物理的に固定し、構造荷重を伝達する。結合強度、剛性、疲労寿命が設計パラメータです。

電気的接続: 電力伝送とデータ通信のための電気コネクタ。ISS の CBM では、結合と同時に電力・データ線が自動接続される設計になっています。

熱的接続: 冷却配管の接続。流体継手のシールが完全である必要があり、微小重力下での液漏れ防止が技術課題です。

位置合わせ(アライメント): 結合時に 2 つのモジュールの相対位置・姿勢を許容公差内に合わせるガイド機構。一般的に、粗い位置合わせ(ガイドコーン/ペタル、$\pm$50 mm 程度)→ 微細な位置合わせ(テーパーピン/ボールロック、$\pm$1 mm 程度)の 2 段階で行います。

結合インターフェースの性能を定量的に見てみましょう。ISS の CBM と、将来の軌道上組立向けに提案されている次世代インターフェースを比較します。

パラメータ ISS CBM 次世代標準IF(概念)
結合方式 ボルト締結(16 本) 電動ラッチ(4〜8 個)
結合力 約 2,700 kN(引張) 約 500 kN
位置合わせ精度 $\pm$3 mm $\pm$0.5 mm
結合時間 約 4 時間(EVA 含む) 約 10 分(自動)
結合/分離サイクル 数回 100 回以上
質量 約 250 kg(1 対) 約 50 kg(1 対)

次世代インターフェースでは、結合時間の大幅な短縮、繰り返し結合/分離への耐久性、軽量化が重視されています。特に、自動組立を実現するにはロボットアームだけで結合/分離が完結する設計が不可欠です。EVA を前提とした手動ボルト締結は排除されなければなりません。

モジュラー設計とインターフェースの概念を理解したところで、次に浮かぶ疑問は「どの順番で組み立てるのが最適か?」です。モジュール数が増えると、可能な組立順序の数が爆発的に増え、手作業で最適な順序を見つけることは不可能になります。

組立順序最適化 — 依存関係グラフとトポロジカルソート

組立順序問題の本質

軌道上組立を計画する際、「どのモジュールをどの順番で取り付けるか」は単なるロジスティクスの問題ではなく、ミッションの成否とコストを決定する本質的な問題です。

ISS の組立で見たように、モジュール間には依存関係があります。例えば、

  • 電力モジュールが取り付けられるまで、電力を必要とする実験モジュールは稼働できない
  • トラス構造が組み上がるまで、そこに取り付ける太陽電池パネルは設置できない
  • ドッキングポートが確保されるまで、次の補給船は到着できない

こうした依存関係を無視した組立順序は、非効率か、あるいは物理的に実行不可能です。

有向非巡回グラフ(DAG)による依存関係のモデル化

依存関係を数学的に表現するために、有向非巡回グラフ(Directed Acyclic Graph: DAG)を使います。

グラフ理論の基本をまず押さえましょう。グラフ $G = (V, E)$ は、ノード(頂点)の集合 $V$ と、エッジ(辺)の集合 $E$ で構成されます。軌道上組立の文脈では、

  • ノード $v_i \in V$: 各モジュール($n$ 個)
  • 有向エッジ $(v_i, v_j) \in E$: 「モジュール $v_i$ がモジュール $v_j$ の前に取り付けられなければならない」という依存関係

「有向」は一方向の関係を表し、「非巡回」は循環依存がないことを保証します。A が B の前に必要で、B が A の前に必要、という矛盾した関係は存在しません(もし存在したら、組立は不可能です)。

トポロジカルソート

DAG が与えられたとき、依存関係を全て満たすようにノードを並べる操作がトポロジカルソートです。数学的に表現すると、全ての有向エッジ $(v_i, v_j) \in E$ について $v_i$ が $v_j$ よりも前に来る線形順序 $\sigma: V \to \{1, 2, \dots, n\}$ を求めることです。

重要な点として、トポロジカルソートは一般に一意ではないことがあります。依存関係だけでは順序が完全に決まらない(並列に実行可能なモジュールが存在する)場合、複数の有効な順序が存在します。

代表的なアルゴリズムは Kahn のアルゴリズムです。

  1. 全ノードの入次数(そのノードに入ってくるエッジの数)を計算する
  2. 入次数が 0 のノード(前提条件がないモジュール)をキューに入れる
  3. キューからノードを 1 つ取り出し、出力順序に追加する
  4. 取り出したノードから出ているエッジを全て削除し、影響を受けたノードの入次数を更新する
  5. 入次数が 0 になったノードをキューに追加する
  6. キューが空になるまで 3〜5 を繰り返す

全ノードが出力されれば成功。出力されないノードが残っていれば、循環依存が存在することを意味します。

このアルゴリズムの計算量は $O(|V| + |E|)$ であり、ノード数(モジュール数)とエッジ数(依存関係の数)の合計に比例します。モジュール数が数百になっても高速に解けます。

最適順序の選択 — コスト関数の導入

トポロジカルソートは「実行可能な」順序を与えますが、「最適な」順序は別の基準で選ぶ必要があります。軌道上組立では、典型的に以下のコスト関数が考えられます。

組立時間の最小化: 各モジュールの取り付け時間 $t_i$ が異なる場合、クリティカルパス(最長の依存チェーン)を特定し、ボトルネックを解消する並列組立を計画します。クリティカルパス長は

$$ T_{\text{critical}} = \max_{\text{path } P} \sum_{v_i \in P} t_i $$

で与えられ、DAG の最長パスを求めることに帰着します。

デルタV の最小化: モジュールを軌道上の組立地点に運ぶ際の合計デルタV を最小化します。モジュールの打ち上げ軌道が異なる場合、投入順序によって軌道遷移の合計コストが変わります。

リスクの最小化: 組立途中の中間状態で構造物が安定していること(例えば、重心が制御可能な範囲にあること、姿勢制御に必要な最低限の推進・電力が確保されていること)を各ステップで保証する順序を選びます。

これらの複数の目的を同時に最適化する多目的最適化問題は一般に NP 困難ですが、モジュール数が数十程度であれば、ヒューリスティクスで十分に良い解が得られます。

組立順序が決まったとしても、実際の組立作業では各モジュールを精密に位置合わせして結合する必要があります。次に、この精度要求と公差管理の問題を見ていきましょう。

精度要求と公差管理 — 微小重力下の位置合わせ

なぜ精度が重要か

地上での建設では、重力が構造物を安定させる味方になります。建物の基礎はその上の構造物の重量で地面に押し付けられ、動くことがありません。ところが微小重力環境では、部品に触れた瞬間に力が作用して漂い始めます。「置く」という概念自体が存在しない環境で、サブミリメートルの精度で部品を合わせなければならないのです。

軌道上組立における精度要求は、構造物の用途によって大きく異なります。

用途 位置精度 角度精度 精度を支配する要因
太陽電池アレイ $\pm$5 mm $\pm$0.5° 電力効率(太陽指向)
通信アンテナ $\pm$1 mm $\pm$0.1° ビーム指向精度
光学望遠鏡(鏡面) $\pm$10 $\mu$m $\pm$0.001° 波面誤差($\lambda/20$)
トラス構造 $\pm$3 mm $\pm$0.3° 構造モード・振動
熱配管結合 $\pm$0.5 mm $\pm$0.5° シール面の密着

光学望遠鏡の鏡面要素では、位置精度がマイクロメートル単位、角度精度がミリ度単位という極めて厳しい要求があることがわかります。

公差の累積 — RSS 法

複数のモジュールを順次結合していく場合、各結合部の位置誤差が累積していきます。この誤差伝播を定量的に評価するのが公差解析(tolerance analysis)です。

最も基本的な方法はRSS(Root Sum Square)法です。各結合部の位置誤差 $\epsilon_i$ が独立で正規分布に従うと仮定すると、$n$ 個の結合部を経た後の累積誤差は

$$ \epsilon_{\text{total}} = \sqrt{\sum_{i=1}^{n} \epsilon_i^2} $$

で見積もれます。

なぜ単純に足し算($\epsilon_{\text{total}} = \sum \epsilon_i$)ではなく二乗和の平方根を使うのでしょうか? これは、各結合部の誤差がランダムな方向に生じるため、あるステップのプラス方向の誤差が別のステップのマイナス方向の誤差で相殺される確率が高いからです。統計的に独立な確率変数の和の分散は、各分散の和になるという性質を使っています。

具体例で確認しましょう。太陽電池アレイを 10 枚のパネルモジュールで構成し、各結合部の位置誤差が $\epsilon_i = 1.0 \, \text{mm}$($1\sigma$)である場合、

最悪ケース(ワーストケース)の累積誤差は単純和で

$$ \epsilon_{\text{worst}} = \sum_{i=1}^{10} 1.0 = 10.0 \, \text{mm} $$

ですが、RSS 法では

$$ \epsilon_{\text{RSS}} = \sqrt{10 \times 1.0^2} = \sqrt{10} \approx 3.16 \, \text{mm} $$

となります。RSS 法による見積もりはワーストケースの約 1/3 で、より現実的な値です。ただし、RSS 法は「各誤差が独立」という仮定に基づいていることに注意が必要です。系統的誤差(例: 熱膨張による一方向の偏り)がある場合は、系統成分をバイアスとして加算した上で、ランダム成分に RSS を適用します。

3 次元の公差伝播

実際の軌道上組立では、位置誤差は 3 次元で考える必要があります。モジュール $i$ の結合後の位置ベクトルを $\bm{r}_i$、結合部の変換行列を $\bm{T}_i$ とすると、$n$ 個の結合を経た後の先端位置は

$$ \bm{r}_{\text{end}} = \bm{T}_1 \bm{T}_2 \cdots \bm{T}_n \bm{r}_0 $$

で表されます。ここで、各 $\bm{T}_i$ に微小な誤差 $\delta \bm{T}_i$ が含まれる場合、先端位置の誤差は

$$ \delta \bm{r}_{\text{end}} \approx \sum_{i=1}^{n} \left(\prod_{j=1}^{i-1} \bm{T}_j\right) \delta \bm{T}_i \left(\prod_{k=i+1}^{n} \bm{T}_k\right) \bm{r}_0 $$

ここで各変換行列の積を $\bm{T}_j$ に渡って書き下すことで、誤差が結合チェーンの位置によって異なる重みで伝播することがわかります。特に、チェーンの根元に近い結合部の誤差ほど、先端位置に大きな影響を与えます。

この性質は直感的に理解できます。10 本つないだ棒の根元を 1° 傾けると先端は大きく動きますが、先端付近を 1° 傾けても根元はほとんど影響を受けません。したがって、構造物の根元に近い結合部ほど精度要求が厳しくなります。

アクティブ位置補正

パッシブな機械精度だけでは目標を達成できない場合、アクティブ位置補正(active alignment)が導入されます。具体的には、

  1. 計測: レーザー測距計、カメラベースのフォトグラメトリ、ストレインゲージなどで結合後の実際の位置を計測する
  2. 偏差算出: 設計上の目標位置と実測位置の差を計算する
  3. アクチュエータ補正: 結合インターフェースに組み込まれたアクチュエータ(圧電素子、ステッピングモータ等)で位置を微調整する

JWST はこのアクティブ補正の代表例です。18 枚の鏡セグメントそれぞれに 7 個のアクチュエータが搭載されており、鏡面の位置と曲率を $\pm$10 nm の精度で調整できます。軌道上組立でも同様のアクティブ補正機構が不可欠です。

精度管理の理論を理解したところで、これらの技術が具体的にどのような応用を可能にするのかを見ていきましょう。

応用 1: 次世代大型宇宙望遠鏡

フェアリングを超える望遠鏡

天文学の歴史は、「より大きな望遠鏡を作る競争」の歴史でもあります。地上の望遠鏡は口径 30〜40 m 級(TMT、ELT)が建設中ですが、宇宙望遠鏡はフェアリング制約により JWST の 6.5 m が実用上の限界でした。

しかし、科学目標の中には、6.5 m では到達できないものが多数あります。特に注目されているのが系外惑星の直接撮像と大気分光分析です。

恒星の光の中に埋もれた惑星の光を分離するには、角度分解能とコントラスト比の両方が必要です。角度分解能はレイリーの基準で

$$ \theta_{\min} = 1.22 \frac{\lambda}{D} $$

で与えられます。ここで $\lambda$ は波長、$D$ は口径です。太陽型恒星のハビタブルゾーンにある地球型惑星を 10 パーセク先で分解するには、可視光($\lambda = 500 \, \text{nm}$)で

波長を代入して角度分解能を計算すると

$$ \theta_{\text{planet}} \approx \frac{1 \, \text{AU}}{10 \, \text{pc}} = \frac{1.496 \times 10^{11}}{3.086 \times 10^{17}} \approx 4.85 \times 10^{-7} \, \text{rad} \approx 0.1 \, \text{arcsec} $$

これをレイリー基準に代入して必要な口径を求めると

$$ D = 1.22 \frac{\lambda}{\theta_{\min}} = 1.22 \times \frac{500 \times 10^{-9}}{4.85 \times 10^{-7}} \approx 1.26 \, \text{m} $$

角度分解能だけなら 1.3 m で十分ですが、実際にはコントラスト(惑星と恒星の明るさの比)が問題です。地球型惑星と太陽型恒星のコントラスト比は可視光で約 $10^{-10}$ であり、この微弱な信号を検出するには大きな集光面積が不可欠です。信号対雑音比(SNR)は集光面積に比例するため、

$$ \text{SNR} \propto D^2 \sqrt{t} $$

ここで $t$ は露出時間です。現実的な露出時間(数十時間)で地球型惑星のスペクトルを取得するには、$D \geq 10 \, \text{m}$ 級の口径が必要と見積もられています。

HWO(Habitable Worlds Observatory)構想

こうした科学的動機から、NASA は HWO(Habitable Worlds Observatory) を次の大型宇宙望遠鏡として構想しています。HWO は口径 6〜8 m 級で、将来的には軌道上組立・アップグレードにより口径をさらに拡大する可能性が検討されています。

軌道上組立による大型望遠鏡の建設シナリオとしては、以下のような段階的アプローチが提案されています。

  1. Phase 1: 6 m 級の初期構成を 1 回の打ち上げで配置。折り畳み展開方式で JWST の延長線上
  2. Phase 2: 追加の鏡セグメントとトラスをロボットが取り付け、口径を 12 m に拡張
  3. Phase 3: さらに鏡セグメントを追加し、口径 20 m 級へ拡張。10 年単位のアップグレードサイクル

この段階的構築アプローチの革新的な点は、望遠鏡の初期投資を抑えつつ、将来の技術発展に応じて性能を向上できることです。JWST のように「一度打ち上げたら変更不可」ではなく、軌道上で進化する望遠鏡が実現します。

宇宙望遠鏡は精密光学系であり最高レベルの精度が求められる応用ですが、軌道上組立にはもう 1 つ、規模こそ桁違いに大きいものの精度要求がそれほど厳しくない応用があります。それが宇宙太陽光発電所です。

応用 2: 宇宙太陽光発電所(SBSP)

エネルギー問題への宇宙からのアプローチ

地上の太陽光発電は天候、昼夜サイクル、大気による減衰に制約されます。静止軌道に太陽電池を配置すれば、年間のほとんど(春分・秋分時の短い食を除く)で 24 時間連続して太陽光を受けられます。しかも大気による減衰がないため、地上の約 10 倍のエネルギー密度(約 1,366 W/m$^2$)が利用可能です。

宇宙太陽光発電所(SBSP: Space-Based Solar Power)の基本コンセプトは、以下の 3 ステップです。

  1. 静止軌道の太陽電池アレイで太陽光を電力に変換する
  2. 電力をマイクロ波(またはレーザー)に変換し、地上に向けてビーム送信する
  3. 地上のレクテナ(整流アンテナ)でマイクロ波を再び電力に変換する

必要な構造物のスケール

商業的に意味のある電力(1 GW 級)を供給するために必要な太陽電池アレイのサイズを見積もってみましょう。

静止軌道での太陽放射照度を $I_0 = 1{,}366 \, \text{W/m}^2$、太陽電池の変換効率を $\eta_{\text{cell}} = 0.30$(30%、現在の三接合太陽電池の実績値)、マイクロ波変換・伝送・受信の総合効率を $\eta_{\text{WPT}} = 0.50$(50%、将来目標値)とします。

地上到達電力 $P_{\text{ground}} = 1 \, \text{GW}$ を得るために必要な太陽電池面積 $A$ は

$$ A = \frac{P_{\text{ground}}}{I_0 \cdot \eta_{\text{cell}} \cdot \eta_{\text{WPT}}} $$

に各値を代入すると

$$ A = \frac{1 \times 10^9}{1{,}366 \times 0.30 \times 0.50} = \frac{1 \times 10^9}{204.9} \approx 4.88 \times 10^6 \, \text{m}^2 $$

つまり、約 4.9 km$^2$(一辺約 2.2 km の正方形)の太陽電池アレイが必要です。これは東京ドーム約 104 個分の面積に相当します。

この巨大な構造物を地上で完成させて打ち上げることは不可能です。しかも、太陽電池の面密度を 0.5 kg/m$^2$(超軽量薄膜太陽電池の目標値)としても、太陽電池だけで約 2,440 トンの質量になります。支持構造、送電アンテナ、姿勢制御系を加えると、総質量は数千〜数万トン規模になるでしょう。

これを実現するには、数百回から数千回の打ち上げと、全自動の軌道上組立が必須です。SBSP は軌道上組立技術の究極の応用先と言えます。

SBSP のモジュラー構造

SBSP の構造設計では、巨大な面積を少数のモジュールタイプの繰り返しで構成するモジュラーアプローチが主流です。代表的な構造コンセプトとして、以下が提案されています。

SPS-ALPHA(Solar Power Satellite via Arbitrarily Large Phased Array): NASA の John Mankins が提案した概念で、数万個の小型モジュール「サンドイッチパネル」(表面が太陽電池、裏面がマイクロ波送信素子)を、大型反射鏡のリム上に並べます。各モジュールは同一設計で量産可能であり、ロボットアームが 1 枚ずつ配置していく組立方式です。

CASSIOPeiA: 英国の International Electric Company が提案した概念で、従来の平面型ではなく円筒形の構造を採用しています。太陽に対する指向制御が不要になる利点がありますが、太陽光の入射角が部位によって異なるため効率は下がります。

いずれの概念でも、大量のモジュールを高速かつ正確に配置する自動組立ロボットが技術の鍵を握っています。

ここまで、軌道上組立の理論的な基盤と応用を見てきました。次に、Pythonを使って組立順序最適化の具体的なアルゴリズムを実装し、理論を実感してみましょう。

Python で組立順序最適化を実装する

組立依存関係グラフの構築と可視化

まず、宇宙構造物の組立依存関係を DAG として構築し、トポロジカルソートで実行可能な組立順序を求めます。ここでは、ISS をモチーフにした仮想的な宇宙ステーションの組立問題を例にとります。

import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict, deque

# --- 宇宙ステーション組立の依存関係グラフを定義 ---
# ノード: 各モジュール名
# エッジ: (前提モジュール, 後続モジュール)

modules = [
    "FGB",          # 0: 基本機能ブロック(電力・推進)
    "Node1",        # 1: 結合ノード1
    "Truss_S0",     # 2: 中央トラス
    "Lab",          # 3: 実験棟
    "Truss_P1",     # 4: 左側トラス1
    "Truss_S1",     # 5: 右側トラス1
    "SolarArray_2A",# 6: 太陽電池2A
    "SolarArray_4A",# 7: 太陽電池4A
    "Node2",        # 8: 結合ノード2
    "Airlock",      # 9: エアロック
    "Truss_P3",     # 10: 左側トラス3
    "Truss_S3",     # 11: 右側トラス3
    "SolarArray_2B",# 12: 太陽電池2B
    "SolarArray_4B",# 13: 太陽電池4B
    "Node3",        # 14: 結合ノード3
    "Cupola",       # 15: キューポラ(観測ドーム)
]

# 依存関係: (前提, 後続)
dependencies = [
    (0, 1),   # FGB -> Node1
    (1, 2),   # Node1 -> Truss_S0
    (1, 3),   # Node1 -> Lab
    (2, 4),   # Truss_S0 -> Truss_P1
    (2, 5),   # Truss_S0 -> Truss_S1
    (4, 6),   # Truss_P1 -> SolarArray_2A
    (5, 7),   # Truss_S1 -> SolarArray_4A
    (3, 8),   # Lab -> Node2
    (8, 9),   # Node2 -> Airlock
    (4, 10),  # Truss_P1 -> Truss_P3
    (5, 11),  # Truss_S1 -> Truss_S3
    (10, 12), # Truss_P3 -> SolarArray_2B
    (11, 13), # Truss_S3 -> SolarArray_4B
    (1, 14),  # Node1 -> Node3
    (14, 15), # Node3 -> Cupola
]

n = len(modules)

# --- 隣接リストとして構築 ---
adj = defaultdict(list)      # adj[u] = [v1, v2, ...] 前提uの後にvを組む
in_degree = [0] * n

for u, v in dependencies:
    adj[u].append(v)
    in_degree[v] += 1

print("=== モジュール一覧と入次数 ===")
for i, mod in enumerate(modules):
    print(f"  {mod:20s}  入次数: {in_degree[i]}")

このコードでは、ISS をモチーフにした 16 モジュールの宇宙ステーションの依存関係グラフを構築しています。各モジュールは ISS の実際のモジュール名に準拠しており、FGB(ザーリャ相当)が全ての起点となる構造を表現しています。

出力を確認すると、FGB の入次数が 0(前提条件なし = 最初に打ち上げ可能)であり、Cupola やSolarArray は入次数が 1(それぞれの前提モジュールが必要)であることがわかります。入次数が 0 のモジュールから組立を始められるという、トポロジカルソートの出発点です。

次に、Kahn のアルゴリズムでトポロジカルソートを実行し、組立順序を求めます。

import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict, deque

# (前のコードブロックの modules, dependencies, adj, in_degree を再定義)
modules = [
    "FGB", "Node1", "Truss_S0", "Lab", "Truss_P1", "Truss_S1",
    "SolarArray_2A", "SolarArray_4A", "Node2", "Airlock",
    "Truss_P3", "Truss_S3", "SolarArray_2B", "SolarArray_4B",
    "Node3", "Cupola",
]
dependencies = [
    (0, 1), (1, 2), (1, 3), (2, 4), (2, 5), (4, 6), (5, 7),
    (3, 8), (8, 9), (4, 10), (5, 11), (10, 12), (11, 13), (1, 14), (14, 15),
]
n = len(modules)
adj = defaultdict(list)
in_degree = [0] * n
for u, v in dependencies:
    adj[u].append(v)
    in_degree[v] += 1

# --- Kahnのアルゴリズムによるトポロジカルソート ---
def kahn_topological_sort(n, adj, in_degree):
    """Kahnのアルゴリズムでトポロジカルソートを実行"""
    in_deg = in_degree.copy()
    queue = deque()

    # 入次数0のノードをキューに追加
    for i in range(n):
        if in_deg[i] == 0:
            queue.append(i)

    order = []
    while queue:
        # キューから取り出す(同じ入次数0の中ではインデックス順)
        node = queue.popleft()
        order.append(node)

        # このノードから出ているエッジを削除
        for neighbor in sorted(adj[node]):
            in_deg[neighbor] -= 1
            if in_deg[neighbor] == 0:
                queue.append(neighbor)

    if len(order) != n:
        raise ValueError("循環依存が検出されました!")

    return order

order = kahn_topological_sort(n, adj, in_degree)

print("=== トポロジカルソートによる組立順序 ===")
for step, idx in enumerate(order):
    print(f"  Step {step+1:2d}: {modules[idx]}")

上の実行結果を見ると、FGB が必ず最初に来ること、Node1 が FGB の直後に来ること、太陽電池アレイがそれを支えるトラスの後に来ることが確認できます。依存関係が正しく守られた順序が自動的に生成されています。

特筆すべき点として、Lab と Truss_S0 は相互に依存関係がないため、どちらを先に組み立ててもよいことがわかります。このような「並列に実行可能な」モジュールの存在は、組立工程の効率化に重要です。

クリティカルパスの算出

次に、各モジュールに組立所要時間を割り当て、クリティカルパス(全体の組立期間を決定する最長の依存チェーン)を求めます。

import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict, deque

# モジュール定義(再掲)
modules = [
    "FGB", "Node1", "Truss_S0", "Lab", "Truss_P1", "Truss_S1",
    "SolarArray_2A", "SolarArray_4A", "Node2", "Airlock",
    "Truss_P3", "Truss_S3", "SolarArray_2B", "SolarArray_4B",
    "Node3", "Cupola",
]
dependencies = [
    (0, 1), (1, 2), (1, 3), (2, 4), (2, 5), (4, 6), (5, 7),
    (3, 8), (8, 9), (4, 10), (5, 11), (10, 12), (11, 13), (1, 14), (14, 15),
]
n = len(modules)
adj = defaultdict(list)
in_degree = [0] * n
for u, v in dependencies:
    adj[u].append(v)
    in_degree[v] += 1

# 各モジュールの組立所要時間(日数)
assembly_time = {
    "FGB": 14, "Node1": 10, "Truss_S0": 12, "Lab": 18,
    "Truss_P1": 8, "Truss_S1": 8, "SolarArray_2A": 5,
    "SolarArray_4A": 5, "Node2": 10, "Airlock": 7,
    "Truss_P3": 8, "Truss_S3": 8, "SolarArray_2B": 5,
    "SolarArray_4B": 5, "Node3": 10, "Cupola": 6,
}

# --- クリティカルパス計算(最早開始時刻) ---
def compute_critical_path(n, adj, in_degree, modules, assembly_time):
    """DAGの最長パス(クリティカルパス)を計算する"""
    in_deg = in_degree.copy()
    earliest_start = [0] * n   # 各モジュールの最早開始時刻
    predecessor = [-1] * n     # クリティカルパス上の前のノード

    # トポロジカル順序で処理
    queue = deque()
    for i in range(n):
        if in_deg[i] == 0:
            queue.append(i)

    topo_order = []
    while queue:
        node = queue.popleft()
        topo_order.append(node)
        for neighbor in adj[node]:
            # 前のモジュールの完了時刻 = earliest_start + assembly_time
            finish = earliest_start[node] + assembly_time[modules[node]]
            if finish > earliest_start[neighbor]:
                earliest_start[neighbor] = finish
                predecessor[neighbor] = node
            in_deg[neighbor] -= 1
            if in_deg[neighbor] == 0:
                queue.append(neighbor)

    # 最も遅く完了するモジュールを特定
    finish_times = [earliest_start[i] + assembly_time[modules[i]] for i in range(n)]
    last_node = np.argmax(finish_times)
    total_time = finish_times[last_node]

    # クリティカルパスをバックトラック
    path = []
    node = last_node
    while node != -1:
        path.append(node)
        node = predecessor[node]
    path.reverse()

    return earliest_start, finish_times, path, total_time

earliest, finishes, critical_path, total_time = compute_critical_path(
    n, adj, in_degree, modules, assembly_time
)

print("=== クリティカルパス解析 ===")
print(f"総組立期間: {total_time} 日")
print(f"クリティカルパス: {' -> '.join(modules[i] for i in critical_path)}")
print()
print(f"{'モジュール':20s} {'開始日':>6s} {'完了日':>6s} {'所要日数':>8s} {'CP上':>4s}")
print("-" * 50)
for i in range(n):
    on_cp = "★" if i in critical_path else ""
    print(f"{modules[i]:20s} {earliest[i]:6d} {finishes[i]:6d} "
          f"{assembly_time[modules[i]]:8d} {on_cp:>4s}")

クリティカルパス解析の結果から、組立期間全体を決定するボトルネックの経路が明確になります。クリティカルパス上にないモジュール(例えば Node3 → Cupola)は多少遅れても全体の期間に影響しない余裕(フロート)があります。一方、クリティカルパス上のモジュールが 1 日でも遅れると、プロジェクト全体が 1 日遅れます。

この情報は、リソース配分の意思決定に直結します。クリティカルパス上のモジュールに優先的にロボットを割り当て、並列可能なモジュールの組立は後回しにする、といった判断が可能になります。

ガントチャートによる組立スケジュールの可視化

最後に、計算した組立スケジュールをガントチャートとして可視化します。

import numpy as np
import matplotlib.pyplot as plt
from collections import defaultdict, deque

# モジュール定義(再掲)
modules = [
    "FGB", "Node1", "Truss_S0", "Lab", "Truss_P1", "Truss_S1",
    "SolarArray_2A", "SolarArray_4A", "Node2", "Airlock",
    "Truss_P3", "Truss_S3", "SolarArray_2B", "SolarArray_4B",
    "Node3", "Cupola",
]
dependencies = [
    (0, 1), (1, 2), (1, 3), (2, 4), (2, 5), (4, 6), (5, 7),
    (3, 8), (8, 9), (4, 10), (5, 11), (10, 12), (11, 13), (1, 14), (14, 15),
]
n = len(modules)
adj = defaultdict(list)
in_degree_list = [0] * n
for u, v in dependencies:
    adj[u].append(v)
    in_degree_list[v] += 1

assembly_time = {
    "FGB": 14, "Node1": 10, "Truss_S0": 12, "Lab": 18,
    "Truss_P1": 8, "Truss_S1": 8, "SolarArray_2A": 5,
    "SolarArray_4A": 5, "Node2": 10, "Airlock": 7,
    "Truss_P3": 8, "Truss_S3": 8, "SolarArray_2B": 5,
    "SolarArray_4B": 5, "Node3": 10, "Cupola": 6,
}

# クリティカルパス計算(関数を再度実行)
in_deg = in_degree_list.copy()
earliest_start = [0] * n
predecessor = [-1] * n
queue = deque()
for i in range(n):
    if in_deg[i] == 0:
        queue.append(i)
while queue:
    node = queue.popleft()
    for neighbor in adj[node]:
        finish = earliest_start[node] + assembly_time[modules[node]]
        if finish > earliest_start[neighbor]:
            earliest_start[neighbor] = finish
            predecessor[neighbor] = node
        in_deg[neighbor] -= 1
        if in_deg[neighbor] == 0:
            queue.append(neighbor)
finish_times = [earliest_start[i] + assembly_time[modules[i]] for i in range(n)]
last_node = int(np.argmax(finish_times))
total_time = finish_times[last_node]
cp_path = []
nd = last_node
while nd != -1:
    cp_path.append(nd)
    nd = predecessor[nd]
cp_path.reverse()
cp_set = set(cp_path)

# --- ガントチャートの描画 ---
fig, ax = plt.subplots(figsize=(14, 8))

# モジュールを開始時刻でソート(見やすさのため)
sorted_indices = sorted(range(n), key=lambda i: (earliest_start[i], i))

for y_pos, idx in enumerate(sorted_indices):
    start = earliest_start[idx]
    duration = assembly_time[modules[idx]]
    color = "#FF6B6B" if idx in cp_set else "#4ECDC4"
    edgecolor = "#D32F2F" if idx in cp_set else "#00897B"

    ax.barh(y_pos, duration, left=start, height=0.6,
            color=color, edgecolor=edgecolor, linewidth=1.5, alpha=0.85)
    ax.text(start + duration / 2, y_pos, f"{duration}d",
            ha='center', va='center', fontsize=9, fontweight='bold', color='white')

ax.set_yticks(range(n))
ax.set_yticklabels([modules[i] for i in sorted_indices], fontsize=10)
ax.set_xlabel("Days from start", fontsize=12)
ax.set_title(f"On-Orbit Assembly Schedule (Critical Path = {total_time} days)",
             fontsize=14, fontweight='bold')
ax.invert_yaxis()
ax.grid(axis='x', alpha=0.3)

# 凡例
from matplotlib.patches import Patch
legend_elements = [
    Patch(facecolor='#FF6B6B', edgecolor='#D32F2F', label='Critical Path'),
    Patch(facecolor='#4ECDC4', edgecolor='#00897B', label='Non-Critical'),
]
ax.legend(handles=legend_elements, loc='lower right', fontsize=11)

plt.tight_layout()
plt.savefig("assembly_gantt.png", dpi=150, bbox_inches='tight')
plt.show()

ガントチャートからいくつかの重要な知見が読み取れます。

  1. クリティカルパス(赤色のバー)が全体の期間を決定していることが視覚的に明確です。赤色のバーの合計長さがプロジェクト全体の所要日数に一致します。
  2. 並列作業が可能な区間が存在します。例えば、Truss_P1 と Truss_S1 は同時に組み立てられるため、2 台のロボットを投入すれば壁時計時間を短縮できます。
  3. Lab の所要日数(18 日)が最も長いにもかかわらず、Lab がクリティカルパスに乗るかどうかはその後の依存チェーンの長さに依存します。個々のモジュールの所要時間だけでなく、パス全体を見る必要があることがわかります。

公差累積シミュレーション

最後に、RSS 法による公差累積のモンテカルロシミュレーションを実装します。組立が進むにつれて位置誤差がどのように累積するかを確認します。

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

# --- パラメータ設定 ---
n_joints = 20           # 結合部の数(20モジュールのチェーン)
sigma_per_joint = 1.0   # 各結合部の位置誤差の標準偏差 [mm]
n_simulations = 10000   # モンテカルロ試行回数

# --- モンテカルロシミュレーション ---
# 各結合部の誤差を正規分布から生成(3次元)
errors_x = np.random.normal(0, sigma_per_joint, (n_simulations, n_joints))
errors_y = np.random.normal(0, sigma_per_joint, (n_simulations, n_joints))
errors_z = np.random.normal(0, sigma_per_joint, (n_simulations, n_joints))

# 累積誤差(各軸方向)
cumulative_x = np.cumsum(errors_x, axis=1)
cumulative_y = np.cumsum(errors_y, axis=1)
cumulative_z = np.cumsum(errors_z, axis=1)

# 3次元ユークリッド距離
cumulative_3d = np.sqrt(cumulative_x**2 + cumulative_y**2 + cumulative_z**2)

# 統計量の計算
mean_3d = np.mean(cumulative_3d, axis=0)
std_3d = np.std(cumulative_3d, axis=0)
p95_3d = np.percentile(cumulative_3d, 95, axis=0)
p99_3d = np.percentile(cumulative_3d, 99, axis=0)

# RSS理論値(各軸ごとにRSSした後の3D距離の期待値)
joints = np.arange(1, n_joints + 1)
rss_per_axis = sigma_per_joint * np.sqrt(joints)
# 3D距離の期待値: E[r] = sigma_3d * sqrt(2) * Gamma(2)/Gamma(3/2)
# ≈ sigma_3d * 1.596 (Maxwellian distributionの平均)
rss_3d_expected = rss_per_axis * np.sqrt(3) * np.sqrt(2) * 0.7979  # ≈ sigma*sqrt(3)*sqrt(2/pi)*...
# より正確に: 3つの独立正規(0, sigma^2)の二乗和の平方根 → Maxwell分布
# E[r] = sigma * sqrt(8/pi) * sqrt(n_joints) (ただし sigma は1軸あたり)
# 3軸の場合: sigma_3d = sigma * sqrt(n_joints), E[r] = sigma_3d * sqrt(8/pi)
rss_3d_theory = sigma_per_joint * np.sqrt(joints) * np.sqrt(8.0 / np.pi)

# ワーストケース
worst_case = sigma_per_joint * joints * np.sqrt(3)  # 全軸同方向に最大偏差

# --- 可視化 ---
fig, axes = plt.subplots(1, 2, figsize=(16, 6))

# 左: 累積誤差の統計
ax1 = axes[0]
ax1.fill_between(joints, mean_3d - std_3d, mean_3d + std_3d,
                 alpha=0.3, color='#4ECDC4', label='Mean ± 1σ (MC)')
ax1.plot(joints, mean_3d, '-o', color='#00897B', markersize=4,
         linewidth=2, label='Mean (Monte Carlo)')
ax1.plot(joints, p95_3d, '--', color='#FF6B6B', linewidth=2, label='95th percentile (MC)')
ax1.plot(joints, p99_3d, ':', color='#D32F2F', linewidth=2, label='99th percentile (MC)')
ax1.plot(joints, rss_3d_theory, '-s', color='#FFA726', markersize=4,
         linewidth=2, label='RSS theory (mean)')
ax1.plot(joints, worst_case, '-^', color='#7E57C2', markersize=4,
         linewidth=2, label='Worst case (3σ each axis)')

ax1.set_xlabel("Number of joints", fontsize=12)
ax1.set_ylabel("Cumulative 3D position error [mm]", fontsize=12)
ax1.set_title("Tolerance Stack-up: Monte Carlo vs RSS Theory", fontsize=13, fontweight='bold')
ax1.legend(fontsize=9, loc='upper left')
ax1.grid(alpha=0.3)
ax1.set_xlim(1, n_joints)

# 右: 最終段の誤差分布ヒストグラム
ax2 = axes[1]
final_errors = cumulative_3d[:, -1]
ax2.hist(final_errors, bins=60, density=True, color='#4ECDC4',
         edgecolor='#00897B', alpha=0.7, label='Monte Carlo')
ax2.axvline(np.mean(final_errors), color='#D32F2F', linewidth=2,
            linestyle='--', label=f'Mean = {np.mean(final_errors):.2f} mm')
ax2.axvline(np.percentile(final_errors, 95), color='#FF6B6B', linewidth=2,
            linestyle=':', label=f'95th pct = {np.percentile(final_errors, 95):.2f} mm')

ax2.set_xlabel("3D position error at final joint [mm]", fontsize=12)
ax2.set_ylabel("Probability density", fontsize=12)
ax2.set_title(f"Error Distribution at Joint #{n_joints}", fontsize=13, fontweight='bold')
ax2.legend(fontsize=10)
ax2.grid(alpha=0.3)

plt.tight_layout()
plt.savefig("tolerance_stackup.png", dpi=150, bbox_inches='tight')
plt.show()

print(f"\n=== 公差累積の要約({n_joints}結合部後)===")
print(f"  モンテカルロ平均:     {np.mean(final_errors):.2f} mm")
print(f"  モンテカルロ95%ile:   {np.percentile(final_errors, 95):.2f} mm")
print(f"  モンテカルロ99%ile:   {np.percentile(final_errors, 99):.2f} mm")
print(f"  RSS理論値(平均):    {rss_3d_theory[-1]:.2f} mm")
print(f"  ワーストケース:       {worst_case[-1]:.2f} mm")

左のグラフからいくつかの重要な知見が得られます。

  1. 累積誤差は結合部の数の平方根に比例して増加することが明確に見て取れます。20 結合部後の平均誤差は、1 結合部あたりの誤差の $\sqrt{20} \approx 4.5$ 倍程度であり、20 倍ではありません。これが RSS 法の効果です。
  2. モンテカルロシミュレーション(緑線)と RSS 理論値(橙線)がよく一致していることから、RSS 法が各結合部の誤差が独立で正規分布に従う場合に妥当な近似であることが確認できます。
  3. ワーストケース(紫線)は RSS の数倍に達するため、安全マージンを設計する際にはワーストケースも考慮に入れる必要がありますが、統計的な設計ではRSS を基準にして 95 パーセンタイルや 99 パーセンタイルで管理するのが現実的です。

右のヒストグラムは、20 結合部後の最終位置誤差の分布を示しています。この分布は Maxwell 分布(3 次元の正規分布の距離)に近い形をしており、平均値を中心に非対称な裾を持っています。95 パーセンタイルの値が設計の基準となり、この値が精度要求(例えば太陽電池アレイなら $\pm$5 mm)を満たすように、各結合部の精度仕様を決定します。

必要精度の逆算

実用上は「最終的な累積誤差をある値以下に抑えるために、各結合部に要求する精度はいくらか」という逆問題を解きたいことが多くあります。

import numpy as np

# --- 逆算: 許容累積誤差から各結合部の精度要求を導出 ---

def required_joint_precision(target_error_95pct, n_joints, n_mc=50000):
    """
    目標の95%ile累積誤差から、各結合部に要求される精度(1σ)を逆算する。
    """
    # 初期推定値: RSS近似
    # 95%ile ≈ mean + 1.645 * std ≈ sigma*sqrt(n)*sqrt(8/pi) + ...
    # 簡易的に sigma_joint ≈ target / (factor * sqrt(n))
    # factor はMC較正で決定

    # まず sigma=1 で較正
    errors_x = np.random.normal(0, 1.0, (n_mc, n_joints))
    errors_y = np.random.normal(0, 1.0, (n_mc, n_joints))
    errors_z = np.random.normal(0, 1.0, (n_mc, n_joints))
    cum_x = np.sum(errors_x, axis=1)
    cum_y = np.sum(errors_y, axis=1)
    cum_z = np.sum(errors_z, axis=1)
    cum_3d = np.sqrt(cum_x**2 + cum_y**2 + cum_z**2)
    p95_unit = np.percentile(cum_3d, 95)

    # スケーリング: sigma=1での95%ileがp95_unitなら
    # sigma=target/p95_unitで目標を達成
    required_sigma = target_error_95pct / p95_unit

    return required_sigma

# 応用例: 太陽電池アレイ(20モジュール、許容5mm)
target_mm = 5.0
n_j = 20
req_sigma = required_joint_precision(target_mm, n_j)
print(f"=== 結合部精度の逆算 ===")
print(f"  目標: {n_j}結合部後の95%ile位置誤差 < {target_mm:.1f} mm")
print(f"  必要な各結合部の精度(1σ): {req_sigma:.3f} mm")
print()

# 複数のシナリオを比較
scenarios = [
    ("太陽電池アレイ", 20, 5.0),
    ("通信アンテナ", 10, 1.0),
    ("大型望遠鏡トラス", 30, 0.5),
    ("SBSP (1000モジュール)", 1000, 50.0),
]

print(f"{'用途':25s} {'結合部数':>8s} {'許容誤差95%':>12s} {'結合部精度1σ':>14s}")
print("-" * 65)
for name, nj, target in scenarios:
    sigma = required_joint_precision(target, nj)
    print(f"{name:25s} {nj:8d} {target:10.1f} mm {sigma:12.4f} mm")

逆算の結果から、用途ごとに要求される結合部精度の違いが定量的に把握できます。太陽電池アレイでは各結合部に $\pm$0.6 mm 程度の精度で済みますが、大型望遠鏡のトラスでは $\pm$0.05 mm 程度が必要になり、加工・組立の難易度が桁違いに上がります。一方、SBSP では結合部数が 1000 と膨大ですが、許容誤差が大きいため各結合部の精度要求はそれほど厳しくありません。

このように、モンテカルロシミュレーションと RSS 理論を組み合わせることで、ミッション要求から結合部の精度仕様を導出する設計プロセスが実現できます。

技術課題と今後の展望

軌道上組立の実用化に向けては、まだ多くの技術課題が残されています。ここでは主要な課題と、それに対するアプローチを整理します。

自律性の向上

ISS の組立は地上管制官が逐次指示する「テレオペレーション」で行われました。しかし、将来の大規模組立では通信遅延(月軌道で約 2.6 秒、ラグランジュ点で数秒〜数十秒)や作業量の膨大さから、高度な自律性が不可欠です。

必要な自律性のレベルは以下のように段階的です。

レベル 内容 現在の技術水準
1 テレオペレーション(人間が逐次操作) ISS で実証済み
2 半自律(人間が高レベル指示、ロボットが詳細実行) 部分的に実証
3 条件付き自律(正常時は自律、異常時に人間介入) 研究段階
4 高度自律(異常対応も自律的に判断) 研究初期段階
5 完全自律(人間の介入なし) 将来構想

現在の技術はレベル 2 の段階にあり、レベル 3〜4 の実現が今後 10〜20 年の目標です。特に、組立中の異常検知と自動リカバリ(例: 結合失敗時の再試行、モジュール損傷時の代替計画生成)は、機械学習と強化学習の宇宙応用として活発に研究されています。

マルチロボット協調

大規模構造物の組立では、単一のロボットアームでは作業効率が不十分です。複数のロボットが協調して同時に組立作業を行うマルチロボット協調が必要になります。

協調の課題は以下の通りです。

  • タスク割り当て: どのロボットがどのモジュールを担当するかの最適割り当て
  • 衝突回避: 複数のロボットが同時に作業する際の物理的干渉の回避
  • 同期: 2 台以上のロボットが協力して 1 つの大型モジュールを保持・配置する際の力の同期制御

これらは地上のマルチロボットシステムでは研究が進んでいますが、微小重力・通信遅延・エネルギー制約のある宇宙環境での実証はこれからです。

標準化とエコシステム

軌道上組立を産業として成立させるには、単一の組織が全てを担うのではなく、多数の企業が部品・サービスを提供するエコシステムが必要です。そのためには、

  • 機械的インターフェースの標準化: LEGO のように、異なるメーカーのモジュールでも結合できる共通規格
  • 電気的インターフェースの標準化: 電力・データの共通プロトコル
  • ソフトウェアインターフェースの標準化: ロボットの制御コマンドの共通フォーマット

NASA の ISAM 戦略ではこの標準化を重要課題として位置づけており、業界全体での標準規格の策定が進められています。

まとめ

本記事では、ロボットによる軌道上組み立て(On-Orbit Assembly)技術を、歴史から最先端、理論からアルゴリズム実装まで体系的に解説しました。

  • フェアリング制約: ロケットのフェアリングサイズが宇宙構造物のサイズを制限する。折り畳み/展開方式にはスケーリング限界がある
  • ISS の教訓: 軌道上組立は実証済みだが、EVA への依存、標準化の不足、組立順序の複雑性、熱変形への対処など多くの課題が明らかになった
  • 次世代技術: NASA ISAM 戦略、Archinaut、OSAM-1 が自動組立の技術基盤を構築しつつある
  • モジュラー設計: 3 階層(セグメント/モジュール/ユニット)のモジュラー構造と標準結合インターフェースが、効率的な組立の鍵
  • 組立順序最適化: DAG によるモデル化とトポロジカルソート、クリティカルパス解析により、最適な組立順序を計画できる
  • 公差管理: RSS 法とモンテカルロシミュレーションで累積誤差を予測し、各結合部の精度仕様を導出できる
  • 応用: 次世代大型望遠鏡(HWO)や宇宙太陽光発電所(SBSP)が、軌道上組立の最も有望な応用先

軌道上組立は、宇宙開発を「打ち上げサイズの制約の中で最適化する」段階から「宇宙空間で自由にインフラを構築する」段階へと進化させる革新的な技術です。この技術の成熟により、これまで夢物語とされてきた宇宙太陽光発電や超大型望遠鏡が現実のものになっていくでしょう。

次のステップとして、以下の記事も参考にしてください。