Skip to main content
IBM Quantum Platform

動的回路を用いた長距離エンタングルメント

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


学習成果

このチュートリアルを完了すると、以下のことを習得できます:

  • 回路中間測定(MCM)と古典的フィードフォワードを用いた動的回路による長距離CNOTゲートの実装方法;
  • ユニタリーSWAPに基づく手法を用いて、同等のゲートを実装する方法;
  • 量子ビット間の距離をパラメータとしてゲート忠実度を測定し、両方のアプローチを比較する方法。

前提条件

このチュートリアルを進める前に、以下の内容についてあらかじめ理解しておいていただくことをお勧めします:


背景

離れた量子ビット間の長距離エンタングルメントは、接続性が限られたデバイスでは困難である。 このチュートリアルでは、測定ベースのプロトコルを使用して長距離制御X(LRCX)ゲートを実装することにより、動的回路がこのようなエンタングルメントを生成する方法を示します。

1における Elisa Bäumerらのアプローチを踏襲し、この方法では回路途中の測定とフィードフォワードを用いて、量子ビットの分離に関係なく一定の深さのゲートを実現している。 中間的なベル・ペアを作り、それぞれのペアから1量子ビットを測定し、古典的な条件付きゲートを適用してエンタングルメントをデバイス全体に伝播させる。 これにより、長いSWAPチェーンが回避され、回路の奥行きと2量子ビットゲートエラーへの露出の両方が低減される。

本ノートでは、 IBM Quantum ハードウェア向けにプロトコルを適応させ、制御点とターゲット点の距離に応じた性能をベンチマークし、ユニタリーSWAPに基づくベースラインと比較する。


要件

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

  • Qiskit SDK v2.0 またはそれ以降、 可視化サポート付き
  • Qiskit Runtime v0.37 以降 (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 以降 (pip install qiskit-aer)

セットアップ

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.visualization import plot_circuit_layout
from qiskit_ibm_runtime import (
    QiskitRuntimeService,
    Batch,
    SamplerV2 as Sampler,
)
import matplotlib.pyplot as plt
import numpy as np

小規模シミュレータの例

実際のQPU上で実行する前に、ノイズのないシミュレータ上で、動的回路とユニタリ回路の両方が理想的なベル状態を生成することを検証する。 IBM Quantum を Sampler 、バックエンドモード AerSimulator として、距離を6という小さな値に設定して使用しています。

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

ここでは、以下に示す動的回路の構成に従い、離れた2つの量子ビット間に長距離CNOTゲートを実装する(参考文献の図 1a を改変)。 1 ). 重要な考え方は、 0|0\rangle に初期化されたアンシラ量子ビットの「バス」を用いて、長距離ゲートテレポーテーションを仲介することである。

長距離CNOT回路

図に示すように、このプロセスは次のように機能する:

  1. コントロール量子ビットとターゲット量子ビットを中間アンシラを介して接続するベルペアのチェーンを準備する。
  2. 隣接するエンタングルされていない量子ビット間でベル測定を行い、コントロールとターゲットがベルペアを共有するまで段階的にエンタングルメントを入れ替える。
  3. このベル・ペアをゲート・テレポーテーションに使い、ローカルなCNOTを一定の深さの決定論的な長距離CNOTに変える。

このアプローチでは、長いSWAPチェーンを一定の深さのプロトコルに置き換えることで、2量子ビットのゲート・エラーにさらされる機会を減らし、デバイス・サイズに応じてスケーラブルに動作させることができる。

以下では、まずLRCX回路のダイナミック回路実装について説明する。 最後に、比較のためにユニタリーベースの実装も提供し、この設定におけるダイナミック回路の利点を強調する。

回路の初期化

まずは、比較の基準となるような単純な量子力学の問題から始めよう。 具体的には、インデックス0に制御量子ビットを持つ回路を初期化し、そこにハダマールゲートを適用する。 これにより重ね合わせ状態が生じ、これに制御X演算を適用すると、制御キュービットとターゲットキュービットの間にベル状態 (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} が生成される。

現段階では、長距離コントロールX(LRCX)自体はまだ建設していない。 その代わり、私たちの目標は、LRCXの役割を強調する明確で最小限の初期回路を定義することだ。 ステップ2では、LRCXがダイナミック回路を使った最適化としてどのように実装できるかを示し、その性能をユニタリー等価回路と比較する。 重要なのは、LRCXプロトコルはどんな初期回路にも適用できるということだ。 ここでは、わかりやすく説明するために、この単純なハダマードのセットアップを使用する。

distance = 6  # The distance of the CNOT gate, with the convention that a distance of zero is a nearest-neighbor CNOT.


