トレリス符号化変調(TCM) — 変調と符号化を統合して利得を得る

衛星通信や電話回線のモデムでは、使える周波数帯域に厳しい上限があります。たとえば電話回線のモデムでは約3.4 kHzしか帯域がなく、その中で少しでも速く、少しでも誤りなくデータを送りたい。ここで普通の発想は「誤り訂正符号を足してビット誤りを減らそう」というものです。ところが、従来のやり方で符号を足すと、冗長ビットの分だけシンボルを多く送る必要が生じ、帯域がさらに必要になってしまいます。帯域が固定なら、シンボルレートを上げられない以上、冗長ビット分を別のどこかに押し込まなければなりません。

この「帯域を一切広げずに、しかも符号化利得を得る」という一見矛盾した要求に応えたのが、1982年にウンガーベック(Gottfried Ungerboeck)が発表したトレリス符号化変調(TCM: Trellis-Coded Modulation)です。アイデアの核心は驚くほどシンプルで、「符号化と変調を別々に設計するのをやめて、ひとつの統合された設計とみなす」ことにあります。冗長ビットはコンスタレーション(信号点配置)を拡大することで吸収し、その拡大した信号点を賢く割り当てることで、誤りに強い構造を作り出します。

TCMを理解すると、次のような場面でなぜそれが効くのかが見えてきます。

  • 電話回線モデム(V.32, V.34): 9600 bps以上の高速モデムは、ほぼ例外なくTCMを採用し、3 dB以上の符号化利得を帯域拡張なしで得ていました
  • 衛星通信・宇宙通信: 帯域も電力も貴重な衛星リンクで、TCMやその発展形が長く使われてきました。後継規格であるDVB-S2Xの適応符号化変調(ACM)の思想的な源流でもあります
  • デジタル放送・無線LAN初期規格: 帯域効率と誤り耐性の両立が求められる場面で、符号化変調の考え方は広く根を張っています

本記事の内容

  • 従来の「符号化+変調を分離する設計」がなぜ帯域効率の面で非効率なのか
  • TCMの中心的アイデア — 冗長をコンスタレーション拡大で吸収する
  • ウンガーベックの集合分割(set partitioning)— 部分集合内の最小ユークリッド距離を段階的に増やす
  • 自由距離 $d_\text{free}$ と漸近符号化利得の定義と導出
  • 8-PSK TCM / 16-QAM TCM の具体的なトレリスと符号器
  • ユークリッド距離に基づく軟判定Viterbi復号
  • Pythonによる8-PSK TCM符号器・トレリス・Viterbi復号の実装と、非符号化QPSKとのBER比較

前提知識

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

  • 畳み込み符号とビタビ復号 — TCMの符号部は畳み込み符号であり、復号はトレリス上のViterbiアルゴリズムです。本記事の土台になります
  • QAM変調の原理 — コンスタレーション拡大の議論で16-QAMを例に使います
  • PSK変調の原理 — 8-PSKの信号点配置とユークリッド距離の計算に使います

これらの記事で「コンスタレーション」「ハミング距離」「トレリス」「Viterbi復号」という言葉に馴染んでおくと、本記事の議論がスムーズに頭に入ります。

なぜ「符号化+変調の分離」は非効率なのか

伝統的な通信系の設計思想

長い間、通信系は「機能ごとにブロックを分けて、それぞれを独立に最適化する」という思想で設計されてきました。送信側では、まず情報ビットに誤り訂正符号(たとえば畳み込み符号)を施し、それから変調器でコンスタレーション上のシンボルに割り当てる。受信側では、まず復調器が各シンボルを最も近い信号点に硬判定し、ビット列に戻したうえで、誤り訂正復号器がそのビット列の誤りを訂正する。この「符号化は符号化、変調は変調」という分業は、設計を見通しよくする一方で、ある重大な代償を払っています。

その代償を具体的に見てみましょう。たとえば符号化率 $R = 1/2$ の畳み込み符号を使うと、1ビットの情報を送るのに2ビットの符号ビットが必要になります。これをQPSK(1シンボルあたり2ビット)で送ると、情報ビットあたりに必要なシンボル数は変わりませんが、もし元々BPSK(1シンボルあたり1ビット)で送っていたシステムに $R=1/2$ 符号を足すと、シンボルレートを2倍にしなければなりません。シンボルレートが2倍になれば、必要な帯域も約2倍になります。

つまり、従来の符号化は「帯域(あるいはシンボルレート)と引き換えに信頼性を買う」取引なのです。帯域に余裕のある通信路ならこれで構いません。しかし、電話回線や衛星リンクのように帯域が物理的に固定されている系では、この取引は成立しません。

帯域を固定したまま冗長を入れるには

帯域を固定するということは、単位時間あたりに送れるシンボル数(シンボルレート)を固定するということです。その制約のもとで誤り訂正の冗長を入れたい。残された自由度はただ一つ、1シンボルが運ぶビット数を変えない代わりに、コンスタレーションの信号点の数を増やすことです。

具体例で考えましょう。非符号化のQPSKは1シンボルで2ビットを運びます。ここに冗長を入れたいが、シンボルレートは変えたくない。そこでコンスタレーションを4点(QPSK)から8点(8-PSK)に拡大します。8-PSKは本来1シンボルで3ビット運べますが、そのうち1ビットを誤り訂正符号の冗長ビットに充て、実質2ビットの情報を運ぶことにします。こうすれば、情報ビットレートもシンボルレートも非符号化QPSKと全く同じまま、1ビット分の冗長を符号化に使えるのです。

