דלג לתוכן הראשי

ניפוי באגים בעבודות Qiskit Runtime

גרסאות חבילות

הקוד בדף זה פותח תוך שימוש בדרישות הבאות. אנחנו ממליצים להשתמש בגרסאות אלה או בגרסאות חדשות יותר.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17

לפני שמגישים עומס עבודה רב-משאבים של Qiskit Runtime להרצה על חומרה, אפשר להשתמש במחלקה Neat (Noisy Estimator Analyzer Tool) של Qiskit Runtime כדי לוודא שעומס עבודת Estimator מוגדר נכון, סביר להניח שיחזיר תוצאות מדויקות, משתמש באפשרויות המתאימות ביותר לבעיה שצוינה, ועוד.

Neat מבצעת Cliffordization על ה-Circuit-ים של הקלט לצורך סימולציה יעילה, תוך שמירה על המבנה והעומק שלהם. Circuit-ים מסוג Clifford סובלים מרמות רעש דומות ומשמשים כממשק טוב לחקר ה-Circuit המקורי שמעניין אותנו.

הדוגמאות הבאות ממחישות מצבים שבהם Neat יכולה להיות משאב שימושי. ראשית, ייבאו את החבילות הרלוונטיות ובצעו אימות לשירות Qiskit Runtime.

הכנת הסביבה

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-aer qiskit-ibm-runtime
import numpy as np
import random

from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp

from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator
from qiskit_ibm_runtime.debug_tools import Neat

from qiskit_aer.noise import NoiseModel, depolarizing_error
# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Generate a preset pass manager
# This will be used to convert the abstract circuit to an equivalent Instruction Set Architecture (ISA) circuit.
pm = generate_preset_pass_manager(backend=backend, optimization_level=0)

# Set the random seed
random.seed(10)

אתחול Circuit יעד

נשקול Circuit בן שישה Qubit-ים עם המאפיינים הבאים:

  • מתחלף בין סיבובי RZ אקראיים ושכבות של שערי CNOT.
  • בעל מבנה מראה, כלומר מפעיל יחידה אוניטרית U ולאחר מכן את ההופכי שלה.
def generate_circuit(n_qubits, n_layers):
r"""
A function to generate a pseudo-random a circuit with ``n_qubits`` qubits and
``2*n_layers`` entangling layers of the type used in this notebook.
"""
# An array of random angles
angles = [
[random.random() for q in range(n_qubits)] for s in range(n_layers)
]

qc = QuantumCircuit(n_qubits)
qubits = list(range(n_qubits))

# do random circuit
for layer in range(n_layers):
# rotations
for q_idx, qubit in enumerate(qubits):
qc.rz(angles[layer][q_idx], qubit)

# cx gates
control_qubits = (
qubits[::2] if layer % 2 == 0 else qubits[1 : n_qubits - 1 : 2]
)
for qubit in control_qubits:
qc.cx(qubit, qubit + 1)

# undo random circuit
for layer in range(n_layers)[::-1]:
# cx gates
control_qubits = (
qubits[::2] if layer % 2 == 0 else qubits[1 : n_qubits - 1 : 2]
)
for qubit in control_qubits:
qc.cx(qubit, qubit + 1)

# rotations
for q_idx, qubit in enumerate(qubits):
qc.rz(-angles[layer][q_idx], qubit)

return qc

# Generate a random circuit
qc = generate_circuit(6, 3)
# Convert the abstract circuit to an equivalent ISA circuit.
isa_qc = pm.run(qc)

qc.draw("mpl", idle_wires=0)

Output of the previous code cell

בחרו אופרטורי Z בודדי-פאולי כמשתנים נצפים והשתמשו בהם לאתחול ה-PUB-ים (primitive unified blocs).

# Initialize the observables
obs = ["ZIIIII", "IZIIII", "IIZIII", "IIIZII", "IIIIZI", "IIIIIZ"]
print(f"Observables: {obs}")

# Map the observables to the backend's layout
isa_obs = [SparsePauliOp(o).apply_layout(isa_qc.layout) for o in obs]

# Initialize the PUBs, which consist of six-qubit circuits with `n_layers` 1, ..., 6
all_n_layers = [1, 2, 3, 4, 5, 6]

pubs = [(pm.run(generate_circuit(6, n)), isa_obs) for n in all_n_layers]
Observables: ['ZIIIII', 'IZIIII', 'IIZIII', 'IIIZII', 'IIIIZI', 'IIIIIZ']

Cliffordization של ה-Circuit-ים

