{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "17463a96",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"El algoritmo Deutsch-Jozsa\"\n",
        "description: \"Curso gratuito « IBM » sobre información y computación cuánticas\"\n",
        "---\n",
        "\n",
        "<span id=\"the-deutsch-jozsa-algorithm\" />\n",
        "\n",
        "# El algoritmo Deutsch-Jozsa\n",
        "\n",
        "El algoritmo de Deutsch supera a todos los algoritmos clásicos para un problema de consulta, pero la ventaja es bastante modesta: una consulta frente a dos.\n",
        "El algoritmo Deutsch-Jozsa amplía esta ventaja y, de hecho, puede utilizarse para resolver un par de problemas de consulta diferentes.\n",
        "\n",
        "Aquí tienes una descripción del circuito cuántico del algoritmo Deutsch-Jozsa.\n",
        "También puede ser necesario un paso clásico adicional de postprocesamiento, que no se muestra en la figura, dependiendo del problema específico que se esté resolviendo.\n",
        "\n",
        "![Algoritmo Deutsch-Jozsa](https://quantum.cloud.ibm.com/learning/images/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/Deutsch-Jozsa.svg)\n",
        "\n",
        "Por supuesto, no hemos discutido qué problemas resuelve este algoritmo; esto se hace en las dos secciones siguientes.\n",
        "\n",
        "<span id=\"the-deutsch-jozsa-problem\" />\n",
        "\n",
        "## El problema Deutsch-Jozsa\n",
        "\n",
        "Empezaremos con el problema de consulta que el algoritmo Deutsch-Jozsa pretendía resolver originalmente, que se conoce como el *problema Deutsch-Jozsa*.\n",
        "\n",
        "La función de entrada para este problema tiene la forma $f:\\Sigma^n \\rightarrow \\Sigma$ para un entero positivo arbitrario $n.$ Al igual que en el problema de Deutsch, la tarea consiste en obtener $0$ si $f$ es constante y $1$ si $f$ es equilibrado, lo que de nuevo significa que el número de cadenas de entrada en las que la función toma el valor $0$ es igual al número de cadenas de entrada en las que la función toma el valor $1$.\n",
        "\n",
        "Obsérvese que, cuando $n$ es mayor que $1,$ existen funciones de la forma $f:\\Sigma^n \\rightarrow \\Sigma$ que no son ni constantes ni equilibradas.\n",
        "Por ejemplo, la función $f:\\Sigma^2\\rightarrow\\Sigma$ definida como\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "f(00) & = 0 \\\\\n",
        "f(01) & = 0 \\\\\n",
        "f(10) & = 0 \\\\\n",
        "f(11) & = 1\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "no pertenece a ninguna de estas dos categorías.\n",
        "Para el problema Deutsch-Jozsa, simplemente no nos preocupamos por funciones como ésta, ya que se consideran entradas \"no importantes\".\n",
        "Es decir, para este problema tenemos la *promesa* de que $f$ es constante o equilibrado.\n",
        "\n",
        "<Figure title=\"Deutsch-Jozsa problem\">\n",
        "  Entrada: una función $f:\\{0,1\\}^n\\rightarrow\\{0,1\\}$ \\ Promesa: $f$ es constante o equilibrada \\ Salida: $0$ si $f$ es constante, $1$ si $f$ es equilibrado\n",
        "</Figure>\n",
        "\n",
        "El algoritmo de Deutsch-Jozsa, con su única consulta, resuelve este problema en el siguiente sentido:\n",
        "si todos y cada uno de los resultados de la medición $n$ son $0,$, entonces la función $f$ es constante;\n",
        "y, en caso contrario, si al menos uno de los resultados de la medición es $1,$, entonces la función $f$ es equilibrada.\n",
        "Otra forma de expresarlo es que al circuito descrito anteriormente le sigue una etapa clásica de posprocesamiento en la que se calcula la operación OR de los resultados de las mediciones para obtener el bit de salida del problema de Deutsch-Jozsa.\n",
        "\n",
        "<span id=\"algorithm-analysis\" />\n",
        "\n",
        "### Análisis de algoritmos\n",
        "\n",
        "Para analizar el rendimiento del algoritmo Deutsch-Jozsa para el problema Deutsch-Jozsa, es útil empezar pensando en la acción de una sola capa de puertas Hadamard.\n",
        "Una operación Hadamard puede expresarse como una matriz de la forma habitual,\n",
        "\n",
        "$$\n",
        "H = \\begin{pmatrix}\n",
        "\\frac{1}{\\sqrt{2}} & \\frac{1}{\\sqrt{2}} \\\\[2mm]\n",
        "\\frac{1}{\\sqrt{2}} & -\\frac{1}{\\sqrt{2}}\n",
        "\\end{pmatrix},\n",
        "$$\n",
        "\n",
        "pero también podemos expresar esta operación en términos de su acción sobre los estados base estándar:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "H \\vert 0\\rangle & = \\frac{1}{\\sqrt{2}} \\vert 0 \\rangle + \\frac{1}{\\sqrt{2}} \\vert 1 \\rangle\\\\[3mm]\n",
        "H \\vert 1\\rangle & = \\frac{1}{\\sqrt{2}} \\vert 0 \\rangle - \\frac{1}{\\sqrt{2}} \\vert 1 \\rangle.\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Estas dos ecuaciones pueden combinarse en una sola fórmula,\n",
        "\n",
        "$$\n",
        "H \\vert a \\rangle = \\frac{1}{\\sqrt{2}} \\vert 0 \\rangle + \\frac{1}{\\sqrt{2}} (-1)^a \\vert 1 \\rangle\n",
        "= \\frac{1}{\\sqrt{2}} \\sum_{b\\in\\{0,1\\}} (-1)^{ab} \\vert b\\rangle,\n",
        "$$\n",
        "\n",
        "lo que es cierto para ambas opciones de $a\\in\\Sigma.$\n",
        "\n",
        "Supongamos ahora que en lugar de un único qubit tenemos $n$ qubits, y que se realiza una operación Hadamard en cada uno de ellos.\n",
        "La operación combinada en los qubits $n$ se describe mediante el producto tensorial $H\\otimes \\cdots \\otimes H$ ( $n$ veces), que escribimos como $H^{\\otimes n}$ para mayor concisión y claridad.\n",
        "Utilizando la fórmula anterior, expandiéndola y simplificándola, podemos expresar la acción de esta operación combinada sobre los estados base estándar de los qubits de $n$ de la siguiente manera:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "  & H^{\\otimes n} \\vert x_{n-1} \\cdots x_1 x_0 \\rangle \\\\\n",
        "  & \\qquad = \\bigl(H \\vert x_{n-1} \\rangle \\bigr) \\otimes \\cdots \\otimes \\bigl(H \\vert x_{0} \\rangle \\bigr) \\\\\n",
        "  & \\qquad = \\Biggl( \\frac{1}{\\sqrt{2}} \\sum_{y_{n-1}\\in\\Sigma} (-1)^{x_{n-1} y_{n-1}} \\vert y_{n-1} \\rangle \\Biggr)\n",
        "  \\otimes \\cdots \\otimes\n",
        "  \\Biggl( \\frac{1}{\\sqrt{2}} \\sum_{y_{0}\\in\\Sigma} (-1)^{x_{0} y_{0}} \\vert y_{0} \\rangle \\Biggr) \\\\\n",
        "  & \\qquad = \\frac{1}{\\sqrt{2^n}} \\sum_{y_{n-1}\\cdots y_0 \\in \\Sigma^n}\n",
        "  (-1)^{x_{n-1}y_{n-1} + \\cdots + x_0 y_0} \\vert y_{n-1} \\cdots y_0 \\rangle.\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Aquí, por cierto, estamos escribiendo cadenas binarias de longitud $n$ como $x_{n-1}\\cdots x_0$ y $y_{n-1}\\cdots y_0,$ siguiendo la convención de indexación de Qiskit.\n",
        "\n",
        "Esta fórmula nos proporciona una herramienta útil para analizar el circuito cuántico anterior.\n",
        "Una vez realizada la primera capa de puertas Hadamard, el estado de los qubits de $n+1$ (incluido el qubit más a la izquierda/abajo, que se trata por separado del resto) es\n",
        "\n",
        "$$\n",
        "\\bigl( H \\vert 1 \\rangle \\bigr) \\bigl( H^{\\otimes n} \\vert 0 \\cdots 0 \\rangle \\bigr)\n",
        "= \\vert - \\rangle \\otimes \\frac{1}{\\sqrt{2^n}} \\sum_{x_{n-1}\\cdots x_0 \\in \\Sigma^n} \\vert x_{n-1} \\cdots x_0 \\rangle.\n",
        "$$\n",
        "\n",
        "Cuando se realiza la operación $U_f$, este estado se transforma en\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{\\sqrt{2^n}}\n",
        "\\sum_{x_{n-1}\\cdots x_0 \\in \\Sigma^n} (-1)^{f(x_{n-1}\\cdots x_0)} \\vert x_{n-1} \\cdots x_0 \\rangle\n",
        "$$\n",
        "\n",
        "mediante exactamente el mismo fenómeno de retroceso de fase que vimos en el análisis del algoritmo de Deutsch.\n",
        "\n",
        "A continuación, se ejecuta la segunda capa de puertas Hadamard, que (mediante la fórmula anterior) transforma este estado en\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{2^n}\n",
        "\\sum_{x_{n-1}\\cdots x_0 \\in \\Sigma^n}\n",
        "\\sum_{y_{n-1}\\cdots y_0 \\in \\Sigma^n}\n",
        "(-1)^{f(x_{n-1}\\cdots x_0) + x_{n-1}y_{n-1} + \\cdots + x_0 y_0}\n",
        "\\vert y_{n-1} \\cdots y_0 \\rangle.\n",
        "$$\n",
        "\n",
        "Esta expresión parece algo complicada, y no se puede concluir demasiado sobre las probabilidades de obtener diferentes resultados de medición sin saber más sobre la función $f.$\n",
        "\n",
        "Afortunadamente, todo lo que necesitamos saber es la probabilidad de que cada uno de los resultados de la medición sea $0$ - porque esa es la probabilidad de que el algoritmo determine que $f$ es constante.\n",
        "Esta probabilidad tiene una fórmula sencilla.\n",
        "\n",
        "$$\n",
        "\\Biggl\\vert\n",
        "\\frac{1}{2^n}\n",
        "\\sum_{x_{n-1}\\cdots x_0 \\in \\Sigma^n}\n",
        "(-1)^{f(x_{n-1}\\cdots x_0)}\n",
        "\\Biggr\\vert^2\n",
        "= \\begin{cases}\n",
        "1 & \\text{if $f$ is constant}\\\\[1mm]\n",
        "0 & \\text{if $f$ is balanced}\n",
        "\\end{cases}\n",
        "$$\n",
        "\n",
        "Ten en cuenta que estos valores corresponden a la probabilidad de medir el estado\n",
        "$\\vert 0^{\\otimes n} \\rangle$, y no directamente al bit de salida clásico final del\n",
        "problema de Deutsch-Jozsa. El algoritmo devuelve « $0$ » cuando todos los resultados de las mediciones\n",
        "son « $0$ » (lo que indica que « $f$ » es constante) y, en caso contrario, devuelve « $1$ »\n",
        "(lo que indica que « $f$ » está en equilibrio).\n",
        "\n",
        "Más en detalle, si $f$ es constante, entonces o bien $f(x_{n-1}\\cdots x_0) = 0$ para cada cadena $x_{n-1}\\cdots x_0,$ en cuyo caso el valor de la suma es $2^n,$ o $f(x_{n-1}\\cdots x_0) = 1$ para cada cadena $x_{n-1}\\cdots x_0,$ en cuyo caso el valor de la suma es $-2^n.$ Dividiendo por $2^n$ y tomando el cuadrado del valor absoluto se obtiene $1.$\n",
        "\n",
        "Si, por el contrario, $f$ está equilibrada, entonces $f$ toma el valor $0$ en la mitad de las cadenas $x_{n-1}\\cdots x_0$ y el valor $1$ en la otra mitad, por lo que los términos $+1$ y $-1$ de la suma se cancelan y nos quedamos con el valor $0.$\n",
        "\n",
        "Concluimos que el algoritmo funciona correctamente siempre que se cumpla la promesa.\n",
        "\n",
        "<span id=\"classical-difficulty\" />\n",
        "\n",
        "### Dificultad clásica\n",
        "\n",
        "El algoritmo Deutsch-Jozsa funciona siempre, nos da siempre la respuesta correcta cuando se cumple la promesa y requiere una única consulta.\n",
        "¿Qué diferencia hay con los algoritmos de consulta clásicos para el problema Deutsch-Jozsa?\n",
        "\n",
        "En primer lugar, cualquier algoritmo clásico *determinista* que resuelva correctamente el problema Deutsch-Jozsa debe realizar exponencialmente muchas consultas: $2^{n-1} + 1$ en el peor de los casos.\n",
        "El razonamiento es que, si un algoritmo determinista consulta $f$ en $2^{n-1}$ o menos cadenas diferentes, y obtiene el mismo valor de función cada vez, entonces ambas respuestas siguen siendo posibles.\n",
        "La función puede ser constante o equilibrada, pero por mala suerte todas las consultas devuelven el mismo valor de función.\n",
        "\n",
        "La segunda posibilidad puede parecer improbable, pero en los algoritmos deterministas no hay aleatoriedad ni incertidumbre, por lo que fallarán sistemáticamente en determinadas funciones.\n",
        "En este sentido, los algoritmos cuánticos tienen una ventaja significativa sobre los clásicos.\n",
        "\n",
        "Sin embargo, hay un inconveniente: los algoritmos *probabilísticos* clásicos pueden resolver el problema Deutsch-Jozsa con una probabilidad muy alta utilizando sólo unas pocas consultas.\n",
        "En concreto, si simplemente elegimos unas cuantas cadenas diferentes de longitud $n$ al azar, y consultamos $f$ sobre esas cadenas, es poco probable que obtengamos el mismo valor de función para todas ellas cuando $f$ esté equilibrado.\n",
        "\n",
        "En concreto, si elegimos $k$ cadenas de entrada $x^1,\\ldots,x^k \\in \\Sigma^n$ uniformemente al azar, evaluamos $f(x^1),\\ldots,f(x^k),$ y respondemos $0$ si los valores de la función son todos iguales, y $1$ en caso contrario, entonces siempre acertaremos cuando $f$ sea constante, y nos equivocaremos en el caso de que $f$ esté equilibrada con probabilidad justo $2^{-k + 1}.$ Si tomamos $k = 11,$ por ejemplo, este algoritmo responderá correctamente con probabilidad mayor que $99.9$ %.\n",
        "\n",
        "Por esta razón, seguimos teniendo una ventaja bastante modesta de los algoritmos cuánticos sobre los clásicos, pero no deja de ser una ventaja cuantificable que representa una mejora con respecto al algoritmo de Deutsch.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "83b0e8b5",
      "metadata": {},
      "source": [
        "<span id=\"deutsch-jozsa-with-qiskit\" />\n",
        "\n",
        "## Deutsch-Jozsa con Qiskit\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "c7839d8f",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit import QuantumCircuit\n",
        "from qiskit_aer import AerSimulator\n",
        "import numpy as np"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "59738a5c",
      "metadata": {},
      "source": [
        "Para implementar el algoritmo Deutsch-Jozsa en Qiskit, empezaremos definiendo una función `dj_query` que genera un circuito cuántico que implementa una puerta de consulta, para una función seleccionada aleatoriamente que satisface la promesa para el problema Deutsch-Jozsa.\n",
        "Con un 50% de probabilidad, la función es constante, y con un 50% de cambio, la función está equilibrada.\n",
        "Para cada una de esas dos posibilidades, la función se selecciona uniformemente entre las funciones de ese tipo.\n",
        "El argumento es el número de bits de entrada de la función.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "b39e7af8",
      "metadata": {},
      "outputs": [],
      "source": [
        "def dj_query(num_qubits):\n",
        "    # Create a circuit implementing for a query gate for a random function\n",
        "    # satisfying the promise for the Deutsch-Jozsa problem.\n",
        "\n",
        "    qc = QuantumCircuit(num_qubits + 1)\n",
        "\n",
        "    if np.random.randint(0, 2):\n",
        "        # Flip output qubit with 50% chance\n",
        "        qc.x(num_qubits)\n",
        "    if np.random.randint(0, 2):\n",
        "        # return constant circuit with 50% chance\n",
        "        return qc\n",
        "\n",
        "    # Choose half the possible input strings\n",
        "    on_states = np.random.choice(\n",
        "        range(2**num_qubits),  # numbers to sample from\n",
        "        2**num_qubits // 2,  # number of samples\n",
        "        replace=False,  # makes sure states are only sampled once\n",
        "    )\n",
        "\n",
        "    def add_cx(qc, bit_string):\n",
        "        for qubit, bit in enumerate(reversed(bit_string)):\n",
        "            if bit == \"1\":\n",
        "                qc.x(qubit)\n",
        "        return qc\n",
        "\n",
        "    for state in on_states:\n",
        "        qc.barrier()  # Barriers are added to help visualize how the functions are created.\n",
        "        qc = add_cx(qc, f\"{state:0b}\")\n",
        "        qc.mcx(list(range(num_qubits)), num_qubits)\n",
        "        qc = add_cx(qc, f\"{state:0b}\")\n",
        "\n",
        "    qc.barrier()\n",
        "\n",
        "    return qc"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "60b36f11",
      "metadata": {},
      "source": [
        "Podemos mostrar la implementación del circuito cuántico de la puerta de consulta utilizando el método `draw` como de costumbre.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "b4a6df3d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/deutsch-jozsa-algorithm/extracted-outputs/b4a6df3d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "display(dj_query(3).draw(output=\"mpl\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6ed6dd43",
      "metadata": {},
      "source": [
        "A continuación definimos una función que crea el circuito Deutsch-Jozsa, tomando como argumento una implementación de circuito cuántico de una puerta de consulta.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "79ab8817",
      "metadata": {},
      "outputs": [],
      "source": [
        "def compile_circuit(function: QuantumCircuit):\n",
        "    # Compiles a circuit for use in the Deutsch-Jozsa algorithm.\n",
        "\n",
        "    n = function.num_qubits - 1\n",
        "    qc = QuantumCircuit(n + 1, n)\n",
        "    qc.x(n)\n",
        "    qc.h(range(n + 1))\n",
        "    qc.compose(function, inplace=True)\n",
        "    qc.h(range(n))\n",
        "    qc.measure(range(n), range(n))\n",
        "    return qc"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "71c07df9",
      "metadata": {},
      "source": [
        "Por último, se define una función que ejecuta una vez el circuito Deutsch-Jozsa.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "8a6cf538",
      "metadata": {},
      "outputs": [],
      "source": [
        "def dj_algorithm(function: QuantumCircuit):\n",
        "    # Determine if a function is constant or balanced.\n",
        "\n",
        "    qc = compile_circuit(function)\n",
        "\n",
        "    result = AerSimulator().run(qc, shots=1, memory=True).result()\n",
        "    measurements = result.get_memory()\n",
        "    if \"1\" in measurements[0]:\n",
        "        return \"balanced\"\n",
        "    return \"constant\""
      ]
    },
    {
      "cell_type": "markdown",
      "id": "49113ebf",
      "metadata": {},
      "source": [
        "Podemos probar nuestra implementación eligiendo una función al azar, mostrando la implementación del circuito cuántico de una puerta de consulta para esta función y, a continuación, ejecutando el algoritmo Deutsch-Jozsa en esa función.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "aca4745e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/deutsch-jozsa-algorithm/extracted-outputs/aca4745e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "'balanced'"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "f = dj_query(3)\n",
        "display(f.draw(\"mpl\"))\n",
        "display(dj_algorithm(f))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "89e58116",
      "metadata": {},
      "source": [
        "<span id=\"the-bernstein-vazirani-problem\" />\n",
        "\n",
        "## El problema de Bernstein-Vazirani\n",
        "\n",
        "A continuación, discutiremos un problema conocido como el *problema Bernstein-Vazirani*.\n",
        "También se denomina *problema de muestreo de Fourier*, aunque existen formulaciones más generales de este problema que también reciben ese nombre.\n",
        "\n",
        "En primer lugar, introduzcamos algo de notación.\n",
        "Para dos cadenas binarias cualesquiera $x = x_{n-1} \\cdots x_0$ y $y = y_{n-1}\\cdots y_0$ de longitud $n,$ definimos\n",
        "\n",
        "$$\n",
        "x \\cdot y = x_{n-1} y_{n-1} \\oplus \\cdots \\oplus x_0 y_0.\n",
        "$$\n",
        "\n",
        "Nos referiremos a esta operación como el *producto punto binario*.\n",
        "Una forma alternativa de definirlo es así.\n",
        "\n",
        "$$\n",
        "x \\cdot y =\n",
        "\\begin{cases}\n",
        "1 & x_{{n-1}} y_{n-1} + \\cdots + x_0 y_0 \\text{ is odd}\\\\[0.5mm]\n",
        "0 & x_{{n-1}} y_{n-1} + \\cdots + x_0 y_0 \\text{ is even}\n",
        "\\end{cases}\n",
        "$$\n",
        "\n",
        "Observe que se trata de una operación simétrica, lo que significa que el resultado no cambia si intercambiamos $x$ y $y,$, por lo que somos libres de hacerlo cuando nos convenga.\n",
        "A veces es útil pensar en el producto binario por puntos $x \\cdot y$ como la paridad de los bits de $x$ en las posiciones en las que la cadena $y$ tiene un $1,$ o, equivalentemente, la paridad de los bits de $y$ en las posiciones en las que la cadena $x$ tiene un $1.$\n",
        "\n",
        "Con esta notación podemos definir el problema de Bernstein-Vazirani.\n",
        "\n",
        "<Figure title=\"Bernstein-Vazirani problem\">\n",
        "  Entrada: una función $f:\\{0,1\\}^n\\rightarrow\\{0,1\\}$ \\ Promesa: existe una cadena binaria $s = s_{n-1} \\cdots s_0$ para la cual $f(x) = s\\cdot x$ para todas $x\\in\\Sigma^n$ \\ Salida: la cadena $s$\n",
        "</Figure>\n",
        "\n",
        "En realidad no necesitamos un nuevo algoritmo cuántico para este problema; el algoritmo Deutsch-Jozsa lo resuelve.\n",
        "En aras de la claridad, vamos a referirnos al circuito cuántico de arriba, que no incluye el paso de post-procesamiento clásico de calcular el OR, como el *circuito Deutsch-Jozsa*.\n",
        "\n",
        "<span id=\"algorithm-analysis\" />\n",
        "\n",
        "### Análisis de algoritmos\n",
        "\n",
        "Para analizar cómo funciona el circuito Deutsch-Jozsa para una función que satisface la promesa para el problema Bernstein-Vazirani, empezaremos con una rápida observación.\n",
        "Utilizando el producto punto binario, podemos describir alternativamente la acción de $n$ puertas Hadamard sobre los estados base estándar de $n$ qubits como sigue.\n",
        "\n",
        "$$\n",
        "H^{\\otimes n} \\vert x \\rangle = \\frac{1}{\\sqrt{2^n}} \\sum_{y\\in\\Sigma^n} (-1)^{x\\cdot y} \\vert y\\rangle\n",
        "$$\n",
        "\n",
        "De forma similar a lo que vimos al analizar el algoritmo de Deutsch, esto se debe a que el valor $(-1)^k$ para cualquier entero $k$ sólo depende de si $k$ es par o impar.\n",
        "\n",
        "Volviendo al circuito Deutsch-Jozsa, una vez realizada la primera capa de compuertas Hadamard, el estado de los qubits de $n+1$ es\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{\\sqrt{2^n}} \\sum_{x \\in \\Sigma^n} \\vert x \\rangle.\n",
        "$$\n",
        "\n",
        "A continuación, se ejecuta la puerta de consulta, que (a través del fenómeno de retroceso de fase) transforma el estado en\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{\\sqrt{2^n}} \\sum_{x \\in \\Sigma^n} (-1)^{f(x)} \\vert x \\rangle.\n",
        "$$\n",
        "\n",
        "Utilizando nuestra fórmula para la acción de una capa de puertas Hadamard, vemos que la segunda capa de puertas Hadamard transforma entonces este estado en\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{2^n}\n",
        "\\sum_{x \\in \\Sigma^n} \\sum_{y \\in \\Sigma^n} (-1)^{f(x) + x \\cdot y} \\vert y \\rangle.\n",
        "$$\n",
        "\n",
        "Ahora podemos hacer algunas simplificaciones, en el exponente de $-1$ dentro de la suma.\n",
        "Se nos promete que $f(x) = s\\cdot x$ para alguna cadena $s = s_{n-1} \\cdots s_0,$ por lo que podemos expresar el estado como\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{2^n}\n",
        "\\sum_{x \\in \\Sigma^n} \\sum_{y \\in \\Sigma^n} (-1)^{s\\cdot x + x \\cdot y} \\vert y \\rangle.\n",
        "$$\n",
        "\n",
        "Dado que $s\\cdot x$ y $x\\cdot y$ son valores binarios, podemos sustituir la suma por el OR exclusivo, de nuevo porque lo único que importa para un número entero en el exponente de $-1$ es si es par o impar.\n",
        "Haciendo uso de la simetría del producto punto binario, obtenemos esta expresión para el estado:\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{2^n}\n",
        "\\sum_{x \\in \\Sigma^n} \\sum_{y \\in \\Sigma^n} (-1)^{(s\\cdot x) \\oplus (y \\cdot x)} \\vert y \\rangle.\n",
        "$$\n",
        "\n",
        "(Se han añadido paréntesis para mayor claridad, aunque en realidad no son necesarios porque es convencional tratar el producto binario por puntos como si tuviera mayor precedencia que el exclusivo-OR)\n",
        "\n",
        "En este punto utilizaremos la siguiente fórmula.\n",
        "\n",
        "$$\n",
        "(s\\cdot x) \\oplus (y \\cdot x) = (s \\oplus y) \\cdot x\n",
        "$$\n",
        "\n",
        "Podemos obtener la fórmula mediante una fórmula similar para los bits,\n",
        "\n",
        "$$\n",
        "(a c) \\oplus (b c) = (a \\oplus b) c,\n",
        "$$\n",
        "\n",
        "junto con una expansión del producto binario por puntos y de la función bitwise exclusive-OR:\n",
        "\n",
        "$$\n",
        "\\begin{aligned}\n",
        "(s\\cdot x) \\oplus (y \\cdot x)\n",
        "& = (s_{n-1} x_{n-1}) \\oplus \\cdots \\oplus (s_{0} x_{0}) \\oplus\n",
        "(y_{n-1} x_{n-1})  \\oplus \\cdots \\oplus (y_{0} x_{0}) \\\\\n",
        "& = (s_{n-1} \\oplus y_{n-1}) x_{n-1}  \\oplus \\cdots \\oplus (s_{0} \\oplus y_{0}) x_{0} \\\\\n",
        "& = (s \\oplus y) \\cdot x\n",
        "\\end{aligned}\n",
        "$$\n",
        "\n",
        "Esto nos permite expresar así el estado del circuito inmediatamente antes de las mediciones:\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{2^n}\n",
        "\\sum_{x \\in \\Sigma^n} \\sum_{y \\in \\Sigma^n} (-1)^{(s\\oplus y)\\cdot x} \\vert y \\rangle.\n",
        "$$\n",
        "\n",
        "El último paso consiste en utilizar otra fórmula, que funciona para todas las cadenas binarias $z = z_{n-1}\\cdots z_0.$\n",
        "\n",
        "$$\n",
        "\\frac{1}{2^n}\n",
        "\\sum_{x \\in \\Sigma^n} (-1)^{z \\cdot x}\n",
        "= \\begin{cases}\n",
        "1 & \\text{if $z = 0^n$}\\\\\n",
        "0 & \\text{if $z\\neq 0^n$}\n",
        "\\end{cases}\n",
        "$$\n",
        "\n",
        "Aquí estamos utilizando una notación simple para cadenas que utilizaremos varias veces más en la lección: $0^n$ es la cadena completamente nula de longitud $n.$\n",
        "\n",
        "Una forma sencilla de argumentar que esta fórmula funciona es considerar los dos casos por separado.\n",
        "Si $z = 0^n,$ entonces $z\\cdot x = 0$ para cada cadena $x\\in\\Sigma^n,$ por lo que el valor de cada término en la suma es $1,$ y obtenemos $1$ sumando y dividiendo por $2^n.$ Por otra parte, si cualquiera de los bits de $z$ es igual a $1,$ entonces el producto binario de puntos $z\\cdot x$ es igual a $0$ para exactamente la mitad de las posibles opciones para $x\\in\\Sigma^n$ y $1$ para la otra mitad - porque el valor del producto binario de puntos $z\\cdot x$ se voltea (de $0$ a $1$ o de $1$ a $0$ ) si volteamos cualquier bit de $x$ en una posición donde $z$ tiene un $1.$\n",
        "\n",
        "Si ahora aplicamos esta fórmula para simplificar el estado del circuito antes de las medidas, obtenemos\n",
        "\n",
        "$$\n",
        "\\vert - \\rangle \\otimes \\frac{1}{2^n}\n",
        "\\sum_{x \\in \\Sigma^n} \\sum_{y \\in \\Sigma^n} (-1)^{(s\\oplus y)\\cdot x} \\vert y \\rangle\n",
        "= \\vert - \\rangle \\otimes \\vert s \\rangle,\n",
        "$$\n",
        "\n",
        "debido a que $s\\oplus y = 0^n$ si y sólo si $y = s.$ Así pues, las mediciones revelan precisamente la cadena $s$ que buscamos.\n",
        "\n",
        "<span id=\"classical-difficulty\" />\n",
        "\n",
        "### Dificultad clásica\n",
        "\n",
        "Mientras que el circuito Deutsch-Jozsa resuelve el problema Bernstein-Vazirani con una sola consulta, cualquier algoritmo de consulta clásico debe realizar al menos $n$ consultas para resolver este problema.\n",
        "\n",
        "Esto puede razonarse mediante el llamado argumento *de la teoría de la información*, que es muy sencillo en este caso.\n",
        "Cada consulta clásica revela un único bit de información sobre la solución, y hay $n$ bits de información que deben ser descubiertos - por lo que se necesitan al menos $n$ consultas.\n",
        "\n",
        "De hecho, es posible resolver el problema Bernstein-Vazirani de forma clásica consultando la función en cada una de las cadenas $n$ que tienen un único $1,$ en cada posición posible, y $0$ para todos los demás bits, lo que revela los bits de $s$ de uno en uno.\n",
        "Por lo tanto, la ventaja de los algoritmos cuánticos sobre los clásicos para este problema es $1$ consultas frente a $n$ consultas.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "28583734",
      "metadata": {},
      "source": [
        "<span id=\"bernstein-vazirani-with-qiskit\" />\n",
        "\n",
        "## Bernstein-Vazirani con Qiskit\n",
        "\n",
        "Ya hemos implementado el circuito Deutsch-Jozsa anteriormente, y aquí lo utilizaremos para resolver el problema Bernstein-Vazirani.\n",
        "Primero definiremos una función que implemente una puerta de consulta para el problema Bernstein-Vazirani dada cualquier cadena binaria $s.$\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "11861a7e",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/learning/images/courses/fundamentals-of-quantum-algorithms/quantum-query-algorithms/deutsch-jozsa-algorithm/extracted-outputs/11861a7e-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def bv_query(s):\n",
        "    # Create a quantum circuit implementing a query gate for the\n",
        "    # Bernstein-Vazirani problem.\n",
        "\n",
        "    qc = QuantumCircuit(len(s) + 1)\n",
        "    for index, bit in enumerate(reversed(s)):\n",
        "        if bit == \"1\":\n",
        "            qc.cx(index, len(s))\n",
        "    return qc\n",
        "\n",
        "\n",
        "display(bv_query(\"1011\").draw(output=\"mpl\"))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c24e91e8",
      "metadata": {},
      "source": [
        "Ahora podemos crear una función que ejecute el circuito Deutsch-Jozsa en la función, utilizando la función `compile_circuit` que se definió anteriormente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "7db2ea99",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "'1011'"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def bv_algorithm(function: QuantumCircuit):\n",
        "    qc = compile_circuit(function)\n",
        "    result = AerSimulator().run(qc, shots=1, memory=True).result()\n",
        "    return result.get_memory()[0]\n",
        "\n",
        "\n",
        "display(bv_algorithm(bv_query(\"1011\")))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "86c52e37",
      "metadata": {},
      "source": [
        "<span id=\"remark-on-nomenclature\" />\n",
        "\n",
        "### Observación sobre la nomenclatura\n",
        "\n",
        "En el contexto del problema Bernstein-Vazirani, es habitual que se haga referencia al algoritmo Deutsch-Jozsa como \"algoritmo Bernstein-Vazirani\"\n",
        "Esto es ligeramente engañoso, porque el algoritmo *es* el algoritmo Deutsch-Jozsa, como Bernstein y Vazirani dejaron muy claro en su trabajo.\n",
        "\n",
        "Lo que hicieron Bernstein y Vazirani después de demostrar que el algoritmo Deutsch-Jozsa resuelve el problema Bernstein-Vazirani (como se ha indicado anteriormente) fue definir un problema mucho más complicado, conocido como el *problema de muestreo recursivo de Fourier*.\n",
        "Se trata de un problema muy elaborado en el que las soluciones a diferentes instancias del problema desbloquean nuevos niveles del problema dispuestos en una estructura arborescente.\n",
        "El problema Bernstein-Vazirani es esencialmente sólo el caso base de este problema más complicado.\n",
        "\n",
        "El problema de muestreo recursivo de Fourier fue el primer ejemplo conocido de problema de consulta en el que los algoritmos cuánticos tienen una ventaja denominada *superpolinómica* sobre los algoritmos probabilísticos, superando así la ventaja de la cuántica sobre la clásica ofrecida por el algoritmo Deutsch-Jozsa.\n",
        "Intuitivamente hablando, la versión recursiva del problema amplifica la ventaja de $1$ frente a $n$ de los algoritmos cuánticos a algo mucho mayor.\n",
        "\n",
        "El aspecto más difícil del análisis matemático que establece esta ventaja es demostrar que los algoritmos de consulta clásicos no pueden resolver el problema sin hacer muchas consultas.\n",
        "Esto es bastante típico; para muchos problemas puede ser muy difícil descartar enfoques clásicos creativos que los resuelvan eficientemente.\n",
        "\n",
        "El problema de Simon, y el algoritmo para él descrito en la siguiente sección, proporciona un ejemplo mucho más simple de una ventaja superpolinómica (y, de hecho, exponencial) de los algoritmos cuánticos sobre los clásicos, y por esta razón el problema de muestreo recursivo de Fourier se discute con menos frecuencia.\n",
        "No obstante, es un problema computacional interesante por derecho propio.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}