ここで素朴な疑問が湧きます。「8-PSKはQPSKより信号点が密集していて、ノイズに弱いのではないか?」 まさにそのとおりで、単純に8-PSKにしただけでは、隣接信号点間の距離が縮まり、むしろ誤りやすくなります。後で計算しますが、平均エネルギーを1に揃えると8-PSKの隣接点間距離は $2\sin(\pi/8)\approx0.765$、QPSKの隣接点間距離は $\sqrt{2}\approx1.414$ なので、8-PSKの隣接距離はQPSKの約0.541倍にすぎません。誤り率を決めるのは距離の2乗なので、$0.765^2/1.414^2 = (2-\sqrt2)/2 \approx 0.293$、すなわちコンスタレーション拡大はそれ単体で $10\log_{10}(2/(2-\sqrt2))\approx 5.3$ dBの損失を生みます。

この損失を埋めて、なお余りある利得を生み出すのが、符号と変調を統合する設計、すなわちTCMの真骨頂です。次節では、その「統合」が具体的に何を意味するのかを見ていきます。

適切な距離尺度はハミング距離ではなくユークリッド距離

従来の分離設計のもう一つの問題は、最適化している距離尺度が間違っている点にあります。誤り訂正符号の世界では、符号語間のハミング距離(異なるビットの個数)を最大化することが性能向上の鍵でした。ハミング距離が大きいほど、ビット列としては区別しやすいからです。

しかし、実際に受信機が観測するのはビット列ではなく、コンスタレーション上の連続的な信号点に雑音が乗ったものです。AWGN(加法的白色ガウス雑音)通信路でシンボル誤り率を決めるのは、信号点間のユークリッド距離です。ハミング距離が大きくても、対応する信号点同士がコンスタレーション上で近ければ、雑音で簡単に取り違えられてしまいます。

ウンガーベックの根本的な洞察は、「符号と変調を一体で設計し、符号語列に対応する信号点列の間のユークリッド距離を直接最大化せよ」というものでした。符号化の段階でハミング距離を稼ぎ、変調の段階でビットを信号点に割り当てる、という二段構えでは、最終的なユークリッド距離は最適化されません。両者を統合してはじめて、AWGN通信路で本当に効く距離が最大化できるのです。

この「ユークリッド距離を最大化する」という目標を達成する具体的な道具立てが、集合分割という手法です。次節で詳しく見ていきましょう。

集合分割 — Ungerboeckのアイデアの核心

直感: 信号点を「だんだん離れていくグループ」に分ける

集合分割の直感を、座席の割り当てに例えてみましょう。大きなホールに観客を座らせるとき、隣同士が騒ぐのを防ぎたいとします。一度に全員を詰め込むのではなく、「まず奇数列と偶数列に分け、次にそれぞれを前半と後半に分け……」と階層的に分けていくと、最終的に各グループの観客同士は遠く離れて座ることになります。

集合分割(set partitioning)はこれと全く同じ発想です。コンスタレーション全体を出発点として、それを2つの部分集合に分割し、各部分集合をさらに2つに分割し……と繰り返します。重要なのは分け方で、毎回「同じ部分集合に属する信号点同士のユークリッド距離が最大になるように」分けるのです。すると分割が進むほど、各部分集合内の信号点はコンスタレーション上で互いに遠く離れていきます。

8-PSKの集合分割を具体的に追う

8-PSKを例に、集合分割を一段ずつ実行してみましょう。8-PSKの8つの信号点は、半径1の円周上に角度 $2\pi k/8$($k = 0, 1, \dots, 7$)で等間隔に並んでいます。信号点 $s_k = e^{j 2\pi k / 8}$ と書けます。

まず、円周上で隣り合う信号点間の距離を計算します。角度差 $\Delta\theta$ の2点間のユークリッド距離は、半径 $r=1$ のとき

$$ d(\Delta\theta) = 2 r \sin\left(\frac{\Delta\theta}{2}\right) = 2\sin\left(\frac{\Delta\theta}{2}\right) $$

です。これは余弦定理 $d^2 = r^2 + r^2 – 2r^2\cos\Delta\theta = 2r^2(1 – \cos\Delta\theta)$ に半角公式 $1 – \cos\Delta\theta = 2\sin^2(\Delta\theta/2)$ を代入すれば得られます。隣接点の角度差は $\Delta\theta = 2\pi/8 = \pi/4$ なので、

$$ \Delta_0 = 2\sin\left(\frac{\pi}{8}\right) \approx 2 \times 0.3827 = 0.7654 $$

これが8-PSK全体(レベル0の部分集合)における最小ユークリッド距離です。

第1段階の分割(レベル0 → レベル1): 8点を2つの4点部分集合 $B_0, B_1$ に分けます。$B_0$ には偶数番号 $\{s_0, s_2, s_4, s_6\}$、$B_1$ には奇数番号 $\{s_1, s_3, s_5, s_7\}$ を集めます。すると $B_0$ の中で隣り合う点は $s_0$ と $s_2$ で、角度差は $2 \times \pi/4 = \pi/2$。距離は

$$ \Delta_1 = 2\sin\left(\frac{\pi}{4}\right) = 2 \times 0.7071 = 1.4142 = \sqrt{2} $$

