Pular para o conteúdo principal

Migrar do Sampler para o Executor

Este guia descreve como migrar cargas de trabalho de amostragem quântica da primitiva Sampler do IBM Quantum® para a primitiva Executor.

Beta release

A primitiva Executor faz parte do modelo de execução dirigida. Todos os componentes do modelo de execução dirigida estão atualmente em beta e podem não ser estáveis. Você está convidado a testá-los e fornecer feedback abrindo um issue nos repositórios do GitHub Samplomatic ou qiskit-ibm-runtime.

Você deve migrar?

Nem todos devem migrar do Sampler para o Executor. Há muitas diferenças entre as primitivas, mas a orientação a seguir pode ajudá-lo a decidir se deve migrar:

Migre para o Executor se você é um cientista de informação quântica que executa experimentos em escala utilitária e precisa de controle refinado e reproduzível sobre técnicas como Pauli twirling, aprendizado e injeção de modelo de ruído, e mudanças de base — ou que precisa de uma das capacidades adicionais fornecidas pelo Executor.

Continue usando o Sampler se você quiser uma interface simples e de alto nível e quiser que a primitiva gerencie a supressão e mitigação de erros para você.

Limitações e ressalvas

Como o Executor e o modelo de execução dirigida estão em beta, observe o seguinte antes de decidir migrar:

  • Ainda sem suporte a simulador: Diferente do Sampler, que tem uma implementação AerSampler em qiskit-aer para simulação local, atualmente não há um backend de simulador para o Executor. Espera-se que o suporte a simulador chegue em breve. Enquanto isso, você ainda pode inspecionar e amostrar o circuito de template localmente para validar seu fluxo de trabalho antes de enviá-lo para o hardware.

  • Este guia aborda apenas o Sampler, não o Estimator. Migrar do Estimator para o Executor é consideravelmente mais complexo do que migrar do Sampler, porque o Estimator calcula valores esperados em vez de retornar amostras brutas. Reproduzir o comportamento do Estimator com o Executor requer processamento adicional. Funções utilitárias para ajudar na migração do Estimator para o Executor ainda estão em desenvolvimento, então este guia descreve intencionalmente apenas o fluxo de trabalho do Sampler.

Principais diferenças entre Executor e Sampler

O Sampler e o Executor ambos amostram os registradores de saída de circuitos quânticos, mas eles são voltados para usuários diferentes:

  • Sampler é uma abstração de alto nível. Ele tem as seguintes características:

    • Ele tem supressão de erros integrada (desacoplamento dinâmico e twirling).

    • Ele toma decisões implícitas por você.

    • Ele é projetado para que os desenvolvedores de algoritmos possam se concentrar na inovação em vez da conversão de dados.

  • Executor faz parte do modelo de execução dirigida. Ele difere do Sampler de várias maneiras e tem as seguintes características:

    • Ele não tem supressão ou mitigação de erros integrada. Em vez disso, você captura sua intenção de design no lado do cliente (usando anotações de circuito e um samplex), e a geração custosa de variantes de circuito é transferida para o lado do servidor.

    • Ele não toma decisões implícitas. Ele segue exatamente suas diretivas, oferecendo controle total e transparência.

    • O Executor e o Samplomatic juntos expõem capacidades adicionais que o Sampler não oferece, incluindo (mas não se limitando) às seguintes:

      • Mais grupos de twirling: o Samplomatic permite que você escolha qual grupo de twirling aplicar por box, em vez de ser limitado à estratégia única que o Sampler aplica por você. Ele também suporta grupos de twirling além do Pauli, como o grupo de twirling "local_c1".
      • Medições kerneled e classified juntas: definir QuantumProgram.meas_level = "both" (adicionado no qiskit-ibm-runtime v0.48.0) solicita que tanto medições classified quanto kerneled estejam presentes nos resultados, em vez de escolher um único tipo de medição por job.
      • Twirling para circuitos com portas fracionárias: o Executor pode aplicar twirling a circuitos que contêm portas fracionárias.
      • Mitigação de erros refinada e componível: por exemplo, escolher quais camadas do circuito mitigar e ajustar as taxas de ruído injetadas no circuito.
      Notes
      • Espera-se que futuras novas capacidades sejam lançadas primeiro para o Executor e podem não ser portadas para o Sampler. Se você depende do acesso aos recursos mais recentes, o Executor é a escolha mais preparada para o futuro.
      • O pacote base do Qiskit ainda não fornece uma classe base para a primitiva Executor (mas fornece para o SamplerV2).

