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.
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
AerSampleremqiskit-aerpara 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 noqiskit-ibm-runtimev0.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).
- 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
-
Mapeamento conceitual
A tabela a seguir demonstra como os conceitos do Sampler se mapeiam para o Executor.
| Concept | Sampler | Executor |
|---|---|---|
| Import | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Input | Lista de PUBs (tuplas) | Um QuantumProgram de objetos QuantumProgramItem |
| Circuit and parameters | tupla (circuit, params, shots) | program.append_circuit_item(circuit, circuit_arguments=...) |
| Twirling | TwirlingOptions | Explícito através de boxes anotados e um samplex (append_samplex_item) |
| Run call | sampler.run([pub, ...]) | executor.run(program) |
| Result type | PrimitiveResult de SamplerPubResult | QuantumProgramResult (iterável) |
| Access data | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| Manage noise | Opções integradas | Deve ser composto manualmente (annotations, samplex, NoiseLearnerV3) |
Visão geral das etapas de migração
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]
- O
qiskit-ibm-runtimev0.48.0 é recomendado porque adiciona a opçãomeas_level = "both"e o grupo de twirlinglocal_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 umCircuitItem, 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 umsamplexItem, 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 emExecutorOptions.
Examples:
| Sampler | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(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:
-
environment(EnvironmentOptions) -
execution(ExecutionOptions): Contém menos opções do que o Sampler. Por exemplo, não há uma opçãomeas_typeno Executor.
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.
| Task | Sampler | Executor |
|---|---|---|
| Get register data | result[0].data.meas | result[0]["meas"] |
| Data type | BitArray | np.ndarray |
| Counts dictionary | result[0].data.meas.get_counts() | Pós-processar o array manualmente |
| Multiple registers | result[0].data.<name> por registrador | result[0]["<name>"] por registrador |
| CircuitItem array shape | - | (parameter_sets, shots, register_bits) |
| SamplexItem array shape | - | (randomizations, parameter_sets, shots, register_bits) |
| Undo measurement twirling | Automático | result[i]["measurement_flips.<name>"] + XOR |
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"]