Skip to main content
IBM Quantum Platform

절단된 벨 쌍을 이용한 동적 회로 벤치마킹

사용량 추정: 헤론 r2 프로세서 기준 22초 (참고: 이는 단순 추정값입니다.) 실행 시간은 다를 수 있습니다.)


학습 성과

  • 회로 중간 측정과 고전적 피드포워드를 활용하여 동적 회로를 구축하고, 멀리 떨어진 큐비트 간의 얽힘을 순간이동시키는 방법;
  • 장치 전반에 걸쳐 벨 쌍의 충실도를 정량화하기 위해 오차 지표를 계산하고 해석하는 방법;
  • 벤치마크 결과를 활용하여 LOCC 기반 연산에 가장 적합한 큐비트 쌍을 식별하는 방법.

전제조건


배경

양자 하드웨어는 일반적으로 국소적 상호작용으로 제한되지만, 많은 알고리즘은 멀리 떨어진 큐비트들 또는 심지어 별도의 프로세서에 있는 큐비트들까지도 얽히게 할 필요가 있습니다. 동적 회로, 즉 중간 회로 측정 및 피드포워드를 갖춘 회로는 실시간 고전적 통신을 활용하여 비국소 양자 연산을 효과적으로 구현함으로써 이러한 한계를 극복하는 방법을 제공한다. 이 접근법에서는 회로의 한 부분(또는 하나의 QPU)에서 얻은 측정 결과가 다른 부분의 게이트를 조건부로 트리거할 수 있어, 얽힘을 장거리로 텔레포트할 수 있게 합니다. 이는 지역적 운영 및 고전적 통신(LOCC) 방식의 기반을 형성하며, 여기서 우리는 얽힌 자원 상태(벨 쌍)를 소비하고 측정 결과를 고전적으로 통신하여 원격 큐비트를 연결합니다.

LOCC의 유망한 활용법 중 하나는 장거리 얽힘 튜토리얼에서 보여준 바와 같이, 텔레포테이션을 통해 가상 장거리 CNOT 게이트를 구현하는 것이다. 하드웨어 연결성으로 인해 직접적인 장거리 CNOT 구현이 불가능할 수 있으므로, 벨 쌍을 생성하고 텔레포테이션 기반 게이트 구현을 수행합니다. 그러나 이러한 작업의 정확도는 하드웨어 특성에 따라 달라집니다. 필요한 지연 시간(측정 결과를 기다리는 동안) 동안의 큐비트 디코히런스와 고전적 통신 지연은 얽힘 상태를 저하시킬 수 있다. 또한 중간 회로 측정의 오류는 조건부 게이트를 통해 회로의 나머지 부분으로 전파되기 때문에 최종 측정의 오류보다 수정하기가 더 어렵습니다.

대조 실험에서 저자들은 LOCC 기반 얽힘에 가장 적합한 장치의 부품을 파악하기 위해 벨 쌍 충실도 벤치마크를 도입했다. 이 아이디어는 프로세서 내의 연결된 4개 큐비트 그룹마다 작은 동적 회로를 구동하는 것입니다. 이 4큐비트 회로는 먼저 가운데 두 큐비트에 벨 쌍을 생성한 다음, 이를 자원으로 활용하여 LOCC를 통해 양쪽 끝의 두 큐비트를 얽히게 합니다. 구체적으로, 큐비트 1과 2는 절단되지 않은 벨 쌍(텔레포테이션 없이 하다마르 연산과 CNOT 연산을 통해 직접 생성된 벨 쌍)으로 국소적으로 준비된 후, 텔레포테이션 루틴이 해당 벨 쌍을 소비하여 큐비트 0과 3을 얽히게 합니다. 회로 실행 중에 큐비트 1과 2를 측정하고, 그 결과에 따라 파울리 보정(큐비트 3에 X, 큐비트 0에 Z)이 적용됩니다. 회로가 끝날 때 큐비트 0과 3은 벨 상태에 놓이게 된다.

