Migrar de Sampler a Executor
Esta guía describe cómo mover cargas de trabajo de muestreo cuántico de la primitiva Sampler de IBM Quantum® a la primitiva Executor.
La primitiva Executor forma parte del modelo de ejecución dirigida. Todos los componentes del modelo de ejecución dirigida están actualmente en beta y podrían no ser estables. Te invitamos a probarlos y a compartir tus comentarios abriendo un issue en los repositorios de GitHub Samplomatic o qiskit-ibm-runtime.
¿Deberías migrar?
No todos deberían migrar de Sampler a Executor. Hay muchas diferencias entre las primitivas, pero la siguiente guía puede ayudarte a decidir si migrar:
Migra a Executor si eres un científico de información cuántica que ejecuta experimentos a escala de utilidad y necesita un control fino y reproducible sobre técnicas como el twirling de Pauli, el aprendizaje e inyección de modelos de ruido, y cambios de base — o si necesitas una de las capacidades adicionales que ofrece Executor.
Sigue usando Sampler si quieres una interfaz simple, de alto nivel, y quieres que la primitiva gestione por ti la supresión y mitigación de errores.
Limitaciones y advertencias
Debido a que Executor y el modelo de ejecución dirigida están en beta, ten en cuenta lo siguiente antes de decidir migrar:
-
Aún no hay soporte de simulador: A diferencia de Sampler, que tiene una implementación de
AerSamplerenqiskit-aerpara simulación local, actualmente no hay un backend de simulador para Executor. Se espera que la compatibilidad con simuladores llegue pronto. Mientras tanto, aún puedes inspeccionar y muestrear el circuito de plantilla localmente para validar tu flujo de trabajo antes de enviarlo al hardware. -
Esta guía cubre solo Sampler, no Estimator. Migrar de Estimator a Executor es considerablemente más complejo que migrar de Sampler porque Estimator calcula valores de expectativa en lugar de devolver muestras sin procesar. Reproducir el comportamiento de Estimator con Executor requiere procesamiento posterior adicional. Las funciones de utilidad para ayudar a migrar de Estimator a Executor aún están en desarrollo, por lo que esta guía describe intencionalmente solo el flujo de trabajo de Sampler.
Diferencias clave entre Executor y Sampler
Sampler y Executor muestrean ambos los registros de salida de los circuitos cuánticos, pero se dirigen a usuarios diferentes:
-
Sampler es una abstracción de alto nivel. Tiene las siguientes características:
-
Tiene supresión de errores integrada (desacoplamiento dinámico y twirling).
-
Toma decisiones implícitas por ti.
-
Está diseñado para que los desarrolladores de algoritmos puedan centrarse en la innovación en lugar de en la conversión de datos.
-
-
Executor forma parte del modelo de ejecución dirigida. Se diferencia de Sampler en muchos aspectos y tiene las siguientes características:
-
No tiene supresión ni mitigación de errores integradas. En su lugar, capturas tu intención de diseño en el lado del cliente (usando anotaciones de circuito y un samplex), y la costosa generación de variantes de circuito se traslada al lado del servidor.
-
No toma decisiones implícitas. Sigue tus directivas exactamente, ofreciendo control y transparencia totales.
-
Executor y Samplomatic juntos exponen capacidades adicionales que Sampler no ofrece, incluyendo (pero sin limitarse a) las siguientes:
- Más grupos de twirling: Samplomatic te permite elegir qué grupo de twirling aplicar
por caja, en lugar de estar limitado a la única estrategia que Sampler aplica por ti. También admite grupos de twirling distintos de Pauli, como el grupo de twirling
"local_c1". - Mediciones kernelizadas y clasificadas juntas: Establecer
QuantumProgram.meas_level = "both"(agregado enqiskit-ibm-runtimev0.48.0) solicita que tanto las mediciones clasificadas como las kernelizadas estén presentes en los resultados, en lugar de elegir un único tipo de medición por trabajo. - Twirling para circuitos con puertas fraccionarias: Executor puede aplicar twirling a circuitos que contienen puertas fraccionarias.
- Mitigación de errores de grano fino y componible: Por ejemplo, elegir qué capas de circuito mitigar y ajustar las tasas de ruido inyectadas en el circuito.
Notas- Se espera que las futuras capacidades nuevas se publiquen primero en Executor y podrían no trasladarse a Sampler. Si dependes de tener acceso a las últimas funciones, Executor es la opción más preparada para el futuro.
- El paquete base de Qiskit todavía no proporciona
una clase base para la primitiva Executor (sí la tiene para
SamplerV2).
- Más grupos de twirling: Samplomatic te permite elegir qué grupo de twirling aplicar
por caja, en lugar de estar limitado a la única estrategia que Sampler aplica por ti. También admite grupos de twirling distintos de Pauli, como el grupo de twirling
-
Mapeo conceptual
La siguiente tabla muestra cómo los conceptos de Sampler se mapean a Executor.
| Concepto | Sampler | Executor |
|---|---|---|
| Importación | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Entrada | Lista de PUBs (tuplas) | Un QuantumProgram de objetos QuantumProgramItem |
| Circuito y parámetros | Tupla (circuit, params, shots) | program.append_circuit_item(circuit, circuit_arguments=...) |
| Twirling | TwirlingOptions | Explícito mediante cajas anotadas y un samplex (append_samplex_item) |
| Llamada de ejecución | sampler.run([pub, ...]) | executor.run(program) |
| Tipo de resultado | PrimitiveResult de SamplerPubResult | QuantumProgramResult (iterable) |
| Acceder a los datos | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| Gestionar el ruido | Opciones integradas | Debe componerse manualmente (anotaciones, samplex, NoiseLearnerV3) |
Resumen de los pasos de migración
Paso 1. Instalar los paquetes necesarios
Executor y el modelo de ejecución dirigida requieren el paquete samplomatic:
pip install qiskit qiskit-ibm-runtime samplomatic
# For visualization support:
# pip install samplomatic[vis]
- Se recomienda
qiskit-ibm-runtimev0.48.0 porque agrega la opciónmeas_level = "both"y el grupo de twirlinglocal_c1. - Se requiere
qiskit >= 2.3.0. - Se requiere
samplomatic >= 0.18.0.
Paso 2. Cambiar las importaciones
Sampler:
from qiskit_ibm_runtime import SamplerV2 as Sampler
Executor:
from qiskit_ibm_runtime import Executor, QuantumProgram
Paso 3. Reemplazar las tuplas PUB con un QuantumProgram
En lugar de pasar una lista de tuplas (PUBs), cuando usas Executor, creas un QuantumProgram y le agregas elementos.
Un QuantumProgram acepta elementos de tipo circuit y samplex:
-
append_circuit_item: Agrega unCircuitItem, que es un circuito y (opcionalmente) sus valores de parámetros. Se ejecuta tal cual, sin ninguna aleatorización.Usa esto cuando solo quieras muestrear un circuito, exactamente como lo haría Sampler con un PUB que no tiene twirling; por ejemplo, cuando envíes un trabajo de muestreo simple, o cuando ya hayas incluido manualmente cualquier variante que quieras.
-
append_samplex_item: Agrega unsamplexItem, que es un circuito de plantilla más un samplex que genera conjuntos de parámetros aleatorizados en el lado del servidor.Usa esto cuando quieras que el contenido del circuito se aleatorice. El caso principal es con twirling (de puerta o de medición) o inyección de ruido. Esta capacidad reemplaza el twirling integrado de Sampler.
Un solo QuantumProgram puede aceptar ambos tipos de elementos; cada elemento agregado se ejecuta como una
tarea independiente y produce su propia entrada en los resultados. En general, usa append_circuit_item cuando tu circuito no necesite aleatorizarse. De lo contrario, usa append_samplex_item.
Las siguientes secciones muestran cada uno por turno: circuitos parametrizados que usan
append_circuit_item, y la migración de twirling usando append_samplex_item.
En los siguientes ejemplos de código, isa_circuit se refiere al circuito que se ha transpilado para ajustarse a la Arquitectura de Conjunto de Instrucciones (ISA) del backend de destino. Este isa_circuit contiene dos parámetros.
Paso 3a. Migrar circuitos parametrizados
Con Sampler, los valores de los parámetros son el segundo elemento de la tupla PUB. Con Executor,
pásalos como circuit_arguments a 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"]
Paso 3b. Migrar el twirling integrado a anotaciones explícitas
Este es el cambio más significativo. Sampler aplica el twirling por ti usando opciones. Con Executor, declaras esa intención explícitamente usando cajas anotadas y un samplex (de Samplomatic).
Sampler (twirling usando opciones):
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True
Executor (twirling usando cajas y un 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
)
Debido a que el circuito de plantilla y el samplex se crean en el lado del cliente, puedes inspeccionarlos y muestrearlos localmente para verificar la salida antes de enviar nada al hardware.
Verificación: Muestrear el circuito de plantilla localmente
Puedes extraer aleatorizaciones del samplex y vincularlas al circuito de plantilla
para confirmar que el samplex está produciendo los valores de parámetros que esperas.
Los valores de parámetros devueltos por samplex.sample son directamente compatibles con los
parámetros del circuito de plantilla.
# 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 más allá, puedes verificar que cada aleatorización sea lógicamente equivalente al
circuito original, por ejemplo, convirtiendo ambos a objetos Operator y comparando sus implementaciones unitarias (después de
tener en cuenta las correcciones de outputs["measurement_flips.<register>"] que deshacen
el twirling de medición), o comparando valores de expectativa de una ejecución local de
StatevectorSampler o StatevectorEstimator. Consulta la guía de Samplomatic
Entradas y salidas de Samplex
para ver un recorrido completo.
Paso 4. Cambiar cómo se solicitan los shots
Mueve los shots del PUB a QuantumProgram(shots=...). En Executor, shots se aplica a todo el trabajo. Envía varios trabajos si necesitas diferentes cantidades de shots.
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)
Paso 5. Actualizar las opciones según sea necesario
Hay menos opciones disponibles para Executor que para Sampler, porque las decisiones de mitigación de errores ahora residen en tus anotaciones y samplex en lugar de en opciones.
También hay una diferencia estructural en dónde residen los ajustes.
-
Con Sampler, todo, incluidas las decisiones que afectan el procesamiento posterior de los resultados, se configura en las opciones de la primitiva o en el PUB.
-
Con Executor, las decisiones que afectan cómo se dan forma y se procesan posteriormente los resultados del trabajo se establecen en el
QuantumProgram, no enExecutorOptions.
Examples:
| Sampler | Executor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions contiene solo ajustes de ejecución y entorno de nivel más bajo que no cambian la estructura de los
datos devueltos. Tiene tres grupos de nivel superior:
-
environment(EnvironmentOptions) -
execution(ExecutionOptions): Contiene menos opciones que con Sampler. Por ejemplo, no hay opciónmeas_typeen Executor.
En particular, las opciones twirling y dynamical_decoupling existen en Sampler pero no en Executor. En su lugar, esos valores de opciones se expresan a través del modelo de ejecución 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)
Paso 6. Actualizar el comando run
La entrada de un trabajo de Executor es el programa, en lugar de PUBs.
Sampler:
# Submit a job
sampler.run([(isa_circuit, parameter_values)])
Executor:
# Submit a job
executor.run(program)
Paso 7. Cambia cómo accedes a los resultados
En Executor, los resultados son arrays de NumPy, no objetos BitArray. Usa la cadena de nombre como índice (result[0]["meas"]) y obtén un np.ndarray de vuelta. No hay necesidad de recordar la ruta de atributo .data.<register>.
Para actualizar de Sampler a Executor, cambia result[i].data.<reg> (BitArray) a result[i]["<reg>"] (np.ndarray), y luego reescribe el post-procesamiento basado en get_counts como operaciones de NumPy.
| Tarea | 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() | Post-process the array manually |
| Multiple registers | result[0].data.<name> per register | result[0]["<name>"] per register |
| CircuitItem array shape | - | (parameter_sets, shots, register_bits) |
| SamplexItem array shape | - | (randomizations, parameter_sets, shots, register_bits) |
| Undo measurement twirling | Automatic | result[i]["measurement_flips.<name>"] + XOR |
El BitArray de Sampler ofrece funciones auxiliares (get_counts, slice_bits, slice_shots, expectation_values, y máscaras de selección posterior). Executor devuelve arrays de NumPy sin procesar para que puedas realizar este posprocesamiento con las operaciones estándar de NumPy.
Paso 8. Maneja los resultados con twirling (correcciones de inversión de bits)
Cuando aplicas twirling de medición mediante un SamplexItem, Executor devuelve las mediciones
brutas (con twirling) más las correcciones de inversión de bits necesarias para deshacer el twirling.
Debes aplicarlas manualmente; nada se corrige implícitamente.
Al usar Executor, deshaz el twirling explícitamente usando las correcciones measurement_flips.<reg> y un XOR, como se muestra en el siguiente ejemplo:
# 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
No hay un paso equivalente en Sampler porque este deshace el twirling por ti.
Ejemplo completo: Migra un job de muestreo 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"]