# Algoritmo de Deutsch-Jozsa

En computación cuántica, el algoritmo de Deutsch-Jozsa es un algoritmo cuántico, propuesto por David Deutsch y Richard Jozsa en 1992. Fue uno de los primeros algoritmos diseñados para ejecutar sobre un computador cuántico y que tiene el potencial de ser más eficiente que los algoritmos clásicos al aprovechar el paralelismo inherente de los estados de superposición cuánticos.
En el problema de Deutsch-Jozsa, se tiene una función (que puede considerarse como un oráculo o caja negra) f(x$_1$, x$_2$,..., x$_n$) que toma n bits de entrada x$_1$, x$_2$,..., x$_n$ y devuelve un valor binario f(x$_1$, x$_2$,..., x$_n$)= 0 ó 1. El objetivo es determinar si la función es constante (0 en todas las entradas o 1 en todas las entradas) o balanceada (devuelve 1 para la mitad de las entradas y 0 para la otra mitad). El problema es determinar cómo es la función (constante o balanceada) aplicando entradas a la caja negra y observando su salida. A modo de ejemplo, considérese la función $f(x)=x\%2$, es decir, la función que devuelve el resto de dividir la entrada entre dos. Esta función devuelve 1 si el argumento es impar y 0 si el argumento es par, por lo que se trata de una función balanceada. La función del algoritmo sería la de llegar a esta misma conclusión con el menor número posible de iteraciones, algo que en el caso clásico requeriría la evaluación repetida de la función hasta alcanzar dos resultados diferentes, y por tanto el número de iteraciones dependería del orden en el que se escogieran las variables de entrada.

## Historia

El algoritmo de Deutsch-Jozsa generaliza el algoritmo de Deutsch (1985), que resuelve el problema planteado para el caso n=1. En el año 1992 Deutsch y Jozsa encontraron la solución al problema para un número arbitrario de bits de entrada.

## Motivación

El de Deutsch-Jozsa es un ejemplo claro de problema que resulta sencillo de resolver para un ordenador cuántico pero muy complejo para un ordenador clásico, que en el peor de los casos necesita $2^{n-1}+1$ iteraciones para determinar si la función es constante o balanceada.
Es importante remarcar que, pese a la importancia de encontrar un problema donde un ordenador cuántico arroja un mejor resultado que uno clásico, no se han encontrado hasta la fecha aplicaciones prácticas reales de este algoritmo.

## Algoritmo de Deutsch

Esta es la versión del algoritmo para una función f(x) de una sola entrada. Se utilizan dos cúbits auxiliares en los cálculos. El algoritmo se ilustra en la figura de la derecha.
El bloque H es una puerta Hadamard cuya operación es la siguiente:
   $H(|0\rangle )={\frac {1}{\sqrt {2}}}(|0\rangle +|1\rangle )$
   $H(|1\rangle )={\frac {1}{\sqrt {2}}}(|0\rangle -|1\rangle )$
El bloque U$_f$ denota el oráculo y realiza la operación siguiente:
   $U_{f}(|0\rangle |0\rangle )=|0\rangle |0\oplus f(0)\rangle =|0\rangle |f(0)\rangle$
   $U_{f}(|0\rangle |1\rangle )=|0\rangle |1\oplus f(0)\rangle =|0\rangle |{\overline {f(0)}}\rangle$
   $U_{f}(|1\rangle |0\rangle )=|1\rangle |0\oplus f(1)\rangle =|1\rangle |f(1)\rangle$
   $U_{f}(|1\rangle |1\rangle )=|1\rangle |1\oplus f(1)\rangle =|1\rangle |{\overline {f(1)}}\rangle$
Además,
   $U_{f}(|0\rangle (|0\rangle -|1\rangle ))=|0\rangle |f(0)-{\overline {f(0)}}\rangle =|0\rangle |(-1)^{f(0)}(|0\rangle -|1\rangle )$
   $U_{f}(|1\rangle (|0\rangle -|1\rangle ))=|1\rangle |f(1)-{\overline {f(1)}}\rangle =|1\rangle |(-1)^{f(1)}(|0\rangle -|1\rangle )$
   $U_{f}(|x\rangle (|0\rangle -|1\rangle ))=|x\rangle |(-1)^{f(x)}(|0\rangle -|1\rangle )$
La entrada al circuito es: $|a\rangle =|0\rangle |1\rangle$, que atraviesa dos puestas Hardamard (véase la figura) obteniéndose $|b\rangle =(1/2)(|0\rangle +|1\rangle )(|0\rangle -|1\rangle )$. Esto atraviesa el bloque U$_f$ obteniéndose
   $|c\rangle =(1/2)\{|0\rangle |(-1)^{f(0)}(|0\rangle -|1\rangle )+|1\rangle |(-1)^{f(1)}(|0\rangle -|1\rangle )\}$
