Skip to main content
IBM Quantum Platform

Feedforward classique et flux de contrôle (circuits dynamiques)

  • Le code de cette page a été développé en tenant compte des exigences suivantes. Nous recommandons d'utiliser ces versions ou des versions plus récentes.

    qiskit[all]~=2.5.1
    

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, sur la base des résultats de ces mesures effectuées au milieu du circuit. Ce processus est également connu sous le nom de " feedforward" classique. Bien qu'il soit encore trop tôt pour comprendre comment tirer le meilleur parti des circuits dynamiques, la communauté de la recherche quantique a déjà identifié un certain nombre de cas d'utilisation, tels que les suivants :

Qiskit prend en charge quatre constructions de flux de contrôle pour les réseaux classiques à propagation directe, chacune étant implémentée sous forme de méthode sur QuantumCircuit. Les constructions et les méthodes correspondantes sont les suivantes :

Chacune de ces méthodes renvoie un gestionnaire de contexte et est généralement utilisée dans une with instruction. La suite de ce guide explique chacune de ces structures et comment les utiliser.

Caution

Les opérations classiques de transmission en aval et de contrôle de flux sur le matériel quantique présentent certaines limites susceptibles d'avoir une incidence sur votre programme. Pour plus d'informations, consultez la section « Exécuter des circuits dynamiques ».


if instruction

L'instruction if est utilisée pour effectuer des opérations conditionnelles basées sur la valeur d'un bit classique ou d'un registre.

Dans l'exemple ci-dessous, nous appliquons une porte de Hadamard à un qubit et nous le mesurons. Si le résultat est 1, nous appliquons une porte X sur le qubit, ce qui a pour effet de le ramener à l'état 0. Nous mesurons ensuite à nouveau le qubit. Le résultat de la mesure doit être 0 avec une probabilité de 100 %.

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}

Output:

Output of the previous code cell

Le with L'instruction peut recevoir une cible d'affectation qui est elle-même un gestionnaire de contexte qui peut être stocké et utilisé ultérieurement pour créer un bloc else, qui est exécuté chaque fois que le contenu de l'instruction if les blocs ne sont pas exécutés.

Dans l'exemple ci-dessous, nous initialisons les registres avec deux qubits et deux bits classiques. Nous appliquons une porte de Hadamard au premier qubit et le mesurons. Si le résultat est 1, nous appliquons une porte de Hadamard sur le deuxième qubit; sinon, nous appliquons une porte X sur le deuxième qubit. Enfin, nous mesurons également le deuxième 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}

Output:

Output of the previous code cell

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

Dans l'exemple ci-dessous, nous appliquons des portes Hadamard à deux qubits et les mesurons. Si le résultat est 01, c'est-à-dire que le premier qubit est 1 et le deuxième qubit est 0, alors nous appliquons une porte X à un troisième qubit. Enfin, nous mesurons le troisième qubit. Notez que pour plus de clarté, nous avons choisi de spécifier l'état du troisième bit classique, qui est 0, dans la if condition. Dans le schéma du circuit, la condition est indiquée par les cercles sur les bits classiques qui sont conditionnés. Un cercle plein indique un conditionnement sur 1, tandis qu'un cercle en pointillés 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}

Output:

Output of the previous code cell

Instruction « switch »

L'instruction « switch » sert à sélectionner des actions en fonction de la valeur d'un bit ou d'un registre classique. Cela s'apparente à une instruction « if », mais vous pouvez définir davantage de cas pour la logique de branchement. L'exemple ci-dessous applique une porte de Hadamard à un qubit, puis le mesure. Si le résultat est 0, appliquez une porte X au qubit; si le résultat est 1, appliquez une porte Z. Le résultat de la mesure devrait être égal à 1 avec une probabilité de 100 %.

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

circuit.h(q0)
circuit.measure(q0, c0)
with circuit.switch(c0) as case:
    with case(0):
        circuit.x(q0)
    with case(1):
        circuit.z(q0)
circuit.measure(q0, c0)

circuit.draw("mpl")

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

Output:

Output of the previous code cell

Comme l'exemple ci-dessus utilisait un seul bit classique, il n'y avait que deux cas possibles; vous auriez donc pu obtenir le même résultat en utilisant une instruction « if-else ». L'instruction « switch » est particulièrement utile pour effectuer une branche en fonction de la valeur d'un registre classique composé de plusieurs bits. L'exemple suivant montre comment définir un cas par défaut, qui est exécuté si aucun des cas précédents ne l'est. Notez que dans une instruction « switch », un seul des blocs est exécuté. Il n'y a pas de « fallthrough ».

