Zum Hauptinhalt springen

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.4.1
qiskit-ibm-runtime~=0.47.0
samplomatic~=0.18.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 directed execution model, gibt dir die Möglichkeit, die Schichten, die du lernen möchtest, explizit anzugeben.

hinweis

NoiseLearner funktioniert nur mit EstimatorV2 und NoiseLearnerV3 funktioniert nur mit Executor.

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 — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime samplomatic
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{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',
'IIIIIIIIIIIIIIIIIIIIIIIIXIX', 'IIIIIIIIIIIIIIIIIIIIIIIIXIY',
'IIIIIIIIIIIIIIIIIIIIIIIIXIZ', 'IIIIIIIIIIIIIIIIIIIIIIIIYII',
'IIIIIIIIIIIIIIIIIIIIIIIIYIX', 'IIIIIIIIIIIIIIIIIIIIIIIIYIY',
'IIIIIIIIIIIIIIIIIIIIIIIIYIZ', 'IIIIIIIIIIIIIIIIIIIIIIIIZII',
'IIIIIIIIIIIIIIIIIIIIIIIIZIX', 'IIIIIIIIIIIIIIIIIIIIIIIIZIY',
'IIIIIIIIIIIIIIIIIIIIIIIIZIZ', 'IIIIIIIIIIIIIIIIIIIIIIIXIII',
'IIIIIIIIIIIIIIIIIIIIIIIYIII', 'IIIIIIIIIIIIIIIIIIIIIIIZIII',
'IIIIIIIIIIIIIIIIIIIIIIXIIII', 'IIIIIIIIIIIIIIIIIIIIIIXXIII',
'IIIIIIIIIIIIIIIIIIIIIIXYIII', 'IIIIIIIIIIIIIIIIIIIIIIXZIII',
'IIIIIIIIIIIIIIIIIIIIIIYIIII', 'IIIIIIIIIIIIIIIIIIIIIIYXIII',
'IIIIIIIIIIIIIIIIIIIIIIYYIII', 'IIIIIIIIIIIIIIIIIIIIIIYZIII',
'IIIIIIIIIIIIIIIIIIIIIIZIIII', 'IIIIIIIIIIIIIIIIIIIIIIZXIII',
'IIIIIIIIIIIIIIIIIIIIIIZYIII', 'IIIIIIIIIIIIIIIIIIIIIIZZIII',
'IIIIIIIIIIIIIIIIIIIIIXIIIII', 'IIIIIIIIIIIIIIIIIIIIIXXIIII',
'IIIIIIIIIIIIIIIIIIIIIXYIIII', 'IIIIIIIIIIIIIIIIIIIIIXZIIII',
'IIIIIIIIIIIIIIIIIIIIIYIIIII', 'IIIIIIIIIIIIIIIIIIIIIYXIIII',
'IIIIIIIIIIIIIIIIIIIIIYYIIII', 'IIIIIIIIIIIIIIIIIIIIIYZIIII',
'IIIIIIIIIIIIIIIIIIIIIZIIIII', 'IIIIIIIIIIIIIIIIIIIIIZXIIII',
'IIIIIIIIIIIIIIIIIIIIIZYIIII', 'IIIIIIIIIIIIIIIIIIIIIZZIIII',
'IIIIIIIIIIIIIIIIIIIIXIIIIII', 'IIIIIIIIIIIIIIIIIIIIXXIIIII',
'IIIIIIIIIIIIIIIIIIIIXYIIIII', 'IIIIIIIIIIIIIIIIIIIIXZIIIII',
'IIIIIIIIIIIIIIIIIIIIYIIIIII', 'IIIIIIIIIIIIIIIIIIIIYXIIIII',
'IIIIIIIIIIIIIIIIIIIIYYIIIII', 'IIIIIIIIIIIIIIIIIIIIYZIIIII',
'IIIIIIIIIIIIIIIIIIIIZIIIIII', 'IIIIIIIIIIIIIIIIIIIIZXIIIII',
'IIIIIIIIIIIIIIIIIIIIZYIIIII', 'IIIIIIIIIIIIIIIIIIIIZZIIIII',
'IIIIIIIIIIIIIIIIIIIXIIIIIII', 'IIIIIIIIIIIIIIIIIIIXXIIIIII',
'IIIIIIIIIIIIIIIIIIIXYIIIIII', 'IIIIIIIIIIIIIIIIIIIXZIIIIII',
'IIIIIIIIIIIIIIIIIIIYIIIIIII', 'IIIIIIIIIIIIIIIIIIIYXIIIIII',
'IIIIIIIIIIIIIIIIIIIYYIIIIII', 'IIIIIIIIIIIIIIIIIIIYZIIIIII', ...]

Along with the error rates:
[5.9e-04 5.3e-04 5.7e-04 ... 0.0e+00 1.0e-05 0.0e+00]

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

Λ(ρ)=expjrj(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 durch die Klasse qiskit_ibm_runtime.options.NoiseLearnerOptions eingekapselt 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 in Qiskit Runtime implementierte EstimatorV2-Primitive verwendet werden. Es kann auf verschiedene Arten an das Primitive übergeben werden. Die nächsten drei Beispiele zeigen, wie du das Rauschmodell direkt an das estimator.options-Attribut übergeben kannst, indem du ein ResilienceOptionsV2-Objekt verwendest, bevor du ein Estimator-Primitive instanziierst, und indem du ein entsprechend formatiertes Dictionary übergibst.

# 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 es wie gewohnt verwendet werden, um Workloads auszuführen und Fehlerminderung durchzufü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='r789B', modifier_ref='', site='before')]

Das Job-Ergebnis ist eine Liste von NoiseLearnerV3Result-Objekten, eines für jeden eingegebenen geboxter Satz von Anweisungen. 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 (truncated to 3): \n{noise_map.generators()[:3]}\n"
)
with numpy.printoptions(threshold=20):
print(
f"Along with the error rates (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_0, Y_0, Z_0]>

Along with the error rates (truncated to 3):
[0.00026 0.00032 0.00023]

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 folgt den in Circuit-Annotationen (in Form eines Samplex) und Optionen angegebenen Design-Intentionen. InjectNoise ist die Annotation zur Angabe, wo Rauschen injiziert werden soll, und das pauli_lindblad_maps-Samplex-Argument gibt an, welche Rauschkarte verwendet werden soll.

Der Circuit im vorherigen Beispiel läuft durch den Boxing-Pass-Manager, der Anweisungen in annotierte Boxen gruppiert. Der relevante Code wird hier zur leichteren Verständlichkeit hinzugefügt.

  • inject_noise_targets="gates" gibt an, den Boxen, die Verschränker enthalten, die InjectNoise-Annotationen hinzuzufügen.
  • inject_noise_strategy="uniform_modification" gibt an, allen äquivalenten Boxen mit InjectNoise-Annotationen dasselbe ref und modifier_ref zuzuweisen.
    • InjectNoise.ref ist ein eindeutiger Bezeichner, der verwendet wird, um einer Box ein Rauschmodell zuzuweisen.
    • InjectNoise.modifier_ref ermöglicht die Skalierung des einer Box zugewiesenen Rauschmodells durch multiplikative Faktoren.
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='r789B', modifier_ref='r789B', site='before')]

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

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 Rauschkarten. 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)

Nächste Schritte

Empfehlungen