Mapeamento conceitual

A tabela a seguir demonstra como os conceitos do Sampler se mapeiam para o Executor.

ConceptSamplerExecutor
Importfrom qiskit_ibm_runtime import SamplerV2from qiskit_ibm_runtime import Executor
InputLista de PUBs (tuplas)Um QuantumProgram de objetos QuantumProgramItem
Circuit and parameterstupla (circuit, params, shots)program.append_circuit_item(circuit, circuit_arguments=...)
TwirlingTwirlingOptionsExplícito através de boxes anotados e um samplex (append_samplex_item)
Run callsampler.run([pub, ...])executor.run(program)
Result typePrimitiveResult de SamplerPubResultQuantumProgramResult (iterável)
Access dataresult[0].data.<register> (BitArray)result[0]["<register>"] (np.ndarray)
Manage noiseOpções integradasDeve ser composto manualmente (annotations, samplex, NoiseLearnerV3)

Visão geral das etapas de migração

  1. Instale o Samplomatic.

  2. Altere os imports.

  3. Substitua as tuplas PUB.

  4. Altere como os shots são expressos.

  5. Atualize outras opções conforme necessário.

  6. Atualize o comando run.

  7. Atualize a análise de resultados.

  8. Desfaça o twirling.

Etapa 1. Instale os pacotes necessários

O Executor e o modelo de execução dirigida exigem o pacote samplomatic:

pip install qiskit qiskit-ibm-runtime samplomatic

# For visualization support:
# pip install samplomatic[vis]
Version notes
  • O qiskit-ibm-runtime v0.48.0 é recomendado porque adiciona a opção meas_level = "both" e o grupo de twirling local_c1.
  • qiskit >= 2.3.0 é necessário.
  • samplomatic >= 0.18.0 é necessário.

Etapa 2. Altere os imports

Sampler:

from qiskit_ibm_runtime import SamplerV2 as Sampler

Executor:

from qiskit_ibm_runtime import Executor, QuantumProgram

Etapa 3. Substitua as tuplas PUB por um QuantumProgram

Em vez de passar uma lista de tuplas (PUBs), ao usar o Executor, você constrói um QuantumProgram e anexa a ele itens.

Um QuantumProgram aceita itens do tipo circuit e samplex:

  • append_circuit_item: Anexa um CircuitItem, que é um circuito e (opcionalmente) seus valores de parâmetro. Ele é executado como está, sem nenhuma randomização.

    Use isso quando você apenas quiser amostrar um circuito, exatamente como o Sampler faria com um PUB que não tem twirling; por exemplo, ao enviar um job de amostragem simples, ou quando você já incluiu manualmente quaisquer variantes que deseja.

  • append_samplex_item: Anexa um samplexItem, que é um circuito de template mais um samplex que gera conjuntos de parâmetros randomizados no lado do servidor.

    Use isso quando você quiser que o conteúdo do circuito seja randomizado. O caso principal é com twirling (de porta ou de medição) ou injeção de ruído. Essa capacidade substitui o twirling integrado do Sampler.

Um único QuantumProgram pode aceitar ambos os tipos de item; cada item anexado é executado como uma tarefa independente e produz sua própria entrada nos resultados. Em geral, use append_circuit_item quando seu circuito não precisar ser randomizado. Caso contrário, use append_samplex_item.

As próximas seções mostram cada um por vez: circuitos parametrizados que usam append_circuit_item, e migração de twirling usando append_samplex_item.

Nos exemplos de código a seguir, isa_circuit se refere ao circuito que foi transpilado para estar em conformidade com a Instruction Set Architecture (ISA) do backend alvo. Esse isa_circuit contém dois parâmetros.

Etapa 3a. Migre circuitos parametrizados

Com o Sampler, os valores de parâmetro são o segundo elemento da tupla PUB. Com o Executor, passe-os como circuit_arguments para append_circuit_item.