L'exemple ci-dessous applique des portes d'Hadamard à deux qubits, puis les mesure. Si le résultat est soit 00, soit 11, appliquez une porte Z au troisième qubit. Si le résultat est 01, appliquez une porte « Y ». Si aucun des cas précédents ne correspond, appliquez une porte X. Enfin, mesurez le troisième qubit.

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.switch(clbits) as case:
    with case(0b000, 0b011):
        circuit.z(q2)
    with case(0b001):
        circuit.y(q2)
    with case(case.DEFAULT):
        circuit.x(q2)
circuit.measure(q2, c2)

circuit.draw("mpl")

# example output counts: {'101': 267, '110': 249, '011': 265, '000': 243}

Output:

Output of the previous code cell

Boucle For

Une boucle « for » sert à parcourir une séquence de valeurs classiques et à effectuer certaines opérations à chaque itération.

L'exemple suivant utilise une boucle « for » pour appliquer 5 portes X à un qubit, puis mesure ce dernier. Comme il effectue un nombre impair de portes X, cela a pour effet global de faire basculer le qubit de l'état 0 à l'état 1.

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

with circuit.for_loop(range(5)) as _:
    circuit.x(q0)
circuit.measure(q0, c0)

circuit.draw("mpl")

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

Output:

Output of the previous code cell

Boucle While

Une boucle « while » sert à répéter des instructions tant qu'une condition est remplie.

L'exemple ci-dessous applique des portes d'Hadamard à deux qubits, puis les mesure. Il crée ensuite une boucle « while » qui répète cette procédure tant que le résultat de la mesure est égal à 11. Par conséquent, la mesure finale ne devrait jamais être égale à 11, les autres possibilités se présentant avec une fréquence à peu près égale.

qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)

q0, q1 = qubits
c0, c1 = clbits

circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.while_loop((clbits, 0b11)):
    circuit.h([q0, q1])
    circuit.measure(q0, c0)
    circuit.measure(q1, c1)

circuit.draw("mpl")

# example output counts: {'01': 334, '10': 368, '00': 322}

Output:

Output of the previous code cell

Expressions classiques

Le module qiskit.circuit.classical d'expression classique de Qiskit contient une représentation exploratoire des opérations d'exécution sur les valeurs classiques au cours de l'exécution du circuit.

L'exemple suivant montre que vous pouvez utiliser le calcul de la parité pour créer un état GHZ à n qubits à l'aide de circuits dynamiques. Tout d'abord, générer des paires de Bell n/2n/2 sur des qubits adjacents. Ensuite, coller ces paires ensemble en utilisant une couche de barrières CNOT entre les paires. Vous mesurez ensuite le qubit cible de toutes les portes CNOT précédentes et réinitialisez chaque qubit mesuré à l'état 0\vert 0 \rangle. Vous appliquez XX à chaque site non mesuré pour lequel la parité de tous les bits précédents est impaire. Enfin, les 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 Pythonmr[0] à un Value nœud (lift est utilisé pour transformer des objets arbitraires en expressions classiques). Ceci n'est pas nécessaire pour mr[1] et le registre classique suivant éventuel, car ils sont des entrées de expr.bit_xor, et tout levage nécessaire est effectué automatiquement dans ces cas. De telles expressions peuvent être construites dans des boucles et d'autres constructions.

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)

Output:

Output of the previous code cell

Store

Vous pouvez utiliser l'instruction store pour enregistrer le résultat d'une expression classique, si celle-ci doit être réutilisée à plusieurs reprises. Les opérations sont automatiquement parallélisées, ce qui rend votre code nettement plus efficace lors de l'exécution.

Par exemple, il est plus naturel et plus efficace en exécution d'écrire B[0]B[1]B[2]B[0] \oplus B[1] \oplus B[2] \ldots, où B=¬AB = \neg A, plutôt que (¬A[0])(¬A[1])(¬A[2])(\neg A[0]) \oplus (\neg A[1]) \oplus (\neg A[2]) \ldots. La première expression calcule la négation en une seule étape parallèle avant la chaîne XOR, au lieu d'évaluer chaque négation de manière séquentielle à l'intérieur de l'expression.

Exemple complet :

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

qregs = QuantumRegister(4, "q")
creg = ClassicalRegister(3, "c")
# temp is a plain ClassicalRegister used as the store target
temp = ClassicalRegister(3, "temp")
qc = QuantumCircuit(qregs, creg, temp)

qc.h([0, 1, 2])
qc.measure([0, 1, 2], creg)

# Store bit-NOT of the full 3-bit register into temp
qc.store(temp, expr.bit_not(creg))

# Compute parity of temp using bit-indexed XOR
parity = expr.bit_xor(
    expr.bit_xor(expr.index(temp, 0), expr.index(temp, 1)),
    expr.index(temp, 2),
)

# Flip q3 if parity of ~creg is 1
with qc.if_test(parity):
    qc.x(3)

qc.measure([0, 1, 2], creg)

qc.draw("mpl")

Output:

Output of the previous code cell

Etapes suivantes

Recommandations
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.