דלג לתוכן הראשי
פרויקט זה בשלב בטא. מצאת באג? פתח issue ב-GitHub. יש שאלה או רעיון? שאל ב-Discussions — נשמח לשמוע ממך!
מה איכות התרגום בדף זה?

התחלה מהירה

המדריך הזה מדגים דוגמה מינימלית עובדת של החבילה qiskit-paulice. אנחנו משתמשים בבדיקות פאולי מרחב-זמן כדי לזהות שגיאות במהלך הרצה של מעגל Clifford, ואז עושים postselection רק על הדגימות שבהן לא זוהתה שגיאה. לתהליך מקצה לקצה עם הסברים מפורטים יותר, שמכמת את שיפור הנאמנות, ראו את המדריך המעמיק.

שלבי העבודה

  1. מכינים את הקלטים: מעגל Clifford של המטען, ה-qubits של היעד וה-qubits העזר שיארחו את הבדיקות, ומודל רעש.
  2. מוצאים בדיקות פאולי מרחב-זמן טובות ומוסיפים אותן למעגל.
  3. דוגמים את המעגל עם הבדיקות.
  4. עושים postselection רק לדגימות שבהן לא זוהתה שגיאה.

1. הכנת הקלטים​

add_pauli_checks מקבלת מעגל Clifford שמסתיים במדידות (או, עם הארגומנט stabilizers, מעגל בלי מדידות, כמו במדריך קודי מרחב-זמן), את ה-qubits של היעד שישמשו למימוש הבדיקות, ומודל רעש שמשמש לדירוג בדיקות מועמדות. כאן אנחנו בונים מעגל Clifford אקראי רדוד, מסדרים אותו על שרשרת חד-ממדית של qubits פיזיים ב-ibm_boston, מצמידים לכל qubit של המטען qubit עזר שכן (כך שבדיקה לא צריכה שערי SWAP), ומסיקים מודל רעש דה-פולריזציה גס מנתוני בנצ'מרק של ה-Backend.

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

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "qiskit_paulice": "qiskit-paulice"}
_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 numpy as np
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_paulice.layout import get_check_qubits
from qiskit_paulice.noise_models import NoiseModel

# Backend and a 1D chain of physical qubits to run on
backend = QiskitRuntimeService().backend("ibm_boston")
layout = [68, 69, 78, 89, 90, 91, 98, 111, 112, 113, 119, 133]

# A shallow brickwork random Clifford payload circuit
rng = np.random.default_rng(1764)
circuit = QuantumCircuit(len(layout))
circuit.h(range(circuit.num_qubits))
for d in range(4):
for i in range(d % 2, circuit.num_qubits - 1, 2):
circuit.cz(i, i + 1)
for q in range(circuit.num_qubits):
if rng.integers(0, 2):
circuit.sx(q)
if rng.integers(0, 2):
circuit.s(q)
circuit.measure_all()

# Pair each payload qubit with a neighboring ancilla to host a check (no SWAPs needed)
target_qubits, ancilla_qubits = get_check_qubits(backend.coupling_map, layout)

# A rough depolarizing noise model from backend benchmark data, used to score checks
noise_model = NoiseModel.from_backend(
backend, layout, uniform_gate_noise=True
)

circuit.draw("mpl", fold=-1)

Output of the previous code cell

2. מציאת בדיקות פאולי מרחב-זמן והוספתן​

add_pauli_checks מחפשת בדיקות יעילות ובעלות משקל נמוך על כל qubit יעד, מוסיפה אותן למעגל ומחזירה סדרה של מופעי CheckedCircuit. מופעי CheckedCircuit מכילים מספר הולך וגדל של בדיקות, מ-0 בדיקות ועד בדיקה אחת לכל qubit יעד. כל בדיקה משפרת את היכולת לזהות שגיאות במחיר של עומק מעגל גדול מעט יותר. בדוגמה הזו נשתמש במעגל עם בדיקה על כל qubit יעד.

from qiskit_paulice import add_pauli_checks

# The circuit has virtual qubits, so we specify our target qubits with virtual indices
target_qubits_v = [layout.index(q) for q in target_qubits]

# Add spacetime Pauli checks
checked_circuit = add_pauli_checks(circuit, target_qubits_v, noise_model)[-1]
checked_circuit.circuit.draw("mpl", fold=-1, scale=0.4, idle_wires=False)

Output of the previous code cell

3. דגימת המעגל עם הבדיקות​

לפני הדגימה אנחנו מבצעים טרנספילציה של המעגל עם הבדיקות ל-qubits פיזיים (initial_layout = layout + ancilla_qubits) ולקבוצת שערי הבסיס הטבעית של ibm_boston. כדי לדמות QPU רועש, אנחנו דוגמים עם Backend מסוג Aer stabilizer רועש ששיעורי השגיאה שלו תואמים ל-noise_model ששימש לבחירת הבדיקות.

from qiskit import transpile
from qiskit_aer import AerSimulator
from qiskit_aer.noise import NoiseModel as AerNoiseModel
from qiskit_aer.noise import ReadoutError, depolarizing_error

# Transpile once: lay the checked circuit out on our qubits and into the native basis
isa_circuit = transpile(
checked_circuit.circuit,
backend,
initial_layout=layout + ancilla_qubits,
optimization_level=0,
)

# Build an Aer noise model matching the depolarizing model used to pick checks
aer_noise = AerNoiseModel()
aer_noise.add_all_qubit_quantum_error(
depolarizing_error(noise_model.gate_noise, 2), ["cz"]
)
p = noise_model.readout_noise
aer_noise.add_all_qubit_readout_error(ReadoutError([[1 - p, p], [p, 1 - p]]))
simulator = AerSimulator(method="stabilizer", noise_model=aer_noise)

counts = (
simulator.run(isa_circuit, shots=1000, seed_simulator=1764)
.result()
.get_counts()
)
print(f"sampled {sum(counts.values())} shots")
sampled 1000 shots

4. Postselection של דגימות ללא שגיאה שזוהתה​

get_postselection_method ממפה כל shot לווקטור הסינדרום שלו; אנחנו שומרים רק את ה-shots עם סינדרום אפסי לגמרי — אלה שבהם אף בדיקה לא זיהתה שגיאה. השלכת ה-shots המסומנים מסירה שגיאות שזוהו מההתפלגות שנותרה, ומשפרת את הנאמנות שלה במחיר של קצב דגימה נמוך יותר. ראו במדריך השוואה כמותית של הנאמנות.

# Keep only the shots in which no check reported an error
ps_fn = checked_circuit.get_postselection_method()
counts_postselected = {
bs: n for bs, n in counts.items() if not ps_fn(bs).any()
}

kept, total = sum(counts_postselected.values()), sum(counts.values())
print(f"kept {kept} of {total} shots ({kept / total:.0%})")
kept 927 of 1000 shots (93%)