ข้ามไปยังเนื้อหาหลัก
โครงการนี้อยู่ในช่วงเบต้า พบข้อผิดพลาด? สร้าง GitHub Issue มีคำถามหรือไอเดีย? ถามใน Discussions — ยินดีรับฟังความคิดเห็น!
คุณภาพการแปลในหน้านี้เป็นอย่างไร?

Circuit cutting สำหรับเงื่อนไขขอบเขตเป็นคาบ

ประมาณการใช้งาน: 2 นาทีบน ibm_boston (หมายเหตุ: นี่เป็นเพียงการประมาณเท่านั้น เวลาจริงอาจแตกต่างกัน)

ผลลัพธ์การเรียนรู้​

  • ใช้การเชื่อมต่อเสมือนเพื่อ transpile โซ่แบบคาบโดยไม่ต้องกำหนดเส้นทางให้เกตที่ขอบเขต

  • แทนที่เกตเหล่านั้นด้วยการทดลอง circuit-cutting และสร้างค่าคาดหมายขึ้นใหม่จากผลลัพธ์ของ Sampler

  • เปรียบเทียบวงจรที่ตัดและไม่ตัด โดยพิจารณาความลึกของวงจรและต้นทุนการสุ่มตัวอย่าง

ความรู้พื้นฐานที่ต้องมี​

คุณควรคุ้นเคยกับ Qiskit patterns, transpilation และ Sampler primitive

พื้นฐาน​

เงื่อนไขขอบเขตเป็นคาบเชื่อมคิวบิตสุดท้ายในโซ่เข้ากับตัวแรก ถ้าคิวบิตเหล่านี้อยู่ไกลกันบนโปรเซสเซอร์ควอนตัม การทำปฏิสัมพันธ์ของมันต้องมีการกำหนดเส้นทาง ซึ่งอาจเพิ่มเกตสองคิวบิตและเพิ่มความลึกของวงจร

Gate cutting แทนที่เกตสองคิวบิตด้วยผลรวมถ่วงน้ำหนักของการดำเนินการคิวบิตเดี่ยวและการวัด การรันการทดลองเหล่านี้และรวมผลลัพธ์จะสร้างค่าคาดหมายของวงจรต้นฉบับขึ้นใหม่ วิธีนี้ลดค่าโสหุ้ยการกำหนดเส้นทางได้ โดยแลกกับการสุ่มตัวอย่างเพิ่มเติม ดู circuit cutting explanation สำหรับการแยกส่วนแบบ quasiprobability ที่อยู่เบื้องหลัง

ก่อนอื่นคุณจะตรวจสอบเวิร์กโฟลว์นี้บนซิมูเลเตอร์ไร้สัญญาณรบกวนหกคิวบิต จากนั้นนำไปใช้กับโซ่อย่างน้อย 30 คิวบิตบนฮาร์ดแวร์ และเปรียบเทียบผลที่ตัดและไม่ตัด วงจรใช้พารามิเตอร์ที่ทราบค่าคาดหมายเชิงวิเคราะห์ ดังนั้นการเปรียบเทียบบนฮาร์ดแวร์จึงไม่ต้องใช้การจำลอง statevector ขนาดใหญ่

ข้อกำหนด​

ก่อนเริ่มบทเรียนนี้ ให้ติดตั้ง:

  • Qiskit SDK v2.3 หรือใหม่กว่า พร้อมรองรับ visualization

  • Qiskit Runtime v0.46 หรือสูงกว่า (pip install 'qiskit-ibm-runtime~=0.46.1')

  • Qiskit addon สำหรับ Circuit cutting เวอร์ชัน v0.10.0 (pip install 'qiskit-addon-cutting~=0.10.0')

  • Qiskit Aer v0.17.2 หรือใหม่กว่า (pip install 'qiskit-aer~=0.17.2')

