Saltar al contenido principal

Avance clásico (feedforward) y flujo de control (circuitos dinámicos)

Package versions

El código de esta página se desarrolló utilizando los siguientes requisitos. Recomendamos usar estas versiones o más recientes.

qiskit[all]~=2.4.0

Los circuitos dinámicos son herramientas poderosas con las que puedes medir qubits en la mitad de la ejecución de un circuito cuántico y luego realizar operaciones lógicas clásicas dentro del circuito, basadas en el resultado de esas mediciones intermedias del circuito (mid-circuit measurements). Este proceso también se conoce como avance clásico (classical feedforward). Si bien aún nos encontramos en una etapa temprana para entender cómo conseguir la mejor manera de aprovechar los circuitos dinámicos, la comunidad de investigación cuántica ya ha identificado una serie de casos de uso (use cases), tales como los siguientes:

Qiskit admite cuatro construcciones de flujo de control para el avance clásico (feedforward), cada una implementada como un método en QuantumCircuit. Las construcciones y sus métodos correspondientes son:

Cada uno de estos métodos devuelve un gestor de contexto y se utiliza típicamente en una declaración with. El resto de esta guía explica cada una de estas construcciones y cómo usarlas.

precaución

Existen algunas limitaciones de las operaciones de avance clásico (feedforward) y flujo de control en hardware cuántico que pueden afectar tu programa. Para obtener más información, consulta Ejecutar circuitos dinámicos.

Declaración if

La declaración if se utiliza para realizar operaciones de manera condicional basándose en el valor de un bit o registro clásico.

En el siguiente ejemplo, aplicamos una puerta de Hadamard a un qubit y lo medimos. Si el resultado es 1, entonces aplicamos una puerta X en el qubit, lo cual tiene el efecto de invertirlo (flipping it) de regreso al estado 0. Luego medimos el qubit nuevamente. El resultado de medición (resulting measurement outcome) debería ser 0 con una probabilidad del 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}

Output of the previous code cell

A la declaración with se le puede dar un objetivo de asignación que es en sí mismo un gestor de contexto (context manager) que puede ser almacenado y posteriormente utilizado para crear un bloque else, el cual se ejecuta siempre que los contenidos del bloque if no se ejecutan.

En el ejemplo a continuación, inicializamos registros con dos qubits y dos bits clásicos. Aplicamos una puerta de Hadamard al primer qubit y lo medimos. Si el resultado es 1, entonces aplicamos una puerta de Hadamard sobre el segundo qubit; de no ser así (o caso contrario, otherwise), aplicamos una puerta X sobre el segundo qubit. Finalmente medimos también el segundo 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 of the previous code cell

Además del condicionamiento sobre un solo bit clásico, también es posible condicionar sobre el valor de un registro clásico compuesto por múltiples bits.

En el ejemplo a continuación, aplicamos puertas de Hadamard a dos qubits y los medimos. Si el resultado es 01, es decir, el primer qubit es 1 y el segundo qubit es 0, entonces aplicamos una puerta X a un tercer qubit. Finalmente, medimos el tercer qubit. Ten en cuenta que, para mayor claridad, elegimos especificar el estado del tercer bit clásico, que es 0, en la condición if. En el dibujo del circuito, la condición se indica mediante los círculos sobre los bits clásicos que están siendo condicionados. Un círculo sólido indica condicionamiento para 1, mientras que un círculo sin relleno indica condicionamiento para 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 of the previous code cell

Declaración switch

La declaración switch se usa para seleccionar acciones en función del valor de un bit o registro clásico. Es similar a una declaración if, pero puedes especificar más casos para la lógica de bifurcación. El ejemplo a continuación aplica una puerta de Hadamard a un qubit y lo mide. Si el resultado es 0, aplica una puerta X en el qubit; si el resultado es 1, aplica una puerta Z. El resultado de medición debería ser 1 con una probabilidad del 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 of the previous code cell

