Pular para o conteúdo principal
Este projeto está em fase beta. Encontrou um bug? Abra uma issue no GitHub. Tem uma pergunta ou ideia? Pergunte no Discussions — adoraríamos receber seu feedback!
Como está a qualidade da tradução nesta página?

Entradas e saídas do Executor

Versões dos pacotes

O código desta página foi desenvolvido usando os seguintes requisitos. Recomendamos usar essas versões ou versões mais recentes.

qiskit[all]~=2.6.1
qiskit-ibm-runtime~=0.50.0
samplomatic~=0.21.0

A primitiva Executor faz parte do modelo de execução direcionada, que fornece mais flexibilidade ao personalizar um fluxo de trabalho de mitigação de erros.

As entradas e saídas da primitiva Executor são muito diferentes das primitivas Sampler e Estimator. Por exemplo, em vez de receber uma lista de PUBs como entrada, o Executor recebe um QuantumProgram, que contém uma lista de objetos QuantumProgramItem. Essas classes de contêiner oferecem mais flexibilidade do que um PUB, que é uma estrutura de dados de tupla simples.

A saída do Executor é um QuantumProgramResult, que é iterável e contém um elemento para cada QuantumProgramItem de entrada.

Entradas: Programas quânticos​

Como dito anteriormente, a entrada para uma primitiva Executor é um QuantumProgram, que é um iterável de objetos QuantumProgramItem. Esses objetos podem ser de dois tipos:

  • CircuitItem, que normalmente armazena um circuito e seus valores de parâmetros (se houver).
  • SamplexItem, que normalmente armazena o seguinte:
    • Um circuito modelo
    • Um objeto samplex, que é usado para gerar conjuntos randomizados 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 para o circuito original

Cada um desses itens representa uma tarefa diferente para o Executor realizar.

Antes de começar​

Alguns dos exemplos de código nesta página usam samplex, que faz parte do pacote Samplomatic. Portanto, antes de executar esses blocos de código, você deve instalar o Samplomatic, conforme mostrado no bloco de código a seguir. Para mais informações, veja 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, depois acrescente itens de programa a ele usando append_circuit_item ou append_samplex_item (se um samplex estiver presente), conforme mostrado nos exemplos a seguir.

A célula a seguir inicializa um QuantumProgram e especifica que ele deve executar 1024 shots para cada configuração de cada item no programa.

nota

Diferentemente do Sampler, um QuantumProgram aceita apenas um único valor de shots. Se você quiser um valor de shots diferente, precisará de um QuantumProgram separado, que seria um job separado.

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "samplomatic": "samplomatic"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
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 alvo, que foi transpilado de acordo com a arquitetura de conjunto de instruções (ISA) do Backend, ao QuantumProgram. Como este circuito tem dois parâmetros, também devemos fornecer os valores dos parâmetros (10 conjuntos neste exemplo). Executar este 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 de circuito são executados sem qualquer tipo de randomização. Ao contrário, os itens samplex permitem que você especifique como randomizar seu conteúdo. A próxima célula usa a função generate_boxing_pass_manager() para agrupar os gates e medições do circuito em caixas e adicionar uma anotação de twirling a cada caixa. Em seguida, gera um circuito modelo e um par samplex usando a função build().

Executar este SamplexItem é a segunda tarefa que o programa realizará.

Veja a documentação da API do Samplomatic para detalhes completos sobre samplex e seus argumentos. Veja o Guia do Transpiler do Samplomatic para informações sobre como usar a função generate_boxing_pass_manager().

# 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())
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 é iterável. Ele contém uma entrada por QuantumProgramItem de entrada na mesma ordem dos itens de entrada. Cada um desses itens de saída é um dicionário onde as chaves são strings que correspondem aos nomes dos registradores clássicos nos circuitos de entrada (entre outros), portanto 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 para o exemplo anterior contém estes itens:

Resultado do CircuitItem​

O primeiro item contém os resultados da execução da primeira tarefa (um CircuitItem) no programa. Ele contém uma única chave, meas, que é o nome do registrador clássico no circuito de entrada. O valor dessa chave mapeia para um np.ndarray de forma (parameter sets, shots, register bits), que é (10, 1024, 3) para o exemplo acima.

O código a seguir ilustra como acessar estas 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}")
Result shape: (10, 1024, 3)

O eixo mais à direita de result_0 percorre os bits clássicos em ordem little-endian. Assim, o índice 0 contém o resultado do bit clássico 0, o bit menos significativo.

Resultado do SamplexItem​

O segundo item contém os resultados da execução da segunda tarefa (um SamplexItem) no programa. Este item contém múltiplas chaves. A chave meas, que é o nome do registrador clássico do circuito de entrada, mapeia para a matriz de resultados desse registrador. Esta 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 chave measurement_flips.meas, que são as correções de bit-flip para desfazer o twirling de medição para o registrador meas. Esta forma de saída será (28, 10, 1, 3) para o nosso exemplo, pois apenas um shot é necessário para realizar o bit-flip.

