בניית ניסוי למידה
המדריך הזה מראה איך לבנות ניסוי למידה סטנדרטי מאפס, ולהשתמש בו כדי ללמוד מודל מתוך נתונים רועשים מדומים.
-
הגדרת קבוצת שערים על טבעת של Qubit-ים
-
בחירת מודל Pauli-Lindblad מקומי-2
-
בניית ניסוי הלמידה
-
יצירת מעגלים
-
הגדרת סימולציה מקומית
-
הרצת התוכנית
-
ניתוח הנתונים
המעגלים שלהלן מדומים מקומית, ולכן המדריך הזה לא דורש אישורי IBM Quantum®. כדי להריץ אותו במקום זה על התקן אמיתי:
-
שלב 1: מחליפים את
FakeMarrakeshב-Backend אמיתי. -
שלב 5: מעבירים את ה-Backend הזה כמצב (mode) של ה-executor, ומסירים את אפשרויות הסימולטור.
1. הגדרת קבוצת שערים על טבעת של Qubit-ים
בונים QiskitGateSet מתוך Target של Backend ותת-קבוצה של ה-Qubit-ים שלו — כאן טבעת של שנים-עשר Qubit-ים של FakeMarrakesh — ואז מוסיפים לה שער אחד: שכבה של שישה שערי CZ שמכסה את הטבעת. כברירת מחדל קבוצת השערים מאותחלת עם שער הכנה P ושער מדידה M.
from qiskit_ibm_runtime.fake_provider import FakeMarrakesh
from qiskit_noise_learning.gate_sets import QiskitGateSet
backend = FakeMarrakesh()
qubit_subset = [*range(25, 30), *range(37, 39), *range(45, 50)]
cz_pairs = [(25, 26), (27, 28), (29, 38), (37, 45), (46, 47), (48, 49)]
gate_set = QiskitGateSet(backend.num_qubits, target=backend.target, qubit_subset=qubit_subset)
with gate_set.build_new_gate("cz_gate", latex_str=r"\mathrm{CZ}") as builder:
for pair in cz_pairs:
builder.circuit.cz(*pair)
list(gate_set)
['M', 'P', 'cz_gate']
from qiskit_ibm_runtime import QiskitRuntimeService
backend = QiskitRuntimeService().backend("ibm_marrakesh")
קוראים ל-draw() כדי לראות את השער על טופולוגיית ההתקן: טבעת ה-Qubit-ים בקבוצת השערים, ואילו זוגות מהם השער מקשר באיחוד (entangles).
gate_set.draw()
2. בחירת מודל Pauli-Lindblad מקומי-2
מחליטים אילו מחוללי Pauli-Lindblad מותר שיהיו לרעש באמצעות PauliLindbladModel. בונים את קבוצת המחוללים מהקישוריות של קבוצת השערים באמצעות k_local() — כאן כל פאולי מקומי-2 על זוגות Qubit-ים מחוברים של השער היוניטרי, ופאולי של Qubit בודד להכנה ולמדידה.
from qiskit_noise_learning.models import PauliLindbladModel
model = PauliLindbladModel.k_local(gate_set, gate_k={"cz_gate": 2, "M": 1, "P": 1})
מציגים את מספר הפרמטרים הלא-ידועים במודל הרעש של כל שער.
{name: len(generators) for name, generators in model.generators.items()}
{'M': 12, 'P': 12, 'cz_gate': 144}
3. בניית ניסוי הלמידה
מרכיבים Experiment על ידי הרכבת שלבי builder. כל שלב קורא את מה ששלבים קודמים כתבו, ולכן הסדר חשוב:
from qiskit_noise_learning.experiment_builder import (
BindFragmentDepths,
CompleteSequences,
EvenDepthVanillaPaths,
Experiment,
GenerateInstructionSequences,
IdentifyRelations,
MergeInstructionSequences,
SPAMPaths,
VanillaInstructionSequences,
)
experiment_builder = (
# Add standard vanilla learning paths and instruction sequences, and identify relations
EvenDepthVanillaPaths()
+ VanillaInstructionSequences()
+ IdentifyRelations()
# Add paths for learning SPAM, then generate and merge instruction sequences for measuring them
+ SPAMPaths()
+ GenerateInstructionSequences()
+ MergeInstructionSequences()
# Finalize by completing the instruction sequences and setting experiment depths
+ CompleteSequences()
+ BindFragmentDepths([2, 16, 32, 64])
)
experiment = experiment_builder.run(
Experiment(fidelity_model=model, shots=20, randomizations=50)
)
print(f"Number of paths: {len(experiment.paths)}")
print(f"Number of instruction sequences: {len(experiment.instruction_sequences)}")
Number of paths: 156
Number of instruction sequences: 37
בודקים את הדרגה של מטריצת התכנון:
print(f"Design matrix rank: {experiment.design_matrix.rank}")
Design matrix rank: 120
4. יצירת מעגלים
מהדרים את הניסוי באמצעות ExecutorCircuitGenerator, ויוצרים QuantumProgram — מעגל תבנית מוּפרמט אחד לכל עומק מקטע.
from qiskit_noise_learning.circuit_generator import ExecutorCircuitGenerator
circuit_generator = ExecutorCircuitGenerator(gate_set)
quantum_program = circuit_generator.generate(experiment)
print(f"Number of template circuits: {len(quantum_program.items)}")
Number of template circuits: 5
משרטטים מעגל תבנית כדי לבדוק אותו.
quantum_program.items[0].circuit.draw("mpl", idle_wires=False, fold=False)
5. הגדרת סימולציה מקומית
כדי להגדיר את מודל הרעש לסימולציה, אנחנו משתמשים במחוללים שכבר נמצאים במודל עם קצבים אקראיים כדי לבנות PauliLindbladMap.
import numpy as np
from qiskit.quantum_info import PauliLindbladMap
rng = np.random.default_rng(1234)
true_maps = {
name: PauliLindbladMap.from_terms(
[
PauliLindbladMap.GeneratorTerm(rate, generator)
for generator, rate in zip(generators, rng.uniform(2e-4, 1.5e-3, len(generators)))
]
)
for name, generators in model.generators.items()
if name != "P"
}
{name: noise_map.num_qubits for name, noise_map in true_maps.items()}
{'M': 156, 'cz_gate': 156}
המפות האלה רחבות כמו ההתקן, כי המודל מבוטא באינדוקס ה-Qubit-ים של backend, אבל הסימולטור רוצה שכל מפה תהיה רחבה כמו השער שעליו היא מיושמת, כשאינדקסי הפאולי רצים על ה-Qubit-ים של השער בסדר פיזי עולה. מצמצמים אותן באמצעות keep_qubits(), שעושה trace out לכל מה שמחוץ לטבעת:
noise_dict = {
name: noise_map.keep_qubits(qubit_subset) for name, noise_map in true_maps.items()
}
{name: noise_map.num_qubits for name, noise_map in noise_dict.items()}
{'M': 12, 'cz_gate': 12}
הסימולטור נותן שם לרעש של שכבה לפי הוראת הקופסה שממנה השכבה נבנתה, ש-to_instruction() מחזירה לכל שער בקבוצת השערים. מזווגים כל קופסה עם המפה שלה, משתמשים בשיטת סימולציית ה-stabilizer, וקובעים את seed_simulator כדי שהנתונים המדומים יהיו ניתנים לשחזור:
from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import Executor
executor = Executor(
mode=AerSimulator(method="stabilizer"),
options={
"simulator": {
"layer_noise_model": [
(gate_set[name].to_instruction(), noise_map)
for name, noise_map in noise_dict.items()
],
"seed_simulator": 42,
}
},
)
executor = Executor(mode=backend)
6. הרצת התוכנית
מריצים את התוכנית, ואז מעצבים את התוצאות באמצעות collect(), כדי לייצר Fit — המכל שממנו שלבי הניתוח קוראים ואליו הם כותבים.
השלב הזה זהה בין אם ה-executor מכוון לסימולטור ובין אם להתקן אמיתי.
job = executor.run(quantum_program)
fit = circuit_generator.collect(job.result())
7. ניתוח הנתונים
בונים צינור ניתוח סביב התאמת המודל בשיטת ריבועים פחותים אי-שליליים.
from qiskit_noise_learning.analysis import (
ComputeObservables, # computes observables from raw data
CurveFitObservables, # performs exponential fitting
LeastSquaresSolve, # solves model with (nonnegative) least squares
)
analyzer = ComputeObservables() + CurveFitObservables() + LeastSquaresSolve()
fit = analyzer.run(fit)
/opt/hostedtoolcache/Python/3.15.1/x64/lib/python3.15/site-packages/qiskit_noise_learning/analysis/model_solve.py:250: UserWarning: 7 of 156 row(s) of the linear system have a non-positive or non-finite uncertainty, so they carry no usable statistical weight. Positions in LinearSystemData.row_labels: 144, 145, 148, 149, 151, 152, 154.
system = LinearSystemData.from_fit(fit)
משרטטים את הממוצעים הנמדדים של ה-observable מול האקספוננטים המותאמים שלהם, תת-גרף אחד לכל זוג CZ, כדי לראות אם הדעיכות שמותאמות הן בכלל אקספוננציאליות.
fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
exponential_fit=True,
)
מחליפים את exponential_fit ב-model_prediction כדי להשוות את אותם נתונים לדעיכות שנגזרות מהקצבים שיצאו מפתרון הריבועים הפחותים האי-שליליים.
fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
model_prediction=True,
)
