Aller au contenu principal

Rétroaction classique et flux de contrôle (circuits dynamiques)

Package versions

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.4.0

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 :

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
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister

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

circuit.h(q0)
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 plein indique un conditionnement sur 1, tandis qu'un cercle vide 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

Prochaines étapes

Recommandations