주 콘텐츠로 건너뛰기

주기적 경계 조건을 위한 Circuit Cutting

사용량 예상: Eagle 프로세서에서 약 2분 (참고: 이 값은 추정치이며, 실제 실행 시간은 다를 수 있습니다.)

배경

이 노트북에서는 인접한 두 Qubit 사이마다 2-Qubit 연산이 있는 주기적 Qubit 체인의 시뮬레이션을 다룹니다. 여기에는 첫 번째와 마지막 Qubit 간의 연산도 포함됩니다. 주기적 체인은 이징 모델(Ising model)이나 분자 시뮬레이션과 같은 물리학 및 화학 문제에서 자주 등장합니다.

현재 IBM Quantum® 장치는 평면 구조입니다. 일부 주기적 체인은 첫 번째와 마지막 Qubit이 서로 이웃하도록 토폴로지에 직접 배치할 수 있습니다. 그러나 문제 규모가 충분히 커지면 첫 번째와 마지막 Qubit이 서로 멀리 떨어지게 되어, 이 두 Qubit 간의 2-Qubit 연산을 위해 많은 SWAP Gate가 필요해집니다. 이러한 주기적 경계 문제는 이 논문에서 연구된 바 있습니다.

이 노트북에서는 첫 번째와 마지막 Qubit이 이웃하지 않는 유틸리티 규모의 주기적 체인 문제를 처리하기 위해 Circuit cutting을 사용하는 방법을 보여줍니다. 이 장거리 연결을 잘라내면 여분의 SWAP Gate를 피할 수 있으며, 그 대신 Circuit의 여러 인스턴스를 실행하고 고전적인 후처리를 수행합니다. 요약하자면, Cutting을 통해 장거리 2-Qubit 연산을 논리적으로 계산할 수 있습니다. 다시 말해, 이 방법은 커플링 맵의 연결성을 효과적으로 높여 SWAP Gate의 수를 줄여줍니다.

Cutting에는 두 가지 유형이 있습니다. Circuit의 와이어를 자르는 방법(wire cutting)과 2-Qubit Gate를 여러 단일 Qubit 연산으로 대체하는 방법(gate cutting)입니다. 이 노트북에서는 gate cutting에 집중합니다. gate cutting에 대한 자세한 내용은 qiskit-addon-cutting설명 자료와 해당 참고 문헌을 참조하세요. wire cutting에 대한 자세한 내용은 기댓값 추정을 위한 Wire Cutting 튜토리얼이나 qiskit-addon-cutting의 튜토리얼을 참조하세요.

요구 사항

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

  • Qiskit SDK v1.2 이상 (pip install qiskit)
  • Qiskit Runtime v0.3 이상 (pip install qiskit-ibm-runtime)
  • Circuit cutting Qiskit addon v.9.0 이상 (pip install qiskit-addon-cutting)

설정

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
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, Batch

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

여기서는 TwoLocal Circuit을 생성하고 일부 관측량(observable)을 정의합니다.

  • 입력: Circuit을 생성하기 위한 파라미터
  • 출력: 추상 Circuit 및 관측량

entangler map의 마지막 Qubit과 첫 번째 Qubit 사이에 주기적 연결이 있는 하드웨어 효율적인 entangler map을 TwoLocal Circuit에 사용합니다. 이 장거리 상호작용은 트랜스파일 과정에서 여분의 SWAP Gate를 유발할 수 있어 Circuit 깊이가 증가합니다.

Backend 및 초기 레이아웃 선택

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

이 노트북에서는 127-Qubit IBM Quantum 장치의 토폴로지에서 가장 긴 1D 체인인 109-Qubit 주기적 1D 체인을 고려합니다. 여분의 SWAP Gate 없이 첫 번째와 마지막 Qubit이 이웃하도록 109-Qubit 주기적 체인을 127-Qubit 장치에 배치하는 것은 불가능합니다.

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_qubits
109

TwoLocal Circuit을 위한 entangler map 구성

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 connectivity

TwoLocal Circuit은 rotation_blocksentangler map을 여러 번 반복할 수 있습니다. 이 경우 반복 횟수에 따라 잘라야 할 주기적 Gate의 수가 결정됩니다. 샘플링 오버헤드는 Cut 수에 따라 지수적으로 증가하므로 (자세한 내용은 기댓값 추정을 위한 Wire Cutting 튜토리얼을 참조), 이 노트북에서는 반복 횟수를 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 of the previous code cell