$B_1$ も同様に最小距離 $\sqrt{2}$ です。元の $0.7654$ から $1.4142$ へと、部分集合内の最小距離が一気に広がりました。これはちょうど $\Delta_1 / \Delta_0 = \sqrt{2}/(2\sin(\pi/8)) \approx 1.848$ 倍です。

第2段階の分割(レベル1 → レベル2): 各4点部分集合をさらに2点ずつに分けます。$B_0 = \{s_0, s_2, s_4, s_6\}$ を $C_0 = \{s_0, s_4\}$ と $C_2 = \{s_2, s_6\}$ に分けると、$C_0$ の2点は角度差 $\pi$(正反対)なので距離は

$$ \Delta_2 = 2\sin\left(\frac{\pi}{2}\right) = 2 $$

これは8-PSKコンスタレーションで取りうる最大距離(直径)です。

まとめると、8-PSKの集合分割では、各段階の部分集合内最小距離は

$$ \Delta_0 = 0.765 \;\to\; \Delta_1 = \sqrt{2} \approx 1.414 \;\to\; \Delta_2 = 2.0 $$

と単調に増加していきます。この増加していく距離の列 $\Delta_0 < \Delta_1 < \Delta_2$ こそが、TCMが利得を生む源泉です。

マッピング・バイ・セット・パーティショニング

集合分割の各段階での選択を、ビットで番号付けすることを考えます。8-PSKの各信号点に3ビット $(z_2, z_1, z_0)$ を割り当てるのですが、ウンガーベックの流儀では、最下位ビット $z_0$ が最も粗い分割(レベル1)を選び、上位ビットほど細かい分割を選ぶように対応させます。

具体的には次のように対応します。

  • $z_0$ : 8点を $B_0$($z_0=0$)と $B_1$($z_0=1$)のどちらに入れるかを選ぶ(部分集合内最小距離 $\Delta_1$)
  • $z_1$ : 選ばれた4点をさらに2点部分集合のどちらに入れるかを選ぶ(最小距離 $\Delta_2$)
  • $z_2$ : 最後に残った2点のどちらかを選ぶ

この「マッピング・バイ・セット・パーティショニング(mapping by set partitioning)」が決定的に重要です。TCMでは、畳み込み符号が生成する符号ビットを下位ビット側(粗い分割を決めるビット)に割り当て、符号化されない情報ビットを上位ビット側(細かい分割の中での選択)に割り当てます。

なぜこの割り当てが効くのか。トレリスの同じ状態から出る2本の枝(後で詳述)は、必ず「同じ部分集合の中の2点」に対応するように設計されます。同じ部分集合内なので、それらの距離は大きな $\Delta_1$ や $\Delta_2$ になっている。逆に、トレリス上で異なる経路に分岐する信号は、できるだけ離れた信号点になるように符号が設計されます。こうして、誤りやすいシンボル対の距離を底上げするのです。

集合分割によって「部分集合内で距離が広がる」という性質を手に入れました。これを実際の符号器とトレリス構造に組み込む方法を、次に見ていきます。

TCM符号器とトレリスの構造

一般的な構造

TCM符号器の一般的な構造は次のとおりです。入力の $m$ 情報ビットのうち、$\tilde{m}$ ビットだけをレート $\tilde{m}/(\tilde{m}+1)$ の畳み込み符号器に通して $\tilde{m}+1$ ビットの符号ビットを生成し、残りの $m – \tilde{m}$ ビットは符号化せずにそのまま通します。合計 $m+1$ ビットを、$2^{m+1}$ 点のコンスタレーション上の1点へ集合分割マッピングで割り当てます。

8-PSK TCMの最も基本的な例では $m = 2$(情報2ビット/シンボル)、$\tilde{m} = 1$ とします。つまり、2つの情報ビット $(u_2, u_1)$ のうち $u_1$ だけをレート1/2の畳み込み符号器に通して2ビット $(z_1, z_0)$ を作り、$u_2$ はそのまま $z_2$ とします。3ビット $(z_2, z_1, z_0)$ で8-PSKの1点を選びます。

このとき、シンボルあたりの情報ビットは2ビットで、非符号化QPSKと同じ帯域効率(2 bit/symbol)を保ちます。冗長ビット1ビット分はコンスタレーションを4点から8点に拡大することで吸収されているわけです。

符号化ビットと非符号化ビットの役割分担

ここで集合分割の対応を思い出してください。符号器が出力する $(z_1, z_0)$ は、集合分割の粗いレベル(部分集合の選択)を決めます。一方、符号化されない $z_2$ は、選ばれた2点部分集合の中でどちらの点を選ぶかを決めます。

この役割分担には深い意味があります。トレリスのある状態から次の状態へ遷移する1本の「枝」は、$(z_1, z_0)$ の値で決まります。同じ枝(同じ $(z_1, z_0)$)でも $z_2$ の値が0か1かで異なる信号点に対応しますが、それらは同じ2点部分集合に属するので、ユークリッド距離が最大値 $\Delta_2 = 2$ になっています。

つまり、$z_2$ のビット誤りだけが起きた場合(トレリス上は同じ経路をたどる)、対応する2信号点は距離 $\Delta_2 = 2$ も離れているので、雑音で取り違えるのは非常に困難です。これを平行枝(parallel transition)と呼びます。平行枝の距離 $\Delta_2$ が大きいことが、TCMの利得の一部を担います。

4状態8-PSK TCMの具体的なトレリス

ウンガーベックが提示した最も有名な例の一つが、4状態の8-PSK TCMです。レート1/2の畳み込み符号器を、拘束長3(メモリ2、状態数 $2^2 = 4$)で構成します。状態を $(s_1, s_0)$(2ビット)で表します。