ה-Circuit-ים של ה-PUB שהוגדרו קודם אינם מסוג Clifford, מה שמקשה על סימולציה קלאסית שלהם. עם זאת, אפשר להשתמש בשיטת Neat to_clifford כדי למפות אותם ל-Circuit-ים מסוג Clifford לסימולציה יעילה יותר. שיטת to_clifford היא עטיפה סביב מעבר ה-Transpiler ConvertISAToClifford, שניתן להשתמש בו גם באופן עצמאי. בפרט, הוא מחליף שערים חד-Qubit שאינם מסוג Clifford ב-Circuit המקורי בשערים חד-Qubit מסוג Clifford, אך אינו משנה את שערי שני ה-Qubit-ים, מספר ה-Qubit-ים, או עומק ה-Circuit.

ראו סימולציה יעילה של Circuit-ים מסוג stabilizer עם פרימיטיבים של Qiskit Aer למידע נוסף על סימולציית Circuit מסוג Clifford. ראשית, אתחלו את Neat.

# You could specify a custom `NoiseModel` here. If `None`, `Neat`
# pulls the noise model from the given backend
noise_model = None

# Initialize `Neat`
analyzer = Neat(backend, noise_model)

לאחר מכן, בצעו Cliffordization על ה-PUB-ים.

clifford_pubs = analyzer.to_clifford(pubs)

clifford_pubs[0].circuit.draw("mpl", idle_wires=0)

Output of the previous code cell

יישום 1: ניתוח השפעת הרעש על תוצאות ה-Circuit

דוגמה זו מראה איך להשתמש ב-Neat כדי לחקור את ההשפעה של מודלי רעש שונים על PUBים כפונקציה של עומק ה-Circuit, על ידי הרצת סימולציות בתנאים אידיאליים (ideal_sim) ורועשים (noisy_sim). זה יכול להיות שימושי כדי לקבוע ציפיות לגבי איכות התוצאות הניסויות לפני הרצת עבודה על QPU. למידע נוסף על מודלי רעש, ראו סימולציה מדויקת ורועשת עם Qiskit Aer primitives.

התוצאות הסימולטיביות תומכות בפעולות מתמטיות, ולכן ניתן להשוות ביניהן (או עם תוצאות ניסויות) כדי לחשב מדדי איכות.

זהירות

QPU יכול להיות מושפע מסוגים שונים של רעש. מודל הרעש של Qiskit Aer שמשמש כאן מדמה רק חלק מהם, ולכן סביר שיהיה פחות חמור מהרעש על QPU אמיתי.

לפרטים על אילו שגיאות נכללות בעת אתחול מודל רעש מ-QPU, ראו את תיעוד ה-API של Aer NoiseModel.

מתחילים בביצוע סימולציות קלאסיות אידיאליות ורועשות.

# Perform a noiseless simulation
ideal_results = analyzer.ideal_sim(clifford_pubs)
print(f"Ideal results:\n {ideal_results}\n")

# Perform a noisy simulation with the backend's noise model
noisy_results = analyzer.noisy_sim(clifford_pubs)
print(f"Noisy results:\n {noisy_results}\n")
Ideal results:
NeatResult([NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.])), NeatPubResult(vals=array([1., 1., 1., 1., 1., 1.]))])
Noisy results:
NeatResult([NeatPubResult(vals=array([0.99023438, 0.99609375, 0.9921875 , 0.99023438, 0.99414062,
0.99414062])), NeatPubResult(vals=array([0.984375 , 0.99414062, 0.98242188, 0.98828125, 0.98632812,
0.99414062])), NeatPubResult(vals=array([0.96679688, 0.97070312, 0.95898438, 0.97851562, 0.98046875,
0.98828125])), NeatPubResult(vals=array([0.9453125 , 0.953125 , 0.97070312, 0.96875 , 0.98242188,
0.99023438])), NeatPubResult(vals=array([0.93164062, 0.9375 , 0.953125 , 0.96875 , 0.96484375,
0.98046875])), NeatPubResult(vals=array([0.92578125, 0.921875 , 0.93359375, 0.953125 , 0.95898438,
0.9765625 ]))])

לאחר מכן, מפעילים פעולות מתמטיות כדי לחשב את ההפרש המוחלט. שאר המדריך משתמש בהפרש המוחלט כמדד איכות להשוואת תוצאות אידיאליות עם תוצאות רועשות או ניסויות, אך ניתן להגדיר מדדי איכות דומים.

ההפרש המוחלט מראה שהשפעת הרעש גדלה עם גודל ה-Circuitים.

# Figure of merit: Absolute difference
def rdiff(res1, re2):
r"""The absolute difference between `res1` and re2`.

--> The closer to `0`, the better.
"""
d = abs(res1 - re2)
return np.round(d.vals * 100, 2)

for idx, (ideal_res, noisy_res) in enumerate(
zip(ideal_results, noisy_results)
):
vals = rdiff(ideal_res, noisy_res)

# Print the mean absolute difference for the observables
mean_vals = np.round(np.mean(vals), 2)
print(
f"Mean absolute difference between ideal and noisy results for circuits with {all_n_layers[idx]} layers:\n {mean_vals}%\n"
)
Mean absolute difference between ideal and noisy results for circuits with 1 layers:
0.72%