Circuit cutting의 결과 품질을 검증하려면 이상적인 결과를 알아야 합니다. 현재 선택한 Circuit은 무차별 대입 고전 시뮬레이션의 범위를 넘어섭니다. 따라서 Circuit이 클리포드(Clifford)가 되도록 파라미터를 신중하게 고정합니다.

첫 두 레이어의 Rx Gate에는 파라미터 값 00을, 마지막 레이어에는 π\pi를 할당합니다. 이를 통해 이 Circuit의 이상적인 결과가 1n|1\rangle^{\otimes n} (nn은 Qubit 수)이 됩니다. 따라서 Qubit 인덱스가 ii일 때, Zi\langle Z_i \rangle의 기댓값은 1-1이고, ZiZi+1\langle Z_i Z_{i+1} \rangle의 기댓값은 +1+1입니다.

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)

관측량 선택

Gate cutting의 이점을 수량화하기 위해 관측량 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle의 기댓값을 측정합니다. 앞서 언급했듯이, 이상적인 기댓값은 각각 1-1+1+1입니다.

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.coeffs

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

  • 입력: 추상 Circuit과 관측값
  • 출력: 장거리 Gate를 절단하여 생성된 대상 Circuit과 관측값

Circuit 트랜스파일

Circuit은 이 단계에서 트랜스파일하거나, 절단 이후에 트랜스파일할 수 있습니다. 절단 이후에 트랜스파일하면 샘플링 오버헤드로 인해 생성된 각 부분 실험(subexperiment)마다 별도로 트랜스파일해야 합니다. 따라서 트랜스파일 오버헤드를 줄이려면 이 단계에서 트랜스파일하는 것이 더 현명합니다.

그러나 이 단계에서 실제 하드웨어 연결성을 기준으로 트랜스파일하면, Transpiler가 주기적인 2-Qubit 연산을 배치하기 위해 여러 개의 SWAP Gate를 삽입하게 되어 Circuit 절단의 이점이 가려집니다. 이 문제를 피하기 위해, 어떤 Gate를 절단해야 하는지 정확히 알고 있다는 사실을 활용할 수 있습니다. 구체적으로, 멀리 떨어진 Qubit 간에 가상 연결을 추가하여 이 주기적인 2-Qubit Gate를 수용하는 가상 커플링 맵(virtual coupling map)을 만들 수 있습니다. 이렇게 하면 추가 SWAP Gate 없이 이 단계에서 Circuit을 트랜스파일할 수 있습니다.

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)

이전 코드 셀의 출력

장거리 주기 연결성 절단

이제 트랜스파일된 Circuit에서 Gate를 절단합니다. 절단해야 할 2-Qubit Gate는 레이아웃의 첫 번째와 마지막 Qubit을 연결하는 Gate임을 유의하세요.

# 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]}
]

트랜스파일된 Circuit의 레이아웃을 관측값에 적용합니다.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

마지막으로, 서로 다른 측정 및 준비 기저(basis)를 샘플링하여 부분 실험이 생성됩니다.

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,
)

장거리 상호작용을 절단하면 측정 및 준비 기저가 서로 다른 여러 Circuit 샘플이 실행됩니다. 이에 대한 자세한 내용은 Constructing a virtual two-qubit gate by sampling single-qubit operationsCutting circuits with multiple two-qubit unitaries를 참고하세요.

절단해야 할 주기적 Gate의 수는 위에서 num_reps로 정의한 TwoLocal 레이어의 반복 횟수와 같습니다. Gate 절단의 샘플링 오버헤드는 6이므로, 부분 실험의 총 수는 6num_reps6^{num\_reps}가 됩니다.

print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2

부분 실험 트랜스파일

현재 부분 실험에는 기저 Gate 집합에 속하지 않는 일부 1-Qubit Gate가 포함된 Circuit이 있습니다. 절단된 Qubit은 서로 다른 기저에서 측정되며, 이를 위해 사용되는 회전 Gate가 반드시 기저 Gate 집합에 속하지 않기 때문입니다. 예를 들어, X 기저에서의 측정은 일반적인 Z 기저 측정 전에 Hadamard Gate를 적용하는 것을 의미하지만, Hadamard는 기저 Gate 집합의 일부가 아닙니다.