Esta expresión puede escribirse:
   $|c\rangle =\left\{{\begin{array}{ll}\pm (1/2)(|0\rangle +|1\rangle )|(|0\rangle -|1\rangle )&{\mbox{si }}~f(0)=f(1)\\\pm (1/2)(|0\rangle -|1\rangle )|(|0\rangle -|1\rangle )&{\mbox{si }}~f(0)\neq f(1)\end{array}}\right.$
Al atravesar la última puerta de Hadamard se obtiene:
   $|d\rangle =\left\{{\begin{array}{ll}\pm {\frac {1}{\sqrt {2}}}|0\rangle (|0\rangle -|1\rangle )&{\mbox{si }}~f(0)=f(1)\\\pm {\frac {1}{\sqrt {2}}}|1\rangle (|0\rangle -|1\rangle )&{\mbox{si }}~f(0)\neq f(1)\end{array}}\right.$
Puesto que si $f(0)=f(1),f(0)\oplus f(1)=0$ y si $f(0)\neq f(1),f(0)\oplus f(1)=1$, podemos escribir
   $\pm {\frac {1}{\sqrt {2}}}|f(0)\oplus f(1)\rangle (|0\rangle -|1\rangle )$
Este es el resultado final: midiendo el primer qúbit de la ecuación se obtiene $f(0)\oplus f(1)$. Si resulta el valor 0, entonces la función f(x) es constante, mientras que si resulta 1, la función es balanceada.

## Algoritmo de Deutsch-Jozsa

Esta es la versión general del algoritmo para funciones f(x) de n entradas.
En este caso, la entrada al circuito es:
   $|a\rangle =|0\rangle ^{\otimes n}|1\rangle$
A continuación de las puertas Hadamard se obtiene:
   $|b\rangle ={\frac {1}{\sqrt {2^{n}}}}\sum _{x=0}^{2^{n}-1}|x\rangle {\frac {|0\rangle -|1\rangle }{\sqrt {2}}}$
A la salida del bloque U$_f$ se tiene:
   $|c\rangle =\sum _{x=0}^{2^{n}-1}(-1)^{f(x)}|x\rangle {\frac {|0\rangle -|1\rangle }{\sqrt {2}}}$
La última puerta Hadamard produce la siguiente salida
   $|d\rangle =\sum _{z=0}^{2^{n}-1}\sum _{x=0}^{2^{n}-1}(-1)^{x\cdot z+f(x)}|z\rangle {\frac {|0\rangle -|1\rangle }{\sqrt {2}}}$
Y por último, realizando la medición, se obtiene z. En el caso en que la función f(x) sea balanceda, las contribuciones para $(0,\ldots ,0)$ se cancelan y la medida de z debe dar una combinación distinta. Por el contrario, si f(x) es constante se obtiene $(0,\ldots ,0)$ en la medida, pues el resto de las contribuciones se cancelan en este caso. Por consiguiente, comprobando si z es cero o distinto de cero se sabe que la función es, respectivamente, constante o balanceada.

## Implementación del algoritmo Deutsch–Jozsa en Qiskit

A continuación, se muestra un ejemplo sencillo de cómo se puede implementar el algoritmo Deutsch–Jozsa en Python usando Qiskit, un entorno de desarrollo de software de computación cuántica de código abierto de IBM. Recorreremos cada parte del código paso a paso para mostrar cómo se traduce la teoría en un circuito cuántico funcional.

### Explicación del código
#### 1. Importando bibliotecas

Utilizamos los elementos principales de Qiskit:
* QuantumCircuit para construir circuitos cuánticos
* Aer, transpile y run para ejecutar nuestro circuito en un simulador clásico

#### 2. Creación de los Oráculos

* Oráculo Constante: Devuelve el mismo valor (0 o 1) para cada posible entrada. En el código, volteamos el qubit de salida si el oráculo siempre debe devolver 1.
* Oráculo Balanceado: Devuelve 0 para la mitad de las entradas y 1 para la otra mitad. Nuestro ejemplo usa una compuerta CNOT (cx) para voltear el qubit de salida cuando el primer qubit de entrada es 1.

#### 3. Construyendo el circuito Deutsch–Jozsa

* Comenzamos con todos los qubits de entrada en el estado $|0\rangle$. El qubit de salida se pone en $|1\rangle$ aplicando una compuerta X.
* Aplicamos compuertas de Hadamard (dj_circuit.h(qubit)) a todos los qubits. La compuerta Hadamard distribuye las amplitudes entre $|0\rangle$ y $|1\rangle$, creando superposiciones.
* Incorporamos el circuito del oráculo, que modifica el qubit de salida de acuerdo con la función (f).
* Aplicamos la compuerta Hadamard de nuevo solo a los qubits de entrada, provocando el patrón de interferencia que revelará si (f) es constante o balanceada.
* Finalmente, medimos todos los qubits de entrada.

#### 4. Interpretando los resultados

* Si (f) es constante, el circuito produce la salida $|0...0\rangle$ (todos ceros en la medición) con un 100% de probabilidad.
* Si (f) es balanceada, esperamos observar cualquier otro patrón de medición (es decir, no todos ceros).

#### 5. Ejecutando el algoritmo

Ejecutamos el circuito usando el aer_simulator integrado de Aer. Debido a que el algoritmo Deutsch–Jozsa solo necesita una ejecución (un conjunto de disparos) para distinguir entre constante y balanceada con un 100% de certeza, ejecutar múltiples disparos siempre dará el mismo resultado.

### Por qué es más rápido que una computadora determinista clásica

De forma clásica, se podría tener que “llamar” a la función (f) (nuestra “caja negra” misteriosa) muchas veces — potencialmente hasta $2^{n-1}+1$ veces— para estar absolutamente seguros de si es constante o balanceada. Sin embargo, la versión cuántica de este problema se puede resolver con una sola llamada al oráculo, más algunas compuertas cuánticas adicionales. Aunque Deutsch–Jozsa se considera más un “ejemplo didáctico” que una aplicación práctica, demuestra una de las ideas clave de los algoritmos cuánticos: aprovechar la superposición y la interferencia para reducir drásticamente el número de llamadas requeridas a la función.