이 최종 얽힌 쌍의 품질을 정량화하기 위해, 우리는 그 안정화자(stabilizer)를 측정한다: 구체적으로, 기저 ZZ ( Z0Z3Z_0Z_3 )에서의 패리티와 기저 XX ( X0X3X_0X_3 )에서의 패리티를 측정한다. 완벽한 벨 쌍의 경우, 이 두 기대값은 모두 +1과 같다. 실제 사용 시 하드웨어 노이즈로 인해 이 값들은 감소합니다. 따라서 우리는 각 큐비트 쌍에 대해 회로를 두 번 반복합니다: 하나의 회로는 큐비트 0과 3을 ZZ 기저에서 측정하고, 다른 회로는 XX 기저에서 측정합니다. 결과로부터, 해당 큐비트 쌍에 대한 X0X3\langle X_0X_3\rangleZ0Z3\langle Z_0Z_3\rangle 의 추정값을 얻습니다. 우리는 얽힘 충실도의 간단한 지표로, 이상값 (1)에 대한 이러한 안정화 장치들의 평균 제곱 오차(MSE)를 사용한다. MSE 값이 낮을수록 두 큐비트가 이상적인 벨 상태에 더 가깝게(더 높은 충실도로) 도달했음을 의미하며, MSE 값이 높을수록 오류가 더 많음을 나타냅니다. 이 실험을 장치 전반에 걸쳐 스캔함으로써, 우리는 서로 다른 큐비트 그룹의 측정 및 피드포워드 능력을 벤치마킹하고 LOCC 연산에 가장 적합한 큐비트 쌍을 식별할 수 있습니다.

이 튜토리얼에서는 IBM Quantum® 기기를 활용한 실험을 통해, 동적 회로를 사용하여 멀리 떨어진 큐비트 간의 얽힘을 생성하고 평가하는 방법을 설명합니다. 이 장치에서 모든 4-큐비트 선형 사슬을 매핑하고, 각 사슬에서 텔레포트 회로를 실행한 다음, MSE 값의 분포를 시각화할 것입니다. 이 종단 간 절차는 ‘ IBM Quantum ’ 컴퓨팅 서비스와 동적 회로 기능을 활용하여, 모듈형 시스템 내에서 회로를 분할하거나 양자 알고리즘을 분산 배치할 때 하드웨어 특성을 고려한 의사결정을 내리는 방법을 보여줍니다.


요구사항

이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하십시오:

  • Qiskit SDK v2.0 또는 이후 버전, 시각화 지원 기능 포함
  • v0.40Qiskit Runtime 또는 이후 (pip install qiskit-ibm-runtime)
  • Qiskit Aer v0.17 이상 (pip install qiskit-aer)

설정

from qiskit import QuantumCircuit

from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler import generate_preset_pass_manager

import numpy as np
import matplotlib.pyplot as plt


