Skip to main content
IBM Quantum Platform

グローバーのアルゴリズム

所要時間の目安:Eagle r3 プロセッサで1分未満(注:これはあくまで目安です。 (実行時間は異なる場合があります。)


学習成果

このチュートリアルを完了すると、以下の内容を理解できるようになります:

  • 1つ以上の計算基底状態をマークするグローバー・オラクルを構築する方法
  • Qiskitのcircuitライブラリにある関 grover_operator() 数の使い方
  • 特定の問題に対して、Grover反復法の最適な反復回数を決定する方法
  • IBM Quantum のサンプラープリミティブを使用して、グローバーのアルゴリズムを実行する方法

前提条件

以下のトピックについて、あらかじめ理解しておくことをお勧めします:


背景

Amplitude Amplification(振幅増幅)は、汎用的な量子アルゴリズム、あるいはサブルーチンであり、いくつかの古典的アルゴリズムに対して2乗の速度向上を実現できる。 グローバーのアルゴリズムは、 非構造化検索問題においてこの計算速度の向上を初めて実証したものである。 グローバーの探索問題を定式化するには、1つ以上の計算基底状態を「探求対象の状態」としてマークするオラクル関数と、マークされた状態の振幅を増幅し、その結果として残りの状態を抑制する増幅回路が必要となる。

ここでは、Groverのオラクルを構築し、Qiskit回路ライブラリの grover_operator() Qiskit回路ライブラリからGroverの探索インスタンスを簡単にセットアップする方法を示す。 ランタイム Sampler プリミティブは、グローバー回路のシームレスな実行を可能にする。


要件

このチュートリアルを始める前に、以下のものがインストールされていることを確認してください:

  • Qiskit SDK v2.0 以降、 可視化機能を搭載
  • Qiskit Runtime v0.22 以降 (pip install qiskit-ibm-runtime)

セットアップ

# Built-in modules
import math

# Imports from Qiskit
from qiskit import QuantumCircuit
from qiskit.circuit.library import grover_operator, MCMTGate, ZGate
from qiskit.visualization import plot_distribution
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

# Imports from qiskit-ibm-runtime
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler


def grover_oracle(marked_states):
    """Build a Grover oracle for multiple marked states

    Here we assume all input marked states have the same number of bits

    Parameters:
        marked_states (str or list): Marked states of oracle

    Returns:
        QuantumCircuit: Quantum circuit representing Grover oracle
    """
    if not isinstance(marked_states, list):
        marked_states = [marked_states]
    # Compute the number of qubits in circuit
    num_qubits = len(marked_states[0])

    qc = QuantumCircuit(num_qubits)
    # Mark each target state in the input list
    for target in marked_states:
        # Flip target bit-string to match Qiskit bit-ordering
        rev_target = target[::-1]
        # Find the indices of all the '0' elements in bit-string
        zero_inds = [
            ind
            for ind in range(num_qubits)
            if rev_target.startswith("0", ind)
        ]
        # Add a multi-controlled Z-gate with pre- and post-applied X-gates (open-controls)
        # where the target bit-string has a '0' entry
        if zero_inds:
            qc.x(zero_inds)
        qc.compose(MCMTGate(ZGate(), num_qubits - 1, 1), inplace=True)
        if zero_inds:
            qc.x(zero_inds)
    return qc

小規模シミュレータの例

このセクションでは、実際の量子ハードウェアで同じ問題を処理する前に、ローカルシミュレータを用いて、小規模な環境でグローバーのアルゴリズムの各ステップを順を追って解説します。

ステップ1:古典的な入力を量子問題にマッピングする

グローバーのアルゴリズムでは、1つ以上の「マーク付き」計算基底状態を指定するオラクルが必要となる。「マーク付き」とは、位相が -1 である状態を指す。 制御Zゲート、あるいは NN 量子ビットに対するその多重制御の一般化は、 2N12^{N}-1 状態('1'* NN ビット列)を表す。 2進表現において基底状態を1つ以上の '0' 「1」で表すには、制御Zゲートの前後で対応する量子ビットにXゲートを適用する必要があり、これはその量子ビットに対してオープン制御を行うことと同等である。 以下のコードでは、ビット列表現によって定義された1つ以上の入力基底状態をマークするオラクルを定義します。 この MCMT ゲートは、マルチ制御Zゲートを実装するために使用されます。

特定のグローバーの実例

オラクル関数ができたので、グローバー探索の特定のインスタンスを定義することができる。 この例では、3量子ビットの計算空間で利用可能な8つの計算状態のうち、2つの計算状態をマークする:

marked_states = ["011", "100"]

oracle = grover_oracle(marked_states)
oracle.draw(output="mpl", style="iqp")

Output:

Output of the previous code cell

グローバー演算子

組み込みのQiskit grover_operator() は、オラクル回路を受け取り、オラクル回路そのものと、オラクルによってマークされた状態を増幅する回路からなる回路を返す。 ここでは、 decompose() 、演算子内のゲートを見るために回路を使用する:

grover_op = grover_operator(oracle)
grover_op.decompose().draw(output="mpl", style="iqp")

Output:

Output of the previous code cell

この grover_op 回路を繰り返し適用すると、マークされた状態が増幅され、回路からの出力分布において最も確率の高いビット列となる。 このようなアプリケーションの最適な数は、可能な計算状態の総数に対するマークされた状態の比率によって決まる:

optimal_num_iterations = math.floor(
    math.pi
    / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)

フルグローバー回路

完全なグローバー実験は、各クビットのハダマードゲートから始まる。すべての計算基底状態の偶数重ね合わせを作り、グローバー演算子(grover_op)を最適な回数繰り返す。 ここでは、 QuantumCircuit.power(INT) 方式を利用して、グローバー演算子を繰り返し適用する。

qc = QuantumCircuit(grover_op.num_qubits)
# Create even superposition of all basis states
qc.h(range(grover_op.num_qubits))
# Apply Grover operator the optimal number of times
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
# Measure all qubits
qc.measure_all()
qc.draw(output="mpl", style="iqp")

Output:

Output of the previous code cell

ステップ2:量子ハードウェア実行に向けた問題の最適化

小規模なシミュレーションを行うため、特定のハードウェアをターゲットとせずに回路をトランスパイルします。

pm = generate_preset_pass_manager(optimization_level=3)
circuit_isa = pm.run(qc)
circuit_isa.draw(output="mpl", idle_wires=False, style="iqp")

Output:

Output of the previous code cell

ステップ3: Qiskit primitivesを使用して実行する

振幅増幅は、プリミティブ SamplerV2 での実行に適したサンプリング問題である。 StatevectorSampler ここでは、局所シミュレーションのために から qiskit.primitives を使用します。

from qiskit.primitives import StatevectorSampler

sampler = StatevectorSampler()
result = sampler.run([circuit_isa], shots=10_000).result()
dist = result[0].data.meas.get_counts()

ステップ4:後処理を行い、結果を希望の古典形式で返す

plot_distribution(dist)

Output:

Output of the previous code cell

ハードウェアの例

手順 1~4

グローバーのアルゴリズムは、本質的にフォールトトレラントなアルゴリズムである。オラクルと拡散演算子の核心をなす多重制御Zゲートにより、2量子ビットのゲート深さは量子ビット数に応じて非常に急速に増加する(これについては次の節で示す)。 つまり、このアルゴリズムは、現在のノイズの多いハードウェア環境ではスケーラビリティに欠けるということです。 このため、より大規模な問題に取り組むのではなく、前述のシミュレータ例と同じ小規模な範囲でハードウェア実行を実証する。

# -------------------------Step 1-------------------------
marked_states = ["011", "100"]

oracle = grover_oracle(marked_states)
grover_op = grover_operator(oracle)

optimal_num_iterations = math.floor(
    math.pi
    / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))
)

