기대값 추정을 위한 와이어 절단
예상 실행 시간: 헤론 프로세서 기준 22초 (참고: 이는 단지 추정치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)
학습 성과
- 대규모 회로를 더 작은 하위 회로로 분할하여 노이즈의 영향을 줄이는 방법
qiskit-addon-cutting
전제조건
- 이 워크플로우에서 사용되는 샘플러 프리미티브를 사용하여
배경
회로 분할(Circuit-knitting)은 회로를 게이트나 큐비트의 수가 더 적은 여러 개의 작은 하위 회로로 나누는 다양한 방법을 포괄하는 총칭입니다. 각 하위 회로는 독립적으로 실행될 수 있으며, 최종 결과는 각 하위 회로의 산출물에 대해 고전적인 후처리를 수행함으로써 얻어집니다. 이 기법은 Circuit cutting Qiskit 애드온에서 사용할 수 있습니다. 이 기법에 대한 자세한 설명은 문서와 기타 입문 자료를 참고하시기 바랍니다.
이 튜토리얼은 회로를 전선을 따라 분할하는 ‘와이어 커팅’이라는 기법에 중점을 둡니다 [1], [2]. 고전 회로에서는 분할 지점에서의 결과가 결정론적으로 결정될 수 있으며, 0이거나 1이므로 분할이 간단하다는 점에 유의하십시오. 그러나 일반적으로, 절단 시점에서의 큐비트의 상태는 혼합 상태이다. 따라서 각 하위 회로는 서로 다른 기저(일반적으로 파울리 기저 [3], [4] 와 같은 단층 촬영적으로 완전한 기저)에서 여러 번 측정되어야 하며, 이에 따라 고유 상태로 준비되어야 한다. 아래 그림( [7] 에서 인용)은 4큐비트 GHZ 상태를 세 개의 하위 회로로 분할하는 와이어 커팅의 예를 보여준다. 여기서 는 기저 집합(일반적으로 Pauli X, Y, Z)을 나타내며, 는 고유상태 집합(일반적으로 , , 및 )을 나타냅니다.

