주 콘텐츠로 건너뛰기

기댓값 추정을 위한 와이어 커팅

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

배경

Circuit-knitting은 회로를 게이트 수 및/또는 Qubit 수가 더 적은 여러 개의 작은 서브회로로 분할하는 다양한 방법을 포괄하는 용어입니다. 각 서브회로는 독립적으로 실행될 수 있으며, 최종 결과는 각 서브회로의 실행 결과에 대한 고전 후처리를 통해 얻습니다. 이 기법은 circuit cutting Qiskit addon을 통해 사용할 수 있으며, 기법에 대한 자세한 설명은 문서와 기타 입문 자료에서 확인할 수 있습니다.

이 노트북은 회로를 와이어 방향으로 분할하는 와이어 커팅(wire cutting)이라는 방법을 다룹니다 [1], [2]. 고전 회로에서는 분할 지점의 상태가 결정론적으로 0 또는 1로 확정되므로 분할이 간단하지만, 양자 회로에서는 절단 지점에서 Qubit의 상태가 일반적으로 혼합 상태입니다. 따라서 각 서브회로는 서로 다른 기저(보통 Pauli 기저 [3], [4]와 같은 토모그래피적으로 완전한 기저 집합)로 여러 번 측정되어야 하며, 그에 대응하는 고유 상태로 준비되어야 합니다. 아래 그림(출처: Ritajit Majumdar의 박사 학위 논문)은 4-Qubit GHZ 상태를 세 개의 서브회로로 와이어 커팅하는 예시를 보여줍니다. 여기서 MjM_j는 기저 집합(보통 Pauli X, Y, Z)을 나타내고, PiP_i는 고유 상태 집합(보통 0|0\rangle, 1|1\rangle, +|+\rangle, +i|+i\rangle)을 나타냅니다.

wc-1.png wc-2.png

각 서브회로는 Qubit 수 및/또는 게이트 수가 더 적기 때문에 노이즈의 영향을 덜 받을 것으로 예상됩니다. 이 노트북은 이 방법을 사용하여 시스템의 노이즈를 효과적으로 억제하는 예시를 보여줍니다.

요구 사항

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

  • 시각화 지원이 포함된 Qiskit SDK v2.0 이상
  • Qiskit Runtime v0.22 이상 ( pip install qiskit-ibm-runtime )
  • Circuit cutting Qiskit addon v0.9.0 이상 (pip install qiskit-addon-cutting)

이 노트북에서는 MBL(Many Body Localization) 회로를 사용합니다. MBL 회로는 하드웨어 효율적인 회로로, θ\thetaϕ\vec{\phi} 두 가지 파라미터로 매개변수화됩니다. θ\theta00으로 설정하고 모든 Qubit의 초기 상태를 0|0\rangle으로 준비하면, ϕ\vec{\phi}의 값에 무관하게 모든 Qubit 위치 ii에 대해 Zi\langle Z_i \rangle의 이상적인 기댓값은 +1+1입니다. MBL 회로에 대한 자세한 내용은 이 논문을 참고하세요.

설정

# 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

from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.result import sampled_expectation_value

from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
cut_wires,
expand_observables,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch

class MBLChainCircuit(QuantumCircuit):
def __init__(
self, num_qubits: int, depth: int, use_cut: bool = False
) -> None:
super().__init__(
num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
)
evolution = MBLChainEvolution(num_qubits, depth, use_cut)
self.compose(evolution, inplace=True)

class MBLChainEvolution(QuantumCircuit):
def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
super().__init__(
num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
)

theta = Parameter("θ")
phis = ParameterVector("φ", num_qubits)