Sampler:

params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)

Executor

program = QuantumProgram(shots=1024)
program.append_circuit_item(
isa_circuit,
circuit_arguments=np.random.rand(10, circuit.num_parameters), # 10 sets
)

# CircuitItem result shape: (parameter_sets, shots, register_bits) -> (10, 1024, 2)
result_0 = result[0]["meas"]

Etapa 3b. Migre o twirling integrado para anotações explícitas

Esta é a mudança mais significativa. O Sampler aplica twirling para você usando opções. Com o Executor, você declara essa intenção explicitamente usando boxes anotados e um samplex (do Samplomatic).

Sampler (twirling usando opções):

sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True

Executor (twirling usando boxes e um samplex):

from samplomatic import build
from samplomatic.transpiler import generate_boxing_pass_manager

# 1. Group gates and measurements into annotated boxes with twirling annotations
boxes_pm = generate_boxing_pass_manager(
enable_gates=True, # gate twirling
enable_measures=True, # measurement twirling
)
boxed_circuit = boxes_pm.run(isa_circuit)

# 2. Build the (template circuit, samplex) pair.
# The template circuit's single-qubit gates are replaced by parameterized gates;
# the samplex encodes how to generate the randomized parameters at runtime.
template_circuit, samplex = build(boxed_circuit)

# 3. Append as a samplex item, specifying the number of randomizations
program = QuantumProgram(shots=1024)
program.append_samplex_item(
template_circuit,
samplex=samplex,
samplex_arguments={
"parameter_values": np.random.rand(10, 2), # original circuit params
},
shape=(28, 10), # 28 randomizations x 10 parameter sets
)

Como o circuito de template e o samplex são construídos no lado do cliente, você pode inspecioná-los e amostrá-los localmente para verificar a saída antes de enviar qualquer coisa ao hardware.

Verificação: amostrar o circuito de template localmente

Você pode extrair randomizações do samplex e vinculá-las ao circuito de template para confirmar que o samplex está produzindo os valores de parâmetro que você espera. Os valores de parâmetro retornados por samplex.sample são diretamente compatíveis com os parâmetros do circuito de template.

# Check which inputs the samplex requires (for the twirling example above,
# this is just the original circuit's parameter values).
print(samplex.inputs())

# Bind the required inputs, then draw a few randomizations locally.
inputs = samplex.inputs().bind(
parameter_values=np.random.rand(2), # one set of the original circuit's params
)
outputs = samplex.sample(inputs, num_randomizations=3)

# Assign one randomization's parameter values to the template circuit and inspect it.
bound_template = template_circuit.assign_parameters(outputs["parameter_values"][0])
bound_template.draw("mpl", idle_wires=False)

Para ir além, você pode verificar se cada randomização é logicamente equivalente ao circuito original, por exemplo, convertendo ambos em objetos Operator e comparando suas implementações unitárias (depois de considerar as correções outputs["measurement_flips.<register>"] que desfazem o twirling de medição), ou comparando valores esperados de uma execução local do StatevectorSampler ou StatevectorEstimator. Veja o guia do Samplomatic Samplex inputs and outputs para um passo a passo completo.

Etapa 4. Altere como os shots são solicitados

Mova os shots do PUB para QuantumProgram(shots=...). No Executor, shots se aplica ao job inteiro. Envie múltiplos jobs se precisar de contagens de shots diferentes.

Sampler:

# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])

Executor:

# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

Etapa 5. Atualize as opções conforme necessário

Há menos opções disponíveis para o Executor do que para o Sampler, porque as escolhas de mitigação de erros agora residem nas suas anotações e samplex em vez de opções.

Também há uma diferença estrutural em onde as configurações residem.

  • Com o Sampler, tudo, incluindo escolhas que afetam o pós-processamento de resultados, é configurado nas opções da primitiva ou no PUB.

  • Com o Executor, escolhas que afetam como os resultados do job são formatados e pós-processados são definidas no QuantumProgram, não em ExecutorOptions.

Examples:

SamplerExecutor
shotsQuantumProgram(shots=...)
meas_typeQuantumProgram(meas_level=...)

