QAOA / 制約付き QAOA

QAOA (Quantum Approximate Optimization Algorithm) は量子コンピュータによる量子回路の実行と古典最適化を交互に繰り返す量子古典ハイブリッドアルゴリズムです。イジング変数で \(N\) 次多項式の目的関数の最適化問題を求解できます。

量子コンピュータを利用するクライアントでアルゴリズムに QAOA を指定することで利用します。 このページでは QAOA のパラメータの設定、求解結果の詳細な情報の取得、活用方法について説明します。

アルゴリズムの詳細はQAOA アルゴリズム及び制約付き QAOA アルゴリズムを参照して下さい。

以下は QulacsClient を用いて QAOA を実行し、求解の実行結果を取得する例です。

from amplify import QAOA, QulacsClient, VariableGenerator, Model, equal_to, solve

# 決定変数の配列を生成
gen = VariableGenerator()
q = gen.array("Binary", 5)

# 目的関数と制約条件を作成
objective = q[0] * q[1] - q[2]
constraint = equal_to(q[0] + q[1] + q[2], 1)

# モデルの定義
model = Model(objective, constraint)

client = QulacsClient(QAOA)
result = solve(model, client)

実行結果の評価

量子コンピュータソルバーにおいては、 solve() 関数の実行結果である amplify.Result クラスで取得できる、 amplify.Result.response_time 及び amplify.Result.execution_time はソルバーとの通信・実行時間ではなく、量子コンピュータ実機/シミュレータとの通信時間・回路の実行時間に対応します。

QAOA では量子状態を shots 回測定してイジング列をサンプリングすることを繰り返します。 そのため、 amplify.Result.response_time 及び amplify.Result.execution_time は対応する時間の全てのサンプリングにおける合計になります。

result = solve(model, client)

result.response_time      # QPU との通信時間の合計
result.execution_time     # QPU での実行時間の合計

QAOAにおいて最適なパラメータで最も頻繁に観測されたイジング列が最適解の有力な候補です。 しかしながら観測回数の最も多いイジング列が最適とは限らないため、最適化過程の全ての測定においてサンプリングされたイジング列すべてを記録します。 その中から目的関数の値が最も小さくなるイジング列が amplify.Result.best で取得できます。

QAOA 固有の結果

amplify.Result.client_result の型はアルゴリズムの型に依存し、アルゴリズム毎に異なる型で実行過程の情報を含む詳細な求解結果を返します。QAOAではamplify.QAOA.Resultが対応します。

QAOA Result 属性一覧

属性

説明

durations

QAOADurations

実行時間の内訳

num_execution

int

コスト関数の総評価回数 (古典最適化のイテレーション数)

optimized_cost

float

最適化で得られた最良のコスト関数値 \(C(\boldsymbol{\theta}^{\textup{opt}})\)

optimized_parameters

tuple[float, ...]

最適化で得られた最良のパラメータ \(\boldsymbol{\theta}^{\textup{opt}}\)

optimized_counts

list[tuple[list[int], int]]

最良パラメータでの測定結果 (イジング列, 出現回数) のリスト

history

Sequence[QAOAHistoryItem]

パラメータ最適化の各ステップの履歴

QAOADurations (実行時間の内訳)

durations は QAOA の各フェーズにかかった時間の内訳を提供します。

d = result.client_result.durations

result.total_time            # amplify.solveにかかった時間
d.total_time                 # QAOA 全体の経過時間
d.total_response_time        # バックエンド との通信時間の合計
d.total_execution_time       # バックエンド での実行時間の合計
d.classical_processing_time  # 古典最適化にかかった時間 (= total_time - total_response_time)

以下の図は、QAOA 実行中の各メトリクスの関係を示しています。古典最適化のイテレーション毎に量子コンピュータでのサンプリングを実行して解を抽出します。

../../_images/sampling_timing.ja.svg
  • total_time: パラメータ最適化と最終測定を含む全体の経過時間

  • total_response_time: 全ステップにおける 量子コンピュータ実機/シミュレータとの通信にかかった時間の合計。キューの待ち時間などを含みます

  • total_execution_time: 量子コンピュータ実機/シミュレータが実際に回路を実行していた時間の合計

  • classical_processing_time: 古典最適化アルゴリズム (scipy.optimize.minimize 等) が走っていた時間の合計。total_time - total_response_time で算出されます

