Migrar de Sampler a Executor
Esta guía describe cómo trasladar las cargas de trabajo de muestreo cuántico de la primitiva « 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 se encuentran actualmente en fase beta y es posible que no sean estables. Te invitamos a que las pruebes y nos des tu opinión abriendo una incidencia en los repositorios de Samplomatic o qiskit-ibm-runtime GitHub.
¿Deberías migrar?
No todo el mundo debería pasar de Sampler a Executor. Existen muchas diferencias entre los tipos primitivos, pero las siguientes indicaciones pueden ayudarte a decidir si conviene realizar la migración:
Cambia a Executor si eres un investigador en información cuántica que lleva a cabo experimentos a escala industrial y necesitas un control preciso y reproducible sobre técnicas como el «Pauli twirling», el aprendizaje y la inyección de modelos de ruido, y los cambios de base, o si necesitas alguna de las capacidades adicionales que ofrece Executor.
Sigue utilizando Sampler si prefieres una interfaz sencilla y de alto nivel, y quieres que la primitiva se encargue de suprimir y mitigar los errores por ti.
Limitaciones y advertencias
Dado que Executor y el modelo de ejecución dirigida se encuentran en fase beta, ten en cuenta lo siguiente antes de decidir realizar la migración:
- Aún no hay compatibilidad con simuladores : a diferencia de Sampler, que cuenta con una
AerSamplerimplementación paraqiskit-aerla simulación local, actualmente no existe ningún backend de simulador para Executor. Se espera que la compatibilidad con el simulador esté disponible en breve. Mientras tanto, puedes seguir revisando y probando el circuito de la plantilla de forma local para validar tu flujo de trabajo antes de enviarlo al departamento de hardware. - Esta guía se refiere únicamente a Sampler, no a Estimator. La migración de «Estimator» a «Executor» es considerablemente más compleja que la migración desde «Sampler», ya que «Estimator» calcula valores esperados en lugar de devolver muestras sin procesar. Para reproducir el comportamiento de Estimator con Executor es necesario realizar un procesamiento posterior adicional. Las funciones de utilidad para facilitar la migración de Estimator a Executor aún se encuentran en fase de desarrollo, por lo que esta guía describe, de forma intencionada, únicamente el flujo de trabajo de Sampler.
Diferencias clave entre Executor y Sampler
Tanto «Sampler» como «Executor» muestrean los registros de salida de los circuitos cuánticos, pero se dirigen a usuarios diferentes:
- Sampler es una abstracción de alto nivel. Presenta las siguientes características:
- Cuenta con supresión de errores integrada (desacoplamiento dinámico y giro).
- 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 los datos. conversión.
- El ejecutor forma parte del modelo de ejecución dirigida. Se diferencia de Sampler en muchos aspectos y presenta las siguientes características:
-
No cuenta con ningún mecanismo integrado de supresión o mitigación de errores. En su lugar, se plasma la intención del diseño en el lado del cliente (mediante anotaciones de circuitos y un «samplex» ), y la costosa generación de variantes de circuitos se traslada al lado del servidor.
-
No toma ninguna decisión implícita. Sigue tus instrucciones al pie de la letra, lo que te ofrece un control total y total transparencia.
-
Executor y Samplomatic, en conjunto, ofrecen funciones adicionales que Sampler no ofrece, entre las que se incluyen (sin limitarse a ellas) las siguientes:
- Más grupos de rotación: Samplomatic te permite elegir qué grupo de rotación aplicar
a cada casilla, en lugar de limitarte a la única estrategia que Sampler aplica automáticamente. También admite grupos de giro distintos de los de Pauli, como el grupo de giro
"local_c1". - Mediciones con kernel y clasificadas conjuntamente: La configuración
QuantumProgram.meas_level = "both"(añadida en v0.48.0qiskit-ibm-runtime) solicita que en los resultados figuren tanto las mediciones clasificadas como las con kernel, en lugar de seleccionar un único tipo de medición por trabajo. - Aplicación de «twirling» a circuitos con puertas fraccionarias: Executor puede aplicar el «twirling» a circuitos que contengan puertas fraccionarias.
- Mitigación de errores de alta precisión y combinable: por ejemplo, elegir qué capas del circuito se van a mitigar y ajustar los índices de ruido inyectados en el circuito.
Notas- Se prevé que las futuras novedades se lancen primero en Executor y es posible que no se incorporen a Sampler. Si necesitas tener acceso a las últimas funciones, Executor es la opción más preparada para el futuro.
- El paquete básico de Qiskit aún no ofrece
una clase base para la primitiva Executor (sí la ofrece para
SamplerV2).
- Más grupos de rotación: Samplomatic te permite elegir qué grupo de rotación aplicar
a cada casilla, en lugar de limitarte a la única estrategia que Sampler aplica automáticamente. También admite grupos de giro distintos de los de Pauli, como el grupo de giro
-
Mapas conceptuales
La siguiente tabla muestra cómo se corresponden los conceptos de Sampler con los de Executor.
Concepto | Muestreador | Ejecutor |
|---|---|---|
| Importar | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Entrada | Lista de PUB (tuplas) | Un conjunto de QuantumProgram objetos QuantumProgramItem |
| Circuito y parámetros | (circuit, params, shots) tupla | program.append_circuit_item(circuit, circuit_arguments=...) |
| Girando | TwirlingOptions | De forma explícita mediante recuadros con anotaciones y un «samplex» (append_samplex_item) |
| Ejecutar llamada | sampler.run([pub, ...]) | executor.run(program) |
| Tipo de resultado | PrimitiveResult de SamplerPubResult | QuantumProgramResult (iterable) |
| Acceder a datos | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| Gestionar el ruido | Opciones integradas | Debe crearse manualmente (anotaciones, samplex, etc. NoiseLearnerV3) |
Resumen de los pasos de la migración
- Instala Samplomatic.
- Modifica las importaciones.
- Sustituir las tuplas « PUB ».
- Modificar la forma en que se expresan los tiros.
- Actualiza las demás opciones según sea necesario.
- Actualiza el comando
run. - Actualizar el análisis de resultados.
- Deshacer el giro.
Paso 1. Instala los paquetes necesarios
El ejecutor 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]qiskit-ibm-runtimev0.48.0 Se recomienda porque añade la opciónmeas_level = "both"y el grupolocal_c1giratorio.qiskit >= 2.3.0es necesario.samplomatic >= 0.18.0es necesario.
Paso 2. Modifica las importaciones
Muestra:
from qiskit_ibm_runtime import SamplerV2 as SamplerAlbacea:
from qiskit_ibm_runtime import Executor, QuantumProgramPaso 3. Sustituye las tuplas « PUB » por un QuantumProgram
En lugar de pasar una lista de tuplas (PUBs), al utilizar Executor, se crea un QuantumProgram y se le añaden elementos.
A admite QuantumProgram elementos de circuito y elementos Samplex :
-
append_circuit_item: Añade unCircuitItem, que es un circuito y (opcionalmente) sus valores de parámetros. Se ejecuta tal cual, sin ningún tipo de aleatorización.Utiliza esta opción cuando solo quieras tomar una muestra de un circuito, exactamente igual que lo haría Sampler con un « PUB » sin giros; por ejemplo, al enviar un trabajo de muestreo sencillo, o cuando ya hayas incluido manualmente las variantes que desees.
-
append_samplex_item: Añade unsamplexItem, que es un circuito de plantilla más un samplex que genera conjuntos de parámetros aleatorios en el lado del servidor.Utiliza esto cuando quieras que el contenido del circuito se muestre de forma aleatoria. El caso principal se da con el giro (de compuerta o de medida) o la inyección de ruido. Esta función sustituye al efecto de giro integrado en Sampler.
Un único QuantumProgram puede admitir ambos tipos de elementos; cada elemento añadido se ejecuta como una
tarea independiente y genera su propia entrada en los resultados. En general, utilízalo append_circuit_item cuando tu circuito no necesite ser aleatorio. De lo contrario, utiliza append_samplex_item.
En los siguientes apartados se muestran, uno tras otro: circuitos parametrizados que utilizan
append_circuit_item, y el «twirling» migratorio mediante el uso de append_samplex_item.
En los siguientes ejemplos de código, se refiere isa_circuit al circuito que se ha transpuesto para ajustarse a la arquitectura de conjunto de instrucciones (ISA) del backend de destino. Esto contiene isa_circuit dos parámetros.
Paso 3a. Migrar circuitos parametrizados
En Sampler, los valores de los parámetros son el segundo elemento de la tupla « PUB ». Con Executor,
pásalas como circuit_arguments a append_circuit_item.
Muestra:
params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)Ejecutor
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 importante. Sampler aplica el efecto giratorio por ti mediante las opciones disponibles. Con Executor, se declara esa intención de forma explícita utilizando recuadros anotados y un «samplex» (de Samplomatic ).
Muestra (girando mediante las opciones):
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = TrueEjecutor (giros con 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
)Dado que el circuito de plantilla y el samplex se crean en el lado del cliente, puedes inspeccionarlos y tomar muestras de ellos localmente para verificar el resultado antes de enviar nada al hardware.
Verificación: Probar el circuito de la plantilla de forma local
Puedes extraer muestras aleatorias del «samplex» y vincularlas al circuito de plantilla
para confirmar que el «samplex» está generando los valores de los parámetros que esperas.
Los valores de los 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 profundizar más, se puede verificar que cada aleatorización es lógicamente equivalente al
circuito original, por ejemplo, convirtiendo ambos en objetos Operator y comparando sus implementaciones unitarias (tras
tener en cuenta las outputs["measurement_flips.<register>"] correcciones que revierten
el «twirling» de la medición), o comparando los valores esperados de una ejecución local
StatevectorSampler o de una StatevectorEstimator ejecución. Consulta la guía
de entradas y salidas
del Samplomatic Samplex para obtener una explicación detallada.
Paso 4. Modificar la forma de solicitar las tomas
Mueve las imágenes de « PUB » a QuantumProgram(shots=...). En Executor, se shots aplica a todo el trabajo. Envía varios trabajos si necesitas un número diferente de tomas.
Muestra:
# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])Albacea:
# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)Paso 5. Actualiza las opciones según sea necesario
Executor dispone de menos opciones que Sampler, ya que las opciones de mitigación de errores ahora se encuentran en las anotaciones y en «samplex», en lugar de en las opciones.
También existe una diferencia estructural en cuanto a la ubicación de los ajustes.
-
Con Sampler, todo, incluidas las opciones que afectan al posprocesamiento de los resultados, se configura en las opciones de la primitiva o en el archivo « PUB ».
-
Con Executor, las opciones que influyen en cómo se configuran y se procesan posteriormente los resultados del trabajo se establecen en el
QuantumProgram, y no en elExecutorOptions.
Ejemplos:
Muestreador | Ejecutor |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions solo contiene ajustes de ejecución y de entorno de nivel inferior
que no modifican la estructura de los datos devueltos. Cuenta con tres grupos de primer nivel:
environment(EnvironmentOptions)execution(ExecutionOptions): Ofrece menos opciones que Sampler. Por ejemplo, no existe la opción «Executormeas_type».experimental
Cabe destacar que las opciones dynamical_decoupling y twirling están disponibles en Sampler, pero no en Executor. En cambio, esos valores de opción se expresan a través del modelo de ejecución dirigida.
Ejemplo:
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 los PUB.
Muestra:
# Submit a job
sampler.run([(isa_circuit, parameter_values)])Albacea:
# Submit a job
executor.run(program)Paso 7. Cambia la forma de acceder a los resultados
En Executor, los resultados son matrices de tipo « NumPy », no BitArray objetos. Utiliza la cadena de nombre como índice (result[0]["meas"]) y obtén un como resultado np.ndarray . No es necesario recordar la ruta del atributo .data.<register> .
Para pasar de Sampler a Executor, cambia result[i].data.<reg> (BitArray) por result[i]["<reg>"] (np.ndarray), y a continuación reescribe el posprocesamiento get_countsbasado en - como operaciones de NumPy.
Tarea | Muestreador | Ejecutor |
|---|---|---|
| Obtener datos de registro | result[0].data.meas | result[0]["meas"] |
| Tipo de datos | BitArray | np.ndarray |
| Diccionario de contadores | result[0].data.meas.get_counts() | Realizar el posprocesamiento de la matriz manualmente |
| Varios registros | result[0].data.<name> por registro | result[0]["<name>"] por registro |
| CircuitItem forma de la matriz | - | (parameter_sets, shots, register_bits) |
| SamplexItem forma de la matriz | - | (randomizations, parameter_sets, shots, register_bits) |
| Deshacer el giro de la medición | Automático | result[i]["measurement_flips.<name>"] + XOR |
Sampler's BitArray ofrece herramientas de ayuda (get_counts, slice_bits, slice_shots, expectation_values, y máscaras de poselección). El ejecutor devuelve matrices « NumPy » sin procesar, por lo que puedes realizar este posprocesamiento con operaciones estándar de « NumPy ».
Paso 8. Gestionar resultados alterados (correcciones de inversión de bits)
Cuando se aplica la rotación de medidas a través de un SamplexItem, Executor devuelve las medidas sin procesar
(rotadas) junto con las correcciones de inversión de bits necesarias para revertir la rotación.
Hay que aplicarlas manualmente; no se corrige nada de forma automática.
Al utilizar Executor, desactiva el «twirling» de forma explícita mediante las correcciones measurement_flips.<reg> y una operación XOR, tal y 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_1En Sampler no existe un paso equivalente, ya que la aplicación desactiva automáticamente el efecto de giro por ti.
Ejemplo completo: Migrar un trabajo de muestreo básico
Muestreador
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()Ejecutor
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"]