Entradas e saídas do sampler
O código desta página foi desenvolvido com base nos seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.
qiskit[all]~=2.5.1 qiskit-ibm-runtime~=0.47.0
Esta página apresenta uma visão geral das entradas e saídas da primitiva Sampler qiskit-ibm-runtime , que executa cargas de trabalho no Serviço de Computação do IBM Quantum®. O Sampler permite definir cargas de trabalho vetorizadas de maneira eficiente, utilizando uma estrutura de dados conhecida como Bloco Unificado Primitivo ( PUB ). Eles são utilizados como entradas para o método run() da primitiva Sampler, que executa a carga de trabalho definida como um trabalho. Em seguida, após a conclusão do trabalho, os resultados são retornados em um formato que depende tanto dos PUBs utilizados quanto das opções de execução especificadas na primitiva.
Entradas
Cada arquivo PUB tem o seguinte formato:
(<single circuit>, <one or more optional parameter value>, <optional shots>),
Pode haver vários parameter values itens, e cada um deles pode ser uma matriz ou um único parâmetro, dependendo do circuito escolhido. Além disso, a entrada deve conter medidas.
Para a primitiva Sampler, um PUB pode conter no máximo três valores:
- Um único circuito
QuantumCircuit, que pode conter um ou maisParameterobjetos Observação: Esses circuitos também devem incluir instruções de medição para cada um dos qubits a serem amostrados. - Um conjunto de valores de parâmetros para vincular o circuito a (necessário apenas se forem utilizados
Parameterobjetos que precisem ser vinculados em tempo de execução) - (Opcionalmente) um número de medições para avaliar o circuito
O código a seguir mostra um exemplo de conjunto de entradas vetorizadas para a Sampler primitiva e as executa em um backend do tipo IBM® como um único RuntimeJobV2 objeto.
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()Saídas
Depois que um ou mais PUBs são enviados a uma QPU para execução e um trabalho é concluído com sucesso, os dados são retornados como um objeto PrimitiveResult contêiner, acessado por meio da chamada ao RuntimeJobV2.result() método. O PrimitiveResult contém uma lista iterável de SamplerPubResult objetos que contêm os resultados da execução de cada PUB. Esses dados são amostras da saída do circuito.
Cada elemento desta lista corresponde a um objeto PUB enviado ao método da run() primitiva (por exemplo, um trabalho enviado com 20 PUBs retornará um PrimitiveResult objeto que contém uma lista de 20 SamplerPubResult objetos, um correspondendo a cada objeto PUB).
Cada SamplerPubResult objeto possui um atributo data e um metadata atributo.
- O
dataatributo é um campo personalizadoDataBinque contém os valores reais das medições, os desvios padrão e assim por diante. Os compartimentos de dados são objetos semelhantes a dicionários que contêm umBitArrayporClassicalRegisterno circuito. - A
BitArrayclasse é um contêiner para dados ordenados de tomadas. Ele armazena as sequências de bits amostradas como bytes dentro de uma matriz bidimensional. O eixo mais à esquerda dessa matriz abrange as imagens ordenadas, enquanto o eixo mais à direita abrange os bytes. - O
metadataatributo contém informações sobre as opções de execução utilizadas (explicadas mais adiante na seção "Metadados do resultado" desta página).
A seguir, apresentamos um esboço visual da estrutura PrimitiveResult de dados:
└── 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, uma única função retorna um PrimitiveResult objeto e contém uma lista de um ou mais SamplerPubResult objetos. Esses SamplerPubResult objetos armazenam, então, os dados de medição de cada PUB que foi enviado para a tarefa.
Como primeiro exemplo, vamos analisar 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")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 255]
[ 0 0]
[ 0 1]
...
[ 3 0]
[ 0 0]
[ 3 254]]
Às vezes, pode ser conveniente converter o formato de bytes em cadeias BitArray de bits. O get_count método retorna um dicionário que mapeia sequências de bits para o número de vezes que elas ocorreram.
# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")Output:
Counts: {'1111111111': 1346, '0000000000': 1754, '0000000001': 55, '1000000000': 56, '1111111110': 92, '0111111111': 23, '1011111111': 15, '0001111111': 23, '1111011011': 1, '1111111101': 45, '1111111011': 108, '1111110111': 32, '0100000000': 10, '0000000111': 21, '0011111111': 21, '1111110000': 26, '1101111111': 47, '1111011111': 23, '1111111010': 6, '1100000000': 45, '1111100000': 32, '1110000000': 21, '1111101111': 13, '0010000000': 14, '0000000011': 19, '0000000101': 2, '0000001110': 2, '0000100000': 4, '0000001111': 20, '1111111100': 22, '0000010000': 5, '1101110111': 4, '1011111101': 1, '0000000010': 15, '0000001000': 12, '1111110110': 7, '1111000000': 3, '0010000001': 1, '0111011111': 3, '1001111111': 3, '1101111011': 3, '0000011111': 16, '0000011110': 3, '0001111011': 1, '1011111011': 3, '1111110011': 4, '1111101011': 2, '0000000100': 6, '1110111111': 12, '1111111000': 17, '0000111111': 5, '0001111101': 2, '1101100000': 2, '1101110001': 1, '1000001111': 2, '1111101110': 1, '1110111101': 1, '1101111101': 2, '1110000100': 1, '0100011111': 1, '1110000010': 1, '0011111110': 2, '0111111110': 1, '1111110010': 1, '0111110111': 1, '0000000110': 1, '0101111111': 1, '1101011111': 1, '1111001111': 1, '1110011111': 1, '0011111000': 2, '1101111110': 3, '1110111110': 1, '0110000000': 2, '1110000111': 1, '0000010111': 3, '0001000000': 3, '0111101111': 1, '0000011100': 1, '1000000001': 1, '1111011010': 1, '0000001010': 1, '1111100111': 2, '1111100011': 2, '0000001101': 1, '0111001111': 1, '1111111001': 1, '1101111000': 1, '0111110000': 1, '1111000111': 1, '1010000000': 1, '0011110000': 1, '1100000001': 1, '1011001101': 1, '0000001100': 1, '1100111111': 1, '1110111011': 1, '1111011101': 1, '1000011111': 1, '1101111001': 1, '0101101111': 1, '0000011011': 1, '0000111011': 1, '0111111100': 1, '1011100000': 1, '0011111011': 1, '0000010010': 1, '1001111011': 1}
Quando um circuito contém mais de um registro clássico, os resultados são armazenados em objetos BitArray diferentes. O exemplo a seguir modifica o trecho 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}")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>)
Use BitArray objetos para um pós-processamento eficiente
Como as matrizes geralmente oferecem melhor desempenho em comparação com os dicionários, é recomendável realizar qualquer pós-processamento diretamente nos BitArray objetos, em vez de nos dicionários de contagens. A BitArray classe 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")Output:
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[0]
[0]
[0]
...
[1]
[0]
[1]]
The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 0]
[ 0 0]
[ 1 255]
...
[ 1 255]
[ 0 0]
[ 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:
[[0]
[0]
[7]
...
[7]
[0]
[7]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 0]
[ 0 0]
[ 1 255]
[ 0 0]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.115234375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.02392578125
The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 0 0]
[ 0 0]
[ 3 254]
...
[ 3 255]
[ 0 0]
[ 3 255]]
Metadados do resultado
Além dos resultados da execução, tanto o objeto PrimitiveResult quanto SamplerPubResult o objeto contêm um atributo de metadados sobre o trabalho que foi enviado. Os metadados que contêm informações sobre todos os PUBs enviados (como as diversas opções de tempo de execução disponíveis) podem ser encontrados no PrimitiveResult.metatada, enquanto os metadados específicos de cada PUB se encontram no SamplerPubResult.metadata.
Os metadados do resultado do Sampler também incluem informações sobre o tempo de execução, conhecidas como “execution span ”.
No campo de metadados, as implementações de primitivas podem retornar qualquer informação sobre a execução que seja relevante para elas, e não há pares chave-valor garantidos pela primitiva base. Portanto, os metadados retornados podem variar de acordo com as diferentes implementações das 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},")Output:
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:10', stop='2026-08-01 08:21:13', size=4096>)])},
'version' : 2,
The metadata of the PubResult result is:
'circuit_metadata' : {},
Exibir intervalos de execução
Os resultados das tarefas SamplerV2 executadas no Serviço de Computação do IBM Quantum contêm informações sobre o tempo de execução em seus metadados.
Essas informações de tempo podem ser usadas para definir limites superior e inferior de data e hora em que determinadas instruções foram executadas na QPU.
As tomadas são agrupadas em ExecutionSpan objetos, cada um dos quais indica uma hora de início, uma hora de término e uma especificação das tomadas coletadas nesse intervalo.
Um intervalo de execução especifica quais dados foram executados durante sua janela, por meio de um ExecutionSpan.mask método. Este método, recebendo como argumento um índice de Bloco Unificado Primitivo ( PUB ), retorna uma máscara booleana que é True verdadeira para todas as tomadas executadas durante sua janela. Os PUBs são indexados pela ordem em que foram passados à chamada de execução do Sampler. Se, por exemplo, uma máscara de imagem ( PUB ) tiver a forma (2, 3) e for executada com quatro fotos, então a forma da máscara é (2, 3, 4). Consulte a página da API execution\_span para obter todos os detalhes.
Para visualizar as informações sobre o intervalo de execução, consulte os metadados do resultado retornado por SamplerV2, que é apresentado na forma de um ExecutionSpans objeto. Este objeto é um contêiner semelhante a uma lista que contém instâncias de subclasses de ExecutionSpan, como SliceSpan.
Exemplo:
# 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-08-01 08:21:37', stop='2026-08-01 08:21:38', 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)Os intervalos de execução podem ser filtrados para incluir informações relativas a PUBs específicas, selecionadas por seus índices:
# take the subset of spans that reference data in PUBs 0 or 2
spans.filter_by_pub([0, 2])Output:
ExecutionSpans([DoubleSliceSpan(<start='2026-08-01 08:21:37', stop='2026-08-01 08:21:38', size=24>)])
Exibir informações gerais sobre o conjunto de intervalos de execução:
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-08-01 08:21:37.606114
End of the last span: 2026-08-01 08:21:38.960352
Total duration (s): 1.354238
Extrair e inspecionar um intervalo específico:
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-08-01 08:21:37.606114
End of first span: 2026-08-01 08:21:38.960352
#shots in first span: 24
É possível que os intervalos de tempo especificados por intervalos de execução distintos se sobreponham. Isso não se deve ao fato de uma QPU estar realizando várias execuções ao mesmo tempo, mas sim a um efeito secundário de certos processos clássicos que podem ocorrer simultaneamente à execução quântica. A garantia oferecida é de que os dados referenciados ocorreram efetivamente no intervalo de execução relatado, mas não necessariamente de que os limites da janela de tempo sejam os mais restritos possíveis.