qc = QuantumCircuit(grover_op.num_qubits)
qc.h(range(grover_op.num_qubits))
qc.compose(grover_op.power(optimal_num_iterations), inplace=True)
qc.measure_all()

# -------------------------Step 2-------------------------
service = QiskitRuntimeService()
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)

target = backend.target
pm = generate_preset_pass_manager(target=target, optimization_level=3)
circuit_isa = pm.run(qc)

# -------------------------Step 3-------------------------
sampler = Sampler(mode=backend)
sampler.options.default_shots = 10_000
sampler.options.environment.job_tags = ["TUT-GA"]
result = sampler.run([circuit_isa]).result()
dist = result[0].data.meas.get_counts()

# -------------------------Step 4-------------------------
plot_distribution(dist)

Output:

Output of the previous code cell

考察:2量子ビットゲートの深度スケーリング

グローバーのアルゴリズムが耐障害性アルゴリズムと見なされる主な理由は、量子ビットの数が増えるにつれて、回路の2量子ビットゲート深度が急速に増加することにある。 オラクル演算子と拡散演算子の両方の核心をなすマルチ制御Zゲートは、制御量子ビットの数に比例して指数関数的に増加する数の2量子ビットゲートに分解される。 さらに、グローバー反復の最適回数が O(2n)O(\sqrt{2^n}) の割合で増加するという事実も相まって、ノイズの多いハードウェアでは、2量子ビットの全体的な処理深度がすぐに現実的ではなくなってしまう。

