Vstupy a výstupy Sampleru
Verze balíčků
Kód na této stránce byl vyvinut za použití následujících požadavků. Doporučujeme používat tyto verze nebo novější.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
Tato stránka poskytuje přehled vstupů a výstupů primitivu Sampler Qiskit Runtime, který spouští úlohy na výpočetních zdrojích IBM Quantum®. Sampler ti umožňuje efektivně definovat vektorizované úlohy pomocí datové struktury zvané Primitive Unified Bloc (PUB). Jsou používány jako vstupy do metody run() primitivu Sampler, který definovanou úlohu spustí jako úlohu. Po dokončení úlohy jsou výsledky vráceny ve formátu, který závisí jak na použitých PUBech, tak na možnostech runtime specifikovaných primitivem.
Vstupy
Každý PUB má formát:
(<jediný obvod>, <jedna nebo více volitelných hodnot parametrů>, <volitelné shots>),
Může existovat více položek hodnot parametrů, přičemž každá položka může být buď pole nebo jediný parametr v závislosti na zvoleném obvodu. Vstup musí také obsahovat měření.
Pro primitiv Sampler může PUB obsahovat maximálně tři hodnoty:
- Jediný
QuantumCircuit, který může obsahovat jeden nebo více objektůParameterPoznámka: Tyto obvody by měly také obsahovat instrukce měření pro každý z qubitů, které mají být vzorkovány. - Kolekce hodnot parametrů pro navázání obvodu s (potřebná pouze tehdy, pokud jsou použity objekty
Parameter, které musí být navázány za běhu) - (Volitelně) počet shotů pro měření obvodu
Následující kód demonstruje příkladovou sadu vektorizovaných vstupů do primitivu Sampler a spustí je na backendu IBM® jako jediný objekt RuntimeJobV2.
# 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,
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)
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()
Výstupy
Po odeslání jednoho nebo více PUBů do QPU k provedení a úspěšném dokončení úlohy jsou data vrácena jako objektový kontejner PrimitiveResult přístupný voláním metody RuntimeJobV2.result(). PrimitiveResult obsahuje iterovatelný seznam objektů SamplerPubResult obsahujících výsledky spuštění pro každý PUB. Tato data jsou vzorky výstupu obvodu.
Každý prvek tohoto seznamu odpovídá PUBu odeslanému do metody run() primitivu (například úloha odeslaná s 20 PUBy vrátí objekt PrimitiveResult obsahující seznam 20 objektů SamplerPubResult, jeden odpovídající každému PUBu).
Každý objekt SamplerPubResult má atribut data i metadata.
- Atribut
dataje přizpůsobenýDataBinobsahující skutečné hodnoty měření, směrodatné odchylky a podobně. Datové koše jsou objekty podobné slovníkům, které obsahují jedenBitArraynaClassicalRegisterv obvodu. - Třída
BitArrayje kontejner pro uspořádaná data shotů. Ukládá vzorkované bitové řetězce jako bajty uvnitř dvourozměrného pole. Nejlevější osa tohoto pole prochází uspořádanými shoty, zatímco nejpravější prochází bajty. - Atribut
metadataobsahuje informace o použitých možnostech runtime (vysvětleno dále v části Metadata výsledků této stránky).
Níže je vizuální přehled datové struktury PrimitiveResult:
└── 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
├── ...
├── ...
└── ...
Stručně řečeno, jedna úloha vrátí objekt PrimitiveResult a obsahuje seznam jednoho nebo více objektů SamplerPubResult. Tyto objekty SamplerPubResult pak ukládají data měření pro každý PUB odeslaný do úlohy.
Jako první příklad se podívejme na následující desetiqubitový obvod:
# 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:
[[ 0 0]
[ 3 255]
[ 0 0]
...
[ 3 255]
[ 2 255]
[ 3 255]]
Někdy je vhodné převést formát bajtů v BitArray na bitové řetězce. Metoda get_count vrátí slovník mapující bitové řetězce na počet jejich výskytů.
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'0000000000': 1649, '1111111111': 1344, '1111111000': 26, '1101111111': 40, '1111110000': 20, '0010000000': 32, '1000000000': 67, '1111110110': 4, '0000011110': 4, '0000000001': 78, '0010100000': 1, '1100000000': 37, '1111111110': 126, '1111110111': 35, '1111011111': 32, '0011111000': 1, '1011110111': 1, '0000011111': 48, '1111000000': 14, '0110000000': 1, '1110111110': 2, '1110011111': 4, '1111100000': 19, '1101111000': 1, '1111111011': 8, '0001011111': 3, '1110000000': 31, '0000000111': 25, '1110000001': 3, '0011111111': 24, '0000100000': 7, '1111111101': 30, '1111101111': 16, '0111111111': 37, '0000011101': 4, '0101111111': 4, '1011111110': 2, '0000000010': 17, '1011111111': 20, '0000100111': 1, '0010000111': 1, '1011010000': 1, '1101101111': 2, '1011110000': 1, '1000000001': 4, '0000001000': 23, '0011111110': 8, '1111111001': 1, '1100111111': 2, '0000011000': 2, '0001111110': 2, '0000111111': 20, '0001111111': 33, '1110111111': 11, '1010000000': 3, '0111011111': 2, '0000000100': 2, '0000000110': 2, '0000001111': 22, '0111101111': 1, '0000010111': 1, '0000000011': 15, '0001000010': 1, '1111111100': 19, '1111101000': 1, '0000001110': 2, '1011110100': 1, '0001000000': 11, '1001111111': 2, '0100000000': 6, '1100000011': 2, '1000001110': 1, '1100001111': 1, '0000010000': 3, '1101111110': 5, '0001111101': 1, '0001110111': 1, '0011000000': 2, '0111101110': 1, '1100000001': 1, '1111000001': 1, '0000000101': 1, '1101110111': 2, '0011111011': 1, '0000111110': 1, '1111101110': 3, '1111001000': 1, '1011111100': 1, '1111110101': 2, '1101001111': 1, '1111011110': 3, '1000011111': 1, '0000001001': 2, '1111010000': 1, '1110100010': 1, '1111110001': 2, '1101110000': 2, '0000010100': 1, '0111111110': 2, '0001000001': 1, '1000010000': 1, '1111011100': 1, '0111111100': 1, '1011101111': 1, '0000111101': 1, '1100011111': 2, '1101100000': 1, '1111011011': 1, '0010011111': 1, '0000110111': 3, '1111100010': 1, '1110111101': 1, '0000111001': 1, '1111100001': 1, '0001111100': 1, '1110011110': 1, '1100000010': 1, '0011110000': 1, '0001100111': 1, '1111010111': 1, '0010000001': 1, '0010000011': 1, '1101000111': 1, '1011111101': 1, '0000001100': 1}
Pokud obvod obsahuje více než jeden klasický registr, výsledky jsou uloženy v různých objektech BitArray. Následující příklad upravuje předchozí úryvek rozdělením klasického registru na dva samostatné registry:
# 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>)
Použití objektů BitArray pro výkonný post-processing
Jelikož pole obecně nabízejí lepší výkon ve srovnání se slovníky, doporučuje se provádět jakýkoli post-processing přímo na objektech BitArray, nikoli na slovnících počtů. Třída BitArray nabízí řadu metod pro provádění běžných operací post-processingu:
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:
[[0]
[0]
[0]
...
[0]
[0]
[0]]
The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 0]
[ 1 248]
[ 0 0]
...
[ 0 0]
[ 0 0]
[ 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:
[[0]
[0]
[0]
...
[0]
[0]
[0]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 0]
[ 1 248]
[ 0 0]
[ 0 0]
[ 0 0]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.07470703125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.0244140625
The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 0 0]
[ 3 240]
[ 0 0]
...
[ 0 0]
[ 0 0]
[ 0 0]]
Metadata výsledků
Kromě výsledků spuštění obsahují oba objekty PrimitiveResult a SamplerPubResult atribut metadat o odeslané úloze. Metadata obsahující informace pro všechny odeslané PUBy (jako jsou různé možnosti runtime dostupné) lze nalézt v PrimitiveResult.metatada, zatímco metadata specifická pro každý PUB jsou v SamplerPubResult.metadata.
Metadata výsledků Sampleru také obsahují informace o časování spuštění zvané rozsah spuštění.
V poli metadat mohou implementace primitivů vrátit jakékoli informace o spuštění, které jsou pro ně relevantní, a neexistují žádné páry klíč-hodnota garantované základním primitivem. Vrácená metadata se tedy mohou lišit v různých implementacích primitivů.
# 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-05-13 14:23:00', stop='2026-05-13 14:23:02', size=4096>)])},
'version' : 2,
The metadata of the PubResult result is:
'circuit_metadata' : {},
Zobrazení rozsahů spuštění
Výsledky úloh SamplerV2 spouštěných v Qiskit Runtime obsahují informace o časování spuštění ve svých metadatech.
Tyto informace o časování lze použít k určení horních a dolních časových hranic toho, kdy byly konkrétní shoty provedeny na QPU.
Shoty jsou seskupeny do objektů ExecutionSpan, z nichž každý udává čas začátku, čas konce a specifikaci, které shoty byly v rozsahu shromážděny.
Rozsah spuštění specifikuje, která data byla provedena během jeho okna, poskytnutím metody ExecutionSpan.mask. Tato metoda, pro libovolný index Primitive Unified Block (PUB), vrátí logickou masku, která je True pro všechny shoty provedené během jejího okna. PUBy jsou indexovány podle pořadí, v jakém byly předány volání Sampler run. Pokud například PUB má tvar (2, 3) a byl spuštěn se čtyřmi shoty, tvar masky je (2, 3, 4). Úplné podrobnosti najdeš na stránce API execution_span.
Chceš-li zobrazit informace o rozsahu spuštění, prostuduj metadata výsledku vráceného SamplerV2, které mají podobu objektu ExecutionSpans. Tento objekt je kontejner podobný seznamu obsahující instance podtříd ExecutionSpan, jako je SliceSpan.
Příklad:
# 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)
ExecutionSpans([DoubleSliceSpan(<start='2026-05-13 14:23:20', stop='2026-05-13 14:23:21', 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)
Rozsahy spuštění lze filtrovat tak, aby zahrnovaly informace týkající se konkrétních PUBů, vybraných podle jejich indexů:
# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])
ExecutionSpans([DoubleSliceSpan(<start='2026-05-13 14:23:20', stop='2026-05-13 14:23:21', size=24>)])
Zobraz globální informace o kolekci rozsahů spuštění:
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)
Number of execution spans: 1
Start of the first span: 2026-05-13 14:23:20.441518
End of the last span: 2026-05-13 14:23:21.564845
Total duration (s): 1.123327
Extrahuj a prozkoumej konkrétní rozsah:
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)
Start of first span: 2026-05-13 14:23:20.441518
End of first span: 2026-05-13 14:23:21.564845
#shots in first span: 24
Je možné, že časová okna specifikovaná různými rozsahy spuštění se překrývají. Není to proto, že by QPU prováděl více spuštění najednou, ale je to artefaktem určitého klasického zpracování, které může probíhat souběžně s kvantovým spuštěním. Zárukou je, že odkazovaná data se určitě vyskytla v hlášeném rozsahu spuštění, ale ne nutně, že hranice časového okna jsou co nejtěsnější.