for layer in range(depth):
layer_parity = layer % 2
# print("layer parity", layer_parity)
for qubit in range(layer_parity, num_qubits - 1, 2):
# print(qubit)
self.cz(qubit, qubit + 1)
self.u(theta, 0, np.pi, qubit)
self.u(theta, 0, np.pi, qubit + 1)
if (
use_cut
and layer_parity == 0
and (
qubit == num_qubits // 2 - 1
or qubit == num_qubits // 2
)
):
self.append(CutWire(), [num_qubits // 2])
if use_cut and layer < depth - 1 and layer_parity == 1:
if qubit == num_qubits // 2:
self.append(CutWire(), [qubit])
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)

Part I. 소규모 예시

1단계: 고전 입력을 양자 문제로 변환

먼저 특정 파라미터 값 없이 템플릿 Circuit을 구성합니다. 절단 위치를 표시하기 위해 CutWire라는 플레이스홀더도 삽입합니다. 소규모 예시로 10-Qubit MBL 회로를 사용합니다.

num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)

Output of the previous code cell

θ=0\theta=0일 때 관측량 1ni=1nZi\frac{1}{n}\sum_{i=1} ^n Z_i의 기댓값을 구하는 것이 목표임을 기억하세요. 파라미터 ϕ\vec{\phi}에는 임의의 값을 사용합니다.

phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
params
[0,
0.2376615174332788,
0.28244289857682414,
0.019248960591717768,
0.46140600996102477,
0.31408025180068433,
0.718184005135733,
0.991153920182475,
0.09289485768301442,
0.8857848280067783,
0.6177529765767047]

이제 Circuit에 CutWire를 삽입하여 절단 위치를 표시합니다. 원래 Circuit의 Qubit 수를 nn이라 할 때, n2\frac{n}{2} 번째 Qubit 이후에 절단이 이루어지도록 두 개의 대략 동일한 절단을 만듭니다. 함수에서 use_cut=True로 설정합니다.

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

Output of the previous code cell

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

다음으로 Circuit을 두 개의 작은 서브회로로 절단합니다. 이 예시에서는 2개의 서브회로만 사용합니다. 이를 위해 Qiskit Addon: Circuit Cutting을 사용합니다.

회로를 더 작은 서브회로로 절단

와이어를 한 지점에서 절단하면 Qubit 수가 하나 증가합니다. 원래 Qubit 외에, 절단 이후의 Circuit에 플레이스홀더로 추가 Qubit이 생깁니다. 다음 그림이 이를 나타냅니다:

wc-4.png

이 Addon은 cut_wires 함수를 사용하여 절단으로 인해 추가되는 Qubit을 처리합니다.

mbl_move = cut_wires(mbl_cut)

관측량 생성 및 확장

이제 관측량 Mz=1ni=1nZiM_z = \frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle를 구성합니다. 각 ii에 대해 Zi\langle Z_i \rangle의 이상적인 값이 +1+1이므로, MzM_z의 이상적인 값도 +1+1입니다.

observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])

단, 절단 이후 가상의 2-Qubit Move 연산이 삽입되면서 Circuit의 Qubit 수가 증가했습니다. 따라서 현재 Circuit에 맞추어 관측량도 항등 연산자를 삽입하여 확장해야 합니다.

new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])

각 관측량이 이제 원래의 6 Qubit 대신 Move 연산이 포함된 Circuit의 7 Qubit에 맞게 확장된 것을 볼 수 있습니다. 다음으로 Circuit을 두 개의 서브회로로 분할합니다.

partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)

서브회로를 시각화합니다.

subcircuits = partitioned_problem.subcircuits
subcircuits[0].draw("mpl", fold=-1)

Output of the previous code cell

subcircuits[1].draw("mpl", fold=-1)

Output of the previous code cell

관측량도 서브회로에 맞게 분할되었습니다.

subobservables = partitioned_problem.subobservables
subobservables
{0: PauliList(['IIIIII', 'IIIIII', 'IIIIII', 'IIIIII', 'IIIIII', 'IZIIII',
'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']),
1: PauliList(['ZIIII', 'IZIII', 'IIZII', 'IIIZI', 'IIIIZ', 'IIIII', 'IIIII',
'IIIII', 'IIIII', 'IIIII'])}

