{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "c60e09a7",
   "metadata": {},
   "source": [
    "# Algoritmo de Deutsch y *phase kickback*\n",
    "\n",
    "Material autónomo en formato Jupyter para estudiar el algoritmo de Deutsch, el fenómeno de *phase kickback*, la construcción de oráculos reversibles y su implementación en Qiskit.\n",
    "\n",
    "Convenciones matemáticas usadas en todo el notebook:\n",
    "\n",
    "$\\newcommand{\\ket}[1]{|#1\\rangle}$\n",
    "$\\newcommand{\\bra}[1]{\\langle #1|}$\n",
    "$\\newcommand{\\braket}[2]{\\langle #1|#2\\rangle}$\n",
    "$\\newcommand{\\C}{\\mathbb{C}}$\n",
    "$\\newcommand{\\oplusxor}{\\oplus}$\n",
    "\n",
    "La notación de Dirac se usa de forma consistente: $\\ket{x}\\ket{y}$ significa $\\ket{x}\\otimes\\ket{y}$. En la parte matemática, el primer registro será el registro de entrada y el segundo registro será el registro auxiliar o de salida.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bda7af36",
   "metadata": {},
   "source": [
    "## 1. Introducción del tema\n",
    "\n",
    "El algoritmo de Deutsch es uno de los ejemplos más simples donde un circuito cuántico obtiene información global sobre una función booleana usando menos consultas al oráculo que cualquier procedimiento clásico determinista exacto.\n",
    "\n",
    "El problema no consiste en conocer los valores individuales $f(0)$ y $f(1)$. El objetivo es decidir una propiedad global:\n",
    "\n",
    "$$\n",
    "\\text{¿se cumple } f(0)=f(1) \\text{ o } f(0)\\neq f(1)?\n",
    "$$\n",
    "\n",
    "Para lograrlo, el algoritmo combina cuatro ideas:\n",
    "\n",
    "1. superposición en el registro de entrada;\n",
    "2. reversibilidad mediante el oráculo unitario $U_f$;\n",
    "3. *phase kickback*, que codifica $f(x)$ como una fase;\n",
    "4. interferencia mediante una compuerta de Hadamard final.\n",
    "\n",
    "La ventaja cuántica aparece porque la fase relativa creada por el oráculo contiene directamente la paridad\n",
    "\n",
    "$$\n",
    "f(0)\\oplusxor f(1),\n",
    "$$\n",
    "\n",
    "que distingue las funciones constantes de las balanceadas.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83148d58",
   "metadata": {},
   "source": [
    "## 2. Objetivos de aprendizaje\n",
    "\n",
    "Al completar este notebook, deberías poder:\n",
    "\n",
    "1. Formular el problema de Deutsch para funciones $f:\\{0,1\\}\\to\\{0,1\\}$.\n",
    "2. Clasificar las cuatro funciones booleanas de un bit como constantes o balanceadas.\n",
    "3. Justificar por qué una consulta clásica no basta para decidir el problema con certeza.\n",
    "4. Construir el oráculo reversible\n",
    "   $$\n",
    "   U_f\\ket{x}\\ket{y}=\\ket{x}\\ket{y\\oplusxor f(x)}.\n",
    "   $$\n",
    "5. Demostrar paso a paso la identidad de *phase kickback*\n",
    "   $$\n",
    "   U_f\\ket{x}\\ket{-}=(-1)^{f(x)}\\ket{x}\\ket{-}.\n",
    "   $$\n",
    "6. Derivar completamente el algoritmo de Deutsch desde el estado inicial hasta la medición.\n",
    "7. Implementar en Qiskit los cuatro oráculos posibles y el circuito completo.\n",
    "8. Diagnosticar errores comunes: qubit auxiliar mal preparado, medición del registro equivocado, confusión entre fase global y fase relativa, y orden de bits en Qiskit.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dc16552b",
   "metadata": {},
   "source": [
    "## 3. Mapa conceptual\n",
    "\n",
    "La estructura lógica del contenido es:\n",
    "\n",
    "$$\n",
    "\\text{álgebra lineal}\n",
    "\\longrightarrow\n",
    "\\text{qubits}\n",
    "\\longrightarrow\n",
    "\\text{operadores } X,Z,H\n",
    "\\longrightarrow\n",
    "\\text{oráculos reversibles}\n",
    "\\longrightarrow\n",
    "\\text{phase kickback}\n",
    "\\longrightarrow\n",
    "\\text{interferencia}\n",
    "\\longrightarrow\n",
    "\\text{Deutsch}.\n",
    "$$\n",
    "\n",
    "La identidad central que conecta los temas es:\n",
    "\n",
    "$$\n",
    "\\ket{-}=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}},\n",
    "\\qquad\n",
    "X\\ket{-}=-\\ket{-},\n",
    "$$\n",
    "\n",
    "y por ello\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{-}=(-1)^{f(x)}\\ket{x}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Después de aplicar el oráculo sobre una superposición del registro de entrada, la información queda en la fase relativa:\n",
    "\n",
    "$$\n",
    "\\frac{1}{\\sqrt{2}}\\left((-1)^{f(0)}\\ket{0}+(-1)^{f(1)}\\ket{1}\\right).\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d55a9dc9",
   "metadata": {},
   "source": [
    "## 4. Preparación del entorno\n",
    "\n",
    "La siguiente celda instala Qiskit y Qiskit Aer únicamente si no están disponibles. El código está escrito para ejecutarse sin cambios en JupyterLab, Anaconda y Google Colab.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f30c94cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Preparación del entorno de simulación.\n",
    "# La instalación se ejecuta sólo si las bibliotecas no están disponibles.\n",
    "\n",
    "import importlib\n",
    "import subprocess\n",
    "import sys\n",
    "\n",
    "try:\n",
    "    from qiskit import QuantumCircuit, transpile\n",
    "    from qiskit_aer import AerSimulator\n",
    "    from qiskit.quantum_info import Statevector, Operator\n",
    "    import qiskit\n",
    "except Exception:\n",
    "    print(\"Qiskit no está disponible. Instalando qiskit y qiskit-aer...\")\n",
    "    subprocess.check_call([\n",
    "        sys.executable, \"-m\", \"pip\", \"install\", \"--quiet\",\n",
    "        \"qiskit>=1.0\", \"qiskit-aer>=0.14\"\n",
    "    ])\n",
    "    importlib.invalidate_caches()\n",
    "    from qiskit import QuantumCircuit, transpile\n",
    "    from qiskit_aer import AerSimulator\n",
    "    from qiskit.quantum_info import Statevector, Operator\n",
    "    import qiskit\n",
    "\n",
    "import numpy as np\n",
    "np.set_printoptions(precision=3, suppress=True)\n",
    "\n",
    "print(\"Entorno listo.\")\n",
    "print(\"Versión de Qiskit:\", qiskit.__version__)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3c63a587",
   "metadata": {},
   "source": [
    "## 5. Estados de un qubit\n",
    "\n",
    "Un qubit puro es un vector unitario en $\\C^2$:\n",
    "\n",
    "$$\n",
    "\\ket{\\psi}=\\alpha\\ket{0}+\\beta\\ket{1},\n",
    "\\qquad\n",
    "\\alpha,\\beta\\in\\C,\n",
    "\\qquad\n",
    "|\\alpha|^2+|\\beta|^2=1.\n",
    "$$\n",
    "\n",
    "Los vectores de la base computacional son\n",
    "\n",
    "$$\n",
    "\\ket{0}=\\begin{pmatrix}1\\\\0\\end{pmatrix},\n",
    "\\qquad\n",
    "\\ket{1}=\\begin{pmatrix}0\\\\1\\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "La medición en la base computacional produce $0$ con probabilidad $|\\alpha|^2$ y $1$ con probabilidad $|\\beta|^2$.\n",
    "\n",
    "La condición de normalización no es decorativa: garantiza que las probabilidades sumen uno.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "630e1038",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Vectores base y utilidades matemáticas.\n",
    "\n",
    "ket0 = np.array([1, 0], dtype=complex)\n",
    "ket1 = np.array([0, 1], dtype=complex)\n",
    "\n",
    "plus = (ket0 + ket1) / np.sqrt(2)\n",
    "minus = (ket0 - ket1) / np.sqrt(2)\n",
    "\n",
    "I = np.eye(2, dtype=complex)\n",
    "X = np.array([[0, 1], [1, 0]], dtype=complex)\n",
    "Z = np.array([[1, 0], [0, -1]], dtype=complex)\n",
    "H = (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)\n",
    "\n",
    "\n",
    "def norma(v):\n",
    "    \"\"\"Calcula la norma euclidiana de un vector complejo.\"\"\"\n",
    "    return np.sqrt(np.vdot(v, v).real)\n",
    "\n",
    "\n",
    "def tensor(*vectores):\n",
    "    \"\"\"Calcula el producto tensorial de varios vectores o matrices.\"\"\"\n",
    "    resultado = np.array([1], dtype=complex)\n",
    "    for v in vectores:\n",
    "        resultado = np.kron(resultado, v)\n",
    "    return resultado\n",
    "\n",
    "\n",
    "def imprimir_vector(nombre, v, tol=1e-10):\n",
    "    \"\"\"Imprime un vector omitiendo partes imaginarias numéricamente nulas.\"\"\"\n",
    "    limpio = []\n",
    "    for z in v:\n",
    "        if abs(z.imag) < tol:\n",
    "            limpio.append(float(np.real(z)))\n",
    "        else:\n",
    "            limpio.append(z)\n",
    "    print(f\"{nombre} = {np.array(limpio)}\")\n",
    "\n",
    "imprimir_vector(\"|0>\", ket0)\n",
    "imprimir_vector(\"|1>\", ket1)\n",
    "imprimir_vector(\"|+>\", plus)\n",
    "imprimir_vector(\"|->\", minus)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b1d0732",
   "metadata": {},
   "source": [
    "## 6. Ejemplo desarrollado: normalización\n",
    "\n",
    "Considera el vector no normalizado\n",
    "\n",
    "$$\n",
    "\\ket{v}=\\ket{0}+i\\ket{1}.\n",
    "$$\n",
    "\n",
    "Primero calculamos su norma:\n",
    "\n",
    "$$\n",
    "\\lVert \\ket{v}\\rVert\n",
    "=\\sqrt{\\braket{v}{v}}\n",
    "=\\sqrt{1^\\ast 1+i^\\ast i}\n",
    "=\\sqrt{1+(-i)i}\n",
    "=\\sqrt{1+1}\n",
    "=\\sqrt{2}.\n",
    "$$\n",
    "\n",
    "Por tanto, el estado normalizado es\n",
    "\n",
    "$$\n",
    "\\ket{\\psi}=\\frac{1}{\\sqrt{2}}\\ket{0}+\\frac{i}{\\sqrt{2}}\\ket{1}.\n",
    "$$\n",
    "\n",
    "Las probabilidades de medición son\n",
    "\n",
    "$$\n",
    "P(0)=\\left|\\frac{1}{\\sqrt{2}}\\right|^2=\\frac12,\n",
    "\\qquad\n",
    "P(1)=\\left|\\frac{i}{\\sqrt{2}}\\right|^2=\\frac12.\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40d9f489",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ejemplo: normalización de |v> = |0> + i|1>.\n",
    "\n",
    "v = ket0 + 1j * ket1\n",
    "psi = v / norma(v)\n",
    "\n",
    "imprimir_vector(\"v\", v)\n",
    "print(\"Norma de v:\", norma(v))\n",
    "imprimir_vector(\"psi normalizado\", psi)\n",
    "print(\"Probabilidades:\", np.abs(psi)**2)\n",
    "print(\"Suma de probabilidades:\", np.sum(np.abs(psi)**2))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8a892f79",
   "metadata": {},
   "source": [
    "## 7. Producto tensorial y registros compuestos\n",
    "\n",
    "Para dos qubits, el estado conjunto pertenece a $\\C^2\\otimes\\C^2\\cong\\C^4$. Usaremos la convención matemática\n",
    "\n",
    "$$\n",
    "\\ket{x}\\ket{y}=\\ket{x}\\otimes\\ket{y}.\n",
    "$$\n",
    "\n",
    "La base computacional de dos qubits es\n",
    "\n",
    "$$\n",
    "\\ket{00},\\quad \\ket{01},\\quad \\ket{10},\\quad \\ket{11}.\n",
    "$$\n",
    "\n",
    "Por ejemplo,\n",
    "\n",
    "$$\n",
    "\\ket{0}\\ket{1}\n",
    "=\\begin{pmatrix}1\\\\0\\end{pmatrix}\\otimes\n",
    "\\begin{pmatrix}0\\\\1\\end{pmatrix}\n",
    "=\n",
    "\\begin{pmatrix}\n",
    "1\\begin{pmatrix}0\\\\1\\end{pmatrix}\\\\\n",
    "0\\begin{pmatrix}0\\\\1\\end{pmatrix}\n",
    "\\end{pmatrix}\n",
    "=\n",
    "\\begin{pmatrix}0\\\\1\\\\0\\\\0\\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "En la simulación con Qiskit se indicará explícitamente qué qubit se mide para evitar ambigüedades de orden de bits.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "71d91f68",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Producto tensorial de estados base.\n",
    "\n",
    "ket00 = tensor(ket0, ket0)\n",
    "ket01 = tensor(ket0, ket1)\n",
    "ket10 = tensor(ket1, ket0)\n",
    "ket11 = tensor(ket1, ket1)\n",
    "\n",
    "imprimir_vector(\"|0>|1>\", ket01)\n",
    "imprimir_vector(\"|1>|0>\", ket10)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22ca8ad2",
   "metadata": {},
   "source": [
    "## 8. Fase global y fase relativa\n",
    "\n",
    "Dos vectores que difieren sólo por una fase global representan el mismo estado físico:\n",
    "\n",
    "$$\n",
    "\\ket{\\psi}\\sim e^{i\\phi}\\ket{\\psi}.\n",
    "$$\n",
    "\n",
    "La razón es que las probabilidades no cambian:\n",
    "\n",
    "$$\n",
    "|e^{i\\phi}\\alpha|^2\n",
    "=(e^{i\\phi}\\alpha)^\\ast(e^{i\\phi}\\alpha)\n",
    "=e^{-i\\phi}\\alpha^\\ast e^{i\\phi}\\alpha\n",
    "=|\\alpha|^2.\n",
    "$$\n",
    "\n",
    "En cambio, una fase relativa sí puede cambiar resultados después de una interferencia. Por ejemplo,\n",
    "\n",
    "$$\n",
    "\\ket{+}=\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}},\n",
    "\\qquad\n",
    "\\ket{-}=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\n",
    "$$\n",
    "\n",
    "tienen las mismas probabilidades en la base computacional, pero se distinguen si aplicamos $H$ antes de medir:\n",
    "\n",
    "$$\n",
    "H\\ket{+}=\\ket{0},\n",
    "\\qquad\n",
    "H\\ket{-}=\\ket{1}.\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "94bfbb75",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Fase global: no cambia probabilidades.\n",
    "# Fase relativa: puede cambiar el resultado después de aplicar H.\n",
    "\n",
    "phi = np.pi / 3\n",
    "psi_global = np.exp(1j * phi) * plus\n",
    "\n",
    "print(\"Probabilidades de |+>:\", np.abs(plus)**2)\n",
    "print(\"Probabilidades de exp(i*pi/3)|+>:\", np.abs(psi_global)**2)\n",
    "\n",
    "imprimir_vector(\"H|+>\", H @ plus)\n",
    "imprimir_vector(\"H|->\", H @ minus)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7cb1ed34",
   "metadata": {},
   "source": [
    "## 9. Operadores elementales: $X$, $Z$ y $H$\n",
    "\n",
    "Las matrices básicas que usaremos son\n",
    "\n",
    "$$\n",
    "X=\\begin{pmatrix}0&1\\\\1&0\\end{pmatrix},\n",
    "\\qquad\n",
    "Z=\\begin{pmatrix}1&0\\\\0&-1\\end{pmatrix},\n",
    "\\qquad\n",
    "H=\\frac{1}{\\sqrt{2}}\\begin{pmatrix}1&1\\\\1&-1\\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "Sus acciones sobre la base computacional son:\n",
    "\n",
    "$$\n",
    "X\\ket{0}=\\ket{1},\\quad X\\ket{1}=\\ket{0},\n",
    "$$\n",
    "\n",
    "$$\n",
    "Z\\ket{0}=\\ket{0},\\quad Z\\ket{1}=-\\ket{1},\n",
    "$$\n",
    "\n",
    "$$\n",
    "H\\ket{0}=\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}}=\\ket{+},\n",
    "\\qquad\n",
    "H\\ket{1}=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}=\\ket{-}.\n",
    "$$\n",
    "\n",
    "El operador $H$ convierte información de fase relativa en diferencia de amplitudes medibles.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8d4e1ee3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Acción de X, Z y H sobre estados relevantes.\n",
    "\n",
    "for nombre, matriz in [(\"X\", X), (\"Z\", Z), (\"H\", H)]:\n",
    "    print(f\"\\nOperador {nombre}\")\n",
    "    imprimir_vector(f\"{nombre}|0>\", matriz @ ket0)\n",
    "    imprimir_vector(f\"{nombre}|1>\", matriz @ ket1)\n",
    "\n",
    "print(\"\\nEstados de Hadamard:\")\n",
    "imprimir_vector(\"H|0>\", H @ ket0)\n",
    "imprimir_vector(\"H|1>\", H @ ket1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b61e5701",
   "metadata": {},
   "source": [
    "## 10. Eigenestados de $X$ y $Z$\n",
    "\n",
    "Un vector no nulo $\\ket{v}$ es eigenestado de un operador $A$ si existe un escalar $\\lambda$ tal que\n",
    "\n",
    "$$\n",
    "A\\ket{v}=\\lambda\\ket{v}.\n",
    "$$\n",
    "\n",
    "Para $Z$:\n",
    "\n",
    "$$\n",
    "Z\\ket{0}=\\ket{0}=1\\cdot\\ket{0},\n",
    "\\qquad\n",
    "Z\\ket{1}=-\\ket{1}=(-1)\\cdot\\ket{1}.\n",
    "$$\n",
    "\n",
    "Por tanto, $\\ket{0}$ y $\\ket{1}$ son eigenestados de $Z$.\n",
    "\n",
    "Para $X$, los estados relevantes son $\\ket{+}$ y $\\ket{-}$. Demostremos el caso clave:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "X\\ket{-}\n",
    "&=X\\left(\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\\right)\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}X\\ket{0}-\\frac{1}{\\sqrt{2}}X\\ket{1}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{1}-\\frac{1}{\\sqrt{2}}\\ket{0}\\\\\n",
    "&=-\\left(\\frac{1}{\\sqrt{2}}\\ket{0}-\\frac{1}{\\sqrt{2}}\\ket{1}\\right)\\\\\n",
    "&=-\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Esta igualdad es el mecanismo algebraico que permite el *phase kickback*.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f39f76f4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Verificación numérica de eigenestados.\n",
    "\n",
    "imprimir_vector(\"X|+>\", X @ plus)\n",
    "imprimir_vector(\"|+>\", plus)\n",
    "print(\"¿X|+> = |+>?\", np.allclose(X @ plus, plus))\n",
    "\n",
    "imprimir_vector(\"X|->\", X @ minus)\n",
    "imprimir_vector(\"-|->\", -minus)\n",
    "print(\"¿X|-> = -|->?\", np.allclose(X @ minus, -minus))\n",
    "\n",
    "print(\"\\nEigenestados de Z:\")\n",
    "print(\"¿Z|0> = |0>?\", np.allclose(Z @ ket0, ket0))\n",
    "print(\"¿Z|1> = -|1>?\", np.allclose(Z @ ket1, -ket1))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfdd26cc",
   "metadata": {},
   "source": [
    "## 11. Ejercicio guiado 1: normalización y fase observable\n",
    "\n",
    "**Enunciado.** Considera\n",
    "\n",
    "$$\n",
    "\\ket{\\phi}=\\frac{1}{2}\\ket{0}+\\frac{\\sqrt{3}}{2}\\ket{1},\n",
    "\\qquad\n",
    "\\ket{\\chi}=\\frac{1}{2}\\ket{0}-\\frac{\\sqrt{3}}{2}\\ket{1}.\n",
    "$$\n",
    "\n",
    "1. Verifica que ambos vectores están normalizados.\n",
    "2. Calcula las probabilidades de medir $0$ y $1$ en la base computacional.\n",
    "3. Determina si $\\ket{\\phi}$ y $\\ket{\\chi}$ son el mismo estado físico.\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "Para $\\ket{\\phi}$:\n",
    "\n",
    "$$\n",
    "\\lVert\\ket{\\phi}\\rVert^2\n",
    "=\\left|\\frac12\\right|^2+\\left|\\frac{\\sqrt3}{2}\\right|^2\n",
    "=\\frac14+\\frac34\n",
    "=1.\n",
    "$$\n",
    "\n",
    "Para $\\ket{\\chi}$:\n",
    "\n",
    "$$\n",
    "\\lVert\\ket{\\chi}\\rVert^2\n",
    "=\\left|\\frac12\\right|^2+\\left|-\\frac{\\sqrt3}{2}\\right|^2\n",
    "=\\frac14+\\frac34\n",
    "=1.\n",
    "$$\n",
    "\n",
    "Las probabilidades en la base computacional coinciden:\n",
    "\n",
    "$$\n",
    "P(0)=\\frac14,\n",
    "\\qquad\n",
    "P(1)=\\frac34.\n",
    "$$\n",
    "\n",
    "Sin embargo, no existe una fase global $e^{i\\theta}$ tal que\n",
    "\n",
    "$$\n",
    "\\ket{\\chi}=e^{i\\theta}\\ket{\\phi}.\n",
    "$$\n",
    "\n",
    "Si la primera amplitud debe conservarse, entonces $e^{i\\theta}=1$. Pero con $e^{i\\theta}=1$, la segunda amplitud no cambia de signo. Por tanto, la diferencia es una fase relativa, no una fase global.\n",
    "\n",
    "**Interpretación.** Una medición directa en la base computacional no distingue estos dos estados, pero una operación posterior de interferencia sí puede distinguirlos.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "884ccffc",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Verificación del ejercicio guiado 1.\n",
    "\n",
    "phi = 0.5 * ket0 + (np.sqrt(3) / 2) * ket1\n",
    "chi = 0.5 * ket0 - (np.sqrt(3) / 2) * ket1\n",
    "\n",
    "print(\"Norma de phi:\", norma(phi))\n",
    "print(\"Norma de chi:\", norma(chi))\n",
    "print(\"Probabilidades de phi:\", np.abs(phi)**2)\n",
    "print(\"Probabilidades de chi:\", np.abs(chi)**2)\n",
    "\n",
    "# Comparación después de Hadamard: aquí la fase relativa sí cambia las probabilidades.\n",
    "print(\"Probabilidades después de H sobre phi:\", np.abs(H @ phi)**2)\n",
    "print(\"Probabilidades después de H sobre chi:\", np.abs(H @ chi)**2)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "133d10de",
   "metadata": {},
   "source": [
    "## 12. Funciones booleanas de un bit\n",
    "\n",
    "Una función booleana de un bit es una aplicación\n",
    "\n",
    "$$\n",
    "f:\\{0,1\\}\\to\\{0,1\\}.\n",
    "$$\n",
    "\n",
    "Como el dominio tiene dos entradas, una función queda determinada por el par ordenado\n",
    "\n",
    "$$\n",
    "(f(0),f(1)).\n",
    "$$\n",
    "\n",
    "Cada valor puede ser $0$ o $1$. Por la regla del producto, hay\n",
    "\n",
    "$$\n",
    "2\\cdot 2=2^2=4\n",
    "$$\n",
    "\n",
    "funciones posibles.\n",
    "\n",
    "La clasificación usada por el algoritmo de Deutsch es:\n",
    "\n",
    "$$\n",
    "\\text{constante} \\iff f(0)=f(1),\n",
    "$$\n",
    "\n",
    "$$\n",
    "\\text{balanceada} \\iff f(0)\\neq f(1).\n",
    "$$\n",
    "\n",
    "Para funciones de un bit, esto equivale a calcular la paridad\n",
    "\n",
    "$$\n",
    "f(0)\\oplusxor f(1).\n",
    "$$\n",
    "\n",
    "Si la paridad es $0$, la función es constante. Si la paridad es $1$, la función es balanceada.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "798cd2b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Enumeración de todas las funciones booleanas de un bit.\n",
    "\n",
    "funciones = {\n",
    "    \"00\": {0: 0, 1: 0},\n",
    "    \"11\": {0: 1, 1: 1},\n",
    "    \"01\": {0: 0, 1: 1},\n",
    "    \"10\": {0: 1, 1: 0},\n",
    "}\n",
    "\n",
    "for etiqueta, tabla in funciones.items():\n",
    "    f0, f1 = tabla[0], tabla[1]\n",
    "    paridad = f0 ^ f1\n",
    "    tipo = \"constante\" if paridad == 0 else \"balanceada\"\n",
    "    print(f\"{etiqueta}: f(0)={f0}, f(1)={f1}, f(0) XOR f(1)={paridad} -> {tipo}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1621e610",
   "metadata": {},
   "source": [
    "## 13. Ejercicio guiado 2: conteo y clasificación\n",
    "\n",
    "**Enunciado.** Determina cuántas funciones $f:\\{0,1\\}\\to\\{0,1\\}$ existen y clasifícalas.\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "El dominio tiene dos elementos: $0$ y $1$. Para cada elemento del dominio, la función puede tomar dos valores posibles: $0$ o $1$.\n",
    "\n",
    "Para $x=0$:\n",
    "\n",
    "$$\n",
    "f(0)\\in\\{0,1\\}.\n",
    "$$\n",
    "\n",
    "Para $x=1$:\n",
    "\n",
    "$$\n",
    "f(1)\\in\\{0,1\\}.\n",
    "$$\n",
    "\n",
    "Por independencia de las elecciones:\n",
    "\n",
    "$$\n",
    "\\#\\{f:\\{0,1\\}\\to\\{0,1\\}\\}=2^2=4.\n",
    "$$\n",
    "\n",
    "Las cuatro funciones son:\n",
    "\n",
    "$$\n",
    "\\begin{array}{c|cc|c}\n",
    "\\text{etiqueta} & f(0)&f(1)&\\text{tipo}\\\\\n",
    "\\hline\n",
    "00&0&0&\\text{constante}\\\\\n",
    "11&1&1&\\text{constante}\\\\\n",
    "01&0&1&\\text{balanceada}\\\\\n",
    "10&1&0&\\text{balanceada}\n",
    "\\end{array}\n",
    "$$\n",
    "\n",
    "**Interpretación.** El algoritmo de Deutsch no identifica cuál de las cuatro funciones se tiene; identifica la propiedad global constante/balanceada.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b81ec964",
   "metadata": {},
   "source": [
    "## 14. Consulta clásica exacta\n",
    "\n",
    "Supón que sólo podemos consultar valores de $f$.\n",
    "\n",
    "Con una sola consulta, por ejemplo a $f(0)$, se obtiene un valor $b\\in\\{0,1\\}$. Después de conocer $f(0)=b$, todavía hay dos funciones compatibles:\n",
    "\n",
    "$$\n",
    "f(1)=b\n",
    "\\quad\\Rightarrow\\quad\n",
    "\\text{constante},\n",
    "$$\n",
    "\n",
    "$$\n",
    "f(1)=1\\oplusxor b\n",
    "\\quad\\Rightarrow\\quad\n",
    "\\text{balanceada}.\n",
    "$$\n",
    "\n",
    "Por tanto, una sola consulta clásica no decide el problema con certeza. Se necesitan dos consultas para conocer ambos valores $f(0)$ y $f(1)$.\n",
    "\n",
    "El circuito cuántico obtiene la paridad $f(0)\\oplusxor f(1)$ con una sola llamada a $U_f$ porque consulta el oráculo en superposición y transforma fases relativas en una salida medible.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "63cc22ff",
   "metadata": {},
   "source": [
    "## 15. Oráculos reversibles\n",
    "\n",
    "Una función clásica $f:\\{0,1\\}\\to\\{0,1\\}$ no siempre puede implementarse directamente como\n",
    "\n",
    "$$\n",
    "\\ket{x}\\mapsto\\ket{f(x)}\n",
    "$$\n",
    "\n",
    "mediante una operación unitaria, porque una operación unitaria debe ser reversible. Por ejemplo, si $f(0)=f(1)=0$, entonces tanto $\\ket{0}$ como $\\ket{1}$ se enviarían a $\\ket{0}$, perdiendo información sobre la entrada.\n",
    "\n",
    "La solución estándar es conservar la entrada y escribir el valor de la función mediante XOR en un segundo registro:\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{y}\n",
    "=\n",
    "\\ket{x}\\ket{y\\oplusxor f(x)}.\n",
    "$$\n",
    "\n",
    "Esta transformación sí es reversible porque aplicar el mismo operador dos veces recupera el estado inicial:\n",
    "\n",
    "$$\n",
    "U_f^2\\ket{x}\\ket{y}\n",
    "=\n",
    "U_f\\ket{x}\\ket{y\\oplusxor f(x)}\n",
    "=\n",
    "\\ket{x}\\ket{y\\oplusxor f(x)\\oplusxor f(x)}\n",
    "=\n",
    "\\ket{x}\\ket{y}.\n",
    "$$\n",
    "\n",
    "La igualdad final usa $a\\oplusxor a=0$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ce735b7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Matriz de U_f en la convención matemática |x>|y> con índice 2*x + y.\n",
    "\n",
    "def matriz_oraculo(funcion):\n",
    "    \"\"\"Devuelve la matriz 4x4 de U_f para una función f:{0,1}->{0,1}.\"\"\"\n",
    "    U = np.zeros((4, 4), dtype=complex)\n",
    "    for x in [0, 1]:\n",
    "        for y in [0, 1]:\n",
    "            y_salida = y ^ funcion[x]\n",
    "            indice_entrada = 2 * x + y\n",
    "            indice_salida = 2 * x + y_salida\n",
    "            U[indice_salida, indice_entrada] = 1\n",
    "    return U\n",
    "\n",
    "for etiqueta, tabla in funciones.items():\n",
    "    U = matriz_oraculo(tabla)\n",
    "    print(f\"\\nU_f para función {etiqueta}:\")\n",
    "    print(U.astype(int))\n",
    "    print(\"¿U_f es unitaria?\", np.allclose(U.conj().T @ U, np.eye(4)))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58f8794d",
   "metadata": {},
   "source": [
    "## 16. Ejemplo desarrollado: oráculo para $f(x)=x$\n",
    "\n",
    "Si $f(0)=0$ y $f(1)=1$, entonces $f(x)=x$. El oráculo satisface\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{y}=\\ket{x}\\ket{y\\oplusxor x}.\n",
    "$$\n",
    "\n",
    "Calculamos las cuatro entradas base:\n",
    "\n",
    "$$\n",
    "U_f\\ket{0}\\ket{0}=\\ket{0}\\ket{0\\oplusxor 0}=\\ket{0}\\ket{0}=\\ket{00},\n",
    "$$\n",
    "\n",
    "$$\n",
    "U_f\\ket{0}\\ket{1}=\\ket{0}\\ket{1\\oplusxor 0}=\\ket{0}\\ket{1}=\\ket{01},\n",
    "$$\n",
    "\n",
    "$$\n",
    "U_f\\ket{1}\\ket{0}=\\ket{1}\\ket{0\\oplusxor 1}=\\ket{1}\\ket{1}=\\ket{11},\n",
    "$$\n",
    "\n",
    "$$\n",
    "U_f\\ket{1}\\ket{1}=\\ket{1}\\ket{1\\oplusxor 1}=\\ket{1}\\ket{0}=\\ket{10}.\n",
    "$$\n",
    "\n",
    "Esto es exactamente una compuerta controlada-NOT con el primer qubit como control y el segundo como objetivo.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c600e7d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Oráculo para f(x)=x en Qiskit.\n",
    "# q0 será la entrada; q1 será la salida.\n",
    "\n",
    "def oraculo_qiskit(etiqueta):\n",
    "    \"\"\"Construye un circuito de dos qubits que implementa U_f para la etiqueta indicada.\"\"\"\n",
    "    qc = QuantumCircuit(2, name=f\"U_{etiqueta}\")\n",
    "    if etiqueta == \"00\":\n",
    "        # f(0)=0, f(1)=0: no se modifica el registro de salida.\n",
    "        pass\n",
    "    elif etiqueta == \"11\":\n",
    "        # f(0)=1, f(1)=1: se invierte siempre el registro de salida.\n",
    "        qc.x(1)\n",
    "    elif etiqueta == \"01\":\n",
    "        # f(0)=0, f(1)=1: se invierte la salida si la entrada es 1.\n",
    "        qc.cx(0, 1)\n",
    "    elif etiqueta == \"10\":\n",
    "        # f(0)=1, f(1)=0: control negativo sobre q0.\n",
    "        qc.x(0)\n",
    "        qc.cx(0, 1)\n",
    "        qc.x(0)\n",
    "    else:\n",
    "        raise ValueError(\"Etiqueta no válida. Usa '00', '11', '01' o '10'.\")\n",
    "    return qc\n",
    "\n",
    "print(oraculo_qiskit(\"01\").draw(output=\"text\"))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f713be5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Verificación de tablas de verdad de los oráculos Qiskit.\n",
    "# Qiskit usa q0 como bit menos significativo en el vector de estado;\n",
    "# por eso extraemos q0 y q1 mediante operaciones bit a bit.\n",
    "\n",
    "def tabla_desde_circuito(oraculo):\n",
    "    \"\"\"Devuelve la tabla (x,y)->(x_salida,y_salida) de un circuito de oráculo.\"\"\"\n",
    "    filas = []\n",
    "    for x in [0, 1]:\n",
    "        for y in [0, 1]:\n",
    "            qc = QuantumCircuit(2)\n",
    "            if x == 1:\n",
    "                qc.x(0)\n",
    "            if y == 1:\n",
    "                qc.x(1)\n",
    "            qc.compose(oraculo, qubits=[0, 1], inplace=True)\n",
    "            sv = Statevector.from_instruction(qc)\n",
    "            indice = int(np.argmax(np.abs(sv.data) ** 2))\n",
    "            x_salida = indice & 1\n",
    "            y_salida = (indice >> 1) & 1\n",
    "            filas.append((x, y, x_salida, y_salida))\n",
    "    return filas\n",
    "\n",
    "for etiqueta in [\"00\", \"11\", \"01\", \"10\"]:\n",
    "    print(f\"\\nTabla de verdad del oráculo {etiqueta}\")\n",
    "    for fila in tabla_desde_circuito(oraculo_qiskit(etiqueta)):\n",
    "        x, y, xs, ys = fila\n",
    "        print(f\"|{x}{y}> -> |{xs}{ys}>\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5d7cb058",
   "metadata": {},
   "source": [
    "## 17. Ejercicio guiado 3: construir una tabla de $U_f$\n",
    "\n",
    "**Enunciado.** Construye la tabla completa de $U_f$ para la función\n",
    "\n",
    "$$\n",
    "f(0)=1,\n",
    "\\qquad\n",
    "f(1)=0.\n",
    "$$\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "El oráculo está definido por\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{y}=\\ket{x}\\ket{y\\oplusxor f(x)}.\n",
    "$$\n",
    "\n",
    "Entrada $\\ket{0}\\ket{0}$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{0}\\ket{0}\n",
    "=\\ket{0}\\ket{0\\oplusxor f(0)}\n",
    "=\\ket{0}\\ket{0\\oplusxor 1}\n",
    "=\\ket{0}\\ket{1}\n",
    "=\\ket{01}.\n",
    "$$\n",
    "\n",
    "Entrada $\\ket{0}\\ket{1}$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{0}\\ket{1}\n",
    "=\\ket{0}\\ket{1\\oplusxor 1}\n",
    "=\\ket{0}\\ket{0}\n",
    "=\\ket{00}.\n",
    "$$\n",
    "\n",
    "Entrada $\\ket{1}\\ket{0}$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{1}\\ket{0}\n",
    "=\\ket{1}\\ket{0\\oplusxor f(1)}\n",
    "=\\ket{1}\\ket{0\\oplusxor 0}\n",
    "=\\ket{1}\\ket{0}\n",
    "=\\ket{10}.\n",
    "$$\n",
    "\n",
    "Entrada $\\ket{1}\\ket{1}$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{1}\\ket{1}\n",
    "=\\ket{1}\\ket{1\\oplusxor 0}\n",
    "=\\ket{1}\\ket{1}\n",
    "=\\ket{11}.\n",
    "$$\n",
    "\n",
    "**Interpretación.** La salida se invierte sólo cuando la entrada es $x=0$. En un circuito, esto puede implementarse como un control negativo: aplicar $X$ al control, luego $CX$, y finalmente restaurar el control con otro $X$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d3912c33",
   "metadata": {},
   "source": [
    "## 18. *Phase kickback*: motivación\n",
    "\n",
    "El oráculo $U_f$ parece escribir la información de $f(x)$ en el segundo registro:\n",
    "\n",
    "$$\n",
    "\\ket{x}\\ket{y}\\mapsto\\ket{x}\\ket{y\\oplusxor f(x)}.\n",
    "$$\n",
    "\n",
    "Sin embargo, si el segundo registro se prepara en el eigenestado\n",
    "\n",
    "$$\n",
    "\\ket{-}=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}},\n",
    "$$\n",
    "\n",
    "entonces la acción sobre el segundo registro regresa como una fase multiplicando al primer registro:\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{-}=(-1)^{f(x)}\\ket{x}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Este fenómeno se llama *phase kickback*. Es fundamental porque permite convertir el valor de una función en una fase relativa sin medir el registro auxiliar.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbe25c03",
   "metadata": {},
   "source": [
    "## 19. Demostración completa de *phase kickback*\n",
    "\n",
    "Partimos de\n",
    "\n",
    "$$\n",
    "\\ket{-}=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}.\n",
    "$$\n",
    "\n",
    "Aplicamos $U_f$ por linealidad:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\ket{x}\\ket{-}\n",
    "&=U_f\\ket{x}\\left(\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\\right)\\\\\n",
    "&=U_f\\left(\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0}-\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1}\\right)\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}U_f\\ket{x}\\ket{0}-\\frac{1}{\\sqrt{2}}U_f\\ket{x}\\ket{1}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0\\oplusxor f(x)}-\n",
    "\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1\\oplusxor f(x)}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Ahora se separan dos casos.\n",
    "\n",
    "**Caso 1:** $f(x)=0$.\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\ket{x}\\ket{-}\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0\\oplusxor 0}\n",
    "-\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1\\oplusxor 0}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0}\n",
    "-\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1}\\\\\n",
    "&=\\ket{x}\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\\\\\n",
    "&=\\ket{x}\\ket{-}\\\\\n",
    "&=(-1)^0\\ket{x}\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "**Caso 2:** $f(x)=1$.\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\ket{x}\\ket{-}\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0\\oplusxor 1}\n",
    "-\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1\\oplusxor 1}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1}\n",
    "-\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0}\\\\\n",
    "&=-\\ket{x}\\left(\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\\right)\\\\\n",
    "&=-\\ket{x}\\ket{-}\\\\\n",
    "&=(-1)^1\\ket{x}\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Por tanto, para ambos casos:\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{-}=(-1)^{f(x)}\\ket{x}\\ket{-}.\n",
    "$$\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03dc1b51",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Verificación matricial de phase kickback para las cuatro funciones.\n",
    "\n",
    "ket_minus_aux = minus\n",
    "\n",
    "for etiqueta, tabla in funciones.items():\n",
    "    U = matriz_oraculo(tabla)\n",
    "    print(f\"\\nFunción {etiqueta}\")\n",
    "    for x, ketx in [(0, ket0), (1, ket1)]:\n",
    "        entrada = tensor(ketx, ket_minus_aux)\n",
    "        salida = U @ entrada\n",
    "        esperado = ((-1) ** tabla[x]) * entrada\n",
    "        print(f\"x={x}, f(x)={tabla[x]}, ¿salida = (-1)^f(x) |x>|->?\", np.allclose(salida, esperado))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce2e5435",
   "metadata": {},
   "source": [
    "## 20. Ejercicio guiado 4: *phase kickback* con registro de dos bits\n",
    "\n",
    "**Enunciado.** Sea un registro de entrada de dos qubits y un auxiliar en $\\ket{-}$. Supón que una función booleana $g:\\{00,01,10,11\\}\\to\\{0,1\\}$ se representa mediante el signo\n",
    "\n",
    "$$\n",
    "s(x)=(-1)^{g(x)}.\n",
    "$$\n",
    "\n",
    "Si\n",
    "\n",
    "$$\n",
    "s(00)=-1,\n",
    "\\qquad\n",
    "s(01)=1,\n",
    "\\qquad\n",
    "s(10)=1,\n",
    "\\qquad\n",
    "s(11)=-1,\n",
    "$$\n",
    "\n",
    "determina el resultado de aplicar el oráculo de fase al estado\n",
    "\n",
    "$$\n",
    "\\ket{00}\\ket{-}.\n",
    "$$\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "La representación por signos significa que\n",
    "\n",
    "$$\n",
    "U_g\\ket{x}\\ket{-}=s(x)\\ket{x}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Para el estado dado, $x=00$. Del enunciado:\n",
    "\n",
    "$$\n",
    "s(00)=-1.\n",
    "$$\n",
    "\n",
    "Por tanto,\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_g\\ket{00}\\ket{-}\n",
    "&=s(00)\\ket{00}\\ket{-}\\\\\n",
    "&=(-1)\\ket{00}\\ket{-}\\\\\n",
    "&=-\\ket{00}\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "**Justificación.** No se cambia la cadena de bits $00$; sólo aparece un factor de fase. Como el estado de entrada no está en superposición con otros valores de $x$, esta fase es global para ese estado aislado. Si hubiera una superposición, el signo relativo entre ramas sí tendría consecuencias observables después de interferir.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd0d237f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulación algebraica del ejercicio guiado 4.\n",
    "\n",
    "signos = {\"00\": -1, \"01\": 1, \"10\": 1, \"11\": -1}\n",
    "estado_inicial = tensor(ket0, ket0, minus)\n",
    "estado_final = signos[\"00\"] * estado_inicial\n",
    "\n",
    "print(\"El signo aplicado a |00>|-> es:\", signos[\"00\"])\n",
    "print(\"¿estado_final = -estado_inicial?\", np.allclose(estado_final, -estado_inicial))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3c73ab6",
   "metadata": {},
   "source": [
    "## 21. Del *phase kickback* al algoritmo de Deutsch\n",
    "\n",
    "El algoritmo inicia con dos registros:\n",
    "\n",
    "$$\n",
    "\\ket{0}\\ket{1}.\n",
    "$$\n",
    "\n",
    "Luego aplica Hadamard a ambos qubits:\n",
    "\n",
    "$$\n",
    "(H\\otimes H)\\ket{0}\\ket{1}\n",
    "=H\\ket{0}\\otimes H\\ket{1}\n",
    "=\\ket{+}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Expandiendo el primer registro:\n",
    "\n",
    "$$\n",
    "\\ket{+}\\ket{-}\n",
    "=\\left(\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}}\\right)\\ket{-}\n",
    "=\f",
    "rac{1}{\\sqrt{2}}\\ket{0}\\ket{-}+\f",
    "rac{1}{\\sqrt{2}}\\ket{1}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Aplicamos el oráculo:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\left(\\frac{1}{\\sqrt{2}}\\ket{0}\\ket{-}+\\frac{1}{\\sqrt{2}}\\ket{1}\\ket{-}\\right)\n",
    "&=\f",
    "rac{1}{\\sqrt{2}}U_f\\ket{0}\\ket{-}\n",
    "+\\frac{1}{\\sqrt{2}}U_f\\ket{1}\\ket{-}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}(-1)^{f(0)}\\ket{0}\\ket{-}\n",
    "+\\frac{1}{\\sqrt{2}}(-1)^{f(1)}\\ket{1}\\ket{-}\\\\\n",
    "&=\\left(\\frac{(-1)^{f(0)}\\ket{0}+(-1)^{f(1)}\\ket{1}}{\\sqrt{2}}\\right)\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "El segundo registro se factoriza. Toda la información necesaria está en la fase relativa del primer qubit.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0f077c16",
   "metadata": {},
   "source": [
    "## 22. Análisis de los dos casos posibles\n",
    "\n",
    "Después del oráculo, el primer qubit está en\n",
    "\n",
    "$$\n",
    "\\ket{\\varphi_f}\n",
    "=\f",
    "rac{(-1)^{f(0)}\\ket{0}+(-1)^{f(1)}\\ket{1}}{\\sqrt{2}}.\n",
    "$$\n",
    "\n",
    "### Caso constante\n",
    "\n",
    "Si $f(0)=f(1)=c$, con $c\\in\\{0,1\\}$, entonces\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\ket{\\varphi_f}\n",
    "&=\\frac{(-1)^c\\ket{0}+(-1)^c\\ket{1}}{\\sqrt{2}}\\\\\n",
    "&=(-1)^c\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}}\\\\\n",
    "&=(-1)^c\\ket{+}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "La fase $(-1)^c$ es global. Al aplicar $H$:\n",
    "\n",
    "$$\n",
    "H\\ket{\\varphi_f}=(-1)^cH\\ket{+}=(-1)^c\\ket{0}.\n",
    "$$\n",
    "\n",
    "La medición del primer qubit produce $0$ con probabilidad $1$.\n",
    "\n",
    "### Caso balanceado\n",
    "\n",
    "Si $f(0)\\neq f(1)$, hay dos posibilidades.\n",
    "\n",
    "Si $(f(0),f(1))=(0,1)$:\n",
    "\n",
    "$$\n",
    "\\ket{\\varphi_f}\n",
    "=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\n",
    "=\\ket{-}.\n",
    "$$\n",
    "\n",
    "Si $(f(0),f(1))=(1,0)$:\n",
    "\n",
    "$$\n",
    "\\ket{\\varphi_f}\n",
    "=\\frac{-\\ket{0}+\\ket{1}}{\\sqrt{2}}\n",
    "=-\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\n",
    "=-\\ket{-}.\n",
    "$$\n",
    "\n",
    "En ambos casos, el estado es $\\pm\\ket{-}$. Al aplicar $H$:\n",
    "\n",
    "$$\n",
    "H(\\pm\\ket{-})=\\pm\\ket{1}.\n",
    "$$\n",
    "\n",
    "La medición del primer qubit produce $1$ con probabilidad $1$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9a8658e4",
   "metadata": {},
   "source": [
    "## 23. Fórmula general después del Hadamard final\n",
    "\n",
    "Podemos calcular directamente las amplitudes finales. Antes del último Hadamard:\n",
    "\n",
    "$$\n",
    "\\ket{\\varphi_f}\n",
    "=\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "(-1)^{f(0)}\\\\\n",
    "(-1)^{f(1)}\n",
    "\\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "Aplicamos\n",
    "\n",
    "$$\n",
    "H=\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "1&1\\\\\n",
    "1&-1\n",
    "\\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "Entonces\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "H\\ket{\\varphi_f}\n",
    "&=\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "1&1\\\\\n",
    "1&-1\n",
    "\\end{pmatrix}\n",
    "\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "(-1)^{f(0)}\\\\\n",
    "(-1)^{f(1)}\n",
    "\\end{pmatrix}\\\\\n",
    "&=\\frac{1}{2}\n",
    "\\begin{pmatrix}\n",
    "1\\cdot(-1)^{f(0)}+1\\cdot(-1)^{f(1)}\\\\\n",
    "1\\cdot(-1)^{f(0)}+(-1)\\cdot(-1)^{f(1)}\n",
    "\\end{pmatrix}\\\\\n",
    "&=\\frac{1}{2}\n",
    "\\begin{pmatrix}\n",
    "(-1)^{f(0)}+(-1)^{f(1)}\\\\\n",
    "(-1)^{f(0)}-(-1)^{f(1)}\n",
    "\\end{pmatrix}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Si $f(0)=f(1)$, la segunda amplitud es cero. Si $f(0)\\neq f(1)$, la primera amplitud es cero. Esa cancelación es interferencia destructiva.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "32f33fd7",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Simulación algebraica del algoritmo de Deutsch usando matrices NumPy.\n",
    "\n",
    "H2 = tensor(H, H)\n",
    "H_sobre_entrada = tensor(H, I)\n",
    "estado_inicial_deutsch = tensor(ket0, ket1)\n",
    "\n",
    "for etiqueta, tabla in funciones.items():\n",
    "    U = matriz_oraculo(tabla)\n",
    "    despues_h = H2 @ estado_inicial_deutsch\n",
    "    despues_oraculo = U @ despues_h\n",
    "    final = H_sobre_entrada @ despues_oraculo\n",
    "    probs = np.abs(final)**2\n",
    "    prob_entrada_0 = probs[0] + probs[1]  # |00>, |01>\n",
    "    prob_entrada_1 = probs[2] + probs[3]  # |10>, |11>\n",
    "    tipo = \"constante\" if tabla[0] == tabla[1] else \"balanceada\"\n",
    "    print(f\"\\nFunción {etiqueta} ({tipo})\")\n",
    "    imprimir_vector(\"estado final\", final)\n",
    "    print(\"P(entrada=0):\", prob_entrada_0)\n",
    "    print(\"P(entrada=1):\", prob_entrada_1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "36b59b67",
   "metadata": {},
   "source": [
    "## 24. Ejercicio guiado 5: estado del primer qubit para función balanceada\n",
    "\n",
    "**Enunciado.** Supón que después del oráculo el estado es\n",
    "\n",
    "$$\n",
    "\\left(\\frac{1}{\\sqrt{2}}(-1)^{f(0)}\\ket{0}+\\frac{1}{\\sqrt{2}}(-1)^{f(1)}\\ket{1}\\right)\\ket{-}.\n",
    "$$\n",
    "\n",
    "Si $f$ es balanceada, determina las posibilidades para el primer qubit.\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "Si $f$ es balanceada, entonces $f(0)\\neq f(1)$. Hay exactamente dos casos.\n",
    "\n",
    "Caso A:\n",
    "\n",
    "$$\n",
    "f(0)=0,\n",
    "\\qquad\n",
    "f(1)=1.\n",
    "$$\n",
    "\n",
    "Entonces\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\ket{\\varphi_f}\n",
    "&=\\frac{1}{\\sqrt{2}}(-1)^0\\ket{0}+\\frac{1}{\\sqrt{2}}(-1)^1\\ket{1}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{0}-\\frac{1}{\\sqrt{2}}\\ket{1}\\\\\n",
    "&=\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Caso B:\n",
    "\n",
    "$$\n",
    "f(0)=1,\n",
    "\\qquad\n",
    "f(1)=0.\n",
    "$$\n",
    "\n",
    "Entonces\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "\\ket{\\varphi_f}\n",
    "&=\\frac{1}{\\sqrt{2}}(-1)^1\\ket{0}+\\frac{1}{\\sqrt{2}}(-1)^0\\ket{1}\\\\\n",
    "&=-\\frac{1}{\\sqrt{2}}\\ket{0}+\\frac{1}{\\sqrt{2}}\\ket{1}\\\\\n",
    "&=-\\left(\\frac{1}{\\sqrt{2}}\\ket{0}-\\frac{1}{\\sqrt{2}}\\ket{1}\\right)\\\\\n",
    "&=-\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "**Interpretación.** Las únicas posibilidades son $\\ket{-}$ y $-\\ket{-}$. Difieren por fase global, por lo que conducen al mismo resultado de medición después del último Hadamard: el primer qubit termina como $\\ket{1}$ salvo fase global.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81049f08",
   "metadata": {},
   "source": [
    "## 25. Implementación completa en Qiskit\n",
    "\n",
    "La implementación usa dos qubits:\n",
    "\n",
    "- `q0`: registro de entrada;\n",
    "- `q1`: registro auxiliar/salida.\n",
    "\n",
    "El circuito sigue esta secuencia:\n",
    "\n",
    "$$\n",
    "\\ket{0}\\ket{0}\n",
    "\\xrightarrow{X\\text{ en auxiliar}}\n",
    "\\ket{0}\\ket{1}\n",
    "\\xrightarrow{H\\otimes H}\n",
    "\\ket{+}\\ket{-}\n",
    "\\xrightarrow{U_f}\n",
    "\\left(\\frac{(-1)^{f(0)}\\ket{0}+(-1)^{f(1)}\\ket{1}}{\\sqrt{2}}\\right)\\ket{-}\n",
    "\\xrightarrow{H\\otimes I}\n",
    "\\begin{cases}\n",
    "\\pm\\ket{0}\\ket{-},& f\\text{ constante},\\\\\n",
    "\\pm\\ket{1}\\ket{-},& f\\text{ balanceada}.\n",
    "\\end{cases}\n",
    "$$\n",
    "\n",
    "Sólo medimos `q0`, porque la propiedad global queda codificada en el registro de entrada.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a597011d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Circuito completo de Deutsch.\n",
    "\n",
    "def circuito_deutsch(etiqueta, medir=True):\n",
    "    \"\"\"Construye el circuito de Deutsch para la función indicada por etiqueta.\"\"\"\n",
    "    if medir:\n",
    "        qc = QuantumCircuit(2, 1)\n",
    "    else:\n",
    "        qc = QuantumCircuit(2)\n",
    "\n",
    "    # Preparar |0>|1>: q0 inicia en |0>, q1 se invierte a |1>.\n",
    "    qc.x(1)\n",
    "\n",
    "    # Preparar |+>|-> mediante Hadamard en ambos qubits.\n",
    "    qc.h(0)\n",
    "    qc.h(1)\n",
    "\n",
    "    # Aplicar el oráculo U_f.\n",
    "    qc.compose(oraculo_qiskit(etiqueta), qubits=[0, 1], inplace=True)\n",
    "\n",
    "    # Convertir fase relativa del primer qubit en resultado medible.\n",
    "    qc.h(0)\n",
    "\n",
    "    # Medir únicamente el qubit de entrada q0.\n",
    "    if medir:\n",
    "        qc.measure(0, 0)\n",
    "\n",
    "    return qc\n",
    "\n",
    "print(circuito_deutsch(\"01\").draw(output=\"text\"))\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "321cb792",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Ejecutar el algoritmo de Deutsch para las cuatro funciones.\n",
    "\n",
    "simulador = AerSimulator()\n",
    "\n",
    "for etiqueta, tabla in funciones.items():\n",
    "    qc = circuito_deutsch(etiqueta, medir=True)\n",
    "    tqc = transpile(qc, simulador)\n",
    "    resultado = simulador.run(tqc, shots=1024).result()\n",
    "    conteos = resultado.get_counts(tqc)\n",
    "    tipo = \"constante\" if tabla[0] == tabla[1] else \"balanceada\"\n",
    "    bit_esperado = \"0\" if tipo == \"constante\" else \"1\"\n",
    "\n",
    "    print(f\"\\nFunción {etiqueta}: f(0)={tabla[0]}, f(1)={tabla[1]} -> {tipo}\")\n",
    "    print(\"Conteos:\", conteos)\n",
    "    print(\"Bit esperado:\", bit_esperado)\n",
    "    assert set(conteos.keys()) == {bit_esperado}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4939564e",
   "metadata": {},
   "source": [
    "## 26. Ejercicio guiado 6: completar el oráculo para $f(0)=0, f(1)=1$\n",
    "\n",
    "**Enunciado.** Implementa el oráculo de dos qubits para\n",
    "\n",
    "$$\n",
    "f(0)=0,\n",
    "\\qquad\n",
    "f(1)=1.\n",
    "$$\n",
    "\n",
    "El qubit `q0` será la entrada y el qubit `q1` será la salida.\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "El oráculo debe satisfacer\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{y}=\\ket{x}\\ket{y\\oplusxor f(x)}.\n",
    "$$\n",
    "\n",
    "Como $f(x)=x$, se requiere\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{y}=\\ket{x}\\ket{y\\oplusxor x}.\n",
    "$$\n",
    "\n",
    "Esto coincide con una compuerta controlada-NOT:\n",
    "\n",
    "- si $x=0$, la salida no cambia;\n",
    "- si $x=1$, la salida se invierte.\n",
    "\n",
    "Por tanto, el código esencial es\n",
    "\n",
    "```python\n",
    "qc.cx(0, 1)\n",
    "```\n",
    "\n",
    "**Interpretación.** La compuerta `cx(0,1)` no revela directamente ambos valores de $f$; implementa la consulta reversible necesaria para que el algoritmo cuántico use interferencia.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "706b7e52",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solución del ejercicio guiado 6.\n",
    "\n",
    "def oraculo_fx_igual_x():\n",
    "    qc = QuantumCircuit(2, name=\"U_f\")\n",
    "    qc.cx(0, 1)\n",
    "    return qc\n",
    "\n",
    "print(oraculo_fx_igual_x().draw(output=\"text\"))\n",
    "print(\"Tabla de verdad:\")\n",
    "for fila in tabla_desde_circuito(oraculo_fx_igual_x()):\n",
    "    x, y, xs, ys = fila\n",
    "    print(f\"|{x}{y}> -> |{xs}{ys}>\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5cfcc9b2",
   "metadata": {},
   "source": [
    "## 27. Ejercicio guiado 7: oráculo para $f(0)=1, f(1)=0$\n",
    "\n",
    "**Enunciado.** Implementa un oráculo para\n",
    "\n",
    "$$\n",
    "f(0)=1,\n",
    "\\qquad\n",
    "f(1)=0.\n",
    "$$\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "Queremos invertir el qubit de salida cuando el qubit de entrada vale $0$, no cuando vale $1$. Una forma de hacerlo es convertir el control negativo en control positivo:\n",
    "\n",
    "1. aplicar $X$ al qubit de entrada: $0\\leftrightarrow 1$;\n",
    "2. aplicar $CX$ con ese qubit como control;\n",
    "3. aplicar de nuevo $X$ al qubit de entrada para restaurarlo.\n",
    "\n",
    "Algebraicamente, para entrada original $x$:\n",
    "\n",
    "$$\n",
    "X\\ket{x}=\\ket{1\\oplusxor x}.\n",
    "$$\n",
    "\n",
    "La compuerta $CX$ invierte la salida si $1\\oplusxor x=1$, es decir, si $x=0$. Finalmente se restaura $x$.\n",
    "\n",
    "El circuito implementa\n",
    "\n",
    "$$\n",
    "\\ket{x}\\ket{y}\n",
    "\\mapsto\n",
    "\\ket{x}\\ket{y\\oplusxor(1\\oplusxor x)}.\n",
    "$$\n",
    "\n",
    "**Interpretación.** Esta función también es balanceada; por tanto, el algoritmo de Deutsch debe producir $1$ al medir el registro de entrada.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b6a29124",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solución del ejercicio guiado 7.\n",
    "\n",
    "def oraculo_fx_igual_1_xor_x():\n",
    "    qc = QuantumCircuit(2, name=\"U_f\")\n",
    "    qc.x(0)\n",
    "    qc.cx(0, 1)\n",
    "    qc.x(0)\n",
    "    return qc\n",
    "\n",
    "print(oraculo_fx_igual_1_xor_x().draw(output=\"text\"))\n",
    "print(\"Tabla de verdad:\")\n",
    "for fila in tabla_desde_circuito(oraculo_fx_igual_1_xor_x()):\n",
    "    x, y, xs, ys = fila\n",
    "    print(f\"|{x}{y}> -> |{xs}{ys}>\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7c7ad3f9",
   "metadata": {},
   "source": [
    "## 28. ¿Qué ocurre si cambiamos el auxiliar?\n",
    "\n",
    "El *phase kickback* útil depende de preparar el segundo registro en $\\ket{-}$, eigenestado de $X$ con eigenvalor $-1$.\n",
    "\n",
    "Si el auxiliar fuera $\\ket{+}$, entonces\n",
    "\n",
    "$$\n",
    "X\\ket{+}=\\ket{+}.\n",
    "$$\n",
    "\n",
    "En ese caso, si $f(x)=1$, la inversión de salida produce eigenvalor $+1$, no un signo negativo. Por tanto,\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{+}=\\ket{x}\\ket{+}\n",
    "$$\n",
    "\n",
    "para cualquier valor de $f(x)$. El oráculo no deja una fase dependiente de $x$.\n",
    "\n",
    "Si el auxiliar fuera $\\ket{0}$, entonces\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{0}=\\ket{x}\\ket{f(x)},\n",
    "$$\n",
    "\n",
    "lo cual puede entrelazar entrada y salida cuando el registro de entrada está en superposición. En ese caso, la información no queda como una fase limpia del primer registro.\n",
    "\n",
    "La preparación $\\ket{-}$ no es arbitraria: se elige porque convierte el XOR reversible en una fase controlada.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8033c898",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Comparación algebraica: auxiliar |->, |+> y |0>.\n",
    "\n",
    "def aplicar_oraculo_a_superposicion(etiqueta, auxiliar):\n",
    "    tabla = funciones[etiqueta]\n",
    "    U = matriz_oraculo(tabla)\n",
    "    entrada = tensor(plus, auxiliar)\n",
    "    return U @ entrada\n",
    "\n",
    "for aux_nombre, aux in [(\"|->\", minus), (\"|+>\", plus), (\"|0>\", ket0)]:\n",
    "    salida = aplicar_oraculo_a_superposicion(\"01\", aux)  # f(x)=x\n",
    "    print(f\"\\nAuxiliar {aux_nombre}, función f(x)=x\")\n",
    "    imprimir_vector(\"salida\", salida)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "960c4a8b",
   "metadata": {},
   "source": [
    "## 29. Ejercicio guiado 8: comparar auxiliares\n",
    "\n",
    "**Enunciado.** Para $f(x)=x$, compara el efecto del oráculo sobre\n",
    "\n",
    "$$\n",
    "\\ket{+}\\ket{-},\n",
    "\\qquad\n",
    "\\ket{+}\\ket{+},\n",
    "\\qquad\n",
    "\\ket{+}\\ket{0}.\n",
    "$$\n",
    "\n",
    "**Desarrollo paso a paso.**\n",
    "\n",
    "Primero, para $\\ket{+}\\ket{-}$:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\ket{+}\\ket{-}\n",
    "&=U_f\\left(\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}}\\right)\\ket{-}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}U_f\\ket{0}\\ket{-}\n",
    "+\\frac{1}{\\sqrt{2}}U_f\\ket{1}\\ket{-}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}(-1)^{f(0)}\\ket{0}\\ket{-}\n",
    "+\\frac{1}{\\sqrt{2}}(-1)^{f(1)}\\ket{1}\\ket{-}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Como $f(0)=0$ y $f(1)=1$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{+}\\ket{-}\n",
    "=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}}\\ket{-}\n",
    "=\\ket{-}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Segundo, para $\\ket{+}\\ket{+}$:\n",
    "\n",
    "$$\n",
    "X\\ket{+}=\\ket{+},\n",
    "$$\n",
    "\n",
    "así que no aparece fase negativa:\n",
    "\n",
    "$$\n",
    "U_f\\ket{+}\\ket{+}=\\ket{+}\\ket{+}.\n",
    "$$\n",
    "\n",
    "Tercero, para $\\ket{+}\\ket{0}$:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\ket{+}\\ket{0}\n",
    "&=\\frac{1}{\\sqrt{2}}U_f\\ket{0}\\ket{0}\n",
    "+\\frac{1}{\\sqrt{2}}U_f\\ket{1}\\ket{0}\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{0}\\ket{0}\n",
    "+\\frac{1}{\\sqrt{2}}\\ket{1}\\ket{1}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "**Interpretación.** Sólo el auxiliar $\\ket{-}$ produce una fase relativa directamente utilizable por el Hadamard final del algoritmo de Deutsch.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0117e893",
   "metadata": {},
   "source": [
    "## 30. Diagnóstico de errores comunes\n",
    "\n",
    "### Error 1: confundir fase global con fase relativa\n",
    "\n",
    "Los estados $\\ket{-}$ y $-\\ket{-}$ son físicamente equivalentes si se consideran de forma aislada. Sin embargo, $\\ket{+}$ y $\\ket{-}$ no son equivalentes: difieren por una fase relativa entre $\\ket{0}$ y $\\ket{1}$.\n",
    "\n",
    "### Error 2: medir el qubit auxiliar\n",
    "\n",
    "El algoritmo decide la propiedad global midiendo el registro de entrada. El auxiliar termina factorizado como $\\ket{-}$ salvo fases globales; no contiene la respuesta de forma directa.\n",
    "\n",
    "### Error 3: preparar el auxiliar en $\\ket{+}$\n",
    "\n",
    "Como $X\\ket{+}=\\ket{+}$, la acción del XOR no produce un signo que dependa de $f(x)$.\n",
    "\n",
    "### Error 4: leer mal el orden de bits en Qiskit\n",
    "\n",
    "En este notebook se evita la ambigüedad midiendo sólo `q0` en un bit clásico. Cuando se miden varios qubits, es necesario documentar explícitamente la correspondencia entre qubits y bits clásicos.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "adf1e12c",
   "metadata": {},
   "source": [
    "## 31. Ejercicios propuestos con solución razonada\n",
    "\n",
    "Los siguientes ejercicios consolidan las ideas anteriores. Cada uno incluye un desarrollo completo para que puedas revisar el razonamiento matemático.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c64923a8",
   "metadata": {},
   "source": [
    "### Propuesto 1: fase global\n",
    "\n",
    "**Enunciado.** Demuestra que $\\ket{\\psi}$ y $-\\ket{\\psi}$ producen las mismas probabilidades de medición en cualquier base ortonormal $\\{\\ket{b_j}\\}$.\n",
    "\n",
    "**Solución.** La amplitud de obtener el resultado asociado a $\\ket{b_j}$ desde $\\ket{\\psi}$ es\n",
    "\n",
    "$$\n",
    "a_j=\\braket{b_j}{\\psi}.\n",
    "$$\n",
    "\n",
    "Desde $-\\ket{\\psi}$, la amplitud es\n",
    "\n",
    "$$\n",
    "a'_j=\\bra{b_j}(-\\ket{\\psi})=-\\braket{b_j}{\\psi}=-a_j.\n",
    "$$\n",
    "\n",
    "La probabilidad correspondiente es\n",
    "\n",
    "$$\n",
    "P'_j=|a'_j|^2=|-a_j|^2=(-a_j)^\\ast(-a_j)=a_j^\\ast a_j=|a_j|^2=P_j.\n",
    "$$\n",
    "\n",
    "**Interpretación.** Una fase global no altera ninguna distribución de probabilidad. Por eso $\\ket{-}$ y $-\\ket{-}$ son indistinguibles como estados físicos aislados.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23ec3062",
   "metadata": {},
   "source": [
    "### Propuesto 2: conteo para dos bits de entrada\n",
    "\n",
    "**Enunciado.** ¿Cuántas funciones $f:\\{0,1\\}^2\\to\\{0,1\\}$ existen?\n",
    "\n",
    "**Solución.** El dominio $\\{0,1\\}^2$ contiene cuatro cadenas:\n",
    "\n",
    "$$\n",
    "00,\n",
    "01,\n",
    "10,\n",
    "11.\n",
    "$$\n",
    "\n",
    "Para cada entrada, $f$ puede tomar dos valores. Por la regla del producto:\n",
    "\n",
    "$$\n",
    "2\\cdot 2\\cdot 2\\cdot 2=2^4=16.\n",
    "$$\n",
    "\n",
    "Como $|\\{0,1\\}^2|=2^2=4$, la fórmula general para funciones $f:\\{0,1\\}^n\\to\\{0,1\\}$ es\n",
    "\n",
    "$$\n",
    "2^{2^n}.\n",
    "$$\n",
    "\n",
    "Para $n=2$:\n",
    "\n",
    "$$\n",
    "2^{2^2}=2^4=16.\n",
    "$$\n",
    "\n",
    "**Interpretación.** El número de funciones crece doblemente exponencial en $n$ como conjunto de tablas de verdad. Los algoritmos oraculares buscan propiedades globales sin reconstruir toda la tabla.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ad58dd8",
   "metadata": {},
   "source": [
    "### Propuesto 3: Hadamard final en forma cerrada\n",
    "\n",
    "**Enunciado.** Sea\n",
    "\n",
    "$$\n",
    "\\ket{\\varphi_{a,b}}=\\frac{(-1)^a\\ket{0}+(-1)^b\\ket{1}}{\\sqrt{2}},\n",
    "\\qquad\n",
    "a,b\\in\\{0,1\\}.\n",
    "$$\n",
    "\n",
    "Calcula $H\\ket{\\varphi_{a,b}}$.\n",
    "\n",
    "**Solución.** Escribimos el estado como vector columna:\n",
    "\n",
    "$$\n",
    "\\ket{\\varphi_{a,b}}\n",
    "=\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "(-1)^a\\\\\n",
    "(-1)^b\n",
    "\\end{pmatrix}.\n",
    "$$\n",
    "\n",
    "Aplicamos $H$:\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "H\\ket{\\varphi_{a,b}}\n",
    "&=\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "1&1\\\\\n",
    "1&-1\n",
    "\\end{pmatrix}\n",
    "\\frac{1}{\\sqrt{2}}\n",
    "\\begin{pmatrix}\n",
    "(-1)^a\\\\\n",
    "(-1)^b\n",
    "\\end{pmatrix}\\\\\n",
    "&=\\frac12\n",
    "\\begin{pmatrix}\n",
    "(-1)^a+(-1)^b\\\\\n",
    "(-1)^a-(-1)^b\n",
    "\\end{pmatrix}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Si $a=b$, entonces $(-1)^a=(-1)^b$ y queda\n",
    "\n",
    "$$\n",
    "H\\ket{\\varphi_{a,b}}\n",
    "=\\frac12\n",
    "\\begin{pmatrix}\n",
    "2(-1)^a\\\\0\n",
    "\\end{pmatrix}\n",
    "=(-1)^a\\ket{0}.\n",
    "$$\n",
    "\n",
    "Si $a\\neq b$, entonces $(-1)^b=-(-1)^a$ y queda\n",
    "\n",
    "$$\n",
    "H\\ket{\\varphi_{a,b}}\n",
    "=\\frac12\n",
    "\\begin{pmatrix}\n",
    "0\\\\2(-1)^a\n",
    "\\end{pmatrix}\n",
    "=(-1)^a\\ket{1}.\n",
    "$$\n",
    "\n",
    "**Interpretación.** El bit medido después del Hadamard final es $a\\oplusxor b$. En Deutsch, $a=f(0)$ y $b=f(1)$.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7c160eb",
   "metadata": {},
   "source": [
    "### Propuesto 4: auxiliar en $\\ket{+}$\n",
    "\n",
    "**Enunciado.** Demuestra que, para cualquier función booleana $f$, se cumple\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{+}=\\ket{x}\\ket{+}.\n",
    "$$\n",
    "\n",
    "**Solución.** Usamos\n",
    "\n",
    "$$\n",
    "\\ket{+}=\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}}.\n",
    "$$\n",
    "\n",
    "Entonces\n",
    "\n",
    "$$\n",
    "\\begin{aligned}\n",
    "U_f\\ket{x}\\ket{+}\n",
    "&=U_f\\ket{x}\\left(\\frac{\\ket{0}+\\ket{1}}{\\sqrt{2}}\\right)\\\\\n",
    "&=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0\\oplusxor f(x)}\n",
    "+\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1\\oplusxor f(x)}.\n",
    "\\end{aligned}\n",
    "$$\n",
    "\n",
    "Si $f(x)=0$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{+}\n",
    "=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0}\n",
    "+\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1}\n",
    "=\\ket{x}\\ket{+}.\n",
    "$$\n",
    "\n",
    "Si $f(x)=1$:\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{+}\n",
    "=\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{1}\n",
    "+\\frac{1}{\\sqrt{2}}\\ket{x}\\ket{0}\n",
    "=\\ket{x}\\ket{+}.\n",
    "$$\n",
    "\n",
    "**Interpretación.** $\\ket{+}$ es eigenestado de $X$ con eigenvalor $+1$, por lo que no produce el signo necesario para el algoritmo.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d40b2be",
   "metadata": {},
   "source": [
    "### Propuesto 5: implementar y verificar el oráculo $f(0)=1, f(1)=0$\n",
    "\n",
    "**Enunciado.** Construye un circuito de Qiskit que implemente\n",
    "\n",
    "$$\n",
    "f(0)=1,\n",
    "\\qquad\n",
    "f(1)=0,\n",
    "$$\n",
    "\n",
    "y verifica que el algoritmo de Deutsch devuelve el bit $1$.\n",
    "\n",
    "**Solución.** El oráculo se implementa con control negativo:\n",
    "\n",
    "$$\n",
    "X_0\\;CX_{0,1}\\;X_0.\n",
    "$$\n",
    "\n",
    "La primera $X$ convierte el caso $x=0$ en control activo. La última $X$ restaura el valor original de entrada.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4a6f2a66",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Solución del propuesto 5.\n",
    "\n",
    "oraculo_10 = oraculo_qiskit(\"10\")\n",
    "print(oraculo_10.draw(output=\"text\"))\n",
    "\n",
    "qc_10 = circuito_deutsch(\"10\", medir=True)\n",
    "tqc_10 = transpile(qc_10, simulador)\n",
    "conteos_10 = simulador.run(tqc_10, shots=1024).result().get_counts(tqc_10)\n",
    "print(\"Conteos para f(0)=1, f(1)=0:\", conteos_10)\n",
    "\n",
    "assert set(conteos_10.keys()) == {\"1\"}\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45920633",
   "metadata": {},
   "source": [
    "## 32. Resumen final\n",
    "\n",
    "El algoritmo de Deutsch decide si una función $f:\\{0,1\\}\\to\\{0,1\\}$ es constante o balanceada usando una sola llamada al oráculo reversible\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{y}=\\ket{x}\\ket{y\\oplusxor f(x)}.\n",
    "$$\n",
    "\n",
    "La clave matemática es preparar el auxiliar en\n",
    "\n",
    "$$\n",
    "\\ket{-}=\\frac{\\ket{0}-\\ket{1}}{\\sqrt{2}},\n",
    "$$\n",
    "\n",
    "porque\n",
    "\n",
    "$$\n",
    "X\\ket{-}=-\\ket{-}.\n",
    "$$\n",
    "\n",
    "De ahí se obtiene\n",
    "\n",
    "$$\n",
    "U_f\\ket{x}\\ket{-}=(-1)^{f(x)}\\ket{x}\\ket{-}.\n",
    "$$\n",
    "\n",
    "Al consultar en superposición:\n",
    "\n",
    "$$\n",
    "\\ket{+}\\ket{-}\n",
    "\\xrightarrow{U_f}\n",
    "\\left(\\frac{(-1)^{f(0)}\\ket{0}+(-1)^{f(1)}\\ket{1}}{\\sqrt{2}}\\right)\\ket{-}.\n",
    "$$\n",
    "\n",
    "El último Hadamard convierte la fase relativa en un resultado determinista:\n",
    "\n",
    "$$\n",
    "\\begin{cases}\n",
    "0, & f(0)=f(1),\\\\\n",
    "1, & f(0)\\neq f(1).\n",
    "\\end{cases}\n",
    "$$\n",
    "\n",
    "El resultado no proviene de leer simultáneamente dos valores clásicos, sino de explotar linealidad, reversibilidad, fase relativa e interferencia.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "954f1b2d",
   "metadata": {},
   "source": [
    "## 33. Referencias académicas sugeridas\n",
    "\n",
    "- Michael A. Nielsen e Isaac L. Chuang, *Quantum Computation and Quantum Information*, Cambridge University Press.\n",
    "- David Deutsch, “Quantum theory, the Church–Turing principle and the universal quantum computer”, *Proceedings of the Royal Society A*, 1985.\n",
    "- IBM Quantum Learning, materiales sobre algoritmos de consulta, algoritmo de Deutsch y Deutsch–Jozsa.\n",
    "- QWorld, materiales QNickel sobre *phase kickback*, algoritmo de Deutsch y algoritmos oraculares convencionales.\n"
   ]
  }
 ],
 "metadata": {
  "colab": {
   "name": "deutsch_phase_kickback.ipynb",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.x"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
