ข้ามไปยังเนื้อหาหลัก

เริ่มต้นใช้งาน circuit cutting ด้วย gate cuts

เวอร์ชันของแพ็กเกจ

โค้ดในหน้านี้พัฒนาโดยใช้ requirements ต่อไปนี้ แนะนำให้ใช้เวอร์ชันเหล่านี้หรือใหม่กว่า

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
qiskit-addon-cutting~=0.10.0

คู่มือนี้สาธิตตัวอย่างการทำงานสองแบบของ gate cuts ด้วยแพ็กเกจ qiskit-addon-cutting ตัวอย่างแรกแสดงวิธีลด depth ของ Circuit (จำนวนคำสั่งของ Circuit) โดยการตัด entangling gates บน Qubit ที่ไม่ติดกันซึ่งจะต้องมีค่าใช้จ่าย SWAP เพิ่มเติมเมื่อ transpile ไปยังฮาร์ดแวร์ ตัวอย่างที่สองครอบคลุมวิธีใช้ gate cutting เพื่อลด circuit width (จำนวน Qubit) โดยการแบ่ง Circuit ออกเป็น Circuit หลายตัวที่มี Qubit น้อยกว่า

ทั้งสองตัวอย่างจะใช้ ansatz efficient_su2 และสร้าง observable เดียวกันขึ้นใหม่

Gate cutting เพื่อลด circuit depth

workflow ต่อไปนี้ลด depth ของ Circuit โดยการตัด distant gates เพื่อหลีกเลี่ยงชุด SWAP gates จำนวนมากที่จะถูกเพิ่มเข้ามาในกรณีอื่น

เริ่มต้นด้วย ansatz efficient_su2 โดยใช้ entanglement แบบ "circular" เพื่อนำ distant gates เข้ามา

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit.circuit.library import efficient_su2
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
from qiskit_ibm_runtime import SamplerV2, Batch
from qiskit_aer.primitives import EstimatorV2
from qiskit_addon_cutting import (
cut_gates,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")
circuit.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Output of the previous code cell

Gate CNOT แต่ละตัวระหว่าง Qubit q0q_0 และ q3q_3 จะนำ SWAP gates สอง Gate เข้ามาหลังการ transpilation (โดยสมมติว่า Qubit เชื่อมต่อกันเป็นเส้นตรง) เพื่อหลีกเลี่ยงการเพิ่ม depth นี้ คุณสามารถแทนที่ distant gates เหล่านี้ด้วยออบเจกต์ TwoQubitQPDGate โดยใช้เมธอด cut_gates() ฟังก์ชันนี้ยังคืนค่ารายการของอินสแตนซ์ QPDBasis ด้วย — หนึ่งตัวสำหรับแต่ละ decomposition

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

Output of the previous code cell

ตอนนี้ที่เพิ่มคำสั่ง cut gate แล้ว subexperiments จะมี depth ที่เล็กกว่าหลัง transpilation เมื่อเทียบกับ Circuit เดิม โค้ดด้านล่างสร้าง subexperiments โดยใช้ generate_cutting_experiments ซึ่งรับ Circuit และ observable ที่จะสร้างขึ้นใหม่

หมายเหตุเกี่ยวกับจำนวนตัวอย่าง

argument num_samples ระบุจำนวนตัวอย่างที่จะดึงจาก quasi-probability distribution และกำหนดความแม่นยำของสัมประสิทธิ์ที่ใช้ในการสร้างใหม่ การส่งค่า infinity (np.inf) จะทำให้มั่นใจว่าสัมประสิทธิ์ทั้งหมดถูกคำนวณอย่างแม่นยำ อ่านเอกสาร API เกี่ยวกับ การสร้าง weights และ การสร้าง cutting experiments สำหรับข้อมูลเพิ่มเติม

เมื่อสร้าง subexperiments แล้ว คุณสามารถ transpile และใช้ primitive Sampler เพื่อ sample distribution และสร้าง expectation values โดยประมาณใหม่ โค้ดต่อไปนี้จะสร้าง transpile และประมวลผล subexperiments จากนั้นสร้างผลลัพธ์ใหม่และเปรียบเทียบกับ expectation value ที่แน่นอน

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)

# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = pass_manager.run(subexperiments)

# Set up the Qiskit Runtime Sampler primitive, submit the subexperiments, and retrieve the results
sampler = SamplerV2(backend)
job = sampler.run(isa_subexperiments, shots=4096 * 3)
results = job.result()

# Reconstruct the results
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)

# Apply the coefficients of the original observable
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