Dado que el ejemplo anterior usó un único bit clásico, solo había dos casos posibles, por lo que podrías haber obtenido el mismo resultado usando una declaración if-else. El switch case es principalmente útil cuando se bifurca según el valor de un registro clásico compuesto por múltiples bits. El siguiente ejemplo muestra cómo construir un caso por defecto, que se ejecuta si ninguno de los casos anteriores se cumple. Ten en cuenta que en una declaración switch, solo se ejecuta uno de los bloques. No hay «caída» (fallthrough).

El ejemplo a continuación aplica puertas de Hadamard a dos qubits y los mide. Si el resultado es 00 o 11, aplica una puerta Z al tercer qubit. Si el resultado es 01, aplica una puerta Y. Si ninguno de los casos anteriores coincidió, aplica una puerta X. Finalmente, mide el tercer 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 of the previous code cell

Bucle for

Un bucle for se usa para iterar sobre una secuencia de valores clásicos y realizar algunas operaciones durante cada iteración.

El siguiente ejemplo usa un bucle for para aplicar 5 puertas X a un qubit y luego medirlo. Dado que realiza un número impar de puertas X, el efecto global es invertir el qubit del estado 0 al estado 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 of the previous code cell

Bucle while

Un bucle while se usa para repetir instrucciones mientras se cumpla alguna condición.

El ejemplo a continuación aplica puertas de Hadamard a dos qubits y los mide. Luego crea un bucle while que repite este procedimiento mientras el resultado de la medición sea 11. Como resultado, la medición final nunca debería ser 11, y las posibilidades restantes aparecen con aproximadamente la misma frecuencia.

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 of the previous code cell

Expresiones clásicas

El módulo de expresiones clásicas de Qiskit qiskit.circuit.classical contiene una representación de exploración de las operaciones de ejecución (runtime operations) en valores clásicos durante la ejecución de circuitos.

El siguiente ejemplo muestra que puedes usar el cálculo de la paridad para crear un estado GHZ de n-qubits usando circuitos dinámicos. Primero, se generan n/2n/2 pares de Bell en los qubits adyacentes. Luego, se unen (o enlazan) estos pares mediante el uso de una capa de puertas CNOT entre ellos. A continuación, mides el qubit objetivo (target qubit) de todos los CNOT anteriores y reinicias cada qubit medido al estado 0\vert 0 \rangle. Aplicas la operación XX a cada sitio no medido para el cual la paridad de todos los bits de medición precedentes sea impar. Finalmente, se aplican puertas CNOT a los qubits medidos para restablecer el entrelazamiento (entanglement) perdido en la medición.

En el cálculo de paridad, el primer elemento de la expresión construida involucra la elevación (lifting) del objeto de Python mr[0] a un nodo Value (lift se utiliza para convertir objetos arbitrarios en expresiones clásicas). Esto no es necesario para mr[1] y el posible registro clásico posterior, ya que son entradas para expr.bit_xor, y cualquier elevación necesaria se realiza automáticamente en estos casos. Tales expresiones se pueden construir en bucles y otras construcciones (constructs).

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 of the previous code cell

Store

Puedes usar la instrucción store para guardar el resultado de una expresión clásica, si esa expresión se va a usar repetidamente. Las operaciones se paralelizan automáticamente, lo que hace que tu código sea significativamente más eficiente en tiempo de ejecución.

Por ejemplo, es más natural y más eficiente en tiempo de ejecución escribir B[0]B[1]B[2]B[0] \oplus B[1] \oplus B[2] \ldots, donde B=¬AB = \neg A, que (¬A[0])(¬A[1])(¬A[2])(\neg A[0]) \oplus (\neg A[1]) \oplus (\neg A[2]) \ldots. La primera forma calcula la negación en un único paso paralelo antes de la cadena XOR, en lugar de evaluar cada negación secuencialmente dentro de la expresión.

Ejemplo completo:

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 of the previous code cell

Siguientes pasos

Recomendaciones