ตัวอย่างบนฮาร์ดแวร์ยังต้องมี บัญชี IBM Quantum® Platform ที่เข้าถึง QPU ที่มีอย่างน้อย 120 คิวบิตได้ และ saved credentials ตัวอย่างซิมูเลเตอร์รันได้โดยไม่ต้องมีบัญชี

ตั้งค่า​

นำเข้าเครื่องมือที่ใช้ในทั้งสองตัวอย่าง Aer รองรับการวัดระหว่างกลางที่ gate cutting นำมาใช้

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_cutting": "qiskit-addon-cutting", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx

from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary
from qiskit.circuit.library import n_local
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.quantum_info import SparsePauliOp, Statevector
from qiskit.result import sampled_expectation_value
from qiskit.transpiler import CouplingMap, PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
GateDirection,
Optimize1qGatesDecomposition,
)
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_aer.primitives import SamplerV2 as AerSampler
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import (
Batch,
QiskitRuntimeService,
SamplerOptions,
SamplerV2,
)

ตัวอย่างซิมูเลเตอร์ขนาดเล็ก​

ใช้วงแหวนหกคิวบิตที่มีเกตเอนแทงเกิลสองชั้น อุปกรณ์จำลองมีการเชื่อมต่อแบบเส้นตรง ดังนั้นเส้นปิดของวงแหวนจึงต้องมีการกำหนดเส้นทาง เว้นแต่จะถูกตัด

ขั้นตอนที่ 1: แมปอินพุต classical ไปยังปัญหา quantum​

ฟังก์ชันต่อไปนี้สร้างชั้นการหมุน RX สลับกับเกต CX บนเส้นเชื่อมสลับกันของวงแหวน การทำซ้ำสองครั้งให้เกตขอบเขตสองตัวที่ต้องตัด ตั้งสองชั้นการหมุนแรกเป็นศูนย์ และชั้นสุดท้ายเป็น π\pi ผลลัพธ์ในอุดมคติจึงเป็น ∣1⟩⊗n|1\rangle^{\otimes n} จนถึงเฟสรวม

วัดค่าคาดหมาย Z คิวบิตเดี่ยวเฉลี่ยและค่าคาดหมาย ZZ ของคู่ที่อยู่ติดกันเฉลี่ย รวมคู่แบบคาบ:

MZ=1n∑i=0n−1⟨Zi⟩,CZZ=1n∑i=0n−1⟨ZiZ(i+1) mod n⟩.M_Z = \frac{1}{n}\sum_{i=0}^{n-1}\langle Z_i\rangle, \qquad C_{ZZ} = \frac{1}{n}\sum_{i=0}^{n-1}\langle Z_i Z_{(i+1)\bmod n}\rangle.

ค่าในอุดมคติของมันคือ −1-1 และ +1+1 ตามลำดับ พารามิเตอร์เหล่านี้เป็นเกณฑ์วัดสำหรับเวิร์กโฟลว์ แต่ไม่ได้แสดงการคำนวณที่ยากสำหรับคลาสสิก

def build_periodic_problem(num_qubits, num_reps=2):
"""Build a periodic benchmark circuit and its Z and ZZ observables."""
edges = [(i, (i + 1) % num_qubits) for i in range(num_qubits)]
circuit = n_local(
num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=edges[::2] + edges[1::2],
reps=num_reps,
)
parameters = np.zeros(circuit.num_parameters)
parameters[-num_qubits:] = np.pi
circuit = circuit.assign_parameters(parameters)

terms = [("Z", [i], 1) for i in range(num_qubits)]
terms += [("ZZ", [i, (i + 1) % num_qubits], 1) for i in range(num_qubits)]
observables = SparsePauliOp.from_sparse_list(terms, num_qubits)
return circuit, observables

num_qubits = 6
circuit, observables = build_periodic_problem(num_qubits)
ideal_expvals = np.array(
[
Statevector(circuit).expectation_value(p).real
for p in observables.paulis
]
)
print("Ideal averages:", ideal_expvals.reshape(2, num_qubits).mean(axis=1))
circuit.draw("mpl", style="iqp", fold=60)
Ideal averages: [-1. 1.]

