Vai al contenuto principale

Migra da Sampler a Executor

Questa guida descrive come spostare i workload di campionamento quantistico dalla primitiva IBM Quantum® Sampler alla primitiva Executor.

Rilascio beta

La primitiva Executor fa parte del modello di esecuzione diretta. Tutti i componenti nel modello di esecuzione diretta sono attualmente in beta e potrebbero non essere stabili. Sei invitato a testarli e fornire feedback aprendo un issue nei repository GitHub Samplomatic o qiskit-ibm-runtime.

Dovresti migrare?

Non tutti dovrebbero migrare da Sampler a Executor. Ci sono molte differenze tra le primitive, ma le seguenti indicazioni possono aiutarti a decidere se migrare:

Migra a Executor se sei uno scienziato dell'informazione quantistica che esegue esperimenti su scala di utilità e ha bisogno di un controllo granulare e riproducibile su tecniche come Pauli twirling, apprendimento e iniezione di modelli di rumore, e cambi di base — oppure se hai bisogno di una delle capacità aggiuntive fornite da Executor.

Continua a usare Sampler se vuoi un'interfaccia semplice e di alto livello e vuoi che la primitiva gestisca per te la soppressione e la mitigazione degli errori.

Limitazioni e avvertenze

Poiché Executor e il modello di esecuzione diretta sono in beta, nota quanto segue prima di decidere di migrare:

  • Nessun supporto simulatore ancora: A differenza di Sampler, che ha un'implementazione AerSampler in qiskit-aer per la simulazione locale, attualmente non esiste un backend simulatore per Executor. Il supporto per il simulatore è previsto a breve. Nel frattempo, puoi comunque ispezionare e campionare il circuito template localmente per validare il tuo workflow prima di inviarlo all'hardware.

  • Questa guida copre solo Sampler, non Estimator. Migrare da Estimator a Executor è considerevolmente più complesso rispetto alla migrazione da Sampler perché Estimator calcola valori di aspettazione anziché restituire campioni grezzi. Riprodurre il comportamento di Estimator con Executor richiede un'elaborazione aggiuntiva successiva. Le funzioni di utilità per aiutare la migrazione da Estimator a Executor sono ancora in fase di sviluppo, quindi questa guida descrive intenzionalmente solo il workflow di Sampler.

Differenze chiave tra Executor e Sampler

Sampler e Executor campionano entrambi i registri di output dei circuiti quantistici, ma si rivolgono a utenti diversi:

  • Sampler è un'astrazione di alto livello. Ha le seguenti caratteristiche:

    • Dispone di soppressione degli errori integrata (dynamical decoupling e twirling).

    • Prende decisioni implicite per te.

    • È progettato in modo che gli sviluppatori di algoritmi possano concentrarsi sull'innovazione anziché sulla conversione dei dati.

  • Executor fa parte del modello di esecuzione diretta. Differisce da Sampler in molti modi e ha le seguenti caratteristiche:

    • Non ha soppressione o mitigazione degli errori integrata. Invece, catturi l'intento del tuo design sul lato client (usando annotazioni dei circuiti e una samplex), e la costosa generazione delle varianti di circuito viene spostata sul lato server.

    • Non prende decisioni implicite. Segue esattamente le tue direttive, offrendo pieno controllo e trasparenza.

    • Executor e Samplomatic insieme espongono capacità aggiuntive che Sampler non offre, incluse (ma non limitate a) le seguenti:

      • Più gruppi di twirling: Samplomatic ti permette di scegliere quale gruppo di twirling applicare per box, invece di essere limitato alla singola strategia che Sampler applica per te. Supporta anche gruppi di twirling diversi da Pauli, come il gruppo di twirling "local_c1".
      • Misurazioni kerneled e classified insieme: impostando QuantumProgram.meas_level = "both" (aggiunto in qiskit-ibm-runtime v0.48.0) richiedi che sia le misurazioni classified sia quelle kerneled siano presenti nei risultati, invece di scegliere un singolo tipo di misurazione per job.
      • Twirling per circuiti con gate frazionari: Executor può applicare twirling a circuiti che contengono gate frazionari.
      • Mitigazione degli errori a grana fine e componibile: ad esempio, scegliendo quali layer di circuito mitigare e regolando i tassi di rumore iniettati nel circuito.
      Note
      • Ci si aspetta che le nuove funzionalità future vengano rilasciate prima per Executor e potrebbero non essere portate su Sampler. Se dipendi dall'accesso alle funzionalità più recenti, Executor è la scelta più a prova di futuro.
      • Il pacchetto base di Qiskit non fornisce ancora una classe base per la primitiva Executor (lo fa per SamplerV2).

