Input e output delle primitive
La versione beta di un nuovo modello di esecuzione è ora disponibile. Il modello di esecuzione diretto offre maggiore flessibilità nella personalizzazione del flusso di lavoro per la mitigazione degli errori. Consulta la guida al modello di esecuzione diretto per ulteriori informazioni.
Versioni dei pacchetti
Il codice in questa pagina è stato sviluppato utilizzando i seguenti requisiti. Si consiglia di usare queste versioni o versioni più recenti.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Questa pagina offre una panoramica degli input e degli output delle primitive Qiskit Runtime che eseguono workload sulle risorse di calcolo di IBM Quantum®. Queste primitive ti permettono di definire in modo efficiente workload vettorizzati tramite una struttura dati nota come Primitive Unified Bloc (PUB). I PUB sono l'unità fondamentale di lavoro che una QPU deve eseguire per portare a termine questi workload. Vengono usati come input al metodo run() per le primitive Sampler ed Estimator, che eseguono il workload definito come job. Una volta completato il job, i risultati vengono restituiti in un formato che dipende sia dai PUB usati sia dalle opzioni di runtime specificate dalla primitiva Sampler o Estimator.
Panoramica dei PUB​
Quando si richiama il metodo run() di una primitiva, l'argomento principale richiesto è una list di una o più tuple — una per ogni circuito eseguito dalla primitiva. Ognuna di queste tuple è considerata un PUB, e gli elementi richiesti in ciascuna tupla dipendono dalla primitiva usata. I dati forniti a queste tuple possono anche essere disposti in una varietà di forme per offrire flessibilità nel workload tramite broadcasting — le cui regole sono descritte in una sezione successiva.
PUB dell'Estimator​
Per la primitiva Estimator, il formato del PUB può contenere al massimo quattro valori:
- Un singolo
QuantumCircuit, che può contenere uno o più oggettiParameter - Una lista di uno o più osservabili, che specificano i valori di aspettazione da stimare, organizzati in un array (per esempio, un singolo osservabile rappresentato come array 0-d, una lista di osservabili come array 1-d, ecc.). I dati possono essere in uno qualsiasi dei formati
ObservablesArrayLike, comePauli,SparsePauliOp,PauliListostr.notaSe hai due osservabili che commutano in PUB diversi ma con lo stesso circuito, non verranno stimati usando la stessa misurazione. Ogni PUB rappresenta una base di misurazione diversa, quindi sono necessarie misurazioni separate per ciascun PUB. Per assicurarti che gli osservabili commutanti vengano stimati con la stessa misurazione, devono essere raggruppati nello stesso PUB.
- Una raccolta di valori parametrici da collegare al circuito. Può essere specificata come un singolo oggetto array-like in cui l'ultimo indice è relativo agli oggetti
Parameterdel circuito, oppure omessa (o equivalentemente impostata aNone) se il circuito non contiene oggettiParameter. - (Facoltativamente) una precisione target per i valori di aspettazione da stimare
PUB del Sampler​
Per la primitiva Sampler, il formato della tupla PUB contiene al massimo tre valori:
- Un singolo
QuantumCircuit, che può contenere uno o più oggettiParameterNota: questi circuito devono includere anche istruzioni di misurazione per ciascuno dei qubit da campionare. - Una raccolta di valori parametrici da collegare al circuito (necessaria solo se vengono usati oggetti
Parameterche devono essere associati a runtime) - (Facoltativamente) un numero di shot con cui misurare il circuito
Il codice seguente mostra un esempio di input vettorizzati per la primitiva Estimator ed esegue questi input su un backend IBM® come un singolo oggetto 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()
Regole di broadcasting​
I PUB aggregano elementi da più array (osservabili e valori parametrici) seguendo le stesse regole di broadcasting di NumPy. Questa sezione riassume brevemente tali regole. Per una spiegazione dettagliata, consulta la documentazione sulle regole di broadcasting di NumPy.
Regole:
- Gli array di input non devono avere necessariamente lo stesso numero di dimensioni.
- L'array risultante avrà lo stesso numero di dimensioni dell'array di input con la dimensione maggiore.
- La dimensione di ciascuna dimensione è la dimensione più grande della dimensione corrispondente.
- Le dimensioni mancanti si assumono di dimensione uno.
- I confronti tra forme partono dalla dimensione più a destra e proseguono verso sinistra.
- Due dimensioni sono compatibili se le loro dimensioni sono uguali o se una di esse è 1.
Esempi di coppie di array che fanno 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
Esempi di coppie di array che non fanno 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 restituisce una stima del valore di aspettazione per ciascun elemento della forma trasmessa (broadcasted).
Di seguito sono riportati alcuni esempi di pattern comuni espressi in termini di broadcasting di array. La loro rappresentazione visiva è mostrata nella figura seguente:
I set di valori parametrici sono rappresentati da array n x m, e gli array di osservabili sono rappresentati da uno o più array a colonna singola. Per ciascun esempio nel codice precedente, i set di valori parametrici vengono combinati con il loro array di osservabili per creare le stime dei valori di aspettazione risultanti.
-
Esempio 1: (broadcast di un singolo osservabile) ha un set di valori parametrici che è un array 5x1 e un array di osservabili 1x1. Il singolo elemento nell'array di osservabili viene combinato con ciascun elemento del set di valori parametrici per creare un singolo array 5x1 in cui ogni elemento è una combinazione dell'elemento originale nel set di valori parametrici con l'elemento nell'array di osservabili.
-
Esempio 2: (zip) ha un set di valori parametrici 5x1 e un array di osservabili 5x1. L'output è un array 5x1 in cui ogni elemento è una combinazione dell'n-esimo elemento del set di valori parametrici con l'n-esimo elemento dell'array di osservabili.
-
Esempio 3: (outer/product) ha un set di valori parametrici 1x6 e un array di osservabili 4x1. La loro combinazione produce un array 4x6 creato combinando ciascun elemento del set di valori parametrici con ogni elemento dell'array di osservabili; quindi ogni valore parametrico diventa un'intera colonna nell'output.
-
Esempio 4: (generalizzazione nd standard) ha un array di set di valori parametrici 3x6 e due array di osservabili 3x1. Questi si combinano per creare due array di output 3x6 in modo simile all'esempio precedente.
# 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)
Ogni SparsePauliOp conta come un singolo elemento in questo contesto, indipendentemente dal numero di Pauli contenuti nello SparsePauliOp. Pertanto, ai fini di queste regole di broadcasting, tutti i seguenti elementi hanno la stessa forma:
a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
Le seguenti liste di operatori, pur essendo equivalenti in termini di informazioni contenute, hanno forme diverse:
list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")] # shape (3, )
Panoramica degli output delle primitive​
Una volta che uno o più PUB vengono inviati a una QPU per l'esecuzione e un job viene completato con successo, i dati vengono restituiti come oggetto contenitore PrimitiveResult a cui si accede chiamando il metodo RuntimeJobV2.result(). Il PrimitiveResult contiene una lista iterabile di oggetti PubResult che contengono i risultati di esecuzione per ciascun PUB. A seconda della primitiva usata, questi dati saranno o valori di aspettazione con le relative barre di errore (nel caso dell'Estimator), o campioni dell'output del circuito (nel caso del Sampler).
Ogni elemento di questa lista corrisponde a ciascun PUB inviato al metodo run() della primitiva (per esempio, un job inviato con 20 PUB restituirà un oggetto PrimitiveResult che contiene una lista di 20 PubResult, uno per ogni PUB).
Ognuno di questi oggetti PubResult possiede sia un attributo data sia un attributo metadata. L'attributo data è un DataBin personalizzato che contiene i valori di misurazione effettivi, le deviazioni standard e così via. Questo DataBin ha vari attributi a seconda della forma o struttura del PUB associato e delle opzioni di mitigazione degli errori specificate dalla primitiva usata per inviare il job (per esempio, ZNE o PEC). L'attributo metadata, invece, contiene informazioni sulle opzioni di runtime e di mitigazione degli errori usate (spiegate più avanti nella sezione Metadati dei risultati di questa pagina).
Di seguito è riportato uno schema visivo della struttura dati PrimitiveResult:
- Estimator Output
- Sampler Output
└── 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
├── ...
├── ...
└── ...
└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── NAME_OF_CLASSICAL_REGISTER
│ │ └── BitArray of count data (default is 'meas')
| |
│ └── NAME_OF_ANOTHER_CLASSICAL_REGISTER
│ └── BitArray of count data (exists only if more than one
| ClassicalRegister was specified in the circuit)
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| └── NAME_OF_CLASSICAL_REGISTER
| └── BitArray of count data for second pub
├── ...
├── ...
└── ...
In sintesi, un singolo job restituisce un oggetto PrimitiveResult che contiene una lista di uno o più oggetti PubResult. Questi oggetti PubResult memorizzano poi i dati di misurazione per ciascun PUB inviato al job.
Ogni PubResult ha formati e attributi diversi a seconda del tipo di primitiva usata per il job. I dettagli sono spiegati di seguito.
Output dell'Estimator​
Ogni PubResult per la primitiva Estimator contiene almeno un array di valori di aspettazione (PubResult.data.evs) e le deviazioni standard associate (o PubResult.data.stds o PubResult.data.ensemble_standard_error a seconda del resilience_level usato), ma può contenere ulteriori dati a seconda delle opzioni di mitigazione degli errori specificate.
Il frammento di codice seguente descrive il formato del PrimitiveResult (e del PubResult associato) per il job creato in precedenza.
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 ]]
Come l'Estimator calcola l'errore​
Oltre alla stima della media degli osservabili passati nei PUB di input (il campo evs del DataBin), l'Estimator cerca anche di fornire una stima dell'errore associato a quei valori di aspettazione. Tutte le query dell'Estimator popolano il campo stds con una quantità simile all'errore standard della media per ciascun valore di aspettazione, ma alcune opzioni di mitigazione degli errori producono informazioni aggiuntive, come ensemble_standard_error.
Considera un singolo osservabile . In assenza di ZNE, puoi pensare a ciascuno shot dell'esecuzione dell'Estimator come a un punto di stima del valore di aspettazione . Se le stime puntuali sono in un vettore Os, allora il valore restituito in ensemble_standard_error è equivalente al seguente (in cui è la deviazione standard della stima del valore di aspettazione e è il numero di shot):
che tratta tutti gli shot come parte di un unico ensemble. Se hai richiesto il twirling dei gate (twirling.enable_gates = True), puoi raggruppare le stime puntuali di in insiemi che condividono un twirl comune. Chiama questi insiemi di stime O_twirls, e ce ne sono num_randomizations (numero di twirl). Allora stds è l'errore standard della media di O_twirls, come in
dove è la deviazione standard di O_twirls e è il numero di twirl. Quando non abiliti il twirling, stds e ensemble_standard_error sono uguali.
Se abiliti ZNE, allora i stds descritti sopra diventano pesi in una regressione non lineare verso un modello di estrapolatore. Ciò che viene infine restituito in stds in questo caso è l'incertezza del modello di fit valutata a un fattore di rumore pari a zero. Quando il fit è scarso, o l'incertezza nel fit è elevata, i stds riportati possono diventare molto grandi. Quando ZNE è abilitato, vengono popolati anche pub_result.data.evs_noise_factors e pub_result.data.stds_noise_factors, in modo da poter eseguire la propria estrapolazione.
Output del Sampler​
Quando un job Sampler viene completato con successo, l'oggetto PrimitiveResult restituito contiene una lista di SamplerPubResult, uno per PUB. I data bin di questi oggetti SamplerPubResult sono oggetti simili a dizionari che contengono un BitArray per ogni ClassicalRegister nel circuito.
La classe BitArray è un contenitore per i dati degli shot ordinati. In dettaglio, memorizza le bitstring campionate come byte all'interno di un array bidimensionale. L'asse più a sinistra di questo array scorre sugli shot ordinati, mentre quello più a destra scorre sui byte.
Come primo esempio, esamina il seguente circuito a dieci qubit:
# 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 volte può essere comodo convertire il formato byte del BitArray in bitstring. Il metodo get_count restituisce un dizionario che mappa le bitstring al numero di volte in cui si sono verificate.
# 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 un circuito contiene più di un registro classico, i risultati vengono memorizzati in oggetti BitArray diversi. L'esempio seguente modifica il frammento precedente suddividendo il registro classico in due registri distinti:
# 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>)
Sfruttare gli oggetti BitArray per un post-processing performante​
Poiché gli array offrono generalmente prestazioni migliori rispetto ai dizionari, è consigliabile eseguire qualsiasi post-processing direttamente sugli oggetti BitArray piuttosto che sui dizionari di conteggi. La classe BitArray offre una serie di metodi per eseguire alcune operazioni di post-processing comuni:
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]]
Metadati dei risultati​
Oltre ai risultati dell'esecuzione, sia gli oggetti PrimitiveResult che PubResult contengono un attributo metadata relativo al job inviato. I metadati contenenti informazioni per tutti i PUB inviati (come le varie opzioni di runtime disponibili) si trovano in PrimitiveResult.metatada, mentre i metadati specifici per ogni PUB si trovano in PubResult.metadata.
Nel campo metadata, le implementazioni delle primitive possono restituire qualsiasi informazione sull'esecuzione che ritengono rilevante, e non vi sono coppie chiave-valore garantite dalla primitiva base. Pertanto, i metadati restituiti potrebbero essere diversi in diverse implementazioni delle 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' : {},
Per i job Sampler, puoi anche consultare i metadati dei risultati per capire quando certi dati sono stati eseguiti; questo si chiama execution span.