ホーム/ブログ/QAOA徹底解説:Max-Cutをステップ・バイ・ステップで解く
AlgorithmsQAOAQiskitPennyLane

QAOA徹底解説:Max-Cutをステップ・バイ・ステップで解く

量子近似最適化アルゴリズム(QAOA)をMax-Cutグラフ問題に適用する実践的な解説 — Qiskit、PennyLane、HLQuantumを使った完全なPythonコード付き。

FreeQuantumComputing
·· 11 min read

QAOA(量子近似最適化アルゴリズム)は、最も注目を集めている近未来の量子アルゴリズムの一つであり、それには正当な理由があります。組合せ最適化問題における量子優位性の真の候補であり、今日のNISQハードウェア上で動作し、しかも午後の数時間で実装できるほど手軽です。

このガイドでは、Max-Cutを例題として、QAOAの完全なパイプラインをゼロから構築します。読み終える頃には、QAOAが実際に何をするのか、どう実装するのか、そしていつ使う価値があるのかを理解できるでしょう。

Max-Cutとは何か

Max-Cutはグラフ分割問題です。与えられたグラフに対して、ノードを2つのグループ(0と1)に分け、グループ間をまたぐエッジの数を最大化します。一般にはNP困難であり、VLSIチップ設計、クラスタリング、ネットワーク解析などに現れ、量子ハードウェアにきれいにマッピングできます。

こちらはシンプルな4ノードのグラフです:

0 --- 1
|   \ |
3 --- 2

このグラフの最大カットは4です(エッジ0-1、1-2、2-3、3-0がすべてまたぐ)。私たちの目標は、これを達成するビット列 0101 または 1010 を見つけることです。

QAOAの発想

QAOAは変分ハイブリッドアルゴリズムです。2つのパラメータ化された演算を交互に適用します:

  1. 問題ユニタリ U_C(γ) — コスト関数(エッジのまたぎ)を位相キックバックとしてエンコードする
  2. ミキサーユニタリ U_B(β) — 解の間を混ぜ合わせる(X回転)

これらの交互のユニタリを p 層重ねることで、QAOAは測定すると良い解が得られやすい量子状態を準備します。古典最適化器が 2p 個の角度 (γ, β) を調整し、期待カット値を最大化します。

PennyLaneでQAOAを構築する

import pennylane as qml
import numpy as np
from scipy.optimize import minimize

# Graph edges
edges = [(0, 1), (1, 2), (2, 3), (3, 0)]
n_qubits = 4

dev = qml.device("default.qubit", wires=n_qubits)

def cost_unitary(gamma):
    """Encode the Max-Cut cost into phases."""
    for u, v in edges:
        qml.CNOT(wires=[u, v])
        qml.RZ(2 * gamma, wires=v)
        qml.CNOT(wires=[u, v])

def mixer_unitary(beta):
    """Apply X-rotation mixer to all qubits."""
    for i in range(n_qubits):
        qml.RX(2 * beta, wires=i)

@qml.qnode(dev)
def qaoa_circuit(params, p=1):
    gammas = params[:p]
    betas = params[p:]
    # Start in uniform superposition
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
    # Alternate p layers
    for layer in range(p):
        cost_unitary(gammas[layer])
        mixer_unitary(betas[layer])
    return qml.probs(wires=range(n_qubits))

def expected_cut(params, p=1):
    """Classical cost function: expected number of cut edges."""
    probs = qaoa_circuit(params, p)
    total = 0.0
    for bitstring_idx, prob in enumerate(probs):
        bits = format(bitstring_idx, f'0{n_qubits}b')
        cut = sum(1 for u, v in edges if bits[u] != bits[v])
        total += prob * cut
    return -total  # Minimize negative cut

# Optimize p=1 QAOA
p = 1
init_params = np.random.uniform(0, np.pi, 2 * p)
result = minimize(expected_cut, init_params, method='COBYLA',
                  options={'maxiter': 200})

print(f"Optimized params: {result.x}")
print(f"Expected cut: {-result.fun:.3f} / 4.0 maximum")

