Zum Hauptinhalt springen

Zu den Qiskit Runtime V2-Primitiven migrieren

warnung

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.

warnung

Sobald die V1-Primitiven nicht mehr unterstützt werden, importiert import <Primitiv> die V2-Version des jeweiligen Primitivs.

from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as 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 der run()-Methode erhalten, das die gewünschte Präzision der Erwartungswertschätzungen angibt.
  • Sampler V2 hat das shots-Argument in seiner run()-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 Beispiel qc.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)

# 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

Sampler-Beispiele (Ein- und Ausgabe)

  # 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()

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:

  • SamplerV2 und EstimatorV2 haben 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 die update()-Methode, die Änderungen auf das options-Attribut anwendet.
  • Wenn du keinen Wert für eine Option angibst, erhält sie den speziellen Wert Unset und es werden die Server-Standardwerte verwendet.
  • Bei V2-Primitiven ist das options-Attribut vom Python-Typ dataclass. Du kannst die eingebaute asdict-Methode verwenden, um es in ein Dictionary umzuwandeln.

Die Liste der verfügbaren Optionen findest du in der API-Referenz.

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

Fehlerminderung und Fehlerunterdrückung

  • Da Sampler V2 Samples ohne Nachbearbeitung zurückgibt, unterstützt er keine Resilience Levels.

  • Sampler V2 unterstützt optimization_level nicht.

  • Estimator V2 wird die Unterstützung für optimization_level um 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_mitigation angibst.

  • Estimator V2 unterstützt resilience_level 0–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 1Level 2
    Measurement TwirlingMeasurement Twirling
    Readout-FehlerminderungReadout-Fehlerminderung
    ZNE
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 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}")

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.

job = estimator.run(...)

# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")

Schritte zur Migration zu Estimator V2

  1. Ersetze from qiskit_ibm_runtime import Estimator durch from qiskit_ibm_runtime import EstimatorV2 as Estimator.

  2. Entferne alle from qiskit_ibm_runtime import Options-Anweisungen, da die Options-Klasse von V2-Primitiven nicht verwendet wird. Du kannst stattdessen Optionen beim Initialisieren der EstimatorV2-Klasse als Dictionary übergeben (zum Beispiel estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), oder sie nach der Initialisierung setzen:

    estimator = Estimator(backend)
    estimator.options.dynamical_decoupling.enable = True
  3. Überprüfe alle unterstützten Optionen und nehme entsprechende Anpassungen vor.

  4. 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 du circuit1 mit observable1 und parameter_set1 ausführen möchtest.

  5. 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.

  6. Du kannst optional die gewünschte Präzision für diesen spezifischen PUB angeben.

  7. Aktualisiere die run()-Methode des Estimators, um die Liste der PUBs zu übergeben. Zum Beispiel: run([(circuit1, observable1, parameter_set1)]). Du kannst hier optional eine precision angeben, die dann für alle PUBs gilt.

  8. 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.

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}")

Mehrere Experimente in einem einzigen Job ausführen

Verwende den Estimator, um die Erwartungswerte mehrerer Circuit-Observable-Paare zu bestimmen.

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}")

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.

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}")

Sessions und erweiterte Optionen verwenden

Nutze Sessions und erweiterte Optionen, um die Circuit-Performance auf QPUs zu optimieren.

vorsicht

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.

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}")

Schritte zur Migration auf Sampler V2

  1. Ersetze from qiskit_ibm_runtime import Sampler durch from qiskit_ibm_runtime import SamplerV2 as Sampler.
  2. Entferne alle from qiskit_ibm_runtime import Options-Anweisungen, da die Klasse Options von V2-Primitives nicht verwendet wird. Stattdessen kannst du Optionen als Dictionary beim Initialisieren der Klasse SamplerV2 übergeben (zum Beispiel sampler = Sampler(backend, options={"default_shots": 1024})), oder sie nach der Initialisierung setzen:
    sampler = Sampler(backend)
    sampler.options.default_shots = 1024
  3. Sieh dir alle unterstützten Optionen an und nimm entsprechende Anpassungen vor.
  4. 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 du circuit1 mit parameter_set1 ausführen möchtest. Du kannst optional die Anzahl der Shots für diesen spezifischen PUB angeben.
  5. Aktualisiere die run()-Methode des Samplers, sodass sie die Liste der PUBs entgegennimmt. Zum Beispiel: run([(circuit1, parameter_set1)]). Optional kannst du hier shots angeben, was dann für alle PUBs gilt.
  6. 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()}")
hinweis

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.

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()

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.

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()}")

Parametrisierte Circuits ausführen

Führe mehrere Experimente in einem einzigen Job aus und nutze Parameterwerte, um Circuits mehrfach wiederverwenden zu können.

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()}")

Sessions und erweiterte Optionen verwenden

Erkunde Sessions und erweiterte Optionen, um die Circuit-Performance auf QPUs zu optimieren.

vorsicht

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.

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()}")

Nächste Schritte

Empfehlungen