회로 깊이를 줄이기 위한 게이트 커팅
이 튜토리얼에서는 멀리 떨어진 Gate들을 커팅하여 회로의 깊이를 줄이고, 라우팅으로 인해 도입되었을 SWAP Gate들을 피하는 방법을 살펴봅니다.
다음은 이 Qiskit 패턴에서 진행할 단계들입니다.
- 1단계: 문제를 양자 회로와 연산자로 매핑:
- 해밀토니안을 양자 회로에 매핑합니다.
- 2단계: 타겟 하드웨어에 맞게 최적화 [커팅 애드온 사용]:
- 회로와 관측가능량을 커팅합니다.
- 하드웨어에 맞게 서브실험을 트랜스파일합니다.
- 3단계: 타겟 하드웨어에서 실행:
- 2단계에서 얻은 서브실험을
Sampler프리미티브를 사용하여 실행합니다.
- 2단계에서 얻은 서브실험을
- 4단계: 결과 후처리 [커팅 애드온 사용]:
- 3단계의 결과를 결합하여 대상 관측가능량의 기댓값을 재구성합니다.
1단계: 매핑
Backend에서 실행할 회로 생성
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
from qiskit.circuit.library import efficient_su2
circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
circuit.draw("mpl", scale=0.8)

관측가능량 지정
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
2단계: 최적화
Backend 지정
Qiskit Runtime의 가짜 Backend 또는 하드웨어 Backend 중 하나를 제공할 수 있습니다.
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
회로를 트랜스파일하고 SWAP을 시각화하며 깊이 확인
Qubit 3과 0 사이의 Gate를 실행하기 위해 두 번의 SWAP이 필요하고, Qubit들을 초기 위치로 되돌리기 위해 다시 두 번의 SWAP이 필요한 레이아웃을 선택합니다.
from qiskit.transpiler import generate_preset_pass_manager
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=[0, 1, 2, 3]
)
transpiled_qc = pass_manager.run(circuit)
print(f"Transpiled circuit depth: {transpiled_qc.depth(lambda x: len(x.qubits) >= 2)}")
Transpiled circuit depth: 30
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, fold=-1)

인덱스를 지정하여 멀리 떨어진 Gate들을 TwoQubitQPDGate로 대체
cut_gates는 지정된 인덱스의 게이트들을 TwoQubitQPDGate로 대체하고, 각 게이트 분해에 대해 하나씩 QPDBasis 인스턴스의 리스트도 반환합니다.
from qiskit_addon_cutting import cut_gates
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]
# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)
qpd_circuit.draw("mpl", scale=0.8)

Backend에서 실행할 서브실험 생성
generate_cutting_experiments는 TwoQubitQPDGate 인스턴스를 포함하는 회로와 PauliList 형태의 관측가능량을 받습니다.
전체 크기 회로의 기댓값을 시뮬레이션하기 위해, 분해된 게이트들의 결합 준확률 분포로부터 다수의 서브실험이 생성되고 하나 이상의 Backend에서 실행됩니다. 분포에서 추출되는 샘플의 수는 num_samples로 제어되며, 각 고유 샘플마다 결합된 하나의 계수가 주어집니다. 계수가 계산되는 방식에 대한 자세한 내용은 설명 자료를 참고해 주세요.
참고: generate_cutting_experiments의 observables 키워드 인자는 PauliList 타입입니다. 관측가능량 항의 계수와 위상은 문제 분해 및 서브실험 실행 중에는 무시됩니다. 이들은 기댓값 재구성 시 다시 적용될 수 있습니다.
import numpy as np
from qiskit_addon_cutting import generate_cutting_experiments
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)
선택된 커팅에 대한 샘플링 오버헤드 계산
여기서는 세 개의 CNOT Gate를 커팅하여 의 샘플링 오버헤드가 발생합니다.
회로 커팅에서 발생하는 샘플링 오버헤드에 대한 자세한 내용은 설명 자료를 참고해 주세요.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
멀리 떨어진 Gate를 커팅한 후 QPD 서브실험이 더 얕아짐을 확인
다음은 QPD 회로에서 임의로 선택된 서브실험의 예시입니다. 그 깊이가 절반 이상 줄어든 것을 확인할 수 있습니다. 더 깊은 회로의 기댓값을 재구성하려면 이러한 확률적 서브실험을 많이 생성하고 평가해야 합니다.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pass_manager.run(subexperiments[100])
print(
f"Original circuit depth after transpile: {transpiled_qc.depth(lambda x: len(x.qubits) >= 2)}"
)
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth(lambda x: len(x.qubits) >= 2)}"
)
transpiled_qpd_circuit.draw("mpl", scale=0.8, idle_wires=False, fold=-1)
Original circuit depth after transpile: 30
QPD subexperiment depth after transpile: 7

Backend용 서브실험 준비
# Transpile the subeperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pass_manager.run(subexperiments)
3단계: 실행
Qiskit Runtime Sampler 프리미티브를 사용하여 서브실험 실행
from qiskit_ibm_runtime import SamplerV2
# Set up the Qiskit Runtime Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)
# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()
4단계: 후처리
기댓값 재구성
각 관측가능량 항에 대한 기댓값을 재구성하고 이를 결합하여 원래 관측가능량의 기댓값을 재구성합니다.
from qiskit_addon_cutting import reconstruct_expectation_values
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)
# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
재구성된 기댓값과 원래 회로 및 관측가능량의 정확한 기댓값 비교
from qiskit_aer.primitives import EstimatorV2
estimator = EstimatorV2()
exact_expval = estimator.run([(circuit, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.44018555
Exact expectation value: 0.50497603
Error in estimation: -0.06479049
Relative error in estimation: -0.12830408