Saltar al contenido principal

Entradas y salidas de las primitivas

Nuevo modelo de ejecución, ahora en versión beta

La versión beta de un nuevo modelo de ejecución ya está disponible. El modelo de ejecución dirigida ofrece mayor flexibilidad para personalizar tu flujo de trabajo de mitigación de errores. Consulta la guía del Modelo de ejecución dirigida para obtener más información.

Versiones de paquetes

El código de esta página fue desarrollado con los siguientes requisitos. Recomendamos usar estas versiones o versiones más recientes.

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

Esta página ofrece una descripción general de las entradas y salidas de las primitivas de Qiskit Runtime que ejecutan cargas de trabajo en los recursos de cómputo de IBM Quantum®. Estas primitivas te permiten definir cargas de trabajo vectorizadas de manera eficiente mediante una estructura de datos conocida como Primitive Unified Bloc (PUB). Estos PUBs son la unidad fundamental de trabajo que un QPU necesita para ejecutar dichas cargas. Se utilizan como entradas al método run() de las primitivas Sampler y Estimator, que ejecutan la carga de trabajo definida como un job. Luego, una vez que el job ha finalizado, los resultados se devuelven en un formato que depende tanto de los PUBs utilizados como de las opciones de tiempo de ejecución especificadas por las primitivas Sampler o Estimator.

Descripción general de los PUBs

Al invocar el método run() de una primitiva, el argumento principal requerido es una list de una o más tuplas — una por cada circuito que ejecuta la primitiva. Cada una de estas tuplas se considera un PUB, y los elementos requeridos en cada tupla de la lista dependen de la primitiva utilizada. Los datos proporcionados a estas tuplas también pueden organizarse en distintas formas para aportar flexibilidad a una carga de trabajo mediante broadcasting — cuyas reglas se describen en una sección posterior.

PUB del Estimator

Para la primitiva Estimator, el formato del PUB debe contener como máximo cuatro valores:

  • Un único QuantumCircuit, que puede contener uno o más objetos Parameter
  • Una lista de uno o más observables que especifican los valores de expectación a estimar, organizados en un arreglo (por ejemplo, un único observable representado como un arreglo de 0 dimensiones, una lista de observables como un arreglo de 1 dimensión, etcétera). Los datos pueden estar en cualquiera de los formatos ObservablesArrayLike, como Pauli, SparsePauliOp, PauliList o str.
    nota

    Si tienes dos observables que conmutan en PUBs distintos pero con el mismo circuito, no se estimarán usando la misma medición. Cada PUB representa una base diferente de medición y, por lo tanto, se requieren mediciones separadas para cada PUB. Para garantizar que los observables que conmutan se estimen usando la misma medición, deben agruparse dentro del mismo PUB.

  • Una colección de valores de parámetros para enlazar con el circuito. Esto puede especificarse como un único objeto tipo arreglo donde el último índice corresponde a los objetos Parameter del circuito, u omitirse (o de forma equivalente, establecerse como None) si el circuito no tiene objetos Parameter.
  • (Opcionalmente) una precisión objetivo para los valores de expectación a estimar

PUB del Sampler

Para la primitiva Sampler, el formato de la tupla PUB contiene como máximo tres valores:

  • Un único QuantumCircuit, que puede contener uno o más objetos Parameter Nota: Estos circuitos también deben incluir instrucciones de medición para cada uno de los qubits a muestrear.
  • Una colección de valores de parámetros para enlazar con el circuito θk\theta_k (solo necesaria si se usan objetos Parameter que deben enlazarse en tiempo de ejecución)
  • (Opcionalmente) un número de shots con el que medir el circuito

El siguiente código muestra un ejemplo de entradas vectorizadas a la primitiva Estimator y las ejecuta en un backend de IBM® como un ú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()

Reglas de broadcasting

Los PUBs agregan elementos de múltiples arreglos (observables y valores de parámetros) siguiendo las mismas reglas de broadcasting que NumPy. Esta sección resume brevemente esas reglas. Para una explicación detallada, consulta la documentación de reglas de broadcasting de NumPy.

Reglas:

  • Los arreglos de entrada no necesitan tener el mismo número de dimensiones.
    • El arreglo resultante tendrá el mismo número de dimensiones que el arreglo de entrada con mayor dimensión.
    • El tamaño de cada dimensión es el tamaño más grande de la dimensión correspondiente.
    • Se asume que las dimensiones faltantes tienen tamaño uno.
  • Las comparaciones de forma comienzan por la dimensión más a la derecha y continúan hacia la izquierda.
  • Dos dimensiones son compatibles si sus tamaños son iguales o si uno de ellos es 1.

