Aller au contenu principal

Rétroaction classique et flux de contrôle

Versions des packages

Le code de cette page a été développé avec les dépendances suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Les circuits dynamiques sont désormais disponibles sur tous les backends

La nouvelle version des circuits dynamiques est maintenant accessible à tous les utilisateurs sur l'ensemble des backends. Tu peux désormais exécuter des circuits dynamiques à l'échelle utilitaire. Consulte l'annonce pour plus de détails.

Les circuits dynamiques sont des outils puissants qui permettent de mesurer des qubits au milieu de l'exécution d'un circuit quantique, puis d'effectuer des opérations logiques classiques au sein du circuit en fonction du résultat de ces mesures intermédiaires. Ce processus est également appelé rétroaction classique. Bien que nous en soyons encore aux premières étapes de la compréhension de la meilleure façon de tirer parti des circuits dynamiques, la communauté de recherche quantique a déjà identifié plusieurs cas d'usage, notamment les suivants :

Ces améliorations apportées par les circuits dynamiques s'accompagnent cependant de compromis. Les mesures intermédiaires et les opérations classiques ont généralement un temps d'exécution plus long que les portes à deux qubits, et cette augmentation de temps risque d'annuler les bénéfices de la réduction de la profondeur du circuit. Par conséquent, réduire la durée des mesures intermédiaires est un axe d'amélioration prioritaire dans la nouvelle version des circuits dynamiques publiée par IBM Quantum®.

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 retourne un gestionnaire de contexte et s'utilise généralement avec une instruction with. Ce guide explique comment utiliser cette instruction conditionnelle.

remarque

Les exemples de code de ce guide utilisent l'instruction de mesure standard pour les mesures intermédiaires. Il est cependant recommandé d'utiliser l'instruction MidCircuitMeasure à la place, si le backend la prend en charge. Consulte la documentation sur les mesures intermédiaires pour plus de détails.

Instruction if

L'instruction if permet d'effectuer des opérations de manière conditionnelle en fonction de la valeur d'un bit ou d'un registre classique.

Dans l'exemple ci-dessous, on applique une porte Hadamard à un qubit et on le mesure. Si le résultat est 1, on applique une porte X sur le qubit, ce qui a pour effet de le faire revenir à l'état 0. On mesure ensuite le qubit à nouveau. Le résultat de la mesure devrait être 0 avec une probabilité de 100 %.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister

qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits

circuit.h(q0)
# Use MidCircuitMeasure() if it's supported by the backend.
# circuit.append(MidCircuitMeasure(), [q0], [c0])
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")

# example output counts: {'0': 1024}

Sortie de la cellule de code précédente

L'instruction with peut recevoir une cible d'affectation qui est elle-même un gestionnaire de contexte pouvant être sauvegardé et utilisé ultérieurement pour créer un bloc else, exécuté à chaque fois que le contenu du bloc if n'est pas exécuté.

Dans l'exemple ci-dessous, on initialise des registres avec deux qubits et deux bits classiques. On applique une porte Hadamard au premier qubit et on le mesure. Si le résultat est 1, on applique une porte Hadamard sur le second qubit ; sinon, on lui applique une porte X. Enfin, on mesure également le second qubit.

qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1) = qubits
(c0, c1) = clbits

circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)

circuit.draw("mpl")

# example output counts: {'01': 260, '11': 272, '10': 492}

Sortie de la cellule de code précédente

En plus de conditionner sur un seul bit classique, il est également possible de conditionner sur la valeur d'un registre classique composé de plusieurs bits.

Dans l'exemple ci-dessous, on applique des portes Hadamard à deux qubits et on les mesure. Si le résultat est 01, c'est-à-dire que le premier qubit est 1 et le second est 0, on applique une porte X à un troisième qubit. Enfin, on mesure le troisième qubit. À noter que par souci de clarté, nous avons choisi de préciser l'état du troisième bit classique, qui est 0, dans la condition if. Dans le dessin du circuit, la condition est indiquée par des cercles sur les bits classiques testés. Un cercle noir indique un conditionnement sur 1, tandis qu'un cercle blanc indique un conditionnement sur 0.

qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits

circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.if_test((clbits, 0b001)):
circuit.x(q2)
circuit.measure(q2, c2)

circuit.draw("mpl")

# example output counts: {'101': 269, '011': 260, '000': 252, '010': 243}

Sortie de la cellule de code précédente

Expressions classiques

Le module d'expressions classiques de Qiskit qiskit.circuit.classical contient une représentation expérimentale des opérations sur les valeurs classiques lors de l'exécution du circuit. En raison des limitations matérielles, seules les conditions QuantumCircuit.if_test() sont actuellement prises en charge.

L'exemple suivant montre comment utiliser le calcul de parité pour créer un état GHZ à n qubits avec des circuits dynamiques. On génère d'abord n/2n/2 paires de Bell sur des qubits adjacents. Ensuite, on relie ces paires entre elles avec une couche de portes CNOT. On mesure ensuite le qubit cible de toutes les portes CNOT précédentes et on remet chaque qubit mesuré à l'état 0\vert 0 \rangle. On applique XX à chaque site non mesuré pour lequel la parité de tous les bits précédents est impaire. Enfin, des portes CNOT sont appliquées aux qubits mesurés pour rétablir l'intrication perdue lors de la mesure.

Dans le calcul de parité, le premier élément de l'expression construite consiste à élever l'objet Python mr[0] vers un nœud Value (lift sert à convertir des objets arbitraires en expressions classiques). Cette étape n'est pas nécessaire pour mr[1] et les éventuels registres classiques suivants, car ils sont des entrées de expr.bit_xor, et toute élévation nécessaire est effectuée automatiquement dans ces cas. De telles expressions peuvent être construites dans des boucles et d'autres structures.

from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr

num_qubits = 8
if num_qubits % 2 or num_qubits < 4:
raise ValueError("num_qubits must be an even integer ≥ 4")
meas_qubits = list(range(2, num_qubits, 2)) # qubits to measure and reset

qr = QuantumRegister(num_qubits, "qr")
mr = ClassicalRegister(len(meas_qubits), "m")
qc = QuantumCircuit(qr, mr)

# Create local Bell pairs
qc.reset(qr)
qc.h(qr[::2])
for ctrl in range(0, num_qubits, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])

# Glue neighboring pairs
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])

# Measure boundary qubits between pairs,reset to 0
for k, q in enumerate(meas_qubits):
qc.measure(qr[q], mr[k])
qc.reset(qr[q])

# Parity-conditioned X corrections
# Each non-measured qubit gets flipped iff the parity (XOR) of all
# preceding measurement bits is 1
for tgt in range(num_qubits):
if tgt in meas_qubits: # skip measured qubits
continue
# all measurement registers whose physical qubit index < tgt
left_bits = [k for k, q in enumerate(meas_qubits) if q < tgt]
if not left_bits: # skip if list empty
continue

# build XOR-parity expression
parity = expr.lift(
mr[left_bits[0]]
) # lift the first bit to Value so it will be treated like a boolean.
for k in left_bits[1:]:
parity = expr.bit_xor(
mr[k], parity
) # calculate parity with all other bits
with qc.if_test(parity): # Add X if parity is 1
qc.x(qr[tgt])

# Re-entangle measured qubits
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
qc.draw(output="mpl", style="iqp", idle_wires=False, fold=-1)

Sortie de la cellule de code précédente

Trouver les backends qui prennent en charge les circuits dynamiques

Pour trouver tous les backends auxquels ton compte a accès et qui prennent en charge les circuits dynamiques, exécute un code similaire à ce qui suit. Cet exemple suppose que tu as sauvegardé tes identifiants de connexion. Tu peux aussi spécifier explicitement tes identifiants lors de l'initialisation de ton compte de service Qiskit Runtime. Cela te permettrait par exemple de voir les backends disponibles pour une instance ou un type de plan spécifique.