def initialize_circuit(distance):
    assert distance >= 0
    control = 0  # control qubit
    n = distance  # number of qubits between target and control

    qr = QuantumRegister(
        n + 2, name="q"
    )  # Circuit with n qubits between control and target
    cr = ClassicalRegister(
        2, name="cr"
    )  # Classical register for measuring control and target qubits

    k = int(n / 2)  # Number of Bell States to be used

    allcr = [cr]
    if (
        distance > 1
    ):  # This classical register will be used to store ZZ measurements.
        # It is only used for long-range CX gates with distance > 1
        c1 = ClassicalRegister(
            k, name="c1"
        )  # Classical register needed for post processing
        allcr.append(c1)
    if (
        distance > 0
    ):  # This classical register will be used to store XX measurements.
        # It is only used if distance > 0
        c2 = ClassicalRegister(
            n - k, name="c2"
        )  # Classical register needed for post processing
        allcr.append(c2)

    qc = QuantumCircuit(qr, *allcr, name="CNOT")

    # Apply a Hadamard gate to the control qubit such that the
    # long-range CNOT gate will prepare a
    # Bell state (|00> + |11>)/sqrt(2)
    qc.h(control)

    return qc


qc = initialize_circuit(distance)
qc.draw(fold=-1, output="mpl", scale=0.5)

Output:

Output of the previous code cell

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

このステップでは、ダイナミック回路を使ってLRCX回路を構成する方法を示す。 その目的は、純粋なユニタリー実装に比べて深さを減らすことで、ハードウェア上での実行のために回路を最適化することである。 その利点を説明するために、動的LRCX構造とそのユニタリー等価物の両方を表示し、後で移調後の性能を比較する。 重要なことは、ここではLRCXを単純なハダマード初期化問題に適用しているが、このプロトコルは長距離CNOTが必要とされるあらゆる回路に適用できるということである。

ベルのペアを準備する

まず、制御量子ビットとターゲット量子ビットの間の経路に沿って、ベル・ペアの連鎖を作ることから始める。 距離が奇数の場合、まずコントロールからその近傍にCNOTを適用する。 偶数距離の場合、このCNOTはベルペア準備ステップの後に適用される。 ベル・ペア・チェーンは、次に連続する量子ビットのペアをエンタングルし、デバイス全体で制御情報を伝送するのに必要なリソースを確立する。

# Determine where to start the Bell pair chain and add an extra CNOT when n is odd
def check_even(n: int) -> int:
    """Return 1 if n is even, else 2."""
    return 1 if n % 2 == 0 else 2


def prepare_bell_pairs(qc, add_barriers=True):
    n = qc.num_qubits - 2  # number of qubits between target and control
    k = int(n / 2)

    if add_barriers:
        qc.barrier()

    x0 = check_even(n)
    if n % 2 != 0:
        qc.cx(0, 1)

    # Create k Bell pairs
    for i in range(k):
        qc.h(x0 + 2 * i)
        qc.cx(x0 + 2 * i, x0 + 2 * i + 1)
    return qc