Ejemplos de pares de arreglos que hacen 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

Ejemplos de pares de arreglos que no hacen 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 devuelve una estimación del valor de expectación por cada elemento de la forma resultante del broadcasting.

A continuación se presentan algunos ejemplos de patrones comunes expresados en términos de broadcasting de arreglos. Su representación visual correspondiente se muestra en la figura siguiente:

Los conjuntos de valores de parámetros se representan como arreglos de n x m, y los arreglos de observables se representan como uno o más arreglos de una sola columna. Para cada ejemplo en el código anterior, los conjuntos de valores de parámetros se combinan con su arreglo de observables para crear las estimaciones de valores de expectación resultantes.

  • Ejemplo 1: (broadcast de un único observable) tiene un conjunto de valores de parámetros que es un arreglo de 5x1 y un arreglo de observables de 1x1. El único elemento del arreglo de observables se combina con cada elemento del conjunto de valores de parámetros para crear un único arreglo de 5x1 donde cada elemento es una combinación del elemento original en el conjunto de valores de parámetros con el elemento del arreglo de observables.

  • Ejemplo 2: (zip) tiene un conjunto de valores de parámetros de 5x1 y un arreglo de observables de 5x1. La salida es un arreglo de 5x1 donde cada elemento es una combinación del n-ésimo elemento del conjunto de valores de parámetros con el n-ésimo elemento del arreglo de observables.

  • Ejemplo 3: (producto exterior) tiene un conjunto de valores de parámetros de 1x6 y un arreglo de observables de 4x1. Su combinación produce un arreglo de 4x6 que se crea combinando cada elemento del conjunto de valores de parámetros con cada elemento del arreglo de observables, por lo que cada valor de parámetro se convierte en una columna completa en la salida.

  • Ejemplo 4: (generalización nd estándar) tiene un arreglo de valores de parámetros de 3x6 y dos arreglos de observables de 3x1. Estos se combinan para crear dos arreglos de salida de 3x6 de manera similar al ejemplo anterior.

Esta imagen ilustra varias representaciones visuales del broadcasting de arreglos

# 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 cuenta como un único elemento en este contexto, independientemente de la cantidad de Paulis que contenga el SparsePauliOp. Por lo tanto, para los fines de estas reglas de broadcasting, todos los elementos siguientes tienen la misma forma:

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

Las siguientes listas de operadores, aunque equivalentes en cuanto a la información que contienen, tienen formas distintas:

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

Descripción general de las salidas de las primitivas

Una vez que uno o más PUBs se envían a un QPU para su ejecución y el job finaliza con éxito, los datos se devuelven como un objeto contenedor PrimitiveResult al que se accede llamando al método RuntimeJobV2.result(). El PrimitiveResult contiene una lista iterable de objetos PubResult que contienen los resultados de ejecución de cada PUB. Dependiendo de la primitiva utilizada, estos datos serán valores de expectación con sus barras de error en el caso del Estimator, o muestras de la salida del circuito en el caso del Sampler.

Cada elemento de esta lista corresponde a cada PUB enviado al método run() de la primitiva (por ejemplo, un job enviado con 20 PUBs devolverá un objeto PrimitiveResult que contiene una lista de 20 PubResults, uno por cada PUB).

Cada uno de estos objetos PubResult posee tanto un atributo data como un atributo metadata. El atributo data es un DataBin personalizado que contiene los valores de medición reales, las desviaciones estándar, etcétera. Este DataBin tiene distintos atributos dependiendo de la forma o estructura del PUB asociado, así como de las opciones de mitigación de errores especificadas por la primitiva utilizada para enviar el job (por ejemplo, ZNE o PEC). Por su parte, el atributo metadata contiene información sobre el tiempo de ejecución y las opciones de mitigación de errores utilizadas (explicado más adelante en la sección Metadatos de resultados de esta página).

A continuación se presenta un esquema visual de la estructura de datos de 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
├── ...
├── ...
└── ...

En pocas palabras, un único job devuelve un objeto PrimitiveResult que contiene una lista de uno o más objetos PubResult. Estos objetos PubResult almacenan los datos de medición de cada PUB enviado al job.

Cada PubResult posee distintos formatos y atributos según el tipo de primitiva utilizada para el job. Los detalles se explican a continuación.

Salida del Estimator

Cada PubResult de la primitiva Estimator contiene al menos un arreglo de valores de expectación (PubResult.data.evs) y las desviaciones estándar asociadas (ya sea PubResult.data.stds o PubResult.data.ensemble_standard_error dependiendo del resilience_level utilizado), pero puede contener más datos dependiendo de las opciones de mitigación de errores especificadas.

