การตัดสายสัญญาณสำหรับการประมาณค่าความคาดหวัง
ประมาณการใช้งาน: 22 วินาทีบนโปรเซสเซอร์ Heron (หมายเหตุ: นี่เป็นเพียงการประมาณการเท่านั้น เวลาจริงอาจแตกต่างออกไป)
ผลการเรียนรู้
หลังจากผ่าน tutorial นี้ ผู้ใช้ควรเข้าใจ:
- วิธีใช้
qiskit-addon-cuttingเพื่อแบ่ง Circuit ขนาดใหญ่ออกเป็น subcircuit ขนาดเล็ก ซึ่งช่วยลดผลกระทบจากสัญญาณรบกวน
ข้อกำหนดเบื้องต้น
เราแนะนำให้ผู้ใช้คุ้นเคยกับหัวข้อต่อไปนี้ก่อนผ่าน tutorial นี้:
- การใช้ Sampler primitive ซึ่งใช้ใน workflow นี้
พื้นหลัง
Circuit-knitting เป็นคำรวมที่ครอบคลุมวิธีการต่างๆ ในการแบ่ง Circuit ออกเป็น subcircuit ขนาดเล็กหลายๆ ชิ้น ซึ่งประกอบด้วย Gate หรือ Qubit น้อยลง แต่ละ subcircuit สามารถรันได้อิสระ และผลลัพธ์สุดท้ายได้จากการประมวลผลแบบคลาสสิกผ่านผลลัพธ์ของแต่ละ subcircuit เทคนิคนี้เข้าถึงได้ผ่าน Circuit cutting Qiskit addon ดูที่ เอกสารประกอบ พร้อมด้วย เนื้อหาเบื้องต้นอื่นๆ สำหรับคำอธิบายอย่างละเอียดของเทคนิคนี้
tutorial นี้เน้นที่วิธีที่เรียกว่า wire cutting ซึ่ง Circuit ถูกแบ่งตามสายสัญญาณ [1], [2] สังเกตว่าในวงจรคลาสสิก การแบ่งนั้นง่ายเพราะผลลัพธ์ที่จุดแบ่งสามารถกำหนดได้แน่นอน คือ 0 หรือ 1 อย่างไรก็ตาม สถานะของ Qubit ที่จุดตัดนั้น โดยทั่วไปจะเป็น mixed state ดังนั้นแต่ละ subcircuit จึงต้องวัดหลายครั้งใน basis ที่แตกต่างกัน (โดยทั่วไปจะเป็น basis ที่ครบถ้วนสำหรับ tomography เช่น Pauli basis [3], [4]) และเตรียมใน eigenstate ที่สอดคล้องกัน ภาพด้านล่าง (ที่มา: [7]) แสดงตัวอย่างของ wire cutting สำหรับ GHZ state ขนาดสี่ Qubit ออกเป็นสาม subcircuit ที่นี่ แทนชุดของ basis (โดยทั่วไปคือ Pauli X, Y และ Z) และ แทนชุดของ eigenstate (โดยทั่วไปคือ , , และ )
เนื่องจากแต่ละ subcircuit มี Qubit และ Gate น้อยกว่า จึงคาดว่าจะได้รับผลกระทบจากสัญญาณรบกวนน้อยลง tutorial นี้แสดงตัวอย่างที่วิธีนี้สามารถใช้เพื่อลดสัญญาณรบกวนในระบบได้อย่างมีประสิทธิภาพ
สิ่งที่ต้องการ
ก่อนเริ่มบทเรียนนี้ ต้องติดตั้งสิ่งต่อไปนี้:
- Qiskit SDK v2.0 หรือใหม่กว่า พร้อมรองรับ visualization
- Qiskit Runtime v0.22 หรือใหม่กว่า (
pip install qiskit-ibm-runtime) - Circuit cutting Qiskit addon v0.10.0 หรือใหม่กว่า (
pip install qiskit-addon-cutting) - Qiskit addon utils 0.3 หรือใหม่กว่า (
pip install qiskit-addon-utils) - Qiskit Aer (
pip install qiskit-aer)
ตั้งค่า
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
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
ตัวอย่างขนาดเล็กด้วย simulator
tutorial นี้ใช้ Qiskit pattern เพื่อจำลอง Circuit Many-Body Localization (MBL) แบบหนึ่งมิติ (1D) วงจร MBL เป็นวงจรที่มีประสิทธิภาพสำหรับฮาร์ดแวร์และมีพารามิเตอร์สองตัวคือ และ เมื่อกำหนด เป็น และเตรียมสถานะเริ่มต้นใน สำหรับ Qubit ทั้งหมด ค่าความคาดหวังในอุดมคติของ จะเท่ากับ สำหรับทุก Qubit ตำแหน่ง โดยไม่ขึ้นกับค่าของ รายละเอียดเพิ่มเติมเกี่ยวกับ Circuit นี้มีอยู่ใน บทความ นี้
โปรดทราบว่าใน simulator ที่ไม่มีสัญญาณรบกวน ค่าความคาดหวังที่ได้จากการใช้และไม่ใช้ circuit cutting จะเท่ากัน
ขั้นตอนที่ 1: แมปอินพุตแบบคลาสสิกไปยังปัญหาควอนตัม
สร้าง Circuit MBL แบบ 1D
ก่อนอื่น เราแสดงฟังก์ชันสำหรับสร้าง Circuit MBL แบบ 1D
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)
เราคำนวณค่าความคาดหวังเฉลี่ย สำหรับ Qubit ทั้งหมดสำหรับ เนื่องจากค่าความคาดหวังในอุดมคติของ ค่าความคาดหวังในอุดมคติของ ก็เป็น เช่นกัน พารามิเตอร์ ถูกเลือกแบบสุ่ม
np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
Circuit ต้องถูกระบุตำแหน่งโดยแทรก CutWire ในตำแหน่งที่ต้องการเพื่อแบ่งพาร์ติชัน สำหรับ tutorial นี้ เราเลือกการแบ่งที่เท่ากัน Circuit MBL ถูกออกแบบมาเพื่อให้การตั้งค่า use_cut=True ในฟังก์ชันแทรกการระบุตำแหน่งอย่างถูกต้องหลังจาก Qubit โดยที่ คือจำนวน Qubit ในวงจรดั้งเดิม เรายังกำหนดพารามิเตอร์ที่สร้างแบบสุ่มให้กับ Circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
ขั้นตอนที่ 2: ปรับ Circuit ให้เหมาะสมสำหรับการรันบนฮาร์ดแวร์ควอนตัม
ตัด Circuit ออกเป็น subcircuit ขนาดเล็ก
ตอนนี้เราแบ่ง Circuit ออกเป็น subcircuit ขนาดเล็กสองส่วนโดยใช้ qiskit-addon-cutting qiskit-addon-cutting เพิ่ม gate Move เสมือนเพื่อแยกตำแหน่ง wire cut โดยปรับจำนวน Qubit ให้เหมาะสม ตอนนี้เราสร้าง Circuit ด้วย gate เสมือนนี้ เนื่องจากมี wire cut หนึ่งจุด จำนวน Qubit ที่เกี่ยวข้องจะเพิ่มขึ้น 1
mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)
สร้างและขยาย observable
observable ตามที่กำหนดไว้ก่อนหน้า จะเป็นค่าเฉลี่ยของ บนแต่ละ Qubit อย่างไรก็ตาม หลังจากแทรก gate Move เสมือน จำนวน Qubit ที่มีผลในวงจรจะเพิ่มขึ้น observable จึงต้องขยายตามไปด้วยเพื่อรองรับการเปลี่ยนแปลงจำนวน Qubit นี้ โปรดทราบว่า observable จะกระทำอย่างง่าย (เหมือน ) บน Qubit เพิ่มเติมที่เพิ่มมาสำหรับ gate Move เสมือน
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
ตอนนี้ Circuit สามารถแบ่งพาร์ติชันตาม gate Move และเราได้ subcircuit รวมถึง subobservable ซึ่งเป็นส่วนของ observable เดิมที่เกี่ยวข้องกับแต่ละ subcircuit
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
ที่นี่เราแสดง subcircuit ทั้งสอง:
subcircuits[0].draw("mpl", fold=-1)
subcircuits[1].draw("mpl", fold=-1)
การขยาย observable โดยใช้การดำเนินการ Move ต้องใช้โครงสร้างข้อมูล PauliList ในการ reconstruct ค่าความคาดหวังของ Circuit เดิม เราต้องการ observable ในรูปแบบ SparsePauliOp
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
ดังที่กล่าวไว้ก่อนหน้า สำหรับแต่ละ cut Circuit ด้านต้นน้ำต้องวัดใน Pauli basis และ Circuit ด้านปลายน้ำต้องเตรียมใน eigenstate ของ basis นั้น ฟังก์ชัน generate_cutting_experiments สร้าง Circuit ที่จำเป็นทั้งหมดและ coefficient ที่เกี่ยวข้องกับแต่ละ Circuit ที่ต้องใช้ในการ reconstruct ดูรายละเอียดเพิ่มเติมใน บทความนี้
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
Transpile Circuit ลงบน backend
สำหรับตัวอย่างแรกที่เกี่ยวข้องกับการจำลองเท่านั้น เรา transpile Circuit ไปยัง basis gate set ของ backend:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<IBMBackend('ibm_fez')>
ขั้นตอนที่ 3: รันโดยใช้ Qiskit primitives
ตอนนี้รัน subexperiment แต่ละชุด:
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: ประมวลผลหลังการรันและส่งคืนผลลัพธ์ในรูปแบบคลาสสิกที่ต้องการ
ตอนนี้เราดึงผลลัพธ์ของการรัน subexperiment แต่ละชุดและ reconstruct ค่าความคาดหวังของ Circuit ที่ไม่ได้ตัด:
# 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_expval
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)
Text(0, 0.5, '$M_Z$')
ตัวอย่างขนาดใหญ่บนฮาร์ดแวร์
ตอนนี้เราสาธิต wire cutting สำหรับ Circuit MBL ขนาด 60 Qubit ทั้ง Circuit ที่ไม่ถูกตัดและ Circuit ที่ถูกตัดจะรันบนฮาร์ดแวร์ 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()
uncut_expval
0.9202473958333336
ขั้นตอนถัดไป
ถ้าพบว่าเนื้อหานี้น่าสนใจ คุณอาจสนใจเนื้อหาต่อไปนี้:
References
[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.
[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).
[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.
[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.
[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.
[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.
[7] Majumdar, R. (2024). Efficient Reduction of Resources and Noise in Discrete Quantum Computing Circuits (Doctoral dissertation, Indian Statistical Institute - Kolkata). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y