Remarques
  • 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 accessible à tous les utilisateurs sur l'ensemble des backends. Consulte l'annonce pour plus de détails.
from qiskit_ibm_runtime import QiskitRuntimeService

service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_boston')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_miami')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_torino')>, <IBMBackend('ibm_kingston')>]

Limitations de Qiskit Runtime

Voici les contraintes à prendre en compte lors de l'exécution de circuits dynamiques dans Qiskit Runtime.

  • En raison de la mémoire physique limitée de l'électronique de contrôle, il existe également une limite sur le nombre d'instructions if et la taille de leurs opérandes. Cette limite dépend du nombre de diffusions (broadcasts) et du nombre de bits diffusés dans un job (et non dans 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 l'évaluation. Une diffusion est un transfert de données classiques uniques, et les bits diffusés correspondent au nombre de bits classiques transférés. Considère l'exemple suivant :

    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_test sur c0 sont considérés comme une seule diffusion car le contenu de c0 n'a pas changé, et il n'est donc pas nécessaire de le diffuser à nouveau. Le if_test sur c1 constitue une deuxième diffusion. La première diffuse les trois bits de c0 et la seconde diffuse un seul bit, pour un total de quatre bits diffusés.

    Actuellement, si tu diffuses 60 bits à chaque fois, le job peut avoir environ 300 diffusions. En revanche, si tu ne diffuses qu'un seul bit à chaque fois, le job peut avoir jusqu'à 2400 diffusions.

  • L'opérande utilisé dans une instruction if_test doit faire 32 bits ou moins. Ainsi, si tu compares un ClassicalRegister entier, la taille de ce ClassicalRegister doit être de 32 bits ou moins. En revanche, si tu ne compares qu'un seul bit d'un ClassicalRegister, ce ClassicalRegister peut avoir n'importe quelle taille (puisque l'opérande ne fait qu'un bit).

    Par exemple, le bloc de code « Non valide » ne fonctionne pas car cr fait plus de 32 bits. En revanche, tu peux utiliser un registre classique de plus de 32 bits si tu ne testes qu'un seul bit, comme indiqué dans le bloc de code « Valide ».

       cr = ClassicalRegister(50)
    qr = QuantumRegister(50)
    circuit = QuantumCircuit(qr, cr)
    ...
    circ.measure(qr, cr)
    with circ.if_test((cr, 15)):
    ...
  • Les conditionnels imbriqués ne sont pas autorisés. Par exemple, le bloc de code suivant ne fonctionnera pas car il contient un if_test à l'intérieur d'un autre if_test :

       c1 = ClassicalRegister(1, "c1")
    c2 = ClassicalRegister(2, "c2")
    ...
    with circ.if_test((c1, 1)):
    with circ.if_test(c2, 1)):
    ...
  • Les instructions reset ou les mesures à l'intérieur des conditionnels ne sont pas prises 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 par Qiskit et Qiskit Runtime.

  • Lorsque 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, while et switch ne sont pas prises en charge.

Utiliser les circuits dynamiques avec Estimator

Étant donné qu'Estimator ne prend pas en charge les circuits dynamiques, tu peux utiliser Sampler et construire tes propres circuits de mesure à la place. Tu peux aussi utiliser la primitive Executor, qui prend en charge les circuits dynamiques.

Pour reproduire le comportement d'Estimator, suis ce processus :

  1. Regroupe les termes de tous les observables en une partition. Cela peut être fait en utilisant l'API PauliList, par exemple.
    remarque

    Tu peux utiliser l'attribut primitif BitArray pour calculer les valeurs d'espérance des observables fournis.

  2. Exécute un circuit de changement de base par partition (le changement de base approprié pour chaque partition). Consulte l'utilitaire complémentaire de bases de mesure measurement_bases module pour plus d'informations. Démarrer avec les utilitaires.
  3. Additionne de nouveau les résultats pour chaque partition.

Prochaines étapes

Recommandations