El siguiente fragmento de código describe el formato de PrimitiveResult (y el PubResult asociado) para el job creado anteriormente.

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

Cómo calcula el error el Estimator

Además de la estimación de la media de los observables pasados en los PUBs de entrada (el campo evs del DataBin), el Estimator también intenta entregar una estimación del error asociado a esos valores de expectación. Todas las consultas al Estimator poblarán el campo stds con una cantidad similar al error estándar de la media para cada valor de expectación, pero algunas opciones de mitigación de errores producen información adicional, como ensemble_standard_error.

Considera un único observable O\mathcal{O}. En ausencia de ZNE, puedes pensar en cada shot de la ejecución del Estimator como si proporcionara una estimación puntual del valor de expectación O\langle \mathcal{O} \rangle. Si las estimaciones puntuales están en un vector Os, entonces el valor devuelto en ensemble_standard_error es equivalente al siguiente (donde σO\sigma_{\mathcal{O}} es la desviación estándar de la estimación del valor de expectación y NshotsN_{shots} es el número de shots):

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

que trata todos los shots como parte de un único ensemble. Si solicitaste twirling de gates (twirling.enable_gates = True), puedes ordenar las estimaciones puntuales de O\langle \mathcal{O} \rangle en conjuntos que comparten un mismo twirl. Llama a estos conjuntos de estimaciones O_twirls; hay num_randomizations (número de twirls) de ellos. Entonces stds es el error estándar de la media de O_twirls, como en

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

donde σO\sigma_{\mathcal{O}} es la desviación estándar de O_twirls y NtwirlsN_{twirls} es el número de twirls. Cuando no habilitas el twirling, stds y ensemble_standard_error son iguales.

Si habilitas ZNE, entonces los stds descritos anteriormente se convierten en pesos en una regresión no lineal sobre un modelo de extrapolación. Lo que finalmente se devuelve en stds en este caso es la incertidumbre del modelo de ajuste evaluada en un factor de ruido de cero. Cuando hay un ajuste deficiente, o gran incertidumbre en el ajuste, los stds reportados pueden volverse muy grandes. Cuando se habilita ZNE, también se populan pub_result.data.evs_noise_factors y pub_result.data.stds_noise_factors, de modo que puedas realizar tu propia extrapolación.

Salida del Sampler

Cuando un job de Sampler se completa con éxito, el objeto PrimitiveResult devuelto contiene una lista de SamplerPubResult, uno por PUB. Los data bins de estos objetos SamplerPubResult son objetos similares a diccionarios que contienen un BitArray por cada ClassicalRegister del circuito.

La clase BitArray es un contenedor para datos de shots ordenados. En más detalle, almacena las cadenas de bits muestreadas como bytes dentro de un array bidimensional. El eje más a la izquierda de este array recorre los shots en orden, mientras que el eje más a la derecha recorre los bytes.

Como primer ejemplo, veamos el siguiente circuito de diez 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]]

A veces puede ser conveniente convertir el formato de bytes del BitArray a cadenas de bits. El método get_count devuelve un diccionario que mapea cadenas de bits al número de veces que ocurrieron.

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

Cuando un circuito contiene más de un registro clásico, los resultados se almacenan en objetos BitArray distintos. El siguiente ejemplo modifica el fragmento anterior dividiendo el registro clásico en dos registros separados:

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

Aprovechando los objetos BitArray para un post-procesamiento eficiente

Dado que los arrays generalmente ofrecen mejor rendimiento en comparación con los diccionarios, es recomendable realizar cualquier post-procesamiento directamente sobre los objetos BitArray en lugar de hacerlo sobre diccionarios de conteos. La clase BitArray ofrece una variedad de métodos para realizar algunas operaciones comunes de post-procesamiento:

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

Metadatos de los resultados

Además de los resultados de ejecución, tanto los objetos PrimitiveResult como PubResult contienen un atributo de metadatos sobre el job enviado. Los metadatos que contienen información para todos los PUBs enviados (como las diversas opciones de runtime disponibles) se pueden encontrar en PrimitiveResult.metatada, mientras que los metadatos específicos de cada PUB se encuentran en PubResult.metadata.

nota

En el campo de metadatos, las implementaciones de primitivas pueden devolver cualquier información sobre la ejecución que sea relevante para ellas, y no hay pares clave-valor que estén garantizados por la primitiva base. Por tanto, los metadatos devueltos pueden ser diferentes según la implementación de la primitiva.

# 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 los jobs de Sampler, también puedes revisar los metadatos de los resultados para entender cuándo se ejecutaron ciertos datos; esto se denomina intervalo de ejecución.