符号器の生成多項式は、ウンガーベックの最適符号として $h_1 = 2_{(8)} = 010$, $h_0 = 5_{(8)} = 101$(8進表記)などが知られていますが、ここでは挙動を具体的に追える単純な形で説明します。入力1ビット $u_1$ と現在の状態から、符号ビット $(z_1, z_0)$ と次状態が決まります。

トレリスの各状態からは、$u_1 \in \{0,1\}$ と非符号化ビット $u_2 \in \{0,1\}$ の組み合わせで、合計4本の枝(信号点)が出ます。ただし $u_1$ が同じで $u_2$ だけが異なる2本は平行枝で、距離 $\Delta_2 = 2$ の信号点対に対応します。$u_1$ が異なる2本は、異なる次状態へ遷移します。

このトレリス設計で達成される性能を、次節で「自由距離」という量で定量化します。

マッピングの設計原則(ウンガーベックのルール)

ウンガーベックは、トレリスを設計する際の経験則を3つのルールにまとめました。

  1. 平行枝には、集合分割の最も深いレベルの部分集合(最大距離の点)を割り当てる。 これにより平行枝の距離が最大化されます。
  2. 同じ状態から出る枝、および同じ状態に入る枝には、ひとつ上のレベルの部分集合(次に大きい距離)を割り当てる。 これにより、1ステップで分岐・合流するパスの距離が確保されます。
  3. すべての信号点を等しい頻度で使う。 コンスタレーションの対称性を保ち、平均電力を一定にします。

これらのルールに従って符号を設計すると、可能な限り大きな自由距離が得られます。次節で、この自由距離を厳密に定義し、符号化利得を計算します。

自由距離と漸近符号化利得

自由距離とは

畳み込み符号で誤り性能を支配するのが最小ハミング距離(自由距離)であったように、TCMで誤り性能を支配するのは、信号点列のユークリッド距離で測った自由距離(free distance)$d_\text{free}$ です。これは、トレリス上で同じ状態から分岐して、いずれ再び同じ状態に合流する2本の異なる経路について、それぞれに対応する信号点列の間のユークリッド距離の最小値として定義されます。

数式で書くと、トレリス上の2つの異なる符号語シンボル列 $\bm{a} = (a_0, a_1, \dots)$ と $\bm{b} = (b_0, b_1, \dots)$(どちらも同じ状態から出て同じ状態に戻る)に対して、

$$ d_\text{free}^2 = \min_{\bm{a} \neq \bm{b}} \sum_k |a_k – b_k|^2 $$

ここで $a_k, b_k$ はコンスタレーション上の複素信号点、$|a_k – b_k|$ はそのユークリッド距離です。和はパスが分岐してから合流するまでの区間にわたります。

$d_\text{free}$ が大きいほど、最も間違えやすいパス対が遠く離れていることになり、誤り率が下がります。

4状態8-PSK TCMの自由距離を求める

4状態8-PSK TCMの $d_\text{free}$ を、2種類の「誤りパス」を比較して求めます。

候補1: 平行枝による誤り。 平行枝同士の距離は前述のとおり $\Delta_2 = 2$ です。これは1シンボルだけ異なるパスなので、

$$ d_\text{parallel}^2 = \Delta_2^2 = 4 $$

候補2: 分岐して数シンボル後に合流するパス。 トレリス上で1ステップ目に分岐するとき、その2つの枝は「同じ状態から出る枝」なのでルール2より距離 $\Delta_1 = \sqrt{2}$ の部分集合に属します。その後、何ステップか経て合流する直前の枝もまた距離 $\Delta_1$ です。最短の非平行誤りパスは3シンボルで、典型的には中間のシンボルで距離 $\Delta_0$、両端で距離 $\Delta_1$ となるパスが最小を与えます。ウンガーベックの設計では、この最短誤りイベントの2乗ユークリッド距離は

$$ d_\text{event}^2 = \Delta_1^2 + \Delta_0^2 + \Delta_1^2 = 2 + 0.586 + 2 = 4.586 $$

ここで $\Delta_0^2 = (2\sin(\pi/8))^2 = 4\sin^2(\pi/8) = 2 – \sqrt{2} \approx 0.586$ を用いました。

2つの候補を比べると $d_\text{parallel}^2 = 4 < d_\text{event}^2 = 4.586$ なので、4状態8-PSK TCMの自由距離は平行枝で決まり、

$$ d_\text{free}^2 = 4, \quad d_\text{free} = 2 $$

となります。状態数を増やす(8状態、16状態……)と、ルール2の制約がさらに効いて分岐パスの距離が伸び、自由距離は平行枝の $4$ を超えて大きくなっていきます。8状態8-PSK TCMでは $d_\text{free}^2 = 4.586$、16状態では $5.172$ と増えていきます。

漸近符号化利得の定義

符号化利得を測るには、比較対象となる非符号化システムを決めなければなりません。同じ情報ビットレート・同じシンボルレート(同じ帯域)・同じ平均電力で送る非符号化システムと、TCMを比べます。8-PSK TCM(2 bit/symbol)の比較対象は、非符号化QPSK(2 bit/symbol)です。

AWGN通信路では、シンボル誤り率は最小ユークリッド距離で近似的に決まります。十分高いSNRでは、誤り率はおおむね

$$ P_e \approx N_\text{free} \cdot Q\left(\frac{d_\text{free}}{2\sigma}\right) $$