Observe que o eixo mais à direita de result_1 percorre os bits clássicos em ordem little-endian.

# 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_1
Result shape: (28, 10, 1024, 3)
Bit-flip corrections shape: (28, 10, 1, 3)

Veja os execution spans​

Uma única chamada a Executor.run() envia um único job. No lado do servidor, porém, o serviço não executa necessariamente todas as configurações do programa quântico ao mesmo tempo. Em vez disso, ele agrupa os circuitos vinculados dos itens do programa em chunks e executa um chunk por vez na QPU. Você não precisa dividir seu programa nem enviar jobs adicionais; a divisão em chunks acontece automaticamente dentro de um único job. O atributo metadata do resultado contém informações de tempo sobre esses chunks em result.metadata.chunk_timing. Esses carimbos de data e hora são os horários reais de início e término de cada chunk na QPU. Você pode usar essas informações para ver quando dados específicos foram coletados na QPU, para entender como o Executor distribuiu o trabalho ou para diagnosticar problemas de desempenho.

chunk_timing é uma lista que contém um ChunkSpan para cada chunk executado. Cada ChunkSpan tem os seguintes atributos:

  • start e stop: Os carimbos de data e hora UTC em que o chunk começou e parou de ser executado na QPU.

  • parts: Uma lista de objetos ChunkPart que descrevem quais itens do programa contribuíram para o chunk. Cada ChunkPart contém o seguinte:

    • idx_item: O índice do item do programa, na ordem em que os itens foram anexados ao QuantumProgram.

    • size: O número de configurações (elementos do formato do item) desse item que foram executadas neste chunk. Por exemplo, o SamplexItem do exemplo anterior tem formato (28, 10), portanto tem 280 configurações no total.

O código a seguir imprime a duração de cada chunk e o conteúdo de cada chunk para o job enviado anteriormente:

for idx, span in enumerate(result.metadata.chunk_timing):
duration = (span.stop - span.start).total_seconds()
print(f"Chunk {idx}: {span.start} to {span.stop} ({duration:.3f} s)")
for part in span.parts:
print(f" Item {part.idx_item}: {part.size} configurations")
Chunk 0: 2026-11-06 01:47:46.009718+00:00 to 2026-11-06 01:49:05.577720+00:00 (79.568 s)
Item 0: 10 configurations
Item 1: 280 configurations

A propriedade QuantumProgramResult.timing encapsula a mesma lista em um objeto ChunkTiming, que também informa dados globais sobre o conjunto de chunks:

timing = result.timing

print("Number of chunks:", len(timing))
print(" Start of the first chunk:", timing.start)
print(" End of the last chunk:", timing.stop)
print(" Total duration (s):", timing.duration)
Number of chunks: 1
Start of the first chunk: 2026-11-06 01:47:46.009718+00:00
End of the last chunk: 2026-11-06 01:49:05.577720+00:00
Total duration (s): 79.568002

Visualize os execution spans​

Use a função draw_chunk_timings para visualizar os tempos dos chunks em um gráfico de barras interativo. O eixo x é o tempo, e cada chunk é desenhado como um segmento horizontal que abrange seus horários de início e término. O eixo y mostra o número cumulativo de configurações concluídas (ou a porcentagem da carga de trabalho total, se você definir normalize_y=True). Passe o cursor sobre um segmento para ver os carimbos de data e hora, a duração e as partes desse chunk.

A função retorna uma figura plotly, que você pode exibir diretamente, salvar em um arquivo ou ambos. Ela aceita a lista chunk_timing ou um objeto ChunkTiming. Você pode passar vários deles, por exemplo de jobs diferentes, para desenhá-los no mesmo gráfico. Defina common_start=True para alinhá-los a um horário de início comum, para fins de comparação.

Plotly necessário

draw_chunk_timings requer o plotly. Você pode instalá-lo executando pip install plotly.

Lacunas entre os chunks

A visualização pode mostrar lacunas entre as execuções dos chunks. Todos os jobs enviados a uma QPU real passam por algum pré-processamento clássico, como a tradução em pulsos que podem ser enviados ao hardware. Como esse pré-processamento pode ser feito em paralelo, o serviço retira da fila e inicia vários jobs simultaneamente. A QPU, no entanto, só consegue processar uma unidade de trabalho por vez. Portanto, se um job gera vários chunks, seus chunks podem ser intercalados com os de jobs de outros usuários, e cada chunk pode ter que esperar sua vez na QPU. É por isso que podem aparecer lacunas entre os chunks.

from qiskit_ibm_runtime.visualization import draw_chunk_timings

fig = draw_chunk_timings(result.timing, names="Executor job")

# Alternatively, call the `draw()` method of the ChunkTiming object
# fig = result.timing.draw()

# Uncomment the following line to display the figure
# fig.show(renderer="notebook")

# Save to a file
# fig.write_html("chunk_timings.html")

Próximos passos​

Recomendações