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:

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

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. Debido a limitaciones de hardware, actualmente solo se admiten condiciones en QuantumCircuit.if_test().

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

Siguientes pasos

Recomendaciones