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?

Noise-Learning-Hilfsprogramm

Paketversionen

Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese oder neuere Versionen zu verwenden.

qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0
samplomatic~=0.21.0

Die Fehlerminderungstechniken PEA und PEC nutzen beide eine Noise-Learning-Komponente, die auf einem Pauli-Lindblad-Rauschmodell basiert. Diese wird normalerweise während der Ausführung verwaltet, nachdem ein oder mehrere Jobs über qiskit-ibm-runtime eingereicht wurden, ohne lokalen Zugriff auf das angepasste Rauschmodell. Ab qiskit-ibm-runtime v0.27.1 wurden jedoch eine NoiseLearner- und eine zugehörige NoiseLearnerOptions-Klasse erstellt, um die Ergebnisse dieser Noise-Learning-Experimente zu erhalten. Diese Ergebnisse können dann lokal als NoiseLearnerResult gespeichert und als Eingabe für spätere Experimente verwendet werden. Diese Seite gibt einen Überblick über die Verwendung und die verfügbaren zugehörigen Optionen.

Außerdem gibt es ab qiskit-ibm-runtime v0.47.0 eine neue NoiseLearnerV3-Klasse, die mit dem Executor-Primitive kompatibel ist. Diese neue Version, ebenfalls Teil des gesteuerten Ausführungsmodells, gibt dir die Möglichkeit, die Schichten, die du lernen möchtest, explizit anzugeben.

hinweis

NoiseLearner funktioniert nur mit serverseitigem EstimatorV2 und NoiseLearnerV3 funktioniert nur mit Executor und dem neuen clientseitigen Estimator (eingeführt in qiskit-ibm-runtime v0.50.0).

NoiseLearner​

Überblick​

Die NoiseLearner-Klasse führt Experimente durch, die Rauschprozesse basierend auf einem Pauli-Lindblad-Rauschmodell für einen (oder mehrere) Circuits charakterisieren. Sie besitzt eine run()-Methode, die die Lernexperimente ausführt und als Eingabe entweder eine Liste von Circuits oder einen PUB entgegennimmt, und gibt ein NoiseLearnerResult zurück, das die gelernten Rauschkanäle und Metadaten über die eingereichten Jobs enthält. Nachfolgend ist ein Code-Snippet zu sehen, das die Verwendung des Hilfsprogramms demonstriert.

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

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_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")
from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap
from qiskit.transpiler import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2
from qiskit_ibm_runtime.noise_learner import NoiseLearner
from qiskit_ibm_runtime.options import (
NoiseLearnerOptions,
ResilienceOptionsV2,
EstimatorOptions,
)

# Build a circuit with two entangling layers
num_qubits = 27
edges = list(CouplingMap.from_line(num_qubits, bidirectional=False))
even_edges = edges[::2]
odd_edges = edges[1::2]

circuit = QuantumCircuit(num_qubits)
for pair in even_edges:
circuit.cx(pair[0], pair[1])
for pair in odd_edges:
circuit.cx(pair[0], pair[1])

# Choose a backend to run on
service = QiskitRuntimeService()
backend = service.least_busy()

# Transpile the circuit for execution
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
circuit_to_learn = pm.run(circuit)

# Instantiate a NoiseLearner object and execute the noise learning program
learner = NoiseLearner(mode=backend)
job = learner.run([circuit_to_learn])
noise_model = job.result()

Das resultierende NoiseLearnerResult.data ist eine Liste von LayerError-Objekten, die das Rauschmodell für jede einzelne verschränkende Schicht enthalten, die zu den Ziel-Circuits gehört. Jeder LayerError speichert die Schichtinformationen in Form eines Circuits und eines Satzes von Qubit-Labels zusammen mit dem PauliLindbladError für das Rauschmodell, das für die jeweilige Schicht gelernt wurde.

import numpy

print(
f"Noise learner result contains {len(noise_model.data)} entries"
f" and has the following type:\n {type(noise_model)}\n"
)
print(
f"Each element of `NoiseLearnerResult` then contains"
f" an object of type:\n {type(noise_model.data[0])}\n"
)
# Results are truncated
with numpy.printoptions(threshold=200):
print(
f"And each of these `LayerError` objects possess"
f" data on the generators for the error channel: \n"
f"{noise_model.data[0].error.generators}\n"
)
# Results are truncated
with numpy.printoptions(threshold=200):
print(
f"Along with the error rates: \n{noise_model.data[0].error.rates}\n"
)
Noise learner result contains 2 entries and has the following type:
<class 'qiskit_ibm_runtime.utils.noise_learner_result.NoiseLearnerResult'>