Six-qubit periodic circuit with two boundary CX gates

ขั้นตอนที่ 2: ปรับปัญหาให้เหมาะสมสำหรับการรันบน quantum hardware​

สร้างแบบจำลองอุปกรณ์ที่มี coupling map เชิงเส้น มันให้เป้าหมายการคอมไพล์ ส่วนการรันในขั้นตอนที่ 3 จะไร้สัญญาณรบกวน

simulation_backend = GenericBackendV2(
num_qubits=num_qubits,
basis_gates=["id", "rz", "sx", "x", "cx"],
coupling_map=CouplingMap.from_line(num_qubits),
seed=1234,
)
initial_layout = list(range(num_qubits))

เพิ่ม virtual connection ระหว่าง physical qubit ที่ปลายทั้งสองของ chain ทำ Transpile หนึ่งครั้งโดยใช้ coupling map ที่ขยายนี้และ basis แบบ CX virtual connection ช่วยไม่ให้ Transpiler ต้อง route Gate ที่ขอบ แต่ Circuit ที่ได้ยังรันบนอุปกรณ์จริงไม่ได้

ค้นหาและตัด CX Gate ที่ขอบสองตัว ใช้ virtual layout กับ observable และสร้างพจน์การแยกส่วนทั้งหมดด้วย num_samples=np.inf ซึ่งทำให้สัมประสิทธิ์การแยกส่วนแม่นยำเป๊ะ แต่การวัดยังคงใช้จำนวน shot จำกัด

สุดท้าย แปลงการทดลองให้เป็น Gate เนทีฟของอุปกรณ์ และแก้ทิศทางของ Gate ตามที่จำเป็น pass เหล่านี้คงการจัดวาง physical qubit ไว้ และไม่ต้องเลือก layout กับ routing ซ้ำสำหรับทุกการทดลอง helper จะคืนค่า observable ที่แมปแล้วซึ่งต้องใช้ในการสร้างผลลัพธ์กลับ

def prepare_cut_experiments(circuit, observables, backend, initial_layout):
"""Compile with a virtual edge, cut it, and translate to native gates."""
boundary = {initial_layout[0], initial_layout[-1]}
virtual_map = CouplingMap(list(backend.coupling_map.get_edges()))
virtual_map.make_symmetric()
virtual_map.add_edge(initial_layout[-1], initial_layout[0])
virtual_map.add_edge(initial_layout[0], initial_layout[-1])

virtual_pm = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_map,
basis_gates=["rz", "sx", "x", "cx"],
initial_layout=initial_layout,
seed_transpiler=1234,
)
virtual_circuit = virtual_pm.run(circuit)
cut_indices = [
i
for i, instruction in enumerate(virtual_circuit.data)
if instruction.operation.name == "cx"
and {virtual_circuit.find_bit(q).index for q in instruction.qubits}
== boundary
]
mapped_observables = observables.apply_layout(virtual_circuit.layout)
qpd_circuit, bases = cut_gates(virtual_circuit, cut_indices)
experiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=mapped_observables.paulis,
num_samples=np.inf,
)
native_pm = PassManager(
[
BasisTranslator(
SessionEquivalenceLibrary,
target_basis=backend.operation_names,
target=backend.target,
),
GateDirection(backend.coupling_map, target=backend.target),
BasisTranslator(
SessionEquivalenceLibrary,
target_basis=backend.operation_names,
target=backend.target,
),
Optimize1qGatesDecomposition(target=backend.target),
]
)
isa_experiments = native_pm.run(experiments)
print(f"Boundary gates cut: {len(cut_indices)}")
print(f"Number of experiments: {len(isa_experiments)}")
print(
f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}"
)
return isa_experiments, coefficients, mapped_observables

