Algoritmo de Deutsch-Jozsa
El algoritmo de Deutsch-Jozsa es uno de los algoritmos más conocidos en la rama cuántica. Es uno de los primeros algoritmos cuánticos que surgieron, y baja notablemente la cota temporal respecto de la mejor alternativa clásica conocida. Este mismo motivó a la creación de los algoritmos más famosos como el de Shor o el de Grover, entre muchos más.
Objetivo
Sección titulada «Objetivo»El algoritmo permite determinar si una función \(f\) es constante o balanceada. Una función es constante cuando \(f\) ante cualquier entrada retorna siempre la misma salida, es decir siempre retorna \(0\) o siempre retorna \(1\). Por otra parte la función es balanceada cuando retorna mitad de veces una salida y mitad de veces otra, es decir retorna \(n/2\) veces \(0\) y \(1\) respectivamente.
El algoritmo clásico, implica recorrer, en el peor de los casos, al menos mitad de las salidas posibles (). El algoritmo cuántico realiza esta operación en una sola pasada.
Deutsch
Sección titulada «Deutsch»Se plantea en un principio una versión reducida del problema, llamado problema de Deutsch, donde la función que se evalúa es binaria:
Esto permite simplificar el algoritmo en su unidad más básica, permitiendo que se comprenda el problema que el algoritmo resuelve. Posteriormente se explicará la versión completa del algoritmo de Deutsch-Jozsa para una función de mayor complejidad.
Solución
Sección titulada «Solución»El siguiente circuito resuelve el problema de Deutsch para una función binaria:

Figura (1): Algoritmo de Deutsch en un circuito con separadores ante la aplicación de cada puerta.
En este circuito cuántico se utilizan las puertas Pauli X, Hadamard y un oráculo \(U_f\) que es un oráculo de fase. Notar que el oráculo de fase se encuentra asociado a \(f\), que es la función que se quiere determinar si es constante o balanceada.
A continuación se analiza paso a paso el estado de los cubits según los separadores provistos en el grafico, donde \(\psi_i\) es el estado en el paso \(i\), empezando con \(i=0\) y finalizando con \(i=4\):
La idea básica del algoritmo es poder reemplazar con un circuito. Si este circuito representa una función constante, la salida del primer cubit será . En caso de que el circuito represente una función balanceada, retornará .
Desarrollo elaborado
De forma detallada el estado de los cubits según los separadores provistos en la Figura 1, donde \(\psi_i\) es el estado en el paso \(i\), empezando con \(i=0\) y finalizando con \(i=4\):
Notar que se elimina el cubit \(\ket-\). Esto se debe a que no se mide ni se utiliza más en el circuito, por lo que solo estorbaría y por ende se puede omitir.
Se analizan los casos posibles. Primero en caso de que \(f\) sea constante, y luego en caso de que \(f\) sea balanceada.
A su vez se factoriza el que, como actúa de fase global, se puede descartar.
Finalmente, aplicar el último paso del circuito es simple.
Ejemplos
Sección titulada «Ejemplos»En estos ejemplos se definirán circuitos que representan tanto funciones constantes como balanceadas. Estos circuitos reemplazarán el oráculo definido previamente que abstraía la función a analizar.
Funciones Constantes
Sección titulada «Funciones Constantes»Un circuito que no posee puertas cuánticas permite representar una función constante , cuya definición se corresponde con para cualquier valor recibido como entrada (en este caso, o ).

Figura (2): Circuito con dos cubits, siendo el primero la entrada de la función y el segundo su correspondiente salida. Denota una función constante donde .
Notar que si (primer cubit en el circuito de la izquierda), el resultado de la función (segundo cubit) es , y de la misma manera si (circuito de la derecha) la salida de sigue siendo , es decir que .
Si se introduce esta función/circuito dentro del algoritmo de Deutsch, la salida debe ser . Gráficamente se observaría de la siguiente manera:

Figura (3): Algoritmo Deutsch en un gráfico con separadores ante la aplicación de cada puerta, utilizando la función constante en vez del oráculo.
Desarrollando paso a paso el estado de los cubits según los separadores provistos:
Se elimina el ya que no es utilizado en la medición. Con este resultado se llega rápidamente a que, utilizando una función constante, se deriva correctamente al resultado .
El estado inicial del algoritmo
Aplicación de sobre el cubit del oráculo.
Aplicación de puertas Hadamard sobre ambos cubits. Recordar que en este ejemplo el oráculo para es un circuito sin puertas cuánticas.
Se aplica Hadamard sobre el cubit menos significativo para obtener la respuesta final.
Ignorando el estado del cubit del oráculo (el cubit más significativo), la probabilidad de obtener es del .
regs = [QuantumRegister(2, "q"), ClassicalRegister(1, "c")]qc = QuantumCircuit(*regs)qc.h(0)qc.x(1)qc.h(1)qc.barrier()# no hace nadaqc.barrier()qc.h(0)qc.measure(0, 0)
La función descrita previamente es constante y retorna 0 ante cualquier entrada. Otro ejemplo de función constante es aquella que siempre retorna 1 como salida, la cual puede implementarse aplicando una puerta Pauli-X a la salida.
Funciones Balanceadas
Sección titulada «Funciones Balanceadas»Puede implementarse un circuito que represente una función balanceada mediante la aplicación de una puerta .

Figura (4): Circuito con dos cubits, siendo el primero la entrada de la función y el segundo su correspondiente salida. Denota una función balanceada.
Notar que si (primer cubit en el circuito de la izquierda), el resultado de la función (segundo cubit) es , y si (circuito de la derecha) la salida de es , es decir que .
Si se introduce esta función/circuito dentro del algoritmo de Deutsch, la salida, a diferencia del previo, debe ser . Gráficamente se observaria de la siguiente manera:

Figura (5): Algoritmo Deutsch en un gráfico con separadores ante la aplicación de cada puerta, utilizando la función balanceada en vez del oráculo.
Desarrollando paso a paso los estados de los cubits en cada separador se obtiene:
Una derivación del paso se puede encontrar en el artículo CNOT. Nuevamente se omite el cubit en estado porque no se utiliza en la medición.
Esto determina que, para una función balanceada, se deriva al resultado correcto. La medición resulta en .
El estado inicial del algoritmo
Aplicación de sobre el cubit del oráculo.
Aplicación de puertas Hadamard sobre ambos cubits. Recordar que en este ejemplo el oráculo para función balanceada es un circuito con la puerta , con el cubit menos significativo como control y el más significativo como objetivo.
Retroceso de fase mediante la aplicación del oráculo.
Se aplica Hadamard sobre el cubit menos significativo para obtener la respuesta final.
Ignorando el estado del cubit del oráculo (el cubit más significativo), la probabilidad de obtener es del .
regs = [QuantumRegister(2, "q"), ClassicalRegister(1, "c")]qc = QuantumCircuit(*regs)qc.h(0)qc.x(1)qc.h(1)qc.barrier()qc.cx(0, 1)qc.barrier()qc.h(0)qc.measure(0, 0)
La función descrita previamente es balanceada, retornando el mismo valor que la entrada (función identidad). Otro ejemplo de función balanceada es aquella que siempre retorna el opuesto de la entrada como salida (función de opuesto). Esta puede implementarse, por ejemplo, aplicando una puerta Pauli-X a la salida luego de haber aplicado .
Deutsch-Jozsa
Sección titulada «Deutsch-Jozsa»El problema que resuelve el algoritmo de Deutsch-Jozsa es la generalización del algoritmo previo para funciones con entradas de tamaño .
Solución
Sección titulada «Solución»El algoritmo de Deutsch-Jozsa se describe de forma genérica con el siguiente circuito:

Figura (6): Algoritmo Deutsch-Jozsa en un circuito con separadores ante la aplicación de cada puerta.
Este mantiene una estructura similar al algoritmo de Deutsch planteado en la figura 1, pero con algunos cambios:
-
Se reemplaza el cubit más significativo de a . Esto es equivalente a aplicar una puerta Pauli-X y Hadamard, pero se evitó para mejorar la legibilidad.
-
Se generaliza el cubit de entrada a cubits que representan los valores de entrada. Esto está representado por el símbolo / previo a .
-
Se aplican las puertas Hadamard a los cubits de entrada, y el oráculo es aplicado a los cubits de entrada y al cubit que de salida de la función.
A continuación se analiza paso a paso el estado de los cubits según los separadores provistos en el gráfico, donde \(\psi_i\) es el estado en el paso \(i\), empezando con \(i=0\) y finalizando con \(i=3\):
Desarrollo elaborado
Pasos 0 y 1
Sección titulada «Pasos 0 y 1»Valor de en caso de que :
Notar que en este caso denota la suma de todas las combinaciones de tamaño , por lo que el caso general (para entradas) es el siguiente:
Finalmente toma el valor:
En el último paso se omite para simplificar los cálculos, ya que no se utiliza en la medición y no interfiere con el resto del algoritmo.
Notar que es diferente a definido previamente. Este puede entenderse como:
cuando :
Notar que se convierte al producto escalar para simplificar la ecuación. Dado que la operación equivale a lo siguiente:
Se puede generalizar la ecuación para :
Finalmente reemplazando en :
Finalmente si se mide la amplitud del estado donde es
Cálculo
Al medir la amplitud de donde es :
Estas amplitudes convergen en diferentes resultados según si es constante o balanceada. Por un lado, si es constante:
Cálculo
Por otro lado, si es balanceada:
Cálculo
Notar que . Esto representa que la mitad de las salidas () es y la otra mitad es , lo cual condice con la definición de función balanceada.
Ahora, a diferencia del algoritmo previo, se puede reemplazar con un circuito que represente una función constante o una función balanceada con entradas. En caso de ser del primer tipo la probabilidad de medir es , y en caso de ser del segundo tipo la probabilidad de medir es .
Ejemplos
Sección titulada «Ejemplos»Sea una función con 3 entradas y una salida, se debe codificar este oráculo en el circuito del algoritmo de Deutsch-Jozsa.
Estado inicial del algoritmo
Aplicación de puertas Hadamard sobre los tres cubits menos significativos. Recordar que en este ejemplo el oráculo para es un circuito sin puertas cuánticas.
Aplicación del oráculo. Recordar que en este ejemplo el oráculo para es un circuito sin puertas cuánticas, por lo que no hay cambios en el estado.
Se aplica Hadamard sobre los cubits que no son del oráculo (todos excepto el más significativo). Así se obtiene la respuesta final.
Ignorando el estado del cubit del oráculo (el cubit más significativo), la probabilidad de obtener es del .
Estado inicial del algoritmo
Aplicación de puertas Hadamard sobre los tres cubits menos significativos. Recordar que en este ejemplo el oráculo para función balanceada es un circuito con la puerta .
Aplicación del oráculo con circuito formado por la puerta .
Se aplica Hadamard sobre los cubits que no son del oráculo (todos menos el más significativo). Así se obtiene la respuesta final.
Ignorando el estado del cubit del oráculo (el cubit más significativo), la probabilidad de obtener es del .
from qiskit import QuantumCircuitfrom qiskit_aer import QasmSimulatorfrom qiskit import transpile
def run_circuit(qc: QuantumCircuit): sim = QasmSimulator() transpiled = transpile(qc) result = sim.run(transpiled).result() return result
def circuit_deutsch_jozsa(function: QuantumCircuit): """ Compila un circuito para usarlo en el algoritmo de Deutsch-Jozsa. """ n = function.num_qubits - 1 qc = QuantumCircuit(n + 1, n) qc.x(n) qc.h(range(n + 1)) qc.compose(function, inplace=True) qc.h(range(n)) qc.measure(range(n), range(n)) return qc
def dj_function(num_inputs): """ Crea una función de Deutsch-Jozsa aleatoria (balanceada/constante). - num_qubits: el número de entradas de la función """
qc = QuantumCircuit(num_inputs + 1) qc.barrier() if np.random.rand() > 0.5: qc.x(num_inputs) else: qc.cx(0, num_inputs) qc.barrier() return qc
def analyze_dj_results(results): """ Analiza los resultados de una ejecución de Deutsch-Jozsa. Determina si la función es constante o balanceada segun los resultados. """ counts = results.get_counts() # obtiene la medición del primer qubit (el de arriba) key = list(counts.keys())[0] if key[-1] == "0": return key + ": constante" else: return key + ": balanceada"
num_inputs = 3if len(sys.argv) >= 2: num_inputs = int(sys.argv[1]) # permite cambiar la cantidad de entradasoracle = dj_function(num_inputs) # crea una función constante/balanceadaprint("Oráculo:")print(oracle.draw())print("Circuito Deutsch-Jozsa:")dj_circuit = circuit_deutsch_jozsa(oracle) # codifica el oráculo en el circuitoprint(dj_circuit.draw())print("Corriendo el circuito de Deutsch-Jozsa:")print(analyze_dj_results(run_circuit(dj_circuit)))
Oráculo: ░ ░q_0: ─░───────░─ ░ ░q_1: ─░───────░─ ░ ░q_2: ─░───────░─ ░ ┌───┐ ░q_3: ─░─┤ X ├─░─ ░ └───┘ ░Circuito Deutsch-Jozsa: ┌───┐ ░ ░ ┌───┐┌─┐q_0: ┤ H ├──────░───────░─┤ H ├┤M├────── ├───┤ ░ ░ ├───┤└╥┘┌─┐q_1: ┤ H ├──────░───────░─┤ H ├─╫─┤M├─── ├───┤ ░ ░ ├───┤ ║ └╥┘┌─┐q_2: ┤ H ├──────░───────░─┤ H ├─╫──╫─┤M├ ├───┤┌───┐ ░ ┌───┐ ░ └───┘ ║ ║ └╥┘q_3: ┤ X ├┤ H ├─░─┤ X ├─░───────╫──╫──╫─ └───┘└───┘ ░ └───┘ ░ ║ ║ ║c: 3/═══════════════════════════╩══╩══╩═ 0 1 2Corriendo el circuito de Deutsch-Jozsa:000: constante
Oráculo: ░ ░q_0: ─░───■───░─ ░ │ ░q_1: ─░───┼───░─ ░ │ ░q_2: ─░───┼───░─ ░ ┌─┴─┐ ░q_3: ─░─┤ X ├─░─ ░ └───┘ ░Circuito Deutsch-Jozsa: ┌───┐ ░ ░ ┌───┐┌─┐q_0: ┤ H ├──────░───■───░─┤ H ├┤M├────── ├───┤ ░ │ ░ ├───┤└╥┘┌─┐q_1: ┤ H ├──────░───┼───░─┤ H ├─╫─┤M├─── ├───┤ ░ │ ░ ├───┤ ║ └╥┘┌─┐q_2: ┤ H ├──────░───┼───░─┤ H ├─╫──╫─┤M├ ├───┤┌───┐ ░ ┌─┴─┐ ░ └───┘ ║ ║ └╥┘q_3: ┤ X ├┤ H ├─░─┤ X ├─░───────╫──╫──╫─ └───┘└───┘ ░ └───┘ ░ ║ ║ ║c: 3/═══════════════════════════╩══╩══╩═ 0 1 2Corriendo el circuito de Deutsch-Jozsa:001: balanceada