Each element of `NoiseLearnerResult` then contains an object of type:
<class 'qiskit_ibm_runtime.utils.noise_learner_result.LayerError'>

And each of these `LayerError` objects possess data on the generators for the error channel:
['IIIIIIIIIIIIIIIIIIIIIIIIIIX', 'IIIIIIIIIIIIIIIIIIIIIIIIIIY',
'IIIIIIIIIIIIIIIIIIIIIIIIIIZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIXI',
'IIIIIIIIIIIIIIIIIIIIIIIIIXX', 'IIIIIIIIIIIIIIIIIIIIIIIIIXY',
'IIIIIIIIIIIIIIIIIIIIIIIIIXZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIYI',
'IIIIIIIIIIIIIIIIIIIIIIIIIYX', 'IIIIIIIIIIIIIIIIIIIIIIIIIYY',
'IIIIIIIIIIIIIIIIIIIIIIIIIYZ', 'IIIIIIIIIIIIIIIIIIIIIIIIIZI',
'IIIIIIIIIIIIIIIIIIIIIIIIIZX', 'IIIIIIIIIIIIIIIIIIIIIIIIIZY',
'IIIIIIIIIIIIIIIIIIIIIIIIIZZ', 'IIIIIIIIIIIIIIIIIIIIIIIIXII',
'IIIIIIIIIIIIIIIIIIIIIIIIXXI', 'IIIIIIIIIIIIIIIIIIIIIIIIXYI',
'IIIIIIIIIIIIIIIIIIIIIIIIXZI', 'IIIIIIIIIIIIIIIIIIIIIIIIYII',
'IIIIIIIIIIIIIIIIIIIIIIIIYXI', 'IIIIIIIIIIIIIIIIIIIIIIIIYYI',
'IIIIIIIIIIIIIIIIIIIIIIIIYZI', 'IIIIIIIIIIIIIIIIIIIIIIIIZII',
'IIIIIIIIIIIIIIIIIIIIIIIIZXI', 'IIIIIIIIIIIIIIIIIIIIIIIIZYI',
'IIIIIIIIIIIIIIIIIIIIIIIIZZI', 'IIIIIIIIIIIIIIIIIIIIIIIXIII',
'IIIIIIIIIIIIIIIIIIIIIIIXXII', 'IIIIIIIIIIIIIIIIIIIIIIIXYII',
'IIIIIIIIIIIIIIIIIIIIIIIXZII', 'IIIIIIIIIIIIIIIIIIIIIIIYIII',
'IIIIIIIIIIIIIIIIIIIIIIIYXII', 'IIIIIIIIIIIIIIIIIIIIIIIYYII',
'IIIIIIIIIIIIIIIIIIIIIIIYZII', 'IIIIIIIIIIIIIIIIIIIIIIIZIII',
'IIIIIIIIIIIIIIIIIIIIIIIZXII', 'IIIIIIIIIIIIIIIIIIIIIIIZYII',
'IIIIIIIIIIIIIIIIIIIIIIIZZII', 'IIIIIIIIIIIIIIIIIIIIIIXIIII',
'IIIIIIIIIIIIIIIIIIIIIIXXIII', 'IIIIIIIIIIIIIIIIIIIIIIXYIII',
'IIIIIIIIIIIIIIIIIIIIIIXZIII', 'IIIIIIIIIIIIIIIIIIIIIIYIIII',
'IIIIIIIIIIIIIIIIIIIIIIYXIII', 'IIIIIIIIIIIIIIIIIIIIIIYYIII',
'IIIIIIIIIIIIIIIIIIIIIIYZIII', 'IIIIIIIIIIIIIIIIIIIIIIZIIII',
'IIIIIIIIIIIIIIIIIIIIIIZXIII', 'IIIIIIIIIIIIIIIIIIIIIIZYIII',
'IIIIIIIIIIIIIIIIIIIIIIZZIII', 'IIIIIIIIIIIIIIIIIIIIIXIIIII',
'IIIIIIIIIIIIIIIIIIIIIXIIIIX', 'IIIIIIIIIIIIIIIIIIIIIXIIIIY',
'IIIIIIIIIIIIIIIIIIIIIXIIIIZ', 'IIIIIIIIIIIIIIIIIIIIIYIIIII',
'IIIIIIIIIIIIIIIIIIIIIYIIIIX', 'IIIIIIIIIIIIIIIIIIIIIYIIIIY',
'IIIIIIIIIIIIIIIIIIIIIYIIIIZ', 'IIIIIIIIIIIIIIIIIIIIIZIIIII',
'IIIIIIIIIIIIIIIIIIIIIZIIIIX', 'IIIIIIIIIIIIIIIIIIIIIZIIIIY',
'IIIIIIIIIIIIIIIIIIIIIZIIIIZ', 'IIIIIIIIIIIIIIIIIIIIXIIIIII',
'IIIIIIIIIIIIIIIIIIIIXIXIIII', 'IIIIIIIIIIIIIIIIIIIIXIYIIII',
'IIIIIIIIIIIIIIIIIIIIXIZIIII', 'IIIIIIIIIIIIIIIIIIIIYIIIIII',
'IIIIIIIIIIIIIIIIIIIIYIXIIII', 'IIIIIIIIIIIIIIIIIIIIYIYIIII',
'IIIIIIIIIIIIIIIIIIIIYIZIIII', 'IIIIIIIIIIIIIIIIIIIIZIIIIII',
'IIIIIIIIIIIIIIIIIIIIZIXIIII', 'IIIIIIIIIIIIIIIIIIIIZIYIIII', ...]

