การลดข้อผิดพลาดในระดับ Utility-scale ด้วย Probabilistic Error Amplification
ประมาณการใช้งาน: 16 นาทีบนโปรเซสเซอร์ Heron r2 (หมายเหตุ: นี่เป็นการประมาณเท่านั้น ระยะเวลาจริงอาจแตกต่างกัน)
พื้นหลัง
บทแนะนำนี้สาธิตวิธีรันการทดลองลดข้อผิดพลาดในระดับ utility-scale ด้วย Qiskit Runtime โดยใช้เวอร์ชันทดลองของ zero noise extrapolation (ZNE) ร่วมกับ probabilistic error amplification (PEA)
อ้างอิง: Y. Kim et al. Evidence for the utility of quantum computing before fault tolerance. Nature 618.7965 (2023)
Zero-Noise Extrapolation (ZNE)
Zero-noise extrapolation (ZNE) เป็นเทคนิคการลดข้อผิดพลาดที่ขจัดผลกระทบของ ค่า noise ที่ไม่รู้จัก ในระหว่างการรัน Circuit โดยที่ค่านั้นสามารถถูกปรับขนาดได้ใน วิธีที่รู้จัก
เทคนิคนี้สมมติว่าค่าคาดหวังเปลี่ยนแปลงตาม noise ด้วยฟังก์ชันที่รู้จัก
โดยที่ เป็นพารามิเตอร์ที่แทนความแรง ของ noise ซึ่งสามารถถูกขยายได้ เราสามารถใช้งาน ZNE ตามขั้นตอนต่อไปนี้:
- ขยาย noise ของ Circuit สำหรับค่าตัวประกอบ noise หลายค่า
- รัน Circuit ที่ถูกขยาย noise ทุกตัวเพื่อวัด
- Extrapolate กลับไปยังขีดจำกัดที่ไม่มี noise

