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

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

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

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

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

คู่มือนี้สาธิตตัวอย่างการทำงานของ wire cuts ด้วยแพ็กเกจ qiskit-addon-cutting โดยครอบคลุมการสร้าง expectation values ของ Circuit 7 Qubit ขึ้นใหม่โดยใช้ wire cutting

wire cut ถูกแทนในแพ็กเกจนี้ด้วยคำสั่งสอง-Qubit Move ซึ่งนิยามเป็นการ reset Qubit ตัวที่สองที่คำสั่งกระทำ ตามด้วยการสลับ Qubit ทั้งสอง การดำเนินการนี้เทียบเท่ากับการถ่ายโอน state ของ Qubit แรกไปยัง Qubit ที่สอง ในขณะที่ทิ้ง state ที่เข้ามาของ Qubit ที่สองในเวลาเดียวกัน

แพ็กเกจได้รับการออกแบบให้สอดคล้องกับวิธีที่ต้องจัดการ wire cuts เมื่อกระทำบน physical Qubit ตัวอย่างเช่น wire cut อาจนำ state ของ physical Qubit nn และดำเนินการต่อใน physical Qubit mm หลังการตัด คิดได้ว่า "instruction cutting" เป็น framework รวมที่พิจารณาทั้ง wire cuts และ gate cuts ภายใน formalism เดียวกัน (เนื่องจาก wire cut เป็นแค่คำสั่ง Move ที่ถูกตัด) การใช้ framework นี้สำหรับ wire cutting ยังช่วยให้นำ Qubit กลับมาใช้ใหม่ได้ ซึ่งอธิบายในส่วนการตัด wire แบบ manual

คำสั่ง single-Qubit CutWire ทำหน้าที่เป็น interface ที่ง่ายและเป็น abstract มากขึ้นสำหรับการทำงานกับ wire cuts ช่วยให้คุณระบุตำแหน่งใน Circuit ที่ wire ควรถูกตัดในระดับสูง และให้ circuit cutting addon แทรกคำสั่ง Move ที่เหมาะสมให้คุณ

ตัวอย่างต่อไปนี้สาธิตการสร้าง expectation value ใหม่หลัง wire cutting คุณจะสร้าง Circuit ที่มี non-local gates หลายตัวและกำหนด observables สำหรับการประมาณ

# 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 import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
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.instructions import Move, CutWire
from qiskit_addon_cutting import (
partition_problem,
generate_cutting_experiments,
cut_wires,
expand_observables,
reconstruct_expectation_values,
)

qc_0 = QuantumCircuit(7)
for i in range(7):
qc_0.rx(np.pi / 4, i)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
qc_0.cx(3, 4)
qc_0.cx(3, 5)
qc_0.cx(3, 6)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)

# Define observable
observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])

# Draw circuit
qc_0.draw("mpl")

Output of the previous code cell

ตัด wire โดยใช้คำสั่ง CutWire ระดับสูง

ต่อไป ทำการตัด wire โดยใช้คำสั่ง single-Qubit CutWire บน Qubit q3q_3 เมื่อ subexperiments พร้อมสำหรับการประมวลผล ให้ใช้ฟังก์ชัน cut_wires() เพื่อแปลง CutWire เป็นคำสั่ง Move บน Qubit ที่จัดสรรใหม่