Along with the error rates:
[0.00037 0.00043 0.00032 ... 0.00031 0.00035 0.00017]

Das LayerError.error-Attribut des Noise-Learning-Ergebnisses enthält die Generatoren und Fehlerraten des angepassten Pauli-Lindblad-Modells, das die folgende Form hat:

Λ(ρ)=exp⁡∑jrj(PjρPj†−ρ),\Lambda(\rho) = \exp{\sum_j r_j \left(P_j \rho P_j^\dagger - \rho\right)},

wobei die rjr_j die LayerError.rates sind und PjP_j die in LayerError.generators angegebenen Pauli-Operatoren.

Noise-Learning-Optionen​

Du kannst aus mehreren Optionen wählen, die du beim Instanziieren eines NoiseLearner-Objekts eingibst. Diese Optionen werden von der Klasse qiskit_ibm_runtime.options.NoiseLearnerOptions gekapselt und umfassen u. a. die Möglichkeit, die maximale Anzahl zu lernender Schichten, die Anzahl der Randomisierungen und die Twirling-Strategie anzugeben. Detaillierte Informationen findest du in der NoiseLearnerOptions-API-Dokumentation.

Im Folgenden ein einfaches Beispiel, das zeigt, wie die NoiseLearnerOptions in einem NoiseLearner-Experiment verwendet werden:

# Build a GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))
# Choose a backend to run on
service = QiskitRuntimeService()
backend = service.least_busy()

# Transpile the circuit for execution
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
circuit_to_run = pm.run(circuit_to_learn)

# Instantiate a NoiseLearnerOptions object
learner_options = NoiseLearnerOptions(
max_layers_to_learn=3, num_randomizations=32, twirling_strategy="all"
)

# Instantiate a NoiseLearner object and execute the noise learning program
learner = NoiseLearner(mode=backend, options=learner_options)
job = learner.run([circuit_to_run])
noise_model = job.result()

Rauschmodell als Eingabe für ein Primitive​

Das auf dem Circuit gelernte Rauschmodell kann auch als Eingabe für das IBM Quantum EstimatorV2-Primitive verwendet werden. Es kann auf verschiedene Arten an das Primitive übergeben werden. Die nächsten drei Beispiele zeigen drei Wege: das Rauschmodell direkt an das estimator.options-Attribut übergeben; ein ResilienceOptionsV2-Objekt verwenden, bevor du ein Estimator-Primitive instanziierst; oder ein entsprechend formatiertes Dictionary übergeben.

# Pass the noise model to the `estimator.options` attribute directly
estimator = EstimatorV2(mode=backend)
estimator.options.resilience.layer_noise_model = noise_model
# Specify options through a ResilienceOptionsV2 object
resilience_options = ResilienceOptionsV2(layer_noise_model=noise_model)
estimator_options = EstimatorOptions(resilience=resilience_options)
estimator = EstimatorV2(mode=backend, options=estimator_options)
# Specify options by using a dictionary
options_dict = {
"resilience_level": 2,
"resilience": {"layer_noise_model": noise_model},
}

estimator = EstimatorV2(mode=backend, options=options_dict)

Nachdem das Rauschmodell an das EstimatorV2-Objekt übergeben wurde, kann dieses wie gewohnt Workloads ausführen und Fehlerminderung durchführen.

NoiseLearnerV3​

Überblick​

Ähnlich wie NoiseLearner führt die NoiseLearnerV3-Klasse Experimente durch, die Rauschprozesse basierend auf einem Pauli-Lindblad-Rauschmodell für einen oder mehrere Circuits charakterisieren. Ihre run()-Methode nimmt eine Liste von Anweisungen entgegen, von denen jede ein twirl-annotierter BoxOp sein muss, der ISA-Operationen enthält.

