Pular para o conteúdo principal

Entradas e saídas dos primitivos

Novo modelo de execução, agora em versão beta

A versão beta de um novo modelo de execução já está disponível. O modelo de execução dirigida oferece mais flexibilidade ao personalizar seu fluxo de trabalho de mitigação de erros. Consulte o guia Modelo de execução dirigida para mais informações.

Versões dos pacotes

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

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

Esta página apresenta uma visão geral das entradas e saídas dos primitivos do Qiskit Runtime que executam cargas de trabalho nos recursos de computação IBM Quantum®. Esses primitivos permitem que você defina eficientemente cargas de trabalho vetorizadas usando uma estrutura de dados conhecida como Primitive Unified Bloc (PUB). Esses PUBs são a unidade fundamental de trabalho que uma QPU precisa para executar essas cargas de trabalho. Eles são usados como entradas para o método run() dos primitivos Sampler e Estimator, que executam a carga de trabalho definida como um job. Em seguida, após a conclusão do job, os resultados são retornados em um formato que depende tanto dos PUBs utilizados quanto das opções de runtime especificadas pelos primitivos Sampler ou Estimator.

Visão geral dos PUBs

Ao invocar o método run() de um primitivo, o principal argumento necessário é uma list de uma ou mais tuplas — uma para cada Circuit sendo executado pelo primitivo. Cada uma dessas tuplas é considerada um PUB, e os elementos obrigatórios de cada tupla na lista dependem do primitivo utilizado. Os dados fornecidos a essas tuplas também podem ser organizados em diversas formas para oferecer flexibilidade em uma carga de trabalho por meio de broadcasting — cujas regras são descritas em uma seção seguinte.

PUB do Estimator

Para o primitivo Estimator, o formato do PUB deve conter no máximo quatro valores:

  • Um único QuantumCircuit, que pode conter um ou mais objetos Parameter
  • Uma lista de um ou mais observáveis, que especificam os valores esperados a estimar, organizados em um array (por exemplo, um único observável representado como um array 0-d, uma lista de observáveis como um array 1-d, e assim por diante). Os dados podem estar em qualquer um dos formatos ObservablesArrayLike, como Pauli, SparsePauliOp, PauliList ou str.
    nota

    Se você tiver dois observáveis comutativos em PUBs diferentes, mas com o mesmo Circuit, eles não serão estimados usando a mesma medição. Cada PUB representa uma base diferente para medição e, portanto, medições separadas são necessárias para cada PUB. Para garantir que observáveis comutativos sejam estimados usando a mesma medição, eles devem ser agrupados dentro do mesmo PUB.

  • Uma coleção de valores de parâmetros para vincular ao Circuit. Isso pode ser especificado como um único objeto semelhante a array, onde o último índice é sobre os objetos Parameter do Circuit, ou omitido (ou equivalentemente, definido como None) se o Circuit não tiver objetos Parameter.
  • (Opcionalmente) uma precisão alvo para os valores esperados a estimar

PUB do Sampler

Para o primitivo Sampler, o formato da tupla PUB contém no máximo três valores:

  • Um único QuantumCircuit, que pode conter um ou mais objetos Parameter Nota: Esses Circuits 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 ao Circuit θk\theta_k (necessário somente se algum objeto Parameter for usado e precisar ser vinculado em tempo de execução)
  • (Opcionalmente) um número de shots para medir o Circuit

O código a seguir demonstra um conjunto de exemplo de entradas vetorizadas para o primitivo Estimator e as executa em um backend IBM® como um único objeto 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,
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()

Regras de broadcasting

Os PUBs agregam elementos de múltiplos arrays (observáveis e valores de parâmetros) seguindo as mesmas regras de broadcasting do NumPy. Esta seção resume brevemente essas regras. Para uma explicação detalhada, consulte a documentação de regras de broadcasting do NumPy.

Regras:

  • Arrays de entrada não precisam ter o mesmo número de dimensões.
    • O array resultante terá o mesmo número de dimensões que o array de entrada com a maior dimensão.
    • O tamanho de cada dimensão é o maior tamanho da dimensão correspondente.
    • Dimensões ausentes são assumidas como tendo tamanho um.
  • As comparações de forma começam pela dimensão mais à direita e continuam para a esquerda.
  • Duas dimensões são compatíveis se seus tamanhos forem iguais ou se um deles for 1.