qc_1 = QuantumCircuit(7)
for i in range(7):
qc_1.rx(np.pi / 4, i)
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.append(CutWire(), [3])
qc_1.cx(3, 4)
qc_1.cx(3, 5)
qc_1.cx(3, 6)
qc_1.append(CutWire(), [3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)

qc_1.draw("mpl")

Output of the previous code cell

หมายเหตุเกี่ยวกับการขยาย observables

เมื่อ Circuit ถูกขยายผ่าน wire cuts หนึ่งตัวหรือมากกว่า observable จะต้องได้รับการอัปเดตเพื่อรองรับ Qubit พิเศษที่ถูกนำเข้ามา แพ็กเกจ qiskit-addon-cutting มีฟังก์ชันอำนวยความสะดวก expand_observables() ซึ่งรับออบเจกต์ PauliList และ Circuit เดิมและที่ขยายแล้วเป็น argument แล้วคืนค่า PauliList ใหม่

PauliList ที่คืนมานี้จะไม่มีข้อมูลเกี่ยวกับสัมประสิทธิ์ของ observable เดิม แต่สิ่งเหล่านี้สามารถละเว้นได้จนกว่าจะสร้าง expectation value สุดท้ายใหม่

# Transform CutWire instructions to Move instructions
qc_2 = cut_wires(qc_1)

# Expand the observable to match the new circuit size
expanded_observable = expand_observables(observable.paulis, qc_0, qc_2)
print(f"Expanded Observable: {expanded_observable}")
qc_2.draw("mpl")
Expanded Observable: ['ZIIIIIIII', 'IIIZIIIII', 'IIIIIIIIZ']

Output of the previous code cell

แบ่งพาร์ติชัน Circuit และ observable

ตอนนี้สามารถแยกปัญหาออกเป็นพาร์ติชันได้ ทำสิ่งนี้โดยใช้ฟังก์ชัน partition_problem() พร้อม partition labels เพื่อระบุวิธีแยก Circuit Qubit ที่มี partition label เดียวกันจะถูกจัดกลุ่มเข้าด้วยกัน และ non-local gates ที่ครอบคลุมมากกว่าหนึ่งพาร์ติชันจะถูกตัด

หากไม่ได้ระบุ partition labels การแบ่งพาร์ติชันจะถูกกำหนดโดยอัตโนมัติตาม connectivity ของ Circuit อ่านส่วนถัดไปเกี่ยวกับการตัด wire แบบ manual สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการระบุ partition labels

partitioned_problem = partition_problem(
circuit=qc_2,
observables=expanded_observable,
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

print(f"Subobservables to measure: \n{subobservables}\n")
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
subcircuits[0].draw("mpl")
Subobservables to measure:
{0: PauliList(['IIIII', 'ZIIII', 'IIIIZ']), 1: PauliList(['ZIII', 'IIII', 'IIII'])}

Sampling overhead: 256.0

Output of the previous code cell

subcircuits[1].draw("mpl")

Output of the previous code cell

ในรูปแบบการแบ่งพาร์ติชันนี้ คุณตัดสอง wire ส่งผลให้มี sampling overhead 444^4

สร้าง subexperiments สำหรับประมวลผลและประมวลผลผลลัพธ์

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

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

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

# Generate subexperiments
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=2**12)
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 ที่แน่นอน

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

# Compute the exact expectation value using the `qiskit_aer` package.
estimator = EstimatorV2()
exact_expval = estimator.run([(qc_0, 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: 1.45965266
Exact expectation value: 1.59099026
Error in estimation: -0.1313376
Relative error in estimation: -0.08255085
หมายเหตุเกี่ยวกับสัมประสิทธิ์ observable

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

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

ตัด wire โดยใช้คำสั่ง Move ระดับต่ำ

ข้อจำกัดอย่างหนึ่งของการใช้คำสั่ง CutWire ระดับสูงกว่าคือไม่อนุญาตให้นำ Qubit กลับมาใช้ใหม่ หากต้องการสิ่งนี้สำหรับ cutting experiment คุณสามารถวางคำสั่ง Move ด้วยตนเองแทน อย่างไรก็ตาม เนื่องจากคำสั่ง Move ทิ้ง state ของ Qubit ปลายทาง จึงเป็นสิ่งสำคัญที่ Qubit นี้จะไม่แชร์ entanglement กับส่วนที่เหลือของระบบ มิฉะนั้น การดำเนินการ reset จะทำให้ state ของ Circuit ยุบตัวบางส่วนหลัง wire cut

โค้ดด้านล่างทำ wire cut บน Qubit q3q_3 สำหรับ Circuit ตัวอย่างเดิมที่แสดงก่อนหน้านี้ ความแตกต่างที่นี่คือคุณสามารถนำ Qubit กลับมาใช้ใหม่ได้โดยการกลับคืนการดำเนินการ Move ตรงที่ wire cut ที่สองถูกทำ (อย่างไรก็ตาม สิ่งนี้ไม่สามารถทำได้เสมอไปและขึ้นอยู่กับ Circuit ที่ถูกตัด)

qc_1 = QuantumCircuit(8)
for i in [*range(4), *range(5, 8)]:
qc_1.rx(np.pi / 4, i)
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.append(Move(), [3, 4])
qc_1.cx(4, 5)
qc_1.cx(4, 6)
qc_1.cx(4, 7)
qc_1.append(Move(), [4, 3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)

# Expand observable
observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])
qc_1.draw("mpl")

Output of the previous code cell

Circuit ด้านบนตอนนี้สามารถแบ่งพาร์ติชันและสร้าง cutting experiments ได้ เพื่อระบุวิธีแบ่งพาร์ติชัน Circuit อย่างชัดเจน คุณสามารถเพิ่ม partition labels ลงในฟังก์ชัน partition_problem() Qubit ที่มี partition label เดียวกันจะถูกจัดกลุ่มเข้าด้วยกัน และ non-local gates ที่ครอบคลุมมากกว่าหนึ่งพาร์ติชันจะถูกตัด key ของ dictionary ที่ผลลัพธ์จาก partition_problem() จะตรงกับ key ที่ระบุใน label string

partitioned_problem = partition_problem(
circuit=qc_1,
partition_labels="AAAABBBB",
observables=observable_expanded.paulis,
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

print(f"Subobservables to measure: \n{subobservables}\n")
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
subcircuits["A"].draw("mpl")
Subobservables to measure:
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']), 'B': PauliList(['ZIII', 'IIII', 'IIII'])}

Sampling overhead: 256.0

Output of the previous code cell

subcircuits["B"].draw("mpl")

Output of the previous code cell

ตอนนี้สามารถสร้าง cutting experiments และสร้าง expectation value ใหม่ได้ในลักษณะเดียวกับส่วนก่อนหน้า

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

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