{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "70810e0b-08f9-48ae-beb5-8c863819000b",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Mitigación de errores de lectura para la primitiva Sampler utilizando M3\"\n",
        "description: \"Utilice el complemento de mitigación de lectura « M3 » con la primitiva «Sampler»\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore braket, Zgrm, newcommand, probs, quasis, topten */}\n",
        "\n",
        "<span id=\"readout-error-mitigation-for-the-sampler-primitive-using-m3\" />\n",
        "\n",
        "# Mitigación de errores de lectura para la primitiva Sampler utilizando M3\n",
        "\n",
        "*Estimación de uso: menos de un minuto en un procesador Heron r2 (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)*\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "A diferencia de la primitiva Estimador, la primitiva Muestreador no tiene soporte incorporado para la mitigación de errores.\n",
        "Varios de los métodos utilizados por el Estimador están diseñados específicamente para valores de expectativas y, por lo tanto, no son aplicables a la primitiva Muestreador. Una excepción es la mitigación de errores de lectura, que es un método muy eficaz que también es aplicable a la primitiva Sampler.\n",
        "\n",
        "El [complemento M3 Qiskit](https://qiskit.github.io/qiskit-addon-mthree/) implementa un método eficaz para mitigar los errores de lectura. Este tutorial explica cómo utilizar el addon M3 Qiskit para mitigar el error de lectura de la primitiva Sampler.\n",
        "\n",
        "<span id=\"what-is-readout-error\" />\n",
        "\n",
        "### ¿Qué es un error de lectura?\n",
        "\n",
        "Inmediatamente antes de la medición, el estado de un registro qubit es descrito por una superposición de estados de base computacional o por una matriz de densidad.\n",
        "A continuación, la medición del registro de qubits en un registro de bits clásico se realiza en dos pasos.\n",
        "Primero se realiza la medición cuántica propiamente dicha.\n",
        "Esto significa que el estado del registro de qubits se proyecta sobre un único estado base que se caracteriza por una cadena de $1$ s y $0$ s.\n",
        "El segundo paso consiste en leer la cadena de bits que caracteriza este estado base y escribirla en la memoria del ordenador clásico.\n",
        "A este paso lo llamamos *lectura*.\n",
        "Resulta que el segundo paso (lectura) incurre en más errores que el primero (proyección sobre los estados base).\n",
        "Esto tiene sentido cuando se recuerda que la lectura requiere detectar un estado cuántico microscópico y amplificarlo al ámbito macroscópico microscópico y amplificarlo al ámbito macroscópico. Un resonador de lectura se acopla a el qubit (transmón), experimentando así un desplazamiento de frecuencia muy pequeño. Un pulso de microondas se hace rebotar en el resonador, que experimenta a su vez pequeños cambios en sus características.  A continuación, el pulso reflejado se amplifica y analiza.  Se trata de un proceso proceso y está sujeto a multitud de errores.\n",
        "\n",
        "El punto importante es que, aunque tanto la medición cuántica como la lectura están sujetas a error, esta última incurre en el error dominante, llamado error de lectura error dominante, llamado error de lectura, en el que se centra este tutorial.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba74fc81-30ac-4365-9ba6-a891ba082bd6",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "source": [
        "<span id=\"theoretical-background\" />\n",
        "\n",
        "### Fundamentos teóricos\n",
        "\n",
        "Si la cadena de bits muestreada (almacenada en la memoria clásica) difiere de la cadena de bits que caracteriza el estado cuántico proyectado, decimos que se ha producido un error de lectura.\n",
        "Se observa que estos errores son aleatorios y no están correlacionados de una muestra a otra.\n",
        "Se ha demostrado que es útil modelar el error de lectura como un *canal clásico ruidoso*.\n",
        "Es decir, para cada par de cadenas de bits $i$ y $j$, existe una probabilidad fija de que un valor verdadero de $j$ sea se lea incorrectamente como $i$.\n",
        "\n",
        "Más concretamente, para cada par de cadenas de bits $(i, j)$, existe una probabilidad (condicional) ${M}_{i,j}$ de que se lea $i$, dado que el valor verdadero es $j.$ Es decir\n",
        "\n",
        "$$\n",
        "    {M}_{i,j} =  \\Pr(\\text{readout value is } i | \\text{true value is } j)\n",
        "    \\text{ for } i,j \\in (0,...,2^n - 1), \\tag{1}\n",
        "$$\n",
        "\n",
        "donde $n$ es el número de bits del registro de lectura.\n",
        "Para concretar, suponemos que $i$ es un entero decimal cuya representación binaria es la cadena de bits que etiqueta los estados de la base de cálculo.\n",
        "Llamamos *matriz de asignación* a la matriz $2^n \\times 2^n$ ${M}$.\n",
        "Para un valor verdadero fijo $j$, la suma de la probabilidad sobre todos los resultados ruidosos $i$ debe dar $1$. Es decir\n",
        "\n",
        "$$\n",
        "    \\sum_{i=0}^{2^n - 1} {M}_{i,j} = 1 \\text{ for all } j\n",
        "$$\n",
        "\n",
        "Una matriz sin entradas negativas que satisface (1) se denomina *izquierda-estocástica*.\n",
        "Una matriz izquierda-estocástica también se denomina *columna-estocástica* porque cada una de sus columnas suma $1$. Experimentalmente determinamos valores aproximados para cada elemento ${M}_{i,j}$ mediante preparando repetidamente cada estado base $|j \\rangle$ y luego calculando las frecuencias de aparición de las cadenas de bits muestreadas.\n",
        "\n",
        "Si un experimento implica estimar una distribución de probabilidad sobre cadenas de bits de salida mediante muestreo repetido, entonces podemos usar ${M}$ para mitigar el error de lectura a nivel de la distribución.\n",
        "El primer paso consiste en repetir muchas veces un circuito fijo de interés, creando un histograma de cadenas de bits muestreadas.\n",
        "El histograma normalizado es la distribución de probabilidad medida sobre las $2^n$ posibles cadenas de bits, que denotamos por ${\\tilde{p}} \\in \\mathbb{R}^{2^n}$. La probabilidad (estimada) ${{\\tilde{p}}}_i$ de la cadena de bits de muestreo $i$ es igual a la suma de todas las cadenas de bits verdaderas $j$, cada una ponderada por la probabilidad de que se confunda con $i$. Esta afirmación en forma de matriz es\n",
        "\n",
        "$$\n",
        "    {\\tilde{p}} = {M} {\\vec{p}}, \\tag{2},\n",
        "$$\n",
        "\n",
        "donde ${\\vec{p}}$ es la distribución verdadera. En otras palabras, el error de lectura tiene el efecto de multiplicar la distribución ideal sobre las cadenas de bits ${\\vec{p}}$ por la matriz de asignación ${M}$ para para producir la distribución observada ${\\tilde{p}}$. Hemos medido ${\\tilde{p}}$ y ${M}$, pero no tenemos acceso directo a ${\\vec{p}}$. En principio, obtendremos obtendremos la verdadera distribución de cadenas de bits para nuestro circuito resolviendo numéricamente la ecuación (2) para ${\\vec{p}}$.\n",
        "\n",
        "Antes de seguir adelante, conviene señalar algunas características importantes de este planteamiento ingenuo.\n",
        "\n",
        "* En la práctica, la ecuación (2) no se resuelve invirtiendo ${M}$. Las rutinas de álgebra lineal de las bibliotecas de software emplean métodos más estables, precisos y eficientes.\n",
        "* Al estimar ${M}$, asumimos que sólo se producían errores de lectura. En particular, asumimos que no hubo errores de preparación de estado y de medición cuántica - o al menos que fueron mitigados.\n",
        "  En la medida en que se trata de una buena suposición, ${M}$ representa realmente error de lectura. Pero cuando *usamos* ${M}$ para corregir una distribución medida sobre cadenas de bits, no hacemos tal suposición. De hecho, esperamos que un circuito introduzca ruido, por ejemplo, errores de puerta. La distribución \"verdadera sigue incluyendo los efectos de cualquier error que no se haya mitigado de otro modo.\n",
        "\n",
        "Este método, aunque útil en algunas circunstancias, adolece de algunas limitaciones.\n",
        "\n",
        "Los recursos de espacio y tiempo necesarios para estimar ${M}$ crecen exponencialmente en $n$ :\n",
        "\n",
        "* La estimación de ${M}$ y ${\\tilde{p}}$ está sujeta a errores estadísticos debidos al muestreo finito.\n",
        "  Este ruido puede hacerse tan pequeño como se desee a costa de más disparos (hasta la escala de tiempo de los parámetros de hardware a la deriva que dan lugar a errores sistemáticos en ${M}$ ). Sin embargo, si no se hacen suposiciones sobre las cadenas de bits observadas al realizar la mitigación, el número de disparos necesarios para estimar ${M}$ crece al menos exponencialmente en $n$.\n",
        "* ${M}$ es una matriz $2^n \\times 2^n$.\n",
        "  En $n>10$, la cantidad de memoria necesaria para almacenar ${M}$ es mayor que la memoria disponible en un portátil potente.\n",
        "\n",
        "Otras limitaciones son:\n",
        "\n",
        "* La distribución recuperada ${\\vec{p}}$ puede tener una o más probabilidades negativas (sin dejar de sumar uno). Una solución es minimizar $||{M} {\\vec{p}} - {\\tilde{p}}||^2$ con la restricción de que cada entrada de ${\\vec{p}}$ sea no negativa. Sin embargo, el tiempo de ejecución de este es varios órdenes de magnitud superior al de la resolución directa de la ecuación (2).\n",
        "* Este procedimiento de mitigación funciona a nivel de una distribución de probabilidad sobre cadenas de bits. En concreto, no puede corregir un error en una cadena de bits cadena de bits observada.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c98d8409-66ad-452f-b87a-d8df886d77d4",
      "metadata": {},
      "source": [
        "<span id=\"qiskit-m3-addon-scaling-to-longer-bitstrings\" />\n",
        "\n",
        "### Complemento Qiskit M3 : escalado a cadenas de bits más largas\n",
        "\n",
        "La resolución de la ecuación (2) mediante rutinas estándar de álgebra lineal numérica está limitada a cadenas de bits de no más de unos 10 bits. M3 puede manejar cadenas de bits mucho más largas. Dos propiedades clave de M3 que lo hacen posible son:\n",
        "\n",
        "* Las correlaciones en el error de lectura de orden tres y superior entre colecciones de bits se suponen despreciables y se ignoran. En principio, a costa de más disparos, también se podrían estimar correlaciones más altas.\n",
        "* En lugar de construir ${M}$ explícitamente, utilizamos una matriz efectiva mucho más pequeña que registra probabilidades sólo para las cadenas de bits recogidas al construir ${\\tilde{p}}$.\n",
        "\n",
        "A grandes rasgos, el procedimiento funciona del siguiente modo.\n",
        "\n",
        "En primer lugar, construimos bloques de construcción a partir de los cuales podemos elaborar una descripción simplificada y eficaz de ${M}$. A continuación, ejecutamos repetidamente el circuito de interés y recopilamos cadenas de bits que utilizamos para construir tanto ${\\tilde{p}}$ como, con la ayuda de los bloques de construcción, una descripción efectiva de ${M}$.\n",
        "\n",
        "Más exactamente,\n",
        "\n",
        "* Las matrices de asignación de un qubit se estiman para cada qubit. Para ello preparamos repetidamente el registro de qubits en el estado todo-cero $|0 ... 0 \\rangle$ y después en el estado todo-uno $|1 ... 1 \\rangle$, y registramos la probabilidad de que cada qubit se lea incorrectamente incorrectamente.\n",
        "* Las correlaciones de orden tres o superior se consideran despreciables y no se tienen en cuenta.\n",
        "\n",
        "  En su lugar construimos un número $n$ de $2 \\times 2$ matrices de asignación single-qubit y un número $n(n-1)/2$ de matrices de asignación de dos qubits $4 \\times 4$ de dos qubits. Estas matrices de asignación de uno y dos qubits se almacenan para su uso posterior uso posterior.\n",
        "* Tras muestrear repetidamente un circuito para construir ${\\tilde{p}}$, construimos una aproximación efectiva a ${M}$ utilizando únicamente cadenas de bits que se muestrean al construir ${\\tilde{p}}$. Esta matriz efectiva se construye utilizando las matrices de uno y dos qubits descritas en el punto anterior.\n",
        "  La dimensión lineal de esta matriz es como máximo del orden del número de disparos utilizados en la construcción de ${\\tilde{p}}$, que es mucho menor que la dimensión $2^n$ de la matriz de asignación completa ${M}$.\n",
        "\n",
        "Para más detalles técnicos sobre M3, puede consultar [*Scalable Mitigation of Measurement Errors on Quantum Computers*](https://journals.aps.org/prxquantum/abstract/10.1103/PRXQuantum.2.040326).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "14f04ac4-5142-4436-93e1-f712d47dde1a",
      "metadata": {},
      "source": [
        "<span id=\"application-of-m3-to-a-quantum-algorithm\" />\n",
        "\n",
        "### Aplicación de M3 a un algoritmo cuántico\n",
        "\n",
        "Aplicaremos M3 's readout mitigation to the hidden shift problem. El problema del desplazamiento oculto, y otros problemas estrechamente relacionados, como [el problema del subgrupo oculto](https://en.wikipedia.org/wiki/Hidden_subgroup_problem), se concibieron originalmente en un entorno tolerante a fallos (más concretamente, antes de que se demostrara que las QPU tolerantes a fallos eran posibles). Pero también se estudian con los procesadores disponibles. Un ejemplo de aceleración exponencial algorítmica obtenida para una variante del problema de turnos ocultos obtenida en QPUs de 127 qubits IBM® puede encontrarse en [este artículo](https://journals.aps.org/prx/accepted/a9074K06A8e1590147da9c69f8c4b64c28247be5a) ( [arXiv version](https://arxiv.org/abs/2401.07934) ).\n",
        "\n",
        "A continuación, toda la aritmética es booleana.\n",
        "Es decir, para $a, b \\in \\mathbb{Z}_2 = \\{0, 1\\}$, suma, $a + b$ es la función lógica XOR.\n",
        "Además, la multiplicación $a \\times b$ (o $a b$ ) es la función lógica AND. Para $x, y \\in \\{0, 1\\}^n$, $x + y$ se define mediante la aplicación bit a bit de XOR.\n",
        "El producto punto $\\cdot: {\\mathbb{Z}_2^n} \\rightarrow \\mathbb{Z}_2$ se define por $x \\cdot y = \\sum_i x_i y_i$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "81cb34b4-566b-4ee1-8309-7464a5fd3bb7",
      "metadata": {},
      "source": [
        "<span id=\"hadamard-operator-and-fourier-transform\" />\n",
        "\n",
        "#### Operador de Hadamard y transformada de Fourier\n",
        "\n",
        "En la implementación de algoritmos cuánticos, es muy común utilizar el operador de Hadamard como transformada de Fourier.\n",
        "Los estados base computacionales se denominan a veces *estados clásicos*. Están en una relación de uno a uno con las cadenas de bits clásicas.\n",
        "El operador de Hadamard $n$ -qubit sobre estados clásicos puede verse como una transformada de Fourier sobre el hipercubo booleano:\n",
        "\n",
        "$$\n",
        "H^{\\otimes n} =  \\frac{1}{\\sqrt{2^n}} \\sum_{x,y \\in {\\mathbb{Z}_2^n}} (-1)^{x \\cdot y} {|{y}\\rangle}{\\langle{x}|}.\n",
        "$$\n",
        "\n",
        "Consideremos un estado ${|{s}\\rangle}$ correspondiente a la cadena de bits fija $s$. Aplicando $H^{\\otimes n}$, y utilizando ${\\langle {x}|{s}\\rangle} = \\delta_{x,s}$, vemos que la transformada de Fourier de ${|{s}\\rangle}$ puede escribirse como\n",
        "\n",
        "$$\n",
        "   H^{\\otimes n} {|{s}\\rangle} =  \\frac{1}{\\sqrt{2^n}} \\sum_{y \\in {\\mathbb{Z}_2^n}} (-1)^{s \\cdot y} {|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "El Hadamard es su propio inverso, es decir, $H^{\\otimes n} H^{\\otimes n} = (H H)^{\\otimes n} = I^{\\otimes n}$. Por lo tanto, la transformada de Fourier inversa es el mismo operador, $H^{\\otimes n}$. Explícitamente, tenemos,\n",
        "\n",
        "$$\n",
        "  {|{s}\\rangle} =  H^{\\otimes n} H^{\\otimes n} {|{s}\\rangle}  =  H^{\\otimes n} \\frac{1}{\\sqrt{2^n}} \\sum_{y \\in {\\mathbb{Z}_2^n}} (-1)^{s \\cdot y} {|{y}\\rangle}.\n",
        "$$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d10fce93-3e08-4a27-b641-eda5e7b05ce0",
      "metadata": {},
      "source": [
        "<span id=\"the-hidden-shift-problem\" />\n",
        "\n",
        "#### El problema del cambio oculto\n",
        "\n",
        "Consideramos un ejemplo sencillo de un *problema de desplazamiento oculto*.\n",
        "El problema consiste en identificar un desplazamiento constante en la entrada de una función.\n",
        "La función que consideramos es el producto punto. Es el miembro más sencillo de una gran clase de funciones que admiten una aceleración cuántica para el problema del desplazamiento oculto mediante técnicas similares a las que se presentan a continuación.\n",
        "\n",
        "Sea $x,y \\in {\\mathbb{Z}_2^m}$ cadenas de bits de longitud $m$. Definimos ${f}: {\\mathbb{Z}_2^m} \\times {\\mathbb{Z}_2^m} \\rightarrow \\{-1,1\\}$ por\n",
        "\n",
        "$$\n",
        "  {f}(x, y) = (-1)^{x \\cdot y}.\n",
        "$$\n",
        "\n",
        "Sea $a,b \\in {\\mathbb{Z}_2^m}$ cadenas de bits fijas de longitud $m$. Definimos además $g: {\\mathbb{Z}_2^m} \\times {\\mathbb{Z}_2^m} \\rightarrow \\{-1,1\\}$ por\n",
        "\n",
        "$$\n",
        "  g(x, y) = {f}(x+a, y+b) = (-1)^{(x+a) \\cdot (y+b)},\n",
        "$$\n",
        "\n",
        "donde $a$ y $b$ son parámetros (ocultos).\n",
        "Se nos dan dos cajas negras, una que implementa $f$, y la otra $g$. Suponemos que sabemos que calculan las funciones definidas anteriormente, excepto que no conocemos ni $a$ ni $b$. El juego consiste en determinar las cadenas de bits ocultas (shifts) $a$ y $b$ haciendo consultas a $f$ y $g$. Está claro que si jugamos el juego de forma clásica necesitamos consultas a $O(2m)$ para determinar $a$ y $b$. Por ejemplo, podemos consultar $g$ con todos los pares de cadenas tales que un elemento del par sea todo ceros, y el otro elemento tenga exactamente un elemento establecido en $1$. En cada consulta, aprendemos un elemento de $a$ o $b$. Sin embargo, veremos que, si las cajas negras se implementan como circuitos cuánticos, podemos determinar $a$ y $b$ con una sola consulta a cada uno de $f$ y $g$.\n",
        "\n",
        "En el contexto de la complejidad algorítmica, una caja negra se denomina *oráculo*.\n",
        "Además de ser opaco, un oráculo tiene la propiedad de que consume la entrada y produce la salida instantáneamente, sin añadir nada al presupuesto de complejidad del algoritmo en el que está integrado. De hecho, en el caso que nos ocupa, los oráculos que implementan $f$ y $g$ son eficientes.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d5e4c1e2-d6f5-4093-afdb-f97e9f062179",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "source": [
        "<span id=\"quantum-circuits-for-$f$-and-$g$\" />\n",
        "\n",
        "#### Circuitos cuánticos para $f$ y $g$\n",
        "\n",
        "Necesitamos los siguientes ingredientes para implementar $f$ y $g$ como circuitos cuánticos.\n",
        "\n",
        "Para estados clásicos de un solo qubit ${|{x_1}\\rangle}, {|{y_1}\\rangle}$, con $x_1,y_1 \\in \\mathbb{Z}_2$, la puerta controlada- $Z$ ${CZ}$ puede escribirse como\n",
        "\n",
        "$$\n",
        "{CZ} {|{x_1}\\rangle}{|{y_1}\\rangle}{x_1} = (-1)^{x_1 y_1} {|{x_1}\\rangle}{x_1}{|{y_1}\\rangle}.\n",
        "$$\n",
        "\n",
        "Operaremos con $m$ puertas CZ, una en $(x_1, y_1)$, y otra en $(x_2, y_2)$, y así sucesivamente, a través de $(x_m, y_m)$. Llamamos a este operador ${CZ}_{x,y}$.\n",
        "\n",
        "$U_f = {CZ}_{x,y}$ es una versión cuántica de ${f} = {f}(x,y)$ :\n",
        "\n",
        "$$\n",
        "%\\CZ_{x,y} {|#1\\rangle}{z} =\n",
        "U_f {|{x}\\rangle}{|{y}\\rangle} = {CZ}_{x,y} {|{x}\\rangle}{|{y}\\rangle} = (-1)^{x \\cdot y}  {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "También necesitamos implementar un desplazamiento de cadena de bits.\n",
        "Denotamos el operador en el registro $x$ $X^{a_1}\\cdots X^{a_m}$ por $X_a$ e igualmente en el registro $y$ $X_b =  X^{b_1}\\cdots X^{b_m}$. Estos operadores aplican $X$ siempre que un bit sea $1$, y la identidad $I$ siempre que sea $0$. Entonces tenemos\n",
        "\n",
        "$$\n",
        " X_a X_b  {|{x}\\rangle}{|{y}\\rangle} = {|{x+a}\\rangle}{|{y+b}\\rangle}.\n",
        "$$\n",
        "\n",
        "La segunda caja negra $g$ se implementa mediante el unitario $U_g$, dado por\n",
        "\n",
        "$$\n",
        "%U_g {|{x}\\rangle}{|{y}\\rangle} = X_aX_b \\CZ_{x,y} X_aX_b {|{x}\\rangle}{|{y}\\rangle}.\n",
        "U_g = X_aX_b {CZ}_{x,y} X_aX_b.\n",
        "$$\n",
        "\n",
        "Para verlo, aplicamos los operadores de derecha a izquierda al estado ${|{x}\\rangle}{|{y}\\rangle}$. Primero\n",
        "\n",
        "$$\n",
        " X_a X_b  {|{x}\\rangle}{|{y}\\rangle} = {|{x+a}\\rangle}{|{y+b}\\rangle}.\n",
        "$$\n",
        "\n",
        "A continuación,\n",
        "\n",
        "$$\n",
        "  {CZ}_{x,y}  {|{x+a}\\rangle}{|{y+b}\\rangle} = (-1)^{(x+a)\\cdot (y+b)} {|{x+a}\\rangle}{|{y+b}\\rangle}.\n",
        "$$\n",
        "\n",
        "Por último,\n",
        "\n",
        "$$\n",
        "  X^a X^b (-1)^{(x+a)\\cdot (y+b)} {|{x+a}\\rangle}{|{y+b}\\rangle} = (-1)^{(x+a)\\cdot (y+b)} {|{x}\\rangle}{|{y}\\rangle},\n",
        "$$\n",
        "\n",
        "que es, en efecto, la versión cuántica de $f(x+a, y+b)$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "de0bb5f9-517e-4bea-91ab-f65e012e8ae9",
      "metadata": {
        "editable": true,
        "jp-MarkdownHeadingCollapsed": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "source": [
        "<span id=\"the-hidden-shift-algorithm\" />\n",
        "\n",
        "#### El algoritmo de cambio oculto\n",
        "\n",
        "Ahora unimos las piezas para resolver el problema del turno oculto.\n",
        "Comenzamos aplicando Hadamards a los registros inicializados al estado todo-cero.\n",
        "\n",
        "$$\n",
        "H^{\\otimes 2m} = H^{\\otimes m} \\otimes H^{\\otimes m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}} = \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{x \\cdot y} {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "A continuación, consultamos el oráculo $g$ para llegar a\n",
        "\n",
        "$$\n",
        "U_g H^{\\otimes 2m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}}\n",
        "= \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{(x+a) \\cdot (y+b)} {|{x}\\rangle}{|{y}\\rangle}\n",
        "$$\n",
        "\n",
        "$$\n",
        "\\approx \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{x \\cdot y + x \\cdot b + y \\cdot a} {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "En la última línea, omitimos el factor de fase global constante $(-1)^{a \\cdot b}$, y denotamos la igualdad hasta una fase por $\\approx$. A continuación, aplicando el oráculo $f$ introducimos otro factor de $(-1)^{x \\cdot y}$, anulando el ya presente. Entonces tenemos:\n",
        "\n",
        "$$\n",
        "U_f U_g H^{\\otimes 2m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}}\n",
        "\\approx \\frac{1}{\\sqrt{2^{2m}}} \\sum_{x, y \\in {\\mathbb{Z}_2^m}} (-1)^{x \\cdot b + y \\cdot a} {|{x}\\rangle}{|{y}\\rangle}.\n",
        "$$\n",
        "\n",
        "El último paso es aplicar la transformada inversa de Fourier, $H^{\\otimes 2m} = H^{\\otimes m} \\otimes H^{\\otimes m}$, dando como resultado\n",
        "\n",
        "$$\n",
        "H^{\\otimes 2m} U_f U_g  H^{\\otimes 2m} {{|{0}\\rangle}^{\\otimes m}}{{|{0}\\rangle}^{\\otimes m}}\n",
        "\\approx {|{b}\\rangle}{|{a}\\rangle}.\n",
        "$$\n",
        "\n",
        "El circuito está terminado. En ausencia de ruido, el muestreo de los registros cuánticos devolverá devolverá las cadenas de bits $b, a$ con la probabilidad $1$.\n",
        "\n",
        "El producto interior booleano es un ejemplo de las llamadas funciones dobladas.\n",
        "No definiremos aquí las funciones curvas sino que nos limitaremos a señalar que \"son máximamente resistentes contra ataques que buscan explotar una dependencia de las salidas en algún subespacio lineal de las entradas\"\n",
        "Esta cita es del artículo [*Quantum algorithms for highly non-linear Boolean functions*](https://arxiv.org/abs/0811.3208), que da algoritmos eficientes de desplazamiento oculto para varias clases de funciones curvas.\n",
        "El algoritmo de este tutorial aparece en la sección 3.1 del artículo.\n",
        "\n",
        "En el caso más general, el circuito para encontrar un desplazamiento oculto $s \\in \\mathbb{Z}^n$ es\n",
        "\n",
        "$$\n",
        " H^{\\otimes n} U_{\\tilde{f}}  H^{\\otimes n} U_g  H^{\\otimes n} {|{0}\\rangle}^{\\otimes n} = {|{s}\\rangle}.\n",
        "$$\n",
        "\n",
        "En el caso general, $f$ y $g$ son funciones de una sola variable.\n",
        "Nuestro ejemplo del producto interior tiene esta forma si dejamos que $f(x, y) \\to f(z)$, siendo $z$ igual a la concatenación de $x$ y $y$, y $s$ igual a la concatenación de $a$ y $b$. El caso general requiere exactamente dos oráculos: Un oráculo para $g$ y otro para $\\tilde{f}$, donde este último es una función conocida como el *dual* de la función doblada $f$. La función producto interior tiene la propiedad autodual $\\tilde{f}=f$.\n",
        "\n",
        "En nuestro circuito para el desplazamiento oculto sobre el producto interior omitimos la capa intermedia de Hadamards que aparece en el circuito para el caso general. Aunque en el caso general esta capa es necesaria, ahorramos un poco de profundidad al omitirla, a expensas de un poco de postprocesamiento, ya que la salida es ${|{b}\\rangle}{|{a}\\rangle}$ en lugar de la deseada ${|{a}\\rangle}{|{b}\\rangle}$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ccc0aec5-63d3-4a43-8fad-fea56ea24ec7",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de que tienes instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.1 o posterior, con soporte [de visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.41 o posterior (`pip install qiskit-ibm-runtime`)\n",
        "* M3 Qiskit addon v3.0 (`pip install mthree`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c090b8dd-754f-4390-93e5-663f818e61d0",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "233dfc67-d280-4827-910d-8e7170ee95ad",
      "metadata": {},
      "outputs": [],
      "source": [
        "from collections.abc import Iterator, Sequence\n",
        "from random import Random\n",
        "from qiskit.circuit import (\n",
        "    CircuitInstruction,\n",
        "    QuantumCircuit,\n",
        "    QuantumRegister,\n",
        "    Qubit,\n",
        ")\n",
        "from qiskit.circuit.library import CZGate, HGate, XGate\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "import timeit\n",
        "import matplotlib.pyplot as plt\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "import mthree"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5cf89647-261d-4af1-af54-75548345df6e",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "## Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "940cc113-5cf3-4688-9747-93268be10088",
      "metadata": {},
      "source": [
        "En primer lugar, escribimos las funciones para implementar el problema del desplazamiento oculto como `QuantumCircuit`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "675fe72b-6a86-42ea-a65f-b29a60725ecb",
      "metadata": {
        "editable": true,
        "scrolled": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [],
      "source": [
        "def apply_hadamards(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply a Hadamard gate to every qubit.\"\"\"\n",
        "    for q in qubits:\n",
        "        yield CircuitInstruction(HGate(), [q], [])\n",
        "\n",
        "\n",
        "def apply_shift(\n",
        "    qubits: Sequence[Qubit], shift: int\n",
        ") -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply X gates where the bits of the shift are equal to 1.\"\"\"\n",
        "    for i, q in zip(range(shift.bit_length()), qubits):\n",
        "        if shift >> i & 1:\n",
        "            yield CircuitInstruction(XGate(), [q], [])\n",
        "\n",
        "\n",
        "def oracle_f(qubits: Sequence[Qubit]) -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply the f oracle.\"\"\"\n",
        "    for i in range(0, len(qubits) - 1, 2):\n",
        "        yield CircuitInstruction(CZGate(), [qubits[i], qubits[i + 1]])\n",
        "\n",
        "\n",
        "def oracle_g(\n",
        "    qubits: Sequence[Qubit], shift: int\n",
        ") -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Apply the g oracle.\"\"\"\n",
        "    yield from apply_shift(qubits, shift)\n",
        "    yield from oracle_f(qubits)\n",
        "    yield from apply_shift(qubits, shift)\n",
        "\n",
        "\n",
        "def determine_hidden_shift(\n",
        "    qubits: Sequence[Qubit], shift: int\n",
        ") -> Iterator[CircuitInstruction]:\n",
        "    \"\"\"Determine the hidden shift.\"\"\"\n",
        "    yield from apply_hadamards(qubits)\n",
        "    yield from oracle_g(qubits, shift)\n",
        "    # We omit this layer in exchange for post processing\n",
        "    # yield from apply_hadamards(qubits)\n",
        "    yield from oracle_f(qubits)\n",
        "    yield from apply_hadamards(qubits)\n",
        "\n",
        "\n",
        "def run_hidden_shift_circuit(n_qubits, rng):\n",
        "    hidden_shift = rng.getrandbits(n_qubits)\n",
        "\n",
        "    qubits = QuantumRegister(n_qubits, name=\"q\")\n",
        "    circuit = QuantumCircuit.from_instructions(\n",
        "        determine_hidden_shift(qubits, hidden_shift), qubits=qubits\n",
        "    )\n",
        "    circuit.measure_all()\n",
        "    # Format the hidden shift as a string.\n",
        "    hidden_shift_string = format(hidden_shift, f\"0{n_qubits}b\")\n",
        "    return (circuit, hidden_shift, hidden_shift_string)\n",
        "\n",
        "\n",
        "def display_circuit(circuit):\n",
        "    return circuit.remove_final_measurements(inplace=False).draw(\n",
        "        \"mpl\", idle_wires=False, scale=0.5, fold=-1\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6960aa96-f612-40b3-9f26-3d3afb07262b",
      "metadata": {},
      "source": [
        "Empezaremos con un pequeño ejemplo:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "8297843e-00c3-4bb5-9d33-a7e558d1698c",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hidden shift string 011010\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/readout-error-mitigation-sampler/extracted-outputs/8297843e-00c3-4bb5-9d33-a7e558d1698c-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "n_qubits = 6\n",
        "random_seed = 12345\n",
        "rng = Random(random_seed)\n",
        "circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(\n",
        "    n_qubits, rng\n",
        ")\n",
        "\n",
        "print(f\"Hidden shift string {hidden_shift_string}\")\n",
        "\n",
        "display_circuit(circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c136f784-e105-4244-a483-b8221577afc3",
      "metadata": {
        "editable": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "source": [
        "<span id=\"step-2-optimize-circuits-for-quantum-hardware-execution\" />\n",
        "\n",
        "## Paso 2: Optimizar los circuitos para la ejecución del hardware cuántico\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "ee4d384a-15e2-4a3f-a52a-def3d184c4fa",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "['shift 011010', 'n_qubits 6', 'seed = 12345']"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "job_tags = [\n",
        "    f\"shift {hidden_shift_string}\",\n",
        "    f\"n_qubits {n_qubits}\",\n",
        "    f\"seed = {random_seed}\",\n",
        "]\n",
        "job_tags"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f2b77d93-c34a-43a4-b436-e7a25024a94a",
      "metadata": {
        "editable": true,
        "scrolled": true,
        "slideshow": {
          "slide_type": ""
        },
        "tags": []
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using backend ibm_kingston\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/readout-error-mitigation-sampler/extracted-outputs/f2b77d93-c34a-43a4-b436-e7a25024a94a-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Uncomment this to run the circuits on a quantum computer on IBMCloud.\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=100\n",
        ")\n",
        "\n",
        "# from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2\n",
        "# backend = FakeMelbourneV2()\n",
        "# backend.refresh(service)\n",
        "\n",
        "print(f\"Using backend {backend.name}\")\n",
        "\n",
        "\n",
        "def get_isa_circuit(circuit, backend):\n",
        "    pass_manager = generate_preset_pass_manager(\n",
        "        optimization_level=3, backend=backend, seed_transpiler=1234\n",
        "    )\n",
        "    isa_circuit = pass_manager.run(circuit)\n",
        "    return isa_circuit\n",
        "\n",
        "\n",
        "isa_circuit = get_isa_circuit(circuit, backend)\n",
        "display_circuit(isa_circuit)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f2f426da-3601-4442-9593-16bb19abf91e",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-circuits-using-qiskit-primitives\" />\n",
        "\n",
        "## Paso 3: Ejecutar circuitos utilizando Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "6ebaea63-e352-4d7c-a7ee-57a2f0a66306",
      "metadata": {},
      "outputs": [],
      "source": [
        "# submit job for solving the hidden shift problem using the Sampler primitive\n",
        "NUM_SHOTS = 50_000\n",
        "\n",
        "\n",
        "def run_sampler(backend, isa_circuit, num_shots):\n",
        "    sampler = Sampler(mode=backend)\n",
        "    sampler.options.environment.job_tags\n",
        "    pubs = [(isa_circuit, None, NUM_SHOTS)]\n",
        "    job = sampler.run(pubs)\n",
        "    return job\n",
        "\n",
        "\n",
        "def setup_mthree_mitigation(isa_circuit, backend):\n",
        "    # retrieve the final qubit mapping so mthree knows which qubits to calibrate\n",
        "    qubit_mapping = mthree.utils.final_measurement_mapping(isa_circuit)\n",
        "\n",
        "    # submit jobs for readout error calibration\n",
        "    mit = mthree.M3Mitigation(backend)\n",
        "    mit.cals_from_system(qubit_mapping, rep_delay=None)\n",
        "\n",
        "    return mit, qubit_mapping"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "9b76b943-fd71-4287-804b-980cacc078f8",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = run_sampler(backend, isa_circuit, NUM_SHOTS)\n",
        "mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "674ad6e1-c496-44ca-9598-470edf4c15dc",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-results-in-classical-format\" />\n",
        "\n",
        "## Paso 4: Procesamiento posterior y devolución de los resultados en formato clásico\n",
        "\n",
        "En la discusión teórica anterior, determinamos que para la entrada $ab$, esperamos la salida $ba$. Una complicación adicional es que, para tener un circuito más simple (pre-transpilado), insertamos las puertas CZ necesarias entre los pares de qubits vecinos pares de qubits vecinos. Esto equivale a intercalar las cadenas de bits $a$ y $b$ como $a1 b1 a2 b2 \\ldots$. La cadena de salida $ba$ se intercalará de forma similar: $b1 a1 b2 a2 \\ldots$. La función `unscramble` a continuación transforma la cadena de salida de $b1 a1 b2 a2 \\ldots$ en $a1 b1 a2 b2 \\ldots$ para que las cadenas de entrada y salida puedan compararse directamente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "aada9b41-0600-4814-a5a3-917d7fb171f9",
      "metadata": {},
      "outputs": [],
      "source": [
        "# retrieve bitstring counts\n",
        "def get_bitstring_counts(job):\n",
        "    result = job.result()\n",
        "    pub_result = result[0]\n",
        "    counts = pub_result.data.meas.get_counts()\n",
        "    return counts, pub_result"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "3930e290-89ff-404d-946b-cdf90119cb48",
      "metadata": {},
      "outputs": [],
      "source": [
        "counts, pub_result = get_bitstring_counts(job)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "138ef822-8e66-4f01-94f6-5a634465d2ac",
      "metadata": {},
      "source": [
        "La distancia de Hamming entre dos cadenas de bits es el número de índices en los que difieren los bits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "3488f07f-7705-4ef2-b033-ebc2cbe19d45",
      "metadata": {},
      "outputs": [],
      "source": [
        "def hamming_distance(s1, s2):\n",
        "    weight = 0\n",
        "    for c1, c2 in zip(s1, s2):\n",
        "        (c1, c2) = (int(c1), int(c2))\n",
        "        if (c1 == 1 and c2 == 1) or (c1 == 0 and c2 == 0):\n",
        "            weight += 1\n",
        "\n",
        "    return weight"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "9e2f66e7-cad3-4893-9fde-ddaec077d938",
      "metadata": {
        "scrolled": true
      },
      "outputs": [],
      "source": [
        "# Replace string of form a1b1a2b2... with b1a1b2a1...\n",
        "# That is, reverse order of successive pairs of bits.\n",
        "def unscramble(bitstring):\n",
        "    ps = [bitstring[i : i + 2][::-1] for i in range(0, len(bitstring), 2)]\n",
        "    return \"\".join(ps)\n",
        "\n",
        "\n",
        "def find_hidden_shift_bitstring(counts, hidden_shift_string):\n",
        "    # convert counts to probabilities\n",
        "    probs = {\n",
        "        unscramble(bitstring): count / NUM_SHOTS\n",
        "        for bitstring, count in counts.items()\n",
        "    }\n",
        "\n",
        "    # Retrieve the most probable bitstring.\n",
        "    most_probable = max(probs, key=lambda x: probs[x])\n",
        "\n",
        "    print(f\"Expected hidden shift string: {hidden_shift_string}\")\n",
        "    if most_probable == hidden_shift_string:\n",
        "        print(\"Most probable bitstring matches hidden shift 😊.\")\n",
        "    else:\n",
        "        print(\"Most probable bitstring didn't match hidden shift ☹️.\")\n",
        "    print(\"Top 10 bitstrings and their probabilities:\")\n",
        "    display(\n",
        "        {\n",
        "            k: (v, hamming_distance(hidden_shift_string, k))\n",
        "            for k, v in sorted(\n",
        "                probs.items(), key=lambda x: x[1], reverse=True\n",
        "            )[:10]\n",
        "        }\n",
        "    )\n",
        "\n",
        "    return probs, most_probable"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "9d3dd975-b628-48fc-8565-86b0ed88c973",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 011010\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'011010': (0.9743, 6),\n",
              " '001010': (0.00812, 5),\n",
              " '010010': (0.0063, 5),\n",
              " '011000': (0.00554, 5),\n",
              " '011011': (0.00492, 5),\n",
              " '011110': (0.00044, 5),\n",
              " '001000': (0.00012, 4),\n",
              " '010000': (8e-05, 4),\n",
              " '001011': (6e-05, 4),\n",
              " '000010': (6e-05, 4)}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "probs, most_probable = find_hidden_shift_bitstring(\n",
        "    counts, hidden_shift_string\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "67a88958-c900-4bd1-8329-d530ba3427ea",
      "metadata": {},
      "source": [
        "Registremos la probabilidad de la cadena de bits más probable antes de aplicar la mitigación de errores de lectura con M3.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "a95daf38-009e-44ea-a9c5-8041bf7b22e2",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "0.9743"
            ]
          },
          "execution_count": 12,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "max_probability_before_M3 = probs[most_probable]\n",
        "max_probability_before_M3"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9531e7bd-e134-4110-ba00-d5a08fe612d9",
      "metadata": {},
      "source": [
        "Ahora aplicamos la corrección de lectura aprendida por M3 a los recuentos.\n",
        "La función `apply_corrections` devuelve una distribución cuasi-probabilística. Esta es una lista de `float` objetos que suman un $1$ e. Sin embargo, algunos valores pueden ser negativos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "3a24d85c-a980-48cf-96ab-5237d370d7af",
      "metadata": {},
      "outputs": [],
      "source": [
        "def perform_mitigation(mit, counts, qubit_mapping):\n",
        "    # mitigate readout error\n",
        "    quasis = mit.apply_correction(counts, qubit_mapping)\n",
        "\n",
        "    # print results\n",
        "    most_probable_after_m3 = unscramble(max(quasis, key=lambda x: quasis[x]))\n",
        "\n",
        "    is_hidden_shift_identified = most_probable_after_m3 == hidden_shift_string\n",
        "    if is_hidden_shift_identified:\n",
        "        print(\"Most probable bitstring matches hidden shift 😊.\")\n",
        "    else:\n",
        "        print(\"Most probable bitstring didn't match hidden shift ☹️.\")\n",
        "    print(\"Top 10 bitstrings and their quasi-probabilities:\")\n",
        "    topten = {\n",
        "        unscramble(k): f\"{v:.2e}\"\n",
        "        for k, v in sorted(quasis.items(), key=lambda x: x[1], reverse=True)[\n",
        "            :10\n",
        "        ]\n",
        "    }\n",
        "    max_probability_after_M3 = float(topten[most_probable_after_m3])\n",
        "    display(topten)\n",
        "\n",
        "    return max_probability_after_M3, is_hidden_shift_identified"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "d304c727-f163-4842-a96a-dad8a067c8d1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 011010\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their quasi-probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'011010': '1.01e+00',\n",
              " '001010': '8.75e-04',\n",
              " '001000': '7.38e-05',\n",
              " '010000': '4.51e-05',\n",
              " '111000': '2.18e-05',\n",
              " '001011': '1.74e-05',\n",
              " '000010': '6.42e-06',\n",
              " '011001': '-7.18e-06',\n",
              " '011000': '-4.53e-04',\n",
              " '010010': '-1.28e-03'}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "print(f\"Expected hidden shift string: {hidden_shift_string}\")\n",
        "max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(\n",
        "    mit, counts, qubit_mapping\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b62fac82-e883-4030-9793-86af8299abaa",
      "metadata": {},
      "source": [
        "<span id=\"compare-identifying-the-hidden-shift-string-before-and-after-applying-m3-correction\" />\n",
        "\n",
        "#### Compare la identificación de la cadena de cambio oculta antes y después de aplicar una correccion M3\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "a3864692-582e-4fe0-8059-f0be59cfd229",
      "metadata": {},
      "outputs": [],
      "source": [
        "def compare_before_and_after_M3(\n",
        "    max_probability_before_M3,\n",
        "    max_probability_after_M3,\n",
        "    is_hidden_shift_identified,\n",
        "):\n",
        "    is_probability_improved = (\n",
        "        max_probability_after_M3 > max_probability_before_M3\n",
        "    )\n",
        "    print(f\"Most probable probability before M3: {max_probability_before_M3}\")\n",
        "    print(f\"Most probable probability after M3: {max_probability_after_M3}\")\n",
        "    if is_hidden_shift_identified and is_probability_improved:\n",
        "        print(\"Readout error mitigation effective! 😊\")\n",
        "    else:\n",
        "        print(\"Readout error mitigation not effective. ☹️\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "98f92ec2-50b5-4456-b40a-3f619cc42ff1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Most probable probability before M3: 0.9743\n",
            "Most probable probability after M3: 1.01\n",
            "Readout error mitigation effective! 😊\n"
          ]
        }
      ],
      "source": [
        "compare_before_and_after_M3(\n",
        "    max_probability_before_M3,\n",
        "    max_probability_after_M3,\n",
        "    is_hidden_shift_identified,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "90c3bec4-9a3d-43b7-8160-bcb615230d01",
      "metadata": {},
      "source": [
        "<span id=\"plot-how-cpu-time-required-by-m3-scales-with-shots\" />\n",
        "\n",
        "### Representa gráficamente cómo varía el tiempo de CPU requerido por M3 en función del número de disparos\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "33addc38-f738-48ed-a29d-9790f446c036",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Applying M3 correction to 5000 shots...\n",
            "\tDone in 0.003321983851492405 seconds.\n",
            "Applying M3 correction to 7500 shots...\n",
            "\tDone in 0.004425413906574249 seconds.\n",
            "Applying M3 correction to 10000 shots...\n",
            "\tDone in 0.006366567220538855 seconds.\n",
            "Applying M3 correction to 12500 shots...\n",
            "\tDone in 0.0071477219462394714 seconds.\n",
            "Applying M3 correction to 15000 shots...\n",
            "\tDone in 0.00860048783943057 seconds.\n",
            "Applying M3 correction to 17500 shots...\n",
            "\tDone in 0.010026784148067236 seconds.\n",
            "Applying M3 correction to 20000 shots...\n",
            "\tDone in 0.011459112167358398 seconds.\n",
            "Applying M3 correction to 22500 shots...\n",
            "\tDone in 0.012727141845971346 seconds.\n",
            "Applying M3 correction to 25000 shots...\n",
            "\tDone in 0.01406092382967472 seconds.\n",
            "Applying M3 correction to 27500 shots...\n",
            "\tDone in 0.01546052098274231 seconds.\n",
            "Applying M3 correction to 30000 shots...\n",
            "\tDone in 0.016769016161561012 seconds.\n",
            "Applying M3 correction to 32500 shots...\n",
            "\tDone in 0.019537431187927723 seconds.\n",
            "Applying M3 correction to 35000 shots...\n",
            "\tDone in 0.019739801064133644 seconds.\n",
            "Applying M3 correction to 37500 shots...\n",
            "\tDone in 0.021093040239065886 seconds.\n",
            "Applying M3 correction to 40000 shots...\n",
            "\tDone in 0.022840639110654593 seconds.\n",
            "Applying M3 correction to 42500 shots...\n",
            "\tDone in 0.023974396288394928 seconds.\n",
            "Applying M3 correction to 45000 shots...\n",
            "\tDone in 0.026412792038172483 seconds.\n",
            "Applying M3 correction to 47500 shots...\n",
            "\tDone in 0.026364430785179138 seconds.\n",
            "Applying M3 correction to 50000 shots...\n",
            "\tDone in 0.02820305060595274 seconds.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "Text(0.5, 1.0, 'Time to apply M3 correction')"
            ]
          },
          "execution_count": 17,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/readout-error-mitigation-sampler/extracted-outputs/33addc38-f738-48ed-a29d-9790f446c036-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# Collect samples for numbers of shots varying from 5000 to 25000.\n",
        "shots_range = range(5000, NUM_SHOTS + 1, 2500)\n",
        "times = []\n",
        "for shots in shots_range:\n",
        "    print(f\"Applying M3 correction to {shots} shots...\")\n",
        "    t0 = timeit.default_timer()\n",
        "    _ = mit.apply_correction(\n",
        "        pub_result.data.meas.slice_shots(range(shots)).get_counts(),\n",
        "        qubit_mapping,\n",
        "    )\n",
        "    t1 = timeit.default_timer()\n",
        "    print(f\"\\tDone in {t1 - t0} seconds.\")\n",
        "    times.append(t1 - t0)\n",
        "\n",
        "fig, ax = plt.subplots()\n",
        "ax.plot(shots_range, times, \"o--\")\n",
        "ax.set_xlabel(\"Shots\")\n",
        "ax.set_ylabel(\"Time (s)\")\n",
        "ax.set_title(\"Time to apply M3 correction\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bdfbeddd-e90e-4a5c-ad19-810603c4f695",
      "metadata": {},
      "source": [
        "<span id=\"interpreting-the-plot\" />\n",
        "\n",
        "#### Interpretación de la trama\n",
        "\n",
        "El gráfico anterior muestra que el tiempo necesario para aplicar la corrección M3 aumenta linealmente con el número de disparos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "4988be5c-46ad-473d-b1b1-c8280d8f6324",
      "metadata": {},
      "source": [
        "<span id=\"scaling-up\" />\n",
        "\n",
        "## Escalado hacia arriba\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "id": "1bf46124-9261-472e-864f-ee2ed0209079",
      "metadata": {
        "jp-MarkdownHeadingCollapsed": true
      },
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hidden shift string 00000010100110101011101110010001010000110011101001101010101001111001100110000111\n"
          ]
        }
      ],
      "source": [
        "n_qubits = 80\n",
        "rng = Random(12345)\n",
        "circuit, hidden_shift, hidden_shift_string = run_hidden_shift_circuit(\n",
        "    n_qubits, rng\n",
        ")\n",
        "\n",
        "print(f\"Hidden shift string {hidden_shift_string}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "53cd9086-27f8-481b-ba96-9bac9b146188",
      "metadata": {},
      "outputs": [],
      "source": [
        "isa_circuit = get_isa_circuit(circuit, backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "b176b2b3-de30-4a9b-905d-a06c7365376a",
      "metadata": {},
      "outputs": [],
      "source": [
        "job = run_sampler(backend, isa_circuit, NUM_SHOTS)\n",
        "mit, qubit_mapping = setup_mthree_mitigation(isa_circuit, backend)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "c4dda06e-e2c6-46b9-9e74-d90bb318419b",
      "metadata": {},
      "outputs": [],
      "source": [
        "counts, pub_result = get_bitstring_counts(job)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "3421d2f3-083f-44a9-bee4-5f05e43ebb4f",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': (0.50402,\n",
              "  80),\n",
              " '00000010100110101011101110010001010000110011100001101010101001111001100110000111': (0.0396,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001111001100100000111': (0.0323,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001101001100110000111': (0.01936,\n",
              "  79),\n",
              " '00000010100110101011101110010011010000110011101001101010101001111001100110000111': (0.01432,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001011001100110000111': (0.0101,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001101010101001110001100110000111': (0.00924,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000010011101001101010101001111001100110000111': (0.00908,\n",
              "  79),\n",
              " '00000010100110101011100110010001010000110011101001101010101001111001100110000111': (0.00888,\n",
              "  79),\n",
              " '00000010100110101011101110010001010000110011101001100010101001111001100110000111': (0.0082,\n",
              "  79)}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "probs, most_probable = find_hidden_shift_bitstring(\n",
        "    counts, hidden_shift_string\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2ec8335f-d3ef-4951-8bbd-b4d01f899be1",
      "metadata": {},
      "source": [
        "Vemos que se ha encontrado la cadena de desplazamiento oculta correcta. Además, las nueve cadenas de bits más probables son erróneas sólo en una posición.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "98d1fb7b-e696-4cef-a8b8-18e44b96eece",
      "metadata": {},
      "source": [
        "Anota la probabilidad más probable:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "c5a9dbdc-ebb7-470a-ac31-10b41217eeed",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "0.50402"
            ]
          },
          "execution_count": 23,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "max_probability_before_M3 = probs[most_probable]\n",
        "max_probability_before_M3"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "b4514b88-3f2d-4e96-88ce-6d291576568d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Expected hidden shift string: 00000010100110101011101110010001010000110011101001101010101001111001100110000111\n",
            "Most probable bitstring matches hidden shift 😊.\n",
            "Top 10 bitstrings and their quasi-probabilities:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'00000010100110101011101110010001010000110011101001101010101001111001100110000111': '9.85e-01',\n",
              " '00000010100110101011101110010001010000110011100001101010101001111001100110000111': '6.84e-03',\n",
              " '00000010100110101011100110010001010000110011101001101010101001111001100110000111': '3.87e-03',\n",
              " '00000010100110101011101110010011010000110011101001101010101001111001100110000111': '3.42e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001111001100100000111': '3.30e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001110001100110000111': '3.28e-03',\n",
              " '00000010100010101011101110010001010000110011101001101010101001111001100110000111': '2.62e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001101001100110000111': '2.43e-03',\n",
              " '00000010100110101011101110010000010000110011101001101010101001111001100110000111': '1.73e-03',\n",
              " '00000010100110101011101110010001010000110011101001101010101001111001000110000111': '1.63e-03'}"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "print(f\"Expected hidden shift string: {hidden_shift_string}\")\n",
        "max_probability_after_M3, is_hidden_shift_identified = perform_mitigation(\n",
        "    mit, counts, qubit_mapping\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 24,
      "id": "ccd14379-6c34-4be8-93ab-728356bf81e0",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Most probable probability before M3: 0.54348\n",
            "Most probable probability after M3: 0.99\n",
            "Readout error mitigation effective! 😊\n"
          ]
        }
      ],
      "source": [
        "compare_before_and_after_M3(\n",
        "    max_probability_before_M3,\n",
        "    max_probability_after_M3,\n",
        "    is_hidden_shift_identified,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "96b6648b-5c26-4a0c-92f2-2d1810fa14b8",
      "metadata": {},
      "source": [
        "Los resultados muestran que el error de lectura fue la fuente dominante de error y que la mitigación M3 fue eficaz.\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"
    },
    "toc": {
      "base_numbering": 0
    },
    "hours": 1,
    "qpuSeconds": 60
  },
  "nbformat": 4,
  "nbformat_minor": 4
}