Skip to main content
IBM Quantum Platform

Q-CTRLの Qiskit Functions を用いた量子位相推定

使用時間の見積もり:Heron r2 プロセッサーで40秒。 (注:これはあくまでも目安です。 実行時間は異なるかもしれない)。


背景

量子位相推定(QPE)は、量子コンピューティングにおける基礎的なアルゴリズムであり、ショールのアルゴリズム、量子化学の基底状態エネルギー推定、固有値問題など、多くの重要なアプリケーションの基礎となっている。 QPEは、ユニタリー作用素の固有状態に関連する位相 φ\varphi を推定します

Uφ=e2πiφφ,U \lvert \varphi \rangle = e^{2\pi i \varphi} \lvert \varphi \rangle,

そして、 mm 計数量子ビット [1] を使用して、 ϵ=O(1/2m)\epsilon = O(1/2^m) の精度で決定する。 これらの量子ビットを重ね合わせた状態で準備し、制御された UU のべき乗を適用し、逆量子フーリエ変換(QFT)を用いて位相を2進エンコードされた測定結果に抽出することで、QPEは、その2進分数が φ\varphi に近似するビット列をピークとする確率分布を生成する。理想的なケースでは、最も可能性の高い測定結果は位相の2進展開に直接対応し、他の結果の確率は計数量子ビットの数に応じて急激に減少します。 しかし、ディープQPE回路をハードウェア上で動作させるには課題がある。多数の量子ビットとエンタングル演算により、アルゴリズムはデコヒーレンスとゲート・エラーの影響を強く受ける。 その結果、ビット列の分布が広がってシフトし、真の固有位相を覆い隠してしまう。 その結果、最も高い確率を持つビット列は、 φ\varphi の正しい2進展開に対応しなくなる可能性がある。

このチュートリアルでは、Qiskit 関数として提供される Q-CTRL の Fire Opal エラー抑制およびパフォーマンス管理ツールを使用して、QPE アルゴリズムの実装を紹介します ( Fire Opal のドキュメントを参照)。 Fire Opalは、動的デカップリング、量子ビットレイアウトの改善、エラー抑制技術などの高度な最適化を自動的に適用し、より忠実な結果をもたらします。 これらの改善により、ハードウェアのビット列分布がノイズのないシミュレーションで得られた分布に近づき、ノイズの影響下でも正しい固有位相を確実に特定できるようになりました。


要件

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

  • Qiskit SDK v1.4 またはそれ以降、 可視化サポート付き
  • Qiskit Runtime v0.40 またはそれ以降 (pip install qiskit-ibm-runtime)
  • Qiskit Functions Catalog v0.9.0 (pip install qiskit-ibm-catalog)
  • Fire Opal SDK v9.0.2 またはそれ以降 (pip install fire-opal)
  • Q-CTRL Visualizer v8.0.2 またはそれ以降 (pip install qctrl-visualizer)

セットアップ

まず、 IBM Quantum のAPIキーを使用して認証を行ってください。 次に、次のようにQiskit関数を選択します。 (このコードは、 アカウントがすでにローカル環境に保存されていることを前提としています。)

from qiskit import QuantumCircuit

import numpy as np
import matplotlib.pyplot as plt
import qiskit
from qiskit import qasm2
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import qctrlvisualizer as qv
from qiskit_ibm_catalog import QiskitFunctionsCatalog

plt.style.use(qv.get_qctrl_style())
catalog = QiskitFunctionsCatalog(channel="ibm_quantum_platform")

# Access Function
perf_mgmt = catalog.load("q-ctrl/performance-management")

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

このチュートリアルでは、既知の単一量子ビットユニタリーの固有位相を回復するためのQPEを説明します。 推定したい位相のユニタリーは、ターゲット量子ビットに適用される単一量子ビット位相ゲートである:

U(θ)=(100eiθ)=eiθ1 ⁣1.U(\theta)= \begin{pmatrix} 1 & 0\\[2pt] 0 & e^{i\theta} \end{pmatrix} = e^{i\theta\,|1\rangle\!\langle 1|}.

その固有状態 ψ=1|\psi\rangle=|1\rangle を用意する。 1|1\rangle は固有値 eiθe^{i\theta} を持つ U(θ)U(\theta) の固有ベクトルなので、推定すべき固有位相は次のようになる:

φ=θ2π(mod1)\varphi = \frac{\theta}{2\pi} \pmod{1}

θ=162π\theta=\tfrac{1}{6}\cdot 2\pi を設定するので、基底真理位相は φ=1/6\varphi=1/6 となる。QPE回路は、角度 θ2k\theta\cdot2^k で制御された位相回転を適用することにより、制御された累乗 U2kU^{2^k} を実装し、次に計数レジスタに逆QFTを適用して測定する。 結果のビット列は、 1/61/6 の2進表現に集中する。