と書けます($Q$ はガウス裾確率、$N_\text{free}$ は最小距離パスの平均個数、$\sigma$ は雑音標準偏差)。ここで重要なのは、誤り率が $d_\text{free}^2 / \sigma^2$ の関数であることです。同じ雑音 $\sigma$ のもとで誤り率を同じにするには、$d_\text{free}^2$ が大きいほど有利です。

ただし、平均電力を揃える必要があります。TCMと非符号化系で平均電力(信号点エネルギーの平均)が異なると、距離の絶対値同士を比べても公平ではありません。そこで、単位平均エネルギーあたりに正規化した最小2乗距離で比べます。

漸近符号化利得 $G$(dB)は次で定義されます。

$$ G = 10 \log_{10}\left(\frac{d_\text{free}^2 / E_\text{TCM}}{d_\text{min,uncoded}^2 / E_\text{uncoded}}\right) $$

ここで $E_\text{TCM}, E_\text{uncoded}$ はそれぞれの系の平均シンボルエネルギー、$d_\text{min,uncoded}$ は非符号化系の最小ユークリッド距離です。

8-PSK TCM 対 QPSK の符号化利得を計算する

具体的に数値を入れましょう。まず信号点を平均エネルギー1に正規化します。8-PSKもQPSKも単位円上の点なので、どちらも全信号点のエネルギーが1、平均エネルギーも $E_\text{TCM} = E_\text{uncoded} = 1$ です。

非符号化QPSKの最小距離は、隣接する2点(角度差 $\pi/2$)の距離なので

$$ d_\text{min,uncoded} = 2\sin\left(\frac{\pi}{4}\right) = \sqrt{2}, \quad d_\text{min,uncoded}^2 = 2 $$

4状態8-PSK TCMの自由距離は前に求めたとおり $d_\text{free}^2 = 4$。両系とも平均エネルギーが等しいので、符号化利得は

$$ G = 10\log_{10}\left(\frac{d_\text{free}^2}{d_\text{min,uncoded}^2}\right) = 10\log_{10}\left(\frac{4}{2}\right) = 10\log_{10}(2) \approx 3.01 \text{ dB} $$

つまり、4状態という非常に小さなトレリスでも、帯域を一切広げずに約3 dBの符号化利得が得られるのです。8状態にすれば $10\log_{10}(4.586/2) \approx 3.6$ dB、16状態で $\approx 4.1$ dBと、状態数を増やすほど利得が伸びます。

ここで「コンスタレーション拡大による約5.3 dBの損失はどこへ行ったのか」を確認しておきます。素朴に8-PSKへ拡大しただけなら最小2乗距離は $2$ から $0.586$ へ縮み、約5.3 dB分損をするはずでした。ところが集合分割マッピングと符号設計によって、平行枝と分岐パスの距離を底上げした結果、TCMの実効的な自由距離は $d_\text{free}^2 = 4$ にまで回復しています。非符号化QPSKの $d_\text{min}^2 = 2$ と比べれば、拡大による損失を埋めたうえでさらに正味 $10\log_{10}(4/2)\approx3$ dBの利得を生み出している、というのが正しい理解です。距離尺度をユークリッド距離に統一して最適化したからこそ、この正味利得が実現します。

シャノン的な視点

シャノンの通信路容量の観点からも、TCMは理にかなっています。AWGN通信路で帯域効率2 bit/symbolを達成するのに必要な最小SNRと、非符号化QPSKが実用的な誤り率(たとえば $10^{-5}$)に必要なSNRの間には、約9 dB近いギャップがあります。これは「符号化で埋められる余地(coding gap)」です。TCMはこのギャップの一部(3〜6 dB程度)を、帯域を犠牲にせず埋めます。LDPCやターボ符号のような強力な符号と組み合わせる符号化変調の現代的な発展(BICMなど)も、この「変調と符号化を一体で考える」というTCMの精神を受け継いでいます。

理論が出揃ったので、いよいよ8-PSK TCMをPythonで実装し、本当に3 dBの利得が出るかをシミュレーションで確かめましょう。

Pythonでの実装

8-PSKコンスタレーションと集合分割マッピング

まず、8-PSKの信号点と、集合分割に基づくビット→信号点マッピングを定義します。ウンガーベックの「マッピング・バイ・セット・パーティショニング」では、3ビット $(z_2, z_1, z_0)$ を信号点インデックスに対応させます。ここでは、$z_0$ が粗い分割、$z_2$ が最深の分割を選ぶように番号付けします。

import numpy as np
import matplotlib.pyplot as plt

# 8-PSK信号点(半径1の単位円上、平均エネルギー=1)
M = 8
angles = 2 * np.pi * np.arange(M) / M
psk8 = np.exp(1j * angles)  # s_k = exp(j 2πk/8)

# 集合分割マッピング:
# 3ビット (z2,z1,z0) を信号点インデックスへ対応させる。
# z0: B0={0,2,4,6} / B1={1,3,5,7}(粗い分割, 距離Δ1=√2)
# z1: 4点をさらに2点ずつへ(距離Δ2=2)
# z2: 最深部分集合内の2点のいずれか
# 物理点インデックス = z0 + 2*z1 + 4*z2 とすると、
# 同じ(z0,z1)で z2違いの2点は角度差πとなり距離2になる。
def bits_to_point_index(z2, z1, z0):
    return z0 + 2 * z1 + 4 * z2