history (QAOA 最適化の履歴)

history はパラメータ最適化の各ステップ (QAOAHistoryItem) のリストです。 古典最適化でコスト関数を評価するたびに 1 エントリが追加されます。コスト関数の評価のたびに量子回路が実行されます。

for step in result.client_result.history:
    print(step.timestamp)           # QAOAの開始時刻からのこのステップの完了までの経過時間
    print(step.parameters)          # ステップのパラメータ値 θ
    print(step.objective)           # コスト関数値 C(θ)
    print(step.counts)              # 測定結果 (イジング列, 出現回数)
    print(step.sampling_durations)  # サンプリングにかかった時間
    print(step.sampling_meta)       # バックエンド固有のメタ情報

sampling_meta (バックエンド固有のメタ情報)

sampling_meta の内容はバックエンドによって異なります。

Qiskit ベース (AerClient / IBMClient) の場合:

meta = result.client_result.history[0].sampling_meta
meta.job_id     # ジョブ ID
meta.circuit    # 実行した Qiskit 回路オブジェクト

詳細は各量子コンピュータクライアントの詳細を参照して下さい

optimized_counts (最良パラメータでの測定結果)

optimized_counts は最良パラメータ \(\boldsymbol{\theta}^{\textup{opt}}\) での測定結果です。各要素は (イジング列, 出現回数) のタプルです。出現回数が多いイジング列ほど最適解の有力な候補です。

for ising_seq, freq in sorted(result.client_result.optimized_counts, key=lambda x: x[1], reverse=True):
    print(f"イジング列: {ising_seq}, 出現回数: {freq}")
optimized_counts のイジング列を変数に変換する

optimized_counts のイジング列は、内部的な量子ビットの測定値をイジング値 (\(\{-1, 1\}\)) で表したものです。amplify.Result.intermediateamplify.Result.ModelConversion.mapping を使って元の変数配列に変換できます。

result = solve(model, client)
sorted_counts = sorted(result.client_result.optimized_counts, key=lambda x: x[1], reverse=True)
for sol, freq in sorted_counts[:5]:
    values = q.substitute(
        {
            k: p.substitute(
                {v: sol[v.id] for v in result.intermediate.model.get_variables()}
            )
            for k, p in result.intermediate.mapping.items()
        }
    )
    print(f"解: {values}, 出現回数: {freq}")
optimized_counts の分布と最適化の質

optimized_counts の分布は、QAOA のパラメータ最適化がどの程度うまく機能したかの指標になります。

  • 測定結果が少数のイジング列に集中している場合: 量子状態が特定の解に収束しており、最適化が成功しています。最頻のイジング列が最適解の有力な候補です。

  • 測定結果が多数のイジング列に分散している場合: 量子状態が広い状態空間に広がっており、最適化が十分に進んでいません。Ansatz回路の深さ を増やす、古典最適化 の調整、あるいは QAOA の種別 の変更を検討してください。

bc = result.client_result.optimized_counts
total = sum(count for _, count in bc)
top_freq = max(count for _, count in bc)
print(f"ユニークなイジング列の数: {len(bc)} / {total} shots")
print(f"最頻イジング列の出現回数: {top_freq} ({100 * top_freq / total:.1f}%)")

パラメータの設定

指定したアルゴリズムのパラメータは client.parameters で設定します。 全てのパラメータにデフォルトの設定が与えられているため、変更なしで動作します。

パラメータ

デフォルト

説明

reps

int

10

Ansatz 回路の深さ (層数 \(p\))。大きいほど表現力が増しますが、回路が深くなります

shots

int

1024

測定回数。大きいほど統計精度が上がりますが、実行時間が増えます

qaoa_type

QAOAType

AUTO

QAOA の種別。回路と扱える次数が変わります

minimize

MinimizeProtocol

ScipyMinimize()

古典最適化手法。デフォルトではscipyのCOBYLAを利用します

QAOA の性能は、Ansatz 回路の構成や古典最適化の設定に大きく影響されます。ここでは、各パラメータの役割と調整のポイントを説明します。

