Zum Hauptinhalt springen
Dieses Projekt befindet sich in der Beta-Phase. Einen Fehler gefunden? GitHub Issue erstellen. Frage oder Idee? In Discussions fragen — wir freuen uns über dein Feedback!
Wie ist die Übersetzungsqualität auf dieser Seite?

Ein Lernexperiment erstellen

Dieses Tutorial zeigt, wie du ein standardmäßiges Lernexperiment von Grund auf erstellst und damit ein Modell aus simulierten verrauschten Daten lernst.

  1. Ein Gate-Set auf einem Ring von Qubits definieren

  2. Ein 2-lokales Pauli-Lindblad-Modell wählen

  3. Das Lernexperiment erstellen

  4. Circuits generieren

  5. Lokale Simulation einrichten

  6. Das Programm ausführen

  7. Die Daten analysieren

Auf echter Hardware ausführen

Die folgenden Circuits werden lokal simuliert, daher benötigt diese Anleitung keine IBM Quantum®-Zugangsdaten. Um sie stattdessen auf einem echten Gerät auszuführen:

  • Schritt 1: Ersetze FakeMarrakesh durch ein echtes Backend.

  • Schritt 5: Übergib dieses Backend als Modus des Executors und entferne die Simulatoroptionen.

1. Ein Gate-Set auf einem Ring von Qubits definieren​

Erstelle ein QiskitGateSet aus dem Target eines Backends und einer Teilmenge seiner Qubits – hier ein Ring aus zwölf Qubits von FakeMarrakesh – und füge dann ein Gate hinzu: eine Lage aus sechs CZ-Gates, die diesen Ring abdeckt. Standardmäßig wird das Gate-Set mit einem Präparations-Gate P und einem Mess-Gate M initialisiert.

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']
Auf echter Hardware ausführen
from qiskit_ibm_runtime import QiskitRuntimeService

backend = QiskitRuntimeService().backend("ibm_marrakesh")

Rufe draw() auf, um das Gate auf der Gerätetopologie zu sehen: den Ring von Qubits im Gate-Set und welche Paare davon das Gate verschränkt.

gate_set.draw()

2. Ein 2-lokales Pauli-Lindblad-Modell wählen​

Lege mit einem PauliLindbladModel fest, welche Pauli-Lindblad-Generatoren das Rauschen haben darf. Baue diese Generatormenge mit k_local() aus der Konnektivität des Gate-Sets auf – hier jedes 2-lokale Pauli auf verbundenen Qubit-Paaren des unitären Gates sowie Einzel-Qubit-Paulis für Präparation und Messung.

from qiskit_noise_learning.models import PauliLindbladModel

model = PauliLindbladModel.k_local(gate_set, gate_k={"cz_gate": 2, "M": 1, "P": 1})

Zeige die Anzahl der unbekannten Parameter für das Rauschmodell jedes Gates an.

{name: len(generators) for name, generators in model.generators.items()}
{'M': 12, 'P': 12, 'cz_gate': 144}

3. Das Lernexperiment erstellen​

Setze ein Experiment aus Builder-Stufen zusammen. Jede Stufe liest, was frühere Stufen geschrieben haben, daher kommt es auf die Reihenfolge an:

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

Beobachte den Rang der Designmatrix:

print(f"Design matrix rank: {experiment.design_matrix.rank}")
Design matrix rank: 120

4. Circuits generieren​

Kompiliere das Experiment mit einem ExecutorCircuitGenerator und erzeuge dabei ein QuantumProgram – ein parametrisiertes Template-Circuit pro Fragmenttiefe.

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

Zeichne ein Template-Circuit zur Inspektion.

quantum_program.items[0].circuit.draw("mpl", idle_wires=False, fold=False)

../_images/4088c7e98baeb5b56925c6c1f276159582c5f7040fbba76de438b919210bcc5f.png

5. Lokale Simulation einrichten​

Um das Rauschmodell für die Simulation zu definieren, verwenden wir die bereits im Modell vorhandenen Generatoren mit zufälligen Raten, um eine PauliLindbladMap zu konstruieren.

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}

Diese Maps sind so breit wie das Gerät, da das Modell in der Qubit-Indizierung von backend ausgedrückt ist. Der Simulator möchte aber, dass jede Map so breit ist wie das Gate, auf das sie angewendet wird, wobei die Pauli-Indizes in aufsteigender physischer Reihenfolge über die Qubits des Gates laufen. Verschmälere sie mit keep_qubits(), das alles außerhalb des Rings herausspurt:

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}

Der Simulator benennt das Rauschen einer Lage nach der Box-Anweisung, aus der die Lage gebaut wurde, die to_instruction() für jedes Gate im Gate-Set zurückgibt. Ordne jede Box ihrer Map zu, verwende die Stabilizer-Simulationsmethode und setze seed_simulator, damit die simulierten Daten reproduzierbar sind:

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,
}
},
)
Auf echter Hardware ausführen
executor = Executor(mode=backend)

6. Das Programm ausführen​

Führe das Programm aus und formatiere die Ergebnisse dann mit collect(), um ein Fit zu erzeugen – den Container, aus dem die Analysestufen lesen und in den sie schreiben.

Dieser Schritt ist derselbe, unabhängig davon, ob der Executor auf einen Simulator oder auf ein echtes Gerät zeigt.

job = executor.run(quantum_program)
fit = circuit_generator.collect(job.result())

7. Die Daten analysieren​

Baue eine Analyse-Pipeline um eine Anpassung des Modells mit nichtnegativen kleinsten Quadraten.

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)

Stelle die gemessenen Mittelwerte der Observablen gegen ihre angepassten Exponentialfunktionen dar, ein Subplot pro CZ-Paar, um zu sehen, ob die angepassten Zerfälle überhaupt exponentiell sind.

fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
exponential_fit=True,
)

Ersetze exponential_fit durch model_prediction, um dieselben Daten mit den Zerfällen zu vergleichen, die sich aus den Raten ergeben, die aus der Lösung der nichtnegativen kleinsten Quadrate hervorgegangen sind.

fit.plot_qubit_pair_decays(
pairs=cz_pairs,
observable_type="means",
model_prediction=True,
)