Skip to main content
IBM Quantum Platform

反復コード

所要時間の推定値:Heronプロセッサで10秒未満(注:これはあくまで推定値です。 (実行時間は状況によって異なる場合があります。)


学習成果

  • 動的回路を用いたビット反転誤り訂正コードの実装方法
  • 符号化された情報を破壊することなく、量子エラーを検出するためのスタビライザーの測定方法
  • 訂正済み結果と未訂正の結果を比較して、量子エラー訂正の性能を評価する方法

前提条件


背景

リアルタイムの量子エラー訂正(QEC)を可能にするためには、量子プログラムの実行中に量子プログラムの流れを動的に制御し、測定結果に応じて量子ゲートを条件付けできるようにする必要がある。 このチュートリアルでは、QEC の非常に単純な形式である bit-flip コードを実行します。 符号化量子ビットを1回のビット反転エラーから保護できる動的量子回路を実証し、ビット反転符号の性能を評価する。

さらにアンシラ量子ビットとエンタングルメントを利用すれば、符号化された量子情報を変換することなく安定化装置を測定することができる。 量子スタビライザーコードは、 kk 論理量子ビットを nn 物理量子ビットにエンコードする。 スタビライザー符号は、パウリ群 Πn\Pi^n からの支持を得て、離散的な誤り集合を訂正することに重点を置いている。

このチュートリアルでは、簡単な量子メモリ実験を用いて、ビットフリップコードについて解説します。 論理状態 ∣1ˉ⟩≡∣111⟩|\bar{1}\rangle \equiv |111\rangle にあるエンコード済み量子ビットを準備し、その後、(エラーを蓄積させるために)複数のアイドル時間を設け、続いてエラーの検出と訂正を行います。 次に、論理誤りの確率をサイクル数(時間)の関数として定量化する。ここで、論理誤りの確率とは、最終測定後に ∣1ˉ⟩|\bar{1}\rangle を復元できない状態にある量子ビットが見つかる確率である(具体的には、誤りに相当する状態は、 ∣000⟩|000\rangle、 ∣001⟩|001\rangle、 ∣010⟩|010\rangle、および ∣100⟩|100\rangle である)。

エラー率と時間の関係について、エンコードされていない個々の量子ビットのエラー率と比較するとともに、最終測定後にのみエラーを検出して訂正し、回路の動作中は動的にエラーを訂正しないリピテーションコードを用いた場合とも比較する。

注:この繰り返し符号はビット反転エラーの訂正のみが可能であるため、完全な誤り訂正符号ではありません。 しかし、その単純さゆえに、量子コンピュータ上でエラー訂正をどのように実装するかを説明するための良い出発点となります。 以下のメモリ実験では、1種類のエラー( ∣1ˉ⟩|\bar{1}\rangle が ∣0ˉ⟩|\bar{0}\rangle へと変化するエラー)のみを検証しており、厳密には古典的な情報の保護を実証しているに過ぎない。


要件

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

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

セットアップ

import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

sns.set()
plt.rc("xtick", labelsize=20)
plt.rc("ytick", labelsize=20)
plt.rc("lines", linewidth=3)
plt.rc("font", size=20)
plt.rc("legend", fontsize="large")
plt.rc("axes", labelsize=20)
plt.rcParams["figure.figsize"] = 15, 6
plt.rcParams["legend.title_fontsize"] = 25
# Qiskit imports
from qiskit import (
    QuantumCircuit,
    QuantumRegister,
    ClassicalRegister,
)

# qiskit-ibm-runtime
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit_ibm_runtime.circuit import MidCircuitMeasure

service = QiskitRuntimeService()

小規模シミュレータの例

本実験の目的は、アイドル遅延中の振幅減衰など、実際のハードウェアノイズ下における論理量子ビットの寿命を測定することであるため、この手順は省略する。ノイズのないシミュレータでは、検出・訂正すべきエラーは発生しないからである。


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

ステップ1:古典的な入力を量子問題に写像する

バックエンドを選択してください

回路処理中にエラーを検出するには、そのメソッド MidCircuitMeasure にアクセスできるバックエンドを選択する必要があります( ドキュメントを参照)。

# You can see all backends that support mid-circuit measurements by running the following code.
service.backends(filters=lambda b: "measure_2" in b.supported_instructions)

Output:

[<IBMBackend('ibm_pittsburgh')>,
 <IBMBackend('ibm_boston')>,
 <IBMBackend('ibm_kingston')>]
# Choose the least busy backend that supports mid-circuit measurements (`measure_2`).

backend = service.least_busy(
    filters=lambda b: "measure_2" in b.supported_instructions,
    operational=True,
    simulator=False,
    dynamic_circuits=True,
)
# backend = service.backend(backend_name) # alternatively, you could choose a specific backend
print(backend.name)

Output:

ibm_boston

複数回の誤り検出・訂正を行うビットフリップ安定化回路のシーケンスを構築する

ビットフリップ符号は、スタビライザー符号の最も単純な例の一つである。 これは、任意のエンコーディング量子ビットで発生する1ビットの反転(X)エラーから状態を保護します。 ビットフリップ誤り XX の動作を考えてみましょう。これは、任意の量子ビットに対して ∣0⟩→∣1⟩|0\rangle \rightarrow |1\rangle および ∣1⟩→∣0⟩|1\rangle \rightarrow |0\rangle を写像し、その結果、 ϵ={E0,E1,E2}={IIX,IXI,XII}\epsilon = \{E_0, E_1, E_2 \} = \{IIX, IXI, XII\} となります。このコードには5つの量子ビットが必要です。そのうち3つは保護状態を符号化するために使用され(「データ量子ビット」)、残りの2つは安定化測定の補助量子ビットとして使用されます。

以下では、(1) データ量子ビットを論理状態 ∣1ˉ⟩|\bar{1} \rangle に準備し、(2) 5μs5 \mu s の遅延を複数サイクル実行した後、誤り訂正(シンドローム量子ビットのリセットを含む)を行い、(3) データ量子ビットの状態を読み出す回路を構築します。

また、エラー訂正を使用せずに、 ∣1⟩|1\rangle 状態の寿命を測定するため、3つの参照キュービットを ∣1⟩|1\rangle 状態に準備し、そのまま放置した後、読み出しを行う。

def build_qc(
    qreg_data,
    qreg_syndrome,
    creg_data,
    creg_syndrome,
    qreg_ref=None,
    creg_ref=None,
):
    """Build a typical error correction circuit"""
    if qreg_ref:
        return QuantumCircuit(
            qreg_data,
            qreg_syndrome,
            creg_data,
            creg_syndrome,
            qreg_ref,
            creg_ref,
        )
    else:
        return QuantumCircuit(
            qreg_data, qreg_syndrome, creg_data, creg_syndrome
        )


def encode_bit_flip(circuit, qreg_data, qreg_ref=None) -> QuantumCircuit:
    """Encode bit-flip. This is done by simply adding a cx"""

    for q in qreg_data:
        circuit.x(q)

    if qreg_ref:
        for q in qreg_ref:
            circuit.x(q)

    circuit.barrier()

    return circuit


def measure_syndrome_bit(
    circuit, qreg_data, qreg_syndrome, creg_syndrome, qreg_ref=None
):
    """
    Measure the syndrome by measuring the parity.
    We reset our ancilla qubits after measuring the stabilizer
    so we can reuse them for repeated stabilizer measurements.
    Because we have already observed the state of the qubit,
    we can write the conditional reset protocol directly to
    avoid another round of qubit measurement if we used
    the `reset` instruction.
    """
    circuit.cx(qreg_data[0], qreg_syndrome[0])
    circuit.cx(qreg_data[1], qreg_syndrome[0])
    circuit.cx(qreg_data[0], qreg_syndrome[1])
    circuit.cx(qreg_data[2], qreg_syndrome[1])
    circuit.barrier()

    for q_measure, c_measure in zip(qreg_syndrome, creg_syndrome):
        circuit.append(MidCircuitMeasure(), [q_measure], [c_measure])

    with circuit.if_test((creg_syndrome[0], 1)):
        circuit.x(qreg_syndrome[0])
    with circuit.if_test((creg_syndrome[1], 1)):
        circuit.x(qreg_syndrome[1])

    circuit.barrier()
    return circuit


def apply_correction_bit(circuit, qreg_data, creg_syndrome):
    """We can detect where an error occurred and correct our state"""
    with circuit.if_test((creg_syndrome, 3)):
        circuit.x(qreg_data[0])
    with circuit.if_test((creg_syndrome, 1)):
        circuit.x(qreg_data[1])
    with circuit.if_test((creg_syndrome, 2)):
        circuit.x(qreg_data[2])
    circuit.barrier()
    return circuit


def apply_final_readout(
    circuit, qreg_data, creg_data, qreg_ref=None, creg_ref=None
):
    """Read out the final measurements"""

    circuit.barrier()
    if qreg_ref:
        circuit.measure(qreg_ref, creg_ref)
    circuit.measure(qreg_data, creg_data)

    return circuit
def build_error_correction_sequence(
    num_cycles, cycles_per_circuit, nq_ref=3, delay=None
) -> QuantumCircuit:
    circuits = []
    reps = []

    qreg_data = QuantumRegister(3, name="qdata")
    qreg_syndrome = QuantumRegister(2, name="qsyndrome")
    creg_data = ClassicalRegister(3, name="cdata")
    creg_syndrome = ClassicalRegister(2, name="csyndrome")
    qreg_ref = QuantumRegister(nq_ref, name="qreference")
    creg_ref = ClassicalRegister(nq_ref, name="creference")

    circuit = build_qc(
        qreg_data,
        qreg_syndrome,
        creg_data,
        creg_syndrome,
        qreg_ref=qreg_ref,
        creg_ref=creg_ref,
    )
    circuit = encode_bit_flip(circuit, qreg_data, qreg_ref=qreg_ref)

    circuit_n = circuit.copy()
    circuit_n = apply_final_readout(
        circuit_n, qreg_data, creg_data, qreg_ref=qreg_ref, creg_ref=creg_ref
    )
    circuits.append(circuit_n)
    reps.append(0)

    for i in range(1, num_cycles + 1):
        if delay:
            circuit.delay(delay, unit="us")
            circuit.barrier()
        circuit = measure_syndrome_bit(
            circuit,
            qreg_data,
            qreg_syndrome,
            creg_syndrome,
            qreg_ref=qreg_ref,
        )
        circuit = apply_correction_bit(circuit, qreg_data, creg_syndrome)
        circuit_n = circuit.copy()
        if i % cycles_per_circuit == 0:
            circuit_n = apply_final_readout(
                circuit_n,
                qreg_data,
                creg_data,
                qreg_ref=qreg_ref,
                creg_ref=creg_ref,
            )
            circuits.append(circuit_n)
            reps.append(i)

    return circuits, np.array(reps)


num_cycles = 40
cycles_per_circuit = 4
nq_ref = 3
circuits, rep_array = build_error_correction_sequence(
    num_cycles, cycles_per_circuit, nq_ref=3, delay=5
)
circuits[1].draw(output="mpl", cregbundle=False, fold=50)

Output:

Output of the previous code cell

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

ジョブの実行総時間を短縮するため、 Qiskit primitives は、ターゲットシステムがサポートする命令および接続方式に準拠した回路およびオブザーバブルのみを受け入れます(これらは「命令セットアーキテクチャ(ISA)回路およびオブザーバブル」と呼ばれます)。 トランスパイレーションについて詳しくはこちら。

ISA回路を生成する

まず、プリセットのパスマネージャーを使用し、最適化レベル3で回路のうち最も長いものをトランスパイルすることで、初期レイアウト(つまり、回路をマッピングする物理量子ビットの選択)を決定します。

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuit_ref = pm.run(circuits[-1])
init_layout = isa_circuit_ref.layout.initial_index_layout(
    filter_ancillas=True
)
print(init_layout)

Output:

[56, 44, 62, 43, 63, 22, 48, 67]

我々の論理量子メモリと比較対象とする参照キュービットについては、振幅減衰コヒーレンス時間の観点から、現時点で最良のキュービットを選択する( T1T_1 )。

# get all qubits ordered by T1
t1_data = []
for i in range(backend.num_qubits):
    try:
        t1_us = backend.properties().t1(i) * 1e6
    except Exception:
        t1_us = 0.0
    t1_data.append((i, t1_us))

t1_data_sorted = sorted(t1_data, key=lambda x: x[1], reverse=True)

# exclude the qubits we have already mapped the error correcting code to
t1_data_sorted = [
    t1_data for t1_data in t1_data_sorted if t1_data[0] not in init_layout[:5]
]

# use the best qubits in terms of T1 for the reference qubits
init_layout = init_layout[:5] + [t1_data[0] for t1_data in t1_data_sorted[:3]]
print(init_layout)

Output:

[56, 44, 62, 43, 63, 143, 131, 31]
# These are the resulting T1 times
properties = backend.properties()
print("Amplitude damping decoherence times for code data qubits:")
for q in init_layout[:3]:
    t1 = properties.t1(q)
    print(f"qubit {q}: T1 = {t1 * 1e6:.0f} mus")

print("\nAmplitude damping decoherence times for reference qubits:")
for q in init_layout[-3:]:
    t1 = properties.t1(q)
    print(f"qubit {q}: T1 = {t1 * 1e6:.0f} mus")

Output:

Amplitude damping decoherence times code data qubits:
qubit 56: T1 = 322 mus
qubit 44: T1 = 263 mus
qubit 62: T1 = 290 mus

Amplitude damping decoherence times reference qubits:
qubit 143: T1 = 442 mus
qubit 131: T1 = 410 mus
qubit 31: T1 = 401 mus
# now we transpile all circuits to this initial layout; this way each circuit is run on the same qubits and we can make a fair comparison
pm = generate_preset_pass_manager(
    backend=backend,
    optimization_level=3,
    initial_layout=init_layout,
)
isa_circuits = pm.run(circuits)
isa_circuits[1].draw("mpl", cregbundle=False, fold=50)

Output:

Output of the previous code cell

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

sampler = Sampler(mode=backend)

sampler.options.environment.job_tags = ["TUT-REPCODE"]
sampler.options.max_execution_time = 600  # this workload is expected to be under 10s, but it is generally a good habit to set a max execution time (here 600s = 10m)
job = sampler.run(isa_circuits, shots=1000)
print(job.job_id())
job.status()

Output:

'DONE'

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

ここでは、3量子ビット反復コードを用いた論理メモリと、符号化されていない個々の参照量子ビットとを、エラー率と時間の関係について比較する。

results = job.result()
def correct_counts(counts_dict):
    """
    Corrects the measured logical qubit encoded in the repetition code using majority vote
    """

    result = {"000": 0, "111": 0}
    for bitstring, count in counts_dict.items():
        key = "111" if bitstring.count("1") > 1 else "000"
        result[key] += count
    return result


accuracy = []  # logical qubit
accuracies_ref = np.zeros(
    (len(results), nq_ref)
)  # accuracies on individual reference qubits

for n, pub_result in enumerate(results):
    # logical accuracy (one minus error probability) for active error correction with repetition code
    counts = pub_result.data.cdata.get_counts()
    shots = sum(counts.values())
    counts_corrected = correct_counts(counts)
    accuracy.append(counts_corrected.get("111", 0) / shots)

    # accuracy for individual physical reference qubits without any error correction
    for i in range(nq_ref):
        counts = pub_result.data.creference.slice_bits(i).get_counts()
        accuracies_ref[n, i] = counts.get("1", 0) / shots

accuracy = np.array(accuracy)
def error_proba_t1(N, t1):
    """
    Exponential fitting function for amplitude damping vs. number of cycles
    """

    t_cycle = 7.3e-6  # approximate time per cycle = 5 mus delay + 2.3 mus for error correction
    return 1 - np.exp(-t_cycle * N / t1)


fig, ax = plt.subplots(1, 1, figsize=(15, 5))
ax.plot(
    rep_array, (1.0 - accuracy) * 100.0, "ko-", linewidth=3, label="rep code"
)

for i in range(nq_ref):
    accuracy_1q = accuracies_ref[:, i]
    if i == 0:
        ax.plot(
            rep_array,
            (1.0 - accuracy_1q) * 100.0,
            "go-",
            linewidth=1,
            label="1q reference",
        )
    else:
        ax.plot(rep_array, (1.0 - accuracy_1q) * 100.0, "go-", linewidth=1)

params_bf, pcov = curve_fit(
    error_proba_t1, rep_array, 1.0 - accuracy, bounds=([0, 5e-3])
)
t1_bf = params_bf[0]
print(f"Best-fit effective T1 = {t1_bf * 1e6:.0f} us")
error_prob_bf = error_proba_t1(
    rep_array, t1_bf
)  # np.array([1 - np.exp(-t_cycle * nt/t1) for nt in rep_array])
ax.plot(rep_array, error_prob_bf * 100.0, "k--", linewidth=1)

ax.set_xlabel("error correction rounds")
ax.set_ylabel("error [%]")
ax.set_ylim(bottom=0)
ax.legend(fontsize=15);

Output:

Best-fit effective T1 = 1397 us
Output of the previous code cell

∣1ˉ⟩|\bar{1}\rangle 状態の寿命が、最高の(符号化されていない)物理量子ビットと比較しても、明らかに向上していることがわかります。 ただし、この実験では、ビット反転という1種類のエラーのみを修正するものであることに留意してください。 論理量子ビットの寿命を延ばすことはできますか? 遅延時間の最適化や、 n=3n=3 を超える範囲での繰り返しコードの規模拡大などについて検討してみるといいでしょう。


次のステップ

この作品に興味をお持ちの方は、以下の資料もご参考になるかもしれません:

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