def create_bell_stab(initial_layouts):
    """
    Create a circuit for a 1D chain of qubits (number
    of qubits must be a multiple of 4), where a middle
    Bell pair is consumed to create a Bell at the edge.
    Takes as input a list of lists, where each element
    of the list is a 1D chain of physical qubits that is
    used as the initial_layout for the transpiled circuit.
    Returns a list of length-2 tuples, each tuple
    contains a circuit to measure the ZZ stabilizer and
    a circuit to measure the XX stabilizer of the edge
    Bell state.
    """
    bell_circuits = []
    for (
        initial_layout
    ) in initial_layouts:  # Iterate over chains of physical qubits
        assert (
            len(initial_layout) % 4 == 0
        ), "The length of the chain must be a multiple of 4, "
        f"len(inital_layout)={len(initial_layout)}"
        num_pairs = len(initial_layout) // 4

        bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)

        for pair_idx in range(num_pairs):
            (q0, q1, q2, q3) = (
                pair_idx * 4,
                pair_idx * 4 + 1,
                pair_idx * 4 + 2,
                pair_idx * 4 + 3,
            )
            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits
            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits

            bell_parallel.h(q0)
            bell_parallel.h(q1)
            bell_parallel.cx(q1, q2)
            bell_parallel.cx(q0, q1)
            bell_parallel.cx(q2, q3)
            bell_parallel.h(q2)

        # add barrier BEFORE measurements and add id in conditional
        bell_parallel.barrier()
        for pair_idx in range(num_pairs):
            (q0, q1, q2, q3) = (
                pair_idx * 4,
                pair_idx * 4 + 1,
                pair_idx * 4 + 2,
                pair_idx * 4 + 3,
            )
            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits

            bell_parallel.measure(q1, ca0)
            bell_parallel.measure(q2, ca1)
        # bell_parallel.barrier() #remove barrier after measurement

        for pair_idx in range(num_pairs):
            (q0, q1, q2, q3) = (
                pair_idx * 4,
                pair_idx * 4 + 1,
                pair_idx * 4 + 2,
                pair_idx * 4 + 3,
            )
            (ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2  # middle qubits
            with bell_parallel.if_test((ca0, 1)):
                bell_parallel.x(q3)
            with bell_parallel.if_test((ca1, 1)):
                bell_parallel.z(q0)
                bell_parallel.id(q0)  # add id here for correct alignment

        bell_zz = bell_parallel.copy()
        bell_zz.barrier()
        bell_xx = bell_parallel.copy()
        bell_xx.barrier()
        for pair_idx in range(num_pairs):
            (q0, q1, q2, q3) = (
                pair_idx * 4,
                pair_idx * 4 + 1,
                pair_idx * 4 + 2,
                pair_idx * 4 + 3,
            )
            bell_xx.h(q0)
            bell_xx.h(q3)
        bell_xx.barrier()
        for pair_idx in range(num_pairs):
            (q0, q1, q2, q3) = (
                pair_idx * 4,
                pair_idx * 4 + 1,
                pair_idx * 4 + 2,
                pair_idx * 4 + 3,
            )
            (c0, c1) = pair_idx * 4, pair_idx * 4 + 3  # edge qubits

            bell_zz.measure(q0, c0)
            bell_zz.measure(q3, c1)

            bell_xx.measure(q0, c0)
            bell_xx.measure(q3, c1)

        bell_circuits.append(bell_zz)
        bell_circuits.append(bell_xx)

    return bell_circuits


def get_mse(result, initial_layouts):
    """
    given a result object and the initial layouts,
    returns a dict of layouts and their mse
    """
    layout_mse = {}
    for layout_idx, initial_layout in enumerate(initial_layouts):
        layout_mse[tuple(initial_layout)] = {}

        num_pairs = len(initial_layout) // 4

        counts_zz = result[2 * layout_idx].data.c.get_counts()
        total_shots = sum(counts_zz.values())

        # Get ZZ expectation value
        exp_zz_list = []
        for pair_idx in range(num_pairs):
            exp_zz = 0
            for bitstr, shots in counts_zz.items():
                bitstr = bitstr[::-1]  # reverse order to big endian
                b1, b0 = (
                    bitstr[pair_idx * 4],
                    bitstr[pair_idx * 4 + 3],
                )  # parse bitstring to get edge measurements for each 4-q chain
                z_val0 = 1 if b0 == "0" else -1
                z_val1 = 1 if b1 == "0" else -1
                exp_zz += z_val0 * z_val1 * shots
            exp_zz /= total_shots
            exp_zz_list.append(exp_zz)

        counts_xx = result[2 * layout_idx + 1].data.c.get_counts()
        total_shots = sum(counts_xx.values())

        # Get XX expectation value
        exp_xx_list = []
        for pair_idx in range(num_pairs):
            exp_xx = 0
            for bitstr, shots in counts_xx.items():
                bitstr = bitstr[::-1]  # reverse order to big endian
                b1, b0 = (
                    bitstr[pair_idx * 4],
                    bitstr[pair_idx * 4 + 3],
                )  # parse bitstring to get edge measurements for each 4-q chain
                x_val0 = 1 if b0 == "0" else -1
                x_val1 = 1 if b1 == "0" else -1
                exp_xx += x_val0 * x_val1 * shots
            exp_xx /= total_shots
            exp_xx_list.append(exp_xx)

        mse_list = [
            ((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2
            for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)
        ]

        print(f"layout {initial_layout}")
        for idx in range(num_pairs):
            layout_mse[tuple(initial_layout)][
                tuple(initial_layout[4 * idx : 4 * idx + 4])
            ] = mse_list[idx]
            print(
                f"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, "
                f"{round(mse_list[idx],4)}"
            )
            # print(f'exp_zz: {round(exp_zz_list[idx],4)},
            # exp_xx: {round(exp_xx_list[idx],4)}')
        print(" ")
    return layout_mse


def plot_mse_ecdfs(layouts_mse, combine_layouts=False):
    """
    Plot CDF of MSE data for multiple layouts.
    Optionally combine all data in a single CDF
    """

    if not combine_layouts:
        for initial_layout, layouts in layouts_mse.items():
            sorted_layouts = dict(
                sorted(layouts.items(), key=lambda item: item[1])
            )  # sort layouts by mse

            # get layouts and mses
            layout_list = list(sorted_layouts.keys())
            mse_list = np.asarray(list(sorted_layouts.values()))

            # convert to numpy
            x = np.array(mse_list)
            y = np.arange(1, len(x) + 1) / len(x)

            # Prepend (x[0], 0) to start CDF at zero
            x = np.insert(x, 0, x[0])
            y = np.insert(y, 0, 0)

            # Create the plot
            plt.plot(
                x,
                y,
                marker="x",
                linestyle="-",
                label=f"qubits: {initial_layout}",
            )

            # add qubits labels for the edge pairs
            for xi, yi, q in zip(x[1:], y[1:], layout_list):
                plt.annotate(
                    [q[0], q[3]],
                    (xi, yi),
                    textcoords="offset points",
                    xytext=(5, -10),
                    ha="left",
                    fontsize=8,
                )

    elif combine_layouts:
        all_layouts = {}
        all_initial_layout = []
        for (
            initial_layout,
            layouts,
        ) in layouts_mse.items():  # puts together all layout information
            all_layouts.update(layouts)
            all_initial_layout += initial_layout

        sorted_layouts = dict(
            sorted(all_layouts.items(), key=lambda item: item[1])
        )  # sort layouts by mse

        # get layouts and mses
        layout_list = list(sorted_layouts.keys())
        mse_list = np.asarray(list(sorted_layouts.values()))

        # convert to numpy
        x = np.array(mse_list)
        y = np.arange(1, len(x) + 1) / len(x)

        # Prepend (x[0], 0) to start CDF at zero
        x = np.insert(x, 0, x[0])
        y = np.insert(y, 0, 0)

        # Create the plot
        plt.plot(
            x,
            y,
            marker="x",
            linestyle="-",
            label=f"qubits: {sorted(list(set(all_initial_layout)))}",
        )

        # add qubit labels for the edge pairs
        for xi, yi, q in zip(x[1:], y[1:], layout_list):
            plt.annotate(
                [q[0], q[3]],
                (xi, yi),
                textcoords="offset points",
                xytext=(5, -10),
                ha="left",
                fontsize=8,
            )

    plt.xscale("log")
    plt.xlabel("Mean squared error of ⟨ZZ⟩ and ⟨XX⟩")
    plt.ylabel("Cumulative distribution function")
    plt.title("CDF for different initial layouts")
    plt.grid(alpha=0.3)
    plt.show()

소규모 시뮬레이터 예시

실제 QPU에서 실행하기 전에, 4 큐비트 체인을 갖춘 잡음이 없는 시뮬레이터에서 회로를 테스트하여 해당 회로가 벨 쌍을 생성하는지 확인합니다 [0, 1, 2, 3]. 회로를 실행하기 위해 백엔드 모드로 AerSimulator IBM QuantumSampler 를 사용합니다.

1단계: 고전적 입력을 양자 문제에 매핑하기

첫 번째 단계는 해당 장치의 토폴로지에 맞춰 설계된 모든 후보 벨 쌍(Bell-pair) 링크를 벤치마킹하기 위한 일련의 양자 회로를 구축하는 것입니다. [0, 1, 2, 3]먼저 4큐비트 체인을 사용하여 이러한 회로를 구성해 보겠습니다.

create_bell_stab() 루틴은 각 체인에 대해 다음 작업을 수행합니다:

  • 중간 벨 쌍을 준비합니다: 큐비트 1에 하다마르 연산을 적용하고, 큐비트 1에서 큐비트 2로 CNOT 연산을 적용합니다. 이것은 큐비트 1과 2를 얽히게 하여 벨 Φ+=(00+11)/2|\Phi^+\rangle = (|00\rangle + |11\rangle)/\sqrt{2} 상태를 생성합니다.
  • 가장자리 큐비트를 얽히게 하라: 큐비트 0에서 큐비트 1로 CNOT을 적용하고, 큐비트 2에서 큐비트 3로 CNOT을 적용하라. 이것은 처음에는 분리된 쌍들을 연결하여, 다음 단계 이후 큐비트 0과 3이 얽히게 됩니다. 큐비트 2에 대한 하다마르 변환도 적용된다(이는 이전 CNOT 연산과 결합되어 큐비트 1과 2에 대한 벨 측정의 일부를 이룬다). 이 시점에서 큐비트 0과 3은 아직 얽히지 않았지만, 큐비트 1과 2는 더 큰 4큐비트 상태에서 이들과 얽혀 있습니다.
  • 중간 회로 측정 및 피드포워드: 큐비트 1과 2(중간 큐비트)를 계산 기저에서 측정하여 두 개의 클래식 비트를 산출한다. 해당 측정 결과에 기반하여 조건부 연산을 적용합니다: 큐비트 1의 측정값(이를 비트 라고 m12m_{12} 칭함)이 1일 경우, 큐비트 3에 XX 게이트를 적용합니다; 큐비트 2의 측정값( m21m_{21} )이 1일 경우, 큐비트 0에 ZZ 게이트를 적용합니다. 이러한 조건부 게이트(Qiskit if_test/if_else construct를 사용하여 구현됨)는 표준 텔레포테이션 보정을 구현합니다. 그들은 큐비트 1과 2의 투영으로 인해 발생하는 무작위 파울리 반전을 "되돌려" 큐비트 0과 3이 측정 결과와 무관하게 알려진 벨 상태에 도달하도록 보장한다. 이 단계 이후, 큐비트 0과 3은 이상적으로 벨 상태로 얽혀 있어야 Φ+|\Phi^+\rangle 한다.
  • 벨 페어 안정기 측정: 그런 다음 회로를 두 가지 버전으로 분할합니다. 첫 번째 버전에서는 큐비트 0과 3에 대한 ZZZZ 안정화기를 측정합니다. 두 번째 버전에서는 이 큐비트들에 대해 XXXX 안정화자를 측정합니다.

각 4큐비트 초기 배열에 대해, 위 함수는 두 개의 회로를 반환합니다(하나는 ZZZZ 용이고, 다른 하나는 XXXX 안정화 측정용입니다). 이 회로에는 중간 회로 측정 및 조건부(if/else) 연산이 포함되어 있으며, 이는 동적 회로의 핵심 명령어입니다.

from qiskit_aer import AerSimulator

# 4-qubit chain for simulation
sim_layout = [[0, 1, 2, 3]]

aer_backend = AerSimulator()
sim_circuits = create_bell_stab(sim_layout)
sim_circuits[1].draw("mpl", fold=-1, idle_wires=False)

Output:

Output of the previous code cell

2단계: 양자 하드웨어 실행을 위한 문제 최적화

회로를 실행하기 전에, 지정된 백엔드에서 지원하는 게이트 연산으로 변환해야 합니다. 트랜스파일레이션은 추상 회로를 선택된 백엔드의 물리적 큐비트 및 게이트 집합에 매핑합니다. 이미 각 체인에 대해 구체적인 물리적 큐비트를 선택했으므로(회로 생성기에 를 initial_layout 지정함으로써), 해당 고정 레이아웃을 사용하는 트랜스파일러를 optimization_level=0 활용합니다. 이는 Qiskit이 큐비트를 재할당하거나 회로 구조를 변경할 수 있는 복잡한 최적화를 수행하지 않도록 지시합니다. 우리는 연산 순서(특히 조건부 게이트)를 명시된 대로 정확히 유지하고 싶습니다.

pm_sim = generate_preset_pass_manager(
    optimization_level=0, backend=aer_backend, initial_layout=sim_layout[0]
)
isa_sim_circuits = pm_sim.run(sim_circuits)
isa_sim_circuits[1].draw("mpl", fold=-1, idle_wires=False)

3단계: Qiskit primitives 명령어로 실행합니다

이제 노이즈 없는 시뮬레이터 백엔드에서 실험을 실행할 수 있습니다.

# Run on noiseless simulator
sampler_sim = Sampler(mode=aer_backend)
sim_job = sampler_sim.run(isa_sim_circuits)
sim_mse = get_mse(sim_job.result(), sim_layout)

Output:

layout [0, 1, 2, 3]
qubits: [0, 1, 2, 3], mse:, 0.0
 

4단계: 원하는 클래식 형식으로 결과를 후처리하고 반환

마지막 단계는 테스트된 각 큐비트 그룹에 대해 평균 제곱 오차 지표(MSE)를 계산하고 결과를 요약하는 것입니다. 각 체인에 대해 측정된 Z0Z3\langle Z_0Z_3\rangle 와 값을 X0X3\langle X_0X_3\rangle 얻었습니다. 만약 큐비트 0과 3이 벨 Φ+|\Phi^+\rangle 상태로 완벽하게 얽혀 있었다면, 이 두 값 모두 +1일 것으로 예상됩니다. 우리는 MSE를 사용하여 편차를 정량화합니다:

MSE=(Z0Z31)2+(X0X31)22.\text{MSE} = \frac{( \langle Z_0Z_3\rangle - 1)^2 + (\langle X_0X_3\rangle - 1)^2}{2}.

완벽한 벨 쌍의 경우 이 값은 0이며, 얽힘 상태의 잡음이 커질수록 증가합니다(무작위 결과의 기대값이 0에 가까울수록 MSE는 1에 수렴합니다). 이 코드는 각 4큐비트 그룹에 대해 이 MSE를 계산합니다. 노이즈가 없는 이 소규모 시뮬레이터 예제에서는 예상대로 MSE가 0인 것을 확인할 수 있습니다.


대규모 하드웨어 예시

이제 우리는 이러한 모든 세부 사항을 더 큰 규모의 단일 워크플로로 통합한 뒤, 이를 실제 양자 하드웨어에서 실행합니다.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True)

우리는 장치 결합 맵을 프로그래밍 방식으로 검색하여 4개의 큐비트로 구성된 모든 선형 연결 체인을 찾아냅니다. 이러한 각 연쇄(큐비트 인덱스 [q0q1q2q3][q0-q1-q2-q3] 로 표시됨)는 얽힘 교환 회로의 테스트 케이스 역할을 한다. 모든 가능한 length-4 경로를 식별함으로써, 해당 프로토콜을 구현할 수 있는 큐비트 그룹화에 대해 최대한의 커버리지를 확보합니다.

우리는 디바이스 그래프에 대해 탐욕적 탐색을 수행하는 헬퍼 함수를 사용하여 이러한 체인을 생성합니다. 이는 4개의 4큐비트 체인을 묶어 16큐비트 그룹으로 구성한 “띠” 형태를 반환합니다. 번들링을 통해 칩의 서로 다른 부분에서 여러 개의 4큐비트 실험을 병렬로 수행하고, 장치 전체를 효율적으로 활용할 수 있습니다. 각 16-큐비트 스트라이프에는 서로 겹치지 않는 네 개의 체인이 포함되어 있으며, 이는 해당 그룹 내에서 어떤 큐비트도 중복되어 사용되지 않음을 의미합니다. 예를 들어, 하나의 스트라이프는 [0123][0-1-2-3], [4567][4-5-6-7], [891011][8-9-10-11], [12131415][12-13-14-15] 등의 체인을 모두 묶어 구성할 수 있습니다. 스트라이프에 포함되지 않은 큐비트는 변수에 leftover 반환됩니다.

from itertools import chain
from collections import defaultdict


def stripes16_from_backend(backend):
    """
    Creates stripes of 16 qubits, four non-overlapping
    four-qubit chains, that cover as much of the coupling
    map as possible. Returns any unused qubits as leftovers.
    """
    # get the undirected adjacency list
    edges = backend.coupling_map.get_edges()
    graph = defaultdict(set)
    for u, v in edges:
        graph[u].add(v)
        graph[v].add(u)

    qubits = sorted(graph)  # all qubit indices that appear

    # greedy search for 4-long linear chains (blocks) ────────────
    used = set()  # qubits already placed in a block
    blocks = []  # each block is a four-qubit list

    for q in qubits:  # deterministic order for reproducibility
        if q in used:
            continue  # already consumed by earlier block

        # depth-first "straight" walk of length 3 without revisiting nodes
        def extend(path):
            if len(path) == 4:
                return path
            tip = path[-1]
            for nbr in sorted(graph[tip]):  # deterministic
                if nbr not in path and nbr not in used:
                    maybe = extend(path + [nbr])
                    if maybe:
                        return maybe
            return None

        block = extend([q])
        if block:  # found a 4-node path
            blocks.append(block)
            used.update(block)

    # bundle four four-qubit blocks into one 16-qubit
    # stripe (max number of measurement compatible with if-else)
    stripes = [
        list(chain.from_iterable(blocks[i : i + 4]))
        for i in range(0, len(blocks) // 4 * 4, 4)  # full groups of four
    ]

    leftovers = set(qubits) - set(chain.from_iterable(stripes))
    return stripes, leftovers
initial_layouts, leftover = stripes16_from_backend(backend)

create_bell_stab()다음으로, 함수 를 사용하여 각 16-큐비트 스트라이프에 대한 회로를 구성합니다. 이 단계를 마치면, 장치상의 모든 4큐비트 체인을 아우르는 회로 목록을 얻게 됩니다. 그런 다음 회로를 실제 백엔드에서 트랜스파일링하여 실행하고, 그 결과를 후처리합니다.

# -------------------------Step 1-------------------------
circuits = create_bell_stab(initial_layouts)

# -------------------------Step 2-------------------------
isa_circuits = []
for ind, init_layout in enumerate(initial_layouts):
    pm = generate_preset_pass_manager(
        optimization_level=0, backend=backend, initial_layout=init_layout
    )
    isa_circ = pm.run(circuits[ind * 2 : ind * 2 + 2])
    isa_circuits.extend(isa_circ)
isa_circuits[1].draw("mpl", fold=-1, idle_wires=False)

# -------------------------Step 3-------------------------
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT_BDC"]
job = sampler.run(isa_circuits)

# -------------------------Step 4-------------------------
layouts_mse = get_mse(job.result(), initial_layouts)

Output:

layout [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
qubits: [0, 1, 2, 3], mse:, 0.6302
qubits: [4, 5, 6, 7], mse:, 0.0949
qubits: [8, 9, 10, 11], mse:, 0.1729
qubits: [12, 13, 14, 15], mse:, 0.0473
 
layout [16, 23, 22, 21, 17, 27, 26, 25, 18, 31, 30, 29, 19, 35, 34, 33]
qubits: [16, 23, 22, 21], mse:, 0.0533
qubits: [17, 27, 26, 25], mse:, 0.2966
qubits: [18, 31, 30, 29], mse:, 0.0447
qubits: [19, 35, 34, 33], mse:, 0.0392
 
layout [36, 41, 42, 43, 37, 45, 46, 47, 38, 49, 50, 51, 39, 53, 54, 55]
qubits: [36, 41, 42, 43], mse:, 0.1577
qubits: [37, 45, 46, 47], mse:, 0.0705
qubits: [38, 49, 50, 51], mse:, 0.2914
qubits: [39, 53, 54, 55], mse:, 0.1711
 
layout [56, 63, 62, 61, 57, 67, 66, 65, 58, 71, 70, 69, 59, 75, 74, 73]
qubits: [56, 63, 62, 61], mse:, 0.1236
qubits: [57, 67, 66, 65], mse:, 0.9969
qubits: [58, 71, 70, 69], mse:, 0.0631
qubits: [59, 75, 74, 73], mse:, 0.0301
 
layout [76, 81, 82, 83, 77, 85, 86, 87, 78, 89, 90, 91, 79, 93, 94, 95]
qubits: [76, 81, 82, 83], mse:, 0.2787
qubits: [77, 85, 86, 87], mse:, 0.0497
qubits: [78, 89, 90, 91], mse:, 0.1271
qubits: [79, 93, 94, 95], mse:, 0.0468
 
layout [96, 103, 102, 101, 97, 107, 106, 105, 98, 111, 110, 109, 99, 115, 114, 113]
qubits: [96, 103, 102, 101], mse:, 0.8657
qubits: [97, 107, 106, 105], mse:, 0.0399
qubits: [98, 111, 110, 109], mse:, 0.0667
qubits: [99, 115, 114, 113], mse:, 0.2444
 
layout [116, 121, 122, 123, 117, 125, 126, 127, 118, 129, 130, 131, 119, 133, 134, 135]
qubits: [116, 121, 122, 123], mse:, 0.0429
qubits: [117, 125, 126, 127], mse:, 0.0487
qubits: [118, 129, 130, 131], mse:, 0.0823
qubits: [119, 133, 134, 135], mse:, 0.0583
 
layout [136, 143, 142, 141, 137, 147, 146, 145, 138, 151, 150, 149, 139, 155, 154, 153]
qubits: [136, 143, 142, 141], mse:, 0.0209
qubits: [137, 147, 146, 145], mse:, 0.0384
qubits: [138, 151, 150, 149], mse:, 0.4941
qubits: [139, 155, 154, 153], mse:, 0.1062
 

결과는 장치 전반에 걸쳐 광범위한 양자 얽힘 품질을 보여준다. 이는 사용된 물리적 큐비트에 따라 벨 상태 충실도가 10배 이상 차이가 날 수 있다는 본 논문의 결과를 확인시켜 준다. 실질적으로 이는 칩 내 특정 영역이나 연결부가 다른 부분보다 회로 중간 측정 및 피드포워드 연산을 훨씬 더 잘 수행한다는 것을 의미합니다. 큐비트 판독 오류, 큐비트 수명, 크로스톡과 같은 요인들이 이러한 차이에 기여할 가능성이 높다. 예를 들어, 한 체인에 특히 노이즈가 심한 판독 큐비트가 포함된 경우, 중간 회로 측정이 신뢰할 수 없어 해당 얽힘 쌍의 충실도가 낮아질 수 있다(높은 MSE).

마지막으로, 모든 체인의 MSE 값에 대한 누적 분포 함수(CDF)를 플롯하여 전체 성능을 시각화합니다. CDF 플롯은 x축에 MSE 임계값을, y축에 해당 MSE 이하를 가지는 큐비트 쌍의 비율을 나타냅니다. 이 곡선은 0에서 시작하여 임계값이 모든 데이터 포인트를 포함하도록 증가함에 따라 1에 접근합니다. 낮은 MSE 근처에서 가파른 상승은 많은 쌍이 높은 정확도를 가짐을 나타내며, 완만한 상승은 많은 쌍이 더 큰 오차를 가짐을 의미한다. 우리는 최상의 쌍들의 신원을 CDF에 주석 처리합니다. 그래프에서 CDF의 각 점은 하나의 4큐비트 체인의 MSE에 해당하며, 해당 실험에서 얽힘 상태를 형성한 큐비트 쌍의 인덱스로 [q0,q3][q0, q3] 그 점을 표시합니다. 이를 통해 어떤 물리적 큐비트 쌍이 최고 성능을 보이는지(CDF의 가장 왼쪽 점들) 쉽게 파악할 수 있습니다.

plot_mse_ecdfs(layouts_mse, combine_layouts=True)

Output:

Output of the previous code cell

다음 단계

권장사항

이 글이 흥미로웠다면, 다음 자료들도 참고해 보시기 바랍니다:


참조

[1] 카레라 바스케스, A., 토르노, C., Ristè, D. 외 양자 프로세서와 실시간 고전적 통신의 결합. 네이처 636, 75-79 (2024).

이 페이지가 도움이 되었습니까?
GitHub에서 버그, 오타를 보고하거나 컨텐츠를 요청하십시오.