Ansatz 回路の深さ (reps)

reps は Ansatz 回路の層数 \(p\) を指定します。

  • 大きいほど量子状態の表現力が増し、理論上はより良い解に近づけます。

  • ただし、回路が深くなると最適化するパラメータ数は reps に比例して増加するため、古典最適化の収束にも影響します。

  • また、実機ではノイズの影響を受けやすくなります。

  • デフォルト: 10

設定例:

client.parameters.reps = 5

測定回数 (shots)

shots はパラメータ最適化の各ステップおよび最終的な解抽出で行う測定回数です。

  • 大きいほどコスト関数の推定精度が上がり、安定した結果が得られます。

  • ただし、1 回の最適化ステップあたりの実行時間, またはQPUでの実行コストを増加させます。

  • デフォルト: 1024

設定例:

client.parameters.shots = 2048

QAOA の種別 (qaoa_type)

qaoa_type は使用する QAOA Ansatz の種類を指定します。

QAOAType

目的関数の受理次数

説明

AUTO (デフォルト)

Ising: 任意次数

自動選択。制約の有無に応じて ORIGINAL または NHOT を使い分けます

ORIGINAL

Ising: 任意次数

標準的な QAOA Ansatz を使用します

NHOT

Ising: 任意次数

N-HOT 制約(イジング変数から成る変数列に対して、ちょうど \(n\) 個の変数だけが \(-1\) になる制約)を考慮した Ansatz を使用します

AUTO_QUADRATIC / ORIGINAL_QUADRATIC / NHOT_QUADRATIC

Ising: 2次

対応する各 QAOA を2 次以下の次数でのみ扱います

注釈

_QUADRATIC が付くタイプでは、Amplify SDK が高次の目的関数を 2 次以下に自動で次数下げします。次数下げにより補助変数が追加され、量子ビット数が増える場合があります。

アルゴリズムの詳細は QAOA および 制約付き QAOA を参照してください。

設定例:

from amplify import QAOAType

client.parameters.qaoa_type = QAOAType.AUTO  # デフォルト

古典最適化手法 (minimize)

minimize はパラメータ最適化に使用する古典最適化手法を指定します。デフォルトでは ScipyMinimize が使用されます。

ScipyMinimize

ScipyMinimizescipy.optimize.minimize をラップした最適化手法です。 scipy.optimize.minimize のパラメータをオブジェクトのプロパティから渡せます。

各パラメータの詳細はSciPyのドキュメントを参照してください。

パラメータ

デフォルト

説明

method

str

"COBYLA"

古典最適化のアルゴリズム名

tol

float | None

None

収束判定の許容誤差。None で scipy のデフォルト

x0

list[float] | None

None

パラメータの初期値。None でランダム初期化

options

dict | None

None

scipy に渡す追加オプション (maxiter, disp など)

Tip

method の選択は問題や状況に依存しますが、勾配を使わない "COBYLA" は量子コンピュータ上の最適化でよく使われます。

設定例:

from amplify import ScipyMinimize

client.parameters.minimize.method = "COBYLA"     # 最適化アルゴリズム
client.parameters.minimize.tol = None            # 収束判定の許容誤差
client.parameters.minimize.x0 = None             # パラメータの初期値 (None でランダム)
client.parameters.minimize.options = {"maxiter": 100, "disp": True}

NoOpMinimize (最適化をスキップ)

NoOpMinimize は古典最適化を行わず、指定されたパラメータでそのまま測定を行います。すでに最適化済みのパラメータで再測定したい場合に使用します。最適化済みのパラメータを再利用して shots を増やした再測定を行うことで、統計精度を向上できます。

設定例:

from amplify import NoOpMinimize

# 1回目: 通常の QAOA (パラメータ最適化 + 測定)
result = solve(model, client)
best_params = result.client_result.optimized_parameters

# 2回目: 最適化済みパラメータで再測定 (shots を増やして精度向上)
client.parameters.shots = 4096
client.parameters.minimize = NoOpMinimize(best_params)
result2 = solve(model, client)

この場合、result2.client_result.num_execution1 になり、測定が一度だけ行われていることが確認できます。