Exemplos de pares de arrays que fazem broadcasting:

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

Exemplos de pares de arrays que não fazem broadcasting:

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 retorna uma estimativa de valor esperado para cada elemento da forma com broadcasting aplicado.

Aqui estão alguns exemplos de padrões comuns expressos em termos de broadcasting de arrays. Sua representação visual correspondente é mostrada na figura a seguir:

Os conjuntos de valores de parâmetros são representados por arrays n x m, e os arrays de observáveis são representados por uma ou mais arrays de coluna única. Para cada exemplo no código anterior, os conjuntos de valores de parâmetros são combinados com seu array de observáveis para criar as estimativas de valores esperados resultantes.

  • Exemplo 1: (broadcasting de observável único) tem um conjunto de valores de parâmetros que é um array 5x1 e um array de observáveis 1x1. O único item no array de observáveis é combinado com cada item no conjunto de valores de parâmetros para criar um único array 5x1, onde cada item é uma combinação do item original no conjunto de valores de parâmetros com o item no array de observáveis.

  • Exemplo 2: (zip) tem um conjunto de valores de parâmetros 5x1 e um array de observáveis 5x1. A saída é um array 5x1, onde cada item é uma combinação do n-ésimo item no conjunto de valores de parâmetros com o n-ésimo item no array de observáveis.

  • Exemplo 3: (produto externo/cartesiano) tem um conjunto de valores de parâmetros 1x6 e um array de observáveis 4x1. Sua combinação resulta em um array 4x6 criado combinando cada item no conjunto de valores de parâmetros com cada item no array de observáveis, e assim cada valor de parâmetro se torna uma coluna inteira na saída.

  • Exemplo 4: (generalização nd padrão) tem um array de conjunto de valores de parâmetros 3x6 e dois arrays de observáveis 3x1. Esses se combinam para criar dois arrays de saída 3x6 de forma semelhante ao exemplo anterior.

Esta imagem ilustra várias representações visuais de broadcasting de arrays

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

Cada SparsePauliOp conta como um único elemento neste contexto, independentemente do número de Paulis contidos no SparsePauliOp. Portanto, para fins dessas regras de broadcasting, todos os seguintes elementos têm a mesma forma:

a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()

As seguintes listas de operadores, embora equivalentes em termos de informação contida, têm formas diferentes:

list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")] # shape (3, )

Visão geral das saídas dos primitivos

Depois que um ou mais PUBs são enviados a uma QPU para execução e um job é 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 PubResult que contêm os resultados de execução para cada PUB. Dependendo do primitivo usado, esses dados serão valores esperados e suas barras de erro no caso do Estimator, ou amostras da saída do Circuit no caso do Sampler.

Cada elemento desta lista corresponde a cada PUB submetido ao método run() do primitivo (por exemplo, um job submetido com 20 PUBs retornará um objeto PrimitiveResult que contém uma lista de 20 PubResults, um correspondendo a cada PUB).

Cada um desses objetos PubResult possui atributos data e metadata. O atributo data é um DataBin personalizado que contém os valores de medição reais, desvios padrão e assim por diante. Este DataBin tem vários atributos dependendo da forma ou estrutura do PUB associado, bem como das opções de mitigação de erros especificadas pelo primitivo usado para submeter o job (por exemplo, ZNE ou PEC). Enquanto isso, o atributo metadata contém informações sobre as opções de runtime e mitigação de erros usadas (explicado posteriormente na seção Metadados do resultado desta página).

A seguir está um esboço visual da estrutura de dados PrimitiveResult:

└── 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
├── ...
├── ...
└── ...

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

Cada PubResult possui formatos e atributos diferentes com base no tipo de primitivo que foi usado para o job. Os detalhes são explicados abaixo.

Saída do Estimator

Cada PubResult para o primitivo Estimator contém pelo menos um array de valores esperados (PubResult.data.evs) e desvios padrão associados (seja PubResult.data.stds ou PubResult.data.ensemble_standard_error, dependendo do resilience_level usado), mas pode conter mais dados dependendo das opções de mitigação de erros especificadas.