부분 실험의 각 Circuit에 전체 트랜스파일 과정을 적용하는 대신, 특정 트랜스파일 패스를 사용할 수 있습니다. 사용 가능한 모든 트랜스파일 패스에 대한 자세한 설명은 이 문서를 참고하세요.

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
]
)

Step 3: Qiskit 기본 요소를 사용한 실행

  • 입력: 대상 Circuit
  • 출력: 준확률 분포(Quasi-probability distributions)

절단된 Circuit 실행에는 SamplerV2 기본 요소를 사용합니다. 이 유형의 Circuit에서 Gate 절단의 효과적인 적용으로 인한 개선만을 확인하기 위해, dynamical decouplingtwirling을 비활성화합니다.

options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False

이제 배치(batch) 모드를 사용하여 작업을 제출합니다.

with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)

print(f"Job ID {cut_job.job_id()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()

Step 4: 후처리 및 원하는 고전적 형식으로 결과 반환

  • 입력: 준확률 분포
  • 출력: 재구성된 기댓값
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

이제 가중치-1 및 가중치-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}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495

교차 검증: 절단하지 않은 기댓값 구하기

절단하지 않은 경우와 비교하여 Circuit 절단 기법의 이점을 교차 검증하는 것이 유용합니다. 여기서는 Circuit을 절단하지 않고 기댓값을 계산합니다. 절단되지 않은 이런 Circuit은 첫 번째와 마지막 Qubit 간의 2-Qubit 연산을 구현하기 위해 많은 수의 SWAP Gate가 필요합니다. SamplerV2를 통해 확률 분포를 얻은 후, sampled_expectation_value 함수를 사용하여 절단되지 않은 Circuit의 기댓값을 구합니다. 이를 통해 모든 인스턴스에서 기본 요소를 균일하게 사용할 수 있습니다. 다만, 기댓값을 직접 계산하기 위해 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}")
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()

이제 절단하지 않은 경우의 모든 가중치-1 및 가중치-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}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656

시각화

주기적 체인 Circuit에 Gate 절단을 사용했을 때 가중치-1 및 가중치-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()

이전 코드 셀의 출력

요약

요약하자면, 109개의 Qubit으로 이루어진 주기적 1D 체인에서 가중치-1 및 가중치-2 Z형 관측값의 평균 기댓값을 계산했습니다. 이를 위해 다음과 같은 작업을 수행했습니다.

  • 1D 체인의 첫 번째와 마지막 Qubit 간에 장거리 연결을 추가한 가상 커플링 맵을 만들고 Circuit을 트랜스파일했습니다.
    • 이 단계에서 트랜스파일함으로써 절단 후 각 부분 실험을 별도로 트랜스파일하는 오버헤드를 피할 수 있었으며,
    • 가상 커플링 맵을 사용하여 첫 번째와 마지막 Qubit 간의 2-Qubit 연산에 필요한 추가 SWAP Gate를 피할 수 있었습니다.
  • Gate 절단을 통해 트랜스파일된 Circuit에서 장거리 연결을 제거했습니다.
  • 적절한 트랜스파일 패스를 적용하여 절단된 Circuit을 기저 Gate 집합으로 변환했습니다.
  • SamplerV2 기본 요소를 사용하여 IBM Quantum 장치에서 절단된 Circuit을 실행했습니다.
  • 절단된 Circuit의 결과를 재구성하여 기댓값을 얻었습니다.

결론

결과를 보면, Gate 절단을 통해 가중치-1 Z\langle Z \rangle 및 가중치-2 ZZ\langle ZZ \rangle 형 관측값의 평균이 주기적 Gate를 절단했을 때 크게 향상되었음을 알 수 있습니다. 이 연구에는 오류 억제 또는 완화 기법이 포함되지 않았음을 유의하세요. 관찰된 개선은 오직 이 문제에 대한 Gate 절단의 적절한 적용 덕분입니다. 완화 및 억제 기법을 함께 사용하면 결과를 더욱 향상시킬 수 있었을 것입니다.

이 연구는 Gate 절단을 효과적으로 활용하여 계산 성능을 향상시키는 예시를 보여줍니다.

튜토리얼 설문조사

이 짧은 설문조사를 통해 이 튜토리얼에 대한 피드백을 제공해 주세요. 여러분의 의견은 콘텐츠와 사용자 경험을 개선하는 데 도움이 됩니다.

설문조사 링크

Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.