Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Entradas e saídas do Sampler

Versões dos pacotes

O código desta página foi desenvolvido com os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.

qiskit[all]~=2.5.2
qiskit-ibm-runtime~=0.47.0

Esta página fornece uma visão geral das entradas e saídas da primitiva qiskit-ibm-runtime Sampler, que executa cargas de trabalho no IBM Quantum® Compute Service. O Sampler permite que você defina eficientemente cargas de trabalho vetorizadas usando uma estrutura de dados conhecida como Primitive Unified Bloc (PUB). Eles são usados como entradas para o método run() da primitiva Sampler, que executa a carga de trabalho definida como um job. Em seguida, após o job ser concluído, os resultados são retornados em um formato que depende tanto dos PUBs usados quanto das opções de runtime especificadas pela primitiva.

Entradas​

Cada PUB está no formato:

(<single circuit>, <one or more optional parameter value>, <optional shots>),

Pode haver múltiplos itens parameter values, e cada item pode ser um array ou um único parâmetro, dependendo do circuito escolhido. Além disso, a entrada deve conter medições.

Para a primitiva Sampler, um PUB pode conter no máximo três valores:

  • Um único QuantumCircuit, que pode conter um ou mais objetos Parameter Nota: Esses circuitos também devem incluir instruções de medição para cada um dos qubits a serem amostrados.
  • Uma coleção de valores de parâmetros para vincular o circuito contra θk\theta_k (necessário apenas se objetos Parameter forem usados e precisarem ser vinculados em tempo de execução)
  • (Opcionalmente) um número de shots para medir o circuito

O código a seguir demonstra um exemplo de conjunto de entradas vetorizadas para a primitiva Sampler e as executa em um backend IBM® como um único objeto RuntimeJobV2 .

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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()

Saídas​

Após um ou mais PUBs serem enviados a um QPU para execução e um job ser concluído com sucesso, os dados são retornados como um objeto contêiner PrimitiveResult acessado chamando o método RuntimeJobV2.result(). O PrimitiveResult contém uma lista iterável de objetos SamplerPubResult que contêm os resultados de execução para cada PUB. Esses dados são amostras da saída do circuito.

Cada elemento desta lista corresponde a um PUB enviado ao método run() da primitiva (por exemplo, um job enviado com 20 PUBs retornará um objeto PrimitiveResult contendo uma lista de 20 objetos SamplerPubResult, um correspondendo a cada PUB).

Cada objeto SamplerPubResult possui tanto um atributo data quanto um atributo metadata.

  • O atributo data é um DataBin personalizado que contém os valores de medição reais, desvios padrão e assim por diante. Os data bins são objetos semelhantes a dicionários que contêm um BitArray por ClassicalRegister no circuito.
  • A classe BitArray é um contêiner para dados de shots ordenados. Ela armazena as bitstrings amostradas como bytes dentro de um array bidimensional. O eixo mais à esquerda desse array percorre os shots ordenados, enquanto o eixo mais à direita percorre os bytes.
  • O atributo metadata contém informações sobre as opções de runtime usadas (explicado mais adiante na seção Metadados do resultado desta página).

A seguir está um esboço visual da estrutura de dados 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
├── ...
├── ...
└── ...

Em termos simples, um único job retorna um objeto PrimitiveResult e contém uma lista de um ou mais objetos SamplerPubResult. Esses objetos SamplerPubResult armazenam então os dados de medição para cada PUB enviado ao job.

Como primeiro exemplo, vamos examinar o seguinte circuito de dez 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")
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]]

Às vezes pode ser conveniente converter do formato de bytes do BitArray para bitstrings. O método get_count retorna um dicionário que mapeia bitstrings para o número de vezes que ocorreram.

# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
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}

Quando um circuito contém mais de um registro clássico, os resultados são armazenados em diferentes objetos BitArray. O exemplo a seguir modifica o snippet anterior dividindo o registro clássico em dois registros distintos:

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

Usar objetos BitArray para pós-processamento eficiente​

Como os arrays geralmente oferecem melhor desempenho em comparação com dicionários, é aconselhável realizar qualquer pós-processamento diretamente nos objetos BitArray em vez de nos dicionários de contagens. A classe BitArray oferece uma variedade de métodos para realizar algumas operações comuns de pós-processamento:

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]
[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]]

Metadados do resultado​

Além dos resultados de execução, tanto os objetos PrimitiveResult quanto SamplerPubResult contêm um atributo de metadados sobre o job enviado. Os metadados contendo informações para todos os PUBs enviados (como as várias opções de runtime disponíveis) podem ser encontrados em PrimitiveResult.metatada, enquanto os metadados específicos de cada PUB estão em SamplerPubResult.metadata.

