Aller au contenu principal

Entrées et sorties des primitives

Nouveau modèle d'exécution, maintenant en version bêta

La version bêta d'un nouveau modèle d'exécution est maintenant disponible. Le modèle d'exécution dirigée offre plus de flexibilité pour personnaliser ton flux de travail d'atténuation des erreurs. Consulte le guide du Modèle d'exécution dirigée pour plus d'informations.

Versions des paquets

Le code de cette page a été développé avec les prérequis suivants. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

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

Cette page donne un aperçu des entrées et sorties des primitives de Qiskit Runtime qui exécutent des charges de travail sur les ressources de calcul d'IBM Quantum®. Ces primitives te permettent de définir des charges de travail vectorisées de manière efficace en utilisant une structure de données appelée Primitive Unified Bloc (PUB). Ces PUBs sont l'unité fondamentale de travail dont un QPU a besoin pour exécuter ces charges. Ils sont utilisés comme entrées de la méthode run() des primitives Sampler et Estimator, qui exécutent la charge de travail définie comme un job. Ensuite, une fois le job terminé, les résultats sont retournés dans un format qui dépend à la fois des PUBs utilisés et des options d'exécution spécifiées par les primitives Sampler ou Estimator.

Aperçu des PUBs

Lors de l'invocation de la méthode run() d'une primitive, l'argument principal requis est une list d'un ou plusieurs tuples — un pour chaque circuit exécuté par la primitive. Chacun de ces tuples est considéré comme un PUB, et les éléments requis de chaque tuple dans la liste dépendent de la primitive utilisée. Les données fournies à ces tuples peuvent également être organisées sous différentes formes pour offrir de la flexibilité à une charge de travail via le broadcasting — dont les règles sont décrites dans une section ultérieure.

PUB de l'Estimator