ExecutorOptions contém apenas configurações de execução e de ambiente de nível mais baixo que não mudam a estrutura dos dados retornados. Tem três grupos de nível superior:

Notavelmente, as opções twirling e dynamical_decoupling existem no Sampler, mas não no Executor. Em vez disso, esses valores de opção são expressos através do modelo de execução dirigida.

Example:

from qiskit_ibm_runtime import Executor, ExecutorOptions

options = ExecutorOptions(
environment={"log_level": "INFO"},
execution={"init_qubits": True},
)
# or mutate after construction:
options = ExecutorOptions()
options.environment.log_level = "INFO"
options.execution.init_qubits = True

executor = Executor(mode=backend, options=options)

Etapa 6. Atualize o comando run

A entrada para um job do Executor é o programa, em vez de PUBs.

Sampler:

# Submit a job
sampler.run([(isa_circuit, parameter_values)])

Executor:

# Submit a job
executor.run(program)

Etapa 7. Altere como você acessa os resultados

No Executor, os resultados são arrays NumPy, não objetos BitArray. Use a string do nome como índice (result[0]["meas"]) e obtenha um np.ndarray de volta. Não há necessidade de lembrar o caminho de atributo .data.<register>.

Para atualizar do Sampler para o Executor, altere result[i].data.<reg> (BitArray) para result[i]["<reg>"] (np.ndarray), então reescreva o pós-processamento baseado em get_counts como operações NumPy.

TaskSamplerExecutor
Get register dataresult[0].data.measresult[0]["meas"]
Data typeBitArraynp.ndarray
Counts dictionaryresult[0].data.meas.get_counts()Pós-processar o array manualmente
Multiple registersresult[0].data.<name> por registradorresult[0]["<name>"] por registrador
CircuitItem array shape-(parameter_sets, shots, register_bits)
SamplexItem array shape-(randomizations, parameter_sets, shots, register_bits)
Undo measurement twirlingAutomáticoresult[i]["measurement_flips.<name>"] + XOR
nota

O BitArray do Sampler oferece auxiliares (get_counts, slice_bits, slice_shots, expectation_values e máscaras de pós-seleção). O Executor retorna arrays NumPy brutos para que você possa realizar esse pós-processamento com operações padrão do NumPy.

Etapa 8. Trate resultados com twirling (correções de bit-flip)

Quando você aplica twirling de medição através de um SamplexItem, o Executor retorna as medições brutas (com twirling) mais as correções de bit-flip necessárias para desfazer o twirling. Você precisa aplicá-las manualmente; nada é corrigido implicitamente.

Ao usar o Executor, desfaça o twirling explicitamente usando as correções measurement_flips.<reg> e um XOR, como mostrado no exemplo a seguir:

# SamplexItem result shape: (randomizations, parameter_sets, shots, register_bits)
result_1 = result[1]["meas"] # example: (28, 10, 1024, 2)

# Bit-flip corrections to undo measurement twirling
flips_1 = result[1]["measurement_flips.meas"] # example: (28, 10, 1, 2)

# Undo the twirling through classical XOR (broadcasts over the shots axis)
unflipped_result_1 = result_1 ^ flips_1

Não há etapa equivalente no Sampler porque ele desfaz o twirling para você.

Exemplo completo: migre um job de amostragem básico

Sampler

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

# 1. Account + backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

# 3. Transpile to ISA
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)

# 4. Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit,)], shots=25)
result = job.result()

# 5. Access results: a BitArray keyed by register name
counts = result[0].data.meas.get_counts()

Executor

import numpy as np
from qiskit.circuit import QuantumCircuit
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, Executor
from qiskit_ibm_runtime.quantum_program import QuantumProgram

# 1. Account + backend (unchanged)
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# 2. Circuit (unchanged)
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.h(1)
circuit.cz(0, 1)
circuit.h(1)
circuit.measure_all()

# 3. Transpile to ISA (unchanged)
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)

# 4. Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)

# 5. Run
executor = Executor(mode=backend)
job = executor.run(program)
result = job.result()

# 6. Access results: a plain np.ndarray keyed by register name
# shape = (shots, register_bits)
meas = result[0]["meas"]

Próximos passos