Entradas e saídas de primitivos
O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos o uso dessas versões ou de versões mais recentes.
qiskit[all]~=2.5.0
Esta página apresenta uma visão geral das entradas e saídas do Qiskit primitives. Com essas primitivas, é possível utilizar uma estrutura de dados conhecida como Bloco Unificado Primitivo ( PUB ) para definir com eficiência cargas de trabalho vetorizadas. Esses PUBs são a unidade fundamental de trabalho para a execução da carga de trabalho. Elas são utilizadas como entradas para o run() método das primitivas Sampler e Estimator, que executam a carga de trabalho definida como um trabalho. Em seguida, após a conclusão do trabalho, os resultados são retornados em um formato que depende dos PUBs utilizados e de quaisquer opções especificadas.
Visão geral dos PUBs
Ao invocar o método run() de uma primitiva, o principal argumento exigido é um conjunto list de uma ou mais tuplas — uma para cada circuito que está sendo executado pela primitiva. Cada uma dessas tuplas é considerada um PUB, e os elementos necessários de cada tupla na lista dependem do tipo primitivo utilizado. Os dados fornecidos a essas tuplas também podem ser organizados de diversas formas para proporcionar flexibilidade a uma carga de trabalho por meio da difusão — cujas regras são descritas na seção a seguir.
Estimador PUB
Para a primitiva Estimator, o formato do site PUB deve conter no máximo quatro valores:
- Um único
QuantumCircuit, que pode conter um ou maisParameterobjetos - Uma lista de um ou mais observáveis, que especifica os valores de expectativa a serem estimados, organizados em uma matriz (por exemplo, um único observável representado como uma matriz 0-d, uma lista de observáveis como uma matriz 1-d e assim por diante). Os dados podem estar em qualquer um dos formatos
ObservablesArrayLike, comoPauli,SparsePauliOp,PauliList, oustr.NoteSe você tiver dois observáveis de comutação em PUBs diferentes, mas com o mesmo circuito, eles não serão estimados usando a mesma medição. Cada PUB representa uma base diferente para medição e, portanto, são necessárias medições separadas para cada PUB. Para garantir que os observáveis de deslocamento sejam estimados usando a mesma medida, eles devem ser agrupados dentro do mesmo e PUB o.
- Uma coleção de valores de parâmetros para vincular o circuito. Isso pode ser especificado como um único objeto do tipo array em que o último índice está sobre os objetos
Parameterdo circuito, ou omitido (ou, de forma equivalente, definido comoNone) se o circuito não tiver objetosParameter. - (Opcionalmente) uma precisão de destino para os valores de expectativa a serem estimados
Amostrador PUB
Para a primitiva Sampler, o formato da tupla PUB contém no máximo três valores:
- Um único circuito
QuantumCircuit, que pode conter um ou maisParameterobjetos Observação: Esses circuitos também devem incluir instruções de medição para cada um dos qubits a serem amostrados. - Uma coleção de valores de parâmetros para vincular o circuito ao site (necessário somente se forem usados objetos
Parameterque devem ser vinculados em tempo de execução) - (Opcionalmente) um número de disparos para medir o circuito com
O código a seguir mostra um exemplo de conjunto de entradas vetorizadas para a 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()Regras de transmissão
Os PUBs agregam elementos de várias matrizes (observáveis e valores de parâmetros) seguindo as mesmas regras de transmissão do site NumPy. Esta seção resume brevemente essas regras. Para obter uma explicação detalhada, consulte a documentação de regras de transmissão do site NumPy.
Regras:
- As matrizes de entrada não precisam ter o mesmo número de dimensões.
- A matriz resultante terá o mesmo número de dimensões que a matriz de entrada com a maior dimensão.
- O tamanho de cada dimensão é o maior tamanho da dimensão correspondente.
- Supõe-se que as dimensões ausentes tenham o tamanho um.
- As comparações de formas começam com a dimensão mais à direita e continuam para a esquerda.
- Duas dimensões são compatíveis se seus tamanhos forem iguais ou se uma delas for 1.
Exemplos de pares de matrizes que transmitem:
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 7Exemplos de pares de matrizes que não transmitem:
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 retorna uma estimativa do valor esperado para cada elemento da forma transmitida.
Aqui estão alguns exemplos de padrões comuns expressos em termos de transmissão de matriz. A representação visual que os acompanha é mostrada na figura a seguir:
Os conjuntos de valores de parâmetros são representados por matrizes n x m, e as matrizes observáveis são representadas por uma ou mais matrizes de coluna única. Para cada exemplo no código anterior, os conjuntos de valores de parâmetros são combinados com sua matriz observável para criar as estimativas de valores de expectativa resultantes.
-
Exemplo 1 : (broadcast single observable) tem um conjunto de valores de parâmetro que é uma matriz 5x1 e uma matriz 1x1 observables. O item na matriz de observáveis é combinado com cada item no conjunto de valores de parâmetros para criar uma única matriz 5x1 em que cada item é uma combinação do item original no conjunto de valores de parâmetros com o item na matriz de observáveis.
-
Exemplo 2 : (zip) tem um conjunto de valores de parâmetro 5x1 e uma matriz de observáveis 5x1. O resultado é uma matriz 5x1 em que cada item é uma combinação do enésimo item no conjunto de valores de parâmetros com o enésimo item na matriz de observáveis.
-
Exemplo 3 : (outer/product) tem um conjunto de valores de parâmetro 1x6 e uma matriz de observáveis 4x1. Sua combinação resulta em uma matriz 4x6 criada pela combinação de cada item no conjunto de valores de parâmetro com cada item na matriz de observáveis e, portanto, cada valor de parâmetro se torna uma coluna inteira na saída.
-
Exemplo 4 : (Padrão e generalização) tem uma matriz de conjunto de valores de parâmetros 3x6 e duas matrizes de observáveis 3x1. Eles se combinam para criar duas matrizes de saída 3x6 de forma semelhante ao exemplo 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 conta como um único elemento nesse contexto, independentemente do número de Paulis contidos no SparsePauliOp. Portanto, para fins dessas regras de transmissão, todos os elementos a seguir têm o mesmo formato:
a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()As seguintes listas de operadores, embora equivalentes em termos de informações contidas, têm formatos diferentes:
list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"])
# list1 has shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")]
# list2 has shape (3, )Visão geral das saídas primitivas
Assim que um ou mais PUBs forem enviados a uma QPU para execução e um trabalho for concluído com sucesso, os dados são retornados como um objeto PrimitiveResult contêiner. O PrimitiveResult contém uma lista iterável de PubResult objetos que contêm os resultados da execução de cada PUB. Por exemplo, um trabalho enviado com 20 PUBs retornará um PrimitiveResult objeto que contém uma lista de 20 PubResults, sendo que cada um corresponde a um PUB.
Cada um desses PubResult objetos possui um atributo data e um metadata atributo opcional. O data atributo é um objeto personalizado DataBin que contém as estimativas do valor esperado, no caso do Estimador, ou amostras da saída do circuito, no caso do Amostrador.
O data atributo também pode incluir outras informações específicas da implementação, como desvios-padrão. O metadata atributo pode conter informações adicionais específicas da implementação sobre a execução do PUB associado.
A seguir, um esboço visual da estrutura de dados do site 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
├── ...
├── ...
└── ...
O texto acima é um exemplo dos dados que podem ser retornados. Os dados efetivamente retornados dependem da implementação.
└── 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
├── ...
├── ...
└── ...
Saída do estimador
Conforme mencionado anteriormente, os dados retornados pela PubResult primitiva Estimator dependem da implementação. Por exemplo, pode conter uma matriz de valores esperados (PubResult.data.evs) e desvios-padrão associados (PubResult.data.stds).
O trecho de código abaixo descreve o formato PrimitiveResult (e PubResult associado) para o trabalho criado acima.
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]]
Saída do sampler
Quando uma tarefa do Sampler é concluída com sucesso, o objeto PrimitiveResult retornado contém uma lista de SamplerPubResults, um por PUB. Os compartimentos de dados desses SamplerPubResult objetos são objetos semelhantes a dicionários que contêm um BitArray por ClassicalRegister circuito.
A classe BitArray é um contêiner para dados de disparo ordenados. Em mais detalhes, ele armazena as cadeias de bits amostradas como bytes em uma matriz bidimensional. O eixo mais à esquerda dessa matriz percorre os disparos ordenados, enquanto o eixo mais à direita percorre os bytes.
Como primeiro exemplo, vejamos o seguinte circuito de dez 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]]
Às vezes, pode ser conveniente converter o formato de bytes do BitArray em cadeias de bits. O get_count método retorna um dicionário que mapeia sequências de bits para o número de vezes que elas ocorreram.
# 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}
Quando um circuito contém mais de um registro clássico, os resultados são armazenados em diferentes BitArray objetos. O exemplo a seguir modifica o trecho anterior, dividindo o registro clássico em dois 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>)
Aproveitando BitArray objetos para pós-processamento de alto desempenho
Como as matrizes geralmente oferecem melhor desempenho em comparação com os dicionários, é aconselhável realizar qualquer pós-processamento diretamente nos BitArray objetos, em vez de nos dicionários de contagens. A BitArray classe oferece uma variedade de métodos para realizar algumas operações comuns de pós-processamento:
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]]
Metadados do resultado
Além dos resultados da execução, os PrimitiveResult objetos PubResult e contêm um atributo de metadados opcional sobre o trabalho que foi enviado. Os metadados retornados (se houver) dependem da implementação.
# 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óximas etapas
- Consulte a API do Qiskit primitives.
- Consulte a API de primitivas do Qiskit Aer.
- Saiba mais sobre as primitivas do
Qiskit Runtime. - Consulte a API do Estimador do Qiskit Runtime.
- Consulte a API do Sampler do Qiskit Runtime.