isa_experiments, coefficients, mapped_observables = prepare_cut_experiments(
circuit, observables, simulation_backend, initial_layout
)
Boundary gates cut: 2
Number of experiments: 36
Sampling overhead: 81.0

การแยกส่วน CX แต่ละตัวมีหกพจน์ เมื่อมีการตัดสองจุดและใช้ basis การวัด Z ร่วมกันหนึ่งชุด จะได้การทดลอง 62=366^2=36 ครั้ง sampling overhead เป็นปริมาณที่ต่างออกไป: ผลรวมกำลังสองของค่าสัมบูรณ์ของสัมประสิทธิ์การแยกส่วนคือ 92=819^2=81 ซึ่งบอกต้นทุนการสุ่มตัวอย่างเพื่อรักษาความแม่นยำภายใต้การสร้างกลับแบบ quasiprobability ดูคำอธิบาย sampling overhead

เพื่อเปรียบเทียบ ให้ Transpile สำเนาของ Circuit ที่ไม่ได้ตัดซึ่งมีการวัด โดยใช้ coupling map จริง measure_all() จะบันทึก logical qubit ดั้งเดิมไว้ใน register meas การ Transpile คงการแมป classical bit นั้นไว้ ดังนั้นสามารถประเมินผล counts ด้วย observable ดั้งเดิมได้

uncut_pm = generate_preset_pass_manager(
optimization_level=1,
backend=simulation_backend,
initial_layout=initial_layout,
seed_transpiler=1234,
)
isa_uncut = uncut_pm.run(circuit.measure_all(inplace=False))
print("Uncut circuit depth:", isa_uncut.depth())
print("Cut circuit depths:", sorted({qc.depth() for qc in isa_experiments}))
Uncut circuit depth: 15
Cut circuit depths: [6, 7, 8, 9, 10, 11, 12, 15]

ขั้นตอนที่ 3: รันด้วย Qiskit primitives​

รันทั้งสองเวอร์ชันด้วย primitive Aer Sampler V2 การทดลองที่ตัดแต่ละครั้งได้ 10,000 shot เช่นเดียวกับ Circuit ที่ไม่ได้ตัดเพียงตัวเดียว ดังนั้นเวิร์กโฟลว์แบบตัดจึงใช้ shot มากกว่า 36 เท่าในการเปรียบเทียบนี้ การที่ shot ต่อ Circuit เท่ากันไม่ได้ทำให้งบการรันรวมเท่ากัน

shots = 10_000
sampler = AerSampler(seed=1234)
cut_result = sampler.run(isa_experiments, shots=shots).result()
uncut_result = sampler.run([isa_uncut], shots=shots).result()

ขั้นตอนที่ 4: Post-process และแสดงผลลัพธ์ในรูปแบบ classical ที่ต้องการ​

สร้างค่าคาดหวังของ Pauli แต่ละตัวกลับโดยใช้ observable ที่แมปแล้วชุดเดียวกันและลำดับการทดลองเดียวกับตอนสร้าง จากนั้นหาค่าเฉลี่ยของ nn ค่าแรกสำหรับ MZM_Z และ nn ค่าที่เหลือสำหรับ CZZC_{ZZ} สำหรับ Circuit ที่ไม่ได้ตัด ให้คำนวณค่าคาดหวังจาก counts ที่วัดได้โดยตรง

cut_expvals = reconstruct_expectation_values(
cut_result, coefficients, mapped_observables.paulis
)
uncut_counts = uncut_result[0].data.meas.get_counts()
uncut_expvals = [
sampled_expectation_value(uncut_counts, p) for p in observables.paulis
]

def compare_results(cut_expvals, uncut_expvals, num_qubits):
"""Display average Z and periodic ZZ values against the ideal values."""
cut_averages = np.asarray(cut_expvals).reshape(2, num_qubits).mean(axis=1)
uncut_averages = (
np.asarray(uncut_expvals).reshape(2, num_qubits).mean(axis=1)
)
print("Cut averages (Z, ZZ):", cut_averages)
print("Uncut averages (Z, ZZ):", uncut_averages)
print("Absolute cut errors:", np.abs(cut_averages - [-1, 1]))
print("Absolute uncut errors:", np.abs(uncut_averages - [-1, 1]))

