Zu den Qiskit Runtime V2-Primitiven migrieren
Die ursprünglichen Primitiven (auch als V1-Primitive bezeichnet), V1 Sampler und V1 Estimator, wurden in qiskit-ibm-runtime 0.23 als veraltet markiert.
Ihre Unterstützung wurde am 15. August 2024 eingestellt.
Mit der Ablösung der V1-Primitiven sollte der gesamte Code auf die V2-Schnittstellen migriert werden. Diese Anleitung beschreibt, was sich in den Qiskit Runtime V2-Primitiven (verfügbar ab qiskit-ibm-runtime 0.21.0) geändert hat und warum, erläutert jedes neue Primitiv im Detail und gibt Beispiele, die dir helfen, Code von den Legacy-Primitiven auf die V2-Primitiven umzustellen. Die Beispiele in dieser Anleitung verwenden alle die Qiskit Runtime-Primitiven, aber grundsätzlich gelten dieselben Änderungen auch für andere Primitiv-Implementierungen. Funktionen, die nur für Qiskit Runtime verfügbar sind – wie etwa Fehlerminderung –, bleiben weiterhin exklusiv für Qiskit Runtime.
Informationen zu den Änderungen an den Qiskit-Referenzprimitiven (jetzt als Statevector-Primitive bezeichnet) findest du im Abschnitt qiskit.primitives auf der Seite zu den Qiskit 1.0-Funktionsänderungen. Die V2-Referenzimplementierungen findest du unter StatevectorSampler und StatevectorEstimator.
Überblick
Version 2 der Primitiven wird mit einer neuen Basisklasse für Sampler und Estimator eingeführt (BaseSamplerV2 und BaseEstimatorV2), zusammen mit neuen Typen für Ein- und Ausgaben.
Die neue Schnittstelle ermöglicht es dir, einen einzelnen Circuit mit mehreren Observablen (beim Estimator) und mehreren Parameterwertmengen anzugeben, sodass Sweeps über Parameterwerte und Observablen effizient definiert werden können. Bisher musstest du denselben Circuit mehrfach angeben, um die Datenmenge zu matchen, die kombiniert werden sollte. Außerdem kannst du neben resilience_level (beim Estimator) als einfachem Steuerknopf jetzt mit den V2-Primitiven einzelne Fehlerminderungs- und Fehlerunterdrückungsmethoden flexibel ein- und ausschalten und so individuell anpassen.
Um die gesamte Job-Ausführungszeit zu reduzieren, akzeptieren V2-Primitive nur Circuits und Observablen, die Anweisungen aus dem vom Ziel-QPU (Quantum Processing Unit) unterstützten Anweisungssatz verwenden. Solche Circuits und Observablen werden als ISA-Circuits (Instruction Set Architecture) bzw. ISA-Observablen bezeichnet. V2-Primitive führen keine Layout-, Routing- oder Übersetzungsoperationen durch. Anleitungen zur Transformation von Circuits findest du in der Transpilationsdokumentation.
Sampler V2 wurde vereinfacht und konzentriert sich auf seine Kernaufgabe: das Sampling des Ausgaberegisters aus der Ausführung von Quantencircuits. Er gibt die Samples zurück – deren Typ durch das Programm definiert wird –, ohne Gewichtungen. Die Ausgabedaten sind außerdem nach den Ausgaberegisternamen getrennt, die das Programm definiert. Diese Änderung ermöglicht in Zukunft die Unterstützung von Circuits mit klassischem Kontrollfluss.
Vollständige Details findest du in der EstimatorV2-API-Referenz und der SamplerV2-API-Referenz.
Wesentliche Änderungen
Import
Aus Gründen der Abwärtskompatibilität musst du die V2-Primitiven explizit importieren. import <Primitiv>V2 as <Primitiv> ist nicht zwingend erforderlich, erleichtert aber die Umstellung des Codes auf V2.
Sobald die V1-Primitiven nicht mehr unterstützt werden, importiert import <Primitiv> die V2-Version des jeweiligen Primitivs.
- Estimator V2
- Estimator (V1)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Estimator
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import Sampler
Ein- und Ausgabe
Eingabe
Sowohl SamplerV2 als auch EstimatorV2 nehmen einen oder mehrere Primitive Unified Blocs (PUBs) als Eingabe entgegen. Jeder PUB ist ein Tupel, das einen Circuit und die dafür vorgesehenen Daten enthält – das können mehrere Observablen und Parameter sein. Jeder PUB liefert ein Ergebnis zurück.
- Sampler V2 PUB-Format: (
<Circuit>,<Parameterwerte>,<Shots>), wobei<Parameterwerte>und<Shots>optional sind. - Estimator V2 PUB-Format: (
<Circuit>,<Observablen>,<Parameterwerte>,<Präzision>), wobei<Parameterwerte>und<Präzision>optional sind. Beim Kombinieren von Observablen und Parameterwerten gelten die Numpy-Broadcasting-Regeln.
Zusätzlich wurden folgende Änderungen vorgenommen:
- Estimator V2 hat ein
precision-Argument in derrun()-Methode erhalten, das die gewünschte Präzision der Erwartungswertschätzungen angibt. - Sampler V2 hat das
shots-Argument in seinerrun()-Methode.
Beispiele
Estimator V2-Beispiel mit Präzision in run():
# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)
Sampler V2-Beispiel mit Shots in run():
# Sample two circuits at 128 shots each.
sampler.run([circuit1, circuit2], shots=128)
# Sample two circuits at different amounts of shots.
# The "None"s are necessary as placeholders
# for the lack of parameter values in this example.
sampler.run([
(circuit1, None, 123),
(circuit2, None, 456),
])
Ausgabe
Die Ausgabe erfolgt jetzt im PubResult-Format. Ein PubResult enthält die Daten und Metadaten, die aus der Ausführung eines einzelnen PUBs resultieren.
-
Estimator V2 gibt weiterhin Erwartungswerte zurück.
-
Der
data-Teil eines Estimator V2 PubResult enthält sowohl Erwartungswerte als auch Standardfehler (stds). V1 hat die Varianz in den Metadaten zurückgegeben. -
Sampler V2 gibt Shot-genaue Messungen in Form von Bitstrings zurück, anstatt der Quasi-Wahrscheinlichkeitsverteilungen der V1-Schnittstelle. Die Bitstrings zeigen die Messergebnisse und erhalten die Shot-Reihenfolge, in der die Messungen durchgeführt wurden.
-
Sampler V2 bietet Hilfsmethoden wie
get_counts()zur einfacheren Migration. -
Die Sampler V2-Ergebnisobjekte ordnen Daten nach den klassischen Registernamen der Eingabecircuits an, um Kompatibilität mit dynamischen Circuits zu gewährleisten. Standardmäßig heißt das klassische Register
meas, wie im folgenden Beispiel gezeigt. Wenn du beim Definieren deines Circuits ein oder mehrere klassische Register mit einem anderen Namen anlegst, verwende diesen Namen, um die Ergebnisse abzurufen. Den Registernamen findest du mit<circuit_name>.cregs, zum Beispielqc.cregs.# Define a quantum circuit with 2 qubits
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()
circuit.draw()┌───┐ ░ ┌─┐
q_0: ┤ H ├──■───░─┤M├───
└───┘┌─┴─┐ ░ └╥┘┌─┐
q_1: ─────┤ X ├─░──╫─┤M├
└───┘ ░ ║ └╥┘
meas: 2/══════════════╩══╩═
0 1
Estimator-Beispiele (Ein- und Ausgabe)
- 1 Circuit, 4 Observablen
- 1 Circuit, 4 Observablen, 2 Parametersätze
- 2 Circuits, 2 Observablen
# Estimator V1: Execute 1 circuit with 4 observables
job = estimator_v1.run([circuit] * 4, [obs1, obs2, obs3, obs4])
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables
job = estimator_v2.run([(circuit, [obs1, obs2, obs3, obs4])])
evs = job.result()[0].data.evs
# Estimator V1: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v1.run([circuit] * 8, [obs1, obs2, obs3, obs4] * 2, [vals1, vals2] * 4)
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v2.run([(circuit, [[obs1], [obs2], [obs3], [obs4]], [[vals1], [vals2]])])
evs = job.result()[0].data.evs
# Estimator V1: Cannot execute 2 circuits with different observables
# Estimator V2: Execute 2 circuits with 2 different observables. There are
# two PUBs because each PUB can have only one circuit.
job = estimator_v2.run([(circuit1, obs1), (circuit2, obs2)])
evs1 = job.result()[0].data.evs # result for pub 1 (circuit 1)
evs2 = job.result()[1].data.evs # result for pub 2 (circuit 2)
Sampler-Beispiele (Ein- und Ausgabe)
- 1 Circuit, 3 Parametersätze
- 2 Circuits, 1 Parametersatz
- V2-Ausgabe in V1-Format konvertieren
# Sampler V1: Execute 1 circuit with 3 parameter sets
job = sampler_v1.run([circuit] * 3, [vals1, vals2, vals3])
dists = job.result().quasi_dists
# Sampler V2: Executing 1 circuit with 3 parameter sets
job = sampler_v2.run([(circuit, [vals1, vals2, vals3])])
counts = job.result()[0].data.meas.get_counts()
# Sampler V1: Execute 2 circuits with 1 parameter set
job = sampler_v1.run([circuit1, circuit2], [vals1] * 2)
dists = job.result().quasi_dists
# Sampler V2: Execute 2 circuits with 1 parameter set
job = sampler_v2.run([(circuit1, vals1), (circuit2, vals1)])
counts1 = job.result()[0].data.meas.get_counts() # result for pub 1 (circuit 1)
counts2 = job.result()[1].data.meas.get_counts() # result for pub 2 (circuit 2)
Das V1-Ausgabeformat war ein Dictionary, bei dem die Schlüssel Bitstrings (als Integer) und die Werte Quasi-Wahrscheinlichkeiten waren – für jeden Circuit. Das V2-Format verwendet dieselben Schlüssel (jedoch als Strings) und Counts als Werte. Um das V2-Format in V1 umzuwandeln, teile die Counts durch die Anzahl der Shots. Wie die Anzahl der Shots festgelegt wird, ist in der Anleitung Sampler-Optionen beschrieben.
v2_result = sampler_v2_job.result()
v1_format = []
for pub_result in v2_result:
counts = pub_result.data.meas.get_counts()
v1_format.append( {int(key, 2): val/shots for key, val in counts.items()} )
Beispiel mit verschiedenen Ausgaberegistern
from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
alpha = ClassicalRegister(5, "alpha")
beta = ClassicalRegister(7, "beta")
qreg = QuantumRegister(12)
circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.measure(qreg[:5], alpha)
circuit.measure(qreg[5:], beta)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=12)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f" >> Counts for the alpha output register: {pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: {pub_result.data.beta.get_counts()}")
Optionen
Optionen werden in den V2-Primitiven auf folgende Weise anders angegeben:
SamplerV2undEstimatorV2haben jetzt separate Optionsklassen. Du kannst die verfügbaren Optionen einsehen und Optionswerte während oder nach der Initialisierung des Primitivs aktualisieren.- Anstelle der
set_options()-Methode haben die V2-Primitiv-Optionen dieupdate()-Methode, die Änderungen auf dasoptions-Attribut anwendet. - Wenn du keinen Wert für eine Option angibst, erhält sie den speziellen Wert
Unsetund es werden die Server-Standardwerte verwendet. - Bei V2-Primitiven ist das
options-Attribut vom Python-Typdataclass. Du kannst die eingebauteasdict-Methode verwenden, um es in ein Dictionary umzuwandeln.
Die Liste der verfügbaren Optionen findest du in der API-Referenz.
- Estimator V2
- Estimator (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
estimator = Estimator(backend, options={"resilience_level": 2})
# Setting options after primitive initialization
# This uses auto complete.
estimator.options.default_shots = 4000
# This does bulk update.
estimator.options.update(default_shots=4000, resilience_level=2)
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(estimator.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
estimator = Estimator(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
estimator.set_options(shots=4000)
- Sampler V2
- Sampler (V1)
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
sampler = Sampler(backend, options={"default_shots": 4096})
# Setting options after primitive initialization
# This uses auto complete.
sampler.options.dynamical_decoupling.enable = True
# Turn on gate twirling. Requires qiskit_ibm_runtime 0.23.0 or later.
sampler.options.twirling.enable_gates = True
# This does bulk update. The value for default_shots is overridden if you specify shots with run() or in the PUB.
sampler.options.update(default_shots=1024, dynamical_decoupling={"sequence_type": "XpXm"})
# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(sampler.options))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
sampler = Sampler(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
sampler.set_options(shots=2000)