Entradas e saídas do executor
O código desta página foi desenvolvido com base nos seguintes requisitos. Recomendamos o uso dessas versões ou versões mais recentes.
qiskit[all]~=2.4.0 qiskit-ibm-runtime~=0.46.1 samplomatic~=0.18.0
A primitiva Executor faz parte do modelo de execução direcionada, que oferece maior flexibilidade na personalização de um fluxo de trabalho de mitigação de erros.
As entradas e saídas da primitiva Executor são muito diferentes das entradas e saídas das primitivas Sampler e Estimator. Por exemplo, em vez de receber uma lista de PUBs como entrada, o Executor recebe um QuantumProgramobjeto que contém uma lista de QuantumProgramItem objetos. Essas classes de contêineres oferecem mais flexibilidade do que um PUB, que é uma estrutura de dados simples do tipo tupla.
A saída do executor é um QuantumProgramResult, que é um iterável e contém um elemento para cada entrada QuantumProgramItem.
Entradas: Programas Quantum
Conforme mencionado anteriormente, a entrada para uma primitiva Executor é um QuantumProgram, que é uma coleção iterável de
QuantumProgramItem objetos. Esses objetos podem ser de dois tipos:
CircuitItem, que normalmente armazena um circuito e os valores de seus parâmetros (se houver).SamplexItem, que normalmente armazena o seguinte:- Um circuito modelo
- Um objeto samplex, utilizado para gerar conjuntos aleatórios de parâmetros em tempo de execução (por exemplo, para realizar twirling ou injetar ruído)
- Argumentos para o samplex, que podem incluir valores de parâmetros do circuito original
Cada um desses itens representa uma tarefa diferente a ser realizada pelo Executor.
Antes de iniciar
Alguns dos exemplos de código nesta página utilizam samplex, que faz parte do pacote Samplomatic. Portanto, antes de executar esses blocos de código, é necessário instalar o Samplomatic, conforme mostrado no bloco de código a seguir. Para mais informações, consulte a documentação do Samplomatic.
pip install samplomatic
# For visualization support, include the visualization dependencies.
# pip install samplomatic[vis]Exemplo: Criar um QuantumProgram com duas tarefas diferentes
Primeiro, inicialize seu programa quântico e, em seguida, acrescente itens ao programa usando ou append_samplex_item``append_circuit_item (se houver um samplex), conforme mostrado nos exemplos a seguir.
A célula a seguir inicializa um QuantumProgram e especifica que ele deve executar 1024 simulações para cada configuração de cada item do programa.
Ao contrário do Sampler, um QuantumProgram aceita apenas um único valor de amostra. QuantumProgramSe você quiser um valor de disparo diferente, precisará de um componente separado, o que seria um trabalho à parte.
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.quantum_program import QuantumProgram
from qiskit_ibm_runtime import Executor, QiskitRuntimeService
from qiskit.circuit import Parameter, QuantumCircuit
import numpy as np
from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager
# Initialize an empty program
program = QuantumProgram(shots=1024)
# Initialize and transpile a 3-qubit quantum circuit with 2 parameters.
circuit = QuantumCircuit(3)
circuit.h(0)
circuit.cx(0, 1)
circuit.cx(1, 2)
circuit.rz(Parameter("theta"), 0)
circuit.rz(Parameter("phi"), 1)
# `measure_all` adds a 3-bit classical register named "meas"
circuit.measure_all()
# Choose the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Generate a preset pass manager
# This will be used to convert the abstract circuit to an
# equivalent Instruction Set Architecture (ISA) circuit.
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)
# Transpile the circuit
isa_circuit = preset_pass_manager.run(circuit)Acrescentar um CircuitItem
Em seguida, acrescente o circuito de destino, que foi transpilado de acordo com a arquitetura do conjunto de instruções (ISA) do backend, ao QuantumProgram. Como este circuito possui dois parâmetros, devemos também fornecer os valores desses parâmetros (10 conjuntos neste exemplo). A execução desta operação CircuitItem é a primeira tarefa que o programa realizará.
# Append the transpiled circuit and an array
# containing 10 sets of parameter values to the program
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(
10, 2
), # 10 sets of parameter values and 2 parameters
)Acrescentar um SamplexItem
Os itens do circuito são executados sem qualquer tipo de aleatoriedade. Pelo contrário, os itens do Samplex permitem que você especifique como randomizar seu conteúdo. A próxima célula usa a generate_boxing_pass_manager() função para agrupar as portas e as medições do circuito em caixas e adicionar uma anotação giratória a cada caixa. Em seguida, gera um circuito modelo e um par de amostras utilizando a build() função.
A execução desta SamplexItem tarefa é a segunda que o programa realizará.
Consulte a documentação da API do Samplomatic para obter todos os detalhes sobre samplex e seus argumentos. Consulte o guia do Samplomatic Transpiler para obter informações sobre como usar a generate_boxing_pass_manager() função.
# Transpile the circuit, additionally grouping gates and measurements into annotated boxes
preset_pass_manager = generate_preset_pass_manager(
backend=backend, optimization_level=0
)
# Use the boxing pass manager to group gates
# and measurements into boxes and add
# a`Twirl` annotation.
preset_pass_manager.post_scheduling = generate_boxing_pass_manager(
# Add gate twirling
enable_gates=True,
# Add measurement twirling
enable_measures=True,
)
boxed_circuit = preset_pass_manager.run(circuit)
# Build the template circuit and the samplex. The template circuit has parametric gates
# without fixed values and the samplex randomly generates the parameter
# values on the server side at runtime to perform twirling.
template_circuit, samplex = build(boxed_circuit)
# Determine what arguments are required by the samplex.
# Input the arguments in samplex_arguments.
print(samplex.inputs())Output:
TensorInterface(<
- 'parameter_values' <float64[2]>: Input parameter values to use during sampling.
>)
# Append the template circuit and samplex as a samplex item
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
# the arguments required by the samplex.sample method
"parameter_values": np.random.rand(10, 2),
},
shape=(28, 10), # 28 randomizations and 10 sets of parameter values
)# Initialize an Executor with the default options
executor = Executor(mode=backend)
# Submit the job
job = executor.run(program)
# Retrieve the result
result = job.result()Saídas
A saída do executor é um QuantumProgramResult, que é um iterável. Ele contém uma entrada por entrada QuantumProgramItem , na mesma ordem em que os itens de entrada aparecem. Cada um desses itens de saída é um dicionário cujas chaves são strings que correspondem aos nomes dos registros clássicos nos circuitos de entrada (entre outros), de modo que você não precisa mais memorizar esses nomes como fazia com a saída do Sampler. Os valores do dicionário são do tipo np.ndarray.
O resultado do exemplo anterior contém os seguintes itens:
CircuitItem resultado
O primeiro item contém os resultados da execução da primeira tarefa (a CircuitItem) do programa. Ele contém uma única chave, meas, que é o nome do registro clássico no circuito de entrada. O valor desta chave corresponde a um np.ndarray de formato (parameter sets, shots, register bits), que é (10, 1024, 3) no exemplo acima.
O código a seguir ilustra como acessar essas informações:
# Access the results of the classical register of task #0, a CircuitItem
result_0 = result[0]["meas"]
print(f"Result shape: {result_0.shape}")Output:
Result shape: (10, 1024, 3)
SamplexItem resultado
O segundo item contém os resultados da execução da segunda tarefa (a SamplexItem) do programa. Este item contém várias chaves. A meas chave, que é o nome do registro clássico do circuito de entrada, remete à matriz de resultados desse registro. Essa matriz tem a forma (randomizations, parameter sets, shots, classical bits), ou (28, 10, 1024, 3) neste exemplo. Além disso, a saída contém uma measurement_flips.meas chave, que corresponde às correções de inversão de bits necessárias para reverter a distorção da medição no meas registro. No nosso exemplo, essa matriz de saída será (28, 10, 1, 3), pois basta um único passo para realizar a inversão de bits.
# Access the results of the classical register of task #1
result_1 = result[1]["meas"]
print(f"Result shape: {result_1.shape}")
# Access the bit-flip corrections
flips_1 = result[1]["measurement_flips.meas"]
print(f"Bit-flip corrections shape: {flips_1.shape}")
# Undo the bit flips via classical XOR
unflipped_result_1 = result_1 ^ flips_1Output:
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)
Próximas etapas
- Explore exemplos que utilizam o Executor.
- Saiba mais sobre o modelo de execução direcionada.
- Entenda a transmissão do Executor.