Das Ergebnis eines NoiseLearnerV3-Jobs enthält eine Liste von NoiseLearnerV3Result-Objekten, eines für jede Eingabeanweisung. Der folgende Code zeigt, wie das Hilfsprogramm verwendet wird.

from qiskit import QuantumCircuit
from qiskit.transpiler import CouplingMap
from qiskit.transpiler import generate_preset_pass_manager

from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.noise_learner_v3 import NoiseLearnerV3
from samplomatic.transpiler import generate_boxing_pass_manager
from samplomatic.utils import find_unique_box_instructions

# Build a circuit with two entangling layers
num_qubits = 27
edges = list(CouplingMap.from_line(num_qubits, bidirectional=False))
even_edges = edges[::2]
odd_edges = edges[1::2]

circuit = QuantumCircuit(num_qubits)
for pair in even_edges:
circuit.cx(pair[0], pair[1])
for pair in odd_edges:
circuit.cx(pair[0], pair[1])

# Choose a backend to run on
service = QiskitRuntimeService()
backend = service.least_busy()

# Transpile the circuit for execution
pm = generate_preset_pass_manager(backend=backend, optimization_level=3)
isa_circuit = pm.run(circuit)

# Run the boxing pass manager to group instructions into annotated boxes
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)
boxed_circuit = boxing_pm.run(isa_circuit)

# Find unique boxed instructions
unique_box_instructions = find_unique_box_instructions(boxed_circuit.data)
print(f"Found {len(unique_box_instructions)} unique layers")
print(
f"Each instruction is of type {type(unique_box_instructions[0].operation)}"
)
print(
f"And has annotations: {unique_box_instructions[0].operation.annotations}"
)

# Instantiate a NoiseLearnerV3 object and execute the noise learning program
learner = NoiseLearnerV3(backend)
learner.options.shots_per_randomization = 128
learner.options.num_randomizations = 32
learner_job = learner.run(unique_box_instructions)
learner_result = learner_job.result()
Found 3 unique layers
Each instruction is of type <class 'qiskit.circuit.controlflow.box.BoxOp'>
And has annotations: [Twirl(group='pauli', dressing='left', decomposition='rzsx'), InjectNoise(ref='r223A', modifier_ref='', site='after')]

Das Job-Ergebnis ist eine Liste von NoiseLearnerV3Result-Objekten – eines für jede als Box übergebene Anweisungsgruppe. NoiseLearnerV3Result hat eine to_pauli_lindblad_map()-Methode, die ein PauliLindbladMap-Objekt zurückgibt, das Methoden besitzt, um Generatoren, Fehlerraten und mehr zu extrahieren.

print(
f"The Noise learner V3 result contains {len(learner_result)} entries"
f" and each has the following type:\n {type(learner_result[0])}\n"
)
noise_map = learner_result[0].to_pauli_lindblad_map()
print(
f"After converting to PauliLindbladMap, you can extract data "
f" on the generators for the error channel "
f"(truncated to 3): \n{noise_map.generators()[:3]}\n"
)
with numpy.printoptions(threshold=20):
print(
f"Along with the error rates "
f"(truncated to 3): \n{noise_map.rates[:3]}\n"
)
The Noise learner V3 result contains 3 entries and each has the following type:
<class 'qiskit_ibm_runtime.results.noise_learner_v3.NoiseLearnerV3Result'>

After converting to PauliLindbladMap, you can extract data on the generators for the error channel (truncated to 3):
<QubitSparsePauliList with 3 elements on 27 qubits: [X_5, Y_5, Z_5]>

Along with the error rates (truncated to 3):
[2.3e-04 9.0e-05 1.1e-04]

Noise-Learning-Optionen​

NoiseLearnerV3 unterstützt mehrere Optionen, darunter die Anzahl der Randomisierungen und die Tiefe der Schichtpaare. Ähnlich wie bei den Primitives kannst du die Optionen beim oder nach dem Instanziieren des NoiseLearnerV3-Objekts angeben. Das vorherige Code-Beispiel hat gezeigt, wie die Optionen shots_per_randomization und num_randomizations gesetzt werden. Detaillierte Informationen findest du in der NoiseLearnerV3Options-API-Dokumentation.

Rauschmodell als Eingabe für Executor​

Executor setzt die Vorgaben um, die in Circuit-Annotationen (in Form eines Samplex) und Optionen festgelegt sind. InjectNoise ist die Annotation zur Angabe, wo Rauschen injiziert werden soll, und das pauli_lindblad_maps-Samplex-Argument gibt an, welche Noise-Map verwendet werden soll.