각 서브회로는 여러 개의 샘플을 생성합니다. 재구성 시 이 모든 샘플의 결과가 반영됩니다. 각 샘플을 subexperiment라고 합니다. Move 연산을 사용하여 관측량을 확장하려면 PauliList 데이터 구조가 필요합니다. 서브실험 재구성 시에도 유용하게 쓸 수 있도록 MzM_z 관측량을 보다 범용적인 SparsePauliOp 데이터 구조로도 생성할 수 있습니다.

M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
M_z
SparsePauliOp(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII', 'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII', 'IIIIIIIIZI', 'IIIIIIIIIZ'],
coeffs=[0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j, 0.1+0.j,
0.1+0.j, 0.1+0.j])
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)

절단된 Qubit이 서로 다른 기저로 측정되는 두 가지 예시를 살펴봅니다. 첫 번째는 일반적인 Z 기저로, 두 번째는 X 기저로 측정됩니다.

subexperiments[0][6].draw("mpl", fold=-1)

Output of the previous code cell

subexperiments[0][2].draw("mpl", fold=-1)

Output of the previous code cell

각 부분 실험 트랜스파일하기

현재는 회로를 실행하기 전에 트랜스파일해야 합니다. 따라서 먼저 각 부분 실험의 회로를 트랜스파일합니다.

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

이제 각 부분 실험의 회로를 트랜스파일해야 합니다. 이를 위해 먼저 패스 매니저를 생성한 후, 이를 사용하여 각 회로를 트랜스파일합니다.

pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
isa_subexperiments[0][0].draw("mpl", fold=-1, idle_wires=False)

이전 코드 셀의 출력

3단계: Qiskit 프리미티브를 사용한 실행

이제 각 부분 실험의 회로를 실행합니다. Qiskit-addon-cutting은 부분 실험 실행에 SamplerV2를 사용합니다.

with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}

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

회로 실행이 완료되면 결과를 가져와서 절단되지 않은 회로와 원래 관측량에 대한 기댓값을 재구성해야 합니다.

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
0.9674376845359803

교차 검증

이제 절단 없이 회로를 실행하고 결과를 확인해 보겠습니다. 절단되지 않은 회로의 실행에는 기댓값 계산에 EstimatorV2를 직접 사용할 수 있습니다. 하지만 동일한 Primitive를 계속 사용하겠습니다. 따라서 SamplerV2를 사용하여 확률 분포를 구하고, sampled_expectation_value 함수를 이용해 기댓값을 계산합니다.

먼저 절단되지 않은 mbl 회로를 트랜스파일해야 합니다.

sampler = SamplerV2(mode=backend)

if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)

다음으로 PUB를 구성하고 절단되지 않은 회로를 실행합니다.

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
uncut_expval
0.9498046875000001

와이어 절단을 통해 얻은 기댓값이 절단되지 않은 경우보다 이상적인 값인 +1+1에 더 가깝다는 것을 알 수 있습니다. 이제 문제 규모를 확장해 보겠습니다.

파트 II. 규모 확장!

앞서 10-Qubit MBL 회로에 대한 결과를 살펴봤어요. 이번에는 더 큰 회로에서도 기댓값의 개선이 이루어진다는 것을 살펴봐요. 이를 위해 60-Qubit MBL 회로에 대해 동일한 과정을 반복합니다.

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

num_qubits = 60
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)

ϕ\vec{\phi}에 대한 무작위 값 집합을 생성합니다.

phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis

다음으로 절단된 회로를 구성합니다.

mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)

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

소규모 예시에서 앞서 본 것처럼, 절단 실험을 위해 회로와 관측량을 분할합니다.

mbl_move = cut_wires(mbl_cut)

# Define observable
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)

# Partition the circuit into subcircuits
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)

# Get subcircuits
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables

적절한 계수를 가진 관측량에 대한 SparsePauliOp 객체도 생성합니다.

M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)

다음으로 부분 실험을 생성하고 각 회로를 트랜스파일합니다.

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

3단계: Qiskit 프리미티브를 사용한 실행

Batch 모드를 사용하여 부분 실험의 모든 회로를 실행합니다.

with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}

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

