Entradas y salidas primitivas
El código de esta página se ha desarrollado teniendo en cuenta los siguientes requisitos. Recomendamos utilizar estas versiones o versiones más recientes.
qiskit[all]~=2.5.0
Esta página ofrece una visión general de las entradas y salidas del sistema de gestión de datos de la Universidad de California ( Qiskit primitives ). Con estas primitivas, puedes utilizar una estructura de datos conocida como « PUB » (bloque unificado de primitivas) para definir de forma eficiente cargas de trabajo vectorizadas. Estos PUB son la unidad básica de trabajo para la ejecución de cargas de trabajo. Se utilizan como entradas para el run() método de las primitivas «Sampler» y «Estimator», que ejecutan la carga de trabajo definida como un trabajo. A continuación, una vez finalizado el proceso, los resultados se devuelven en un formato que depende de los PUB utilizados y de las opciones especificadas.
Descripción general de los PUB
Al invocar el método run() de una primitiva, el argumento principal que se requiere es una lista list de una o más tuplas: una por cada circuito que ejecute la primitiva. Cada una de estas tuplas se considera un « PUB », y los elementos necesarios de cada tupla de la lista dependen del tipo primitivo utilizado. Los datos proporcionados a estas tuplas también pueden organizarse de diversas formas para ofrecer flexibilidad en una carga de trabajo mediante la difusión, cuyas reglas se describen en una sección posterior.
Estimador PUB
Para la primitiva Estimator, el formato de PUB debe contener como máximo cuatro valores:
- Un único
QuantumCircuit, que puede contener uno o variosParameterobjetos - Una lista de uno o más observables, que especifican los valores de expectativa a estimar, ordenados en una matriz (por ejemplo, un único observable representado como una matriz 0-d, una lista de observables como una matriz 1-d, etc.). Los datos pueden estar en cualquiera de los formatos de
ObservablesArrayLikecomoPauli,SparsePauliOp,PauliList, ostr.NoteSi tienes dos observables de conmutación en diferentes PUB, pero con el mismo circuito, no se estimarán utilizando la misma medición. Cada PUB representa una base de medición diferente y, por lo tanto, se requieren mediciones separadas para cada PUB. Para garantizar que los observables de desplazamiento se estimen utilizando la misma medida, deben agruparse dentro del mismo PUB.
- Una colección de valores de parámetros para enlazar el circuito. Puede especificarse como un único objeto de tipo matriz en el que el último índice se encuentra sobre los objetos
Parameterdel circuito, u omitirse (o, de forma equivalente, establecerse enNone) si el circuito no tiene objetosParameter. - (Opcionalmente) un objetivo de precisión para los valores de expectativa a estimar
Muestreador PUB
Para la primitiva Sampler, el formato de la tupla PUB contiene como máximo tres valores:
- Un único *circuito *
QuantumCircuit, que puede contener uno o másParameterobjetos Nota: Estos circuitos también deben incluir instrucciones de medición para cada uno de los qubits que se vayan a muestrear. - Una colección de valores de parámetros para enlazar el circuito con (sólo se necesita si se utiliza algún objeto
Parameterque deba enlazarse en tiempo de ejecución) - (Opcionalmente) un número de disparos para medir el circuito con
El siguiente código muestra un ejemplo de conjunto de entradas vectorizadas para 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()Normas de radiodifusión
Los PUB agregan elementos de múltiples matrices (observables y valores de parámetros) siguiendo las mismas reglas de difusión que NumPy. Esta sección resume brevemente esas normas. Para una explicación detallada, consulte la documentación sobre las reglas de difusión en NumPy.
Reglas:
- No es necesario que las matrices de entrada tengan el mismo número de dimensiones.
- La matriz resultante tendrá el mismo número de dimensiones que la matriz de entrada con la dimensión mayor.
- El tamaño de cada dimensión es el mayor tamaño de la dimensión correspondiente.
- Se supone que las dimensiones que faltan tienen tamaño uno.
- Las comparaciones de formas empiezan por la dimensión situada 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 matrices que emiten:
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 7Ejemplos de pares de matrices que no emiten:
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 devuelve una estimación del valor esperado para cada elemento de la forma difundida.
He aquí algunos ejemplos de patrones comunes expresados en términos de emisión de matrices. En la figura siguiente se muestra su representación visual:
Los conjuntos de valores de parámetros se representan mediante matrices n x m, y las matrices observables se representan mediante una o más matrices de una sola columna. Para cada ejemplo del código anterior, los conjuntos de valores de los parámetros se combinan con su matriz observable para crear las estimaciones de valores de expectativas resultantes.
-
Ejemplo 1 : (broadcast single observable) tiene un conjunto de valores de parámetro que es un array 5x1 y un array 1x1 observables. El elemento de la matriz de observables se combina con cada elemento del conjunto de valores de los parámetros para crear una única matriz 5x1 en la que cada elemento es una combinación del elemento original del conjunto de valores de los parámetros con el elemento de la matriz de observables.
-
Ejemplo 2 : (zip) tiene un conjunto de valores de parámetros 5x1 y una matriz de observables 5x1. La salida es una matriz 5x1 en la que cada elemento es una combinación del enésimo elemento del conjunto de valores de parámetros con el enésimo elemento de la matriz de observables.
-
Ejemplo 3 : (outer/product) tiene un conjunto de valores de parámetros 1x6 y una matriz de observables 4x1. Su combinación da como resultado una matriz 4x6 que se crea combinando cada elemento del conjunto de valores de parámetros con cada elemento de la matriz de observables, y así cada valor de parámetro se convierte en una columna entera en la salida.
-
Ejemplo 4 : (Generalización estándar nd) tiene una matriz de conjuntos de valores de parámetros 3x6 y dos matrices de observables 3x1. Estos se combinan para crear dos matrices de salida 3x6 de forma similar al ejemplo anterior.
# 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)Cada SparsePauliOp cuenta como un único elemento en este contexto, independientemente del número de Paulis que contenga SparsePauliOp. Así pues, a efectos de estas normas de radiodifusión, 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 contenida, tienen formas diferentes:
list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"])
# list1 has shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")]
# list2 has shape (3, )Resumen de las salidas primitivas
Una vez que se envían uno o varios PUB a una QPU para su ejecución y un trabajo se completa con éxito, los datos se devuelven como un objeto PrimitiveResult contenedor. El objeto PrimitiveResult contiene una lista iterable de PubResult objetos que recogen los resultados de la ejecución de cada PUB. Por ejemplo, un trabajo enviado con 20 PUB devolverá un PrimitiveResult objeto que contiene una lista de 20 elementos PubResults, uno correspondiente a cada PUB.
Cada uno de estos PubResult objetos tiene un atributo data y un metadata atributo opcional. El data atributo es un objeto personalizado DataBin que contiene las estimaciones del valor esperado en el caso del Estimador, o muestras de la salida del circuito en el caso del Muestreador.
El data atributo también podría incluir otra información específica de la implementación, como las desviaciones estándar. El metadata atributo puede contener información adicional específica de la implementación sobre la ejecución del PUB asociado.
A continuación se muestra un esquema visual de la estructura de datos de 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
├── ...
├── ...
└── ...
Lo anterior es un ejemplo de los datos que podrían devolverse. Los datos que se devuelven dependen de la implementación.
└── 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
├── ...
├── ...
└── ...
Salida del estimador
Como se ha indicado anteriormente, los datos devueltos en la PubResult primitiva Estimator dependen de la implementación. Por ejemplo, podría contener una matriz de valores esperados (PubResult.data.evs) y las desviaciones estándar asociadas (PubResult.data.stds).
El siguiente fragmento de código describe el formato PrimitiveResult (y el asociado PubResult) para el trabajo creado anteriormente.
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]]
Salida del muestreador
Cuando un trabajo de Sampler se completa con éxito, el objeto PrimitiveResult devuelto contiene una lista de SamplerPubResults, uno por cada PUB Los contenedores de datos de estos SamplerPubResult objetos son objetos similares a diccionarios que contienen uno BitArray por ClassicalRegister cada elemento del circuito.
La clase BitArray es un contenedor de datos de tomas ordenadas. Más detalladamente, almacena las cadenas de bits muestreadas como bytes dentro de una matriz bidimensional. El eje de la izquierda de esta matriz recorre las tomas ordenadas, mientras que el eje de la derecha recorre los bytes.
Como primer ejemplo, veamos el siguiente circuito de diez qubits:
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]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 0 0]]
A veces puede resultar útil convertir los datos del formato de bytes en cadenas BitArray de bits. El get_count método devuelve un diccionario que asocia cadenas de bits con el número de veces que han aparecido.
# optionally convert the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")Output:
Counts: {'1111111111': 507, '0000000000': 517}
Cuando un circuito contiene más de un registro clásico, los resultados se almacenan en diferentes BitArray objetos. El siguiente ejemplo modifica el fragmento anterior dividiendo el registro clásico en dos registros distintos:
# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
qreg := QuantumRegister(10),
alpha := ClassicalRegister(1, "alpha"),
beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))
# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)
# transpile the circuit
transpiled_circuit = pm.run(circuit)
# run the Sampler job and retrieve the results
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>)
Aprovechamiento de BitArray objetos para un posprocesamiento eficaz
Dado que las matrices suelen ofrecer un mejor rendimiento en comparación con los diccionarios, es recomendable realizar cualquier posprocesamiento directamente sobre los BitArray objetos en lugar de sobre los diccionarios de recuentos. La BitArray clase ofrece una serie de métodos para realizar algunas operaciones comunes de posprocesamiento:
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:
[[0]
[1]
[1]
...
[0]
[0]
[0]]
The shape of register `beta` is (1024, 2).
The bytes in register `beta`, shot by shot:
[[ 0 0]
[ 1 255]
[ 1 255]
...
[ 0 0]
[ 0 0]
[ 0 0]]
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:
[[0]
[7]
[7]
...
[0]
[0]
[0]]
The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 0]
[ 1 255]
[ 1 255]
[ 1 255]
[ 0 0]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: -0.03515625
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: -0.03515625
The shape of the merged results is (1024, 2).
The bytes of the merged results:
[[ 0 0]
[ 3 255]
[ 3 255]
...
[ 0 0]
[ 0 0]
[ 0 0]]
Metadatos del resultado
Además de los resultados de la ejecución, los PrimitiveResult objetos PubResult y contienen un atributo de metadatos opcional sobre el trabajo que se envió. Los metadatos devueltos (si los hay) dependen de la implementación.
# 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' : {},
Próximos pasos
- Consulte la API de « Qiskit primitives ».
- Consulte la API de primitivas de Qiskit Aer.
- Más información sobre las primitivas de « Qiskit Runtime ».
- Consulte la API de Estimator de Qiskit Runtime.
- Consulte la API de Sampler de Qiskit Runtime.