Ingressi e uscite primitivi
Il codice di questa pagina è stato sviluppato in base ai seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.
qiskit[all]~=2.5.1
Questa pagina offre una panoramica degli input e degli output delle primitive dell' Qiskit SDK. Con queste primitive è possibile utilizzare una struttura dati nota come Primitive Unified Bloc ( PUB ) per definire in modo efficiente i carichi di lavoro vettorializzati. Questi PUB rappresentano l'unità di lavoro fondamentale per l'esecuzione del carico di lavoro. Vengono utilizzati come input per il metodo run() delle primitive Sampler ed Estimator, che eseguono il carico di lavoro definito come un’attività. Una volta completato il processo, i risultati vengono restituiti in un formato che dipende dai PUB utilizzati e dalle eventuali opzioni specificate.
Panoramica dei PUB
Quando si richiama il metodo run() di una primitiva, l'argomento principale richiesto è un insieme list di una o più tuple: una per ciascun circuito eseguito dalla primitiva. Ciascuna di queste tuple è considerata un’ PUB, e gli elementi richiesti per ogni tupla dell’elenco dipendono dal tipo primitivo utilizzato. I dati forniti a queste tuple possono inoltre essere organizzati in diverse forme per garantire flessibilità nel carico di lavoro tramite la trasmissione diffusa, le cui regole sono descritte in una sezione successiva.
Stima PUB
Per la primitiva Estimator, il formato di PUB deve contenere al massimo quattro valori:
- Un singolo
QuantumCircuit, che può contenere uno o più oggettiParameteroggetti - Un elenco di una o più osservabili, che specificano i valori di aspettativa da stimare, disposti in una matrice (ad esempio, una singola osservabile rappresentata come matrice 0-d, un elenco di osservabili come matrice 1-d e così via). I dati possono essere in uno dei formati
ObservablesArrayLikecomePauli,SparsePauliOp,PauliList, ostr.NoteSe si hanno due osservabili pendolari in PUB diversi ma con lo stesso circuito, essi non saranno stimati utilizzando la stessa misurazione. Ogni PUB rappresenta una base di misurazione diversa e, pertanto, sono necessarie misurazioni separate per ogni PUB. Per garantire che gli osservabili relativi al pendolarismo siano stimati utilizzando la stessa misurazione, essi devono essere raggruppati all'interno dello stesso gruppo di osservabili ( PUB ).
- Un insieme di valori di parametri con cui vincolare il circuito. Può essere specificato come un singolo oggetto di tipo array in cui l'ultimo indice è sugli oggetti
Parameterdel circuito, oppure omesso (o equivalentemente, impostato suNone) se il circuito non ha oggettiParameter. - (facoltativamente) una precisione target per i valori di aspettativa da stimare
Campionatore PUB
Per la primitiva Sampler, il formato della tupla PUB contiene al massimo tre valori:
- Un singolo circuito
QuantumCircuit, che può contenere uno o piùParameteroggetti Nota: questi circuiti devono inoltre includere istruzioni di misurazione per ciascuno dei qubit da campionare. - Un insieme di valori di parametri per vincolare il circuito a (necessario solo se si utilizzano oggetti
Parameterche devono essere vincolati in fase di esecuzione) - (Opzionalmente) un numero di scatti per misurare il circuito con
Il codice seguente mostra un esempio di insieme di input vettorializzati per la Estimator primitiva.
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.primitives import StatevectorEstimator
import numpy as np
# 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 without providing a backend
pm = generate_preset_pass_manager(optimization_level=1)
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, 10),
np.linspace(-4 * np.pi, 4 * np.pi, 10),
]
).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 = StatevectorEstimator()
estimator_pub = (transpiled_circuit, observables, params)
# Run the transpiled circuit
# using the set of parameters and observables.
job = estimator.run([estimator_pub])
result = job.result()Regole di trasmissione
I PUB aggregano elementi da più array (osservabili e valori dei parametri) seguendo le stesse regole di trasmissione di NumPy. Questa sezione riassume brevemente tali regole. Per una spiegazione dettagliata, consultare la documentazione sulle regole di trasmissione NumPy.
Regole:
- Non è necessario che gli array di input abbiano lo stesso numero di dimensioni.
- La matrice risultante avrà lo stesso numero di dimensioni della matrice di input con la dimensione maggiore.
- La dimensione di ogni dimensione è la dimensione più grande della dimensione corrispondente.
- Si presume che le dimensioni mancanti abbiano dimensione uno.
- I confronti tra le forme iniziano con la dimensione più a destra e proseguono verso sinistra.
- Due dimensioni sono compatibili se le loro dimensioni sono uguali o se una di esse è pari a 1.
Esempi di coppie di array che trasmettono:
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 7Esempi di coppie di array che non trasmettono:
A1 (1d array): 5
A2 (1d array): 3
A1 (2d array): 2 x 1
# The following would work if the middle dimension were 2,
# instead of 5.
A2 (3d array): 6 x 5 x 4Estimator restituisce una stima del valore atteso per ciascun elemento della matrice trasmessa.
Ecco alcuni esempi di modelli comuni espressi in termini di trasmissione di array. La loro rappresentazione visiva è riportata nella figura seguente:
Gli insiemi di valori dei parametri sono rappresentati da matrici n x m e le matrici di osservabili sono rappresentate da una o più matrici a colonna singola. Per ogni esempio del codice precedente, gli insiemi di valori dei parametri vengono combinati con il loro array di osservabili per creare le stime dei valori di aspettativa risultanti.
-
Esempio 1 : (broadcast single observable) ha un insieme di valori dei parametri che è una matrice 5x1 e una matrice di osservabili 1x1. L'elemento dell'array di osservabili viene combinato con ogni elemento dell'insieme di valori dei parametri per creare un singolo array 5x1 in cui ogni elemento è una combinazione dell'elemento originale dell'insieme di valori dei parametri con l'elemento dell'array di osservabili.
-
Esempio 2 : (zip) ha un insieme di valori di parametri 5x1 e un array di osservabili 5x1. L'output è un array 5x1 in cui ogni elemento è una combinazione dell'nesimo elemento dell'insieme dei valori dei parametri con l'nesimo elemento dell'array delle osservabili.
-
Esempio 3 : (outer/product) ha un insieme di valori di parametri 1x6 e un array di osservabili 4x1. La loro combinazione dà luogo a un array 4x6 che viene creato combinando ogni elemento dell'insieme dei valori dei parametri con ogni elemento dell'array degli osservabili; pertanto, ogni valore dei parametri diventa un'intera colonna nell'output.
-
Esempio 4 : (Generalizzazione standard nd) ha un array di valori di parametri 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 paoli contenuti in SparsePauliOp. Pertanto, ai fini di queste regole di trasmissione, tutti i seguenti elementi hanno la stessa forma:
a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()I seguenti elenchi di operatori, pur essendo equivalenti in termini di informazioni contenute, hanno forme diverse:
list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"])
# list1 has shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")]
# list2 has shape (3, )Panoramica delle uscite primitive
Una volta che uno o più PUB vengono inviati a una QPU per l'esecuzione e un'operazione viene completata con successo, i dati vengono restituiti sotto forma di oggetto PrimitiveResult contenitore. L'oggetto PrimitiveResult contiene un elenco iterabile di PubResult oggetti che contengono i risultati dell'esecuzione per ciascun PUB. Ad esempio, un lavoro inviato con 20 PUB restituirà un PrimitiveResult oggetto contenente un elenco di 20 elementi PubResults, uno per ogni PUB.
Ciascuno di questi PubResult oggetti possiede sia un attributo data che un attributo metadata opzionale. L'attributo data è un oggetto personalizzato DataBin che contiene le stime del valore atteso nel caso dello stimatore, oppure campioni dell'uscita del circuito nel caso del campionatore.
L'attributo data potrebbe inoltre includere altre informazioni specifiche dell'implementazione, come le deviazioni standard. L'attributo metadata può contenere ulteriori informazioni specifiche dell'implementazione relative all'esecuzione dell' PUB associato.
Di seguito viene illustrata la struttura dei dati di PrimitiveResult :
└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object,
| | ## which includes data such as the following:
│ ├── 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,
| | ## which includes data such as the following:
| ├── 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
├── ...
├── ...
└── ...
Quello che segue è un esempio dei dati che potrebbero essere restituiti. I dati effettivamente restituiti dipendono dall'implementazione.
└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── NAME_OF_CLASSICAL_REGISTER
│ │ └── BitArray of count data for first PUB (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
├── ...
├── ...
└── ...
Risultato dello stimatore
Come già detto, i dati restituiti dalla PubResult primitiva Estimator dipendono dall'implementazione. Ad esempio, potrebbe contenere un array di valori attesi (PubResult.data.evs) e delle relative deviazioni standard (PubResult.data.stds).
Il seguente frammento di codice descrive il formato PrimitiveResult (e il relativo PubResult) per il lavoro creato in precedenza.
print(
f"The result of the submitted job had {len(result)} PUB and "
f"has a value:\n {result}\n"
)
print(
f"The associated PubResult of this job has the following data bins:"
f"\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 number of parameters in the circuit -- "
"combined with our array of observables having shape (3, 1)."
)
print(
f"The expectation values measured from this PUB are: \n{result[0].data.evs}"
)Output:
The result of the submitted job had 1 PUB and has a value:
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10)), metadata={'target_precision': 0.0, 'circuit_metadata': {}})], metadata={'version': 2})
The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10))
And this DataBin has attributes: dict_keys(['evs', 'stds'])
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:
[[ 3.06161700e-16 4.52395120e-01 4.36594428e-01 2.16506351e-01
6.33718361e-01 -6.33718361e-01 -2.16506351e-01 -4.36594428e-01
-4.52395120e-01 -3.06161700e-16]
[ 1.22464680e-16 6.42787610e-01 9.84807753e-01 8.66025404e-01
3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01
-6.42787610e-01 -1.22464680e-16]
[ 4.89858720e-16 2.62002630e-01 -1.11618897e-01 -4.33012702e-01
9.25416578e-01 -9.25416578e-01 4.33012702e-01 1.11618897e-01
-2.62002630e-01 -4.89858720e-16]]
Uscita campionatore
Quando un processo Sampler viene completato con successo, l'oggetto PrimitiveResult restituito contiene un elenco di SamplerPubResults, uno per ogni PUB e. I contenitori di dati di questi SamplerPubResult oggetti sono oggetti simili a dizionari che contengono uno BitArray per ClassicalRegister ogni circuito.
La classe BitArray è un contenitore di dati ordinati sui pallini. In dettaglio, memorizza le stringhe di bit campionate come byte all'interno di un array bidimensionale. L'asse più a sinistra di questa matrice si riferisce agli scatti ordinati, mentre l'asse più a destra si riferisce ai byte.
Come primo esempio, esaminiamo il seguente circuito a dieci qubit:
from qiskit.primitives import StatevectorSampler
# 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)
sampler = StatevectorSampler()
# run the Sampler job and retrieve the results
job = sampler.run([transpiled_circuit])
result = job.result()
# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")
# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")Output:
Databin: DataBin(meas=BitArray(<shape=(), num_shots=1024, num_bits=10>))
BitArray: BitArray(<shape=(), num_shots=1024, num_bits=10>)
The shape of register `meas` is (1024, 2).
The bytes in register `alpha`, shot by shot:
[[ 3 255]
[ 0 0]
[ 0 0]
...
[ 0 0]
[ 0 0]
[ 0 0]]
A volte può essere utile convertire i dati dal formato byte in stringhe BitArray di bit. Il get_count metodo restituisce un dizionario che associa le stringhe di bit al numero di volte in cui sono state rilevate.
# optionally convert the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")Output:
Counts: {'1111111111': 517, '0000000000': 507}
Quando un circuito contiene più di un registro classico, i risultati vengono memorizzati in diversi BitArray oggetti. 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
job = sampler.run([transpiled_circuit])
result = job.result()
# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")Output:
BitArray for register 'alpha': BitArray(<shape=(), num_shots=1024, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=1024, num_bits=9>)
Sfruttare BitArray gli oggetti per una post-elaborazione performante
Poiché gli array offrono generalmente prestazioni migliori rispetto ai dizionari, è consigliabile eseguire qualsiasi post-elaborazione direttamente sugli BitArray oggetti piuttosto che sui dizionari dei conteggi. La BitArray classe offre una serie di metodi per eseguire alcune operazioni comuni di post-elaborazione:
print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")
print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")
# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == "0b1"
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")
# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")
# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)
# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
print(f"Exp. val. for observable `{o}` is: {e}")
# concatenate the bitstrings in `alpha` and `beta` to "merge" the results
# of the two registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")Output:
The shape of register `alpha` is (1024, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[0]
[1]
...
[0]
[1]
[1]]
The shape of register `beta` is (1024, 2).
The bytes in register `beta`, shot by shot:
[[ 1 255]
[ 0 0]
[ 1 255]
...
[ 0 0]
[ 1 255]
[ 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 (1024, 1).
The bytes in `beta` after bit-wise slicing:
[[7]
[0]
[7]
...
[0]
[7]
[7]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 1 255]
[ 0 0]
[ 1 255]
[ 0 0]
[ 0 0]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.01171875
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.01171875
The shape of the merged results is (1024, 2).
The bytes of the merged results:
[[ 3 255]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 3 255]]
Metadati dei risultati
Oltre ai risultati dell'esecuzione, gli PrimitiveResult oggetti PubResult e contengono un attributo di metadati facoltativo relativo al processo inviato. I metadati restituiti (se presenti) dipendono dall'implementazione.
# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
print(f"'{key}' : {val},")
print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
print(f"'{key}' : {val},")Output:
The metadata of the PrimitiveResult is:
'version' : 2,
The metadata of the PubResult result is:
'shots' : 1024,
'circuit_metadata' : {},
Passi successivi
- Esamina l'API delle primitive di Qiskit SDK.
- Esamina l'API delle primitive di Qiskit Aer.
- Scopri di più sulle primitive dell' IBM Quantum.
- Consulta l'API di Estimator
qiskit-ibm-runtime. - Consulta la documentazione sull'API Sampler
qiskit-ibm-runtime.