Migration de Sampler vers Executor
Ce guide explique comment transférer les charges de travail d'échantillonnage quantique de la primitive « Sampler » d' IBM Quantum® vers la primitive « Executor ».
La primitive « Executor » fait partie du modèle d'exécution dirigée. Tous les composants du modèle d'exécution dirigée sont actuellement en version bêta et peuvent ne pas être stables. Nous vous invitons à les tester et à nous faire part de vos commentaires en créant un ticket dans les dépôts Samplomatic ou qiskit-ibm-runtime sur GitHub.
Devriez-vous migrer?
Tout le monde ne devrait pas passer de Sampler à Executor. Il existe de nombreuses différences entre les types primitifs, mais les conseils suivants peuvent vous aider à décider s'il convient de procéder à la migration :
Optez pour Executor si vous êtes un chercheur en information quantique qui mène des expériences à grande échelle et qui a besoin d’un contrôle précis et reproductible sur des techniques telles que la rotation de Pauli, l’apprentissage et l’injection de modèles de bruit, ainsi que les changements de base — ou si vous avez besoin de l’une des fonctionnalités supplémentaires offertes par Executor.
Continuez à utiliser Sampler si vous recherchez une interface simple et intuitive et si vous souhaitez que la primitive se charge pour vous de la suppression et de l'atténuation des erreurs.
Limites et mises en garde
Executor et le modèle d'exécution dirigée étant encore en version bêta, veuillez tenir compte des points suivants avant de décider de procéder à la migration :
- Pas encore de prise en charge du simulateur : contrairement à Sampler, qui dispose d’une
AerSamplerimplémentation permettantqiskit-aerla simulation locale, il n’existe actuellement aucun backend de simulateur pour Executor. La prise en charge du simulateur devrait être disponible prochainement. En attendant, vous pouvez toujours examiner et tester le circuit type en local afin de valider votre processus avant de le soumettre à la fabrication. - Ce guide concerne uniquement Sampler, et non Estimator. La migration d'Estimator vers Executor est nettement plus complexe que celle depuis Sampler, car Estimator calcule des valeurs attendues plutôt que de renvoyer des échantillons bruts. Pour reproduire le comportement d'Estimator avec Executor, un post-traitement supplémentaire est nécessaire. Les fonctions utilitaires destinées à faciliter la migration d' Estimator vers Executor sont encore en cours de développement; c'est pourquoi ce guide ne décrit, à dessein, que le workflow Sampler.
Principales différences entre Executor et Sampler
Les fonctions « Sampler » et « Executor » échantillonnent toutes deux les registres de sortie des circuits quantiques, mais elles s'adressent à des utilisateurs différents :
- Sampler est une abstraction de haut niveau. Il présente les caractéristiques suivantes :
- Il intègre des mécanismes de suppression des erreurs (découplage dynamique et twirling).
- Il prend des décisions implicites à votre place.
- Il est conçu pour permettre aux développeurs d'algorithmes de se concentrer sur l'innovation plutôt que sur les données. Conversion.
- L'exécuteur fait partie du modèle d'exécution dirigée. Il diffère de Sampler à bien des égards et présente les caractéristiques suivantes :
-
Il ne dispose d'aucun mécanisme intégré de suppression ou d'atténuation des erreurs. Au lieu de cela, vous définissez votre intention de conception côté client (à l'aide d'annotations de circuit et d'un samplex), et la génération, coûteuse en ressources, des variantes de circuit est transférée côté serveur.
-
Il ne prend aucune décision implicite. Il suit vos instructions à la lettre, vous offrant ainsi un contrôle total et une transparence absolue.
-
Executor et Samplomatic offrent ensemble des fonctionnalités supplémentaires que Sampler ne propose pas, notamment (mais sans s'y limiter) les suivantes :
- Davantage de groupes de rotation : Samplomatic vous permet de choisir le groupe de rotation à appliquer
à chaque case, plutôt que d'être limité à la stratégie unique que Sampler applique pour vous. Il prend également en charge les groupes de torsion autres que celui de Pauli, tels que le groupe de torsion
"local_c1". - Mesures avec noyau et classées regroupées : ce paramètre
QuantumProgram.meas_level = "both"(ajouté dans v0.48.0qiskit-ibm-runtime) demande que les résultats contiennent à la fois les mesures classées et celles avec noyau, au lieu de choisir un seul type de mesure par tâche. - Twirling pour les circuits comportant des portes fractionnaires : Executor peut appliquer le twirling à des circuits contenant des portes fractionnaires.
- Atténuation des erreurs fine et modulable : par exemple, choisir les couches du circuit à traiter et ajuster les taux de bruit injectés dans le circuit.
Remarques- Les nouvelles fonctionnalités à venir devraient être intégrées en priorité à Executor et pourraient ne pas être portées sur Sampler. Si vous tenez à pouvoir bénéficier des dernières fonctionnalités, Executor est le choix le plus pérenne.
- Le paquet Qiskit de base ne fournit pas encore
de classe de base pour la primitive Executor (contrairement à
SamplerV2).
- Davantage de groupes de rotation : Samplomatic vous permet de choisir le groupe de rotation à appliquer
à chaque case, plutôt que d'être limité à la stratégie unique que Sampler applique pour vous. Il prend également en charge les groupes de torsion autres que celui de Pauli, tels que le groupe de torsion
-
Cartographie conceptuelle
Le tableau suivant montre comment les concepts de Sampler correspondent à ceux d'Executor.
Concept | Echantillonneur | Exécuteur |
|---|---|---|
| Importer | from qiskit_ibm_runtime import SamplerV2 | from qiskit_ibm_runtime import Executor |
| Entrée | Liste des PUB (tuples) | Un ensemble QuantumProgram d'objets QuantumProgramItem |
| Circuit et paramètres | (circuit, params, shots) bloc de données | program.append_circuit_item(circuit, circuit_arguments=...) |
| Virevolter | TwirlingOptions | Explicite grâce à des encadrés annotés et à un samplex (append_samplex_item) |
| Exécuter l'appel | sampler.run([pub, ...]) | executor.run(program) |
| Type de résultat | PrimitiveResultdeSamplerPubResult | QuantumProgramResult (itérable) |
| Accès aux données | result[0].data.<register> (BitArray) | result[0]["<register>"] (np.ndarray) |
| Gérer le bruit | Options intégrées | Doit être composé manuellement (annotations, samplex, etc. NoiseLearnerV3) |
Présentation générale des étapes de migration
- Installez Samplomatic.
- Modifiez les importations.
- Remplacer les tuples « PUB ».
- Modifier la manière dont les plans sont décrits.
- Modifiez les autres options si nécessaire.
- Mettez à jour la commande
run. - Mise à jour de l'analyse des résultats.
- Annuler la rotation.
Etape 1. Installez les paquets nécessaires
Executor et le modèle d'exécution dirigée nécessitent le samplomatic package :
pip install qiskit qiskit-ibm-runtime samplomatic
# For visualization support:
# pip install samplomatic[vis]qiskit-ibm-runtimev0.48.0 est recommandé car il ajoute l'optionmeas_level = "both"et le groupe de rotationlocal_c1.qiskit >= 2.3.0est obligatoire.samplomatic >= 0.18.0est obligatoire.
Étape 2. Modifier les importations
Extrait :
from qiskit_ibm_runtime import SamplerV2 as SamplerExécuteur testamentaire :
from qiskit_ibm_runtime import Executor, QuantumProgramÉtape 3. Remplacer les tuples de type « PUB » par un QuantumProgram
Au lieu de passer une liste de tuples (PUB), lorsque vous utilisez Executor, vous créez un QuantumProgram et y ajoutez des éléments.
A accepte QuantumProgram les articles de circuit et les articles Samplex :
-
append_circuit_item: Ajoute unCircuitItem, qui est un circuit, ainsi que (facultativement) ses valeurs de paramètres. Il est exécuté tel quel, sans aucune randomisation.Utilisez cette option lorsque vous souhaitez simplement échantillonner un circuit, exactement comme le ferait Sampler avec un fichier « PUB » ne comportant aucun « twirling »; par exemple, lorsque vous soumettez une tâche d'échantillonnage simple, ou lorsque vous avez déjà inclus manuellement toutes les variantes souhaitées.
-
append_samplex_item: Ajoute unsamplexItem, qui est un circuit modèle associé à un samplex générant des ensembles de paramètres aléatoires côté serveur.Utilisez cette option lorsque vous souhaitez que le contenu du circuit soit aléatoire. Le cas principal concerne la rotation (porte ou mesure) ou l'injection de bruit. Cette fonctionnalité remplace la fonction « twirling » intégrée à Sampler.
Un seul QuantumProgram peut accepter les deux types d'éléments; chaque élément ajouté est exécuté en tant que
tâche indépendante et génère sa propre entrée dans les résultats. En règle générale, utilisez append_circuit_item lorsque votre circuit n'a pas besoin d'être aléatoire. Sinon, utilisez append_samplex_item.
Les sections suivantes présentent successivement : les circuits paramétrés qui utilisent
append_circuit_item, et la migration du twirling à l'aide de append_samplex_item.
Dans les exemples de code suivants, isa_circuit désigne le circuit qui a été transpilé afin de se conformer à l' architecture de jeu d'instructions (ISA) du backend cible. Ceci comporte isa_circuit deux paramètres.
Étape n° 3a e. Migrer des circuits paramétrés
Avec Sampler, les valeurs des paramètres correspondent au deuxième élément du tuple « PUB ». Avec Executor,
transmettez-les circuit_arguments à append_circuit_item.
Extrait :
params = np.random.rand(10, circuit.num_parameters) # 10 parameter sets
pubs = (isa_circuit, params)Exécuteur
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"]Étape n° 3b e. Migrer les « twirls » intégrés vers des annotations explicites
C'est le changement le plus important. Sampler effectue des mouvements de rotation à votre place grâce à ses options. Avec Executor, vous déclarez explicitement cette intention à l'aide de cases annotées et d'un « samplex » (de Samplomatic ).
Exemple (rotation à l'aide des options) :
sampler = Sampler(mode=backend)
sampler.options.twirling.enable_gates = True
sampler.options.twirling.enable_measure = TrueExécuteur (figurations à l'aide de boîtes et d'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
)Comme le circuit modèle et le samplex sont générés côté client, vous pouvez les examiner et les tester localement afin de vérifier le résultat avant d'envoyer quoi que ce soit vers le matériel.
Vérification : tester le circuit type localement
Vous pouvez extraire des échantillons aléatoires du samplex et les associer au circuit modèle
afin de vérifier que le samplex génère bien les valeurs de paramètres attendues.
Les valeurs des paramètres renvoyées par samplex.sample sont directement compatibles avec les
paramètres du circuit modèle.
# 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)Pour aller plus loin, vous pouvez vérifier que chaque randomisation est logiquement équivalente au
circuit d’origine, par exemple en convertissant les deux en objets Operator et en comparant leurs implémentations unitaires (après
avoir pris en compte les corrections outputs["measurement_flips.<register>"] qui annulent
l’effet de « measurement twirling »), ou en comparant les valeurs d’espérance issues d’une exécution locale
StatevectorSampler ou d’une StatevectorEstimator exécution. Consultez le guide
des entrées et sorties
du Samplomatic Samplex pour un aperçu complet.
Étape 4. Modifier la manière dont les prises de vue sont demandées
Déplacer les images de l' PUB vers QuantumProgram(shots=...). Dans Executor, cela s'applique shots à l'ensemble de la tâche. Envoyez plusieurs demandes si vous avez besoin d'un nombre de prises différent.
Extrait :
# Run — shots are passed to run()
sampler = Sampler(mode=backend)
job = sampler.run([(isa_circuit, None, 25)])Exécuteur testamentaire :
# Build a QuantumProgram — shots are on the program
program = QuantumProgram(shots=25)
program.append_circuit_item(isa_circuit)Étape 5. Mettez à jour les options si nécessaire
Executor propose moins d'options que Sampler, car les paramètres de gestion des erreurs se trouvent désormais dans vos annotations et dans samplex, et non plus dans les options.
Il existe également une différence structurelle quant à l'emplacement des paramètres.
-
Avec Sampler, tout, y compris les choix qui influent sur le post-traitement des résultats, se configure dans les options de la primitive ou dans le fichier « PUB ».
-
Avec Executor, les paramètres qui influent sur la manière dont les résultats du travail sont générés et post-traités sont définis dans le
QuantumProgram, et non dans leExecutorOptions.
Exemples :
Echantillonneur | Exécuteur |
|---|---|
shots | QuantumProgram(shots=...) |
meas_type | QuantumProgram(meas_level=...) |
ExecutorOptions ne contient que des paramètres d'exécution et d'environnement de bas niveau
qui ne modifient pas la structure des données renvoyées. Elle comprend trois groupes de premier niveau :
environment(EnvironmentOptions)execution(ExecutionOptions): Offre moins d'options que Sampler. Par exemple, il n'y a pas d'option « Executormeas_type».experimental
Il convient de noter que les options dynamical_decoupling et twirling existent dans Sampler, mais pas dans Executor. Au contraire, ces valeurs d'option sont exprimées par le biais du modèle d'exécution dirigée.
Exemple :
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)Étape 6. Mettre à jour la commande run
L'entrée d'une tâche Executor est le programme, et non des PUB.
Extrait :
# Submit a job
sampler.run([(isa_circuit, parameter_values)])Exécuteur testamentaire :
# Submit a job
executor.run(program)Etape 7. Modifier la manière dont vous accédez aux résultats
Dans Executor, les résultats sont des tableaux de type « NumPy », et non des objets BitArray . Utilisez la chaîne de caractères comme index (result[0]["meas"]) et récupérez un np.ndarray résultat. Il n'est pas nécessaire de mémoriser le chemin d'accès à l'attribut .data.<register> .
Pour passer de Sampler à Executor, remplacez result[i].data.<reg> (BitArray) par result[i]["<reg>"] (np.ndarray), puis réécrivez le post-traitement get_countsbasé sur - en opérations de type « NumPy ».
Tâche | Echantillonneur | Exécuteur |
|---|---|---|
| Récupérer les données du registre | result[0].data.meas | result[0]["meas"] |
| Type de données | BitArray | np.ndarray |
| Dictionnaire des nombres cardinaux | result[0].data.meas.get_counts() | Traiter manuellement le tableau |
| Registres multiples | result[0].data.<name> par registre | result[0]["<name>"] par registre |
| CircuitItem forme du tableau | - | (parameter_sets, shots, register_bits) |
| SamplexItem forme du tableau | - | (randomizations, parameter_sets, shots, register_bits) |
| Annuler la rotation de la mesure | Automatique | result[i]["measurement_flips.<name>"] + XOR |
Sampler's BitArray propose des aides (get_counts, slice_bits, slice_shots, expectation_values, et des masques de post-sélection). L'exécuteur renvoie des tableaux d' NumPy s brutes, ce qui vous permet d'effectuer ce post-traitement à l'aide d'opérations standard d' NumPy.
Étape 8. Gérer les résultats altérés (corrections de bits inversés)
Lorsque vous appliquez une transformation de mesure par rotation via un SamplexItem, Executor renvoie les mesures brutes
(rotées) ainsi que les corrections par inversion de bits nécessaires pour annuler la rotation.
Vous devez les appliquer manuellement; aucune correction n'est effectuée automatiquement.
Lorsque vous utilisez Executor, annulez explicitement le « twirling » à l'aide des corrections measurement_flips.<reg> et d'une opération XOR, comme le montre l'exemple suivant :
# 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_1Il n'existe pas d'étape équivalente dans Sampler, car celui-ci annule automatiquement le « twirling » à votre place.
Exemple complet : migration d'une tâche d'échantillonnage de base
Echantillonneur
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()Exécuteur
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"]