Saltar al contenido principal

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.

Versión beta

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 AerSampler en qiskit-aer para 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 en qiskit-ibm-runtime v0.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).

Mapeo conceptual

La siguiente tabla muestra cómo los conceptos de Sampler se mapean a Executor.

ConceptoSamplerExecutor
Importaciónfrom qiskit_ibm_runtime import SamplerV2from qiskit_ibm_runtime import Executor
EntradaLista de PUBs (tuplas)Un QuantumProgram de objetos QuantumProgramItem
Circuito y parámetrosTupla (circuit, params, shots)program.append_circuit_item(circuit, circuit_arguments=...)
TwirlingTwirlingOptionsExplícito mediante cajas anotadas y un samplex (append_samplex_item)
Llamada de ejecuciónsampler.run([pub, ...])executor.run(program)
Tipo de resultadoPrimitiveResult de SamplerPubResultQuantumProgramResult (iterable)
Acceder a los datosresult[0].data.<register> (BitArray)result[0]["<register>"] (np.ndarray)
Gestionar el ruidoOpciones integradasDebe componerse manualmente (anotaciones, samplex, NoiseLearnerV3)

Resumen de los pasos de migración

  1. Instala Samplomatic.

  2. Cambia las importaciones.

  3. Reemplaza las tuplas PUB.

  4. Cambia cómo se expresan los shots.

  5. Actualiza otras opciones según sea necesario.

  6. Actualiza el comando run.

  7. Actualiza el análisis de resultados.

  8. Deshaz el twirling.

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]
Notas de versión
  • Se recomienda qiskit-ibm-runtime v0.48.0 porque agrega la opción meas_level = "both" y el grupo de twirling local_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 un CircuitItem, 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 un samplexItem, 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 en ExecutorOptions.

Examples:

SamplerExecutor
shotsQuantumProgram(shots=...)
meas_typeQuantumProgram(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:

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.

TareaSamplerExecutor
Get register dataresult[0].data.measresult[0]["meas"]
Data typeBitArraynp.ndarray
Counts dictionaryresult[0].data.meas.get_counts()Post-process the array manually
Multiple registersresult[0].data.<name> per registerresult[0]["<name>"] per register
CircuitItem array shape-(parameter_sets, shots, register_bits)
SamplexItem array shape-(randomizations, parameter_sets, shots, register_bits)
Undo measurement twirlingAutomaticresult[i]["measurement_flips.<name>"] + XOR
nota

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"]

Próximos pasos