Mean absolute difference between ideal and noisy results for circuits with 2 layers:
1.17%

Mean absolute difference between ideal and noisy results for circuits with 3 layers:
2.6%

Mean absolute difference between ideal and noisy results for circuits with 4 layers:
3.16%

Mean absolute difference between ideal and noisy results for circuits with 5 layers:
4.4%

Mean absolute difference between ideal and noisy results for circuits with 6 layers:
5.5%

אפשר להיעזר בהנחיות הגסות והמפושטות הבאות לשיפור Circuitים מסוג זה:

  • אם ההפרש המוחלט הממוצע גדול מ-90%, הפחתת שגיאות כנראה לא תעזור.
  • אם ההפרש המוחלט הממוצע קטן מ-90%, Probabilistic Error Amplification (PEA) כנראה יוכל לשפר את התוצאות.
  • אם ההפרש המוחלט הממוצע קטן מ-80%, ZNE with gate folding כנראה גם יוכל לשפר את התוצאות.

מכיוון שכל ההפרשים המוחלטים לעיל קטנים מ-90%, יישום PEA על ה-Circuit המקורי יקווה לשפר את איכות תוצאותיו. ניתן לציין מודלי רעש שונים ב-analyzer. הדוגמה הבאה מבצעת את אותו בדיקה אך מוסיפה מודל רעש מותאם אישית.

# Set up a noise model with strength 0.02 on every two-qubit gate
noise_model = NoiseModel()
for qubits in backend.coupling_map:
noise_model.add_quantum_error(
depolarizing_error(0.02, 2), ["ecr", "cx"], qubits
)

# Update the analyzer's noise model
analyzer.noise_model = noise_model

# Perform a noiseless simulation
ideal_results = analyzer.ideal_sim(clifford_pubs)

# Perform a noisy simulation with the backend's noise model
noisy_results = analyzer.noisy_sim(clifford_pubs)

# Compare the results
for idx, (ideal_res, noisy_res) in enumerate(
zip(ideal_results, noisy_results)
):
values = rdiff(ideal_res, noisy_res)

# Print the mean absolute difference for the observables
mean_values = np.round(np.mean(values), 2)
print(
f"Mean absolute difference between ideal and noisy results for circuits with {all_n_layers[idx]} layers:\n {mean_values}%\n"
)
Mean absolute difference between ideal and noisy results for circuits with 1 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 2 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 3 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 4 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 5 layers:
0.0%

Mean absolute difference between ideal and noisy results for circuits with 6 layers:
0.0%

יישום 2: השוואת אסטרטגיות שונות

דוגמה זו משתמשת ב-Neat כדי לעזור לזהות את האפשרויות הטובות ביותר עבור ה-PUBs שלך. לשם כך, שקול להריץ בעיית אמידה עם PEA, שלא ניתן לדמות עם qiskit_aer. אפשר להשתמש ב-Neat כדי לעזור לקבוע אילו גורמי הגברת רעש יעבדו הכי טוב, ואז להשתמש בגורמים אלה כשמריצים את הניסוי המקורי על QPU.

# Generate a circuit with six qubits and six layers
isa_qc = pm.run(generate_circuit(6, 3))

# Use the same observables as previously
pubs = [(isa_qc, isa_obs)]
clifford_pubs = analyzer.to_clifford(pubs)
noise_factors = [
[1, 1.1],
[1, 1.1, 1.2],
[1, 1.5, 2],
[1, 1.5, 2, 2.5, 3],
[1, 4],
]
# Run the PUBs on a QPU
estimator = Estimator(backend)
estimator.options.default_shots = 100000
estimator.options.twirling.enable_gates = True
estimator.options.twirling.enable_measure = True
estimator.options.twirling.shots_per_randomization = 100
estimator.options.resilience.measure_mitigation = True
estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.amplifier = "pea"

jobs = []
for factors in noise_factors:
estimator.options.resilience.zne.noise_factors = factors
jobs.append(estimator.run(clifford_pubs))

results = [job.result() for job in jobs]
# Perform a noiseless simulation
ideal_results = analyzer.ideal_sim(clifford_pubs)
# Look at the mean absolute difference to quickly tell the best choice for your options
for factors, res in zip(noise_factors, results):
d = rdiff(ideal_results[0], res[0])
print(
f"Mean absolute difference for factors {factors}:\n {np.round(np.mean(d), 2)}%\n"
)
Mean absolute difference for factors [1, 1.1]:
6.83%

Mean absolute difference for factors [1, 1.1, 1.2]:
8.76%

Mean absolute difference for factors [1, 1.5, 2]:
8.03%

Mean absolute difference for factors [1, 1.5, 2, 2.5, 3]:
10.17%

Mean absolute difference for factors [1, 4]:
8.02%

התוצאה עם ההפרש הקטן ביותר מציעה אילו אפשרויות לבחור.

הצעדים הבאים