Mappatura concettuale

La tabella seguente mostra come i concetti di Sampler si mappano su Executor.

ConcettoSamplerExecutor
Importfrom qiskit_ibm_runtime import SamplerV2from qiskit_ibm_runtime import Executor
InputElenco di PUB (tuple)Un QuantumProgram di oggetti QuantumProgramItem
Circuito e parametritupla (circuit, params, shots)program.append_circuit_item(circuit, circuit_arguments=...)
TwirlingTwirlingOptionsEsplicito tramite box annotati e un samplex (append_samplex_item)
Chiamata di esecuzionesampler.run([pub, ...])executor.run(program)
Tipo di risultatoPrimitiveResult di SamplerPubResultQuantumProgramResult (iterabile)
Accesso ai datiresult[0].data.<register> (BitArray)result[0]["<register>"] (np.ndarray)
Gestione del rumoreOpzioni integrateDeve essere composto manualmente (annotazioni, samplex, NoiseLearnerV3)

Panoramica dei passaggi di migrazione

  1. Installa Samplomatic.

  2. Modifica gli import.

  3. Sostituisci le tuple PUB.

  4. Modifica il modo in cui vengono espressi gli shot.

  5. Aggiorna altre opzioni secondo necessità.

  6. Aggiorna il comando run.

  7. Aggiorna l'analisi dei risultati.

  8. Annulla il twirling.

Passaggio 1. Installa i pacchetti richiesti

Executor e il modello di esecuzione diretta richiedono il pacchetto samplomatic:

pip install qiskit qiskit-ibm-runtime samplomatic

# For visualization support:
# pip install samplomatic[vis]
Note sulla versione
  • qiskit-ibm-runtime v0.48.0 è consigliato perché aggiunge l'opzione meas_level = "both" e il gruppo di twirling local_c1.
  • È richiesto qiskit >= 2.3.0.
  • È richiesto samplomatic >= 0.18.0.

Passaggio 2. Modifica gli import

Sampler:

from qiskit_ibm_runtime import SamplerV2 as Sampler

Executor:

from qiskit_ibm_runtime import Executor, QuantumProgram

Passaggio 3. Sostituisci le tuple PUB con un QuantumProgram

Invece di passare una lista di tuple (PUB), quando usi Executor, costruisci un QuantumProgram e vi aggiungi degli item.

Un QuantumProgram accetta item di tipo circuit e samplex:

  • append_circuit_item: aggiunge un CircuitItem, che è un circuito e (opzionalmente) i suoi valori dei parametri. Viene eseguito così com'è, senza alcuna randomizzazione.

    Usalo quando vuoi semplicemente campionare un circuito, esattamente come farebbe Sampler con un PUB senza twirling; ad esempio, quando invii un job di campionamento semplice, o quando hai già incluso manualmente le varianti che desideri.

  • append_samplex_item: aggiunge un samplexItem, che è un circuito template più una samplex che genera set di parametri randomizzati lato server.

    Usalo quando vuoi che il contenuto del circuito sia randomizzato. Il caso principale è con il twirling (di gate o di misurazione) o l'iniezione di rumore. Questa capacità sostituisce il twirling integrato di Sampler.

Un singolo QuantumProgram può accettare entrambi i tipi di item; ogni item aggiunto viene eseguito come un task indipendente e produce una propria voce nei risultati. In generale, usa append_circuit_item quando il tuo circuito non deve essere randomizzato. Altrimenti, usa append_samplex_item.

Le sezioni seguenti mostrano ciascuno a turno: circuiti parametrizzati che usano append_circuit_item, e la migrazione del twirling usando append_samplex_item.

Negli esempi di codice seguenti, isa_circuit si riferisce al circuito che è stato traspilato per conformarsi alla Instruction Set Architecture (ISA) del backend di destinazione. Questo isa_circuit contiene due parametri.

Passaggio 3a. Migra i circuiti parametrizzati

Con Sampler, i valori dei parametri sono il secondo elemento della tupla PUB. Con Executor, passali come circuit_arguments ad 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"]

Passaggio 3b. Migra il twirling integrato ad annotazioni esplicite

