Exécuter des circuits dynamiques
Versions des packages
Le code sur cette page a été développé en utilisant les exigences suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.
qiskit[all]~=2.4.0
qiskit-ibm-runtime~=0.46.1
Les circuits dynamiques sont des outils puissants avec lesquels tu peux mesurer des qubits au milieu d'une exécution de circuit quantique et ensuite effectuer des opérations de logique classique dans le circuit, sur la base du résultat de ces mesures au milieu du circuit. Ce processus est également connu sous le nom de retour d'information classique. Bien que nous soyons aux débuts de la compréhension de la meilleure façon de profiter des circuits dynamiques, la communauté de recherche quantique a déjà identifié un certain nombre de cas d'utilisation, tels que les suivants :
- Préparation efficace des états quantiques, comme l'état GHZ, l'état W (pour plus d'informations sur l'état W, se référer également à "State preparation by shallow circuits using feed forward"), et une large classe d'états produits matriciels
- Intrication longue portée efficace entre qubits sur la même puce en utilisant des circuits peu profonds
- Échantillonnage efficace de circuits de type IQP
Ces améliorations apportées par les circuits dynamiques, cependant, s'accompagnent de compromis. Les mesures au milieu du circuit et les opérations classiques ont généralement un temps d'exécution plus long que les gates à deux qubits, et cette augmentation de temps pourrait annuler les avantages d'une profondeur de circuit réduite. Par conséquent, la réduction de la durée des mesures au milieu du circuit est un axe d'amélioration prioritaire alors qu'IBM Quantum® publie la nouvelle version des circuits dynamiques. Pour d'autres restrictions lors de l'utilisation de circuits dynamiques, consulte le tableau de compatibilité des fonctionnalités Estimator ou Sampler.
La spécification OpenQASM 3 définit un certain nombre de structures de flux de contrôle, mais Qiskit Runtime ne prend actuellement en charge que l'instruction conditionnelle if. Dans le SDK Qiskit, cela correspond à la méthode if_test sur QuantumCircuit. Cette méthode renvoie un gestionnaire de contexte et est généralement utilisée dans une instruction with. Ce guide décrit comment utiliser cette instruction conditionnelle.
Les exemples de code dans ce guide utilisent l'instruction de mesure standard pour les mesures au milieu du circuit. Cependant, il est recommandé d'utiliser l'instruction MidCircuitMeasure à la place, si le Backend le prend en charge. Consulte la section Mesures au milieu du circuit pour plus de détails.
Trouver les Backends qui prennent en charge les circuits dynamiques
Pour trouver tous les Backends auxquels ton compte peut accéder et qui prennent en charge les circuits dynamiques, exécute du code comme le suivant. Cet exemple suppose que tu as sauvegardé tes identifiants de connexion. Tu pourrais également spécifier explicitement les identifiants lors de l'initialisation de ton compte de service Qiskit Runtime. Cela te permettrait de voir les Backends disponibles sur une instance ou un type de plan spécifique, par exemple.
- Les Backends disponibles pour le compte dépendent de l'instance spécifiée dans les identifiants.
- La nouvelle version des circuits dynamiques est maintenant disponible pour tous les utilisateurs sur tous les Backends. Consulte l'annonce pour plus de détails.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
# This cell is hidden from users. It hides all those "...instance was not set..." warnings.
import warnings
warnings.filterwarnings("ignore", message=".*Instance was not set*")
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_kingston')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_boston')>]
Mesures au milieu du circuit
Avant qiskit-ibm-runtime v0.43.0, measure était la seule instruction de mesure dans Qiskit. Les mesures au milieu du circuit, cependant, ont des exigences de réglage différentes des mesures terminales (mesures qui se produisent à la fin d'un circuit). Par exemple, tu dois prendre en compte la durée de l'instruction lors du réglage d'une mesure au milieu du circuit car des instructions plus longues provoquent des circuits plus bruyants. Tu n'as pas besoin de prendre en compte la durée de l'instruction pour les mesures terminales car il n'y a pas d'instructions après les mesures terminales.
L'instruction MidCircuitMeasure correspond à l'instruction measure_2 signalée dans les supported_instructions du Backend. Cependant, measure_2 n'est pas pris en charge sur tous les Backends. Utilise service.backends(filters=lambda b: "measure_2" in b.supported_instructions) pour trouver les Backends qui le prennent en charge. De nouvelles mesures pourraient être ajoutées à l'avenir, mais ce n'est pas garanti.
Méthode MidCircuitMeasure
Dans qiskit-ibm-runtime v0.43.0, l'instruction MidCircuitMeasure a été introduite. Comme son nom l'indique, c'est une nouvelle instruction de mesure optimisée pour les mesures au milieu du circuit sur les QPU IBM®. Bien que tu puisses utiliser QuantumCircuit.measure pour une mesure au milieu du circuit, en raison de sa conception, MidCircuitMeasure est généralement un meilleur choix. Par exemple, il ajoute moins de surcharge à ton circuit que lors de l'utilisation de QuantumCircuit.measure.
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime.circuit import MidCircuitMeasure
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, dynamic_circuits=True
)
circ = QuantumCircuit(2, 2)
circ.x(0)
circ.append(MidCircuitMeasure(), [0], [0])
# circ.measure([0], [0])
# circ.measure_all()
print(circ.draw(cregbundle=False))
┌───┐┌────────────┐
q_0: ┤ X ├┤0 ├
└───┘│ │
q_1: ─────┤ Measure_2 ├
│ │
c_0: ═════╡0 ╞
└────────────┘
c_1: ═══════════════════
- Il doit y avoir au moins un registre classique pour pouvoir utiliser les mesures.
- La primitive Sampler nécessite des mesures de circuit. Tu peux ajouter des mesures de circuit avec la primitive Estimator, mais elles sont ignorées.
Store
Avec qiskit-ibm-runtime version 0.47.0 ou ultérieure, tu peux utiliser l'instruction store pour enregistrer le résultat d'une expression classique, si cette expression est utilisée de manière répétée. Les opérations sont automatiquement parallélisées, ce qui rend ton code nettement plus efficace à l'exécution.
Pour plus d'informations, consulte le guide Retour d'information classique et flux de contrôle.
Lorsque tu utilises store pour enregistrer une valeur dans un registre classique sur un vrai Backend, cette valeur n'est sauvegardée en mémoire que pendant l'exécution et n'est pas copiée ni retournée dans le résultat du job.
Par exemple, dans le code suivant, temp a la même valeur que creg pendant l'exécution, et le if_test fonctionne comme prévu. Mais une fois le job terminé, le BitArray temp retourné dans le résultat du job ne contient pas la valeur de creg. C'est-à-dire que job.result()[0].data.temp vaut 0.
creg = ClassicalRegister(3, "c")
temp = ClassicalRegister(3, "temp")
...
qc.store(temp, creg)
with circuit.if_test((temp, 0b001)):
...
Exemple complet
Le code suivant crée et exécute un circuit dynamique sur le matériel IBM®.
from qiskit_ibm_runtime import SamplerV2, QiskitRuntimeService
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.transpiler import generate_preset_pass_manager
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, dynamic_circuits=True
)
# Create a dynamic circuit
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
qc = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
qc.h(q0)
qc.measure(q0, c0)
with qc.if_test((c0, 1)):
qc.x(q0)
qc.measure(q0, c0)
# Convert to an ISA circuit for the given backend
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(qc)
# Generate samplers for backend targets
sampler = SamplerV2(backend)
# Submit jobs
sampler_job = sampler.run([isa_circuit])
result = sampler_job.result()
print(
f">>> {' Job ID:':<10} {sampler_job.job_id()} ({sampler_job.status()})"
)
>>> Job ID: d88cakp789is7391vq0g (DONE)
Limitations de Qiskit Runtime
Sois conscient des contraintes suivantes lors de l'exécution de circuits dynamiques dans Qiskit Runtime.
-
En raison de la mémoire physique limitée sur l'électronique de contrôle, il y a également une limite sur le nombre d'instructions
ifet la taille de leurs opérandes. Cette limite est une fonction du nombre de diffusions et du nombre de bits diffusés dans un job (pas un circuit).Lors du traitement d'une condition
if, les données de mesure doivent être transférées vers la logique de contrôle pour effectuer cette évaluation. Une diffusion est un transfert de données classiques uniques, et les bits diffusés sont le nombre de bits classiques transférés. Considère ce qui suit :c0 = ClassicalRegister(3)c1 = ClassicalRegister(5)...with circuit.if_test((c0, 1)) ...with circuit.if_test((c0, 3)) ...with circuit.if_test((c1[2], 1)) ...Dans l'exemple de code précédent, les deux premiers objets
if_testsurc0sont considérés comme une seule diffusion car le contenu dec0n'a pas changé, et n'a donc pas besoin d'être re-diffusé. Leif_testsurc1est une deuxième diffusion. La première diffuse les trois bits dansc0et la seconde diffuse juste un bit, faisant un total de quatre bits diffusés.Actuellement, si tu diffuses 60 bits à chaque fois, alors le job peut avoir environ 300 diffusions. Si tu ne diffuses qu'un seul bit à chaque fois, cependant, le job peut avoir 2400 diffusions.
-
L'opérande utilisé dans une instruction
if_testdoit être de 32 bits ou moins. Ainsi, si tu compares unClassicalRegisterentier, la taille de ceClassicalRegisterdoit être de 32 bits ou moins. Si tu compares juste un seul bit d'unClassicalRegister, cependant, ceClassicalRegisterpeut être de n'importe quelle taille (puisque l'opérande n'est qu'un bit).Par exemple, le bloc de code "Non valide" ne fonctionne pas car
crfait plus de 32 bits. Tu peux cependant utiliser un registre classique plus large que 32 bits si tu testes seulement un bit, comme indiqué dans le bloc de code "Valide".- Non valide
- Valide
cr = ClassicalRegister(50)qr = QuantumRegister(50)circuit = QuantumCircuit(qr, cr)...circ.measure(qr, cr)with circ.if_test((cr, 15)):...cr = ClassicalRegister(50)qr = QuantumRegister(50)circuit = QuantumCircuit(qr, cr)...circ.measure(qr, cr)with circ.if_test((cr[5], 1)):... -
Les conditionnelles imbriquées ne sont pas autorisées. Par exemple, le bloc de code suivant ne fonctionnera pas car il a un
if_testà l'intérieur d'un autreif_test:- Non valide
- Valide
c1 = ClassicalRegister(1, "c1")c2 = ClassicalRegister(2, "c2")...with circ.if_test((c1, 1)):with circ.if_test(c2, 1)):...cr = ClassicalRegister(2)...with circuit.if_test((cr, 0b11)):... -
L'utilisation de
resetou de mesures dans les conditionnelles n'est pas prise en charge. -
Les opérations arithmétiques ne sont pas prises en charge.
-
Consulte le tableau des fonctionnalités OpenQASM 3 pour déterminer quelles fonctionnalités OpenQASM 3 sont prises en charge sur Qiskit et Qiskit Runtime.
-
Lorsqu'OpenQASM 3 (au lieu de
QuantumCircuit) est utilisé comme format d'entrée pour passer des circuits aux primitives Qiskit Runtime, seules les instructions pouvant être chargées dans Qiskit sont prises en charge. Les opérations classiques, par exemple, ne sont pas prises en charge car elles ne peuvent pas être chargées dans Qiskit. Consulte Importer un programme OpenQASM 3 dans Qiskit pour plus d'informations. -
Les instructions
for,whileetswitchne sont pas prises en charge.
Utiliser des circuits dynamiques avec Estimator
Puisque Estimator ne prend pas en charge les circuits dynamiques, tu peux utiliser Sampler et construire tes propres circuits de mesure à la place.
Pour reproduire le comportement d'Estimator, suis ce processus :
- Regroupe les termes de tous les observables en une partition. Cela peut être fait en utilisant l'API
PauliList, par exemple.remarqueTu peux utiliser l'attribut primitif
BitArraypour calculer les valeurs d'espérance des observables fournis. - Exécute un circuit de changement de base par partition (quel que soit le changement de base à effectuer pour chaque partition). Consulte l'utilitaire addon de bases de mesure
measurement_basesmodule pour plus d'informations. Pour plus d'informations, consulte la documentation du package d'utilitaires addon Qiskit. - Additionne à nouveau les résultats pour chaque partition.
Restrictions
Consulte tout tableau de compatibilité des fonctionnalités pour comprendre les restrictions lors de l'utilisation de circuits dynamiques. Remarque que la compatibilité des fonctionnalités ne dépend pas de la primitive.
Étapes suivantes
- Apprends comment implémenter un découplage dynamique précis en utilisant stretch.
- Consulte le guide retour d'information classique et flux de contrôle.
- Utilise la visualisation du programme de circuit pour déboguer et optimiser tes circuits dynamiques.
- Toutes les fonctions ne sont pas compatibles avec les circuits dynamiques. Consulte la section de compatibilité des fonctionnalités pour Sampler ou Executor pour plus de détails.