Primitive-Eingaben und -Ausgaben
Die Beta-Version eines neuen Ausführungsmodells ist jetzt verfügbar. Das gerichtete Ausführungsmodell bietet mehr Flexibilität bei der Anpassung deines Fehlerminderungsworkflows. Weitere Informationen findest du im Leitfaden zum Gerichteten Ausführungsmodell.
Paketversionen
Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen, diese oder neuere Versionen zu verwenden.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Diese Seite gibt einen Überblick über die Eingaben und Ausgaben der Qiskit Runtime Primitives, die Workloads auf IBM Quantum®-Rechenressourcen ausführen. Diese Primitives ermöglichen es dir, vektorisierte Workloads mithilfe einer Datenstruktur namens Primitive Unified Bloc (PUB) effizient zu definieren. Diese PUBs sind die grundlegende Arbeitseinheit, die ein QPU zur Ausführung dieser Workloads benötigt. Sie werden als Eingaben für die run()-Methode der Sampler- und Estimator-Primitives verwendet, die den definierten Workload als Job ausführen. Sobald der Job abgeschlossen ist, werden die Ergebnisse in einem Format zurückgegeben, das sowohl von den verwendeten PUBs als auch von den für die Sampler- oder Estimator-Primitives angegebenen Laufzeitoptionen abhängt.
Überblick über PUBs
Beim Aufrufen der run()-Methode eines Primitive ist das Hauptargument eine list aus einem oder mehreren Tupeln – eines für jeden Circuit, der vom Primitive ausgeführt wird. Jedes dieser Tupel gilt als PUB, und die erforderlichen Elemente jedes Tupels in der Liste hängen vom verwendeten Primitive ab. Die in diesen Tupeln bereitgestellten Daten können auch in verschiedenen Formen angeordnet werden, um durch Broadcasting Flexibilität in einem Workload zu bieten – die entsprechenden Regeln sind in einem nachfolgenden Abschnitt beschrieben.
Estimator-PUB
Beim Estimator-Primitive sollte das PUB-Format höchstens vier Werte enthalten:
- Einen einzelnen
QuantumCircuit, der ein oder mehrereParameter-Objekte enthalten kann - Eine Liste aus einem oder mehreren Observablen, die die zu schätzenden Erwartungswerte angeben und in einem Array angeordnet sind (zum Beispiel ein einzelnes Observable als 0-d-Array, eine Liste von Observablen als 1-d-Array usw.). Die Daten können in jedem der
ObservablesArrayLike-Formate vorliegen, etwaPauli,SparsePauliOp,PauliListoderstr.hinweisWenn du zwei kommutierende Observablen in verschiedenen PUBs, aber mit demselben Circuit hast, werden sie nicht mit derselben Messung geschätzt. Jeder PUB steht für eine andere Messbasis, daher sind für jeden PUB separate Messungen erforderlich. Um sicherzustellen, dass kommutierende Observablen mit derselben Messung geschätzt werden, müssen sie innerhalb desselben PUBs gruppiert werden.
- Eine Sammlung von Parameterwerten, gegen die der Circuit gebunden wird. Dies kann als einzelnes array-ähnliches Objekt angegeben werden, bei dem der letzte Index über die
Parameter-Objekte des Circuits läuft, oder es kann weggelassen werden (bzw. äquivalent aufNonegesetzt werden), wenn der Circuit keineParameter-Objekte hat. - (Optional) eine Zielpräzision für die zu schätzenden Erwartungswerte
Sampler-PUB
Beim Sampler-Primitive enthält das PUB-Tupel höchstens drei Werte:
- Einen einzelnen
QuantumCircuit, der ein oder mehrereParameter-Objekte enthalten kann Hinweis: Diese Circuits sollten außerdem Messanweisungen für jeden der zu messenden Qubits enthalten. - Eine Sammlung von Parameterwerten, gegen die der Circuit gebunden wird (nur erforderlich, wenn
Parameter-Objekte verwendet werden, die zur Laufzeit gebunden werden müssen) - (Optional) eine Anzahl von Shots, mit denen der Circuit gemessen werden soll
Der folgende Code zeigt ein Beispiel für vektorisierte Eingaben an das Estimator-Primitive und führt sie als einzelnes RuntimeJobV2-Objekt auf einem IBM®-Backend aus.
# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
from qiskit.circuit import (
Parameter,
QuantumCircuit,
ClassicalRegister,
QuantumRegister,
)
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.containers import BitArray
from qiskit_ibm_runtime import (
QiskitRuntimeService,
EstimatorV2 as Estimator,
SamplerV2 as Sampler,
)
import numpy as np
# Instantiate runtime service and get
# the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)
# Transpile the circuit
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
layout = transpiled_circuit.layout
# Now define a sweep over parameter values, the last axis of dimension 2 is
# for the two parameters "a" and "b"
params = np.vstack(
[
np.linspace(-np.pi, np.pi, 100),
np.linspace(-4 * np.pi, 4 * np.pi, 100),
]
).T
# Define three observables. The inner length-1 lists cause this array of
# observables to have shape (3, 1), rather than shape (3,) if they were
# omitted.
observables = [
[SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
[SparsePauliOp("XX")],
[SparsePauliOp("IY")],
]
# Apply the same layout as the transpiled circuit.
observables = [
[observable.apply_layout(layout) for observable in observable_set]
for observable_set in observables
]
# Estimate the expectation value for all 300 combinations of observables
# and parameter values, where the pub result will have shape (3, 100).
#
# This shape is due to our array of parameter bindings having shape
# (100, 2), combined with our array of observables having shape (3, 1).
estimator_pub = (transpiled_circuit, observables, params)
# Instantiate the new estimator object, then run the transpiled circuit
# using the set of parameters and observables.
estimator = Estimator(mode=backend)
job = estimator.run([estimator_pub])
result = job.result()
Broadcasting-Regeln
Die PUBs fassen Elemente aus mehreren Arrays (Observablen und Parameterwerte) zusammen, indem sie dieselben Broadcasting-Regeln wie NumPy befolgen. Dieser Abschnitt fasst diese Regeln kurz zusammen. Eine ausführliche Erklärung findest du in der NumPy-Dokumentation zu Broadcasting-Regeln.
Regeln:
- Eingabe-Arrays müssen nicht dieselbe Anzahl an Dimensionen haben.
- Das resultierende Array hat die gleiche Anzahl an Dimensionen wie das Eingabe-Array mit der größten Dimension.
- Die Größe jeder Dimension ist die größte Größe der entsprechenden Dimension.
- Fehlende Dimensionen werden als Größe eins angenommen.
- Dimensionsvergleiche beginnen mit der äußersten rechten Dimension und setzen sich nach links fort.
- Zwei Dimensionen sind kompatibel, wenn ihre Größen gleich sind oder wenn eine davon 1 ist.
Beispiele für Array-Paare, die Broadcasting unterstützen:
A1 (1d array): 1
A2 (2d array): 3 x 5
Result (2d array): 3 x 5
A1 (3d array): 11 x 2 x 7
A2 (3d array): 11 x 1 x 7
Result (3d array): 11 x 2 x 7
Beispiele für Array-Paare, die kein Broadcasting unterstützen:
A1 (1d array): 5
A2 (1d array): 3
A1 (2d array): 2 x 1
A2 (3d array): 6 x 5 x 4 # This would work if the middle dimension were 2, but it is 5.
EstimatorV2 gibt einen Erwartungswertes-Schätzwert für jedes Element der ausgebroadcasteten Form zurück.
Hier sind einige Beispiele für häufige Muster, ausgedrückt in Begriffen des Array-Broadcastings. Die zugehörigen visuellen Darstellungen sind in der folgenden Abbildung zu sehen:
Parameterwertmengen werden durch n×m-Arrays dargestellt, und Observable-Arrays werden durch eine oder mehrere einspaltige Arrays dargestellt. In jedem der vorherigen Code-Beispiele werden die Parameterwertmengen mit ihrem Observable-Array kombiniert, um die resultierenden Erwartungswertes-Schätzwerte zu erstellen.
-
Beispiel 1: (einzelnes Observable broadcasted) hat eine Parameterwertmenge als 5×1-Array und ein 1×1-Observable-Array. Das eine Element im Observable-Array wird mit jedem Element in der Parameterwertmenge kombiniert, um ein einzelnes 5×1-Array zu erstellen, bei dem jedes Element eine Kombination des ursprünglichen Elements aus der Parameterwertmenge mit dem Element aus dem Observable-Array ist.
-
Beispiel 2: (Zip) hat eine 5×1-Parameterwertmenge und ein 5×1-Observable-Array. Die Ausgabe ist ein 5×1-Array, bei dem jedes Element eine Kombination des n-ten Elements in der Parameterwertmenge mit dem n-ten Element im Observable-Array ist.
-
Beispiel 3: (Äußeres Produkt) hat eine 1×6-Parameterwertmenge und ein 4×1-Observable-Array. Ihre Kombination ergibt ein 4×6-Array, das durch die Kombination jedes Elements der Parameterwertmenge mit jedem Element im Observable-Array entsteht – daher wird jeder Parameterwert zu einer ganzen Spalte in der Ausgabe.
-
Beispiel 4: (Standard-nd-Verallgemeinerung) hat ein 3×6-Parameterwertmengen-Array und zwei 3×1-Observable-Arrays. Diese kombinieren sich zu zwei 3×6-Ausgabe-Arrays, ähnlich wie im vorherigen Beispiel.
# Broadcast single observable
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = SparsePauliOp("ZZZ") # shape ()
# >> pub result has shape (5,)
# Zip
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = [
SparsePauliOp(pauli) for pauli in ["III", "XXX", "YYY", "ZZZ", "XYZ"]
] # shape (5,)
# >> pub result has shape (5,)
# Outer/Product
parameter_values = np.random.uniform(size=(1, 6)) # shape (1, 6)
observables = [
[SparsePauliOp(pauli)] for pauli in ["III", "XXX", "YYY", "ZZZ"]
] # shape (4, 1)
# >> pub result has shape (4, 6)
# Standard nd generalization
parameter_values = np.random.uniform(size=(3, 6)) # shape (3, 6)
observables = [
[
[SparsePauliOp(["XII"])],
[SparsePauliOp(["IXI"])],
[SparsePauliOp(["IIX"])],
],
[
[SparsePauliOp(["ZII"])],
[SparsePauliOp(["IZI"])],
[SparsePauliOp(["IIZ"])],
],
] # shape (2, 3, 1)
# >> pub result has shape (2, 3, 6)
Jede SparsePauliOp zählt in diesem Kontext als ein einzelnes Element, unabhängig von der Anzahl der darin enthaltenen Paulis. Daher haben für die Zwecke dieser Broadcasting-Regeln alle folgenden Elemente dieselbe Form:
a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
Die folgenden Operatorlisten sind zwar inhaltlich äquivalent, haben aber unterschiedliche Formen:
list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")] # shape (3, )
Überblick über Primitive-Ausgaben
Sobald ein oder mehrere PUBs zur Ausführung an ein QPU gesendet wurden und ein Job erfolgreich abgeschlossen ist, werden die Daten als PrimitiveResult-Container-Objekt zurückgegeben, auf das durch Aufrufen der RuntimeJobV2.result()-Methode zugegriffen werden kann. Das PrimitiveResult enthält eine iterierbare Liste von PubResult-Objekten, die die Ausführungsergebnisse für jeden PUB enthalten. Je nach verwendetem Primitive sind diese Daten entweder Erwartungswerte und ihre Fehlerbalken (beim Estimator) oder Stichproben der Circuit-Ausgabe (beim Sampler).
Jedes Element dieser Liste entspricht einem PUB, der an die run()-Methode des Primitive übergeben wurde (ein Job, der zum Beispiel mit 20 PUBs gesendet wird, gibt ein PrimitiveResult-Objekt zurück, das eine Liste mit 20 PubResults enthält, eines für jeden PUB).
Jedes dieser PubResult-Objekte besitzt sowohl ein data- als auch ein metadata-Attribut. Das data-Attribut ist ein angepasstes DataBin, das die eigentlichen Messwerte, Standardabweichungen usw. enthält. Dieses DataBin hat verschiedene Attribute, abhängig von der Form oder Struktur des zugehörigen PUBs sowie den vom verwendeten Primitive angegebenen Fehlerminderungsoptionen (zum Beispiel ZNE oder PEC). Das metadata-Attribut hingegen enthält Informationen über die verwendeten Laufzeit- und Fehlerminderungsoptionen (erläutert im Abschnitt Ergebnis-Metadaten auf dieser Seite).
Im Folgenden ist eine visuelle Übersicht der PrimitiveResult-Datenstruktur dargestellt:
- Estimator-Ausgabe
- Sampler-Ausgabe
└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── evs
│ │ └── List of estimated expectation values in the shape
| | specified by the first pub
│ └── stds
│ └── List of calculated standard deviations in the
| same shape as above
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| ├── evs
| │ └── List of estimated expectation values in the shape
| | specified by the second pub
| └── stds
| └── List of calculated standard deviations in the
| same shape as above
├── ...
├── ...
└── ...
└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── NAME_OF_CLASSICAL_REGISTER
│ │ └── BitArray of count data (default is 'meas')
| |
│ └── NAME_OF_ANOTHER_CLASSICAL_REGISTER
│ └── BitArray of count data (exists only if more than one
| ClassicalRegister was specified in the circuit)
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| └── NAME_OF_CLASSICAL_REGISTER
| └── BitArray of count data for second pub
├── ...
├── ...
└── ...
Kurz gesagt: Ein einzelner Job gibt ein PrimitiveResult-Objekt zurück und enthält eine Liste aus einem oder mehreren PubResult-Objekten. Diese PubResult-Objekte speichern dann die Messdaten für jeden PUB, der dem Job übergeben wurde.
Jedes PubResult besitzt je nach dem für den Job verwendeten Primitive-Typ unterschiedliche Formate und Attribute. Die Details werden im Folgenden erläutert.
Estimator-Ausgabe
Jedes PubResult des Estimator-Primitive enthält mindestens ein Array von Erwartungswerten (PubResult.data.evs) und zugehörigen Standardabweichungen (entweder PubResult.data.stds oder PubResult.data.ensemble_standard_error, abhängig vom verwendeten resilience_level), kann aber je nach angegebenen Fehlerminderungsoptionen weitere Daten enthalten.
Der folgende Code-Ausschnitt beschreibt das PrimitiveResult- (und zugehörige PubResult-)Format für den oben erstellten Job.
print(
f"The result of the submitted job had {len(result)} PUB and has a value:\n {result}\n"
)
print(
f"The associated PubResult of this job has the following data bins:\n {result[0].data}\n"
)
print(f"And this DataBin has attributes: {result[0].data.keys()}")
print(
"Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the\n\
number of parameters in the circuit -- combined with our array of observables having shape (3, 1). \n"
)
print(
f"The expectation values measured from this PUB are: \n{result[0].data.evs}"
)
The result of the submitted job had 1 PUB and has a value:
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})
The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100))
And this DataBin has attributes: dict_keys(['evs', 'stds', 'ensemble_standard_error'])
Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the
number of parameters in the circuit -- combined with our array of observables having shape (3, 1).
The expectation values measured from this PUB are:
[[ 0.00948597 0.12163221 0.29100944 0.40535344 0.46625814 0.54716103
0.57690846 0.59809047 0.5784682 0.50924868 0.4579837 0.40035644
0.37174056 0.32887613 0.25850853 0.26396412 0.25852429 0.26074166
0.29282485 0.34388535 0.37368314 0.43562138 0.46912323 0.51955146
0.54430185 0.55467261 0.5162183 0.52744696 0.47261781 0.42613541
0.35400013 0.33217125 0.29600426 0.27561903 0.25307754 0.25672088
0.28783701 0.36612701 0.40433263 0.44428286 0.51028376 0.55034507
0.55979913 0.57160124 0.54127534 0.49753533 0.42942659 0.32552331
0.20215918 0.04303087 -0.08115732 -0.18473659 -0.34015892 -0.44489319
-0.49112115 -0.54588034 -0.60601287 -0.55869218 -0.53353861 -0.51628053
-0.44978534 -0.38090252 -0.32481576 -0.28832245 -0.27057547 -0.26542929
-0.27054473 -0.29367389 -0.31531828 -0.38462352 -0.40276794 -0.47168997
-0.48548191 -0.5382924 -0.52716406 -0.53277032 -0.50776933 -0.48512907
-0.44335198 -0.38756463 -0.34438156 -0.29199194 -0.2729216 -0.24602918
-0.23527174 -0.3019153 -0.35159518 -0.38303379 -0.42434541 -0.47743033
-0.54652609 -0.5877912 -0.59175701 -0.57386895 -0.56416812 -0.48022381
-0.3853372 -0.2639702 -0.12030502 0.02081148]
[ 0.00581765 0.0552677 0.15998546 0.20725389 0.25452232 0.34178711
0.39196437 0.47050268 0.50031815 0.527952 0.57231161 0.64066903
0.72429779 0.77011181 0.78174711 0.86610308 0.88646487 0.91337151
0.94245978 0.98100173 0.97372966 1.00936279 1.01881647 1.0544496
1.01954368 1.03699664 0.99845469 1.03845105 1.00936279 1.00354513
0.95409508 0.95264067 0.91264431 0.91846196 0.8355604 0.80283611
0.77956549 0.74102354 0.69520953 0.64575948 0.58976457 0.53231524
0.43996 0.3956004 0.32069812 0.27706572 0.22470684 0.16653032
0.07272066 -0.00218162 -0.05817653 -0.06253977 -0.15853104 -0.25015908
-0.28506499 -0.34251432 -0.44359604 -0.44432324 -0.53158804 -0.60285429
-0.637033 -0.67630215 -0.71266249 -0.76793019 -0.81519862 -0.86464867
-0.90173621 -0.93155168 -0.9337333 -0.98245614 -0.99627307 -1.01518044
-1.01590764 -1.04863194 -1.00499955 -1.02827016 -1.01663485 -1.0108172
-1.02317971 -0.97518407 -0.96500318 -0.94682302 -0.901009 -0.87846559
-0.79556404 -0.84937733 -0.78101991 -0.73811472 -0.65521316 -0.57667485
-0.59921825 -0.49813653 -0.44577766 -0.36505772 -0.33524225 -0.25888556
-0.21161713 -0.12289792 -0.03781474 0.00654486]
[ 0.01315429 0.18799671 0.42203343 0.603453 0.67799397 0.75253494
0.76185256 0.72567827 0.65661825 0.49054535 0.3436558 0.16004385
0.01918334 -0.11235955 -0.26473006 -0.33817484 -0.36941628 -0.39188819
-0.35681008 -0.29323102 -0.22636339 -0.13812003 -0.08057002 -0.01534667
0.06906002 0.07234859 0.03398191 0.01644286 -0.06412716 -0.15127432
-0.24609482 -0.28829816 -0.32063579 -0.3672239 -0.32940532 -0.28939435
-0.20389148 -0.00876953 0.11345574 0.24280625 0.43080296 0.5683749
0.67963826 0.74760208 0.76185256 0.71800493 0.63414634 0.48451631
0.3315977 0.08824335 -0.10413812 -0.30693341 -0.52178679 -0.6396273
-0.69717731 -0.74924637 -0.76842971 -0.67306111 -0.53548918 -0.42970677
-0.26253768 -0.08550288 0.06303097 0.19128528 0.27404768 0.33379008
0.36064675 0.34420389 0.30309674 0.2132091 0.19073719 0.07180049
0.04494382 -0.02795286 -0.04932858 -0.03727049 0.00109619 0.04055906
0.13647575 0.20005481 0.27624007 0.36283913 0.3551658 0.38640723
0.32502055 0.24554673 0.07782954 -0.02795286 -0.19347767 -0.3781858
-0.49383393 -0.67744588 -0.73773637 -0.78268019 -0.793094 -0.70156207
-0.55905728 -0.40504248 -0.20279529 0.0350781 ]]
Wie der Estimator den Fehler berechnet
Zusätzlich zur Schätzung des Mittelwerts der in den Eingabe-PUBs übergebenen Observablen (das Feld evs des DataBin) versucht der Estimator auch, eine Schätzung des mit diesen Erwartungswerten verbundenen Fehlers zu liefern. Alle Estimator-Abfragen füllen das Feld stds mit einer Größe wie dem Standardfehler des Mittelwerts für jeden Erwartungswert, aber einige Fehlerminderungsoptionen liefern zusätzliche Informationen, wie zum Beispiel ensemble_standard_error.
Betrachte ein einzelnes Observable . In Abwesenheit von ZNE kann man sich jeden Shot der Estimator-Ausführung als Punktschätzung des Erwartungswerts vorstellen. Wenn die punktweisen Schätzwerte in einem Vektor Os vorliegen, dann entspricht der in ensemble_standard_error zurückgegebene Wert dem Folgenden (wobei die Standardabweichung der Erwartungswertsschätzung und die Anzahl der Shots ist):
was alle Shots als Teil eines einzigen Ensembles behandelt. Wenn du Gate-Twirling angefordert hast (twirling.enable_gates = True), kannst du die punktweisen Schätzwerte von in Mengen sortieren, die ein gemeinsames Twirl teilen. Nenne diese Schätzwert-Mengen O_twirls; es gibt num_randomizations (Anzahl der Twirls) davon. Dann ist stds der Standardfehler des Mittelwerts von O_twirls, wie in
wobei die Standardabweichung von O_twirls und die Anzahl der Twirls ist. Wenn du Twirling nicht aktivierst, sind stds und ensemble_standard_error gleich.
Wenn du ZNE aktivierst, werden die oben beschriebenen stds zu Gewichten in einer nichtlinearen Regression an ein Extrapolationsmodell. Was letztendlich in stds zurückgegeben wird, ist die Unsicherheit des Fit-Modells, ausgewertet bei einem Rauschfaktor von null. Bei einem schlechten Fit oder einer großen Unsicherheit im Fit können die gemeldeten stds sehr groß werden. Wenn ZNE aktiviert ist, werden auch pub_result.data.evs_noise_factors und pub_result.data.stds_noise_factors befüllt, sodass du deine eigene Extrapolation durchführen kannst.
Sampler-Ausgabe
Wenn ein Sampler-Job erfolgreich abgeschlossen wurde, enthält das zurückgegebene PrimitiveResult-Objekt eine Liste von SamplerPubResults, eines pro PUB. Die DataBins dieser SamplerPubResult-Objekte sind wörterbuch-ähnliche Objekte, die ein BitArray pro ClassicalRegister im Circuit enthalten.
Die BitArray-Klasse ist ein Container für geordnete Shot-Daten. Genauer gesagt speichert sie die gesampelten Bitstrings als Bytes in einem zweidimensionalen Array. Die äußerste linke Achse dieses Arrays läuft über die geordneten Shots, während die äußerste rechte Achse über die Bytes läuft.
Als erstes Beispiel betrachten wir den folgenden Zehn-Qubit-Circuit:
# generate a ten-qubit GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))
# append measurements with the `measure_all` method
circuit.measure_all()
# transpile the circuit
transpiled_circuit = pm.run(circuit)
# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()
# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")
# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")
Databin: DataBin(meas=BitArray(<shape=(), num_shots=4096, num_bits=10>))
BitArray: BitArray(<shape=(), num_shots=4096, num_bits=10>)
The shape of register `meas` is (4096, 2).
The bytes in register `alpha`, shot by shot:
[[ 3 254]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 0 0]]
Es kann manchmal praktisch sein, vom Byte-Format im BitArray zu Bitstrings zu wechseln. Die Methode get_count gibt ein Wörterbuch zurück, das Bitstrings auf die Anzahl ihrer Auftritte abbildet.
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'1111111110': 199, '0000000000': 1337, '1111111111': 1052, '1111111000': 33, '1110000000': 65, '1100100000': 2, '1100000000': 25, '0010001110': 1, '0000000011': 30, '1111111011': 58, '1111111010': 25, '0000000110': 7, '0010000001': 11, '0000000001': 179, '1110111110': 6, '1111110000': 33, '1111101111': 49, '1110111111': 40, '0000111010': 2, '0100000000': 35, '0000000010': 51, '0000100000': 31, '0110000000': 7, '0000001111': 22, '1111111100': 24, '1011111110': 5, '0001111111': 58, '0000111111': 24, '1111101110': 10, '0000010001': 5, '0000001001': 2, '0011111111': 38, '0000001000': 11, '1111100000': 34, '0111111111': 45, '0000000100': 18, '0000000101': 2, '1011111111': 11, '1110000001': 13, '1101111000': 1, '0010000000': 52, '0000010000': 17, '0000011111': 15, '1110100001': 1, '0111111110': 9, '0000000111': 19, '1101111111': 15, '1111110111': 17, '0011111110': 5, '0001101110': 1, '0111111011': 6, '0100001000': 2, '0010001111': 1, '1111011000': 1, '0000111110': 4, '0011110010': 1, '1110111100': 2, '1111000000': 8, '1111111101': 27, '0000011110': 6, '0001000000': 5, '1111010000': 3, '0000011011': 4, '0001111110': 9, '1111011110': 6, '1110001111': 2, '0100000001': 7, '1110111011': 3, '1111101101': 2, '1101111110': 5, '1110000010': 7, '0111111000': 1, '1110111000': 1, '0000100001': 2, '1110100000': 6, '1000000001': 2, '0001011111': 1, '0000010111': 1, '1011111100': 1, '0111110000': 5, '0110111111': 2, '0010000010': 1, '0001111100': 4, '0011111001': 2, '1111110011': 1, '1110000011': 5, '0000001011': 8, '0100000010': 3, '1111011111': 13, '0010111000': 2, '0100111110': 1, '1111101000': 2, '1110110000': 2, '1100000001': 1, '0001110000': 3, '1011101111': 2, '1111000001': 2, '1111110001': 8, '1111110110': 4, '1100000010': 3, '0011000000': 2, '1110011111': 3, '0011101111': 3, '0010010000': 2, '0000100010': 1, '1100001110': 1, '0001111011': 4, '1010000000': 3, '0000001110': 5, '0000001010': 2, '0011111011': 4, '0100100000': 2, '1111110100': 1, '1111100011': 3, '0000110110': 1, '0001111101': 2, '1111100001': 2, '1000000000': 5, '0010000011': 3, '0010011111': 3, '0100001111': 1, '0100000111': 1, '1011101110': 1, '0011110111': 1, '1100000111': 1, '1100111111': 3, '0001111010': 1, '1101111011': 1, '0111111100': 2, '0100000110': 2, '0100000011': 2, '0001101111': 3, '0001000001': 1, '1111110010': 1, '0010100000': 1, '0011100000': 4, '1010001111': 1, '0101111111': 2, '1111101001': 1, '1110111101': 1, '0000011101': 1, '1110001000': 2, '0001111001': 1, '0101000000': 1, '1111111001': 5, '0001110111': 2, '0000111001': 1, '0100001011': 1, '0000010011': 1, '1011110111': 1, '0011110001': 1, '0000001100': 2, '0111010111': 1, '0001101011': 1, '1110010000': 2, '1110000100': 1, '0010111111': 3, '0111011100': 1, '1010001000': 1, '0000101110': 1, '0011111100': 2, '0000111100': 2, '1110011110': 1, '0011111000': 2, '0110100000': 1, '1001101111': 1, '1011000000': 1, '1101000000': 1, '1110001011': 1, '1110110111': 1, '0110111110': 1, '0011011111': 1, '0111100000': 1, '0000110111': 1, '0000010010': 2, '1111101100': 2, '1111011101': 1, '1101100000': 1, '0010111110': 1, '1101101110': 1, '1111001111': 1, '1101111100': 1, '1011111010': 1, '0001100000': 1, '1101110111': 1, '1100001011': 1}
Wenn ein Circuit mehr als ein klassisches Register enthält, werden die Ergebnisse in verschiedenen BitArray-Objekten gespeichert. Das folgende Beispiel ändert den vorherigen Code-Ausschnitt, indem das klassische Register in zwei separate Register aufgeteilt wird:
# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
qreg := QuantumRegister(10),
alpha := ClassicalRegister(1, "alpha"),
beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))
# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)
# transpile the circuit
transpiled_circuit = pm.run(circuit)
# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()
# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")
BitArray for register 'alpha': BitArray(<shape=(), num_shots=4096, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=4096, num_bits=9>)
BitArray-Objekte für performante Nachverarbeitung nutzen
Da Arrays im Allgemeinen eine bessere Leistung als Wörterbücher bieten, empfiehlt es sich, jede Nachverarbeitung direkt auf den BitArray-Objekten durchzuführen, anstatt auf Zählwörterbüchern. Die BitArray-Klasse bietet eine Reihe von Methoden für gängige Nachverarbeitungsoperationen:
print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")
print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")
# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == "0b1"
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")
# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")
# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)
# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
print(f"Exp. val. for observable `{o}` is: {e}")
# concatenate the bitstrings in `alpha` and `beta` to "merge" the results of the two
# registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[1]
[1]
...
[0]
[0]
[1]]
The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 135]
[ 0 247]
[ 1 247]
...
[ 0 0]
[ 1 224]
[ 1 255]]
The shape of `beta` after post-selection is (0, 2).
The bytes in `beta` after post-selection:
[]
The shape of `beta` after bit-wise slicing is (4096, 1).
The bytes in `beta` after bit-wise slicing:
[[7]
[7]
[7]
...
[0]
[0]
[7]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 135]
[ 0 247]
[ 1 247]
[ 1 128]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.068359375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.06396484375
The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 1 15]
[ 1 239]
[ 3 239]
...
[ 0 0]
[ 3 192]
[ 3 255]]
Ergebnis-Metadaten
Zusätzlich zu den Ausführungsergebnissen enthalten sowohl die PrimitiveResult- als auch die PubResult-Objekte ein Metadaten-Attribut über den eingereichten Job. Die Metadaten mit Informationen für alle eingereichten PUBs (wie die verschiedenen verfügbaren Laufzeitoptionen) finden sich in PrimitiveResult.metadata, während die für jeden PUB spezifischen Metadaten in PubResult.metadata zu finden sind.
Im Metadatenfeld können Primitive-Implementierungen beliebige ausführungsrelevante Informationen zurückgeben, und es gibt keine Schlüssel-Wert-Paare, die von jedem Basis-Primitive garantiert werden. Daher können die zurückgegebenen Metadaten je nach Primitive-Implementierung unterschiedlich sein.
# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
print(f"'{key}' : {val},")
print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
print(f"'{key}' : {val},")
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-01-15 08:07:33', stop='2026-01-15 08:07:36', size=4096>)])},
'version' : 2,
The metadata of the PubResult result is:
'circuit_metadata' : {},
Bei Sampler-Jobs kannst du die Ergebnis-Metadaten auch auswerten, um zu verstehen, wann bestimmte Daten ausgeführt wurden; dies wird als Ausführungsspanne bezeichnet.