تخفيف الأخطاء على نطاق 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) هي تقنية لتخفيف الأخطاء تُزيل تأثيرات الضوضاء المجهولة أثناء تن فيذ الدائرة، والتي يمكن تحجيمها بطريقة معروفة.
تفترض هذه التقنية أن قيم القيمة المتوقعة تتناسب مع الضوضاء وفق دالة معروفة
حيث يُحدِّد شدة الضوضاء وبالإمكان تضخيمها. يمكن تطبيق ZNE وفق الخطوات التالية:
- تضخيم ضوضاء الدائرة لعدة عوامل ضوضاء
- تشغيل كل دائرة مُضخَّمة الضوضاء لقياس
- الاستقراء نحو حد الضوضاء الصفرية

تضخيم الضوضاء لـ ZNE
يكمن التحدي الرئيسي في التطبيق الناجح لـ ZNE في امتلاك نموذج دقيق للضوضاء في قيمة التوقع وتضخيم الضوضاء بطريقة معروفة.
ثمة ثلاث طرق شائعة لتنفيذ تضخيم الأخطاء في ZNE.
| Pulse stretching | Gate folding | Probabilistic error amplification |
|---|---|---|
| Scale pulse duration via calibration | Repeat gates in identity cycles | Add noise via 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 أن ضوضاء البوابة تتناسب مع المدة الزمنية، وهو ما لا ينطبق عادةً على أرض الواقع. كما أن معايرته مكلفة.
- يتطلب gate folding عوامل تمديد كبيرة تُقيّد بشدة عمق الدوائر القابلة للتشغيل.
- يمكن تطبيق PEA على أي دائرة قابلة للتشغيل بعامل الضوضاء الأصلي ()، غير أنه يستلزم تعلّم نموذج الضوضاء.
تعلّم نموذج الضوضاء لـ PEA
يفترض PEA نموذج ضوضاء طبقي مشابهاً لما تستخدمه probabilistic error cancellation (PEC)، إلا أنه يتجنب تكاليف أخذ العينات التي تتصاعد أسياً مع ضوضاء الدائرة.
| الخطوة 1 | الخطوة 2 | الخطوة 3 |
|---|---|---|
| Pauli twirl layers of two-qubit gates | Repeat identity pairs of layers and learn the noise | Derive a fidelity (error for each 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)
الإعداد
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: تحويل المدخلات الكلاسيكية إلى مسألة كمومية
إنشاء دائرة نموذج Ising ذات معاملات قابلة للضبط
أولاً، اختر نظام تشغيل backend للعمل عليه. يعمل هذا المثال التوضيحي على backend من 127 qubit، غير أنه يمكنك تعديله ليتناسب مع أي backend متاح لديك.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
backend
<IBMBackend('ibm_kingston')>
دوال مساعدة لبناء الدائرة
بعد ذلك، أنشئ بعض الدوال المساعدة لبناء الدوائر الخاصة بالتطور الزمني المُقسَّم بـ Trotter لنموذج Ising المستعرض ثنائي الأبعاد بما يتوافق مع تضولوجيا 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
تعريف ترابطات طبقة التشابك
لتنفيذ محاكاة Ising المُقسَّمة بـ Trotter، عرِّف ثلاث طبقات من ترابطات بوابات ثنائية الـ qubit للجهاز، تتكرر عند كل خطوة من خطوات Trotter. تحدد هذه الطبقات الثلاث المُلتوية بـ Pauli التي تحتاج إلى تعلّم الضوضاء الخاصة بها لتطبيق التخفيف.
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)]
إزالة الـ qubit ذات الأداء الضعيف
ألقِ نظرة على خريطة الترابط الخاصة بـ backend وتحقق مما إذا كانت هناك أي qubits ترتبط بتوصيلات ذات معدل خطأ مرتفع. أزل هذه الـ qubits "الرديئة" من تجربتك.
# Plot gate error map
# NOTE: These can change over time, so your results may look different
plot_error_map(backend)

bad_qubits = {
56,
63,
67,
} # qubits removed based on high coupling error (1.00)
good_qubits = list(set(range(backend.num_qubits)).difference(bad_qubits))
print("Physical qubits:\n", good_qubits)
Physical qubits:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 57, 58, 59, 60, 61, 62, 64, 65, 66, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155]
توليد دائرة Trotter الرئيسية
num_steps = 6
theta = Parameter("theta")
circuit = trotter_circuit(
theta, layer_couplings, num_steps, qubits=good_qubits, backend=backend
)
إنشاء قائمة بقيم المعاملات لتعيينها لاحقاً
num_params = 12
# 12 parameter values for Rx between [0, pi/2].
# Reshape to outer product broadcast with observables
parameter_values = np.linspace(0, np.pi / 2, num_params).reshape(
(num_params, 1)
)
num_params = parameter_values.size