각 하위 회로는 큐비트와 게이트의 수가 적기 때문에, 노이즈에 덜 취약할 것으로 예상됩니다. 이 튜토리얼에서는 이 방법을 사용하여 시스템의 노이즈를 효과적으로 억제할 수 있는 사례를 보여줍니다.
요구사항
이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:
- Qiskit SDK v2.0 또는 이후 버전, 시각화 지원 기능 포함
- Qiskit Runtime v0.22 또는 이후 (
pip install qiskit-ibm-runtime) - 회로 생성 Qiskit 애드온 v0.10.0 이상 (
pip install qiskit-addon-cutting) - Qiskit 애드온 유틸리티 0.3 이상 (
pip install qiskit-addon-utils) - Qiskit Aer (
pip install qiskit-aer)
설정
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_aer import AerSimulator
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소규모 시뮬레이터 예시
이 튜토리얼에서는 1차원( 1D ) 다체 국소화(MBL) 회로를 시뮬레이션하기 위한 Qiskit 패턴 을 구현합니다. MBL 회로는 하드웨어 효율이 높은 회로이며, 두 가지 매개변수 와 로 정의됩니다. 를 로 설정하고, 모든 큐비트에 대해 에서 초기 상태를 준비하면, 의 값과 무관하게 모든 큐비트 위치 에 대해 의 이상적인 기대값은 가 됩니다. 이 회로에 대한 자세한 내용은 이 기사에서 확인할 수 있습니다.
소음이 없는 시뮬레이터에서는 회로 절단 유무에 관계없이 얻어지는 기대값이 동일하다는 점에 유의하십시오.
1단계: 고전적 입력을 양자 문제에 매핑하기
1D MBL 회로를 구성하십시오
먼저, 1D MBL 회로를 구성하는 함수를 제시합니다.
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)num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)Output:
우리는 에 대해 모든 큐비트에 대한 평균 기대값 을 계산한다. 의 이상적인 기대값은 이므로, 의 이상적인 기대값 또한 이다. 매개변수 는 무작위로 선택된다.
np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis회로를 분할하려면 원하는 위치에 CutWire 를 삽입하여 주석을 달아야 합니다. 이 튜토리얼에서는 균등 분할 방식을 선택합니다. MBL 회로는 함수 내의 설정 use_cut=True (setting)이 큐비트 뒤에 주석을 올바르게 삽입하도록 설계되었으며, 여기서 는 원래 회로의 큐비트 수를 나타냅니다. 또한 무작위로 생성된 매개변수를 회로에 적용했습니다.
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)Output:
2단계: 양자 하드웨어 실행을 위한 문제 최적화
회로를 더 작은 하위 회로로 분할하십시오
qiskit-addon-cutting이제 를 사용하여 회로를 두 개의 더 작은 하위 회로로 나눕니다. qiskit-addon-cutting 큐비트 수를 적절히 조정하여 선 절단 지점을 분할하는 가상 Move 게이트를 추가합니다. 이제 이 가상 게이트를 사용하여 회로를 만들겠습니다. 전선이 하나 끊어졌으므로, 관련 큐비트의 수는 1개 증가하게 됩니다.
mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)Output:
관측 가능한 항을 구성하고 확장한다
앞서 정의한 바와 같이, 관측량은 각 큐비트에 대한 의 평균이 될 것입니다. 그러나 가상 Move 게이트를 삽입하면 회로 내의 유효 큐비트 수가 증가한다. 또한 큐비트 수의 이러한 변화를 반영하기 위해 관측량도 그에 맞게 확장되어야 한다. 가상 Move 게이트를 위해 추가된 큐비트에 대해 관측 연산자는 항상 사소하게 작용한다는 점에 유의하십시오( 와 같이).
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observableOutput:
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obsOutput:
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
이제 회로를 게이트를 Move 따라 분할할 수 있으며, 이를 통해 하위 회로와 함께 각 하위 회로와 연관된 원래 관측량의 일부인 하위 관측량을 얻을 수 있다.
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables여기서는 두 개의 하위 회로를 시각화합니다:
subcircuits[0].draw("mpl", fold=-1)Output:
subcircuits[1].draw("mpl", fold=-1)Output:
연산을 Move 사용하여 관측 가능한 영역을 확장하려면 데이터 구조가 PauliList 필요합니다. 원래 회로의 기대값을 재구성하려면, 관측량을 형식으로 SparsePauliOp 표현해야 합니다.
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)앞서 논의한 바와 같이, 각 측정 시마다 상류 회로는 파울리 기저에서 측정되어야 하며, 하류 회로는 해당 기저의 고유상태로 준비되어야 한다. 이 함수는 generate_cutting_experiments 재구성에 필요한 모든 회로와 각 회로에 해당하는 계수를 생성합니다. 자세한 내용은 이 논문 에서 확인하실 수 있습니다.
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)회로를 백엔드로 변환합니다
시뮬레이션만 포함된 첫 번째 예제에서는 회로를 백엔드의 기본 게이트 집합으로 변환합니다:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)Output:
<IBMBackend('ibm_fez')>
3단계: Qiskit primitives 명령어로 실행합니다
이제 각 하위 실험을 실행하십시오:
pm_basis = generate_preset_pass_manager(
optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
label: pm_basis.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}sampler = SamplerV2(mode=AerSimulator())
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in basis_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_expvalOutput:
np.float64(0.9953821063041687)
methods = [
"Uncut",
"Wire cut",
]
values = [
1,
reconstructed_expval,
] # since the ideal expectation value in noiseless simulation is +1
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)Output:
Text(0, 0.5, '$M_Z$')
대규모 하드웨어 예시
이제 60큐비트 MBL 회로의 와이어 커팅을 시연해 보겠습니다. 미수정 버전과 수정된 버전의 회로 모두 IBM Quantum® 하드웨어에서 실행될 예정입니다:
num_qubits = 60
depth = 2
# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)
# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)
# Define observable and expand to account for the wire cut
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)
# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
# Obtain subexperiments and coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
sampler.options.environment.job_tags = ["TUT_WC"]
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
results = {label: job.result() for label, job in jobs.items()}
# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]
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)
# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()Output:
uncut_expvalOutput:
0.9202473958333336
다음 단계
이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:
참조
[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). 소형 양자 컴퓨터에서 대규모 양자 회로를 시뮬레이션합니다. 물리적 검토 편지, 125(15), 150504.
[2] Tang, W., Tomesh, T., Suchara, M., 라슨, J., & 마르토노시, M. (2021, 4월). Cutqc: 대규모 양자 회로 평가에 소형 양자 컴퓨터 사용. 프로그래밍 언어 및 운영 체제를 위한 아키텍처 지원에 관한 제26회 ACM 국제 컨퍼런스 논문집(473-486쪽).
[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). 최대 가능성 단층 촬영을 통한 양자 회로 절단. npj 양자 정보, 7(1), 64.
[4] Majumdar, R., & Wood, C. J. (2022). 오류로 인해 양자 회로 절단이 완화되었습니다. arXiv 미리 인쇄 arXiv:2211.13431.
[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., 세샤드리, P. V., & 심한, Y. (2023). 퀀텀-클래식 워크로드 병렬화: 분할 기법의 영향 프로파일링. 2023년 IEEE 양자 컴퓨팅 및 엔지니어링(QCE) 국제 컨퍼런스(1권, 990-1000페이지). IEEE.
[6] Bhoumik, D., Majumdar, R., 사하, A., & Sur-Kolay, S. (2023). 노이즈 및 시간 최적화를 통한 양자 회로의 분산 스케줄링. arXiv 미리 인쇄 arXiv:2309.06005.
[7] Majumdar, R. (2024). 이산 양자 컴퓨팅 회로에서 자원 및 잡음의 효율적 저감 (박사 학위 논문, 인도 통계 연구소 - 콜카타). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y