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 Optionen angeben 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)
Fehlerminderung und Fehlerunterdrückung
-
Da Sampler V2 Samples ohne Nachbearbeitung zurückgibt, unterstützt er keine Resilience Levels.
-
Sampler V2 unterstützt
optimization_levelnicht. -
Estimator V2 wird die Unterstützung für
optimization_levelum den 30. September 2024 einstellen. -
Estimator V2 unterstützt Resilience Level 3 nicht. Dies liegt daran, dass Resilience Level 3 im V1-Estimator Probabilistic Error Cancellation (PEC) verwendet, das nachweislich unverzerrte Ergebnisse auf Kosten exponentieller Verarbeitungszeit liefert. Level 3 wurde entfernt, um diesen Kompromiss deutlicher sichtbar zu machen. Du kannst PEC jedoch weiterhin als Fehlerminderungsmethode verwenden, indem du die Option
pec_mitigationangibst. -
Estimator V2 unterstützt
resilience_level0–2, wie in der folgenden Tabelle beschrieben. Diese Optionen sind ausgereifter als ihre V1-Entsprechungen. Du kannst außerdem einzelne Fehlerminderungs- und Fehlerunterdrückungsmethoden explizit ein- oder ausschalten.Level 1 Level 2 Measurement Twirling Measurement Twirling Readout-Fehlerminderung Readout-Fehlerminderung ZNE
- 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)
# Set resilience_level to 0
estimator.options.resilience_level = 0
# Turn on measurement error mitigation
estimator.options.resilience.measure_mitigation = True
from qiskit_ibm_runtime import Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
print(f">> dynamical decoupling sequence to use: {sampler.options.dynamical_decoupling.sequence_type}")
from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2
Transpilation
V2-Primitive unterstützen ausschließlich Circuits, die der Instruction Set Architecture (ISA) eines bestimmten Backends entsprechen. Da die Primitiven keine Layout-, Routing- und Übersetzungsoperationen durchführen, werden die entsprechenden Transpilationsoptionen aus V1 nicht unterstützt.
Job-Status
Die V2-Primitiven haben eine neue RuntimeJobV2-Klasse, die von BasePrimitiveJob erbt. Die status()-Methode dieser neuen Klasse gibt einen String zurück, anstatt eines JobStatus-Enums aus Qiskit. Details findest du in der RuntimeJobV2-API-Referenz.
- V2-Primitive
- V1-Primitive
job = estimator.run(...)
# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")
from qiskit.providers.jobstatus import JobStatus
job = estimator.run(...)
#check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() is JobStatus.RUNNING}")
Schritte zur Migration zu Estimator V2
-
Ersetze
from qiskit_ibm_runtime import Estimatordurchfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
Entferne alle
from qiskit_ibm_runtime import Options-Anweisungen, da dieOptions-Klasse von V2-Primitiven nicht verwendet wird. Du kannst stattdessen Optionen beim Initialisieren derEstimatorV2-Klasse als Dictionary übergeben (zum Beispielestimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), oder sie nach der Initialisierung setzen:estimator = Estimator(backend)
estimator.options.dynamical_decoupling.enable = True -
Überprüfe alle unterstützten Optionen und nehme entsprechende Anpassungen vor.
-
Fasse jeden Circuit, den du ausführen möchtest, zusammen mit den Observablen und Parameterwerten, die du auf den Circuit anwenden möchtest, in einem Tupel (einem PUB) zusammen. Verwende beispielsweise
(circuit1, observable1, parameter_set1), wenn ducircuit1mitobservable1undparameter_set1ausführen möchtest. -
Möglicherweise musst du deine Arrays von Observablen oder Parametersätzen umformen, wenn du deren äußeres Produkt anwenden möchtest. Zum Beispiel ergibt ein Observablen-Array der Form (4, 1) und ein Parametersatz-Array der Form (1, 6) ein Ergebnis von (4, 6) Erwartungswerten. Mehr dazu findest du in den Numpy-Broadcasting-Regeln.
-
Du kannst optional die gewünschte Präzision für diesen spezifischen PUB angeben.
-
Aktualisiere die
run()-Methode des Estimators, um die Liste der PUBs zu übergeben. Zum Beispiel:run([(circuit1, observable1, parameter_set1)]). Du kannst hier optional eineprecisionangeben, die dann für alle PUBs gilt. -
Die Ergebnisse von Estimator V2-Jobs sind nach PUBs gruppiert. Du kannst den Erwartungswert und den Standardfehler für jeden PUB durch Indizierung abrufen. Zum Beispiel:
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
Vollständige Estimator-Beispiele
Ein einzelnes Experiment ausführen
Verwende den Estimator, um den Erwartungswert eines einzelnen Circuit-Observable-Paares zu bestimmen.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(backend)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
estimator = Estimator(backend)
job = estimator.run(isa_circuit, isa_observable)
result = job.result()
print(f" > Observable: {observable.paulis}")
print(f" > Expectation value: {result.values}")
print(f" > Metadata: {result.metadata}")
Mehrere Experimente in einem einzigen Job ausführen
Verwende den Estimator, um die Erwartungswerte mehrerer Circuit-Observable-Paare zu bestimmen.
- Estimator V2
- Estimator (V1)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
n_qubits = 3
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run([(isa_circuits[0], isa_observables[0]),(isa_circuits[1], isa_observables[1]),(isa_circuits[2], isa_observables[2])])
job_result = job.result()
for idx in range(len(job_result)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
# Get ISA circuits
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]
estimator = Estimator(backend)
job = estimator.run(isa_circuits, isa_observables)
result = job.result()
print(f" > Expectation values: {result.values}")
Parametrisierte Circuits ausführen
Verwende den Estimator, um mehrere Experimente in einem einzigen Job auszuführen, und nutze dabei Parameterwerte, um die Wiederverwendbarkeit von Circuits zu erhöhen. Im folgenden Beispiel sind Schritt 1 und 2 für V1 und V2 identisch.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import EstimatorV2 as Estimator
# Step 3: Execute using Qiskit primitives.
# Reshape observable array for broadcasting
reshaped_ops = np.fromiter(isa_observables, dtype=object)
reshaped_ops = reshaped_ops.reshape((4, 1))
estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(chsh_isa_circuit, reshaped_ops, individual_phases)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
print(f">>> Metadata: {pub_result.metadata}")
import numpy as np
from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
theta = Parameter("θ")
chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)
number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]
ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]
from qiskit_ibm_runtime import Estimator
# Step 3: Execute using Qiskit Primitives.
num_ops = len(isa_observables)
batch_circuits = [chsh_isa_circuit] * number_of_phases * num_ops
batch_ops = [op for op in isa_observables for _ in individual_phases]
batch_phases = individual_phases * num_ops
estimator = Estimator(backend, options={"shots": int(1e4)})
job = estimator.run(batch_circuits, batch_ops, batch_phases)
expvals = job.result().values
Sessions und erweiterte Optionen verwenden
Nutze Sessions und erweiterte Optionen, um die Circuit-Performance auf QPUs zu optimieren.
Der folgende Codeblock gibt für Nutzer des Open Plans einen Fehler zurück, da er Sessions verwendet. Workloads im Open Plan können nur im Job-Modus oder im Batch-Modus ausgeführt werden.
- Estimator V2
- Estimator (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator()
estimator.options.resilience_level = 1
job = estimator.run([(isa_circuit, isa_observable)])
another_job = estimator.run([(another_isa_circuit, another_isa_observable)])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")
# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)
options = Options()
options.optimization_level = 2
options.resilience_level = 2
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator(options=options)
job = estimator.run(isa_circuit, isa_observable)
another_job = estimator.run(another_isa_circuit, another_isa_observable)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Expectation values job 1: {result.values}")
# second job
print(f" > Expectation values job 2: {another_result.values}")
Schritte zur Migration auf Sampler V2
- Ersetze
from qiskit_ibm_runtime import Samplerdurchfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - Entferne alle
from qiskit_ibm_runtime import Options-Anweisungen, da die KlasseOptionsvon V2-Primitives nicht verwendet wird. Stattdessen kannst du Optionen als Dictionary beim Initialisieren der KlasseSamplerV2übergeben (zum Beispielsampler = Sampler(backend, options={"default_shots": 1024})), oder sie nach der Initialisierung setzen:sampler = Sampler(backend)
sampler.options.default_shots = 1024 - Sieh dir alle unterstützten Optionen an und nimm entsprechende Anpassungen vor.
- Fasse jeden Circuit, den du ausführen möchtest, zusammen mit den Observables und Parameterwerten, die du auf den Circuit anwenden willst, in einem Tupel (einem PUB) zusammen. Verwende zum Beispiel
(circuit1, parameter_set1), wenn ducircuit1mitparameter_set1ausführen möchtest. Du kannst optional die Anzahl der Shots für diesen spezifischen PUB angeben. - Aktualisiere die
run()-Methode des Samplers, sodass sie die Liste der PUBs entgegennimmt. Zum Beispiel:run([(circuit1, parameter_set1)]). Optional kannst du hiershotsangeben, was dann für alle PUBs gilt. - Die Job-Ergebnisse von Sampler V2 sind nach PUBs gruppiert. Du kannst die Ausgabedaten für jeden PUB durch Indexierung abrufen. Während Sampler V2 ungewichtete Samples zurückgibt, bietet die Ergebnisklasse eine praktische Methode, um stattdessen Counts zu erhalten. Zum Beispiel:
pub_result = job.result()[0]
print(f">>> Counts: {pub_result.data.meas.get_counts()}")
print(f">>> Per-shot measurement: {pub_result.data.meas.get_counts()}")
Du benötigst den Namen des klassischen Registers, um die Ergebnisse abzurufen. Standardmäßig heißt es meas, wenn du measure_all() verwendest. Wenn du beim Definieren deines Circuits ein oder mehrere klassische Register mit einem nicht standardmäßigen Namen erstellst, verwende diesen Namen, um die Ergebnisse abzurufen. Den Namen des klassischen Registers findest du durch Ausführen von <circuit_name>.cregs. Zum Beispiel: qc.cregs.
Vollständige Sampler-Beispiele
Ein einzelnes Experiment ausführen
Verwende den Sampler, um die Counts oder die Quasi-Wahrscheinlichkeitsverteilung eines einzelnen Circuits zu bestimmen.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
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()
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuit)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Mehrere Experimente in einem einzigen Job ausführen
Verwende den Sampler, um die Counts oder Quasi-Wahrscheinlichkeitsverteilungen mehrerer Circuits in einem einzigen Job zu bestimmen.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)
sampler = Sampler(backend)
job = sampler.run(isa_circuits)
result = job.result()
for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
n_qubits = 127
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()
sampler = Sampler(backend)
job = sampler.run(circuits)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
Parametrisierte Circuits ausführen
Führe mehrere Experimente in einem einzigen Job aus und nutze Parameterwerte, um Circuits mehrfach wiederverwenden zu können.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 127
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(backend)
job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService
# Step 1: Map classical inputs to a quantum problem
num_qubits = 5
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()
# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]
# Step 2: Optimize problem for quantum execution.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
# Step 3: Execute using Qiskit primitives.
from qiskit_ibm_runtime import Sampler
sampler = Sampler(backend)
job = sampler.run([isa_circuit] * 3, parameter_values)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
Sessions und erweiterte Optionen verwenden
Erkunde Sessions und erweiterte Optionen, um die Circuit-Performance auf QPUs zu optimieren.
Der folgende Codeblock gibt für Nutzer des Open Plans einen Fehler zurück, da er Sessions verwendet. Workloads im Open Plan können nur im Job-Modus oder im Batch-Modus ausgeführt werden.
- Sampler V2
- Sampler (V1)
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
service = QiskitRuntimeService()
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler()
job = sampler.run([isa_circuit])
another_job = sampler.run([another_isa_circuit])
result = job.result()
another_result = another_job.result()
# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")
# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session, Options
n_qubits = 127
rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()
options = Options()
options.optimization_level = 2
options.resilience_level = 0
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler(options=options)
job = sampler.run(circuit)
another_job = sampler.run(another_circuit)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
Nächste Schritte
- Erfahre mehr über das Festlegen von Optionen im Leitfaden Optionen angeben.
- Lerne mehr über Primitive-Eingaben und -Ausgaben.
- Experimentiere mit dem Tutorial zur CHSH-Ungleichung.