estimator = EstimatorV2()
exact_expval = (
estimator.run([(circuit, observable, [0.4] * len(circuit.parameters))])
.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.49812826
Exact expectation value: 0.50497603
Error in estimation: -0.00684778
Relative error in estimation: -0.0135606
หมายเหตุเกี่ยวกับสัมประสิทธิ์ observable

เพื่อสร้าง expectation value ใหม่อย่างแม่นยำ ต้องนำสัมประสิทธิ์ของ observable เดิม (ซึ่งแตกต่างจากสัมประสิทธิ์ในผลลัพธ์ของ generate_cutting_experiments()) มาใช้กับผลลัพธ์ของการสร้างใหม่ เนื่องจากข้อมูลนี้สูญหายไปเมื่อสร้าง cutting experiments หรือเมื่อ observable ถูกขยาย

โดยทั่วไปสัมประสิทธิ์เหล่านี้สามารถนำมาใช้ผ่าน numpy.dot() ดังที่แสดงด้านบน

Gate cutting เพื่อลด circuit width

ส่วนนี้สาธิตการใช้ gate cutting เพื่อลด circuit width เริ่มต้นด้วย efficient_su2 เดิมแต่ใช้ entanglement แบบ "linear"

qc = efficient_su2(4, entanglement="linear", reps=2)
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")

qc.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Output of the previous code cell

จากนั้นสร้าง subcircuits และ subobservables ที่จะประมวลผลโดยใช้ฟังก์ชัน partition_problem() ฟังก์ชันนี้รับ Circuit, observable และ partitioning scheme ที่เป็น optional แล้วคืนค่า cut circuits และ observables ในรูปแบบ dictionary

การแบ่งพาร์ติชันกำหนดด้วย label string ในรูปแบบ "AABB" โดยแต่ละ label ใน string นี้สอดคล้องกับ Qubit ที่มีดัชนีเดียวกันใน argument circuit Qubit ที่มี partition label เดียวกันจะถูกจัดกลุ่มเข้าด้วยกัน และ non-local gates ที่ครอบคลุมมากกว่าหนึ่งพาร์ติชันจะถูกตัด

หมายเหตุ

kwarg observables ของ partition_problem มีประเภท PauliList สัมประสิทธิ์และเฟสของ observable term จะถูกละเว้นระหว่างการ decompose ปัญหาและการประมวลผล subexperiments ซึ่งสามารถนำกลับมาใช้ได้อีกครั้งระหว่างการสร้าง expectation value ใหม่

partitioned_problem = partition_problem(
circuit=qc, partition_labels="AABB", observables=observable.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
print(f"Subobservables: {subobservables}")
subcircuits["A"].draw("mpl", scale=0.8)
Sampling overhead: 81.0
Subobservables: {'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']), 'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}

Output of the previous code cell

subcircuits["B"].draw("mpl", scale=0.8)

Output of the previous code cell

ขั้นตอนต่อไปคือการใช้ subcircuits และ subobservables เพื่อสร้าง subexperiments ที่จะประมวลผลบน QPU โดยใช้เมธอด generate_cutting_experiments

เพื่อประมาณ expectation value ของ Circuit ขนาดเต็ม จะสร้าง subexperiments จำนวนมากจาก joint quasi-probability distribution ของ decomposed gates แล้วประมวลผลบน QPU หนึ่งตัวหรือมากกว่า จำนวนตัวอย่างที่จะดึงจาก distribution นี้ถูกควบคุมด้วย argument num_samples

โค้ดต่อไปนี้สร้าง subexperiments และประมวลผลโดยใช้ primitive Sampler บน local simulator (ในการรันบน QPU ให้เปลี่ยน backend เป็น QPU resource ที่คุณเลือก)

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)

# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=4096 * 3)
for label, subsystem_subexpts in isa_subexperiments.items()
}

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}

สุดท้าย expectation value ของ Circuit เต็มจะถูกสร้างขึ้นใหม่โดยใช้เมธอด reconstruct_expectation_values

โค้ดด้านล่างสร้างผลลัพธ์ใหม่และเปรียบเทียบกับ expectation value ที่แน่นอน

# Get expectation values for each observable term
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)

# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

estimator = EstimatorV2()
exact_expval = (
estimator.run([(qc, observable, [0.4] * len(qc.parameters))])
.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.53571896
Exact expectation value: 0.56254612
Error in estimation: -0.02682716
Relative error in estimation: -0.04768882

ขั้นตอนต่อไป

คำแนะนำ
Source: IBM Quantum docs — updated 13 ก.พ. 2569
English version on doQumentation — updated 7 พ.ค. 2569
This translation based on the English version of 11 มี.ค. 2569