qc = prepare_bell_pairs(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output:

Output of the previous code cell

ベル基底で隣接する量子ビットのペアを測定する

次に、 隣接する量子ビットをベル基底で測定する( XXXXZZZZ の2量子ビット測定)。これにより、ターゲット量子ビットとコントロールに隣接する量子ビットの間に長距離ベル対が生成されます(次のステップでフィードフォワードにより実装されるパウリ補正まで)。 並行して、CNOTゲートをテレポートさせ、意図したターゲット量子ビットに作用させるエンタングル測定を実装する。

def measure_bell_basis(qc, add_barriers=True):
    n = qc.num_qubits - 2  # number of qubits between target and control
    k = int(n / 2)

    if n > 1:
        _, c1, c2 = qc.cregs
    elif n > 0:
        _, c2 = qc.cregs

    # Determine where to start the Bell pair chain and add an extra CNOT
    # when n is odd
    x0 = 1 if n % 2 == 0 else 2

    # Entangling layer that implements the Bell measurement
    # (and additionally adds the CNOT to be
    # teleported, if n is even)
    for i in range(k + 1):
        qc.cx(x0 - 1 + 2 * i, x0 + 2 * i)

    for i in range(1, k + x0):
        if i == 1:
            qc.h(2 * i + 1 - x0)
        else:
            qc.h(2 * i + 1 - x0)

    if add_barriers:
        qc.barrier()

    # Map the ZZ measurements onto classical register c1
    for i in range(k):
        if i == 0:
            qc.measure(2 * i + x0, c1[i])
        else:
            qc.measure(2 * i + x0, c1[i])

    # Map the XX measurements onto classical register c2
    for i in range(1, k + x0):
        if i == 1:
            qc.measure(2 * i + 1 - x0, c2[i - 1])
        else:
            qc.measure(2 * i + 1 - x0, c2[i - 1])
    return qc


qc = measure_bell_basis(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output:

Output of the previous code cell

フィードフォワード補正を適用して、パウリ副生成子演算子を補正する

ベル基底の測定ではパウリ副生成物が生じるため、記録された結果を用いて補正する必要がある。 これは2つのステップで行われる。 まず、 ZZZZ の全測定値のパリティを計算する必要がある。このパリティは、 XX ゲートをターゲット量子ビットに条件付きで適用するために使われる。 同様に、 XXXX の測定値のパリティが計算され、 ZZ ゲートを制御量子ビットに条件付きで適用するために使用される。

Qiskitの新しい古典式フレームワークを使えば、これらのパリティは回路の古典処理層で直接計算できる。 各測定ビットに個別の条件ゲートのシーケンスを適用する代わりに、関連するすべての測定結果のXOR(パリティ)を表す単一の古典的な式を構築することができる。 この式は、 if_test 単一ブロックの条件として使用され、補正ゲートを一定の深さで適用することができる。 このアプローチは回路を単純化し、フィードフォワード補正が不必要な追加レイテンシを発生させないことを保証する。

def apply_ffwd_corrections(qc):
    control = 0  # control qubit
    target = qc.num_qubits - 1  # target qubit
    n = qc.num_qubits - 2  # number of qubits between target and control

    k = int(n / 2)
    x0 = check_even(n)

    if n > 1:
        _, c1, c2 = qc.cregs
    elif n > 0:
        _, c2 = qc.cregs

    # First, let's compute the parity of all ZZ measurements
    for i in range(k):
        if i == 0:
            parity_ZZ = expr.lift(
                c1[i]
            )  # Store the value of the first ZZ measurement in parity_ZZ
        else:
            parity_ZZ = expr.bit_xor(
                c1[i], parity_ZZ
            )  # Successively compute the parity via XOR operations

    for i in range(1, k + x0):
        if i == 1:
            parity_XX = expr.lift(
                c2[i - 1]
            )  # Store the value of the first XX measurement in parity_XX
        else:
            parity_XX = expr.bit_xor(
                c2[i - 1], parity_XX
            )  # Successively compute the parity via XOR operations

    if n > 0:
        with qc.if_test(parity_XX):
            qc.z(control)

    if n > 1:
        with qc.if_test(parity_ZZ):
            qc.x(target)
    return qc


qc = apply_ffwd_corrections(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output:

Output of the previous code cell

制御クビットとターゲットクビット

我々は、 XXXXYYYYZZZZ ベースにおける制御量子ビットとターゲット量子ビットの測定を可能にするヘルパー関数を定義する。 ベル状態 (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} を検証するためには、 XXXXZZZZ の期待値がともに +1+1 であることが必要である。 YYYY の測定もここでサポートされており、忠実度を計算する際に以下で使用される。

def measure_in_basis(qc, basis="XX", add_barrier=True):
    control = 0  # control qubit
    target = qc.num_qubits - 1  # target qubit

    assert basis in ["XX", "YY", "ZZ"]

    qc = (
        qc.copy()
    )  # We copy the circuit because we want to measure in different bases
    cr = qc.cregs[0]

    if add_barrier:
        qc.barrier()

    if basis == "XX":
        qc.h(control)
        qc.h(target)
    elif basis == "YY":
        qc.sdg(control)
        qc.sdg(target)
        qc.h(control)
        qc.h(target)

    qc.measure(control, cr[0])
    qc.measure(target, cr[1])
    return qc


qc_YY = measure_in_basis(qc.copy(), basis="YY")
qc_YY.draw(
    output="mpl", fold=-1, scale=0.5
)  # Circuit for measuring in the YY basis

Output:

Output of the previous code cell

すべてをまとめて

上記で定義したさまざまな手順を組み合わせることで、1次元( 1D )の直線の両端に長距離CXゲートを作成します。 手順は以下の通りです:

  • +|+\rangle での制御量子ビットの初期化
  • ベル・ペアの準備
  • 隣接する量子ビットペアの測定
  • MCMに依存するフィードフォワード補正の適用
def lrcx(distance, prep_barrier=True, pre_measure_barrier=True):
    qc = initialize_circuit(distance)
    qc = prepare_bell_pairs(qc, prep_barrier)
    qc = measure_bell_basis(qc, pre_measure_barrier)
    qc = apply_ffwd_corrections(qc)
    return qc


qc = lrcx(distance)
# Apply the measurement in the XX, YY, and ZZ bases
qc_XX, qc_YY, qc_ZZ = [
    measure_in_basis(qc, basis=basis) for basis in ["XX", "YY", "ZZ"]
]

qc_YY.draw(
    output="mpl", fold=-1, scale=0.5
)  # Circuit for measuring in the YY basis

Output:

Output of the previous code cell

単一基底に基づく実装で量子ビットを中央に交換する

比較のため、まず、最近接接続とユニタリーゲートを用いて長距離CNOTゲートを実装した場合について検討する。 次の図の左側は、n個のn-量子ビットからなる 1D チェーンを跨ぐ長距離CNOTゲートの回路であり、この回路では隣接量子ビット間の接続のみが考慮されている。 中央には、局所的なCNOTゲートを用いて実装可能な等価なユニタリー分解があり、回路の深さは O(n)O(n) である。

長距離CNOT回路

中央の回路は、次のように実装できます:

def cnot_unitary(distance):
    """Generate a long range CNOT gate using local CNOTs on a 1D
    chain of qubits subject to n
    nearest-neighbor connections only.


    Args:
        distance (int) : The distance of the CNOT gate,
        with the convention that
        a distance of 0 is a nearest-neighbor CNOT.

    Returns:
        QuantumCircuit: A Quantum Circuit implementing a
        long-range CNOT gate
        between qubit 0 and qubit distance+1
    """
    assert distance >= 0
    n = distance  # number of qubits between target and control

    qr = QuantumRegister(
        n + 2, name="q"
    )  # Circuit with n qubits between control and target
    cr = ClassicalRegister(
        2, name="cr"
    )  # Classical register for measuring control and target qubits

    qc = QuantumCircuit(qr, cr, name="CNOT_unitary")

    control_qubit = 0

    qc.h(control_qubit)  # Prepare the control qubit in the |+> state

    k = int(n / 2)
    qc.barrier()
    for i in range(control_qubit, control_qubit + k):
        qc.cx(i, i + 1)
        qc.cx(i + 1, i)
        qc.cx(-i - 1, -i - 2)
        qc.cx(-i - 2, -i - 1)
    if n % 2 == 1:
        qc.cx(k + 2, k + 1)
        qc.cx(k + 1, k + 2)
    qc.barrier()
    qc.cx(k, k + 1)
    for i in range(control_qubit, control_qubit + k):
        qc.cx(k - i, k - 1 - i)
        qc.cx(k - 1 - i, k - i)
        qc.cx(k + i + 1, k + i + 2)
        qc.cx(k + i + 2, k + i + 1)
    if n % 2 == 1:
        qc.cx(-2, -1)
        qc.cx(-1, -2)

    return qc


qc_uni = cnot_unitary(distance)

それでは、前述の動的回路と同様に、 XXXXYYYY、および ZZZZ の各ベースで測定を行う回路を組み立てましょう。

# Apply the measurement in the XX, YY, and ZZ bases
qc_uni_XX, qc_uni_YY, qc_uni_ZZ = [
    measure_in_basis(qc_uni, basis=basis) for basis in ["XX", "YY", "ZZ"]
]

qc_uni_YY.draw(
    output="mpl", fold=-1, scale=0.5
)  # Circuit for measuring in the YY basis

Output:

Output of the previous code cell

distance=6小規模な例について、動的回路とユニタリー回路の両方を構築したので、次にそれらをトランスパイルし、まずはノイズのないシミュレータ上で実行する。

from qiskit_aer import AerSimulator

aer_backend = AerSimulator()
pm_sim = generate_preset_pass_manager(
    optimization_level=0, backend=aer_backend
)

# Dynamic circuits
isa_sim_dyn = pm_sim.run([qc_XX, qc_YY, qc_ZZ])

# Unitary circuits
isa_sim_uni = pm_sim.run([qc_uni_XX, qc_uni_YY, qc_uni_ZZ])

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

これで、ノイズのないシミュレータ・バックエンド上で実験を実行できるようになりました。 回路の実行には、バックエンドモードとして「 AerSimulator 」を設定した Qiskit Runtime サンプラーを使用しています。

sampler_sim = Sampler(mode=aer_backend)
sim_job = sampler_sim.run(isa_sim_dyn + isa_sim_uni)
sim_results = sim_job.result()

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

実験が正常に完了した後、測定データを後処理して、有意義な指標を抽出します。 このステップでは、以下の作業を行います:

  • 長距離CXのパフォーマンスを評価するための品質指標を定義する。
  • 生の測定結果からパウリ演算子の期待値を計算する。
  • これらを使って、生成されたベル状態の忠実度を計算する。

ノイズのないシミュレーションにおいて、構築された回路について、忠実度指標が 11 であることを検証する。 実際のQPUを用いた実験において、この分析により、動的回路がユニタリーなベースライン実装と比較してどの程度の性能を発揮しているかが明確に把握できるだろう。

品質メトリック

長距離CXプロトコルの成功を評価するため、出力状態が理想的なベル状態にどれだけ近いかを測定する。 これを定量化する便利な方法は、パウリ演算子の期待値を用いて状態の忠実度を計算することである。 XX\braket{XX}YY\braket{YY}、および ZZ\braket{ZZ} が分かれば、制御状態およびターゲット状態におけるベル状態の忠実度を計算することができる。具体的には、

F=14(1+XXYY+ZZ) F = \frac{1}{4} (1 + \braket{XX} - \braket{YY} + \braket{ZZ})

生の測定データからこれらの期待値を計算するために、ヘルパー関数のセットを定義する:

  • compute_ZZ_expectation: ZZ、2量子ビットのパウリ演算子の期待値を計算します。
  • compute_fidelity: XXXX, YYYY, ZZZZ の期待値を上記の忠実度式に組み合わせる。
  • get_counts_from_bitarray:バックエンドの結果オブジェクトからカウントを抽出するユーティリティ。
def compute_ZZ_expectation(counts):
    total = sum(counts.values())
    expectation = 0
    for bitstring, count in counts.items():
        # Ensure bitstring is 2 bits
        z1 = (-1) ** (int(bitstring[-1]))
        z2 = (-1) ** (int(bitstring[-2]))
        expectation += z1 * z2 * count
    return expectation / total


def compute_fidelity(counts_xx, counts_yy, counts_zz):
    xx, yy, zz = [
        compute_ZZ_expectation(c) for c in [counts_xx, counts_yy, counts_zz]
    ]
    return 1 / 4 * (1 + xx - yy + zz)
# Dynamic fidelity
counts_xx = sim_results[0].data.cr.get_counts()
counts_yy = sim_results[1].data.cr.get_counts()
counts_zz = sim_results[2].data.cr.get_counts()
fidelity_dyn = compute_fidelity(counts_xx, counts_yy, counts_zz)

# Unitary fidelity
counts_xx = sim_results[3].data.cr.get_counts()
counts_yy = sim_results[4].data.cr.get_counts()
counts_zz = sim_results[5].data.cr.get_counts()
fidelity_uni = compute_fidelity(counts_xx, counts_yy, counts_zz)

print(f"Dynamic fidelity (distance={distance}): {fidelity_dyn:.4f}")
print(f"Unitary fidelity (distance={distance}): {fidelity_uni:.4f}")

Output:

Dynamic fidelity (distance=6): 1.0000
Unitary fidelity (distance=6): 1.0000

ノイズのないシミュレーションでは予想通り、動的回路とユニタリー回路の両方における忠実度は 11 となる。


大規模なハードウェアの例

ここで、これらすべての詳細を統合して、より大規模な単一のワークフローを作成し、それを実際の量子ハードウェア上で実行します。

異なる距離用の回路を生成する

現在、最大60キュービットの距離にわたる長距離CX回路を生成しています。 各距離について、 XXXXYYYY、および ZZZZ の基底で測定を行う回路を構築し、これらは後でフィデリティを計算するために使用される。

距離のリストには短距離と長距離の両方が含まれ、 distance = 0 は最近接CXに対応する。 これらの同じ距離は、後で比較のために対応するユニタリー回路を生成するためにも使われる。

# -------------------------Step 1-------------------------
distances = [
    0,
    1,
    2,
    3,
    6,
    11,
    16,
    21,
    28,
    35,
    44,
    55,
    60,
]  # Distances for long range CX. distance of 0 is a nearest-neighbor CX
distances.sort()
assert min(distances) >= 0
basis_list = ["XX", "YY", "ZZ"]

# Dynamic circuits
circuits_dyn = []
for distance in distances:
    for basis in basis_list:
        circuits_dyn.append(
            measure_in_basis(lrcx(distance, prep_barrier=False), basis=basis)
        )
print(f"Number of circuits: {len(circuits_dyn)}")

# Unitary circuits
circuits_uni = []
for distance in distances:
    for basis in basis_list:
        circuits_uni.append(
            measure_in_basis(cnot_unitary(distance), basis=basis)
        )

print(f"Number of circuits: {len(circuits_uni)}")

ダイナミック回路とユニタリー回路の両方が、さまざまな距離で使えるようになった。 まず、バックエンド・デバイスを選択する必要がある。

# -------------------------Step 2-------------------------
# Set up access to IBM Quantum devices
from qiskit.circuit import IfElseOp

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

次のステップでは、バックエンドが if_else 命令をサポートしていることを確認する。これは、新しいバージョンのダイナミック回路に必要な命令である。 この機能はまだアーリーアクセスの段階なので、 IfElseOp がまだ利用可能でない場合は、明示的にバックエンドターゲットに追加する。

if "if_else" not in backend.target.operation_names:
    backend.target.add_instruction(IfElseOp, name="if_else")

1D チェーンの選択にはLayer Fidelity文字列を使用してください

動的回路とユニタリー回路の性能を 1D チェーンで比較したいので、レイヤー忠実度ストリングを使用して、デバイスから最適な量子ビットのチェーンの線形トポロジーを選択する。 これにより、両タイプの回路が同じ接続制約の下でトランスパイルされ、その性能を公平に比較することができる。

# This selects best qubits for longest distance and uses
# the same control for all lengths
lf_qubits = backend.properties().to_dict()[
    "general_qlists"
]  # best linear chain qubits
chosen_layouts = {
    distance: [
        val["qubits"]
        for val in lf_qubits
        if val["name"] == f"lf_{distances[-1] + 2}"
    ][0][: distance + 2]
    for distance in distances
}
print(chosen_layouts[max(distances)])  # best qubits at each distance

Output:

[11, 12, 13, 14, 15, 19, 35, 34, 33, 39, 53, 54, 55, 59, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 76, 81, 82, 83, 84, 85, 86, 87, 97, 107, 108, 109, 110, 111, 98, 91, 92, 93, 94, 95, 99, 115, 114, 113, 119, 133, 132, 131, 138, 151, 150, 149, 148]
isa_circuits_dyn = []
isa_circuits_uni = []

# Using the same initial layouts for both circuits for better
# apples to apples comparison
for qc in circuits_dyn:
    pm = generate_preset_pass_manager(
        optimization_level=1,
        backend=backend,
        initial_layout=chosen_layouts[qc.num_qubits - 2],
    )
    isa_circuits_dyn.append(pm.run(qc))

for qc in circuits_uni:
    pm = generate_preset_pass_manager(
        optimization_level=1,
        backend=backend,
        initial_layout=chosen_layouts[qc.num_qubits - 2],
    )
    isa_circuits_uni.append(pm.run(qc))
print(
    f"2Q depth: "
    f"{isa_circuits_dyn[14].depth(lambda x: x.operation.num_qubits == 2)}"
)
isa_circuits_dyn[14].draw("mpl", fold=-1, idle_wires=0)

Output:

2Q depth: 2
Output of the previous code cell
print(
    f"2Q depth: "
    f"{isa_circuits_uni[14].depth(lambda x: x.operation.num_qubits == 2)}"
)
isa_circuits_uni[14].draw("mpl", fold=-1, idle_wires=False)

Output:

2Q depth: 13
Output of the previous code cell

LRCX回路に使用される量子ビットを可視化する

このセクションでは、LRCX回路がどのようにハードウェアにマッピングされるかを検証する。 まず、回路で使用されている物理的な量子ビットを可視化することから始め、次にレイアウトにおける制御とターゲットの距離が演算回数にどのような影響を与えるかを研究する。

# Note: the qubit coordinates must be hard-coded.
# The backend API does not currently provide this information directly.
# If using a different backend, you will need to
# adjust the coordinates accordingly,
# or set the qubit_coordinates = None to use the default layout coordinates.


def _heron_coords_r2():
    """Generate coordinates for the Heron layout in R2. Note"""
    cord_map = np.array(
        [
            [
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                3,
                7,
                11,
                15,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                1,
                5,
                9,
                13,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                3,
                7,
                11,
                15,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                1,
                5,
                9,
                13,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                3,
                7,
                11,
                15,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                1,
                5,
                9,
                13,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
                3,
                7,
                11,
                15,
                0,
                1,
                2,
                3,
                4,
                5,
                6,
                7,
                8,
                9,
                10,
                11,
                12,
                13,
                14,
                15,
            ],
            -1
            * np.array([j for i in range(15) for j in [i] * [16, 4][i % 2]]),
        ],
        dtype=int,
    )

    hcords = []
    ycords = cord_map[0]
    xcords = cord_map[1]
    for i in range(156):
        hcords.append([xcords[i] + 1, np.abs(ycords[i]) + 1])

    return hcords


# Visualize the active qubits in the circuit layout
plot_circuit_layout(
    circuit=isa_circuits_uni[-1],
    backend=backend,
    view="physical",
    qubit_coordinates=_heron_coords_r2(),
)

Output:

Output of the previous code cell

次に、実際のバックエンド上で実験を実行します。 また、バッチ処理を活用して、複数の試行にわたって実験を効率的に実行しています。 反復実験を行うことで、ユニタリー法とダイナミック法の比較をより正確に行うための平均値を算出できるほか、実行ごとの偏差を比較することで、それらの変動性を定量化することができる。

# -------------------------Step 3-------------------------
num_trials = 10
jobs_uni = []
jobs_dyn = []
with Batch(backend=backend) as batch:
    sampler = Sampler(mode=batch)
    sampler.options.environment.job_tags = ["TUT_LRE"]
    for _ in range(num_trials):
        jobs_uni.append(sampler.run(isa_circuits_uni, shots=1024))
        jobs_dyn.append(sampler.run(isa_circuits_dyn, shots=1024))

動的な長距離CX回路の忠実度を計算する。 各距離について、 XX\braket{XX}YY\braket{YY}、および ZZ\braket{ZZ} の各ベースにおける測定結果を抽出する。 これらの結果は、以前に定義したヘルパー関数を用いて統合され、 F=14(1+XXYY+ZZ)F = \tfrac{1}{4} \big( 1 + \langle XX \rangle - \langle YY \rangle + \langle ZZ \rangle \big) に基づいてフィデリティが算出される。これにより、各距離における動的に実行されたプロトコルの観測されたフィデリティが得られる。

# -------------------------Step 4-------------------------
fidelities_dyn = []

# loop over trials
for job in jobs_dyn:
    result_dyn = job.result()
    trial_fidelities = []
    # loop over all distances
    for ind, dist in enumerate(distances):
        counts_xx = result_dyn[ind * 3].data.cr.get_counts()
        counts_yy = result_dyn[ind * 3 + 1].data.cr.get_counts()
        counts_zz = result_dyn[ind * 3 + 2].data.cr.get_counts()
        trial_fidelities.append(
            compute_fidelity(counts_xx, counts_yy, counts_zz)
        )
    fidelities_dyn.append(trial_fidelities)
# average over trials for each distance
avg_fidelities_dyn = np.mean(fidelities_dyn, axis=0)
std_fidelities_dyn = np.std(fidelities_dyn, axis=0)

ここでユニタリー長距離CX回路の忠実度を計算するが、これは上記のダイナミック回路の場合と同じ方法である。

fidelities_uni = []

# loop over trials
for job in jobs_uni:
    result_uni = job.result()
    trial_fidelities = []
    # loop over all distances
    for ind, dist in enumerate(distances):
        counts_xx = result_uni[ind * 3].data.cr.get_counts()
        counts_yy = result_uni[ind * 3 + 1].data.cr.get_counts()
        counts_zz = result_uni[ind * 3 + 2].data.cr.get_counts()
        trial_fidelities.append(
            compute_fidelity(counts_xx, counts_yy, counts_zz)
        )
    fidelities_uni.append(trial_fidelities)
# average over trials for each distance
avg_fidelities_uni = np.mean(fidelities_uni, axis=0)
std_fidelities_uni = np.std(fidelities_uni, axis=0)

結果をプロットする

結果を視覚的に把握できるよう、以下の図では、各手法について、もつれた量子ビット間の距離を変化させた際に測定された推定ゲート忠実度をプロットしています。

fig, ax = plt.subplots()

# Unitary with error bars
ax.errorbar(
    distances,
    avg_fidelities_uni,
    yerr=std_fidelities_uni,
    fmt="o-.",
    color="c",
    ecolor="c",
    elinewidth=1,
    capsize=4,
    label="Unitary",
)
# Dynamic with error bars
ax.errorbar(
    distances,
    avg_fidelities_dyn,
    yerr=std_fidelities_dyn,
    fmt="o-.",
    color="m",
    ecolor="m",
    elinewidth=1,
    capsize=4,
    label="Dynamic",
)
# Random gate baseline
ax.axhline(y=1 / 4, linestyle="--", color="gray", label="Random gate")

legend = ax.legend(frameon=True)
for text in legend.get_texts():
    text.set_color("black")
legend.get_frame().set_facecolor("white")
legend.get_frame().set_edgecolor("black")
ax.set_title(
    "Bell State Fidelity vs Control–Target Separation", color="black"
)
ax.set_xlabel("Distance", color="black")
ax.set_ylabel("Bell state fidelity", color="black")
ax.grid(linestyle=":", linewidth=0.6, alpha=0.4, color="gray")
ax.set_ylim((0.2, 1))
ax.set_facecolor("white")
fig.patch.set_facecolor("white")
for spine in ax.spines.values():
    spine.set_visible(True)
    spine.set_color("black")
ax.tick_params(axis="x", colors="black")
ax.tick_params(axis="y", colors="black")
plt.show()

Output:

Output of the previous code cell

上記の忠実度プロットから、LRCXは一貫して直接ユニタリー実装を上回らなかった。 実際、コントロールとターゲットの距離が短い場合、ユニタリー回路の方が高い忠実度を達成した。 しかし、セパレーションが大きくなると、ダイナミック回路はユニタリー実装よりも優れた忠実度を達成し始める。 動的回路は、長いSWAPチェーンを回避することで回路の深さを減らすが、回路途中の測定、古典的なフィードフォワード、制御パスの遅延によって、さらに回路時間がかかる。 レイテンシーが増加すると、デコヒーレンスと読み出しエラーが増加し、短距離では深度の節約を上回る可能性がある。

とはいえ、ダイナミック・アプローチがユニタリー・アプローチを凌駕するクロスオーバー・ポイントが観察される。 ユニタリー回路の深さは量子ビット間の距離に応じて直線的に成長するが、ダイナミック回路の深さは一定である。

キーポイント:

  • ダイナミック回路の直接的な利点: 現在の主な動機は、 2量子ビットの深さを減らすことであり、必ずしも忠実度を向上させることではない。
  • 今日、忠実度が悪化する理由: 特にコントロールとターゲットの分離が小さい場合、測定と古典演算による回路時間の増加が支配的となることが多い。
  • 将来に向けて: ハードウェアが改良され、特に読み出しの高速化、古典的な制御レイテンシの短縮、回路途中のオーバーヘッドの削減が進めば、これらの深さと時間の短縮が、測定可能な忠実度の向上につながることが期待される。
# Compute metrics for each distance, skipping the basis circuits since
# they are identical for each distance
depths_2q_dyn = [
    c.depth(lambda x: x.operation.num_qubits == 2)
    for c in isa_circuits_dyn[::3]
]
meas_dyn = [
    sum(1 for instr in c.data if instr.operation.name == "measure")
    for c in isa_circuits_dyn[::3]
]

depths_2q_uni = [
    c.depth(lambda x: x.operation.num_qubits == 2)
    for c in isa_circuits_uni[::3]
]
meas_uni = [
    sum(1 for instr in c.data if instr.operation.name == "measure")
    for c in isa_circuits_uni[::3]
]

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].plot(
    distances, depths_2q_uni, "o-.", color="c", label="Unitary (2Q depth)"
)
axes[0].plot(
    distances, depths_2q_dyn, "o-.", color="m", label="Dynamic (2Q depth)"
)
axes[0].set_xlabel("Number of qubits between control and target")
axes[0].set_ylabel("Two-qubit depth")
axes[0].grid(True, linestyle=":", linewidth=0.6, alpha=0.4)
axes[0].legend()

axes[1].plot(
    distances, meas_uni, "o-.", color="c", label="Unitary (# measurements)"
)
axes[1].plot(
    distances, meas_dyn, "o-.", color="m", label="Dynamic (# measurements)"
)
axes[1].set_xlabel("Number of qubits between control and target")
axes[1].set_ylabel("Number of measurements")
axes[1].grid(True, linestyle=":", linewidth=0.6, alpha=0.4)
axes[1].legend()

fig.suptitle("Scaling of Unitary vs Dynamic LRCX with Distance", fontsize=12)

plt.tight_layout()
plt.show()

Output:

Output of the previous code cell

この2量子ビットの深さのプロットは、ダイナミック回路で実装されたLRCXの主な利点を強調している。 一方、ユニタリー実装では、SWAPチェーンが必要なため、距離に応じて線形に成長する。 深さは2量子ビット演算の論理スケーリングを捉え、測定回数は動的回路の追加オーバーヘッドを反映する。 これらの測定は並列に実行されるため効率的だが、現在のハードウェアでは固定コストがかかる。

今日、忠実度が悪化する理由:特にコントロールとターゲットの分離が小さい場合、測定と古典演算による回路時間の増加が支配的となることが多い。 例えば、Heron r2 プロセッサの平均読み出し長は2,280 nsであるのに対し、 2Q ゲート長はわずか68 nsである。

測定と古典的なレイテンシが改善されるにつれて、ダイナミック回路の一定深さと一定測定のスケーリングが、より大規模な回路で明確な忠実度と実行時間の利点をもたらすことが期待される。


次のステップ

この作品に興味を持たれた方は、以下の資料もご参照ください:


参照

[1] ダイナミック回路を用いた効率的な長距離エンタングルメント, 著 Elisa Bäumer, Vinay Tripathi, Derek S. Wang, Patrick Rall, Edward H. Chen, Swarnadeep Majumder, Alireza Seif, Zlatko K. Minev. IBM クァンタム、(2023年)。 https://arxiv.org/abs/2308.13065

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