Migrar do Sampler para o Executor
Este guia descreve como migrar cargas de trabalho de amostragem quântica da primitiva “ IBM Quantum® ” do Sampler para a primitiva do Executor.
A primitiva Executor faz parte do modelo de execução direcionada. Todos os componentes do modelo de execução direcionada estão atualmente em fase beta e podem não estar estáveis. Você está convidado a testá-los e enviar seus comentários, abrindo uma solicitação nos repositórios do Samplomatic ou do qiskit-ibm-runtime GitHub.
Você deveria migrar?
Nem todos devem migrar do Sampler para o Executor. Existem muitas diferenças entre as primitivas, mas as orientações a seguir podem ajudá-lo a decidir se deve migrar:
Migre para o Executor se você for um cientista da informação quântica que realiza experimentos em escala de utilidade e precisa de um controle detalhado e reproduzível sobre técnicas como o “Pauli twirling”, o aprendizado e a injeção de modelos de ruído e as mudanças de base — ou se precisar de um dos recursos adicionais oferecidos pelo Executor.
Continue usando o Sampler se desejar uma interface simples e de alto nível e quiser que a primitiva cuide da supressão e mitigação de erros para você.
Limitações e ressalvas
Como o Executor e o modelo de execução direcionada estão em fase beta, observe o seguinte antes de decidir migrar:
- Ainda não há suporte a simulador : ao contrário do Sampler, que possui uma
AerSamplerimplementaçãoqiskit-aerpara simulação local, atualmente não há um backend de simulador para o Executor. Simulador A previsão é que o suporte seja disponibilizado em breve. Enquanto isso, você ainda pode examinar e testar o circuito modelo localmente para validar seu fluxo de trabalho antes de enviá-lo para a produção de hardware. - Este guia aborda apenas o Sampler, e não o Estimator. A migração do Estimator para o Executor é consideravelmente mais complexa do que a migração do Sampler, pois o Estimator calcula valores esperados, em vez de retornar amostras brutas. Reproduzir o comportamento do Estimator com o Executor requer um pós-processamento adicional. As funções utilitárias para auxiliar na migração do Estimator para o Executor ainda estão em desenvolvimento; portanto, este guia descreve intencionalmente apenas o fluxo de trabalho do Sampler.
Principais diferenças entre o Executor e o Sampler
Tanto o Sampler quanto o Executor fazem a amostragem dos registros de saída dos circuitos quânticos, mas se destinam a usuários diferentes:
- O Sampler é uma abstração de alto nível. Possui as seguintes características:
- Possui supressão de erros integrada (desacoplamento dinâmico e twirling).
- Ele toma decisões implícitas por você.
- Ele foi projetado para que os desenvolvedores de algoritmos possam se concentrar na inovação, em vez de nos dados. conversão.
- O Executor faz parte do modelo de execução direcionada. Ele difere do Sampler em muitos aspectos e apresenta as seguintes características:
-
Não possui nenhum mecanismo integrado de supressão ou mitigação de erros. Em vez disso, você define sua intenção de projeto no lado do cliente (usando anotações de circuito e um samplex ), e a geração, que exige muitos recursos, de variantes de circuito é transferida para o lado do servidor.
-
Ele não toma nenhuma decisão implícita. Ele segue suas instruções à risca, proporcionando total controle e transparência.
-
O Executor e o Samplomatic, em conjunto, oferecem recursos adicionais que o Sampler não oferece, incluindo (mas não se limitando a) os seguintes:
- Mais grupos de rotação: o Samplomatic permite que você escolha qual grupo de rotação aplicar
por caixa, em vez de ficar limitado à única estratégia que o Sampler aplica automaticamente. Ele também oferece suporte a grupos de giro que não sejam o de Pauli, como o grupo de giro
"local_c1". - Medidas com kernel e classificadas em conjunto: A configuração
QuantumProgram.meas_level = "both"(adicionada em v0.48.0qiskit-ibm-runtime) solicita que tanto as medidas classificadas quanto as com kernel estejam presentes nos resultados, em vez de selecionar um único tipo de medida por tarefa. - Twirling para circuitos com portas fracionárias: o Executor pode aplicar o twirling a circuitos que contenham portas fracionárias.
- Mitigação de erros com granularidade fina e combinável: por exemplo, escolher quais camadas do circuito devem ser mitigadas e ajustar as taxas de ruído injetadas no circuito.
Notas- Espera-se que os novos recursos futuros sejam lançados primeiro no Executor e talvez não sejam portados para o Sampler. Se você depende do acesso aos recursos mais recentes, o Executor é a opção mais preparada para o futuro.
- O pacote básico do Qiskit ainda não oferece
uma classe base para a primitiva Executor (mas oferece para
SamplerV2).
- Mais grupos de rotação: o Samplomatic permite que você escolha qual grupo de rotação aplicar
por caixa, em vez de ficar limitado à única estratégia que o Sampler aplica automaticamente. Ele também oferece suporte a grupos de giro que não sejam o de Pauli, como o grupo de giro
-
Mapeamento conceitual
A tabela a seguir mostra como os conceitos do Sampler se correspondem aos do Executor.
Conceito | Amostra | Executor |
|---|---|---|
| Importar | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Entrada | Lista de PUBs (tuplas) | Um conjunto de QuantumProgram objetos QuantumProgramItem |
| Circuito e parâmetros | (circuit, params, shots) tupla | program.append_circuit_item(circuit, circuit_arguments=...) |
| Giro | TwirlingOptions | Explicado por meio de caixas anotadas e um samplex (append_samplex_item) |
| Executar chamada | sampler.run([pub, ...]) | executor.run(program) |
| Tipo de resultado | PrimitiveResult de SamplerPubResult | QuantumProgramResult (iterável) |
| Dados do Acesso | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| Gerenciar o ruído | Opções integradas | Deve ser composto manualmente (anotações, samplex, NoiseLearnerV3) |
Visão geral das etapas da migração
- Instale o Samplomatic.
- Altere as importações.
- Substituir as tuplas de PUB.
- Altere a forma como as jogadas são descritas.
- Atualize as demais opções conforme necessário.
- Atualize o comando
run. - Atualizar a análise dos resultados.
- Desfazer o giro.
Etapa 1. Instale os pacotes necessários
O Executor e o modelo de execução direcionada exigem o pacote samplomatic :
pip install qiskit qiskit-ibm-runtime samplomatic
# For visualization support:
# pip install samplomatic[vis]qiskit-ibm-runtimev0.48.0 é recomendado porque adiciona a opçãomeas_level = "both"e o grupo de giroslocal_c1.qiskit >= 2.3.0é necessário.samplomatic >= 0.18.0é necessário.
Etapa 2. Altere as importações
Amostra:
from qiskit_ibm_runtime import SamplerV2 as SamplerExecutor:
from qiskit_ibm_runtime import Executor, QuantumProgramEtapa 3. Substitua as tuplas PUB por um QuantumProgram
Em vez de passar uma lista de tuplas (PUBs), ao usar o Executor, você cria uma e QuantumProgram acrescenta itens a ela.
A QuantumProgram aceita itens de circuito e itens Samplex :
-
append_circuit_item: Acrescenta umCircuitItem, que é um circuito e (opcionalmente) seus valores de parâmetros. É executado tal como está, sem qualquer aleatorização.Use isso quando quiser apenas fazer uma amostragem de um circuito, exatamente como o Sampler faria com um “ PUB ” sem twirling; por exemplo, ao enviar um trabalho de amostragem simples ou quando você já tiver incluído manualmente quaisquer variantes desejadas.
-
append_samplex_item: Acrescenta umsamplexItem, que é um circuito modelo mais um samplex que gera conjuntos de parâmetros aleatórios no lado do servidor.Use isso quando quiser que o conteúdo do circuito seja aleatorizado. O caso principal envolve giros (de porta ou de medição) ou injeção de ruído. Esse recurso substitui a função de rotação integrada do Sampler.
Um único QuantumProgram pode aceitar ambos os tipos de item; cada item anexado é executado como uma
tarefa independente e gera sua própria entrada nos resultados. Em geral, use append_circuit_item quando seu circuito não precisar ser aleatorizado. Caso contrário, use append_samplex_item.
As próximas seções mostram, uma a uma: circuitos parametrizados que utilizam
append_circuit_iteme a migração do twirling por meio de append_samplex_item.
Nos exemplos de código a seguir, isa_circuit refere-se ao circuito que foi transpilado para se adequar à Arquitetura de Conjunto de Instruções (ISA) do backend de destino. Isso contém isa_circuit dois parâmetros.
Etapa 3a e. Migrar circuitos parametrizados
No Sampler, os valores dos parâmetros são o segundo elemento da tupla PUB. Com o Executor,
passe-os como circuit_arguments para append_circuit_item.
Amostra:
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 e. Migrar o “twirling” integrado para anotações explícitas
Essa é a mudança mais significativa. O Sampler aplica o efeito de giro para você por meio de opções. Com o Executor, você declara essa intenção explicitamente usando caixas anotadas e um samplex (do Samplomatic ).
Exemplos (girando com as opções):
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = TrueExecutor (giros com caixas 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 modelo e o samplex são criados no lado do cliente, você pode inspecioná-los e testá-los localmente para verificar o resultado antes de enviar qualquer coisa para o hardware.
Verificação: Teste o circuito modelo localmente
Você pode gerar amostras aleatórias do samplex e vinculá-las ao circuito modelo
para confirmar se o samplex está produzindo os valores de parâmetros esperados.
Os valores dos parâmetros retornados por samplex.sample são diretamente compatíveis com os
parâmetros do circuito modelo.
# 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 aprofundar, é possível verificar se cada randomização é logicamente equivalente ao
circuito original, por exemplo, convertendo ambos em objetos Operator e comparando suas implementações unitárias (após
levar em conta as outputs["measurement_flips.<register>"] correções que revertem
o efeito da medição), ou comparando os valores esperados de uma execução local
StatevectorSampler ou de uma StatevectorEstimator execução. Consulte o guia
de entradas e saídas
do Samplomatic Samplex para obter um passo a passo completo.
Etapa 4. Alterar a forma como os pedidos de bebidas são feitos
Mova as fotos do PUB para QuantumProgram(shots=...). No Executor, isso se aplica shots a todo o trabalho. Envie vários trabalhos caso precise de quantidades diferentes de fotos.
Amostra:
# 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
O Executor tem menos opções disponíveis do que o Sampler, pois
as opções de mitigação de erros agora estão nas suas anotações e no samplex, em vez de nas opções.
Há também uma diferença estrutural quanto ao local onde as configurações estão armazenadas.
-
Com o Sampler, tudo, inclusive as opções que afetam o pós-processamento dos resultados, é configurado nas opções da primitiva ou no arquivo “ PUB ”.
-
Com o Executor, as opções que afetam a forma como os resultados do trabalho são moldados e pós-processados são definidas no
QuantumProgram, e não noExecutorOptions.
Exemplos:
Amostra | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions contém apenas configurações de execução e de ambiente de nível inferior
que não alteram a estrutura dos dados retornados. Possui 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á a opção “Executormeas_type”.experimental
Vale destacar que as opções dynamical_decoupling e twirling existem no Sampler, mas não no Executor. Em vez disso, esses valores de opção são expressos por meio do modelo de execução direcionada.
Exemplo:
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 uma tarefa do Executor é o programa, em vez de PUBs.
Amostra:
# Submit a job
sampler.run([(isa_circuit, parameter_values)])Executor:
# Submit a job
executor.run(program)Etapa 7. Altere a forma como você acessa os resultados
No Executor, os resultados são matrizes do tipo NumPy, e não BitArray objetos. Use a string de nome como índice (result[0]["meas"]) e receba um de volta np.ndarray . Não é preciso lembrar o caminho do atributo .data.<register> .
Para atualizar do Sampler para o Executor, altere result[i].data.<reg> (BitArray) para result[i]["<reg>"] (np.ndarray), e, em seguida, reescreva o pós-processamento get_countsbaseado em como operações d NumPy.
Tarefa | Amostra | Executor |
|---|---|---|
| Obter dados do registro | result[0].data.meas | result[0]["meas"] |
| Tipo de dados | BitArray | np.ndarray |
| Dicionário de contagens | result[0].data.meas.get_counts() | Fazer o pós-processamento da matriz manualmente |
| Vários registros | result[0].data.<name> por registro | result[0]["<name>"] por registro |
| CircuitItem forma da matriz | - | (parameter_sets, shots, register_bits) |
| SamplexItem forma da matriz | - | (randomizations, parameter_sets, shots, register_bits) |
| Desfazer a rotação da medição | Automático | result[i]["measurement_flips.<name>"] + XOR |
O Sampler's BitArray oferece auxiliares (get_counts, slice_bits, slice_shots, expectation_values, e máscaras pós-seleção). O Executor retorna matrizes “ NumPy ” em formato bruto, para que você possa realizar esse pós-processamento com operações padrão de “ NumPy ”.
Etapa 8. Tratar resultados de rotação (correções de inversão de bits)
Quando você aplica a rotação de medição por meio de um SamplexItem, o Executor retorna as medições brutas
(rotacionadas) mais as correções de inversão de bits necessárias para reverter a rotação.
É preciso aplicá-las manualmente; nada é corrigido automaticamente.
Ao usar o Executor, desfaça o twirling explicitamente utilizando as correções measurement_flips.<reg> e uma operação XOR, conforme 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_1Não há uma etapa equivalente no Sampler, pois ele desativa automaticamente o efeito de giro para você.
Exemplo completo: Migrar um trabalho básico de amostragem
Amostra
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"]