동적 회로와의 장거리 얽힘
예상 사용 시간: Heron r2 프로세서에서 4분. (참고: 이는 추정치일 뿐입니다. 런타임은 다를 수 있습니다.)
학습 성과
- 중간 회로 측정(MCM)과 고전적 피드포워드를 활용한 동적 회로를 사용하여 장거리 CNOT 게이트를 구현하는 방법;
- 유니터리 SWAP 기반 접근법을 사용하여 동등 게이트를 구현하는 방법;
- 큐비트 간 거리에 따른 게이트 충실도를 측정하여 두 접근법을 비교하는 방법.
전제조건
- 벨 상태, 얽힘, 양자 게이트 등을 포함한 양자 컴퓨팅의 기본 개념;
- 동적 회로에 대한 이해 (회로 중간 측정 및 고전적 피드포워드);
- Qiskit SDK 및 IBM Quantum® Compute Service 에 대한 기본 지식과, IBM Quantum 계정에 대한 접근 권한이 필요합니다.
배경
연결이 제한된 디바이스에서는 멀리 떨어진 큐비트 간의 장거리 얽힘이 어렵습니다. 이 튜토리얼에서는 측정 기반 프로토콜을 사용하여 장거리 제어-X(LRCX) 게이트를 구현하여 동적 회로가 이러한 얽힘을 생성하는 방법을 보여 줍니다.
1의 엘리사 바우머 등의 접근 방식에 따라, 이 방법은 중간 회로 측정과 피드포워드를 사용하여 큐비트 분리와 관계없이 일정한 깊이 게이트를 달성합니다. 중간 벨 쌍을 생성하고, 각 쌍에서 하나의 큐비트를 측정하며, 고전적인 조건부 게이트를 적용하여 장치 전체에 얽힘을 전파합니다. 이렇게 하면 긴 스왑 체인을 피할 수 있어 회로 깊이와 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에서 실행하기 전에, 노이즈가 없는 시뮬레이터에서 동적 회로와 유니터리 회로 모두 이상적인 벨 상태를 생성하는지 확인합니다. 우리는 백엔드 모드로 AerSimulator IBM QuantumSampler 를 사용하며, 거리는 6으로 설정합니다.
1단계: 고전적 입력을 양자 문제에 매핑하기
이제 아래에 제시된 동적 회로 구성 방식(참고문헌의 그림 1a 을 참고하여 수정함)에 따라, 멀리 떨어진 두 큐비트 사이에 장거리 CNOT 게이트를 구현합니다. 1 ). 핵심 아이디어는 로 초기화된 보조 큐비트(ancilla qubits)로 구성된 “버스”를 활용하여 장거리 게이트 텔레포테이션을 매개하는 것이다.
그림과 같이 프로세스는 다음과 같이 작동합니다:
- 중간 부호기를 통해 제어 큐비트와 타깃 큐비트를 연결하는 벨 쌍 체인을 준비합니다.
- 제어와 대상이 벨 쌍을 공유할 때까지 얽히지 않은 이웃 큐비트 간에 벨 측정을 수행하여 단계별로 얽힘을 교환합니다.
- 이 벨 쌍을 게이트 텔레포트에 사용하여 로컬 CNOT를 일정한 깊이의 결정론적 장거리 CNOT로 전환합니다.
이 접근 방식은 긴 SWAP 체인을 일정한 깊이 프로토콜로 대체하여 2쿼비트 게이트 오류에 대한 노출을 줄이고 장치 크기에 따라 작업을 확장할 수 있도록 합니다.
다음에서는 먼저 LRCX 회로의 동적 회로 구현을 살펴보겠습니다. 마지막에는 이 설정에서 동적 회로의 장점을 강조하기 위해 비교를 위한 단일 기반 구현도 제공합니다.
회로 초기화
비교 기준이 될 간단한 양자 문제부터 시작해 보겠습니다. 구체적으로, 인덱스 0에 제어 큐비트가 있는 회로를 초기화한 다음, 여기에 하다마르 게이트를 적용합니다. 이를 통해 중첩 상태가 생성되며, 여기에 제어-X 연산을 적용하면 제어 큐비트와 대상 큐비트 사이에 벨 상태 가 생성된다.
현 단계에서는 아직 장거리 제어-X(LRCX) 자체를 구축하지 않고 있습니다. 대신, 우리의 목표는 LRCX의 역할을 강조하는 명확하고 최소한의 초기 회로를 정의하는 것입니다. 2단계에서는 동적 회로를 사용한 최적화를 통해 LRCX를 구현하는 방법을 보여주고, 단일 회로와 성능을 비교합니다. 중요한 점은 LRCX 프로토콜은 모든 초기 회로에 적용할 수 있다는 점입니다. 여기서는 명확한 데모를 위해 이 간단한 Hadamard 설정을 사용합니다.
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:
2단계: 양자 하드웨어 실행을 위한 문제 최적화
이 단계에서는 동적 회로를 사용하여 LRCX 회로를 구성하는 방법을 보여줍니다. 목표는 순수 단일 구현에 비해 깊이를 줄임으로써 하드웨어에서 실행할 수 있도록 회로를 최적화하는 것입니다. 이점을 설명하기 위해 동적 LRCX 구조와 단일 구조 모두 표시하고 나중에 변환 후 성능을 비교하겠습니다. 중요한 점은 여기서는 간단한 Hadamard 초기화 문제에 LRCX를 적용했지만, 이 프로토콜은 장거리 CNOT가 필요한 모든 회로에 적용될 수 있다는 점입니다.
벨 쌍 준비하기
먼저 제어 큐비트와 타겟 큐비트 사이의 경로를 따라 벨 쌍의 체인을 생성합니다. 거리가 홀수인 경우, 먼저 컨트롤의 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:
벨 기저에서 인접한 큐비트 쌍을 측정한다
다음으로 벨 기준에서 얽히지 않은 이웃 큐비트를 측정합니다( 및 의 두 큐비트 측정). 이렇게 하면 목표 큐비트와 제어에 인접한 큐비트 사이에 장거리 벨 쌍이 생성됩니다(다음 단계에서 피드포워드를 통해 구현될 폴리 보정까지). 이와 동시에 의도한 목표 큐비트에 작용하도록 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:
파울리 부산물 연산자를 보정하기 위해 피드포워드 보정을 적용한다
벨 기준 측정은 기록된 결과를 사용하여 보정해야 하는 폴리 부산물을 도입합니다. 이 작업은 두 단계로 이루어집니다. 먼저, 모든 측정값의 패리티를 계산한 다음 게이트를 대상 큐비트에 조건부로 적용하는 데 사용해야 합니다. 마찬가지로 측정값의 패리티가 계산되어 제어 큐비트에 게이트를 조건부로 적용하는 데 사용됩니다.
키스킷의 새로운 클래식 표현식 프레임워크를 사용하면 이러한 패리티를 회로의 클래식 처리 계층에서 직접 계산할 수 있습니다. 각 측정 비트에 대해 일련의 개별 조건부 게이트를 적용하는 대신 모든 관련 측정 결과의 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:
제어 큐비트와 대상 큐비트 측정
, , 또는 베이스에서 제어 및 대상 큐비트를 측정할 수 있는 도우미 함수를 정의합니다. 벨 상태 를 확인하려면 과 의 기대값은 모두 상태의 안정화기이므로 여야 합니다. 여기에서도 측정값이 지원되며 아래에서 충실도를 계산할 때 사용됩니다.
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 basisOutput:
모두 합쳐서
위에서 정의한 여러 단계를 결합하여, 1차원( 1D ) 선의 양 끝에 장거리 CX 게이트를 생성합니다. 단계는 다음과 같습니다:
- 에서 제어 큐비트 초기화
- 벨 쌍 준비하기
- 인접 큐비트 쌍 측정
- 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 basisOutput:
단일체 기반 구현에서 큐비트를 중간으로 교환
비교를 위해, 먼저 최인접 노드 연결과 유니터리 게이트를 사용하여 장거리 CNOT 게이트를 구현한 경우를 살펴보겠습니다. 다음 그림에서 왼쪽은 n개의 n-큐비트로 구성된 근접 인접 연결만 허용되는 1D (QNC) 체인을 아우르는 장거리 CNOT 게이트 회로이다. 가운데는 국소적인 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)이제 앞서 동적 회로를 만들었던 것과 마찬가지로, , , 베이스에서 측정하는 회로를 만들어 보세요.
# 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 basisOutput:
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의 성능을 평가하기 위한 품질 지표를 정의합니다.
- 원시 측정 결과에서 폴리 연산자의 기대값을 계산합니다.
- 이를 사용하여 생성된 벨 상태의 충실도를 계산합니다.
무잡음 시뮬레이션을 통해, 구축된 회로에 대해 충실도 지표가 임을 검증할 것입니다. 실제 QPU를 대상으로 한 실험에서, 이 분석은 동적 회로가 단일 연산 기반 구현 방식에 비해 얼마나 우수한 성능을 보이는지 명확히 보여줄 것입니다.
품질 메트릭
장거리 CX 프로토콜의 성공 여부를 평가하기 위해, 우리는 출력 상태가 이상적인 벨 상태에 얼마나 근접한지를 측정합니다. 이를 정량화하는 편리한 방법은 파울리 연산자의 기대값을 이용해 상태 충실도를 계산하는 것이다. , , 를 알면 제어 상태와 목표 상태에 대한 벨 상태의 충실도를 계산할 수 있다. 특히,
원시 측정 데이터에서 이러한 기대값을 계산하기 위해 일련의 도우미 함수를 정의합니다:
compute_ZZ_expectation: 측정 횟수가 주어지면 기준으로 2쿼비트 폴리 연산자의 기대값을 계산합니다.compute_fidelity: , , 의 기대값을 위의 충실도 식에 결합합니다.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
잡음이 없는 시뮬레이션에서 예상되듯이, 동적 회로와 유니터리 회로 모두에서 충실도는 이다.
대규모 하드웨어 예시
이제 우리는 이러한 모든 세부 사항을 더 큰 규모의 단일 워크플로로 통합한 뒤, 이를 실제 양자 하드웨어에서 실행합니다.
다양한 거리용 회로 생성
이제 최대 60 큐비트 간격에 이르는 다양한 큐비트 간격에 대해 장거리 CX 회로를 생성합니다. 각 거리마다, , , 기저에서 측정하는 회로를 구축하며, 이는 나중에 충실도를 계산하는 데 사용될 것입니다.
거리 목록에는 단거리와 장거리가 모두 포함되며 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 distanceOutput:
[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
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
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:
다음으로, 실제 백엔드에서 실험을 실행합니다. 또한 여러 번의 실험을 효율적으로 수행하기 위해 배치 처리를 활용합니다. 반복적인 실험을 수행함으로써, 우리는 평균값을 산출하여 단일 방법과 동적 방법 간의 비교 정확도를 높일 수 있을 뿐만 아니라, 실행 간 편차를 비교함으로써 두 방법의 변동성을 정량화할 수 있다.
# -------------------------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 회로의 충실도를 계산한다. 각 거리별로 , , 데이터베이스에서 측정 결과를 추출합니다. 이러한 결과들은 앞서 정의된 보조 함수들을 활용하여 에 따라 충실도를 계산하는 데 사용됩니다. 이를 통해 각 거리에서 동적으로 실행된 프로토콜의 관측된 충실도를 얻을 수 있습니다.
# -------------------------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:
위의 충실도 플롯을 보면, 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:
이 2큐비트 깊이 플롯은 제어 큐비트와 타겟 큐비트 사이의 간격이 증가해도 성능이 본질적으로 일정하게 유지된다는 동적 회로로 구현된 LRCX의 주요 이점을 강조합니다. 반면, 단일 구현은 필요한 스왑 체인으로 인해 거리에 따라 선형적으로 증가합니다. 깊이는 2큐비트 연산의 논리적 스케일링을 캡처하는 반면, 측정 횟수는 동적 회로의 추가 오버헤드를 반영합니다. 이러한 측정은 병렬로 수행되기 때문에 효율적이지만 오늘날의 하드웨어에는 여전히 고정 비용이 발생합니다.
오늘날 충실도가 더 나빠질 수 있는 이유 측정 및 기존 작업으로 인한 회로 시간 증가는 특히 제어 대상과 타겟의 간격이 작을 때 더 큰 영향을 미칩니다. 예를 들어, Heron r2 프로세서의 평균 판독 길이는 2,280ns인 반면 2Q 게이트 길이는 68ns에 불과합니다.
측정 및 기존 지연 시간이 개선됨에 따라 동적 회로의 일정한 깊이 및 일정한 측정 스케일링은 더 큰 회로에서 명확한 충실도와 런타임 이점을 제공할 것으로 예상됩니다.
다음 단계
이 글이 흥미로웠다면, 다음 자료들도 관심 있게 읽어보시기 바랍니다:
참조
[1] 동적 회로를 사용한 효율적인 장거리 얽힘, 작성자 엘리사 바우머, 비나이 트리파티, 데릭 왕, 패트릭 랄, 에드워드 첸, 스와르나딥 마줌더, 알리레자 세이프, 즐라트코 K. 미네브. IBM 퀀텀, (2023). https://arxiv.org/abs/2308.13065