O Sampler do lado do cliente envia sua carga de trabalho como um job do Executor. Os metadados do resultado do Executor subjacente, que incluem informações de temporização de execução chamadas spans de execução, estão disponíveis na chave "executor" de PrimitiveResult.metadata.

nota

No campo de metadados, as implementações de primitivas podem retornar qualquer informação sobre execução que seja relevante para elas, e não há pares chave-valor garantidos pela primitiva base. Assim, os metadados retornados podem ser diferentes em diferentes implementações de primitivas.

# 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-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' : {},

Ver os spans de execução​

O Sampler do lado do cliente usa o Executor para executar jobs, que executa seu trabalho na QPU em chunks. A temporização de cada chunk está em result.metadata["executor"].chunk_timing, uma lista de objetos ChunkSpan. Cada ChunkSpan tem timestamps start e stop e uma lista de parts, em que cada parte informa um índice de item (idx_item) e o número de configurações desse item (size) que foram executadas no chunk.

O Sampler cria um item de programa do Executor por PUB, então idx_item é o índice do PUB, na ordem em que os PUBs foram passados para run(). Se o twirling estiver habilitado, cada randomização de cada conjunto de valores de parâmetros conta como uma configuração separada.

Para visualizar as temporizações, passe a lista para draw_chunk_timings, como mostrado no exemplo a seguir.

Para saber o que significam os timestamps dos chunks, como ler o gráfico e por que podem aparecer lacunas entre chunks, consulte Visualizar spans de execução no guia de entradas e saídas do Executor.

# Define a circuit with one parameter.
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)

# Define two PUBs, each with a different set of parameter values
sampler_pubs = [
(transpiled_circuit, np.random.uniform(size=(10, 1))),
(transpiled_circuit, np.random.uniform(size=(20, 1))),
]

job = sampler.run(sampler_pubs, shots=1024)
result = job.result()
from qiskit_ibm_runtime.visualization import draw_chunk_timings

# Note the "executor" key: the timing comes from the underlying Executor job
chunk_timing = result.metadata["executor"].chunk_timing

for idx, span in enumerate(chunk_timing):
duration = (span.stop - span.start).total_seconds()
print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
for part in span.parts:
print(f" PUB {part.idx_item}: {part.size} configurations")

fig = draw_chunk_timings(chunk_timing, names="Sampler job")

# Uncomment the following line to display the figure
# fig.show(renderer="notebook")

# Save to a file
# fig.write_html("chunk_timings.html")
Chunk 0: 2026-11-06 01:36:36.203348+00:00 to 2026-11-06 01:36:45.420844+00:00 (9.217 s)
PUB 0: 10 configurations
PUB 1: 20 configurations

Spans de execução no Sampler legado​

A partir do qiskit-ibm-runtime v0.50.0, o Sampler legado do lado do servidor está obsoleto. O Sampler do lado do cliente não retorna objetos ExecutionSpans; use a temporização de chunks descrita anteriormente. Consulte Migre para primitives do lado do cliente para ver as instruções de migração.

Clique para saber mais.

Os resultados de jobs do Sampler legado do lado do servidor contêm informações de temporização de execução em result.metadata["execution"]["execution_spans"], na forma de um objeto ExecutionSpans. Esse objeto é um contêiner semelhante a uma lista de objetos ExecutionSpan, cada um dos quais indica uma hora de início, uma hora de término e uma especificação de quais shots foram coletados no span.

Um span de execução especifica quais dados foram executados durante sua janela fornecendo um método ExecutionSpan.mask. Esse método, dado qualquer índice de PUB, retorna uma máscara booleana que é True para todos os shots executados durante sua janela. Se, por exemplo, um PUB tem forma (2, 3) e foi executado com quatro shots, a forma da máscara é (2, 3, 4). Consulte a página de API execution_span para obter detalhes completos.

from qiskit.primitives import BitArray

spans = result.metadata["execution"]["execution_spans"]

# Get the mask of the 1st PUB for the 0th span.
mask = spans[0].mask(0)

# Create a new bit array containing only the PUB-0 data collected during this span.
bits = result[0].data.meas
filtered_data = BitArray(bits.array[mask], bits.num_bits)

# Take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])

# View global information about the collection of execution spans
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)

Para visualizar spans de execução legados, use a função draw_execution_spans.

É possível que as janelas de tempo especificadas por spans de execução distintos se sobreponham. Isso não ocorre porque um QPU estava realizando múltiplas execuções simultaneamente, mas é um artefato de certo processamento clássico que pode ocorrer concomitantemente com a execução quântica. A garantia é que os dados referenciados definitivamente ocorreram no span de execução reportado, mas não necessariamente que os limites da janela de tempo sejam tão precisos quanto possível.