การขยาย noise สำหรับ ZNE
ความท้าทายหลักในการใช้งาน ZNE ให้ประสบความสำเร็จคือการมีโมเดล noise ที่แม่นยำสำหรับค่าคาดหวัง และการขยาย noise ในวิธีที่รู้จัก
มีสามวิธีทั่วไปในการขยาย noise สำหรับ ZNE
| Pulse stretching | Gate folding | Probabilistic error amplification |
|---|---|---|
| ปรับขนาดระยะเวลาของ pulse ผ่านการ calibration | ทำซ้ำ Gate ในวงรอบ identity | เพิ่ม noise ผ่านการ sampling Pauli channels |
| Kandala et al. Nature (2019) | Shultz et al. PRA (2022) | Li & Benjamin PRX (2017) |
| สำหรับการทดลองระดับ utility-scale นั้น probabilistic error amplification (PEA) น่าสนใจที่สุด |
- Pulse stretching สมมติว่า noise ของ Gate เป็นสัดส่วนกับระยะเวลา ซึ่งโดยทั่วไปไม่เป็นความจริง และการ calibration ก็มีต้นทุนสูง
- Gate folding ต้องการค่าตัวประกอบการยืดขนาดที่มาก ซึ่งจำกัด depth ของ Circuit ที่สามารถรันได้อย่างมาก
- PEA สามารถใช้กับ Circuit ใดก็ได้ที่สามารถรันด้วยค่า noise ดั้งเดิม () แต่ต้องการการเรียนรู้โมเดล noise
เรียนรู้โมเดล noise สำหรับ PEA
PEA สมมติโมเดล noise แบบ layer-based เดียวกับ probabilistic error cancellation (PEC) แต่หลีกเลี่ยงค่าใช้จ่ายจากการ sampling ที่เพิ่มขึ้นแบบ exponential ตาม noise ของ Circuit
| ขั้นตอนที่ 1 | ขั้นตอนที่ 2 | ขั้นตอนที่ 3 |
|---|---|---|
| Pauli twirl layers ของ two-qubit Gates | ทำซ้ำ identity pairs ของ layers และเรียนรู้ noise | คำนวณค่า fidelity (ข้อผิดพลาดสำหรับแต่ละ noise channel) |
![]() | ![]() |
อ้างอิง: E. van den Berg, Z. Minev, A. Kandala, and K. Temme, Probabilistic error cancellation with sparse Pauli-Lindblad models on noisy quantum processors arXiv:2201.09866
ข้อกำหนด
ก่อนเริ่มบทแนะนำนี้ ตรวจสอบให้แน่ใจว่าได้ติดตั้งสิ่งต่อไปนี้แล้ว:
- Qiskit SDK v1.0 หรือใหม่กว่า พร้อมรองรับ visualization
- Qiskit Runtime v0.22 หรือใหม่กว่า (
pip install qiskit-ibm-runtime)
การตั้งค่า
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime rustworkx
from __future__ import annotations
from collections.abc import Sequence
from collections import defaultdict
import numpy as np
import rustworkx
import matplotlib.pyplot as plt
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.circuit.library import CXGate, CZGate, ECRGate
from qiskit.providers import Backend
from qiskit.visualization import plot_error_map
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import PubResult
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
ขั้นตอนที่ 1: แมป input แบบ classical ไปยังปัญหาเชิงควอนตัม
สร้าง Circuit โมเดล Ising แบบมีพารามิเตอร์
ขั้นแรก เลือก Backend ที่จะใช้รัน การสาธิตนี้รันบน Backend ขนาด 127 Qubit แต่สามารถปรับเปลี่ยนเป็น Backend อื่นที่ใช้งานได้
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend
<IBMBackend('ibm_kingston')>
ฟังก์ชันช่วยสำหรับการสร้าง Circuit
ต่อไป สร้างฟังก์ชันช่วยเพื่อสร้าง Circuit สำหรับ Trotterized time evolution ของโมเดล Ising สนามขวาง 2 มิติที่สอดคล้องกับ topology ของ Backend
"""Trotter circuit generation"""
def remove_qubit_couplings(
couplings: Sequence[tuple[int, int]], qubits: Sequence[int] | None = None
) -> list[tuple[int, int]]:
"""Remove qubits from a coupling list.
Args:
couplings: A sequence of qubit couplings.
qubits: Optional, the qubits to remove.
Returns:
The input couplings with the specified qubits removed.
"""
if qubits is None:
return couplings
qubits = set(qubits)
return [edge for edge in couplings if not qubits.intersection(edge)]
def coupling_qubits(
*couplings: Sequence[tuple[int, int]],
allowed_qubits: Sequence[int] | None = None,
) -> list[int]:
"""Return a sorted list of all qubits involved in one or more couplings lists.
Args:
couplings: one or more coupling lists.
allowed_qubits: Optional, the allowed qubits to include. If None all
qubits are allowed.
Returns:
The intersection of all qubits in the couplings and the allowed qubits.
"""
qubits = set()
for edges in couplings:
for edge in edges:
qubits.update(edge)
if allowed_qubits is not None:
qubits = qubits.intersection(allowed_qubits)
return list(qubits)
def construct_layer_couplings(
backend: Backend,
) -> list[list[tuple[int, int]]]:
"""Separate a coupling map into disjoint 2-qubit gate layers.
Args:
backend: A backend to construct layer couplings for.
Returns:
A list of disjoint layers of directed couplings for the input coupling map.
"""
coupling_graph = backend.coupling_map.graph.to_undirected(
multigraph=False
)
edge_coloring = rustworkx.graph_bipartite_edge_color(coupling_graph)
layers = defaultdict(list)
for edge_idx, color in edge_coloring.items():
layers[color].append(
coupling_graph.get_edge_endpoints_by_index(edge_idx)
)
layers = [sorted(layers[i]) for i in sorted(layers.keys())]
return layers
def entangling_layer(
gate_2q: str,
couplings: Sequence[tuple[int, int]],
qubits: Sequence[int] | None = None,
) -> QuantumCircuit:
"""Generating a entangling layer for the specified couplings.
This corresponds to a Trotter layer for a ZZ Ising term with angle Pi/2.
Args:
gate_2q: The 2-qubit basis gate for the layer, should be "cx", "cz", or "ecr".
couplings: A sequence of qubit couplings to add CX gates to.
qubits: Optional, the physical qubits for the layer. Any couplings involving
qubits not in this list will be removed. If None the range up to the largest
qubit in the couplings will be used.
Returns:
The QuantumCircuit for the entangling layer.
"""
# Get qubits and convert to set to order
if qubits is None:
qubits = range(1 + max(coupling_qubits(couplings)))
qubits = set(qubits)
# Mapping of physical qubit to virtual qubit
qubit_mapping = {q: i for i, q in enumerate(qubits)}
# Convert couplings to indices for virtual qubits
indices = [
[qubit_mapping[i] for i in edge]
for edge in couplings
if qubits.issuperset(edge)
]
# Layer circuit on virtual qubits
circuit = QuantumCircuit(len(qubits))
# Get 2-qubit basis gate and pre and post rotation circuits
gate2q = None
pre = QuantumCircuit(2)
post = QuantumCircuit(2)
if gate_2q == "cx":
gate2q = CXGate()
# Pre-rotation
pre.sdg(0)
pre.z(1)
pre.sx(1)
pre.s(1)
# Post-rotation
post.sdg(1)
post.sxdg(1)
post.s(1)
elif gate_2q == "ecr":
gate2q = ECRGate()
# Pre-rotation
pre.z(0)
pre.s(1)
pre.sx(1)
pre.s(1)
# Post-rotation
post.x(0)
post.sdg(1)
post.sxdg(1)
post.s(1)
elif gate_2q == "cz":
gate2q = CZGate()
# Identity pre-rotation
# Post-rotation
post.sdg([0, 1])
else:
raise ValueError(
f"Invalid 2-qubit basis gate {gate_2q}, should be 'cx', 'cz', or 'ecr'"
)
# Add 1Q pre-rotations
for inds in indices:
circuit.compose(pre, qubits=inds, inplace=True)
# Use barriers around 2-qubit basis gate to specify a layer for PEA noise learning
circuit.barrier()
for inds in indices:
circuit.append(gate2q, (inds[0], inds[1]))
circuit.barrier()
# Add 1Q post-rotations after barrier
for inds in indices:
circuit.compose(post, qubits=inds, inplace=True)
# Add physical qubits as metadata
circuit.metadata["physical_qubits"] = tuple(qubits)
return circuit
def trotter_circuit(
theta: Parameter | float,
layer_couplings: Sequence[Sequence[tuple[int, int]]],
num_steps: int,
gate_2q: str | None = "cx",
backend: Backend | None = None,
qubits: Sequence[int] | None = None,
) -> QuantumCircuit:
"""Generate a Trotter circuit for the 2D Ising
Args:
theta: The angle parameter for X.
layer_couplings: A list of couplings for each entangling layer.
num_steps: the number of Trotter steps.
gate_2q: The 2-qubit basis gate to use in entangling layers.
Can be "cx", "cz", "ecr", or None if a backend is provided.
backend: A backend to get the 2-qubit basis gate from, if provided
will override the basis_gate field.
qubits: Optional, the allowed physical qubits to truncate the
couplings to. If None the range up to the largest
qubit in the couplings will be used.
Returns:
The Trotter circuit.
"""
if backend is not None:
try:
basis_gates = backend.configuration().basis_gates
except AttributeError:
basis_gates = backend.basis_gates
for gate in ["cx", "cz", "ecr"]:
if gate in basis_gates:
gate_2q = gate
break
# If no qubits, get the largest qubit from all layers and
# specify the range so the same one is used for all layers.
if qubits is None:
qubits = range(1 + max(coupling_qubits(layer_couplings)))
# Generate the entangling layers
layers = [
entangling_layer(gate_2q, couplings, qubits=qubits)
for couplings in layer_couplings
]
# Construct the circuit for a single Trotter step
num_qubits = len(qubits)
trotter_step = QuantumCircuit(num_qubits)
trotter_step.rx(theta, range(num_qubits))
for layer in layers:
trotter_step.compose(layer, range(num_qubits), inplace=True)
# Construct the circuit for the specified number of Trotter steps
circuit = QuantumCircuit(num_qubits)
for _ in range(num_steps):
circuit.rx(theta, range(num_qubits))
for layer in layers:
circuit.compose(layer, range(num_qubits), inplace=True)
circuit.metadata["physical_qubits"] = tuple(qubits)
return circuit
กำหนด couplings ของ entangling layer
เพื่อใช้งาน Trotterized Ising simulation กำหนดสาม layers ของ two-qubit Gate couplings สำหรับอุปกรณ์ ซึ่งจะถูกทำซ้ำในแต่ละ Trotter step สิ่งเหล่านี้กำหนดสาม twirled layers ที่ต้องเรียนรู้ noise เพื่อใช้งานการลดข้อผิดพลาด
layer_couplings = construct_layer_couplings(backend)
for i, layer in enumerate(layer_couplings):
print(f"Layer {i}:\n{layer}\n")
Layer 0:
[(2, 3), (4, 5), (6, 7), (8, 9), (10, 11), (12, 13), (14, 15), (16, 23), (18, 31), (19, 35), (20, 21), (25, 37), (26, 27), (28, 29), (33, 39), (36, 41), (38, 49), (42, 43), (45, 46), (47, 57), (51, 52), (53, 54), (56, 63), (58, 71), (59, 75), (61, 62), (64, 65), (66, 67), (68, 69), (72, 73), (76, 81), (79, 93), (82, 83), (84, 85), (86, 87), (88, 89), (91, 98), (94, 95), (97, 107), (99, 115), (100, 101), (102, 103), (105, 117), (108, 109), (110, 111), (113, 114), (116, 121), (118, 129), (123, 136), (124, 125), (126, 127), (130, 131), (132, 133), (135, 139), (138, 151), (142, 143), (144, 145), (146, 147), (152, 153), (154, 155)]
Layer 1:
[(0, 1), (3, 16), (5, 6), (7, 8), (11, 18), (13, 14), (17, 27), (21, 22), (23, 24), (25, 26), (29, 38), (30, 31), (32, 33), (34, 35), (39, 53), (41, 42), (43, 56), (44, 45), (47, 48), (49, 50), (51, 58), (54, 55), (57, 67), (60, 61), (62, 63), (65, 66), (69, 78), (70, 71), (73, 79), (74, 75), (77, 85), (80, 81), (83, 84), (87, 97), (89, 90), (91, 92), (93, 94), (96, 103), (101, 116), (104, 105), (106, 107), (109, 118), (111, 112), (113, 119), (114, 115), (117, 125), (121, 122), (123, 124), (127, 137), (128, 129), (131, 138), (133, 134), (136, 143), (139, 155), (140, 141), (145, 146), (147, 148), (149, 150), (151, 152)]
Layer 2:
[(1, 2), (3, 4), (7, 17), (9, 10), (11, 12), (15, 19), (21, 36), (22, 23), (24, 25), (27, 28), (29, 30), (31, 32), (33, 34), (37, 45), (40, 41), (43, 44), (46, 47), (48, 49), (50, 51), (52, 53), (55, 59), (61, 76), (63, 64), (65, 77), (67, 68), (69, 70), (71, 72), (73, 74), (78, 89), (81, 82), (83, 96), (85, 86), (87, 88), (90, 91), (92, 93), (95, 99), (98, 111), (101, 102), (103, 104), (105, 106), (107, 108), (109, 110), (112, 113), (119, 133), (120, 121), (122, 123), (125, 126), (127, 128), (129, 130), (131, 132), (134, 135), (137, 147), (141, 142), (143, 144), (148, 149), (150, 151), (153, 154)]