この回路は、 mm (推定精度を設定するための)計数量子ビットと1つのターゲット量子ビットを使用する。 量子フーリエ変換(QFT)とその逆変換、固有位相の10進数と2進数の間のマッピングを行うユーティリティ関数、シミュレーションとハードウェアの結果を比較するために生のカウントを確率に正規化するヘルパーなどです。

def inverse_quantum_fourier_transform(quantum_circuit, number_of_qubits):
    """
    Apply an inverse Quantum Fourier Transform the first `number_of_qubits` qubits in the
    `quantum_circuit`.
    """
    for qubit in range(number_of_qubits // 2):
        quantum_circuit.swap(qubit, number_of_qubits - qubit - 1)
    for j in range(number_of_qubits):
        for m in range(j):
            quantum_circuit.cp(-np.pi / float(2 ** (j - m)), m, j)
        quantum_circuit.h(j)
    return quantum_circuit
def bitstring_count_to_probabilities(data, shot_count):
    """
    This function turns an unsorted dictionary of bitstring counts into a sorted dictionary
    of probabilities.
    """
    # Turn the bitstring counts into probabilities.
    probabilities = {
        bitstring: bitstring_count / shot_count
        for bitstring, bitstring_count in data.items()
    }

    sorted_probabilities = dict(
        sorted(probabilities.items(), key=lambda x: x[1], reverse=True)
    )

    return sorted_probabilities

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

QPE回路は、計数量子ビットを重ね合わせた状態で準備し、制御された位相回転を適用して目的の固有位相をエンコードし、測定前に逆QFTで仕上げることで構築する。

def quantum_phase_estimation_benchmark_circuit(
    number_of_counting_qubits, phase
):
    """
    Create the circuit for quantum phase estimation.

    Parameters
    ----------
    number_of_counting_qubits : The number of qubits in the circuit.
    phase : The desired phase.

    Returns
    -------
    QuantumCircuit
        The quantum phase estimation circuit for `number_of_counting_qubits` qubits.
    """
    qc = QuantumCircuit(
        number_of_counting_qubits + 1, number_of_counting_qubits
    )
    target = number_of_counting_qubits

    # |1> eigenstate for the single-qubit phase gate
    qc.x(target)

    # Hadamards on counting register
    for q in range(number_of_counting_qubits):
        qc.h(q)

    # ONE controlled phase per counting qubit: cp(phase * 2**k)
    for k in range(number_of_counting_qubits):
        qc.cp(phase * (1 << k), k, target)

    qc.barrier()

    # Inverse QFT on counting register
    inverse_quantum_fourier_transform(qc, number_of_counting_qubits)

    qc.barrier()
    for q in range(number_of_counting_qubits):
        qc.measure(q, q)
    return qc

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

実験のショット数と量子ビット数を設定し、 mm 2進数を使ってターゲット位相 φ=1/6\varphi = 1/6 をエンコードする。 これらのパラメータを使って、シミュレーション、デフォルトのハードウェア、Fire Opalで強化されたバックエンドで実行されるQPE回路を構築する。

shot_count = 10000
num_qubits = 35
phase = (1 / 6) * 2 * np.pi
circuits_quantum_phase_estimation = (
    quantum_phase_estimation_benchmark_circuit(
        number_of_counting_qubits=num_qubits, phase=phase
    )
)

MPSシミュレーションを実行する

まず、 matrix_product_state シミュレータを使って参照分布を生成し、後でハードウェアの結果と比較するために、カウント数を正規化された確率に変換する。

# Run the algorithm on the IBM Aer simulator.
aer_simulator = AerSimulator(method="matrix_product_state")

# Transpile the circuits for the simulator.
transpiled_circuits = qiskit.transpile(
    circuits_quantum_phase_estimation, aer_simulator
)
simulated_result = (
    aer_simulator.run(transpiled_circuits, shots=shot_count)
    .result()
    .get_counts()
)
simulated_result_probabilities = []

simulated_result_probabilities.append(
    bitstring_count_to_probabilities(
        simulated_result,
        shot_count=shot_count,
    )
)

ハードウェア上で実行する

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuits = pm.run(circuits_quantum_phase_estimation)
# Run the algorithm with IBM default.
sampler = Sampler(backend)

# Run all circuits using IBM Quantum Compute Service.
ibm_default_job = sampler.run([isa_circuits], shots=shot_count)

Fire Opal搭載ハードウェアで動作する

# Run the circuit using Sampler
fire_opal_job = perf_mgmt.run(
    primitive="sampler",
    pubs=[qasm2.dumps(circuits_quantum_phase_estimation)],
    backend_name=backend.name,
    options={"default_shots": shot_count},
)

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

# Retrieve results.
ibm_default_result = ibm_default_job.result()
ibm_default_probabilities = []

for idx, pub_result in enumerate(ibm_default_result):
    ibm_default_probabilities.append(
        bitstring_count_to_probabilities(
            pub_result.data.c0.get_counts(),
            shot_count=shot_count,
        )
    )
fire_opal_result = fire_opal_job.result()

fire_opal_probabilities = []
for idx, pub_result in enumerate(fire_opal_result):
    fire_opal_probabilities.append(
        bitstring_count_to_probabilities(
            pub_result.data.c0.get_counts(),
            shot_count=shot_count,
        )
    )
data = {
    "simulation": simulated_result_probabilities,
    "default": ibm_default_probabilities,
    "fire_opal": fire_opal_probabilities,
}
def plot_distributions(
    data,
    number_of_counting_qubits,
    top_k=None,
    by="prob",
    shot_count=None,
):
    def nrm(d):
        s = sum(d.values())
        return {k: (v / s if s else 0.0) for k, v in d.items()}

    def as_float(d):
        return {k: float(v) for k, v in d.items()}

    def to_space(d):
        if by == "prob":
            return nrm(as_float(d))
        else:
            if shot_count and 0.99 <= sum(d.values()) <= 1.01:
                return {
                    k: v * float(shot_count) for k, v in as_float(d).items()
                }
            else:
                return as_float(d)

    def topk(d, k):
        items = sorted(d.items(), key=lambda kv: kv[1], reverse=True)
        return items[: (k or len(d))]

    phase = "1/6"

    sim = to_space(data["simulation"])
    dft = to_space(data["default"])
    qct = to_space(data["fire_opal"])

    correct = max(sim, key=sim.get) if sim else None
    print("Correct result:", correct)

    sim_items = topk(sim, top_k)
    dft_items = topk(dft, top_k)
    qct_items = topk(qct, top_k)

    sim_keys, y_sim = zip(*sim_items) if sim_items else ([], [])
    dft_keys, y_dft = zip(*dft_items) if dft_items else ([], [])
    qct_keys, y_qct = zip(*qct_items) if qct_items else ([], [])

    fig, axes = plt.subplots(3, 1, layout="constrained")
    ylab = "Probabilities"

    def panel(ax, keys, ys, title, color):
        x = np.arange(len(keys))
        bars = ax.bar(x, ys, color=color)
        ax.set_title(title)
        ax.set_ylabel(ylab)
        ax.set_xticks(x)
        ax.set_xticklabels(keys, rotation=90)
        ax.set_xlabel("Bitstrings")
        if correct in keys:
            i = keys.index(correct)
            bars[i].set_edgecolor("black")
            bars[i].set_linewidth(2)
        return max(ys, default=0.0)

    c_sim, c_dft, c_qct = (
        qv.QCTRL_STYLE_COLORS[5],
        qv.QCTRL_STYLE_COLORS[1],
        qv.QCTRL_STYLE_COLORS[0],
    )
    m1 = panel(axes[0], list(sim_keys), list(y_sim), "Simulation", c_sim)
    m2 = panel(axes[1], list(dft_keys), list(y_dft), "Default", c_dft)
    m3 = panel(axes[2], list(qct_keys), list(y_qct), "Q-CTRL", c_qct)

    for ax, m in zip(axes, (m1, m2, m3)):
        ax.set_ylim(0, 1.05 * (m or 1.0))

    for ax in axes:
        ax.label_outer()
    fig.suptitle(
        rf"{number_of_counting_qubits} counting qubits, $2\pi\varphi$={phase}"
    )
    fig.set_size_inches(20, 10)
    plt.show()
experiment_index = 0
phase_index = 0

distributions = {
    "simulation": data["simulation"][phase_index],
    "default": data["default"][phase_index],
    "fire_opal": data["fire_opal"][phase_index],
}

plot_distributions(
    distributions, num_qubits, top_k=100, by="prob", shot_count=shot_count
)

Output:

Correct result: 00101010101010101010101010101010101
Output of the previous code cell

シミュレーションは、正しい固有相のベースラインを設定する。 デフォルトのハードウェア動作では、ノイズが多くの不正確なビット列に確率を分散させるため、この結果が不明瞭になる。 Q-CTRL Performance Managementを使用することで、分布がよりシャープになり、正しい結果が回復されるため、この規模でも信頼性の高いQPEが可能になります。


参照

[1] 第7講位相推定と因数分解. IBM Quantum Learning - 量子アルゴリズムの基礎 2025年10月3日取得。


チュートリアル調査

このチュートリアルに関するフィードバックをお寄せください。 あなたの洞察は、私たちのコンテンツの提供とユーザーエクスペリエンスを向上させるのに役立ちます。

アンケートへのリンク

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