Ein- und Ausgänge des Samplers
Der Code auf dieser Seite wurde unter Berücksichtigung der folgenden Anforderungen entwickelt. Wir empfehlen, diese Versionen oder neuere zu verwenden.
qiskit[all]~=2.5.2 qiskit-ibm-runtime~=0.47.0
Diese Seite bietet einen Überblick über die Ein- und Ausgänge der qiskit-ibm-runtime „Sampler“-Primitive, die Workloads auf dem IBM Quantum® Compute -Dienst ausführt. Mit Sampler können Sie vektorisierte Workloads mithilfe einer Datenstruktur namens „Primitive Unified Bloc“ ( PUB ) effizient definieren. Sie dienen als Eingaben für die Methode run() der Sampler-Primitive, die die definierte Arbeitslast als Job ausführt. Nachdem der Auftrag abgeschlossen ist, werden die Ergebnisse in einem Format zurückgegeben, das sowohl von den verwendeten PUBs als auch von den in der Primitive angegebenen Laufzeitoptionen abhängt.
Eingaben
Jede „ PUB “-Datei hat das folgende Format:
(<single circuit>, <one or more optional parameter value>, <optional shots>),
Es können mehrere parameter values Elemente vorhanden sein, und jedes Element kann je nach gewähltem Schaltkreis entweder ein Array oder ein einzelner Parameter sein. Außerdem muss die Eingabe Messwerte enthalten.
Bei der Sampler-Primitive kann ein „ PUB “ höchstens drei Werte enthalten:
- Ein Einzelobjekt
QuantumCircuit, das ein oder mehrereParameterObjekte enthalten kann Hinweis: Diese Schaltkreise sollten auch Messanweisungen für jedes der zu messenden Qubits enthalten. - Eine Sammlung von Parameterwerten zur Anbindung der Schaltung an (nur erforderlich, wenn Objekte
Parameterverwendet werden, die zur Laufzeit gebunden werden müssen) - (optional) eine bestimmte Anzahl von Messungen zur Überprüfung der Schaltung
Der folgende Code zeigt ein Beispiel für eine Reihe vektorisierter Eingaben für die Sampler Primitive und führt diese auf einem „ IBM® “-Backend als ein einziges RuntimeJobV2 Objekt aus.
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
# Legacy server-side primitive before qiskit-ibm-runtime 0.50.0
# from qiskit_ibm_runtime import Sampler
# Client-side primitive introduced in qiskit-ibm-runtime 0.50.0
from qiskit_ibm_runtime.executor_sampler import 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)
circuit.measure_all()
# 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
sampler_pub = (transpiled_circuit, params)
# Instantiate the new Sampler object, then run the transpiled circuit
# using the set of parameters and observables.
sampler = Sampler(mode=backend)
job = sampler.run([sampler_pub])
result = job.result()Ausgaben
Nachdem ein oder mehrere PUBs zur Ausführung an eine QPU gesendet wurden und ein Job erfolgreich abgeschlossen wurde, werden die Daten als PrimitiveResult Containerobjekt zurückgegeben, auf das durch Aufruf der RuntimeJobV2.result() Methode zugegriffen wird. Das PrimitiveResult enthält eine iterierbare Liste von SamplerPubResult Objekten, die die Ausführungsergebnisse für jeden PUB enthalten. Diese Daten sind Abtastwerte des Schaltungsausgangs.
Jedes Element dieser Liste entspricht einem „ PUB “, das an die Methode run() der Primitive übergeben wurde (ein Job, der beispielsweise mit 20 PUBs übergeben wurde, gibt ein PrimitiveResult Objekt zurück, das eine Liste mit 20 SamplerPubResult Objekten enthält, wobei jedes Objekt einem „ PUB “ entspricht).
Jedes SamplerPubResult Objekt verfügt sowohl über ein data als auch über ein metadata Attribut.
- Das
dataAttribut ist ein benutzerdefiniertesDataBinFeld, das die tatsächlichen Messwerte, Standardabweichungen usw. enthält. Die Datenbehälter sind dict-ähnliche Objekte, die jeweils einen WertBitArrayproClassicalRegisterSchaltkreis enthalten. - Die
BitArrayKlasse dient als Container für geordnete Aufnahmedaten. Es speichert die abgetasteten Bitfolgen als Bytes in einem zweidimensionalen Array. Die Achse ganz links in diesem Array erstreckt sich über geordnete Aufnahmen, während sich die Achse ganz rechts über Bytes erstreckt. - Das
metadataAttribut enthält Informationen zu den verwendeten Laufzeitoptionen (die später im Abschnitt „Ergebnis-Metadaten“ dieser Seite erläutert werden).
Im Folgenden finden Sie eine grafische Darstellung der PrimitiveResult Datenstruktur:
└── PrimitiveResult
├── SamplerPubResult[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)
├── SamplerPubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| └── NAME_OF_CLASSICAL_REGISTER
| └── BitArray of count data for second pub
├── ...
├── ...
└── ...
Einfach ausgedrückt: Eine einzelne Funktion gibt ein PrimitiveResult Objekt zurück und enthält eine Liste mit einem oder mehreren SamplerPubResult Objekten. Diese SamplerPubResult Objekte speichern dann die Messdaten für jeden „ PUB “, der an den Job übermittelt wurde.
Betrachten wir als erstes Beispiel die folgende Schaltung mit zehn Qubits:
# 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")Output:
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 247]
[ 0 0]
[ 3 223]
...
[ 0 255]
[ 0 0]
[ 0 0]]
Manchmal kann es praktisch sein, Daten aus dem BitArray Byte-Format in Bitstrings umzuwandeln. Die get_count Methode gibt ein Wörterbuch zurück, in dem Bitstrings der Häufigkeit ihres Auftretens zugeordnet sind.
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")Output:
Counts: {'1111110111': 28, '0000000000': 1680, '1111011111': 35, '1111111111': 1467, '1110000000': 28, '0000000010': 15, '1111111011': 15, '1110111111': 7, '1000000000': 52, '1111101111': 16, '1111110000': 22, '0000000101': 3, '0001111101': 1, '0000000111': 17, '0000000001': 66, '1111111110': 119, '0000001000': 15, '0011111111': 24, '1111111101': 38, '0000001111': 32, '1100000000': 39, '1101111111': 51, '0000011111': 34, '1110000010': 2, '0010000000': 25, '0111111111': 21, '0001111111': 34, '1011111111': 16, '1000011111': 3, '0000001101': 1, '1111100000': 22, '1111111100': 22, '1111111000': 8, '0010001011': 1, '0011000000': 1, '0000000011': 16, '0011111100': 2, '1011110001': 1, '1101111100': 1, '1101111101': 1, '0000000110': 2, '0000000100': 2, '0001111110': 6, '0000001110': 4, '0011110111': 2, '1101111110': 3, '0111111011': 1, '0110000000': 1, '0000001011': 2, '1111110110': 3, '0111111000': 1, '1010000000': 2, '0001110111': 1, '1111101000': 1, '0010011110': 1, '1111100111': 1, '0111110111': 1, '0100000000': 5, '1101110111': 1, '0000001001': 1, '0010000001': 1, '1111000001': 1, '0000010000': 3, '1111101110': 1, '1111000000': 8, '0001011111': 3, '1000000001': 1, '1111010111': 2, '0010011111': 1, '1011111110': 1, '1101101111': 1, '1111011110': 2, '1111111010': 1, '1111010000': 1, '1101000000': 1, '1100001111': 1, '1100011111': 1, '0000111111': 4, '0010111111': 1, '1000000110': 1, '1110111110': 1, '1101011111': 2, '0000011000': 1, '1101110000': 2, '1011111011': 1, '1000111111': 1, '1011000000': 1, '1110000001': 2, '0101111111': 2, '1111110101': 1, '0010000011': 1, '0000011110': 1, '0000011101': 1, '0111000000': 1, '0001110000': 1, '0001000000': 4, '1101111000': 1, '1011111000': 1, '0111110000': 1, '1101110110': 1, '0011011111': 1, '0000100000': 2, '1111101100': 1, '1100001000': 1, '1010000011': 1, '0011111110': 1, '1010001110': 1, '1000001000': 1, '1000000011': 1, '1111101011': 1}
Wenn eine Schaltung mehr als ein klassisches Register enthält, werden die Ergebnisse in verschiedenen BitArray Objekten gespeichert. Das folgende Beispiel erweitert den vorherigen Codeausschnitt, indem es das klassische Register in zwei separate Register aufteilt:
# 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}")Output:
BitArray for register 'alpha': BitArray(<shape=(), num_shots=4096, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=4096, num_bits=9>)
Verwenden Sie BitArray Objekte für eine leistungsstarke Nachbearbeitung
Da Arrays im Vergleich zu Dictionaries in der Regel eine bessere Leistung bieten, ist es ratsam, jegliche Nachbearbeitung direkt an den BitArray Objekten durchzuführen und nicht an Dictionaries mit Zählwerten. Die BitArray Klasse bietet eine Reihe von Methoden zur Durchführung einiger gängiger Nachbearbeitungsvorgänge:
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")Output:
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[0]
[1]
...
[0]
[1]
[0]]
The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 1 255]
[ 0 0]
[ 1 255]
...
[ 0 0]
[ 1 255]
[ 0 0]]
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]
[0]
[7]
...
[0]
[7]
[0]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 1 255]
[ 0 0]
[ 1 255]
[ 1 255]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.0595703125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.02783203125
The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 3 255]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 0 0]]
Metadaten zum Ergebnis
Zusätzlich zu den Ausführungsergebnissen enthalten sowohl das PrimitiveResult als auch SamplerPubResult das Objekt ein Metadatenattribut zu dem übermittelten Auftrag. Die Metadaten, die Informationen zu allen eingereichten PUBs enthalten (wie beispielsweise die verschiedenen verfügbaren Laufzeitoptionen ), finden Sie in der PrimitiveResult.metatada, während die für jede einzelne „ PUB “ spezifischen Metadaten in der zu finden SamplerPubResult.metadata sind.
Die Metadaten des Sampler-Ergebnisses enthalten auch Informationen zum Ausführungszeitpunkt, die als „ Ausführungsdauer“ bezeichnet werden.
Im Metadatenfeld können Primitivimplementierungen beliebige für sie relevante Informationen zur Ausführung zurückgeben, und es gibt keine Schlüssel-Wert-Paare, die von der Basisprimitive garantiert werden. Daher können sich die zurückgegebenen Metadaten bei verschiedenen Implementierungen der Primitive unterscheiden.
# 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},")Output:
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 07:46:47', stop='2026-09-01 07:46:49', size=4096>)])},
'version' : 2,
The metadata of the PubResult result is:
'circuit_metadata' : {},
Ausführungsspannen anzeigen
Die Ergebnisse der im „ IBM Quantum Compute “-Dienst ausgeführten Jobs SamplerV2 enthalten in ihren Metadaten Angaben zum Ausführungszeitpunkt.
Anhand dieser Zeitangaben lassen sich obere und untere Zeitgrenzen dafür festlegen, wann bestimmte Befehle auf der QPU ausgeführt wurden.
Die Aufnahmen sind in ExecutionSpan Objekte gruppiert, von denen jedes eine Startzeit, eine Endzeit und eine Angabe darüber enthält, welche Aufnahmen in diesem Zeitraum erfasst wurden.
Ein Ausführungsfenster gibt durch die Angabe einer ExecutionSpan.mask Methode an, welche Daten während dieses Zeitraums ausgeführt wurden. Diese Methode gibt bei Angabe eines beliebigen Indexes eines „Primitive Unified Block“ ( PUB ) eine boolesche Maske zurück, die für alle während ihres True Zeitfensters ausgeführten Shots gilt. PUBs werden in der Reihenfolge indiziert, in der sie dem Aufruf des Sampler-Laufs übergeben wurden. Wenn beispielsweise ein „ PUB “ die Form hat (2, 3) und mit vier Schüssen ausgeführt wurde, dann hat die Maske (2, 3, 4)die Form. Ausführliche Informationen finden Sie auf der Seite zur execution_span-API.
Um Informationen zur Ausführungsdauer anzuzeigen, überprüfen Sie die Metadaten des von SamplerV2zurückgegebenen Ergebnisses, das in Form eines ExecutionSpans Objekts vorliegt. Dieses Objekt ist ein listenähnlicher Container, der Instanzen von Unterklassen von enthält ExecutionSpan, wie SliceSpanbeispielsweise.
Beispiel:
# Define two circuits, each with one parameter with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.cx(0, 1)
circuit.h(0)
circuit.measure_all()
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
params = np.random.uniform(size=(2, 3)).T
sampler_pub = (transpiled_circuit, params)
# Instantiate the new Estimator object, then run the transpiled circuit
# using the set of parameters and observables.
job = sampler.run([sampler_pub], shots=4)
result = job.result()
spans = job.result().metadata["execution"]["execution_spans"]
print(spans)Output:
ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 08:11:01', stop='2026-09-01 08:11:02', size=24>)])
from qiskit.primitives import BitArray
# Get the mask of the 1st PUB for the 0th span.
mask = spans[0].mask(0)
# Decide whether the 0th shot of parameter set (1, 2) occurred in this span.
in_this_span = mask[2, 1, 0]
# Create a new bit array containing only the PUB-1 data collected during this span.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)Ausführungsspannen können gefiltert werden, um Informationen zu bestimmten PUBs einzubeziehen, die anhand ihrer Indizes ausgewählt werden:
# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])Output:
ExecutionSpans([DoubleSliceSpan(<start='2026-09-01 08:11:01', stop='2026-09-01 08:11:02', size=24>)])
Globale Informationen zur Erfassung von Ausführungsspannen anzeigen:
print("Number of execution spans:", len(spans))
print(" Start of the first span:", spans.start)
print(" End of the last span:", spans.stop)
print(" Total duration (s):", spans.duration)Output:
Number of execution spans: 1
Start of the first span: 2026-09-01 08:11:01.798871
End of the last span: 2026-09-01 08:11:02.894134
Total duration (s): 1.095263
Einen bestimmten Abschnitt extrahieren und überprüfen:
spans.sort()
print(" Start of first span:", spans[0].start)
print(" End of first span:", spans[0].stop)
print("#shots in first span:", spans[0].size)Output:
Start of first span: 2026-09-01 08:11:01.798871
End of first span: 2026-09-01 08:11:02.894134
#shots in first span: 24
Es ist möglich, dass sich Zeitfenster, die durch unterschiedliche Ausführungszeiträume festgelegt sind, überschneiden. Dies liegt nicht daran, dass eine QPU mehrere Befehle gleichzeitig ausgeführt hat, sondern ist vielmehr ein Nebeneffekt bestimmter klassischer Verarbeitungsprozesse, die möglicherweise parallel zur Quantenausführung ablaufen. Es wird garantiert, dass die betreffenden Daten definitiv innerhalb des angegebenen Ausführungszeitraums aufgetreten sind, jedoch nicht unbedingt, dass die Grenzen des Zeitfensters so eng wie möglich sind.