Wire Cutting ในรูปแบบคำสั่ง `Move` แบบสอง Qubit
ในบทแนะนำนี้ เราจะสร้างค่าความคาดหวัง (expectation values) ของ Circuit ขนาดเจ็ด Qubit ขึ้นใหม่ โดยแบ่งมันออกเป็นสอง Circuit ขนาดสี่ Qubit ผ่านการใช้ wire cutting
นี่คือขั้นตอนที่เราจะดำเนินการใน Qiskit pattern นี้:
- ขั้นตอนที่ 1: แมปปัญหาไปยัง Quantum Circuit และ Operator:
- แมป Hamiltonian ลงบน Quantum Circuit
- ขั้นตอนที่ 2: ปรับให้เหมาะสมสำหรับฮาร์ดแวร์เป้าหมาย [ใช้ cutting addon]:
- ตัด Circuit และ Observable
- Transpile subexperiment สำหรับฮาร์ดแวร์
- ขั้นตอนที่ 3: รันบนฮาร์ดแวร์เป้าหมาย:
- รัน subexperiment ที่ได้จากขั้นตอนที่ 2 โดยใช้ primitive
Sampler
- รัน subexperiment ที่ได้จากขั้นตอนที่ 2 โดยใช้ primitive
- ขั้นตอนที่ 4: ประมวลผลผลลัพธ์ [ใช้ cutting addon]:
- รวมผลลัพธ์จากขั้นตอนที่ 3 เพื่อสร้างค่าความคาดหวังของ Observable ที่ต้องการขึ้นใหม่
ขั้นตอนที่ 1: แมป
สร้าง Circuit สำหรับตัด
เริ่มต้นด้วย Circuit ที่ได้รับแรงบันดาลใจจาก Fig. 1(a) ของ arXiv:2302.03366v1
# 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
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)
<qiskit.circuit.instructionset.InstructionSet at 0x7f16ab191a80>
qc_0.draw("mpl")

ระบุ Observable
from qiskit.quantum_info import SparsePauliOp
observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])
ขั้นตอนที่ 2: ปรับให้เหมาะสม
สร้าง Circuit ใหม่ที่มีคำสั่ง Move วางอยู่ในตำแหน่งที่ต้องการตัด
จาก Circuit ข้างต้น เราต้องการวาง wire cut สองจุดบนเส้น Qubit กลาง เพื่อให้ Circuit แยกออกเป็นสอง Circuit ขนาดสี่ Qubit แต่ละอัน วิธีหนึ่งคือการวางคำสั่ง Move แบบสอง Qubit ด้วยตนเอง เพื่อย้ายสถานะจาก Qubit wire หนึ่งไปยังอีกอัน คำสั่ง Move มีความหมายเทียบเท่ากับการ reset Qubit ที่สอง ตามด้วย SWAP gate ผลของคำสั่งนี้คือโอนสถานะของ Qubit แรก (ต้นทาง) ไปยัง Qubit ที่สอง (ปลายทาง) พร้อมกับทิ้งสถานะขาเข้าของ Qubit ที่สอง เพื่อให้การทำงานนี้เป็นไปตามที่ต้องการ สิ่งสำคัญคือ Qubit ที่สอง (ปลายทาง) ต้องไม่มีการพัวพันกับส่วนอื่นของระบบ มิฉะนั้นการ reset จะทำให้สถานะของส่วนที่เหลือของระบบยุบตัวบางส่วน
ที่นี่ เราสร้าง Circuit ใหม่ที่มี Qubit เพิ่มอีกหนึ่งตัวและการดำเนินการ Move ในตำแหน่งที่กำหนด ในตัวอย่างนี้ เราสามารถนำ Qubit กลับมาใช้ใหม่ได้: Qubit ต้นทางของ Move แรกกลายเป็น Qubit ปลายทางของการดำเนินการ Move ที่สอง
หมายเหตุ: เป็นทางเลือกแทนการทำงานกับคำสั่ง Move โดยตรง อาจเลือกทำเครื่องหมาย wire cut โดยใช้คำสั่ง CutWire แบบ single-qubit ฟังก์ชัน cut_wires มีไว้เพื่อแปลง CutWire เป็นคำสั่ง Move บน Qubit ที่จัดสรรใหม่ อย่างไรก็ตาม ต่างจากวิธีด้วยตนเอง วิธีอัตโนมัตินี้ไม่อนุญาตให้นำ Qubit wire กลับมาใช้ซ้ำ ดู how-to guide ของ CutWire สำหรับรายละเอียด
from qiskit_addon_cutting.instructions import Move
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)
qc_1.draw("mpl")