fig, ax = plt.subplots(figsize=(7, 4))
positions = np.arange(2)
for offset, values, label, color in [
(-0.25, [-1, 1], "Ideal", "#4589ff"),
(0, cut_averages, "Cut", "#a56eff"),
(0.25, uncut_averages, "Uncut", "#009d9a"),
]:
ax.bar(
positions + offset, values, width=0.25, label=label, color=color
)
ax.set_xticks(positions, ["Average Z", "Average periodic ZZ"])
ax.set_ylabel("Expectation value")
ax.axhline(0, color="black", linewidth=0.8)
ax.legend()
fig.tight_layout()
plt.show()

compare_results(cut_expvals, uncut_expvals, num_qubits)
Cut averages (Z, ZZ): [-1.00100833 0.99778333]
Uncut averages (Z, ZZ): [-1. 1.]
Absolute cut errors: [0.00100833 0.00221667]
Absolute uncut errors: [0. 0.]

Ideal, cut, and uncut expectation values from noiseless simulation

ผลลัพธ์แบบไม่มี noise ควรใกล้เคียงค่าในอุดมคติ การสร้างกลับด้วย shot จำกัดอาจแกว่งได้ และค่าประมาณที่สร้างกลับอาจอยู่นอกช่วง [−1,1][-1,1] การตัดไม่ได้ช่วยปรับปรุงผลลัพธ์ที่ไม่มี noise อยู่แล้ว ตัวอย่างนี้ใช้ตรวจสอบการแยกส่วน การแมป layout และการสร้างกลับ ก่อนรันบนฮาร์ดแวร์

ตัวอย่างฮาร์ดแวร์ขนาดใหญ่​

ใช้เวิร์กโฟลว์ซ้ำบน QPU ที่พร้อมใช้งานและคิวน้อยที่สุดซึ่งมีอย่างน้อย 120 qubit เลือก shortest path ที่ยาวที่สุดใน coupling graph แบบไม่มีทิศทางของมัน: แต่ละคู่ที่อยู่ติดกันเชื่อมต่อกัน ขณะที่ปลายทั้งสองอยู่ห่างกัน วิธีนี้ปรับ chain ให้เข้ากับอุปกรณ์ที่เลือก แทนที่จะพึ่ง layout Eagle แบบตายตัว โค้ดจะตรวจสอบว่า chain มี qubit อย่างน้อย 30 ตัว และปลายทั้งสองไม่ได้เป็นเพื่อนบ้านกัน

บล็อกต่อไปนี้รวมสี่ขั้นตอนเข้าด้วยกัน Gate twirling, measurement twirling และ dynamical decoupling ถูกปิดทั้งสองเวอร์ชัน ทั้งสองงานรันใน batch เดียวกันและได้รับแท็กของบทเรียน TUT_CCPBC การทดลองที่ตัดยังคงใช้ shot รวมมากกว่า ดังนั้นให้เปรียบเทียบทั้งความแม่นยำและการใช้งาน QPU

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=120
)

# ------------------------- Step 1 -------------------------
graph = backend.coupling_map.graph.to_undirected(multigraph=False)
paths = rx.all_pairs_dijkstra_shortest_paths(graph, lambda _: 1)
initial_layout = list(
max(
(
path
for source_paths in paths.values()
for path in source_paths.values()
),
key=len,
)
)
num_qubits = len(initial_layout)
if num_qubits < 30:
raise ValueError(
"Select a backend with a shortest path of at least 30 qubits."
)
if graph.has_edge(initial_layout[0], initial_layout[-1]):
raise ValueError("The chain endpoints must not be directly connected.")
print(f"Backend: {backend.name}; chain length: {num_qubits}")
print("Initial layout:", initial_layout)
circuit, observables = build_periodic_problem(num_qubits)