以下では、量子ビット数を増やしながらグローバー回路を構築し、それらをトランスパイルして、その結果得られる2量子ビットゲートの深さをプロットし、このスケーリングを明らかにする。

import matplotlib.pyplot as plt

num_qubits_list = list(range(3, 10))
two_q_depths = []
backend = service.least_busy(
    operational=True, simulator=False, min_num_qubits=127
)
for n in num_qubits_list:
    # Mark a single state for simplicity
    marked = ["1" * n]
    oracle_n = grover_oracle(marked)
    grover_op_n = grover_operator(oracle_n)

    # Optimal number of iterations
    num_iters = math.floor(
        math.pi / (4 * math.asin(math.sqrt(len(marked) / 2**n)))
    )

    # Build the full Grover circuit
    qc_n = QuantumCircuit(n)
    qc_n.h(range(n))
    qc_n.compose(grover_op_n.power(num_iters), inplace=True)
    qc_n.measure_all()

    # Transpile to a basis gate set and count 2Q depth
    pm_n = generate_preset_pass_manager(backend=backend, optimization_level=3)
    qc_transpiled = pm_n.run(qc_n)

    # Compute depth restricted to 2-qubit operations
    depth_2q = qc_transpiled.depth(lambda x: x.operation.num_qubits == 2)

    two_q_depths.append(depth_2q)
    print(f"n={n}: optimal_iters={num_iters}, 2Q depth={depth_2q}")

# Plot
fig, ax = plt.subplots(figsize=(8, 5))
ax.plot(
    num_qubits_list,
    two_q_depths,
    "o-",
    linewidth=2,
    markersize=8,
    color="#6929C4",
)
ax.set_xlabel("Number of qubits", fontsize=13)
ax.set_ylabel("Two-qubit gate depth", fontsize=13)
ax.set_title("Grover's algorithm: 2Q depth scaling", fontsize=14)
ax.set_yscale("log")
ax.grid(True, alpha=0.3)
ax.set_xticks(num_qubits_list)
plt.tight_layout()
plt.show()

Output:

n=3: optimal_iters=2, 2Q depth=39
n=4: optimal_iters=3, 2Q depth=111
n=5: optimal_iters=4, 2Q depth=466
n=6: optimal_iters=6, 2Q depth=1646
n=7: optimal_iters=8, 2Q depth=3550
n=8: optimal_iters=12, 2Q depth=7989
n=9: optimal_iters=17, 2Q depth=14824
Output of the previous code cell

グラフが示すように、2量子ビットゲートの深さは量子ビット数とともに極めて急速に増加し、おおむね指数関数的な増加を示す。 このため、現在のノイズの多い量子ハードウェアでは、問題の規模が非常に小さい場合を除き、グローバーのアルゴリズムは実用的なものとは言えない。 このアルゴリズムは、エラー訂正技術によって深層回路を確実に実行できるようになる将来の耐故障性量子コンピュータにおいて、依然として重要な研究対象である。


次のステップ

推奨事項

この作品が興味深かった方は、以下の資料もご参考になるかもしれません:

このページは役に立ちましたか?
バグや誤字の報告、またはコンテンツの要求はGitHubで行ってください。