สร้าง Observable เพื่อใช้กับ Circuit ใหม่
Observable นี้สอดคล้องกับ observable แต่เราต้องคำนึงถึง Qubit wire เพิ่มเติมที่ถูกเพิ่มเข้าไปอย่างถูกต้อง (กล่าวคือ เราแทรก "I" ที่ index 4) โปรดทราบว่าใน Qiskit การแสดงแบบสตริง qubit-0 จะสอดคล้องกับอักขระ Pauli ทางขวาสุด
observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])
แยก Circuit และ Observable
เช่นเดียวกับในบทแนะนำก่อนหน้า Qubit ที่มี partition label เดียวกันจะถูกจัดกลุ่มเข้าด้วยกัน และ gate ที่ไม่ใช่ local ซึ่งครอบคลุมมากกว่าหนึ่ง partition จะถูกตัด
from qiskit_addon_cutting import partition_problem
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
แสดงภาพปัญหาที่ถูกแยกออก
subobservables
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']),
'B': PauliList(['ZIII', 'IIII', 'IIII'])}
subcircuits["A"].draw("mpl")

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

คำนวณ sampling overhead สำหรับการตัดที่เลือก
ที่นี่เราตัดสอง wire ส่งผลให้เกิด sampling overhead ที่
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ sampling overhead ที่เกิดจาก circuit cutting โปรดดู เนื้อหาอธิบาย
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 256.0
สร้าง subexperiment เพื่อรันบน Backend
generate_cutting_experiments รับ argument circuits/observables เป็น dictionary ที่แมป qubit partition label ไปยัง subcircuit/subobservables ที่เกี่ยวข้อง
เพื่อจำลองค่าความคาดหวังของ Circuit ขนาดเต็ม subexperiment จำนวนมากจะถูกสร้างขึ้นจากการกระจาย quasiprobability แบบร่วม (joint quasiprobability distribution) ของ gate ที่ถูกแยกออก จากนั้นรันบน Backend หนึ่งหรือหลายตัว จำนวนตัวอย่างที่นำมาจากการกระจายถูกควบคุมโดย num_samples และจะมี coefficient รวมหนึ่งค่าสำหรับแต่ละตัวอย่างที่ไม่ซ้ำกัน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับวิธีคำนวณ coefficient โปรดดู เนื้อหาอธิบาย
from qiskit_addon_cutting import generate_cutting_experiments
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)
เลือก Backend
ที่นี่เราใช้ fake backend ซึ่งจะทำให้ Qiskit Runtime ทำงานในโหมด local (กล่าวคือ บน local simulator)
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
backend = FakeManilaV2()
เตรียม subexperiment สำหรับ Backend
เราต้อง transpile Circuit โดยใช้ Backend เป็นเป้าหมายก่อนส่งไปยัง Qiskit Runtime
from qiskit.transpiler import generate_preset_pass_manager
# Transpile the subexperiments to ISA circuits
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()
}
ขั้นตอนที่ 3: รัน
รัน subexperiment โดยใช้ Qiskit Runtime Sampler primitive
from qiskit_ibm_runtime import SamplerV2, Batch
# 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()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
ขั้นตอนที่ 4: ประมวลผลหลังการรัน
สร้างค่าความคาดหวังขึ้นใหม่
สร้างค่าความคาดหวังสำหรับแต่ละ Observable term ขึ้นใหม่ และรวมเข้าด้วยกันเพื่อสร้างค่าความคาดหวังสำหรับ Observable ดั้งเดิม
from qiskit_addon_cutting import reconstruct_expectation_values
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)
เปรียบเทียบค่าความคาดหวังที่สร้างขึ้นใหม่กับค่าความคาดหวังที่แน่นอนจาก Circuit และ Observable ดั้งเดิม
from qiskit_aer.primitives import EstimatorV2
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.51319069
Exact expectation value: 1.59099026
Error in estimation: -0.07779957
Relative error in estimation: -0.04890009