# ------------------------- Step 2 -------------------------
isa_experiments, coefficients, mapped_observables = prepare_cut_experiments(
circuit, observables, backend, initial_layout
)
uncut_pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
initial_layout=initial_layout,
seed_transpiler=1234,
)
isa_uncut = uncut_pm.run(circuit.measure_all(inplace=False))
print("Uncut circuit depth:", isa_uncut.depth())
print("Cut circuit depths:", sorted({qc.depth() for qc in isa_experiments}))

# ------------------------- Step 3 -------------------------
options = SamplerOptions()
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False
options.environment.job_tags = ["TUT_CCPBC"]
shots = 10_000
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch, options=options)
cut_job = sampler.run(isa_experiments, shots=shots)
uncut_job = sampler.run([isa_uncut], shots=shots)
print("Cut job ID:", cut_job.job_id())
print("Uncut job ID:", uncut_job.job_id())
cut_result = cut_job.result()
uncut_result = uncut_job.result()

# ------------------------- Step 4 -------------------------
cut_expvals = reconstruct_expectation_values(
cut_result, coefficients, mapped_observables.paulis
)
uncut_counts = uncut_result[0].data.meas.get_counts()
uncut_expvals = [
sampled_expectation_value(uncut_counts, p) for p in observables.paulis
]
compare_results(cut_expvals, uncut_expvals, num_qubits)
qpu_seconds = sum(
job.metrics()["usage"]["quantum_seconds"] for job in [cut_job, uncut_job]
)
print(
f"Total QPU usage: {qpu_seconds:.1f} seconds ({qpu_seconds / 60:.2f} minutes)"
)
Backend: ibm_boston; chain length: 33
Initial layout: [0, 1, 2, 3, 16, 23, 24, 25, 37, 45, 44, 43, 56, 63, 64, 65, 77, 85, 86, 87, 97, 107, 106, 105, 117, 125, 124, 123, 136, 143, 142, 141, 140]
Boundary gates cut: 2
Number of experiments: 36
Sampling overhead: 81.0
Uncut circuit depth: 375
Cut circuit depths: [20]
Cut job ID: dagp7fj9k43c73admp40
Uncut job ID: dagp7fj9k43c73admp5g
Cut averages (Z, ZZ): [-0.92305152 0.89586667]
Uncut averages (Z, ZZ): [-0.61106061 0.51606061]
Absolute cut errors: [0.07694848 0.10413333]
Absolute uncut errors: [0.38893939 0.48393939]
Total QPU usage: 102.0 seconds (1.70 minutes)

Ideal, cut, and uncut expectation values measured on ibm_boston

ในการรันที่บันทึกไว้บน ibm_boston ค่าประมาณจากการตัดอยู่ที่ประมาณ MZ=−0.923M_Z=-0.923 และ CZZ=0.896C_{ZZ}=0.896 เทียบกับ −0.611-0.611 และ 0.5160.516 สำหรับ Circuit ที่ไม่ได้ตัด ค่าประมาณจากการตัดทั้งสองใกล้ค่าในอุดมคติ −1-1 และ +1+1 มากกว่า

เปรียบเทียบแต่ละผลลัพธ์กับค่าในอุดมคติ และตรวจดู depth ของ Circuit กับการใช้งาน QPU รวม การตัดทำให้ไม่ต้อง route Gate แบบเป็นคาบ แต่ก็เพิ่มการดำเนินการกลางทางและความไม่แน่นอนทางสถิติเพิ่มเติม noise ของฮาร์ดแวร์และการ calibrate มีผลต่อการเปรียบเทียบ จึงไม่รับประกันว่าจะดีขึ้น ต้องรันซ้ำหลายครั้งเพื่อประเมินว่าความแตกต่างที่สังเกตได้มีนัยสำคัญทางสถิติหรือไม่

ขั้นตอนถัดไป​

คำแนะนำ