O trecho de código abaixo descreve o formato PrimitiveResult (e o PubResult associado) para o job criado acima.

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

Como o Estimator calcula o erro

Além da estimativa da média dos observáveis passados nos PUBs de entrada (o campo evs do DataBin), o Estimator também tenta fornecer uma estimativa do erro associado a esses valores esperados. Todas as consultas do estimador irão preencher o campo stds com uma quantidade semelhante ao erro padrão da média para cada valor esperado, mas algumas opções de mitigação de erros produzem informações adicionais, como ensemble_standard_error.

Considere um único observável O\mathcal{O}. Na ausência de ZNE, você pode pensar em cada shot da execução do Estimator como fornecendo uma estimativa pontual do valor esperado O\langle \mathcal{O} \rangle. Se as estimativas pontuais estiverem em um vetor Os, então o valor retornado em ensemble_standard_error é equivalente ao seguinte (no qual σO\sigma_{\mathcal{O}} é o desvio padrão da estimativa do valor esperado e NshotsN_{shots} é o número de shots):

σONshots,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{shots}} },

que trata todos os shots como parte de um único ensemble. Se você solicitou twirling de gates (twirling.enable_gates = True), você pode classificar as estimativas pontuais de O\langle \mathcal{O} \rangle em conjuntos que compartilham um twirl comum. Chame esses conjuntos de estimativas de O_twirls, e há num_randomizations (número de twirls) deles. Então stds é o erro padrão da média de O_twirls, como em

σONtwirls,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{twirls}} },

onde σO\sigma_{\mathcal{O}} é o desvio padrão de O_twirls e NtwirlsN_{twirls} é o número de twirls. Quando você não ativa o twirling, stds e ensemble_standard_error são iguais.

Se você ativar o ZNE, então os stds descritos acima se tornam pesos em uma regressão não linear a um modelo extrator. O que finalmente é retornado nos stds nesse caso é a incerteza do modelo ajustado avaliado em um fator de ruído igual a zero. Quando há um ajuste ruim, ou grande incerteza no ajuste, os stds relatados podem se tornar muito grandes. Quando o ZNE está ativado, pub_result.data.evs_noise_factors e pub_result.data.stds_noise_factors também são preenchidos, para que você possa fazer sua própria extrapolação.

Saída do Sampler

Quando um job do Sampler é concluído com sucesso, o objeto PrimitiveResult retornado contém uma lista de SamplerPubResults, um por PUB. Os data bins desses objetos SamplerPubResult são objetos semelhantes a dicionários que contêm um BitArray por ClassicalRegister no Circuit.

A classe BitArray é um contêiner para dados de shots ordenados. Em mais detalhes, ela armazena as bitstrings amostradas como bytes dentro de um array bidimensional. O eixo mais à esquerda deste array percorre os shots ordenados, enquanto o eixo mais à direita percorre os bytes.

Como primeiro exemplo, vamos examinar o seguinte Circuit 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 254]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 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 mapeando bitstrings 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}")
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}

Quando um Circuit contém mais de um registrador clássico, os resultados são armazenados em diferentes objetos BitArray. O exemplo a seguir modifica o trecho anterior dividindo o registrador clássico em dois registradores 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>)

Aproveitando objetos BitArray para pós-processamento eficiente

Como arrays geralmente oferecem melhor desempenho em comparação a dicionários, é aconselhável realizar qualquer pós-processamento diretamente nos objetos BitArray em vez de em 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]
[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]]

Metadados do resultado

Além dos resultados de execução, os objetos PrimitiveResult e PubResult contêm um atributo de metadados sobre o job submetido. Os metadados contendo informações para todos os PUBs submetidos (como as diversas opções de runtime disponíveis) podem ser encontrados em PrimitiveResult.metatada, enquanto os metadados específicos de cada PUB são encontrados em PubResult.metadata.

nota

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

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

Para jobs do Sampler, você também pode revisar os metadados do resultado para entender quando certos dados foram executados; isso é chamado de intervalo de execução.