# マッピングの確認: (z0,z1)固定でz2を変えた2点の距離
for z0 in range(2):
    for z1 in range(2):
        i0 = bits_to_point_index(0, z1, z0)
        i1 = bits_to_point_index(1, z1, z0)
        d = abs(psk8[i0] - psk8[i1])
        print(f"z0={z0}, z1={z1}: 平行枝の2点 {i0},{i1} 距離={d:.4f}")

このコードの出力では、すべての平行枝($z_2$ だけ異なる2点)の距離が $2.0000$ になっているはずです。これは集合分割の最深レベルの距離 $\Delta_2 = 2$ にほかなりません。$z_2$ を符号化しない情報ビットに割り当てることで、その1ビットの誤りは距離2の信号点取り違えに対応し、極めて起こりにくくなる——これがマッピング設計の意図どおりに効いていることが確認できます。

レート1/2畳み込み符号器とトレリスの構築

次に、4状態(メモリ2)のレート1/2畳み込み符号器を定義し、トレリス(状態遷移表)を構築します。情報ビット $u_1$ を符号化して $(z_1, z_0)$ を生成し、非符号化ビット $u_2$ をそのまま $z_2$ とします。

# 4状態(メモリ2)レート1/2 畳み込み符号器
# 状態 state ∈ {0,1,2,3} = (s1 s0) の2ビット
# 入力 u1, シフトレジスタ [s0, s1]
# Ungerboeckの4状態8-PSK最適符号に対応する遷移則:
#   z0 = s1                 (状態の上位ビットがそのまま下位符号ビットに)
#   z1 = u1 XOR s0
# 次状態: s1' = u1, s0' = s1   → next_state = (u1<<1) | s1
# この割り当てにより、同じ状態から出る2本(u1違い)は距離Δ1=√2の部分集合に、
# 同じ状態に合流する2本も距離Δ1の部分集合に属し、d_free^2=4を達成する。
def encode_step(state, u1):
    s0 = state & 1
    s1 = (state >> 1) & 1
    z0 = s1
    z1 = u1 ^ s0
    next_state = ((u1 << 1) | s1) & 0b11  # s1'=u1, s0'=s1
    return z1, z0, next_state

# トレリス遷移表を作る:
# trans[state][(u2,u1)] = (信号点インデックス, 次状態)
num_states = 4
trans = {}
for state in range(num_states):
    for u2 in range(2):
        for u1 in range(2):
            z1, z0, ns = encode_step(state, u1)
            idx = bits_to_point_index(u2, z1, z0)
            trans[(state, u2, u1)] = (idx, ns)

# 確認: 状態0から出る4本の枝
for u2 in range(2):
    for u1 in range(2):
        idx, ns = trans[(0, u2, u1)]
        print(f"state0, u2={u2}, u1={u1} -> 点{idx} (角度{np.degrees(angles[idx]):.0f}°), 次状態{ns}")

この出力から、状態0から出る4本の枝が確認できます。$u_1$ が同じで $u_2$ だけ異なる2本(平行枝)は同じ次状態へ遷移し、距離2の信号点対に対応します。$u_1$ が異なる2本は別々の次状態へ分岐し、ルール2に従って距離 $\Delta_1 = \sqrt{2}$ の部分集合の信号点になっています。トレリスがウンガーベックの設計原則どおりに構築できていることがわかります。

送信: 情報ビット列を信号点列に符号化

トレリスを使って、ランダムな情報ビット列を8-PSK信号点列に変換する送信処理を実装します。

def tcm_encode(info_bits, init_state=0):
    """情報ビット列(2bit/symに分割)をTCM符号化し8-PSK信号点列を返す"""
    state = init_state
    symbols = []
    states = [state]
    # info_bits を (u2, u1) のペアに分割
    for k in range(0, len(info_bits), 2):
        u2 = info_bits[k]
        u1 = info_bits[k + 1]
        idx, next_state = trans[(state, u2, u1)]
        symbols.append(psk8[idx])
        state = next_state
        states.append(state)
    return np.array(symbols), states

# 動作確認
np.random.seed(0)
test_bits = np.random.randint(0, 2, 20)
syms, st = tcm_encode(test_bits)
print("情報ビット:", test_bits)
print("シンボル数:", len(syms), "(=情報ビット数/2)")
print("状態遷移:", st)

この確認から、20個の情報ビットが10個の8-PSKシンボルに符号化されていることがわかります。1シンボルあたり2ビットという帯域効率は非符号化QPSKと完全に同じです。状態遷移の系列を見ると、各シンボルでトレリス上を1ステップずつ進んでいることが追えます。送信側の処理は、トレリスの遷移表を引くだけのシンプルなものになっています。

受信: ユークリッド距離に基づく軟判定Viterbi復号

復号は、受信信号点列に対して、トレリス上で最もユークリッド距離が近い経路を探すViterbiアルゴリズムです。畳み込み符号のViterbiではハミング距離(枝メトリック)を使いましたが、TCMでは受信点と各枝の信号点とのユークリッド2乗距離を枝メトリックに使う点が本質的な違いです。これが「軟判定」であり、硬判定してからビット復号する従来法より優れています。

平行枝の扱いに注意が必要です。同じ $(state, u_1)$ から $u_2$ 違いで出る2本の平行枝のうち、受信点に近い方を先に選んで枝メトリックを確定させます(per-survivor処理)。