これを実行すると、p=1 で典型的には expected_cut ≈ 3.5 に達します — 最大カットの87.5%です。

最良解のサンプリング

最適化の後、回路をサンプリングして最も可能性の高い良い解を見つけます:

@qml.qnode(dev)
def sample_circuit(params, p=1, shots=1000):
    gammas = params[:p]
    betas = params[p:]
    for i in range(n_qubits):
        qml.Hadamard(wires=i)
    for layer in range(p):
        cost_unitary(gammas[layer])
        mixer_unitary(betas[layer])
    return qml.sample(wires=range(n_qubits))

samples = sample_circuit(result.x, shots=1000)

# Count cut values for each bitstring
from collections import Counter
cut_counts = Counter()
for bits in samples:
    key = ''.join(str(b) for b in bits)
    cut = sum(1 for u, v in edges if bits[u] != bits[v])
    cut_counts[(key, cut)] += 1

# Show top 5 most frequent bitstrings
for (bits, cut), count in sorted(cut_counts.items(), key=lambda x: -x[1])[:5]:
    print(f"{bits}  cut={cut}  frequency={count/10:.1f}%")

期待される出力:

0101  cut=4  frequency=28.3%
1010  cut=4  frequency=27.1%
0110  cut=3  frequency=8.4%
...

最適解(01011010)が支配的になります — QAOAがそれらの確率を増幅したのです。

同じ問題をHLQuantumで

HLQuantumの組み込みQAOAを使えば、回路の構築を完全に省略できます:

import hlquantum as hlq

graph = [(0, 1), (1, 2), (2, 3), (3, 0)]

result = hlq.algorithms.qaoa(
    graph=graph,
    problem='max_cut',
    p=2,
    backend='pennylane',
    shots=2000,
)

print(result.best_solution)   # '0101' or '1010'
print(result.cut_value)       # 4
print(result.approximation_ratio)  # ~0.96 for p=2

単一のフラグでバックエンドを切り替えられます:

# Run on Qiskit Aer
result_qiskit = hlq.algorithms.qaoa(graph=graph, problem='max_cut', p=2, backend='qiskit')

# Run on real IonQ hardware (if you have credits)
result_ionq = hlq.algorithms.qaoa(graph=graph, problem='max_cut', p=2,
                                   backend='ionq', device='aria-1')

より良い結果のためにpを増やす

QAOAの近似比は深さ p とともに向上します。p→∞ では、QAOAは問題を厳密に解きます。実際には:

pMax-Cutでの典型的な近似比必要なゲート数
1~0.752×edges + 4n
2~0.864×edges + 8n
3~0.916×edges + 12n
5~0.9510×edges + 20n

トレードオフとして、深い回路は実ハードウェア上でより多くのノイズを蓄積します。シミュレータ上では p=3 以上を使いましょう。NISQ QPU上では、p=1 または p=2 が通常の実用的な限界です。

QAOAはいつ使う価値があるのか

現在有用な用途:

  • NISQデバイスのベンチマーク(QAOAは標準的なベンチマーク回路)
  • 変分量子アルゴリズムに関する学術研究
  • シミュレータ上の小規模グラフ(20ノード未満)

まだ有用ではない用途:

  • 実ハードウェア上での大規模な本番最適化 — 古典的ヒューリスティクス(シミュレーテッドアニーリング、タブーサーチ)は実用的な問題において依然としてQAOAを上回る
  • 大規模グラフ — 回路の深さはグラフの密度とともに増大する

その可能性: 高深度回路を扱える将来の誤り耐性ハードウェア上では、大きな p でのQAOAが密なNP困難最適化に対して真の高速化をもたらしうるかもしれません。理論的基盤は堅固ですが、ハードウェアはまだそこに達していません。

次のステップ

  • PennyLane SDKガイド — より多くの変分アルゴリズムのためにPennyLaneをセットアップする
  • VQEガイド — 量子化学のための姉妹アルゴリズム
  • HLQuantum — 一つのAPIで任意のバックエンド上でQAOAを実行する