이제 각 회로의 결과를 가져와서 절단되지 않은 회로와 원래 관측량에 해당하는 기댓값을 재구성합니다.

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
0.9631355921427409

교차 검증

소규모 예시와 마찬가지로, 절단되지 않은 회로를 실행하여 기댓값을 다시 한 번 구하고 회로 절단 결과와 비교합니다. Primitive 사용의 일관성을 위해 SamplerV2를 사용합니다.

sampler = SamplerV2(mode=backend)

if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)

pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
uncut_expval
0.9426757812499998

시각화

와이어 절단을 통해 얻어진 기댓값의 개선 효과를 시각화해 보겠습니다.

ax = plt.gca()
methods = ["cut", "uncut"]
values = [reconstructed_expval, uncut_expval]

plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
ax.set_ylim([0.85, 1.02])
plt.text(0.3, 0.99, "Exact result")
plt.show()

이전 코드 셀의 출력

결론

소규모 및 대규모 문제 모두에서 와이어 절단이 절단되지 않은 경우보다 더 나은 결과를 제공한다는 것을 확인할 수 있습니다. 이 실험에서는 오류 완화 기법을 사용하지 않았습니다. 따라서 결과의 개선은 전적으로 와이어 절단 덕분입니다. 다양한 완화 방법을 회로 절단과 함께 사용하면 결과를 더욱 개선할 수 있을 것입니다.

또한 이 노트북에서는 두 서브서킷을 동일한 하드웨어에서 계산했습니다. [5], [6]의 저자들은 노이즈 정보를 활용하여 노이즈 억제를 극대화하고 프로세스를 병렬화하기 위해 서브서킷을 서로 다른 하드웨어에 분산시키는 방법을 제시합니다.

부록: 리소스 스케일링 고려사항

실행해야 할 회로 수는 절단 횟수에 따라 증가합니다. 따라서 많은 절단을 통해 더 작은 서브서킷을 생성하면 성능이 더욱 향상되지만, 그만큼 회로 실행 횟수도 크게 늘어나 대부분의 경우 실용적이지 않을 수 있습니다. 아래는 50-Qubit 회로에서 절단 횟수에 따른 서브서킷 수의 예를 보여줍니다.

wc-5.png

다섯 번의 절단만으로도 부분 실험 수가 약 20만 개에 달한다는 점에 유의하세요. 따라서 회로 절단은 절단 횟수가 적은 경우에만 사용하는 것이 좋습니다.

절단 친화적 회로와 절단 비친화적 회로의 예시

절단 친화적 회로

앞서 언급했듯이, 회로를 적은 수의 절단으로 더 작은 독립적인 서브서킷으로 분할할 수 있을 때 절단 친화적이라고 합니다. 하드웨어 커플링 맵에 매핑할 때 SWAP Gate가 거의 또는 전혀 필요 없는 하드웨어 효율적인 회로는 일반적으로 절단 친화적입니다. 아래는 양자 화학에 사용되는 여기 보존 앤샛츠(excitation preserving ansatz)의 예시입니다. 이러한 회로는 Qubit 수에 관계없이 단 한 번의 절단으로 두 개의 서브서킷으로 분할할 수 있습니다.

wc-6.png

절단 비친화적 회로

독립적인 파티션을 형성하는 데 필요한 절단 횟수가 Qubit 수나 깊이에 따라 크게 증가하는 경우 절단 비친화적 회로라고 합니다. 각 절단마다 추가적인 Qubit이 필요하기 때문에, 절단 횟수가 늘어날수록 유효 Qubit 수도 증가합니다. 아래는 3-Qubit Grover 회로의 가능한 절단 예시를 보여줍니다.

wc-7.png

세 번의 절단이 필요하며, 절단이 수평보다 수직에 가깝다는 것을 알 수 있습니다. 즉, 절단 횟수가 Qubit 수에 비례하여 선형적으로 증가할 것으로 예상되며, 이는 절단에 적합하지 않습니다.

참고 문헌

[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.

[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).

[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.

[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.

[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.

[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.

튜토리얼 설문조사

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

설문조사 링크

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.