Questo è il cambiamento più significativo. Sampler applica il twirling per te usando le opzioni. Con Executor, dichiari quell'intento esplicitamente usando box annotati e una samplex (da Samplomatic).

Sampler (twirling tramite opzioni):

sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = True

Executor (twirling tramite box e una 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
)

Poiché il circuito template e la samplex sono costruiti lato client, puoi ispezionarli e campionarli localmente per verificare l'output prima di inviare qualsiasi cosa all'hardware.

Verifica: campiona il circuito template localmente

Puoi estrarre randomizzazioni dalla samplex e vincolarle al circuito template per confermare che la samplex stia producendo i valori dei parametri che ti aspetti. I valori dei parametri restituiti da samplex.sample sono direttamente compatibili con i parametri del circuito 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)

Per andare oltre, puoi verificare che ogni randomizzazione sia logicamente equivalente al circuito originale, ad esempio, convertendo entrambi in oggetti Operator e confrontando le loro implementazioni unitarie (dopo aver tenuto conto delle correzioni outputs["measurement_flips.<register>"] che annullano il twirling delle misurazioni), oppure confrontando i valori attesi da un'esecuzione locale di StatevectorSampler o StatevectorEstimator. Consulta la guida Samplomatic Samplex inputs and outputs per una procedura completa.

Passaggio 4. Modifica il modo in cui vengono richiesti gli shot

Sposta gli shot dal PUB a QuantumProgram(shots=...). In Executor, shots si applica all'intero job. Invia più job se hai bisogno di conteggi di shot diversi.

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)

Passaggio 5. Aggiorna le opzioni secondo necessità

Ci sono meno opzioni disponibili per Executor rispetto a Sampler, perché le scelte di mitigazione degli errori ora risiedono nelle tue annotazioni e nella samplex invece che nelle opzioni.

C'è anche una differenza strutturale su dove risiedono le impostazioni.

  • Con Sampler, tutto, incluse le scelte che influenzano il post-processing dei risultati, viene configurato nelle opzioni della primitiva o nel PUB.

  • Con Executor, le scelte che influenzano come vengono formati e post-processati i risultati del job sono impostate su QuantumProgram, non su ExecutorOptions.

Examples:

SamplerExecutor
shotsQuantumProgram(shots=...)
meas_typeQuantumProgram(meas_level=...)

ExecutorOptions contiene solo impostazioni di esecuzione e ambiente di livello più basso che non modificano la struttura dei dati restituiti. Ha tre gruppi di primo livello:

In particolare, le opzioni twirling e dynamical_decoupling esistono in Sampler ma non in Executor. Invece, quei valori delle opzioni sono espressi tramite il modello di esecuzione diretta.

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)

Passaggio 6. Aggiorna il comando run

L'input per un job Executor è il programma, invece dei PUB.

Sampler:

# Submit a job
sampler.run([(isa_circuit, parameter_values)])

Executor:

# Submit a job
executor.run(program)

Passaggio 7. Modifica il modo in cui accedi ai risultati

In Executor, i risultati sono array NumPy, non oggetti BitArray. Usa la stringa del nome come indice (result[0]["meas"]) e ottieni indietro un np.ndarray. Non c'è bisogno di ricordare il percorso dell'attributo .data.<register>.

Per aggiornare da Sampler a Executor, cambia result[i].data.<reg> (BitArray) in result[i]["<reg>"] (np.ndarray), poi riscrivi il post-processing basato su get_counts come operazioni NumPy.

TaskSamplerExecutor
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

Il BitArray di Sampler offre helper (get_counts, slice_bits, slice_shots, expectation_values, e maschere di post-selezione). Executor restituisce array NumPy grezzi, così puoi eseguire questo post-processing con le operazioni NumPy standard.

Passaggio 8. Gestisci i risultati con twirling (correzioni bit-flip)

Quando applichi il twirling delle misurazioni tramite un SamplexItem, Executor restituisce le misurazioni grezze (con twirling) più le correzioni bit-flip necessarie per annullare il twirling. Devi applicarle manualmente; nulla viene corretto implicitamente.

Quando usi Executor, annulla il twirling esplicitamente usando le correzioni measurement_flips.<reg> e uno XOR, come mostrato nell'esempio seguente:

# 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

Non esiste un passaggio equivalente in Sampler perché annulla il twirling per te.

Esempio completo: migra un job di campionamento di base

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

Passaggi successivi