Pour la primitive Estimator, le format du PUB doit contenir au maximum quatre valeurs :

  • Un seul QuantumCircuit, qui peut contenir un ou plusieurs objets Parameter
  • Une liste d'un ou plusieurs observables qui spécifient les valeurs d'espérance à estimer, organisés en un tableau (par exemple, un seul observable représenté comme un tableau de dimension 0, une liste d'observables comme un tableau de dimension 1, etc.). Les données peuvent être dans n'importe quel format ObservablesArrayLike comme Pauli, SparsePauliOp, PauliList ou str.
    remarque

    Si tu as deux observables qui commutent dans des PUBs différents mais avec le même circuit, ils ne seront pas estimés en utilisant la même mesure. Chaque PUB représente une base de mesure différente et, par conséquent, des mesures séparées sont nécessaires pour chaque PUB. Pour garantir que les observables qui commutent soient estimés en utilisant la même mesure, ils doivent être regroupés dans le même PUB.

  • Une collection de valeurs de paramètres à lier au circuit. Cela peut être spécifié comme un seul objet de type tableau où le dernier indice correspond aux objets Parameter du circuit, ou omis (ou de manière équivalente, défini comme None) si le circuit n'a pas d'objets Parameter.
  • (Optionnellement) une précision cible pour les valeurs d'espérance à estimer

PUB du Sampler

Pour la primitive Sampler, le format du tuple PUB contient au maximum trois valeurs :

  • Un seul QuantumCircuit, qui peut contenir un ou plusieurs objets Parameter Note : ces circuits doivent également inclure des instructions de mesure pour chacun des qubits à échantillonner.
  • Une collection de valeurs de paramètres à lier au circuit θk\theta_k (nécessaire uniquement si des objets Parameter sont utilisés et doivent être liés au moment de l'exécution)
  • (Optionnellement) un nombre de shots avec lequel mesurer le circuit

Le code suivant montre un exemple d'entrées vectorisées pour la primitive Estimator et les exécute sur un backend IBM® en tant qu'objet RuntimeJobV2 unique.

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

Règles de broadcasting

Les PUBs agrègent des éléments de plusieurs tableaux (observables et valeurs de paramètres) en suivant les mêmes règles de broadcasting que NumPy. Cette section résume brièvement ces règles. Pour une explication détaillée, consulte la documentation des règles de broadcasting de NumPy.

Règles :

  • Les tableaux d'entrée n'ont pas besoin d'avoir le même nombre de dimensions.
    • Le tableau résultant aura le même nombre de dimensions que le tableau d'entrée avec le plus grand nombre de dimensions.
    • La taille de chaque dimension est la taille la plus grande de la dimension correspondante.
    • Les dimensions manquantes sont supposées avoir une taille de un.
  • Les comparaisons de forme commencent par la dimension la plus à droite et continuent vers la gauche.
  • Deux dimensions sont compatibles si leurs tailles sont égales ou si l'une d'entre elles est 1.

Exemples de paires de tableaux compatibles avec le 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

Exemples de paires de tableaux non compatibles avec le 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 retourne une estimation de la valeur d'espérance pour chaque élément de la forme résultant du broadcasting.

Voici quelques exemples de motifs courants exprimés en termes de broadcasting de tableaux. Leur représentation visuelle correspondante est montrée dans la figure suivante :

Les ensembles de valeurs de paramètres sont représentés comme des tableaux de n x m, et les tableaux d'observables sont représentés comme un ou plusieurs tableaux à une seule colonne. Pour chaque exemple dans le code précédent, les ensembles de valeurs de paramètres sont combinés avec leur tableau d'observables pour créer les estimations de valeurs d'espérance résultantes.

  • Exemple 1 : (broadcast d'un seul observable) possède un ensemble de valeurs de paramètres qui est un tableau de 5x1 et un tableau d'observables de 1x1. L'unique élément du tableau d'observables est combiné avec chaque élément de l'ensemble de valeurs de paramètres pour créer un seul tableau de 5x1 où chaque élément est une combinaison de l'élément original dans l'ensemble de valeurs de paramètres avec l'élément du tableau d'observables.

  • Exemple 2 : (zip) possède un ensemble de valeurs de paramètres de 5x1 et un tableau d'observables de 5x1. La sortie est un tableau de 5x1 où chaque élément est une combinaison du n-ième élément de l'ensemble de valeurs de paramètres avec le n-ième élément du tableau d'observables.

  • Exemple 3 : (produit extérieur) possède un ensemble de valeurs de paramètres de 1x6 et un tableau d'observables de 4x1. Leur combinaison produit un tableau de 4x6 créé en combinant chaque élément de l'ensemble de valeurs de paramètres avec chaque élément du tableau d'observables, de sorte que chaque valeur de paramètre devient une colonne complète dans la sortie.

  • Exemple 4 : (généralisation nd standard) possède un tableau de valeurs de paramètres de 3x6 et deux tableaux d'observables de 3x1. Ceux-ci sont combinés pour créer deux tableaux de sortie de 3x6 de manière similaire à l'exemple précédent.

Cette image illustre plusieurs représentations visuelles du broadcasting de tableaux

# 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

Chaque SparsePauliOp compte comme un seul élément dans ce contexte, indépendamment du nombre de Paulis que contient le SparsePauliOp. Par conséquent, pour les besoins de ces règles de broadcasting, tous les éléments suivants ont la même forme :

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

Les listes d'opérateurs suivantes, bien qu'équivalentes en termes d'information contenue, ont des formes différentes :

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

Aperçu des sorties des primitives

Une fois qu'un ou plusieurs PUBs sont envoyés à un QPU pour exécution et que le job se termine avec succès, les données sont retournées sous forme d'objet conteneur PrimitiveResult auquel on accède en appelant la méthode RuntimeJobV2.result(). Le PrimitiveResult contient une liste itérable d'objets PubResult qui contiennent les résultats d'exécution de chaque PUB. Selon la primitive utilisée, ces données seront des valeurs d'espérance avec leurs barres d'erreur dans le cas de l'Estimator, ou des échantillons de la sortie du circuit dans le cas du Sampler.

Chaque élément de cette liste correspond à chaque PUB envoyé à la méthode run() de la primitive (par exemple, un job envoyé avec 20 PUBs retournera un objet PrimitiveResult contenant une liste de 20 PubResults, un pour chaque PUB).

Chacun de ces objets PubResult possède à la fois un attribut data et un attribut metadata. L'attribut data est un DataBin personnalisé qui contient les valeurs de mesure réelles, les écarts-types, etc. Ce DataBin a différents attributs selon la forme ou la structure du PUB associé, ainsi que les options d'atténuation des erreurs spécifiées par la primitive utilisée pour envoyer le job (par exemple, ZNE ou PEC). L'attribut metadata contient quant à lui des informations sur le temps d'exécution et les options d'atténuation des erreurs utilisées (expliqué plus loin dans la section Métadonnées des résultats de cette page).

Voici un schéma visuel de la structure de données 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 résumé, un seul job retourne un objet PrimitiveResult qui contient une liste d'un ou plusieurs objets PubResult. Ces objets PubResult stockent les données de mesure de chaque PUB envoyé au job.

Chaque PubResult possède des formats et attributs différents selon le type de primitive utilisée pour le job. Les détails sont expliqués ci-dessous.

Sortie de l'Estimator

Chaque PubResult de la primitive Estimator contient au moins un tableau de valeurs d'espérance (PubResult.data.evs) et les écarts-types associés (soit PubResult.data.stds soit PubResult.data.ensemble_standard_error selon le resilience_level utilisé), mais peut contenir plus de données selon les options d'atténuation des erreurs spécifiées.

Le fragment de code suivant décrit le format de PrimitiveResult (et le PubResult associé) pour le job créé précédemment.

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

Comment l'Estimator calcule l'erreur

En plus de l'estimation de la moyenne des observables passés dans les PUBs d'entrée (le champ evs du DataBin), l'Estimator tente également de fournir une estimation de l'erreur associée à ces valeurs d'espérance. Toutes les requêtes à l'Estimator rempliront le champ stds avec une quantité similaire à l'erreur standard de la moyenne pour chaque valeur d'espérance, mais certaines options d'atténuation des erreurs produisent des informations supplémentaires, comme ensemble_standard_error.

Considère un seul observable O\mathcal{O}. En l'absence de ZNE, tu peux considérer chaque shot de l'exécution de l'Estimator comme fournissant une estimation ponctuelle de la valeur d'espérance O\langle \mathcal{O} \rangle. Si les estimations ponctuelles sont dans un vecteur Os, alors la valeur retournée dans ensemble_standard_error est équivalente à ce qui suit (où σO\sigma_{\mathcal{O}} est l'écart-type de l'estimation de la valeur d'espérance et NshotsN_{shots} est le nombre de shots) :

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

qui traite tous les shots comme faisant partie d'un seul ensemble. Si tu as demandé le twirling de portes (twirling.enable_gates = True), tu peux organiser les estimations ponctuelles de O\langle \mathcal{O} \rangle en ensembles partageant un même twirl. Appelle ces ensembles d'estimations O_twirls ; il y en a num_randomizations (nombre de twirls). Alors stds est l'erreur standard de la moyenne de O_twirls, comme dans

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

σO\sigma_{\mathcal{O}} est l'écart-type de O_twirls et NtwirlsN_{twirls} est le nombre de twirls. Lorsque tu n'actives pas le twirling, stds et ensemble_standard_error sont égaux.

Si tu actives ZNE, alors les stds décrits précédemment deviennent des poids dans une régression non linéaire sur un modèle d'extrapolation. Ce qui est finalement retourné dans stds dans ce cas est l'incertitude du modèle d'ajustement évaluée à un facteur de bruit de zéro. Lorsqu'il y a un mauvais ajustement, ou une grande incertitude dans l'ajustement, les stds reportés peuvent devenir très grands. Lorsque ZNE est activé, pub_result.data.evs_noise_factors et pub_result.data.stds_noise_factors sont également remplis, de sorte que tu puisses effectuer ta propre extrapolation.

Sortie du Sampler

Lorsqu'un job de Sampler se termine avec succès, l'objet PrimitiveResult retourné contient une liste de SamplerPubResult, un par PUB. Les data bins de ces objets SamplerPubResult sont des objets similaires à des dictionnaires qui contiennent un BitArray par ClassicalRegister du circuit.

La classe BitArray est un conteneur pour les données de shots ordonnées. Plus précisément, elle stocke les chaînes de bits échantillonnées sous forme de bytes dans un tableau bidimensionnel. L'axe le plus à gauche de ce tableau parcourt les shots dans l'ordre, tandis que l'axe le plus à droite parcourt les bytes.

Comme premier exemple, examinons le circuit suivant à dix 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]]

Parfois, il peut être pratique de convertir le format de bytes du BitArray en chaînes de bits. La méthode get_count retourne un dictionnaire qui associe les chaînes de bits au nombre de fois qu'elles sont apparues.

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

Lorsqu'un circuit contient plus d'un registre classique, les résultats sont stockés dans des objets BitArray distincts. L'exemple suivant modifie le fragment précédent en divisant le registre classique en deux registres séparés :

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

Exploiter les objets BitArray pour un post-traitement performant

Étant donné que les tableaux offrent généralement de meilleures performances par rapport aux dictionnaires, il est recommandé d'effectuer tout post-traitement directement sur les objets BitArray plutôt que sur des dictionnaires de comptages. La classe BitArray offre une variété de méthodes pour effectuer certaines opérations courantes de post-traitement :

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

Métadonnées des résultats

En plus des résultats d'exécution, les objets PrimitiveResult et PubResult contiennent un attribut de métadonnées sur le job soumis. Les métadonnées contenant des informations pour tous les PUBs envoyés (comme les diverses options de runtime disponibles) se trouvent dans PrimitiveResult.metatada, tandis que les métadonnées spécifiques à chaque PUB se trouvent dans PubResult.metadata.

remarque

Dans le champ de métadonnées, les implémentations de primitives peuvent retourner toute information sur l'exécution qui leur est pertinente, et il n'y a pas de paires clé-valeur garanties par la primitive de base. Ainsi, les métadonnées retournées peuvent être différentes selon l'implémentation de la primitive.

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

Pour les jobs de Sampler, tu peux également consulter les métadonnées des résultats pour comprendre quand certaines données ont été exécutées ; c'est ce qu'on appelle l'intervalle d'exécution.