Der Circuit im vorherigen Beispiel durchläuft den Boxing Pass Manager, der Anweisungen zu annotierten Boxen gruppiert. Der relevante Code ist hier zum besseren Verständnis aufgeführt.

  • inject_noise_targets=”gates” gibt an, dass die InjectNoise-Annotationen zu Boxen hinzugefügt werden, die Entangler enthalten.
  • inject_noise_strategy="uniform_modification" gibt an, dass allen äquivalenten Boxen mit InjectNoise-Annotationen dieselbe ref und modifier_ref zugewiesen wird.
    • InjectNoise.ref ist ein eindeutiger Bezeichner, der verwendet wird, um dieser Box ein Rauschmodell zuzuweisen.
    • InjectNoise.modifier_ref ermöglicht es, das einer Box zugewiesene Rauschmodell durch multiplikative Faktoren zu skalieren.
boxing_pm = generate_boxing_pass_manager(
enable_gates=True,
enable_measures=False,
inject_noise_targets="gates", # no measurement mitigation
inject_noise_strategy="uniform_modification",
)

Der Circuit aus dem vorherigen Beispiel enthält drei Boxen, von denen zwei InjectNoise-Annotationen mit unterschiedlichen ref-Attributen enthalten (da sie nicht äquivalent sind).

# box_circuit comes from the example above
for idx, instruction in enumerate(boxed_circuit):
# The `InjectNoise` annotation defines which boxes to inject noise.
print(f"Annotations of box #{idx}: {instruction.operation.annotations}\n")
Annotations of box #0: [Twirl(group='pauli', dressing='left', decomposition='rzsx'), InjectNoise(ref='r223A', modifier_ref='r223A', site='after')]

Annotations of box #1: [Twirl(group='pauli', dressing='left', decomposition='rzsx'), InjectNoise(ref='r621A', modifier_ref='r621A', site='after')]

Annotations of box #2: [Twirl(group='pauli', dressing='right', decomposition='rzsx')]

Das Ergebnis des NoiseLearnerV3-Jobs muss in ein Dictionary umgewandelt werden, bevor es an Executor übergeben wird. Die Schlüssel dieses Dictionarys sind die InjectNoise.ref-Attribute und die Werte sind die entsprechenden Noise-Maps. Diese Zuordnung teilt Executor mit, welche Rauschmodelle wo injiziert werden sollen.

Der folgende Code zeigt, wie der Circuit und das NoiseLearnerV3-Ergebnis aus dem vorherigen Beispiel an Executor übergeben werden, der die Circuit-Varianten mit den injizierten Rauschmodellen generiert und sie auf Hardware ausführt.

from qiskit_ibm_runtime.quantum_program import QuantumProgram
from samplomatic import build

# Generate a quantum program
program = QuantumProgram(shots=1000)

# Build the template circuit and samplex pair
template_circuit, samplex = build(boxed_circuit)

# Convert the NoiseLearnerV3 result to a dictionary
noise_maps = learner_result.to_dict(
instructions=unique_box_instructions, require_refs=False
)

# Append the samplex item and execute
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"pauli_lindblad_maps": noise_maps,
},
)

executor = Executor(backend)
executor_job = executor.run(program)

Verwendung von NoiseLearnerV3 mit clientseitigem Estimator​

Der folgende Code zeigt, wie man das Hilfsprogramm mit dem neuen clientseitigen Estimator verwendet

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

from qiskit_ibm_runtime import QiskitRuntimeService, NoiseLearnerV3
from qiskit_ibm_runtime.executor_estimator import Estimator

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit + observable
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)

observable = SparsePauliOp("ZZ")

# 3. Transpile to ISA
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
pubs = [(isa_circuit, isa_observable)]

# 4. Initialize Estimator with options
estimator = Estimator(backend)
estimator.options.resilience.pec_mitigation = True

# 5. Extract the unique boxed layers from PUBs
layers = estimator.find_unique_layers(pubs)

# 6. Learn the noise model for those layers.
learner = NoiseLearnerV3(backend)
learner_job = learner.run(layers)
learner_result = learner_job.result()

# 7. Convert the result to Pauli-Lindblad maps and pass them to Estimator.
pauli_lindblad_maps = learner_result.to_pauli_lindblad_maps()
estimator.options.resilience.layer_noise_model = zip(
layers, pauli_lindblad_maps
)

# 8. Execute the target PUBs.
job = estimator.run(pubs)
result = job.result()

Nächste Schritte​

Empfehlungen