주기 경계 조건을 위한 회로 절단
사용량 추정치: Eagle 프로세서에서 2분(참고: 이는 추정치일 뿐입니다. 런타임은 다를 수 있습니다.)
배경
이 노트북에서는 첫 번째 큐비트와 마지막 큐비트를 포함해 인접한 두 큐비트 사이에 두 개의 큐비트 연산이 있는 주기적인 큐비트 체인 시뮬레이션을 고려합니다. 주기적 사슬은 아이싱 모델이나 분자 시뮬레이션과 같은 물리학 및 화학 문제에서 자주 발견됩니다.
현재 IBM 퀀텀® 디바이스는 평면형입니다. 첫 번째 큐비트와 마지막 큐비트가 이웃하는 토폴로지에 직접 주기적 체인을 삽입할 수 있습니다. 그러나 충분히 큰 문제의 경우 첫 번째 큐비트와 마지막 큐비트가 멀리 떨어져 있을 수 있으므로 이 두 큐비트 사이의 2큐비트 연산을 위해 많은 스왑 게이트가 필요합니다. 이러한 주기적 경계 문제는 이 논문 에서 연구되었습니다.
이 노트에서는 첫 번째 큐비트와 마지막 큐비트가 이웃이 아닌 유틸리티 스케일의 주기적 체인 문제를 처리하기 위해 회로 절단을 사용하는 방법을 보여드립니다. 이 장거리 연결을 끊으면 회로의 여러 인스턴스를 실행하는 데 드는 추가 SWAP 게이트와 일부 고전적인 후처리를 피할 수 있습니다. 요약하면, 절단은 장거리 2쿼비트 연산을 논리적으로 계산하기 위해 통합될 수 있습니다. 즉, 이 접근 방식은 커플링 맵의 연결성을 효과적으로 증가시켜 더 적은 수의 스왑 게이트로 이어집니다.
회로 와이어를 자르는 방법( wire cutting)과 2큐비트 게이트를 여러 개의 단일 큐비트 연산으로 대체하는 방법( gate cutting)의 두 가지 유형이 있습니다. 이 노트에서는 게이트 커팅에 중점을 두겠습니다. 게이트 절단에 대한 자세한 내용은 qiskit-addon-cutting 의 설명 자료 과 해당 참조를 참조하세요. 와이어 절단에 대한 자세한 내용은 기대값 추정을 위한 와이어 절단 자습서 또는 키스킷-애드온-커팅 의 자습서를 참조하십시오.
요구사항
이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:
- Qiskit SDK v1.2 또는 이후 (
pip install qiskit) - Qiskit Runtime v0.3 또는 이후 (
pip install qiskit-ibm-runtime) - 회로 절단 Qiskit 애드온 v.9.0 또는 이후 버전 (
pip install qiskit-addon-cutting)
설정
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, Batch1단계: 고전적 입력을 양자 문제에 매핑하기
여기서는 TwoLocal 회로를 생성하고 몇 가지 관찰 가능 항목을 정의하겠습니다.
- 입력: 회로를 생성하기 위한 파라미터
- 출력: 추상 회로 및 관측 가능
entangler map 의 마지막 큐비트와 첫 번째 큐비트 사이에 주기적으로 연결되는 TwoLocal 회로에 대해 하드웨어적으로 효율적인 entangler map 을 고려합니다. 이러한 장거리 상호 작용은 트랜스필레이션 중에 추가 SWAP 게이트로 이어질 수 있으므로 회로의 깊이를 증가시킬 수 있습니다.
백엔드 및 초기 레이아웃 선택
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)이 노트북에서는 127 큐비트 IBM 양자 디바이스의 토폴로지에서 가장 긴 1D 체인인 109 큐비트 주기적 1D 체인을 고려하겠습니다. 127 큐비트 장치에서 첫 번째 큐비트와 마지막 큐비트가 이웃하도록 109 큐비트 주기적 체인을 배열하는 것은 추가 SWAP 게이트를 통합하지 않고는 불가능합니다.
init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]
# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubitsOutput:
109
TwoLocal 회로에 대한 얽힘 생성기(entangler) 맵을 구축하십시오
coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivityTwoLocal 회로를 사용하면 rotation_blocks 와 entangler map 을 여러 번 반복할 수 있습니다. 이 경우 반복 횟수에 따라 절단해야 하는 주기적 게이트의 수가 결정됩니다. 샘플링 오버헤드는 절단 횟수에 따라 기하급수적으로 증가하므로(자세한 내용은 기대값 추정을 위한 와이어 절단 자습서 참조), 이 노트에서는 반복 횟수를 2로 고정하겠습니다.
num_reps = 2
entangler_map = []
for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)
for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)Output:
회로 절단을 사용하여 결과물의 품질을 검증하려면 이상적인 결과를 알아야 합니다. 현재 선택되는 회로는 무차별 대입 방식의 기존 시뮬레이션을 뛰어넘습니다. 따라서 회로의 파라미터를 신중하게 수정하여 절벽으로 만듭니다.
Rx 게이트의 처음 두 레이어에는 파라미터 값을 할당하고, 마지막 레이어에는 값을 할당합니다. 이렇게 하면 이 회로의 이상적인 결과는 , 이 큐비트 수입니다. 따라서 이 큐비트의 인덱스인 과 의 기대값은 각각 과 입니다.
params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)
ansatz.assign_parameters(params, inplace=True)선택 가능한 관측량
게이트 절단의 이점을 정량화하기 위해 관측 변수 와 의 기대값을 측정합니다. 앞서 설명한 것처럼 이상적인 기대값은 각각 와 입니다.
observables = []
for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)
for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)
observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffs2단계: 양자 하드웨어 실행을 위한 문제 최적화
- 입력: 추상 회로 및 관측값
- 출력: 장거리 게이트를 절단하여 생성된 목표 회로 및 관측 가능 항목
회로를 트랜스파일하다
회로는 이 단계에서 또는 잘라낸 후에 트랜스파일할 수 있습니다. 절단 후 트랜스파일링하면 샘플링 오버헤드로 인해 생성된 각 하위 실험을 트랜스파일링해야 합니다. 따라서 이 단계에서 트랜스파일링하는 것이 트랜스파일링 오버헤드를 줄이는 데 더 현명합니다.
그러나 이 단계에서 네이티브 하드웨어 연결을 통해 트랜스파일링을 수행하면 트랜스파일러는 주기적인 2쿼비트 연산을 배치하기 위해 여러 개의 SWAP 게이트를 추가하여 회로 절단의 이점을 무력화합니다. 이 문제를 피하기 위해 우리는 잘라야 할 게이트를 정확히 알고 있다는 점을 활용할 수 있습니다. 특히, 이러한 주기적인 2큐비트 게이트를 수용하기 위해 멀리 떨어진 큐비트 사이에 가상 연결을 추가하여 가상 커플링 맵을 만들 수 있습니다. 이렇게 하면 추가 스왑 게이트를 통합하지 않고도 이 단계에서 회로를 트랜스파일링할 수 있습니다.
coupling_map = backend.configuration().coupling_map
# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)
virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)Output:
장거리 주기적 연결성을 차단하라
이제 트랜스파일된 회로에서 게이트를 잘라냅니다. 잘라내야 하는 2큐비트 게이트는 레이아웃의 마지막 큐비트와 첫 번째 큐비트를 연결하는 게이트입니다.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(virtual_mapped_circuit.data)
if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}
== {init_layout[-1], init_layout[0]}
]트랜스파일된 회로의 레이아웃을 관측 가능 영역에 적용합니다.
trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)마지막으로 다양한 측정 및 준비 기반에 대한 샘플링을 통해 하위 실험을 생성합니다.
qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)장거리 상호 작용을 잘라내면 측정 및 준비 기반이 다른 회로의 여러 샘플을 실행하게 된다는 점에 유의하세요. 이에 대한 자세한 내용은 단일 큐비트 연산을 샘플링하여 가상 2큐비트 게이트 구축하기 및 여러 개의 2큐비트 유니터리를 사용한 회로 절단 에서 확인할 수 있습니다.
절단할 주기적 게이트의 수는 위의 num_reps 으로 정의된 TwoLocal 레이어의 반복 횟수와 같습니다. 게이트 커팅의 샘플링 오버헤드는 6입니다. 따라서 총 하위 실험 수는 입니다.
print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")Output:
Number of subexperiments is 36 = 6**2
하위 실험을 트랜스파일하다
이 시점에서 하위 실험에는 기본 게이트 세트에 없는 일부 1큐비트 게이트가 있는 회로가 포함되어 있습니다. 이는 절단된 큐비트가 다른 기준으로 측정되고, 이를 위해 사용되는 회전 게이트가 반드시 기준 게이트 세트에 속하지 않기 때문입니다. 예를 들어, X 기준으로 측정한다는 것은 Z 기준으로 측정하는 일반적인 방식보다 먼저 하다마드 게이트를 적용하는 것을 의미합니다. 하지만 하다마드는 기본 게이트 세트의 일부가 아닙니다.
하위 실험의 각 회로에 전체 트랜스필레이션 프로세스를 적용하는 대신 특정 트랜스필레이션 패스를 사용할 수 있습니다. 사용 가능한 모든 트랜스퓔레이션 패스에 대한 자세한 설명은 이 문서 을 참조하세요.
BasisTranslator 및 Optimize1qGatesDecomposition 패스를 적용하여 이 회로의 모든 게이트가 기본 게이트 세트에 속하도록 합니다. 이 두 패스를 사용하면 라우팅 및 초기 레이아웃 선택과 같은 다른 단계를 다시 수행하지 않기 때문에 전체 트랜스파일링 프로세스보다 빠릅니다.
pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)
subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)3단계: Qiskit primitives 명령어로 실행합니다
- 입력: 대상 회로
- 출력: 준확률 분포
절단 회로를 실행하기 위해 SamplerV2 프리미티브를 사용합니다. dynamical decoupling 및 twirling 을 비활성화하여 결과에서 얻을 수 있는 개선은 이러한 유형의 회로에 대한 게이트 절단을 효과적으로 적용했기 때문일 뿐입니다.
options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False이제 배치 모드를 사용하여 작업을 제출하겠습니다.
with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)
print(f"Job ID {cut_job.job_id()}")Output:
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()4단계: 후처리 수행 및 원하는 클래식 형식으로 결과 반환
- 입력: 준확률 분포
- 출력: 재구성된 기대값
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)이제 weight-1 및 weight-2 Z형 관측값의 평균을 계산합니다.
cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")Output:
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495
교차 검증: 절단되지 않은 기대값 획득
회로 절단 기술의 이점을 미절단 기술과 교차 검증하는 것이 유용합니다. 여기서는 회로를 절단하지 않고 기대값을 계산해 보겠습니다. 이러한 언컷 회로는 첫 번째 큐비트와 마지막 큐비트 사이의 2큐비트 연산을 구현하는 데 필요한 많은 수의 스왑 게이트로 인해 문제가 발생할 수 있습니다. SamplerV2 을 통해 확률 분포를 구한 후 sampled_expectation_value 함수를 사용하여 절단되지 않은 회로의 기대값을 구합니다. 이렇게 하면 모든 인스턴스에서 프리미티브를 균일하게 사용할 수 있습니다. 하지만 EstimatorV2 을 사용하여 기대값을 직접 계산할 수도 있습니다.
if ansatz.num_clbits == 0:
ansatz.measure_all()
pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)
transpiled_circuit = pm_uncut.run(ansatz)sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")Output:
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()이제 잘라내지 않고 모든 weight-1 및 weight-2 Z형 관측값의 평균 기대값을 계산해 보겠습니다.
uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]
uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")Output:
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656
시각화
이제 주기적 체인 회로에 게이트 절단을 사용할 때 weight-1 및 weight-2 관측값에 대해 얻은 개선 사항을 시각화해 보겠습니다
mpl.rcParams.update(mpl.rcParamsDefault)
fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))
ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]
br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]
plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)
plt.axhline(y=0, color="k", linestyle="-")
plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)
plt.legend(fontsize=14)
plt.show()Output:
요약
요약하면, 109개의 큐비트로 구성된 주기적 1D 체인에 대한 weight-1 및 weight-2 Z 유형 관측값의 평균 기대값을 계산했습니다. 이를 위해 당사는 다음을 수행합니다
- 1D 체인의 첫 번째 큐비트와 마지막 큐비트 사이에 장거리 연결을 추가하여 가상 커플링 맵을 생성하고 회로를 트랜스파일했습니다.
- 이 단계에서 트랜실레이션을 사용하면 절단 후 각 하위 실험을 개별적으로 트랜실링하는 오버헤드를 피할 수 있었습니다,
- 가상 커플링 맵을 사용하면 첫 번째 큐비트와 마지막 큐비트 사이의 2큐비트 연산을 위한 추가 스왑 게이트를 피할 수 있었습니다.
- 게이트 절단을 통해 트랜스파일 회로에서 장거리 연결성을 제거했습니다.
- 적절한 트랜스필레이션 패스를 적용하여 절단된 회로를 베이시스 게이트 세트로 변환했습니다.
SamplerV2프리미티브를 사용하여 IBM 퀀텀 디바이스에서 절단 회로를 실행했습니다.- 는 절단된 회로의 결과를 재구성하여 기대값을 얻습니다.
추론
결과에서 주기적 게이트를 줄임으로써 weight-1 및 weight-2 유형 관측소의 평균이 크게 향상되었음을 알 수 있습니다. 이 연구에는 오류 억제 또는 완화 기술이 포함되어 있지 않습니다. 관찰된 개선 사항은 전적으로 이 문제에 대해 게이트 절단을 적절히 사용했기 때문입니다. 완화 및 억제 기술을 사용하면 결과를 더욱 개선할 수 있었습니다.
이 연구는 계산 성능을 개선하기 위해 게이트 절단을 효과적으로 사용한 사례를 보여줍니다.
튜토리얼 설문조사
이 튜토리얼에 대한 피드백을 제공하려면 간단한 설문조사에 참여해 주세요. 여러분의 인사이트는 콘텐츠 제공과 사용자 경험을 개선하는 데 도움이 됩니다.