def tcm_viterbi_decode(rx_symbols, init_state=0):
    """ユークリッド距離に基づく軟判定Viterbi復号"""
    n_steps = len(rx_symbols)
    INF = 1e18
    # path_metric[state]: その状態に至る最小累積メトリック
    path_metric = np.full(num_states, INF)
    path_metric[init_state] = 0.0
    # 経路復元用: 各ステップ・各状態に対し (前状態, u2, u1)
    back = [dict() for _ in range(n_steps)]

    for k in range(n_steps):
        r = rx_symbols[k]
        new_metric = np.full(num_states, INF)
        for state in range(num_states):
            if path_metric[state] >= INF:
                continue
            for u1 in range(2):
                # 平行枝(u2=0,1)のうち受信点に近い方を選ぶ
                best_u2, best_d, best_ns = None, INF, None
                for u2 in range(2):
                    idx, ns = trans[(state, u2, u1)]
                    d = abs(r - psk8[idx]) ** 2  # ユークリッド2乗距離
                    if d < best_d:
                        best_d, best_u2, best_ns = d, u2, ns
                cand = path_metric[state] + best_d
                if cand < new_metric[best_ns]:
                    new_metric[best_ns] = cand
                    back[k][best_ns] = (state, best_u2, u1)
        path_metric = new_metric

    # 最小メトリックの終端状態からトレースバック
    end_state = int(np.argmin(path_metric))
    decoded = []
    state = end_state
    for k in range(n_steps - 1, -1, -1):
        prev_state, u2, u1 = back[k][state]
        decoded.append((u2, u1))
        state = prev_state
    decoded.reverse()
    # (u2,u1)ペアをビット列へ
    bits = []
    for (u2, u1) in decoded:
        bits.extend([u2, u1])
    return np.array(bits)

# 雑音なしで復号できるか確認
decoded = tcm_viterbi_decode(syms)
print("元ビット  :", test_bits)
print("復号ビット:", decoded)
print("一致:", np.array_equal(test_bits, decoded))

雑音のない理想条件では、復号ビットが元の情報ビットと完全に一致するはずです。これでViterbi復号器が正しくトレリスをたどっていることが保証されます。枝メトリックにユークリッド2乗距離 $|r – s|^2$ を使っている点が、軟判定TCM復号の核心です。次に雑音を加えて、誤り率の改善を測ります。

非符号化QPSKとのBER比較

いよいよ本題です。同じ情報ビットレート・同じ帯域・同じ平均電力のもとで、4状態8-PSK TCMと非符号化QPSKのビット誤り率(BER)を比較します。横軸は1ビットあたりの信号対雑音比 $E_b/N_0$ とします。

from scipy.special import erfc

def qpsk_uncoded_ber(ebn0_db_range):
    """非符号化QPSKの理論BER: Q(sqrt(2 Eb/N0))"""
    ber = []
    for ebn0_db in ebn0_db_range:
        ebn0 = 10 ** (ebn0_db / 10)
        ber.append(0.5 * erfc(np.sqrt(ebn0)))  # = Q(sqrt(2 Eb/N0))
    return np.array(ber)

def tcm_simulate_ber(ebn0_db, num_bits=200000):
    """8-PSK TCMのBERをモンテカルロで測定"""
    ebn0 = 10 ** (ebn0_db / 10)
    # 2 bit/symbol。シンボルエネルギーEs=1, Eb=Es/2。
    # N0 = Eb/(Eb/N0), 雑音はI/Qに N0/2 ずつ → σ^2 = N0/2 (複素全体でN0)
    Eb = 0.5
    N0 = Eb / ebn0
    sigma = np.sqrt(N0 / 2)

    info = np.random.randint(0, 2, num_bits)
    syms, _ = tcm_encode(info)
    noise = sigma * (np.random.randn(len(syms)) + 1j * np.random.randn(len(syms)))
    rx = syms + noise
    decoded = tcm_viterbi_decode(rx)
    n = min(len(info), len(decoded))
    errors = np.sum(info[:n] != decoded[:n])
    return errors / n

np.random.seed(1)
ebn0_range = np.arange(0, 9, 1.0)
ber_qpsk = qpsk_uncoded_ber(ebn0_range)
ber_tcm = np.array([tcm_simulate_ber(e) for e in ebn0_range])

plt.figure(figsize=(8, 5))
plt.semilogy(ebn0_range, ber_qpsk, 'b--o', label='Uncoded QPSK (theory)')
plt.semilogy(ebn0_range, np.maximum(ber_tcm, 1e-6), 'r-s',
             label='4-state 8-PSK TCM (sim)')
plt.xlabel('$E_b/N_0$ (dB)')
plt.ylabel('BER')
plt.title('8-PSK TCM vs Uncoded QPSK (same bandwidth, 2 bit/sym)')
plt.legend()
plt.grid(True, which='both', alpha=0.3)
plt.ylim(1e-6, 1)
plt.tight_layout()
plt.show()

このBER曲線から、TCMの効果と、その「効き方」の両方が読み取れます。まず注意したいのは、$E_b/N_0$ が4 dBより低い領域では、TCMの曲線はむしろ非符号化QPSKより上(誤り率が高い)にあることです。SNRが低いとViterbi復号が誤った経路を選びやすく、短い誤りイベントが頻発するため、符号化利得が損失を上回りません。両曲線は $E_b/N_0 \approx 4$ dB付近で交差します。

しかしSNRが上がると形勢は一変します。交差点を超えると、TCMの曲線は急速にQPSKの下へ潜り込み、傾きも大きくなります。実測では、TCMが BER $= 10^{-4}$ に達するのは $E_b/N_0 \approx 6.2$ dB、一方QPSKが同じ BER に達するのは約 $8.4$ dB なので、$10^{-4}$ での水平距離は約 $2.2$ dBです。この利得は理論上の漸近符号化利得 $3$ dB へ、SNRを上げるほど漸近していきます(漸近利得は十分高いSNRでのみ完全に現れる量である点に注意してください)。第二に、両者は全く同じ帯域効率(2 bit/symbol)で動作しているにもかかわらずこの差が出ている、という点が決定的です。帯域を1 Hzも増やさずに、高SNR領域で純粋な利得が得られているのです。

集合分割と距離の可視化

最後に、集合分割の各レベルで部分集合内最小距離がどう増えていくかを可視化し、TCMの利得の源泉を直感的に確認します。

# 各レベルの部分集合内最小距離を計算
def min_distance(indices):
    pts = psk8[indices]
    dmin = np.inf
    for i in range(len(pts)):
        for j in range(i + 1, len(pts)):
            dmin = min(dmin, abs(pts[i] - pts[j]))
    return dmin

level0 = list(range(8))            # 全8点
level1 = [0, 2, 4, 6]              # B0
level2 = [0, 4]                    # C0
d0 = min_distance(level0)
d1 = min_distance(level1)
d2 = min_distance(level2)

fig, axes = plt.subplots(1, 3, figsize=(13, 4.3))
titles = [f'Level 0 (all 8)\n$\\Delta_0$={d0:.3f}',
          f'Level 1 (4 pts)\n$\\Delta_1$={d1:.3f}',
          f'Level 2 (2 pts)\n$\\Delta_2$={d2:.3f}']
subsets = [level0, level1, level2]
for ax, idxs, title in zip(axes, subsets, titles):
    theta = np.linspace(0, 2 * np.pi, 200)
    ax.plot(np.cos(theta), np.sin(theta), 'k:', alpha=0.3)
    ax.scatter(psk8.real, psk8.imag, c='lightgray', s=40)
    ax.scatter(psk8[idxs].real, psk8[idxs].imag, c='red', s=90)
    ax.set_title(title)
    ax.set_aspect('equal')
    ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

print(f"距離の増加: Δ0={d0:.3f} -> Δ1={d1:.3f} -> Δ2={d2:.3f}")
print(f"比 Δ1/Δ0={d1/d0:.3f}, Δ2/Δ0={d2/d0:.3f}")

3枚のコンスタレーション図から、集合分割が進む(赤い点が減る)ほど、選ばれた信号点同士が大きく離れていく様子が一目でわかります。レベル0では隣接点が $\Delta_0 \approx 0.765$ と近接していますが、レベル2では正反対の2点だけになり距離 $\Delta_2 = 2.0$ にまで広がります。この「分割が深いほど距離が大きい」という階層構造を、符号化ビットと非符号化ビットに賢く割り当てることが、TCMが帯域拡張なしで利得を生む仕組みそのものです。出力される距離の比からも、$\Delta_1$ が $\Delta_0$ の約1.85倍、$\Delta_2$ が約2.6倍に増えていることが定量的に確認できます。

まとめ

本記事では、トレリス符号化変調(TCM)について、その動機から具体的な設計・復号・性能評価までを解説しました。

  • 分離設計の限界: 符号化と変調を別々に設計すると、冗長ビットがシンボルレート(帯域)の増加を招きます。さらに、最適化すべき距離尺度はビット列のハミング距離ではなく、AWGN通信路で誤り率を決める信号点間のユークリッド距離です
  • TCMの核心: 冗長ビットをコンスタレーション拡大(QPSK→8-PSK等)で吸収し、帯域とビットレートを保ったまま符号化の余地を作ります。8-PSK化単体では隣接距離がQPSKの約0.541倍に縮み、最小2乗距離で約5.3 dB損をしますが、集合分割と符号設計でそれを埋めて正味の利得を出します
  • 集合分割: コンスタレーションを階層的に2分割し続け、部分集合内最小距離を $\Delta_0 < \Delta_1 < \Delta_2$ と段階的に増やします。符号化ビットを粗い分割(部分集合選択)に、非符号化ビットを最深の分割(平行枝)に割り当てるのが要点です
  • 自由距離と符号化利得: 性能を支配するのはユークリッド自由距離 $d_\text{free}$ です。4状態8-PSK TCMでは $d_\text{free}^2 = 4$、非符号化QPSKの $d_\text{min}^2 = 2$ に対し約3 dBの漸近符号化利得が得られ、状態数を増やせば4 dB超まで伸びます
  • 軟判定Viterbi復号: 受信点とのユークリッド2乗距離を枝メトリックとするViterbiアルゴリズムで最尤系列を求めます。Pythonシミュレーションでは、$E_b/N_0\approx4$ dBで両曲線が交差し、それ以上のSNRでTCMが優位となって、BER $=10^{-4}$ で約2.2 dB、さらに高SNRでは漸近利得3 dBへ近づく利得が再現されました

TCMは「変調と符号化を一体で設計する」という符号化変調(coded modulation)の出発点であり、その思想はBICM(ビットインターリーブ符号化変調)やLDPC・ターボ符号と高次変調を組み合わせた現代の規格へと受け継がれています。帯域も電力も貴重な宇宙通信・衛星通信の世界では、この「同じ帯域でより信頼性高く」という発想が今も中心的な役割を果たしています。

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