{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "aee4cea9-ff2a-4c2b-a552-e2bc294414cb",
   "metadata": {},
   "source": [
    "# Quantum Computation, part 2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0e6cd111-2dcb-47ff-bda1-ef14a3dd60cb",
   "metadata": {},
   "source": [
    "Computational note for Quantum Computation course (MAT3420), spring 2026\n",
    "\n",
    "Part 2 covers lecture 12 and onward"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "770e7714-4d11-45ff-a516-09d7609bb284",
   "metadata": {},
   "outputs": [],
   "source": [
    "# print options\n",
    "import sympy as sy\n",
    "sy.init_printing(use_unicode=True)\n",
    "import numpy as np\n",
    "np.set_printoptions(suppress=True, legacy='1.25')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "455d8a0c-9fa8-4f6c-a424-855a37cb457c",
   "metadata": {},
   "source": [
    "## Lecture 12"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "708956f8-8b48-4144-8467-cbd510ff5577",
   "metadata": {},
   "source": [
    "The CHSH game:\n",
    "two cooperating players Alice and Bob plays the following game\n",
    "- Charlie (referee) gives them input bits $x$ and $y$ separately\n",
    "- they need to independently give back answer bits $a$ and $b$\n",
    "- they win if $a + b = x y \\bmod{2}$ holds, lose otherwise\n",
    "\n",
    "Alice and Bob prepares the EPR pair state before the game, and share the   ($H_A = \\mathbb{C}^2 = H_B$):\n",
    "$$ w = \\frac{1}{\\sqrt{2}} (\\ket{00} + \\ket{11}) \\in H_A \\otimes H_B $$\n",
    "\n",
    "For Alice's part, if $x = 0$, she will measure her part in the computational basis to decide $a$.\n",
    "Otherwise, she will measure it in the $(\\ket{+}, \\ket{-})$-basis, and reply with $a = 0$ in the \"+\" case, $a = 1$ in the \"-\" case.\n",
    "\n",
    "For Bob's part, he is going to work with the orthonormal basis\n",
    "$$ v = \\cos \\frac{\\pi}{8} \\ket{0} + \\sin \\frac{\\pi}{8} \\ket{1}, \\quad\n",
    "v' = \\cos \\frac{5\\pi}{8} \\ket{0} + \\sin \\frac{5\\pi}{8} \\ket{1} $$\n",
    "if $y = 0$, and otherwise with the orthonormal basis\n",
    "$$ w = \\cos \\frac{-\\pi}{8} \\ket{0} + \\sin \\frac{-\\pi}{8} \\ket{1}, \\quad\n",
    "w' = \\cos \\frac{3\\pi}{8} \\ket{0} + \\sin \\frac{3\\pi}{8} \\ket{1}. $$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "2d8f057e-5047-41c1-b5a2-442510a3110f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "rng = np.random.default_rng()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ec09a01c-ed02-421b-bcbd-079361cf5e6b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([0.70710678, 0.        , 0.        , 0.70710678])"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "zeroket = np.array([1, 0])\n",
    "oneket = np.array([0, 1])\n",
    "plusket = np.array([1, 1]) / np.sqrt(2)\n",
    "minusket = np.array([1, -1]) / np.sqrt(2)\n",
    "EPR_stt = (np.kron(zeroket, zeroket) + np.kron(oneket, oneket)) / np.sqrt(2)\n",
    "EPR_stt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "62e30b4e-e111-4930-b32f-b120f93f83ef",
   "metadata": {},
   "outputs": [],
   "source": [
    "bob_v = np.array([np.cos(np.pi / 8), np.sin(np.pi / 8)])\n",
    "bob_vp = np.array([np.cos(5 * np.pi / 8), np.sin(5 * np.pi / 8)])\n",
    "bob_w = np.array([np.cos(-np.pi / 8), np.sin(-np.pi / 8)])\n",
    "bob_wp = np.array([np.cos(3 * np.pi / 8), np.sin(3 * np.pi / 8)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "08fabb07-144b-43e1-a67a-3516be87adc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "def col_vec_inn_prod(v1, v2):\n",
    "    \"\"\"Hilbert space inner product of column vectors.\"\"\"\n",
    "    return np.conjugate(v1).dot(v2)\n",
    "\n",
    "\n",
    "def measure_part_in_twoqubit(v, loc, basis_choice):\n",
    "    \"\"\"Measure one qubit of a two-qubit state in a specified basis.\n",
    "\n",
    "    The function performs a projective measurement on one subsystem of a\n",
    "    two-qubit state and returns both the measurement outcome and the\n",
    "    post-measurement state.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    v : numpy.ndarray\n",
    "        One-dimensional complex array of length 4 representing a two-qubit\n",
    "        state in the computational basis.\n",
    "    loc : {\"A\", \"B\"}\n",
    "        Which qubit to measure.\n",
    "        ``\"A\"`` measures the left (first) qubit,\n",
    "        ``\"B\"`` measures the right (second) qubit.\n",
    "    basis_choice : int\n",
    "        Choice of measurement basis for the measured qubit:\n",
    "        ``0`` for the computational basis,\n",
    "        ``1`` for the |+⟩ / |−⟩ basis;\n",
    "        ``2`` for Bob's first basis;\n",
    "        ``3`` for Bob's second basis.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    int\n",
    "        Measurement outcome, ``0`` or ``1``, in the chosen basis.\n",
    "    numpy.ndarray\n",
    "        One-dimensional complex array of length 4 representing the\n",
    "        normalized post-measurement two-qubit state.\n",
    "    \"\"\"\n",
    "    # to be on the safe side, do not assume ||v|| = 1\n",
    "    # normalize the input vector\n",
    "    norm_v = np.sqrt(np.real(col_vec_inn_prod(v, v)))\n",
    "    v = v / norm_v\n",
    "    # decide basis\n",
    "    match basis_choice:\n",
    "        case 0:\n",
    "            basis_v0 = zeroket\n",
    "            basis_v1 = oneket\n",
    "        case 1:\n",
    "            basis_v0 = plusket\n",
    "            basis_v1 = minusket\n",
    "        case 2:\n",
    "            basis_v0 = bob_v\n",
    "            basis_v1 = bob_vp\n",
    "        case _:\n",
    "            basis_v0 = bob_w\n",
    "            basis_v1 = bob_wp\n",
    "    # decide PVM\n",
    "    sE0 = np.atleast_2d(basis_v0).T @ np.atleast_2d(basis_v0)\n",
    "    sE1 = np.atleast_2d(basis_v1).T @ np.atleast_2d(basis_v1)\n",
    "    if loc == \"A\":\n",
    "        E0 = np.kron(sE0, np.eye(2))\n",
    "        E1 = np.kron(sE1, np.eye(2))\n",
    "    else:\n",
    "        E0 = np.kron(np.eye(2), sE0)\n",
    "        E1 = np.kron(np.eye(2), sE1)\n",
    "    # the case 0 happens with probability ⟨v, E0(v)⟩\n",
    "    threshold = col_vec_inn_prod(v, E0 @ v)\n",
    "    rand_num = rng.random()\n",
    "    if rand_num < threshold:\n",
    "        E0v = E0 @ v\n",
    "        norm_E0v = np.sqrt(np.real(col_vec_inn_prod(E0v, E0v)))\n",
    "        new_state = E0v / norm_E0v\n",
    "        return (0, new_state)\n",
    "    else:\n",
    "        E1v = E1 @ v\n",
    "        norm_E1v = np.sqrt(np.real(col_vec_inn_prod(E1v, E1v)))\n",
    "        new_state = E1v / norm_E1v\n",
    "        return (1, new_state)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "899165f6-e3df-4a9b-a698-ab6ae8fe287e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, array([ 0.5, -0.5, -0.5,  0.5]))"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "measure_part_in_twoqubit(EPR_stt, \"A\", 1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "cd03b21c-53f5-46dd-a584-3b7fc9bb412b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, array([ 0.14644661, -0.35355339, -0.35355339,  0.85355339]))"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "measure_part_in_twoqubit(EPR_stt, \"B\", 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "38e07b24-f53c-4c27-b762-e278ba8e9c41",
   "metadata": {},
   "outputs": [],
   "source": [
    "def play_CHSH():\n",
    "    init_vec = EPR_stt\n",
    "    # Charlie's question bits\n",
    "    x, y = rng.integers(2), rng.integers(2)\n",
    "    # Alice computes her answer first\n",
    "    a, new_state = measure_part_in_twoqubit(init_vec, \"A\", x)\n",
    "    # Bob computes his answer next\n",
    "    b, _ = measure_part_in_twoqubit(new_state, \"B\", y + 2)\n",
    "    # they win if a + b = x y mod 2\n",
    "    if (a + b) % 2 == (x * y) % 2:\n",
    "        return \"win\"\n",
    "    else:\n",
    "        return \"lose\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "bb49e1da-aa61-490c-9120-7651c3eec0e8",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAABQAAAAOCAYAAAAvxDzwAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABfUlEQVQ4EX2U21UCQQyGgWMBix2IHaB2gB1ACUIHeHiCV0rAFqAD6ECxBEtQOli/b5yMy0VzTsjtTybJDNuu67olLRaLCjFLxs+P9hr/ruET18OeZJ8Y7WXBWVCez+er0EPiW8PDhl2d4ozDtVJcx5OoPkbs1U/oCbvZtbgx+GEDFxMkXCpI8BZ+bID+Ut8JHDInDMW1C11l7RU5JbhFjhqgJfYqY1r47aYbthJfdJtwbefOgTUygs/odr0lYWP8EhEb4LeQl/IiJkZu4RhhJ6cAWLAjnhHYPjwl4G2LeQtQs0O7e4A9Ubag5Ar+69I8p0u4VJAEb+8OGe+rhR5Al36DfbR8fIWIfWFUcDdGdkT3VgiQXblHgalbfI7aL6BfJUYedACYUCHPOsD3QczC1znXt7rPOdl1LCxooQOydxwqlgfG4xW7yTkFgHKfjV2M7A37vzW5ELa79enYqeRafKuFiLlr8yboh+Ytu5sZ/AkHXfo4uE8bCHKy8nH4BheYx7SVqsu/AAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle 83$"
      ],
      "text/plain": [
       "83"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[play_CHSH() for _ in range(100)].count(\"win\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ad1037d-f3b1-4c99-acc5-e596203ab8ce",
   "metadata": {},
   "source": [
    "## Lecture 13"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "0874735c-5af0-495e-a872-d688313a7519",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sympy as sy\n",
    "from sympy.physics.quantum import TensorProduct\n",
    "from functools import reduce\n",
    "import itertools as it"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "01b788c6-92ff-4043-8de8-4772c02a97dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "zeroket = sy.Matrix([1, 0])\n",
    "oneket = sy.Matrix([0, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "8031eefc-9818-4513-896a-922e1ee13e1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def comp_basis(x):\n",
    "    \"\"\"Return the computational basis vector |x⟩.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    x : iterable of int\n",
    "        Iterable of bits (0 or 1) specifying the computational basis\n",
    "        index.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    sympy.Matrix\n",
    "        One-dimensional SymPy column matrix representing the vector |x⟩.\n",
    "    \"\"\"\n",
    "    vec_seq = (zeroket if xi == 0 else oneket for xi in x)\n",
    "    return reduce(TensorProduct, vec_seq)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "68847437-dc64-43c8-82a5-7cbf10123184",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\left[\\begin{matrix}0\\\\1\\\\0\\\\0\\end{matrix}\\right], \\  \\left[\\begin{matrix}0\\\\0\\\\1\\\\0\\end{matrix}\\right]\\right)$"
      ],
      "text/plain": [
       "⎛⎡0⎤  ⎡0⎤⎞\n",
       "⎜⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢1⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥, ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢1⎥⎟\n",
       "⎜⎢ ⎥  ⎢ ⎥⎟\n",
       "⎝⎣0⎦  ⎣0⎦⎠"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# |01⟩ and |10⟩\n",
    "comp_basis([0, 1]), comp_basis([1, 0])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "d10a7668-3ddb-4ab7-aae0-46d08d11f4b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "def dot_pr(x, y):\n",
    "    \"\"\"Return the dot product of (integer) sequences.\"\"\"\n",
    "    return sum(xi * yi for xi, yi in it.zip_longest(x, y))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "b303562b-958c-4797-8292-45785cfcc158",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAAgAAAAOCAYAAAASVl2WAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAZElEQVQYGWP8//8/Aww0NDQYAdmrgdgYyP4AEmcBMgSA9GwgfgfEJkCsBMRwAFIAUhkKEgGyy4AUyBQ4YIKzcDBGFUACBj0chKHhJQQLN0ZQZAGDGBRBIOACxKC4OQfE94B4NwDm+hiAOyllRAAAAABJRU5ErkJggg==",
      "text/latex": [
       "$\\displaystyle 1$"
      ],
      "text/plain": [
       "1"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "dot_pr([0, 1], [1, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "0e61644f-1bbf-4b38-9167-7c56be0aa29d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def v_y(y):\n",
    "    \"\"\"Construct the state vector from Proposition 13.1.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    y : iterable of int\n",
    "        Iterable of bits (0 or 1).\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    sympy.Matrix\n",
    "        One-dimensional SymPy column matrix representing the normalized sum of\n",
    "        (-1)^(x · y) |x⟩ over all bit strings x of the same length as y.\n",
    "    \"\"\"\n",
    "    n = len(y)\n",
    "    ind_list = it.product(range(2), repeat=n)\n",
    "    res = sy.Matrix([0] * 2**n)\n",
    "    for x in ind_list:\n",
    "        res += comp_basis(x) * (1/sy.sqrt(2**n)) * (-1)**dot_pr(x, y)\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "1bd16cdd-1be3-44c3-a1bb-a6853d1d14e3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\left[\\begin{matrix}\\frac{1}{2}\\\\\\frac{1}{2}\\\\\\frac{1}{2}\\\\\\frac{1}{2}\\end{matrix}\\right], \\  \\left[\\begin{matrix}\\frac{1}{2}\\\\- \\frac{1}{2}\\\\- \\frac{1}{2}\\\\\\frac{1}{2}\\end{matrix}\\right]\\right)$"
      ],
      "text/plain": [
       "⎛⎡1/2⎤  ⎡1/2 ⎤⎞\n",
       "⎜⎢   ⎥  ⎢    ⎥⎟\n",
       "⎜⎢1/2⎥  ⎢-1/2⎥⎟\n",
       "⎜⎢   ⎥, ⎢    ⎥⎟\n",
       "⎜⎢1/2⎥  ⎢-1/2⎥⎟\n",
       "⎜⎢   ⎥  ⎢    ⎥⎟\n",
       "⎝⎣1/2⎦  ⎣1/2 ⎦⎠"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v_y([0, 0]), v_y([1, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "d7ccfa59-b2fd-4761-a580-ca82a39ea288",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tens_pow_H(k):\n",
    "    \"\"\"Return the tensor product of copies of the Hadamard matrix.\"\"\"\n",
    "    H = sy.Matrix([[1, 1],\n",
    "                   [1, -1]]) * (1/sy.sqrt(2))\n",
    "    return reduce(TensorProduct, (H,) * k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "cc17f6d0-ad18-4bf0-9c78-db55207d7ca6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}\\frac{1}{2} & \\frac{1}{2} & \\frac{1}{2} & \\frac{1}{2}\\\\\\frac{1}{2} & - \\frac{1}{2} & \\frac{1}{2} & - \\frac{1}{2}\\\\\\frac{1}{2} & \\frac{1}{2} & - \\frac{1}{2} & - \\frac{1}{2}\\\\\\frac{1}{2} & - \\frac{1}{2} & - \\frac{1}{2} & \\frac{1}{2}\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "⎡1/2  1/2   1/2   1/2 ⎤\n",
       "⎢                     ⎥\n",
       "⎢1/2  -1/2  1/2   -1/2⎥\n",
       "⎢                     ⎥\n",
       "⎢1/2  1/2   -1/2  -1/2⎥\n",
       "⎢                     ⎥\n",
       "⎣1/2  -1/2  -1/2  1/2 ⎦"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tens_pow_H(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "077b77d0-4c8d-466b-b031-c923901b67ee",
   "metadata": {},
   "source": [
    "### Deutsch-Josza problem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "69e128c6-ace1-4834-93d2-c95d9c63ba22",
   "metadata": {},
   "outputs": [],
   "source": [
    "def U(k, f):\n",
    "    \"\"\"Return the U_f gate for the Deutsch-Josza problem.\"\"\"\n",
    "    ind_list = it.product(range(2), repeat=k+1)\n",
    "    res = sy.zeros(2**(k+1))\n",
    "    for in_x in ind_list:\n",
    "        out_x = in_x[:-1] + (f(in_x[:-1]) ^ in_x[-1],)\n",
    "        res += comp_basis(out_x) * comp_basis(in_x).T\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "d037d22b-8af3-4af6-acd6-ba864435d39c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}0 & 1 & 0 & 0\\\\1 & 0 & 0 & 0\\\\0 & 0 & 0 & 1\\\\0 & 0 & 1 & 0\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "⎡0  1  0  0⎤\n",
       "⎢          ⎥\n",
       "⎢1  0  0  0⎥\n",
       "⎢          ⎥\n",
       "⎢0  0  0  1⎥\n",
       "⎢          ⎥\n",
       "⎣0  0  1  0⎦"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "U(1, lambda x: 1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad4a0d9d-7c40-4a3b-af4e-b505253a1ad9",
   "metadata": {},
   "source": [
    "the vector $v_y$ for $y = 1 \\cdots 1$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "9ed6d854-96ba-4e1e-a686-92a1839b9d23",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "⎡ √2 ⎤\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎣ 4  ⎦"
      ]
     },
     "execution_count": 22,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "v_y([1] * 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "81e60671-f56a-41a4-bdcc-4056128e4c12",
   "metadata": {},
   "source": [
    "the constant case gives $\\pm v_y$ for $y = 1 \\cdots 1$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "fcf60d26-4816-4a7c-9641-353196d2b354",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "⎡-√2 ⎤\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎣ 4  ⎦"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "U(2, lambda x: 1) * v_y([1] * 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ff5da84-9621-4a9f-833e-b6501277c46e",
   "metadata": {},
   "source": [
    "the balanced case gives a vector orthogonal to the above"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "d4fd780a-b56b-4db7-bcdb-73af84be9f2e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\- \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4}\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "⎡ √2 ⎤\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢-√2 ⎥\n",
       "⎢────⎥\n",
       "⎢ 4  ⎥\n",
       "⎢    ⎥\n",
       "⎢ √2 ⎥\n",
       "⎢ ── ⎥\n",
       "⎣ 4  ⎦"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "U(2, lambda x: x[0]) * v_y([1] * 3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "e93d2c31-1af3-4297-aa66-259ddfc119e6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\left[\\begin{matrix}0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\1\\end{matrix}\\right], \\  \\left[\\begin{matrix}0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\\\-1\\end{matrix}\\right], \\  \\left[\\begin{matrix}0\\\\0\\\\0\\\\1\\\\0\\\\0\\\\0\\\\0\\end{matrix}\\right], \\  \\left[\\begin{matrix}0\\\\1\\\\0\\\\0\\\\0\\\\0\\\\0\\\\0\\end{matrix}\\right]\\right)$"
      ],
      "text/plain": [
       "⎛⎡0⎤  ⎡0 ⎤  ⎡0⎤  ⎡0⎤⎞\n",
       "⎜⎢ ⎥  ⎢  ⎥  ⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0 ⎥  ⎢0⎥  ⎢1⎥⎟\n",
       "⎜⎢ ⎥  ⎢  ⎥  ⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0 ⎥  ⎢0⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥  ⎢  ⎥  ⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0 ⎥  ⎢1⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥, ⎢  ⎥, ⎢ ⎥, ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0 ⎥  ⎢0⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥  ⎢  ⎥  ⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0 ⎥  ⎢0⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥  ⎢  ⎥  ⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0 ⎥  ⎢0⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥  ⎢  ⎥  ⎢ ⎥  ⎢ ⎥⎟\n",
       "⎝⎣1⎦  ⎣-1⎦  ⎣0⎦  ⎣0⎦⎠"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# computational basis presentation\n",
    "Hpow = tens_pow_H(3)\n",
    "Hpow * U(2, lambda x: 0) * Hpow * comp_basis([1] * 3), \\\n",
    "    Hpow * U(2, lambda x: 1) * Hpow * comp_basis([1] * 3), \\\n",
    "    Hpow * U(2, lambda x: x[0]) * Hpow * comp_basis([1] * 3), \\\n",
    "    Hpow * U(2, lambda x: (x[0] + x[1]) % 2) * Hpow * comp_basis([1] * 3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "50423d86-a493-4b6f-8092-242ea4146abe",
   "metadata": {},
   "source": [
    "### Bernstein-Vazirani problem"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "ad23dd27-5341-43a9-883f-e2e917098572",
   "metadata": {},
   "outputs": [],
   "source": [
    "def V(k, f):\n",
    "    \"\"\"Return V_f gate for the Bernstein-Vazirani problem.\"\"\"\n",
    "    ind_list = it.product(range(2), repeat=k)\n",
    "    res = sy.zeros(2**k)\n",
    "    for x in ind_list:\n",
    "        res += comp_basis(x) * comp_basis(x).T * (-1)**f(x)\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "9fd9521c-a06b-4e2a-9444-0ee200c8daea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & -1 & 0 & 0\\\\0 & 0 & 1 & 0\\\\0 & 0 & 0 & -1\\end{matrix}\\right], \\  \\left[\\begin{matrix}1 & 0 & 0 & 0\\\\0 & -1 & 0 & 0\\\\0 & 0 & -1 & 0\\\\0 & 0 & 0 & 1\\end{matrix}\\right]\\right)$"
      ],
      "text/plain": [
       "⎛⎡1  0   0  0 ⎤  ⎡1  0   0   0⎤⎞\n",
       "⎜⎢            ⎥  ⎢            ⎥⎟\n",
       "⎜⎢0  -1  0  0 ⎥  ⎢0  -1  0   0⎥⎟\n",
       "⎜⎢            ⎥, ⎢            ⎥⎟\n",
       "⎜⎢0  0   1  0 ⎥  ⎢0  0   -1  0⎥⎟\n",
       "⎜⎢            ⎥  ⎢            ⎥⎟\n",
       "⎝⎣0  0   0  -1⎦  ⎣0  0   0   1⎦⎠"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "V(2, lambda x: dot_pr(x, [0, 1])), V(2, lambda x: dot_pr(x, [1, 1]))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "1bd9c8a3-3e50-44e2-aee5-709267a6e4ea",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\left[\\begin{matrix}\\frac{1}{2}\\\\- \\frac{1}{2}\\\\\\frac{1}{2}\\\\- \\frac{1}{2}\\end{matrix}\\right], \\  \\left[\\begin{matrix}\\frac{1}{2}\\\\- \\frac{1}{2}\\\\\\frac{1}{2}\\\\- \\frac{1}{2}\\end{matrix}\\right]\\right)$"
      ],
      "text/plain": [
       "⎛⎡1/2 ⎤  ⎡1/2 ⎤⎞\n",
       "⎜⎢    ⎥  ⎢    ⎥⎟\n",
       "⎜⎢-1/2⎥  ⎢-1/2⎥⎟\n",
       "⎜⎢    ⎥, ⎢    ⎥⎟\n",
       "⎜⎢1/2 ⎥  ⎢1/2 ⎥⎟\n",
       "⎜⎢    ⎥  ⎢    ⎥⎟\n",
       "⎝⎣-1/2⎦  ⎣-1/2⎦⎠"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# check Proposition 13.3\n",
    "s = [0, 1]\n",
    "V(2, lambda x: dot_pr(x, s)) * v_y([0, 0]), v_y(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "f135e0df-d567-4c12-bc0d-e328a325301b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\left[\\begin{matrix}0\\\\1\\\\0\\\\0\\end{matrix}\\right], \\  \\left[\\begin{matrix}0\\\\1\\\\0\\\\0\\end{matrix}\\right]\\right)$"
      ],
      "text/plain": [
       "⎛⎡0⎤  ⎡0⎤⎞\n",
       "⎜⎢ ⎥  ⎢ ⎥⎟\n",
       "⎜⎢1⎥  ⎢1⎥⎟\n",
       "⎜⎢ ⎥, ⎢ ⎥⎟\n",
       "⎜⎢0⎥  ⎢0⎥⎟\n",
       "⎜⎢ ⎥  ⎢ ⎥⎟\n",
       "⎝⎣0⎦  ⎣0⎦⎠"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s = [0, 1]\n",
    "tens_pow_H(2) * V(2, lambda x: dot_pr(x, s)) * v_y([0, 0]), comp_basis(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9de85a23-f1a7-4d6c-ba69-7d397d4ff433",
   "metadata": {},
   "source": [
    "## Lecture 14"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "69ef11cd-ab5c-40f6-bd80-508b5e95cb0b",
   "metadata": {},
   "source": [
    "Simon's problem: we work with Boolean functions $f \\colon \\{0, 1 \\}^k \\to  \\{0, 1 \\}^k$ and a secret string  $s \\in \\{0, 1 \\}^k$ that contains $1$.\n",
    "We know that $f(x) = f(y)$ if and only if either $x = y$ or $y$ is the bitwise-XOR, and want to work out $s$ from $f$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "38de3296-d4ec-41d1-b99f-23ceacd23ffe",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "rng = np.random.default_rng()\n",
    "import itertools as it\n",
    "from functools import reduce"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "ac112b5a-509e-4d8d-aba2-83201782ce86",
   "metadata": {},
   "outputs": [],
   "source": [
    "# set secret string\n",
    "k = 4  # 5 is the sensible maximum of naive implementation?\n",
    "s = tuple([rng.integers(2) for _ in range(k)])\n",
    "if 1 not in s:\n",
    "    # try again\n",
    "    s = tuple([rng.integers(2) for _ in range(k)])\n",
    "    if 1 not in s:\n",
    "        print(\"we could not generate a secret string.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "bc0c4c67-5e09-4e7d-b6c1-b2c416ac99fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "# function satisfying the above condition\n",
    "def f_from_s(x):\n",
    "    \"\"\"Return x or x XOR s according to a fixed selection rule.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    x : tuple of int\n",
    "        Tuple of bits (0 or 1) of length ``k`` representing the input.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    tuple of int\n",
    "        A tuple equal to either ``x`` (if ``s`` is the sequence of 0's)\n",
    "        or ``x XOR s``.\n",
    "    \"\"\"\n",
    "    ind_s_1 = [i for i in range(k) if s[i] == 1][0]\n",
    "\n",
    "    if x[ind_s_1] == 0:\n",
    "        return x\n",
    "    y_l = [x[i] ^ s[i] for i in range(k)]\n",
    "    return tuple(y_l)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "id": "0030a649-883a-4fb9-b1e5-e9b76ea9004a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "((1, 1, 1, 0), (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 1))"
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s, f_from_s(s), f_from_s((0,) * k), f_from_s((1,) * k)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f4d5fb63-b12c-4c9d-b38d-91ad6c3da3f0",
   "metadata": {},
   "source": [
    "### classical algorithm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce6bff0d-8bb5-4997-aa34-c71cdd214a77",
   "metadata": {},
   "source": [
    "Th classical \"birthday attack\" algorithm works by picking input strings $x^1, x^2, \\ldots$ and querying $f(x^1), f(x^2), \\ldots$, recording the results.\n",
    "It stops when it sees a pair $x^i, x^j$ such that $f(x^i) = f(x^j)$.\n",
    "Then the secret is $s = x^i \\oplus x^j$, the bitwise XOR."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "id": "c84c2f04-b072-46a1-8ff5-8feb117be1a6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "found secret [1, 1, 1, 0] after 9 queries\n"
     ]
    }
   ],
   "source": [
    "res_rec = {}  # dictionary of f(x) as key and x as value\n",
    "ind_list = it.product(range(2), repeat=k)\n",
    "for x in ind_list:\n",
    "    f_x = f_from_s(x)\n",
    "    if f_x in res_rec.keys():\n",
    "        prev_x = res_rec[f_x]\n",
    "        found_secret = [x[i] ^ prev_x[i] for i in range(k)]\n",
    "        print(f\"found secret {found_secret} after {1 + len(res_rec)} queries\")\n",
    "        break\n",
    "    # if f(x) is not found, record the results and continue with next x\n",
    "    res_rec[f_x] = x"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7928a1a5-745b-4b63-a856-5cf61cbf0391",
   "metadata": {},
   "source": [
    "the expected run time is $\\sqrt{2^k}$, where $k$ is the length of secret"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "id": "36d00761-fe2f-4a19-a4a9-938a7d2075c6",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAABoAAAAOCAYAAAAxDQxDAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABdklEQVQ4EZWU7VHCQBCGgaEABjuIHejQQUpQrEDpQP7mn4MdaAvagemAkQ5MB2A6iM97k2VyZA/Jzmz27t2v7N7djpumGXlUFMUN+Aq58vQehu2mxffIa3gDVgmb6JOgD/B5QteDCfgNuEWu4VfWa/iLdSZjNxHKZykvJeyfsJ0hP82Hdc1a+zdhvUQYqGUyEl9K9xjuHOMtWE7MWS8RigcU747TOShHeXAMwvmA51EiEqhloVTHyYX0t64iBufHRDhk6Gqk/UVsmt7ZhTnX6qh1uspDW5ZOH2uuQkUk0K0Z1LJOHO9sTG3V7idty3Q1h7YsBMPPWuadlWHVFGudzQIHPdAu6ZpnLV4h9QBTVKJQnFOyisopAWQkjgj8F6BE6o38R/pJGz9d21s2O2LU4Yy6ms5aZVvpAcZBLW5gjZsjsdclOiDvDGQt3yX8KGx8OlQx0KVQG/QIRRojmmGaXyPkD0KVRsOWvQKrqhrWUF3AL+BhYvwBQvh/HPlSYygAAAAASUVORK5CYII=",
      "text/latex": [
       "$\\displaystyle 4.0$"
      ],
      "text/plain": [
       "4.0"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.sqrt(2**k)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80e331a0-cb69-4bcc-bb0d-0f3884e5cb8e",
   "metadata": {},
   "source": [
    "### Simon's quantum algorithm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0a1d2967-1880-4a31-9905-32dc448fa641",
   "metadata": {},
   "source": [
    "the quantum algorithm looks at the state\n",
    "$$ v = (H^{\\otimes k} \\otimes I_{2^k}) U_f (H^{\\otimes k} \\otimes I_{2^k}) \\ket{0 \\cdots 0} $$\n",
    "in the $2k$-qubit space $\\mathbb{C}^{2^{2 k}} = \\mathbb{C}^{2^k} \\otimes \\mathbb{C}^{2^k}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "84d37ee3-1a44-444e-86aa-e4ce8a1ae13d",
   "metadata": {},
   "outputs": [],
   "source": [
    "zeroket = np.array([1, 0])\n",
    "oneket = np.array([0, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "57aae6a3-bf07-411a-ada9-e7e57f10cd9d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def comp_basis(x):\n",
    "    # computational basis vector\n",
    "    # input\n",
    "    #   x: array of int (0 or 1)\n",
    "    # output\n",
    "    #   numpy.array, 1-dimensional array representing |x⟩\n",
    "    vec_seq = (zeroket if xi == 0 else oneket for xi in x)\n",
    "    return reduce(np.kron, vec_seq)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "afd18dda-79e0-46d4-8a7b-303cc8f57e08",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tens_pow_H(k):\n",
    "    # tensor product of copies of the Hadamard matrix\n",
    "    H = np.array([[1, 1],\n",
    "                  [1, -1]]) * (1/np.sqrt(2))\n",
    "    return reduce(np.kron, [H] * k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "id": "50818f7f-3353-4b5b-9b16-c7b4c5b9384e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def termwise_XOR(x, y):\n",
    "    # termwise XOR of tuples\n",
    "    return tuple([xi ^ yi for xi, yi in it.zip_longest(x, y)])\n",
    "\n",
    "\n",
    "def U(k, f):\n",
    "    # U_f gate for the Simon algorithm\n",
    "    ind_list = it.product(range(2), repeat=2*k)\n",
    "    res = np.zeros((2**(2*k), 2**(2*k)), dtype=np.int8)\n",
    "    for in_x in ind_list:\n",
    "        out_x = in_x[0:k] + termwise_XOR(f(in_x[0:k]), in_x[k:2*k])\n",
    "        in_as_row = np.atleast_2d(comp_basis(in_x))\n",
    "        out_as_column = np.atleast_2d(comp_basis(out_x)).T\n",
    "        res += out_as_column @ in_as_row\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "3379fa9e-efa9-4472-8c1d-7961473e266d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFkAAAAVCAYAAAAtkUK4AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD20lEQVRYCe2Z7VEbMRCGjScFOKQD0wEfFQQ6AFIB0EHyE/5lkg6ACgJ0EFJC6AA6CLgD8jyydMhnnTGO7YOZ7IwsabWSVq9Wq9W58/j42MnT8fFxP6//L4/i8xweJfy6nYxOTk4+U13PWFMX6TvWD16P1J96kJYE56x7n/HEsaIVd0aiYZdsi/xLYMQf6oKUeJuU763Dv4kiIaP+QKFHSnzL0gZtg2Hx6Rfet6daKP2oj1lrf3GV8VrRnXkFeUB+ptIBZCoC8ot8Q2Yi6ip5Sr6T8QTHQXbgX2f8W8qrJMe6I12RviIzAnAc85I2Nyr0J7e+Tr5GPheK87SmO/P/ZiEfyQfv4ooE7rSwOvlHOZ9OgnMIT2DeZ2038PeyelPRflpttUHU08Y09ZmF37bu4hl06Ebt91l0MO3aarap39ImCDkJ0Iv9LePokvTdI3PB91RUpyWf6B/KrerOelyjuPa6/Lhwj3eJBPMOmZEjnwnWwc+aikVPhb6qabxipxmZr0F3cd3XXWhB+dGt1gQYTcc/RBK0p0su9KGuGxH4DyT9uT45l/HidNPs/4n0h6QfvoRX1IG2mYjxXoPurmlHkF14yR8XFxcBym/tJCe4F7QHKyVXRleTX5DKSJvwU8TSofxAOiB5WS6MGN/NXabuBgPbXX5c+D1pWvLiukLh73kH6oIZAJZP2aPiToYNpJ4ANooY8cnIXJDOMxmqC6Fl6y6ufUFeJVXgTFoaIAiYx73pKNa7C7TBudaTqOT/DXfcBE/VQqgl3V1rT5CnIpTU366Sj0UB8H6SBKqJjETSRqa8JJtvRql9Jh5zt6V7MGBB1qS1okZCSSOQNfLKginnFqoFlsZwkg6y6fLTfZTkFJNKVj5smfGXudvU3bXeC3I40k1rQEkvi7HnNjyVT778DLnSa81YNY8adDcla/WlaWhXyVKetBmIP09t6Z5pppHdCbJWtpU1VEWUFBAvC4+7T9QqwTuiPojCgR/LIaMtfSTJrd/o4Zo2X0KBKAvmPulgyAmWL8+IY5ILSuLFnL6t6F5TRuO5WeHTnJZqnDpmifAMQUqW51g+o6tvHXFRKSxzB7Vyn+BpI+wTCJ4gC6SkbD2e7iDj3FLxA9Owqfk39m9F96RV1OEofSByQXswk+9Mcq3m6KNL0vLHNqpVxaaYHJ3dYAOCtW6UH/uYMsU4yxDxLnhzAEdgPNXBLQaQWYiPgzxaWAaAE+dAJ92Jz+43R+iuFYtneHQlS3YhXlBTP6/tsGA6RMmRV+WC55vn8OJYfSKu/hlxhrgDu294cfMEaqaxwM6oys8OVcz/F6MGOzRSzAclAAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle \\left( 256, \\  256\\right)$"
      ],
      "text/plain": [
       "(256, 256)"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "U(k, f_from_s).shape"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "id": "5f31c599-b790-4848-9e10-308144c57053",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0]]\n"
     ]
    }
   ],
   "source": [
    "# first 20 x 20 portion of U_f\n",
    "print(U(k, f_from_s)[0:20, 0:20])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "a58cc63a-17ef-446f-8230-5f2ec4bb3bfb",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1]\n",
      " [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0]]\n"
     ]
    }
   ],
   "source": [
    "# bottom right 20x20 portion\n",
    "print(U(k, f_from_s)[-20:, -20:])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "id": "2cf02e84-a537-4920-b042-247b2c0e6099",
   "metadata": {},
   "outputs": [],
   "source": [
    "H_pow_ampl = np.kron(tens_pow_H(k), np.identity(2**k))\n",
    "simon_gate = H_pow_ampl @ U(k, f_from_s) @ H_pow_ampl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "id": "81af28e7-b336-4791-b9a2-b2c0700f38b1",
   "metadata": {},
   "outputs": [],
   "source": [
    "v = simon_gate @ comp_basis([0] * (2 * k))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "id": "7d0ce930-4a6f-4b38-8947-b4bdcbb2e478",
   "metadata": {},
   "outputs": [],
   "source": [
    "def proj(x):\n",
    "    \"\"\"Return the projection for the computational basis.\"\"\"\n",
    "    basis_vec = np.atleast_2d(comp_basis(x))\n",
    "    return basis_vec.T @ basis_vec\n",
    "\n",
    "\n",
    "def meas_prob(x):\n",
    "    \"\"\"Return the probability of observing x in the first half of v.\"\"\"\n",
    "    return np.dot(v, np.kron(proj(x), np.identity(2**k)) @ v)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "id": "0cc5d542-8578-4692-b88e-ef48230acd80",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0, 0, 0, 0) with probability 0.12499999999999983\n",
      "(0, 0, 0, 1) with probability 0.12499999999999983\n",
      "(0, 0, 1, 0) with probability 3.8457049370327185e-64\n",
      "(0, 0, 1, 1) with probability 3.8457049370327185e-64\n",
      "(0, 1, 0, 0) with probability 3.8457049370327185e-64\n",
      "(0, 1, 0, 1) with probability 3.8457049370327185e-64\n",
      "(0, 1, 1, 0) with probability 0.12499999999999983\n",
      "(0, 1, 1, 1) with probability 0.12499999999999983\n",
      "(1, 0, 0, 0) with probability 3.8457049370327185e-64\n",
      "(1, 0, 0, 1) with probability 3.8457049370327185e-64\n",
      "(1, 0, 1, 0) with probability 0.12499999999999983\n",
      "(1, 0, 1, 1) with probability 0.12499999999999983\n",
      "(1, 1, 0, 0) with probability 0.12499999999999983\n",
      "(1, 1, 0, 1) with probability 0.12499999999999983\n",
      "(1, 1, 1, 0) with probability 3.8457049370327185e-64\n",
      "(1, 1, 1, 1) with probability 3.8457049370327185e-64\n"
     ]
    }
   ],
   "source": [
    "intervals = {}\n",
    "t = 0.0\n",
    "for x in it.product(range(2), repeat=k):\n",
    "    prob_for_x = meas_prob(x)\n",
    "    intervals[x] = (t, t + prob_for_x)\n",
    "    t += prob_for_x\n",
    "    print(f\"{x} with probability {prob_for_x}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "id": "23eba243-74db-4962-a2a2-11565bcc18b8",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_samples = []\n",
    "for _ in range(k):  # when k is small, it's better to repeat a bit more\n",
    "    rand_num = rng.random()\n",
    "    for x in it.product(range(2), repeat=k):\n",
    "        if intervals[x][0] < rand_num and rand_num < intervals[x][1]:\n",
    "            x_samples.append(x)\n",
    "            break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "id": "b38d7882-fd0c-4aaf-962d-db6feccf984e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[ \\left( 0, \\  1, \\  1, \\  0\\right), \\  \\left( 1, \\  0, \\  1, \\  0\\right), \\  \\left( 0, \\  1, \\  1, \\  1\\right), \\  \\left( 1, \\  0, \\  1, \\  1\\right)\\right]$"
      ],
      "text/plain": [
       "[(0, 1, 1, 0), (1, 0, 1, 0), (0, 1, 1, 1), (1, 0, 1, 1)]"
      ]
     },
     "execution_count": 48,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x_samples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "id": "2ff6fc0c-48b7-43c8-bed9-d0dd94d8c3e8",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 1, 1, 0) is a candidate for the secret\n"
     ]
    }
   ],
   "source": [
    "# find s by solving x∙s = 0 mod 2 for the x in the above samples\n",
    "def inn_prod(x, s):\n",
    "    return sum(xi * si for xi, si in it.zip_longest(x, s))\n",
    "\n",
    "\n",
    "def predicate(s):\n",
    "    if s == (0,) * k:\n",
    "        return False\n",
    "    inn_prod_mod_2_list = [inn_prod(x, s) % 2 for x in x_samples]\n",
    "    if 1 in inn_prod_mod_2_list:\n",
    "        return False\n",
    "    return True\n",
    "\n",
    "\n",
    "for s_cand in it.product(range(2), repeat=k):\n",
    "    if predicate(s_cand):\n",
    "        print(f\"{s_cand} is a candidate for the secret\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f1addf3-f495-41fe-843b-0e376e3008aa",
   "metadata": {},
   "source": [
    "Variation: every time we get a new candidate for $x^i = x^i_1 \\cdots x^i_k$, we set the syste of linear equations\n",
    "$$ x^i_1 s_1 + x^i_2 s_2 + \\cdots + x^i_k s_k = 0 \\bmod 2 \\quad (i = 1, 2, \\ldots )$$\n",
    "and continue until there is only one non-zero solution for $s$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "id": "26a9c99b-1f1c-440b-9cc5-82428aed1939",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{s_1: -s_2}\n",
      "{s_1: -s_2}\n",
      "{s_1: -s_2, s_4: 0}\n",
      "{s_1: -s_2, s_4: 0}\n",
      "{s_1: s_3, s_2: -s_3, s_4: 0}\n",
      "got a unique solution after 7 trials\n"
     ]
    }
   ],
   "source": [
    "import sympy as sy\n",
    "# prepare variables s_1, ..., s_k\n",
    "s_var = [sy.Symbol(f\"s_{j+1}\") for j in range(k)]\n",
    "\n",
    "x_samples = []\n",
    "l_poly = []\n",
    "for trial_cnt in range(2 * k):\n",
    "    rand_num = rng.random()\n",
    "    for x in it.product(range(2), repeat=k):\n",
    "        if intervals[x][0] < rand_num and rand_num < intervals[x][1]:\n",
    "            new_x = x\n",
    "            break\n",
    "    # ignore x^i = (0,..,0), otherwise set linear polynomial with\n",
    "    # coefficients x^i_j\n",
    "    if new_x == (0,) * k:\n",
    "        continue\n",
    "    else:\n",
    "        f2 = sy.FF(2)\n",
    "        i_th_lin_poly = sy.Poly(sum(f2(new_x[j]) * s_var[j] for j in range(k)))\n",
    "        l_poly.append(i_th_lin_poly)\n",
    "    # find solutions\n",
    "    ans_set = sy.solve(l_poly)\n",
    "    print(ans_set)\n",
    "    if len(ans_set) == k - 1:\n",
    "        print(f\"got a unique solution after {trial_cnt + 1} trials\")\n",
    "        break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "id": "677d5eeb-f497-49b0-a428-e68af80d402b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGEAAAAVCAYAAABWtYB0AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACNElEQVRYCe1Z21HDMBB0GArIQAehA0I6SDrAdEDogF/njwkdACVAB6QDJukAOiBQArtG51iyPCOQfF+6GSHdyb499vSwlFFVVbdFUdygUMrVarX7bea/QzEAji3OR0jCGmCv6NgMBZr9+hkA5zX3x/7ugxUPnkN7Rpmi/X3oSdvSwmHUGljAIMGUT5QzlDVsHzS44k0CHh7jwSeUPcoFygQluWjhMHBlrC0g74D50sLeQl+gdBLRlwSO+NI44PrF2ZBcEJAKDgPXwgLOEnBj1HUCBNvoD9AXtLXlqK3kdhIGOHh9HzdvsM+RDK4yluQkWHQkUebwwmXcFVmG2G9JToJFR5ziG+UejyeuLSfBZSROF4K51/VJXo76mFG0n7pYeSa4jMTpvr1APMos4bnBkpwEi444BXuCLEOdJQeexSYbdAOWk9BQkazB65+Jx5vMhM71UHQSAr8IPDH9zaSFw6gisXjFw1sGV6Yw7OBbZkvTH5IE2Ugkk83LJtgv1Dymx4oWDuMcDAtcPML/HvWlEGJ4uoJ+LbZ23XuLiheZUQoPF1zPeArkesYbVwLVgva7af7rgk8LhzFqYQGHfPECj6OeG/EMhXdJ1kkaen2L2psEvBQscMasb1B3plqwk4AHtXAYigYWMOokhCxHAfQUMzgcNAEmCC0cwqlhRScB5HPqdb59DWnJKi0cBqyJRbzoJMDHEkHf09nAooXDf0MTq5A9QXby/BvzwCOJ7jFo5TdmfnGWPy1h6aNrLvojAAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle \\left[ 1, \\  1, \\  1, \\  0\\right]$"
      ],
      "text/plain": [
       "[1, 1, 1, 0]"
      ]
     },
     "execution_count": 51,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# find free variables\n",
    "free_vars = set()\n",
    "for key in ans_set.keys():\n",
    "    free_vars |= ans_set[key].free_symbols\n",
    "# get the nonzero solution as a string\n",
    "if len(free_vars) == 0:\n",
    "    subs_dict = {}\n",
    "else:\n",
    "    subs_dict = {list(free_vars)[0]: 1}\n",
    "\n",
    "ans_seq = []\n",
    "for the_var in s_var:\n",
    "    if the_var in ans_set.keys():\n",
    "        ans_seq.append(ans_set[the_var].subs(subs_dict) % 2)\n",
    "    else:\n",
    "        ans_seq.append(1)\n",
    "\n",
    "ans_seq"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c8c74e11-cd72-44d0-8325-6a7bd073f7f2",
   "metadata": {},
   "source": [
    "#### optimization 1: sparse matrices"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2358f1f8-c2bb-41c6-8e08-7e8e38cb7396",
   "metadata": {},
   "source": [
    "try bigger $k$ with sparse matrix algorithms from `scipy` library"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "id": "360f9994-420e-415c-9447-982c734ff26d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# set secret string\n",
    "k = 7\n",
    "s = tuple([rng.integers(2) for _ in range(k)])\n",
    "if 1 not in s:\n",
    "    # try again\n",
    "    s = tuple([rng.integers(2) for _ in range(k)])\n",
    "    if 1 not in s:\n",
    "        print(\"we could not generate a secret string.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "id": "b8efd579-1190-4abd-947a-bff9584c6e4a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 0, 0, 1, 0, 0, 1)"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "id": "8ceb53ea-1534-4306-82d8-e1ce85795e6a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# sparse matrix for large k\n",
    "import scipy.sparse as spa\n",
    "\n",
    "\n",
    "def ind_from_seq(x):\n",
    "    \"\"\"Convert the binary sequence of length k to matrix index 0...2**k-1.\"\"\"\n",
    "    return sum(2**(len(x)-j-1) * x[j] for j in range(len(x)))\n",
    "\n",
    "\n",
    "def comp_basis_sp(x):\n",
    "    \"\"\"Return 2-d column vector representing the x-th basis.\"\"\"\n",
    "    mat_size = 2**len(x)\n",
    "    res = spa.dok_matrix((mat_size, 1))\n",
    "    res[ind_from_seq(x), 0] = 1\n",
    "    return res.tocsr()\n",
    "\n",
    "\n",
    "def U_sp(k, f):\n",
    "    \"\"\"Return U_f gate for Simon's algorithm.\"\"\"\n",
    "    mat_size = 2**(2*k)\n",
    "    res = spa.dok_matrix((mat_size, mat_size))\n",
    "    ind_list = it.product(range(2), repeat=2*k)\n",
    "    for in_x in ind_list:\n",
    "        out_x = in_x[0:k] + termwise_XOR(f(in_x[0:k]), in_x[k:2*k])\n",
    "        res[ind_from_seq(out_x), ind_from_seq(in_x)] = 1\n",
    "    return res.tocsr()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "id": "7f84bdfe-881f-4b21-a362-8457e8519b4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "H_pow_ampl_sp = spa.kron(tens_pow_H(k), np.identity(2**k))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "id": "1eea58ab-b69c-4a71-b87f-2f39f0e4d6d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "simon_gate_sp = H_pow_ampl_sp @ U_sp(k, f_from_s) @ H_pow_ampl_sp"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "id": "d587ee95-b496-468d-9098-9c5478bfc142",
   "metadata": {},
   "outputs": [],
   "source": [
    "v = simon_gate_sp.dot(comp_basis_sp([0] * (2 * k)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "id": "88d4ab04-01e0-4295-8be8-936ee2b00dbf",
   "metadata": {},
   "outputs": [],
   "source": [
    "# sparse version of helper commands\n",
    "def proj(x):\n",
    "    \"\"\"Return projection for the computational basis.\"\"\"\n",
    "    basis_vec = comp_basis_sp(x)\n",
    "    return basis_vec @ basis_vec.T\n",
    "\n",
    "\n",
    "def meas_prob(x):\n",
    "    \"\"\"Return the probability of observing x in the first half of v.\"\"\"\n",
    "    return (v.T @ spa.kron(proj(x), np.identity(2**k)) @ v)[0, 0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "id": "022adc66-c94f-478b-ba05-be4745decd8a",
   "metadata": {},
   "outputs": [],
   "source": [
    "intervals = {}\n",
    "t = 0.0\n",
    "for x in it.product(range(2), repeat=k):\n",
    "    prob_for_x = meas_prob(x)\n",
    "    intervals[x] = (t, t + prob_for_x)\n",
    "    t += prob_for_x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "id": "67f905de-d7bb-44d0-897d-a641d8c96c7b",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_samples = []\n",
    "for _ in range(k):  # when k is small, it's better to repeat a bit more\n",
    "    rand_num = rng.random()\n",
    "    for x in it.product(range(2), repeat=k):\n",
    "        if intervals[x][0] < rand_num and rand_num < intervals[x][1]:\n",
    "            x_samples.append(x)\n",
    "            break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "id": "58af8a76-5413-46fa-921b-346bb6f3f5af",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[ \\left( 0, \\  1, \\  0, \\  1, \\  0, \\  1, \\  1\\right), \\  \\left( 1, \\  1, \\  1, \\  0, \\  0, \\  0, \\  1\\right), \\  \\left( 0, \\  1, \\  1, \\  1, \\  0, \\  1, \\  1\\right), \\  \\left( 1, \\  0, \\  0, \\  1, \\  1, \\  0, \\  0\\right), \\  \\left( 0, \\  1, \\  1, \\  1, \\  0, \\  0, \\  1\\right), \\  \\left( 0, \\  1, \\  1, \\  0, \\  1, \\  1, \\  0\\right), \\  \\left( 1, \\  0, \\  1, \\  1, \\  0, \\  1, \\  0\\right)\\right]$"
      ],
      "text/plain": [
       "[(0, 1, 0, 1, 0, 1, 1), (1, 1, 1, 0, 0, 0, 1), (0, 1, 1, 1, 0, 1, 1), (1, 0, 0 ↪\n",
       "\n",
       "↪ , 1, 1, 0, 0), (0, 1, 1, 1, 0, 0, 1), (0, 1, 1, 0, 1, 1, 0), (1, 0, 1, 1, 0, ↪\n",
       "\n",
       "↪  1, 0)]"
      ]
     },
     "execution_count": 61,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x_samples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "id": "2331ecbe-660f-4647-804c-6d692756ae47",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 0, 0, 1, 0, 0, 1) is a candidate for the secret\n"
     ]
    }
   ],
   "source": [
    "for s_cand in it.product(range(2), repeat=k):\n",
    "    if predicate(s_cand):\n",
    "        print(f\"{s_cand} is a candidate for the secret\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "72635b98-e7bf-4c62-8ea9-a6bd06a56e4e",
   "metadata": {},
   "source": [
    "#### optimization 2: fast Walsh-Hadamard transform"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a094d9c-9276-45c9-afdc-073646616fbd",
   "metadata": {},
   "source": [
    "we avoid creating matrices and do in-place manipulation as much as possible"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "id": "78fa068c-04f8-4d88-b632-679b8ff262b0",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(1, 1, 1, 0, 0, 1, 0, 1, 0, 1)"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# set secret string\n",
    "k = 10\n",
    "s = tuple([rng.integers(2) for _ in range(k)])\n",
    "if 1 not in s:\n",
    "    # try again\n",
    "    s = tuple([rng.integers(2) for _ in range(k)])\n",
    "    if 1 not in s:\n",
    "        print(\"we could not generate a secret string.\")\n",
    "\n",
    "s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 64,
   "id": "13ebef43-d7ad-41ad-9085-b990c37f8214",
   "metadata": {},
   "outputs": [],
   "source": [
    "def fwht(a):\n",
    "    \"\"\"Perform the in-place Fast Walsh–Hadamard Transform of array a.\"\"\"\n",
    "    h = 1\n",
    "    n = len(a)\n",
    "    while h < n:\n",
    "        # perform FWHT\n",
    "        for i in range(0, n, h * 2):\n",
    "            for j in range(i, i + h):\n",
    "                x = a[j]\n",
    "                y = a[j + h]\n",
    "                a[j] = x + y\n",
    "                a[j + h] = x - y\n",
    "        # normalize and increment h\n",
    "        a *= 1/np.sqrt(2)\n",
    "        h *= 2\n",
    "\n",
    "\n",
    "def H_pow_ampl_fwht(x, k):\n",
    "    \"\"\"Apply (H^{⊗k} ⊗ I_{2^k}) to x using FWHT.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    x : numpy.ndarray\n",
    "        One-dimensional array of length ``2**(2*k)`` representing a state\n",
    "        on $2k$ qubits.\n",
    "    k : int\n",
    "        Number of qubits on which H^{⊗k} acts (the first subsystem).\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    numpy.ndarray\n",
    "        One-dimensional array representing (H^{⊗k} ⊗ I_{2^k}) x.\n",
    "    \"\"\"\n",
    "    x = x.reshape(2**k, 2**k)  # shape (2^k, 2^k)\n",
    "\n",
    "    # Apply FWHT along axis 0\n",
    "    for col in range(2**k):\n",
    "        fwht(x[:, col])\n",
    "\n",
    "    return x.reshape(2**(2*k))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "64c1c76c-65ba-43d0-8d34-4fadec922ed5",
   "metadata": {},
   "outputs": [],
   "source": [
    "def apply_index_permutation(x, perm):\n",
    "    \"\"\"Apply a basis index permutation to a state vector.\n",
    "\n",
    "    The permutation ``perm`` is regarded as the action on computational\n",
    "    basis indices, and the function returns the permuted state\n",
    "    y = P x, where P|i⟩ = |perm[i]⟩.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    x : numpy.ndarray\n",
    "        One-dimensional array of length ``N``, representing a vector in an\n",
    "        ``N``-dimensional vector space.\n",
    "    perm : array_like of int\n",
    "        One-dimensional array-like of length ``N`` representing a permutation\n",
    "        of ``range(N)``.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    numpy.ndarray\n",
    "        One-dimensional array ``y`` of the same shape and dtype as ``x``,\n",
    "        satisfying ``y[perm[i]] = x[i]`` for all ``i``.\n",
    "    \"\"\"\n",
    "    perm = np.asarray(perm)\n",
    "    y = np.empty_like(x)\n",
    "    y[perm] = x  # y[perm[i]] = x[i]\n",
    "    return y"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "a03f0651-9359-4d28-98db-6f1d993b735e",
   "metadata": {},
   "outputs": [],
   "source": [
    "perm_rep_Uf = [0] * 2**(2*k)\n",
    "ind_list = it.product(range(2), repeat=2*k)\n",
    "for in_x in ind_list:\n",
    "    out_x = in_x[0:k] + termwise_XOR(f_from_s(in_x[0:k]), in_x[k:2*k])\n",
    "    perm_rep_Uf[ind_from_seq(in_x)] = ind_from_seq(out_x)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "d363d493-b4f3-4a0b-a6c1-34f6a07a8fcb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def comp_basis_big(x):\n",
    "    \"\"\"Return 2-d column vector representing the x-th basis.\"\"\"\n",
    "    res = np.array([0] * 2**len(x), dtype='float64')\n",
    "    res[ind_from_seq(x)] = 1\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "e0ef2eb7-48cb-4ce3-bf91-764a56830bea",
   "metadata": {},
   "outputs": [],
   "source": [
    "start_vec = comp_basis_big([0] * (2 * k))\n",
    "first_H_eff_vec = H_pow_ampl_fwht(start_vec, k)\n",
    "permed_vec = apply_index_permutation(first_H_eff_vec, perm_rep_Uf)\n",
    "second_H_eff_vec = H_pow_ampl_fwht(permed_vec, k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "4a16eacf-bcb5-4c59-849b-05520d922a8d",
   "metadata": {},
   "outputs": [],
   "source": [
    "# adaptation of helper commands\n",
    "def pair_first_half(v, w):\n",
    "    \"\"\"Contract a 2k-qubit state v with a k-qubit state w over the first k\n",
    "    qubits. (Components are assumed to be real.)\n",
    "\n",
    "    The input state ``v`` is interpreted as a bipartite state on\n",
    "    H_A ⊗ H_B, where each subsystem has k qubits. The function\n",
    "    contracts against a state ``w`` on H_A to produce a state on H_B.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    v : numpy.ndarray\n",
    "        One-dimensional array of length ``2**(2*k)`` representing a state\n",
    "        on $2k$ qubits.\n",
    "    w : numpy.ndarray\n",
    "        One-dimensional array of length ``2**k`` representing a state on\n",
    "        the first $k$ qubits.\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    numpy.ndarray\n",
    "        One-dimensional array of length ``2**k`` representing the resulting\n",
    "        state on the last ``k`` qubits.\n",
    "    \"\"\"\n",
    "    # Reshape v as a bipartite state: (first k qubits, last k qubits)\n",
    "    v_reshaped = v.reshape(2**k, 2**k)\n",
    "\n",
    "    # Contract over the first subsystem: u = (<w| ⊗ I) |v>\n",
    "    # This is u = w.conj().T @ v_reshaped\n",
    "    return w.T @ v_reshaped\n",
    "\n",
    "\n",
    "def meas_prob(x, v):\n",
    "    \"\"\"Return the probability of observing x in the first half of v.\"\"\"\n",
    "    w = comp_basis_big(x)\n",
    "    u = pair_first_half(v, w)\n",
    "    return np.dot(u, u)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "id": "fbeba937-ef55-41df-9ab4-0039211e50ba",
   "metadata": {},
   "outputs": [],
   "source": [
    "intervals = {}\n",
    "t = 0.0\n",
    "for x in it.product(range(2), repeat=k):\n",
    "    prob_for_x = meas_prob(x, second_H_eff_vec)\n",
    "    intervals[x] = (t, t + prob_for_x)\n",
    "    t += prob_for_x"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99a5db36-01f3-419e-94f9-f084b56e8fdd",
   "metadata": {},
   "source": [
    "perform the measurement to get strings $x^1, x^2, \\dots$, then solve the linear equations to get the secret string"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "id": "1be2ad4d-2d6d-44f1-9b3e-f423f4acbd9a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{s_10: -s_2 - s_3 - s_5 - s_6 - s_7}\n",
      "{s_1: -s_6 - s_7 - s_9, s_10: -s_2 - s_3 - s_5 - s_6 - s_7}\n",
      "{s_1: -s_6 - s_7 - s_9, s_10: -s_5 - s_8 + s_9, s_2: -s_3 - s_6 - s_7 + s_8 - s_9}\n",
      "{s_1: -s_6 - s_7 - s_9, s_10: -s_5 - s_8 + s_9, s_2: -s_3 - s_6 - s_7 + s_8 - s_9, s_4: -s_5 - 2*s_8 + s_9}\n",
      "{s_1: -s_8, s_10: -s_5 - s_8 + s_9, s_2: -s_3, s_4: -s_5 - 2*s_8 + s_9, s_6: -s_7 + s_8 - s_9}\n",
      "{s_1: -s_8, s_10: -s_7 + 3*s_8 - 2*s_9, s_2: -s_3, s_4: -s_7 + 2*s_8 - 2*s_9, s_5: s_7 - 4*s_8 + 3*s_9, s_6: -s_7 + s_8 - s_9}\n",
      "{s_1: -s_8, s_10: -s_7 + 3*s_8, s_2: -s_3, s_4: -s_7 + 2*s_8, s_5: s_7 - 4*s_8, s_6: -s_7 + s_8, s_9: 0}\n",
      "{s_1: -s_8, s_10: -s_7 + 3*s_8, s_2: -s_3, s_4: -s_7 + 2*s_8, s_5: s_7 - 4*s_8, s_6: -s_7 + s_8, s_9: 0}\n",
      "{s_1: -s_8, s_10: -s_7 + 3*s_8, s_2: s_7 - 3*s_8, s_3: -s_7 + 3*s_8, s_4: -s_7 + 2*s_8, s_5: s_7 - 4*s_8, s_6: -s_7 + s_8, s_9: 0}\n",
      "{s_1: -s_8, s_10: 2*s_8, s_2: -2*s_8, s_3: 2*s_8, s_4: s_8, s_5: -3*s_8, s_6: 0, s_7: s_8, s_9: 0}\n",
      "got a unique solution after 10 trials\n"
     ]
    }
   ],
   "source": [
    "import sympy as sy\n",
    "# prepare variables s_1, ..., s_k\n",
    "s_var = [sy.Symbol(f\"s_{j+1}\") for j in range(k)]\n",
    "\n",
    "x_samples = []\n",
    "l_poly = []\n",
    "for trial_cnt in range(2 * k):\n",
    "    rand_num = rng.random()\n",
    "    for x in it.product(range(2), repeat=k):\n",
    "        if intervals[x][0] < rand_num and rand_num < intervals[x][1]:\n",
    "            new_x = x\n",
    "            break\n",
    "    # ignore x^i = (0,..,0), otherwise set linear polynomial with\n",
    "    # coefficients x^i_j\n",
    "    if new_x == (0,) * k:\n",
    "        continue\n",
    "    else:\n",
    "        f2 = sy.FF(2)\n",
    "        i_th_lin_poly = sy.Poly(sum(f2(new_x[j]) * s_var[j] for j in range(k)))\n",
    "        l_poly.append(i_th_lin_poly)\n",
    "    # find solutions\n",
    "    ans_set = sy.solve(l_poly)\n",
    "    print(ans_set)\n",
    "    if len(ans_set) == k - 1:\n",
    "        print(f\"got a unique solution after {trial_cnt + 1} trials\")\n",
    "        break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "id": "8d82573a-6864-4e36-81b4-9d691b6ecafb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAQAAAAAVCAYAAACpHjD6AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADhUlEQVR4Ae1c0XHUMBB1GArIQAehA0I6CB1wdEDogN+7PyZ0AFTAQAekAybXAXRAuBJ4z/GSyyHPyF7p2fHsziiSZXuf9q12bevsHK3X63dN07xFoaw2m832thl/g4FgYGkMIL7vxfsREsAljPyOHVdLMzbsCQaCgTQDiPc27h+nd9/14sDn2PqKcor27m5PuVY3GCr8jfIM5RJ9v9hRWsRY1bkzfmBXdSwld7RLYZMYh0FHqTrPh/gpmQCg4BiD/Ixyg/IC5QSligDrGorfo/5GgA77GvVLlKJJAPqqY3XjV3G3OD+p+FPhWNAo5l4XP4Pm+CMb4H6Nwe5QuB7AtYEv+/tKtqH/AvqOUbfBT91o8y6D2x+5XUpUWBw/SnXuyIsKCzhKP0n4U3HX+UnC3xg/JRNAqaDL0LPCMalFxx/oP4dBvMKVEiVWqTHPRU9w5/OEir/BOFMngHPwyseMQ7Fbf+4vJUqsUmOei57gzucJFX+DcSZLAJlX9yc+3m/PVmKVGO+cdAR3Pm+o+BuLM1kCAK0W3Hzm75NSjwBKrD5bHmp/cOfznIq/UThTJoAcWp/mHFToGCVWoSHPRk1w53OFir//cKZMAKlnf6PRshl/Ly0hSqwS452TjuDO5w0Vf6NwJksAeGaxW//Ubb712WKgywVKLNdAZ3hycOdzioq/sTiTJYCOVr5+fJKg2O4ASr6erMRKmPSgu4I7n/tU/A3GcScAZB67Wo+hiK8Y803DQzlFx3YvqzVOHOpXYh3ak9wuYFNSb6rTiTU77mij06YUTcm+Ajgq/rJxzNCcBGALB3ZVtnPNAX9AEF8/HCw47xNOukH9yk5GmwnlNcqbg77RONSjxLJxo67G3R6GNathTcQd7apmk5HW1VVxVPzl4uzb3vs1IJQxm1D4cgGDcovCZ3J+OcjAbQXtn11z1MdCOJ+6+ZHEDoWLfmco/DaAeP/Ei0NFKizgSLjrbJJgqbgT2yThTjz3cuOp/RqwNwFw0LmCycEr+BVqBnE1UeHQABWWCids8k3LpfkJ9rQJIOcRIIe5MyisGvzdIFQ4hFNhqXDCpm4SjayW6KfGnQAQ+LzlKPV7fa9vVDgcgApLhRM29U6rrB1L9JMZ7k4AUHQBgj6Ywoq1CocmqLBUOGGTb2Iu0U8tI7YGYKvwKwTzvcU3H29xdjAQDMyJAcS3/U9A/qq3+gvJ7ZjgsXMw6AAAAABJRU5ErkJggg==",
      "text/latex": [
       "$\\displaystyle \\left[ 1, \\  0, \\  0, \\  1, \\  1, \\  0, \\  1, \\  1, \\  0, \\  0\\right]$"
      ],
      "text/plain": [
       "[1, 0, 0, 1, 1, 0, 1, 1, 0, 0]"
      ]
     },
     "execution_count": 72,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# find free variables\n",
    "free_vars = set()\n",
    "for key in ans_set.keys():\n",
    "    free_vars |= ans_set[key].free_symbols\n",
    "# get the nonzero solution as a string\n",
    "if len(free_vars) == 0:\n",
    "    subs_dict = {}\n",
    "else:\n",
    "    subs_dict = {list(free_vars)[0]: 1}\n",
    "\n",
    "ans_seq = []\n",
    "for the_var in s_var:\n",
    "    if the_var in ans_set.keys():\n",
    "        ans_seq.append(ans_set[the_var].subs(subs_dict) % 2)\n",
    "    else:\n",
    "        ans_seq.append(1)\n",
    "\n",
    "ans_seq"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a5cea2f5-8aed-4ada-b3f2-47105281b3f0",
   "metadata": {},
   "source": [
    "How about the classical algorithm?"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "id": "e98f0b1f-e089-4938-b652-4c0cdfda7458",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAACQAAAAOCAYAAABHN77IAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACNUlEQVQ4EZ2V7U0DMQyGr4gBStkANigwAWUDWIFuQMW//kOwAawAG9ANgG5AmYCPDcrzhDjqhWuRsBS99hvHduLcpbdcLhtlOp3uAeNkNE0f1L6Gn2UuQfabZO4Q/GBM4OeZ+xPwvc5O7+A+wzwLuZ4FYViAZBQkdwp3zzhDfwDlLPIWPNFW0A1+wThBbxXvfC34vMBdgRHT3HKuX2zlBefgOYRFhETwyyBAk5ei5VnjaX0xLH6j4GuePpiK0Rndtdq32lGQx+2EI0l2DDNwhPLKXD+IjBZvIk9wk5wx2dXaJ/iRcbddjWLAHfUQuDitVHnm9RsyVwoP/4x1odV044buahI73R9wlAqqHUjowtQe9BIA3R12yVCS+a7dJ3/m/ipWv0GrIBYZ2GKOGAZ/ZmyUvMZWxZe3zn+QJ9adrtM/LYsIeYdpl+i27AUsX1n4VehlfsDvpuL/Y+7Gpf61mATefHdzj9553PDerwW4rpWrcT9WjUqP03tPBRHQi5ruQeUYLbONLcHfT3gAln9Sy6Ey8ItWdW0uuPIf8sdke2KiCtc28bOd+2A5GfQ9R9vzl+VX2uUTJzSLllm990BclcNsGCgJPp7kEVhfYovc1BbXe98ipnbIAcrc/PF0ePwNxOonbgIDjIMH3d0joxSIHuKPzXepAT3pT4ZJTFYE+xXDt8872oD6vjGO0eepoDzhPSktQDd563FlgcG6jhy6nTz7zsD6qbEA/3F2w8fVX4xvW/q6vwF+KOWE9iRiKQAAAABJRU5ErkJggg==",
      "text/latex": [
       "$\\displaystyle 32.0$"
      ],
      "text/plain": [
       "32.0"
      ]
     },
     "execution_count": 73,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# query count estimate\n",
    "np.sqrt(2**k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "id": "3a8f67c2-32a7-4964-8085-8c091d6f9ca4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "found secret [1, 1, 1, 0, 0, 1, 0, 1, 0, 1] after 25 queries\n"
     ]
    }
   ],
   "source": [
    "res_rec = {}  # dictionary of f(x) as key and x as value\n",
    "ind_list = it.product(range(2), repeat=k)\n",
    "for _ in range(2**k):\n",
    "    x = tuple([rng.integers(2) for _ in range(k)])\n",
    "    if x in res_rec.values():\n",
    "        continue\n",
    "    f_x = f_from_s(x)\n",
    "    if f_x in res_rec.keys():\n",
    "        prev_x = res_rec[f_x]\n",
    "        found_secret = [x[i] ^ prev_x[i] for i in range(k)]\n",
    "        print(f\"found secret {found_secret} after {1 + len(res_rec)} queries\")\n",
    "        break\n",
    "    # if f(x) is not found, record the results and continue with next x\n",
    "    res_rec[f_x] = x"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a25b1f6-0930-4215-9496-84e8e3c424ed",
   "metadata": {},
   "source": [
    "## Lecture 15"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "473578af-c7f0-477b-b8e5-a78e2b40ba48",
   "metadata": {},
   "source": [
    "### Extended Euclidean division algorithm"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "13a3803f-6b25-4a39-bf49-e949c6acd740",
   "metadata": {},
   "source": [
    "find $x$ and $y$ satisfying\n",
    "$$ a x + b y = \\operatorname{gcd}(a, b) $$"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9f64fab9-9f2b-45ed-a2bb-17cec25fe1bc",
   "metadata": {},
   "source": [
    "construct three sequences $r_i$, $s_i$, and $t_i$ in the following way:\n",
    "- $i = 0$: $r_0 = a$, $s_0 = 1$, $t_0 = 0$;\n",
    "- $i = 1$: $r_1 = b$, $s_1 = 0$, $t_1 = 1$;\n",
    "- the $(i+1)$-th step:\n",
    "    + find $q_i$ such that $r_{i + 1} = r_{i - 1} - q_i r_i$ holds for $0 \\le r_{i + 1} < r_i$;\n",
    "    + if $r_{i + 1} = 0$, then terminate the algorithm with output $x = s_i$, $y = t_i$, and $\\gcd(a, b) = r_i$;\n",
    "    + otherwise set $s_{i + 1} = s_{i-1} - q_i s_i$ and $t_{i + 1} = t_{i -1} - q_i t_i$ and proceed to the next step.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f75f3c16-284e-4ae6-ba01-02f06182a31f",
   "metadata": {},
   "source": [
    "The algorithm is set up to keep the condition\n",
    "$$ a s_i + b t_i = r_i \\quad (i = 0, 1, \\ldots) $$\n",
    "while making $r_i$ smaller at each step.\n",
    "At each step, $r_{i+1}$ is the remainder of $r_{i-1}$ divided by $r_i$, and $q_i$ is the quotient\n",
    "We get the correct answer because $\\gcd(a, b)$ is the smallest positive integer solution $k$ for $a x + b y = k$ with (possively negative) integers $x$ and $y$.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "id": "05e8b864-74b7-42f4-9bbd-a9e76f9a92eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "def ext_Euclid_div(a, b, report=False):\n",
    "    \"\"\"Returns (gcd(a,b), x, y) such that a * x + b * y = gcd(a, b).\"\"\"\n",
    "    # i = 0 and i = 1\n",
    "    r = [a, b]\n",
    "    s = [1, 0]\n",
    "    t = [0, 1]\n",
    "    for i in range(2, max(a, b)**2):\n",
    "        q_i, rem = divmod(r[-2], r[-1])\n",
    "        if rem == 0:\n",
    "            if report:\n",
    "                print(f\"found {r[-1]} = gcd({a}, {b}) after {i} steps\")\n",
    "            return (r[-1], s[-1], t[-1])\n",
    "        # add next terms, then continue\n",
    "        r.append(rem)\n",
    "        s.append(s[-2] - q_i * s[-1])\n",
    "        t.append(t[-2] - q_i * t[-1])\n",
    "        if report:\n",
    "            print(f\"we now have r: {r[-1]}, s: {s[-1]}, t: {t[-1]}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "id": "b0797ff5-4fc2-4dd5-a3cd-591697e247d7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "we now have r: 16762, s: 1, t: -1\n",
      "we now have r: 7378, s: -1, t: 2\n",
      "we now have r: 2006, s: 3, t: -5\n",
      "we now have r: 1360, s: -10, t: 17\n",
      "we now have r: 646, s: 13, t: -22\n",
      "we now have r: 68, s: -36, t: 61\n",
      "we now have r: 34, s: 337, t: -571\n",
      "found 34 = gcd(40902, 24140) after 9 steps\n"
     ]
    }
   ],
   "source": [
    "a, b = 40902, 24140  # from Knuth's book\n",
    "gcd, x, y = ext_Euclid_div(a, b, report=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "id": "9c8cd499-0a58-4bc1-b500-6b103b10ff2d",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(34, True)"
      ]
     },
     "execution_count": 77,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "a * x + b * y, gcd == a * x + b * y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "55958580-f726-422f-ab21-da280dd647d1",
   "metadata": {},
   "source": [
    "### RSA cryptsystem"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3a835b7-08e9-4885-b699-a421feb3997b",
   "metadata": {},
   "source": [
    "Preparation: first Alice chooses (big) prime numbers $p$ and $q$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 78,
   "id": "088107ea-c4f9-4cb5-9161-1517474627b2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# helper function to compute exponentioal modulo k for big exponent\n",
    "from functools import reduce\n",
    "\n",
    "\n",
    "def pow_mod(base, expo, k):\n",
    "    \"\"\"Returns base**expo mod k, allowing numpy integer inputs.\"\"\"\n",
    "    if expo < 0:\n",
    "        return None\n",
    "    return reduce(lambda s, t: (s * t) % k, [base] * expo)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6cd04338-d40c-457a-a4f4-a21b5efb9926",
   "metadata": {},
   "source": [
    "We take lange integers at random and check if it is prime or not by [the Miller-Rabin primality test](https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test). The code is taken from [Rosetta Code](https://rosettacode.org/wiki/Miller-Rabin_primality_test#Python:_Probably_correct_answers) and lightly modified."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "id": "ccf8d5e2-1d0f-44e8-ab15-a9fd4707d39b",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "rng = np.random.default_rng()\n",
    "\n",
    "\n",
    "def is_prime(n: np.int64):\n",
    "    \"\"\"\n",
    "    Miller-Rabin primality test.\n",
    "\n",
    "    A return value of False means n is certainly not prime. A return value of\n",
    "    True means n is very likely a prime.\n",
    "    \"\"\"\n",
    "    # special cases for small n\n",
    "    if n in [2, 3, 5, 7]:\n",
    "        return True\n",
    "    elif n < 10:\n",
    "        return False\n",
    "\n",
    "    s = 0\n",
    "    d = n-1\n",
    "    while d % 2 == 0:\n",
    "        d >>= 1\n",
    "        s += 1\n",
    "    assert (2**s * d == n-1)\n",
    "\n",
    "    def trial_composite(a):\n",
    "        if pow_mod(a, d, n) == 1:\n",
    "            return False\n",
    "        for i in range(s):\n",
    "            if pow_mod(a, 2**i * d, n) == n-1:\n",
    "                return False\n",
    "        return True\n",
    "\n",
    "    for i in range(8):  # number of trials\n",
    "        a = rng.integers(2, n)\n",
    "        if trial_composite(a):\n",
    "            return False\n",
    "\n",
    "    return True\n",
    "\n",
    "\n",
    "def getLargePrime(keysize=1024, report=False):\n",
    "    \"\"\"Generate a random prime number with specified keysize\"\"\"\n",
    "    for try_count in range(keysize ** 2):\n",
    "        num = rng.integers(2**(keysize-1), 2**(keysize))\n",
    "        if is_prime(num):\n",
    "            if report:\n",
    "                print(f\"found a prime number {num} after {try_count} trials.\")\n",
    "            return num"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 80,
   "id": "9bfbdee7-67d4-46e0-b3a2-1d850cc47181",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "found a prime number 839 after 10 trials.\n",
      "found a prime number 733 after 1 trials.\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "(839, 733)"
      ]
     },
     "execution_count": 80,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p = getLargePrime(10, report=True)\n",
    "q = getLargePrime(10, report=True)\n",
    "p, q"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d36d0ce4-0fb2-4f62-a869-eb705e50eba6",
   "metadata": {},
   "source": [
    "she chooses another integer $e$ that is coprime to the least common multiple $\\operatorname{lcm}(p-1,q-1)$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 81,
   "id": "671e354a-655b-46b6-804a-02100f8b599a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAAAVCAYAAACwnEswAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFf0lEQVRoBe2Z61FUSRSAr5QBsGsGmAFqBCsZIEYAZKDlL/hnaQZqBD4yUCNYJQPJACQD9vt6+vT09PQdBhxhq+RU9fTpc8+j+7z6XhjOz8+HehwcHGzU61t81j+r9EfP12tDBYeHh89YblakG0HZxwZjj7F+IxtYgVH2PudHz8PYqNR7Tn1e4I4RF3iwzfSI+Xki5B/WKtjPSx3k+hX0L5lWJmiv8uKE+T5DvuPCUCEVb1DfQztywexePsaDznwGz181nfWFtuFx/y8qOdcfoc+dpeK5EorOnwiqP50p4+p6wLMzEQHcgHiet65TQFgo+JX5gcSATNepEZABPJz1BPxTxfsd/GXQmNUpbQu8BAXcgOrs5+DJEcyuN5kN4sCsc82wIic9w2NmZa9i+w1y5SzqY61tk6Hok/6rgL4f6PiboR88h/r1TwkG6wTQ9NM/PrubaTrgTcbraY+FreMzIzYc2WSmJRrP5LMcg2cAN+qu1bvFCAgHhB7psengUb6WSXRoqdyz3qAtZRsZ+Tx4C7sQvjLK3luGK66PsPlkSVl9ZAz217LADsKpZBoFlpsRLVGFr+AVr4ajNCvy8C+Lx8jo8IHZ6jLzZ2xBt4rqACjXg5lqzQxL2YbX6qtt9PTfCI2z6w9jsH6XH53Uaw0Dz8zitlfLL9QVZRuZcXLimOr1uRlou7ByekHNIilwc9mKjBn0sjBNkWVtG+Rn6PnMbLuNPYx1h6mF68GMwY4ty6yp28eoeQ7h4VNpgacAMKfsHxWaPLCXCg8Zx8hYJU8ZJwwzd+HFCr+tyjumfeFY2jaynxzoMaF+Zl3artsxy9UBNlI7ReM9hmfwDul1Eo0agy1blk7yAhoFlOgM3wbMcBV+q5jD2ZFx1aOChuNifog+L+bXDHUakKi8IlQhJoGjhcvYHrBhe4tKVp8JNuag1tZl1571AzY9o4nk+A6uzR4Ygw0DouBpjyNoKPGCUrEHes9Q8SIHhmjM9+CPYBjccEo8/wDyruIJ+gDNzPIeWqqKi+AUMTsToMM9nzGsDPWp+0emg64O0Om9qK0E4LYkbdatfvJw8msMUkDMsiJYc/RwFFv28pvV68yLghkZbGsK6N1Xvv2oy2ptwQrqyci3tG32avvQSVambdNWbYIJ3WSYPFrpr+fwY9BEaMFn62sttV4jaDZv1rSMR8sycyOYOrSFoOmA4Iu55XXd26hZ3XV8pTPs1DqDFsG0RbV3kMlltcg71kp4dDlgX95LvVfsUBR7i7VzKgwD4mF7DDKp1PY09lweIcp/spr+RoVEu3FepCuclzRkuwbpbKpyDrvQdtbjd9KcHmjaNDCx1zkDVyBY6b1zJhvYPOrolP/UgLihXmYq4wF8O2kPEq0lHO3HXtCUC/DL3/sn5O2fPVvy+Toc+kI+dHYrJDNdaDvbV3/Ptmp0RrENn+tfgbfosPJasAqLneahwTo2IEbrUfMwlpa47+0FMLTNwg3vgydHM3tJn+ZniRdcnh3GbiLwA81M/MJc3ph6fMHPHI5Jdip6QZFfyjYC3hdx79Xy3i22mFSdeT++Fi9qOUV+BPFPNDOXN+v4I2LcW61oSt47/Al4kydutBfRAbpRrZWYZX4xz0Satc7T0TrvhGGQu+/d8MoXzjYzxvi0pWN2kTGYXVjWNnye9QWjrjjP3p4lPgNm/hDYNT5CRKd7jzvLM2rTF4puckHX5n78cdGFX69Wyy3gAXxhJ7Cauw5cpZOwYfCs0vtrWbEZ6+vlLUw94L8ifnswsjkrKbXxFBAM24fH3o+nW/xDMPxhO7Xt/nbAltWh79PHclSIhr0nZi4iiX8o7OGg19d0dn1eulP5j6HGc7S2r3Ez13Tm/6cZ/Oybl58V5fvrP+zOgiLe6ePrAAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle \\left( 306708, \\  5\\right)$"
      ],
      "text/plain": [
       "(306708, 5)"
      ]
     },
     "execution_count": 81,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gcd, _, _ = ext_Euclid_div(p-1, q-1)\n",
    "c = int((p - 1) * (q - 1) / gcd)  # lcm\n",
    "e_cands = [3, 5, 7, 11, 13, 17, 19]\n",
    "for e_cand in e_cands:\n",
    "    gcd_e_c, _, _ = ext_Euclid_div(e_cand, c)\n",
    "    if gcd_e_c == 1:\n",
    "        e = e_cand\n",
    "        break\n",
    "    e = None\n",
    "c, e"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ce84955d-f946-4d3f-a682-d00a421226b3",
   "metadata": {},
   "source": [
    "she finds $d$ satisfying $e d \\equiv 1 \\bmod \\operatorname{lcm}(p-1,q-1)$ (this is the *private key*)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 82,
   "id": "c7b9d36f-a6d0-4297-b922-2c986396307d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAGQAAAAVCAYAAACwnEswAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE8klEQVRoBe2Z61VUMRCA1z0WsGoH0AFqB9ABYAVKB3r8Bf882gHQgdCBWoFHOpAOhO0Avy/eibn3Zh8sLIuPOSebZDKZTOaV3Ozg6upqUJb9/f21sv+/3dbPbeqjpuvhoICDg4PXdDcK1F/VZH+9vYEbUdZWtNG1Rud5+QdaXGBgm+o59ZuE6PyAdzMnlKe0x53hAbgRuLcF3v4J+M8FrtdkXL571HvdQXDvG9wP6nXKe3DnFToVGnI/o31hH9qzkpb+JX3lCrxtobqnX0M3+2XNWXozCMbQHbnSQ3/oJGVSP7Uf0OCP6btBNzrNk1RWS6n0NYgeeBo8K7VGDgXlYeZ8o/Mu5sqH/jfqLUo2Cm1lOhQfk2lryKAtHcJ9CCpJHsrlGmPqWwP4KetceoP2A0VZP1LGySBMdgOHXYkkALcjnvbEdMbYK0hUYBdegvhCqRqk4dmd41ryaxkSnF4kH+XMyqet7F1HMDrkobEfUQLOwKf9BGIZtbLCd6beirXdU9rHsEHuwiSFTEF0nabppFTSzLmsp5cquKULbqYXNeC+UjaZOyombNL+3sE5bGRo1GlRLd3KARnVvTYYDfnx7MgpYEHpVNQ2vD7JtOBRjbxm/AW0k5xAJUd6KdhlOR0PUPHn8KoZVppSnphzH2ttsGvK0rPLPHttYVHGqYWJGveStgesUaOBeukKnOmvlyLBDRibR4GPpRWgn5SCjEDHW5FGP6VDhp5QjB7PkBYNuFWANtgyZXlYf7+pBGxKxYTHGxl6cW+j0KkEz4NJURnKnuTxTJ/u9fDWGK4TNy/nCBrbw9OD1DGLB2oZcaBWAtpgTYMoZC09XEsqNmV0qEQjQ2urEHO7+BK84obhSvx12nr3NPAwN2o/lET0vaFlQ9PWKZS1Gq3l3Dtoa4NkED0yC7nIwmzMNOBmvd2Yz02DkUqO6Wv0AbV0szY/zTkievwuqQJryF8ZYv0qXYHUKH6g6UCrBOUYGSG3AaaoVnpgg54dRovG2Gw27K1nUqqCLBktnCMZMSF//wSuygPeGvwxde/GB87zrHY1D+7BO/p3XafA8FDXIxcWhk06V0WPuzsAp6dqGBfTA30JMJ2UsEFHDxUvvYaNlFfS2Y4IcbwFzDM1rlPnyKCdvJ5aA3pW1qIv8YTmrMXw7jvq8UKDKGwSfBEZ2IgHtEWl1jzXhT43YzVF+pzheFYkfY1j1HXBlwQ/7lrGp69Ra88+GinOqyPoWlHcMPdAb8kFXdXBGvplVTrG+ZAfPeP5HKvEQRpeWk5RmemZpESyMVOIqaJmqCDVYJYM0KvEC+p8IaAtzS7lZSakAV5n0oAq0SeUXMB5gQjjJTy4DIx5/RayM4BzHa/u09JbmjTnzzS9lSySsz3gCVjvUpnm+x6AjxSjJymsBlTBKjq8b0BbPm8pZVqY+LgIvYevypSvYGr7Cj7djKhdyyhRoR7iOk3vmwG6dF1krAZGkxtNQNv1Ikp0LGX1IuIaGRqe9hd+dITHXHqLRZs199Jrb9PZoVbZ/wENoAuj01TaMtYylMMaOooOvj5sFtATWw90y1j4D+PpmbR0YzQ6MWrTmZkMwsKmnvtwF78XNkMfIwSZ+K1zm0KyltGh7lP6jwhxDQ+2WR9t0v0L8AoFtb7yl7hpdZ6zU/7H0AUba/lqe1fCLHGf9581evaW5xNPvoX+BOJoLxpTKSbfAAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle \\left( 184025, \\  1\\right)$"
      ],
      "text/plain": [
       "(184025, 1)"
      ]
     },
     "execution_count": 82,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "_, d, _ = ext_Euclid_div(e, c)\n",
    "if d < 0:\n",
    "    d = d % c\n",
    "d, (e * d) % c"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e3def09a-130f-448f-887a-1dfb56e56b86",
   "metadata": {},
   "source": [
    "she advertises $N = p q$ and $e$ as the *public key*."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 83,
   "id": "bed0aeda-fdcd-4e78-b129-f67edea40c4a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Public key: N=614987, e=5\n"
     ]
    }
   ],
   "source": [
    "N = p * q\n",
    "print(f\"Public key: N={N}, e={e}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d92db874-16ae-4809-8393-e68e59969bee",
   "metadata": {},
   "source": [
    "Now, suppose that Bob wants to send a message represented by an integer $x$ to Alice.\n",
    "He first checks that $x$ does not share prime factors with $N$ (if it does have one of them as a prime factor, he asks for a new public key)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 84,
   "id": "bba29203-ed79-4ce1-ad56-3b1d03e67145",
   "metadata": {},
   "outputs": [],
   "source": [
    "x = 42"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 85,
   "id": "506700ba-e790-4778-ba0c-57c93369a839",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 85,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gcd, _, _ = ext_Euclid_div(x, N)\n",
    "gcd == 1  # should be true"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d6266eae-8390-434a-8093-65dc222695af",
   "metadata": {},
   "source": [
    "he computes $y = x^e$, and sends it to Alice"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 86,
   "id": "83cf5e51-1654-4e39-9a43-dfdfd81157ba",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "313988"
      ]
     },
     "execution_count": 86,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "y = pow_mod(x, e, N)\n",
    "y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef091e0e-3096-4db7-8cd5-7233e62545dd",
   "metadata": {},
   "source": [
    "Alice recoves $x$ as $x \\equiv y^d \\bmod N$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "id": "be25c6dd-918f-42f5-9fcd-c1532fc48492",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "42"
      ]
     },
     "execution_count": 87,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pow_mod(y, d, N)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a0b1a532-1142-4653-8cb7-e74ac6101187",
   "metadata": {},
   "source": [
    "## Lecture 16"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 88,
   "id": "8eed13ad-2151-4076-adb6-e62979d26a71",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "rng = np.random.default_rng()\n",
    "import itertools as it\n",
    "from functools import reduce"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 89,
   "id": "226f92c7-ab82-4fd8-823b-802b9f1776f5",
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 15\n",
    "k = 4\n",
    "Q = 2**k  # this should be bigger than N^2 to allow s not dividing Q\n",
    "          # for N=15, we know s=2 (z=4,11,14) or s=4 (z=2,7,8,13) will divide Q,\n",
    "          # so Q > N is enough"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "id": "d5f4b356-966a-41c7-8eca-a46e809b8c33",
   "metadata": {},
   "outputs": [],
   "source": [
    "zeroket = np.array([1, 0])\n",
    "oneket = np.array([0, 1])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "id": "7a5d5e32-983f-4164-9750-25cc74eb03f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "def bin_seq_for_int(x: int):\n",
    "    \"\"\"Returns binary sequence representation of the integer x.\n",
    "\n",
    "    Parameters\n",
    "    ==========\n",
    "        x: int\n",
    "\n",
    "    Returns\n",
    "    =======\n",
    "        tuple of int\n",
    "          (x0, x1, ..., x(k-1)) with xi = 0, 1 and\n",
    "          x = x0 + 2 * x1 + ... + 2**(k-1) * x(k-1)\n",
    "    \"\"\"\n",
    "    # for i=0,..., k-1, do bit shift by i and bitwise AND with 1\n",
    "    return tuple((x >> i) & 1 for i in range(k))\n",
    "\n",
    "\n",
    "def int_from_bin_seq(x_seq):\n",
    "    \"\"\"Returns integer for the binary sequence of length k.\"\"\"\n",
    "    return sum(2**n * x_seq[n] for n in range(k))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 92,
   "id": "ae4bdc99-39a3-4d92-bcfa-031fbcb07069",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPQAAAAVCAYAAABxN8PZAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFM0lEQVR4Ae1c0XETMRB1UkGADkwHCakA0oGTDkg6gM/kjwkdABUwSQdABQzpIHSQ4A7Cexqt5yzrpLWk25zH1oyiO+1q3+4+SXdn+zJ5enqadOvl5eW0ex4e5+Sh/u58Ob8l+RhDzsfgQ0nuNnWMJt8xnf1Jp1xdXX3A6WGna+kwJ19S3p20zMDU576lTbWtHe/qVLVU1HC+orPHHYwFpM3QHKP96DqCPwo5N4Ib1CPozoPhzU5h+9obe0D7GvUafX+bAXhDVjiE02BBh5vtHO1X76JJA7zcvLDi3QpnNPNLw3mo4xY0Og8wO36hPYrNkj657/+GMY+ob1CZ9BfoH2RBw+4f2P+E9hbtBC39Zt8JjpstatgywfExqLG8X2/RDpJf+tMtwInOC98/OO9WOBIz8NRcyJiSdh0cr5vkvKsjt9zclb4knIvKYYhXjFPUC4z9nhhfLQLGOYwcoHWLmQZxzInN85TvVFUXKxw6VIDFOOUKoo6pQvFZeSe/qNs+vzScL3RkQZ8hcalbuZy8Ys6oh55C8y6i/Rt97+A/ryYtihUOfV0Ly3NELlrFmsvXGHjP+dhKvhYXFaBr4Wg47+rs44TPSL23qzl5RWDrDn2HAby1D4v4TnmLYoVDX0uwGO9Zi0BTNkbEe8rNlrISLkrwS3A0nDsdXqFPUH8mPMvJE0PbiDC5NFekl7VoVjj0swKLXJGTocuz8z50gGK/ggsxoWorcDScOx0uaH6YdZ/wKCdPDG0mksXKZ+a+oln0fWOl3wqHeKVY5GoqDg/YjoH3AcNbMl3KxZIRxUkpjoZzp8MFzYUQu5UV/3Jy0Xvu9pWRA1Y4DCeGRa4sFvSm8G5Ee5SLIbBLOXfzgguau8Y84VlOnhjaTJTacGTXe2iAZoVDV0ux+KzExTZ0GQPvQ8co9ku5kPHathRHw7nT4YIefcGzh2w4sYksfQyoqljh0MkKrG1aaFV8agdXcKGFcHoVOBrOnQ4XNHcNWRQxB3Py2Jgh+vjQH7vVZCAslLcoVjj0tQSLXKV2+hY5oI2x8N4qnpydEi5yNmPyEhwN506HC5pXtthCEWdyctFLttidUptGcqwX8mel/KAmLEfouOvsfpNKLCscxqHG6gTNDSx7N1KZA8KNhfdO6P2HDeJVc1GJpcbpRKvh3OlwQd+hHncGh4c5uejLw7xcMaV/4hPwDy1/WldUMJY/fHlEOxMD3i6/k30f9BVjWeHQXy2WxOZbt4EFfUunPi/FOfDGRsF7J7Ctml+duHmY5Vx09vAK1iFObjAJ+KLDSkF/Ts4dh4VfmPMqzInA3f2Hn7A4dJNXvhorfnnDT1T+HHGOyg/BuBHxt93EXBScV2FZ4dBhLZYE52O7QMtbt97i9SgvyjfGj4J3+LG180vI9VwmORcd9y40FvU9F3bfu6M5ed+4sB92ZqgHYf8Q51ZYVjjMEbCmqPfafNX6RizU3nlh5cfYcDwXJnMZ+c9y3tXhLTcLr3p8waKv5OR948J+vp7Jq6tFscKywmHO+GorudCWWt82jffaeLV5pZ4VlobzhY5b0FhkfD7ly9LRD8dyck0WYIO34y2+K87CWWFZ4TBgYJEbcpR6iWaRmxa+eazeebEASxy08CNhfiGywiGgFRZwspyHOm5B+6zwLZDUa4g5uTfT25wD/HOvtK3ACssKh9khN6m7qDCDrXzbFN5bxRvmMXZuhaXhfEln8R9L6LVf7bO+hZeTxyLf9dVnAHnnfyu5RZv9uqoebdXCjvfVnAzdo+E8pvMfXOd0GQxjhgMAAAAASUVORK5CYII=",
      "text/latex": [
       "$\\displaystyle \\left( \\left( 1, \\  0, \\  0, \\  0\\right), \\  \\left( 1, \\  1, \\  0, \\  0\\right)\\right)$"
      ],
      "text/plain": [
       "((1, 0, 0, 0), (1, 1, 0, 0))"
      ]
     },
     "execution_count": 92,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bin_seq_for_int(1), bin_seq_for_int(3)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1e319815-760c-4366-8459-e1f3122b2f30",
   "metadata": {},
   "source": [
    "### Fourier transform"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 93,
   "id": "52b8c399-3f38-4eb3-8aaf-fe09e4d3bc93",
   "metadata": {},
   "outputs": [],
   "source": [
    "def Fourier(n, j, l):\n",
    "    \"\"\"Returns the coefficient of Fourier transform matrix of size n.\"\"\"\n",
    "    return np.exp(2 * 1j * np.pi * j * l / n) / np.sqrt(n)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4f1d9d3b-03d0-4a58-ad0d-34bfb3443e24",
   "metadata": {},
   "source": [
    "Me need to take care of different conventions for indexing:\n",
    "On one hand, we say bit strings $x_0 x_1 \\cdots x_{k-1}$ represents the integer\n",
    "$$ x = x_0 + 2 x_1 + \\cdots 2^{k-1} x_{k-1}. $$\n",
    "On the other, the Kronecker product convention says that this string corresponds to the $j$-th row / column of the matrix, where\n",
    "$$ j = 2^{k-1} x_0 + 2^{k-2} x_1 + \\cdots + x_{k-1}. $$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 94,
   "id": "7a202794-c59c-47f1-8867-3a050825ed1e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAPQAAAAVCAYAAABxN8PZAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFaElEQVR4Ae2c8XXURhDGDRU4oQPTAYYKAh0YOgA6SP60/8sjHYRUkAcdABXwcAdOBxB34Hw/ZfdOp9PujnZXg/24fU9eaWd2vpn5NCvp7uSjm5ubo/F2fn5+Mj6e7pfkU/3D8W5+a/JxG3J+G3yoyd1dnWPJ95zO/aNRu7i4+FWHj0ZDO7sl+Y7y4aBnBk5C7nvaNNs68G5OVU9FC+d7OvdYwWgi7UzdE/W/DQOTPwY5C8E7bafSvZ5M73Yo22+Csa/qH2p7o7F/ugEEQ144wFmwpMNie63+bXDRpRNe8ryw+N3DSS8cfHXGytaMfClyPtUZClqDx4rlk/rTOQJS8jD+l+Z80/ZYGw7+pPFVClp2v8j+7+rfqz9Sj9+MPdN+t6KWLRecEIMZK/j1i/pV8os/4yac5HnhlSMvHOL2wBIGOTXXTPApy/lYJ95yc9X7k6ASbVYuQ1wxnmt7rXl/J+Z2GRbGKxk6Vj8UM0a1z4nNcc53VM3NCweHKrCIM96hmGNqUEzxfuCiMqmcs9qW1IyF841OLOgXAsndypXkleEtmvZc2pczMz5r7Kn8Z+Xr0bxw8HURVuAILnrFWspXivdFfpdAMnIvHFzwxMqEvCuycD7Wua8DnpGSt6sl+S78qkdPZZ1b+2mLviPv0bxw8LUGi3hf9Ag0Z6PAe43fObiUzAsHfE+sVLypcQvngw5X6GfaPqYsGeSZqX1EOrksV6SfW9G8cPCzAQuu4GztNnteNPi9yF8vHJzyxFqUhK2yhfNBh4Lmw6yr7dy9vZJ8b8IKA7FYeWZONUvRp+bGcS8c8Gqx4OokOrxin+K91u+lrnrh4Jcn1tI8oG/hfNChoCmEuVtZDNFK8v+1vv/fB04ueOEQzhwWXHkUdAvvc36vQY8XToqLNWKas2nhfNChoFmdrueshLGSPDO1myi34MTV9WsHNC8cXK3F4lmJYlu7pXiv9Xupv144+OWJtTQP6Fs4H3Qo6Fvf9IwTF5y5EzmOEVBT88LByQasVKE1xW6d3OC3FWLQ88IBzBNrURK2yhbOBx0KmtUpFsXWxHavJN9qrrvHQ//crSaB0JD3aF44+FqDBVe5K0qPHGAjx3uN3zV+eeHgmyfW0lxYOB90KGiubHOFEkFL8qiX7bUK5haN7Nwg5GelfFAzbacauBytsqy4LVheOMRhxhoFzQJWvBtpzAFwOd7Nfjf64YVDvJ5Y4C1pFs4HHQr6UtuTjPWSPE6NH1DEK2YcjwX2r8jlZ45VTXP54cs39WfRgPYpXL6TfTkZq8bywsFfK1aMLfTDAjYZ2zkMeanOQTCW5N3qd6sfXjjE64k1IitZMyMddoucR517egXrkQ7eKSBedNhrGi/JWdlofDFPgXEisLp/CEnS7pCw+NVY9csbsod9fo54rY0PwViI+G03mJum4yYsLxwctmLF4EJsr9Vzi5hsQQ95Vb41v8T7D8tFh9yaaiaSG/CynEed4V1oFfUVhZ16d7QkT82bjsvOmbbj6fgax15YXjjkSFgn2q6s+Wr1DSxtyfPCy4/bhhO4cDmXlf8i52MdbrlpXPV4wSLVSvLUvOk4r2dydfVoXlheOOSMV1vhwtpafbtrvLfGa80rel5YFs43OkNBq8h4PuVl6dkPx0pySxZkg1u0Ht8VF+G8sLxwCFhYcANHuZdoNrnp4VvASp4XG7DMTg8/MuY3Ii8cAL2whFPkfKozFHTICm+b5F5DLMmDmWT3SuB/JKV9BV5YXjhkB25yd1HTDPby7a7w3iveaR7njr2wLJzv6Gz+Ywleh2o/SxVeST4X+WGsPQPKO/+54r364tdV7Wj7Fg687+dk7REL53M6/wG9MZVRgZObhwAAAABJRU5ErkJggg==",
      "text/latex": [
       "$\\displaystyle \\left( \\left( 1, \\  0, \\  0, \\  0\\right), \\  \\left( 0, \\  0, \\  0, \\  1\\right)\\right)$"
      ],
      "text/plain": [
       "((1, 0, 0, 0), (0, 0, 0, 1))"
      ]
     },
     "execution_count": 94,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bin_seq_for_int(1), bin_seq_for_int(1)[::-1]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "id": "34eeef78-11e5-4ae9-8ffb-18767113dfeb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAADsAAAAVCAYAAAD4g5b1AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADJklEQVRYCdWX7XETMRCGHQ8FmNCB0wEhFRB34KQDkg7M8Mv+Cx0kqSCTdECogMEd2B0Q3IF5HnE6dHdysMdfeGdkSavV3r67q5Xcms/nrbQNh8NuOj/UcQ5Hu5XQaDQaMH2bsA552C3wlBiOjJzEQp/ujP5jYBQ/zDsMPyU85w/wnxLeRofo1uEPtFPGs7pyeF140c53jJ+dwx+nsswN3oz+Vn4Ay0QA3+hPZaYE74Z2XeNpyD38x5S/zhhd2nBH03ABCPg1/ApY5gLVph59IMafGQisx7gSBOY/4L+nn8U0VviGViEErmAoXKcPMNJo19dXnmsM7YKmY+9fUKCtdecbZZ1iEOokLve0IthLPhJCXZM8YV56sLa2r+k5H55gr5mQkhHtwDfyJRW4xNdp8+NZnZar1cF3pn1kviqcLGUzIVnf5lBQU+yppHfywdTOyBbf5St+jFwlz6MECh9tzHXIL8ami9EW/MbOK/qWJr57sUA43CKsV4pUISu+nmlsMZgUzEZXKI8pbkRNo5zCxt5dMbBRoKZvrND1T4uvK1jDbgXMEoqMqiljRPWQSj0z8v8XsjCZhV8WGCS+APaYQTb/2Ww1tpx7h3lOTPmYRnfMc+cDkd0RNlhttS3alfu4Z7ZjZF8i07aSGij1rBplgZrSeyNsMRjH9P+6MUJABWuIGxFCgTxL+Yy+QvD0lKBVshfCBo/RCX0ZUcY+ET1mdRLLs2A1vCHAJkF60TfW4EsqKKs4cs53QnzLgtR42sLTAbn6Y1CmXj1j2hktR3rNd3B4bkUB5qaP14+OatEL1KtpTGs8OZVZkd4U8iH90r3o1/kWpCfG9VffObxckdKm8RF/hfSSgDyHDYLvuk/D1GONPwLIxesr+3hvKM4w0BGfe9YCHWggdKiODddf8Z1F2ZZ1drHnOvyXBfBE0Ov+d0VHn9ZZV88m92NPlzZRZxuvSVbdyuM6cFf/8RzNVt+21R3eJuL7A7ZIkUWVbClL0GHa/VxKeEdC2GS6iyscgRhZP28xqh94+cvSFUpzxWHZ/duQE8/fjE3PR5Hfg5R3qGOwDMST2v8bvrIcfYePPD4AAAAASUVORK5CYII=",
      "text/latex": [
       "$\\displaystyle \\left( 8, \\  12\\right)$"
      ],
      "text/plain": [
       "(8, 12)"
      ]
     },
     "execution_count": 95,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def converted_index(x):\n",
    "    \"\"\"Returns j = 2^{k-1} x_0 + 2^{k-2} x_1 + ... + x_{k-1}.\"\"\"\n",
    "    x_seq = bin_seq_for_int(x)\n",
    "    j = sum(2**(k-1-n) * x_seq[n] for n in range(k))\n",
    "    return j\n",
    "\n",
    "# x = 1 gives the sequence (1, 0, 0, 0), hence j = 8, and vice versa\n",
    "# x = 3 gives the sequence (1, 1, 0, 0), hence j = 12, and vice versa\n",
    "converted_index(1), converted_index(3)  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 96,
   "id": "0ee7c7d2-79a2-477d-a66f-fadc17b10047",
   "metadata": {},
   "outputs": [],
   "source": [
    "Fourier_Q_mat = np.array([[Fourier(Q, converted_index(j), converted_index(l))\n",
    "                           for l in range(Q)] for j in range(Q)])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cac37667-3ebe-458a-8223-f89c2c61b19b",
   "metadata": {},
   "source": [
    "### Shor's algorithm for period finding"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 97,
   "id": "b91fff20-741a-43af-9d1e-8a22cfab726b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def tens_pow_H(k):\n",
    "    # tensor product of copies of the Hadamard matrix\n",
    "    H = np.array([[1, 1],\n",
    "                  [1, -1]]) * (1/np.sqrt(2))\n",
    "    return reduce(np.kron, [H] * k)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 98,
   "id": "03e4a68b-f785-4950-810d-c4b9ae32f5a7",
   "metadata": {},
   "outputs": [],
   "source": [
    "def termwise_XOR(x, y):\n",
    "    # termwise XOR of tuples\n",
    "    return tuple(xi ^ yi for xi, yi in it.zip_longest(x, y))\n",
    "\n",
    "\n",
    "def comp_basis(x_seq):\n",
    "    \"\"\"Returns computational basis vector for binary sequence.\n",
    "\n",
    "    Parameters\n",
    "    ==========\n",
    "        x_seq: list of int\n",
    "    \n",
    "    Returns\n",
    "    =======\n",
    "        numpy.array\n",
    "          1-dimensional array representing |x⟩\n",
    "    \"\"\"\n",
    "    vec_seq = (zeroket if xi == 0 else oneket for xi in x_seq)\n",
    "    return reduce(np.kron, vec_seq)\n",
    "\n",
    "\n",
    "def U(k, f):\n",
    "    # U_f gate for the Shor's algorithm\n",
    "    res = np.zeros((2**(2*k), 2**(2*k)), dtype=np.int8)\n",
    "    for in_x in range(Q):\n",
    "        in_x_seq = bin_seq_for_int(in_x)\n",
    "        f_x_seq = bin_seq_for_int(f(in_x))\n",
    "        out_x_seq = in_x_seq + f_x_seq\n",
    "        in_as_row = np.atleast_2d(comp_basis(in_x_seq + (0,) * k))\n",
    "        out_as_column = np.atleast_2d(comp_basis(out_x_seq)).T\n",
    "        res += out_as_column @ in_as_row\n",
    "    return res"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 99,
   "id": "2eac836a-280a-464d-b3b7-ae0799133723",
   "metadata": {},
   "outputs": [],
   "source": [
    "z = 2  # coprime to N=15, period 4\n",
    "\n",
    "\n",
    "def f_from_z(x):\n",
    "    return z**x % N"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "id": "780c1c4f-6d8d-4330-9e13-46e102020981",
   "metadata": {},
   "outputs": [],
   "source": [
    "H_pow_ampl = np.kron(tens_pow_H(k), np.identity(2**k))\n",
    "F_Q_ampl = np.kron(Fourier_Q_mat, np.identity(2**k))\n",
    "Shor_gate = F_Q_ampl @ U(k, f_from_z) @ H_pow_ampl"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 101,
   "id": "fced37d7-0dc2-41d6-83dd-ea9772771f92",
   "metadata": {},
   "outputs": [],
   "source": [
    "v = Shor_gate @ comp_basis((0,) * (2 * k))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 102,
   "id": "0ce449e7-f599-4b1b-8edf-e30b5625970b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def proj(x):\n",
    "    \"\"\"Return the projection for the computational basis.\"\"\"\n",
    "    basis_vec = np.atleast_2d(comp_basis(x))\n",
    "    return basis_vec.T @ basis_vec\n",
    "\n",
    "\n",
    "def meas_prob(x):\n",
    "    \"\"\"Return the probability of observing x in the first half of v.\"\"\"\n",
    "    return np.real(np.dot(np.conjugate(v), np.kron(proj(x), np.identity(2**k)) @ v))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 103,
   "id": "d91336ed-272c-40cc-b25a-3921486602ab",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0, 0, 0, 0) with probability 0.24999999999999983\n",
      "(0, 0, 0, 1) with probability 0.24999999999999983\n",
      "(0, 0, 1, 0) with probability 0.24999999999999983\n",
      "(0, 0, 1, 1) with probability 0.24999999999999983\n",
      "(0, 1, 0, 0) with probability 2.4470051813928706e-33\n",
      "(0, 1, 0, 1) with probability 4.073856156508302e-31\n",
      "(0, 1, 1, 0) with probability 4.498781880176817e-32\n",
      "(0, 1, 1, 1) with probability 2.189959002944489e-31\n",
      "(1, 0, 0, 0) with probability 3.186062412001537e-33\n",
      "(1, 0, 0, 1) with probability 1.979696497851188e-31\n",
      "(1, 0, 1, 0) with probability 5.554620943171135e-32\n",
      "(1, 0, 1, 1) with probability 5.289982201335954e-31\n",
      "(1, 1, 0, 0) with probability 1.159853971866433e-32\n",
      "(1, 1, 0, 1) with probability 7.433780893255674e-31\n",
      "(1, 1, 1, 0) with probability 1.2971604543390791e-31\n",
      "(1, 1, 1, 1) with probability 1.1455302955436829e-30\n"
     ]
    }
   ],
   "source": [
    "intervals = {}\n",
    "t = 0.0\n",
    "for x in it.product(range(2), repeat=k):\n",
    "    prob_for_x = meas_prob(x)\n",
    "    intervals[x] = (t, t + prob_for_x)\n",
    "    t += prob_for_x\n",
    "    print(f\"{x} with probability {prob_for_x}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 104,
   "id": "b03aca49-ffcf-4a90-86c1-73ce90cfa57e",
   "metadata": {},
   "outputs": [],
   "source": [
    "x_samples = []\n",
    "for _ in range(k):  # when k is small, it's better to repeat a bit more\n",
    "    rand_num = rng.random()\n",
    "    for x in it.product(range(2), repeat=k):\n",
    "        if intervals[x][0] < rand_num and rand_num < intervals[x][1]:\n",
    "            x_samples.append(x)\n",
    "            break"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 105,
   "id": "969b1f0f-6f9c-429d-a1f8-9d8dd7a058cb",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[ \\left( 0, \\  0, \\  0, \\  0\\right), \\  \\left( 0, \\  0, \\  1, \\  1\\right), \\  \\left( 0, \\  0, \\  1, \\  1\\right), \\  \\left( 0, \\  0, \\  1, \\  1\\right)\\right]$"
      ],
      "text/plain": [
       "[(0, 0, 0, 0), (0, 0, 1, 1), (0, 0, 1, 1), (0, 0, 1, 1)]"
      ]
     },
     "execution_count": 105,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x_samples  # binary sequnces replesenting c*L for L = Q/s"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 106,
   "id": "1ee8e55e-913e-4861-974f-b207a56df899",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAADsAAAAVCAYAAAD4g5b1AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACj0lEQVRYCdWX0VHbQBCGDZMCBOlAdAChguAOgBKgA/NovyYdkFSQIR1ASsAd2B2EuAPn+8465xDnYCTZjHfmtHur1e3+2tvVqTefz3vpGA6HZTrfVTmHY7+X0Gg0GjA9TlS7LJYVniWGPTMnceMcdgq/CYraBb0v4Y5xgjyr3e6hK9HFZz8hPzlHP67bdjVn7ddiMnkz7L7pM4BlUiD/gp+ojFTpvzM3cAG4+AH6Z2CZC/QW3ocHQv6CoLM+8sNC2/7KWsb6akzRE/aPyJ/hsw+V0sBuo0HkGiBfOEf+3xb3+WvtImFvVq+YuxsOor4tZ811Y4quxBXiizV7ySIh1dHijfwM+wlr+NZTMqMFejP/LlThEl+xz8VanbaMRFBT1vKt56j+EnI2m9SJ79JtbJ21qilAhq2eiTZ0du5vrEllfOZU4uu7jW08k5xFGx0ABer2jR26zXJtnxVfKVi3mN22a7Ix/QT0164XbrCe+ALYQ4RVtdZg3dC57YDW8Krt3WjdFg9Zs4WZ7ZQA6OfmEL785nbqoNliIaGCNcWddEsA2tmP0owie2yzdt+TQqkK1hS3DgZANqTccdMXsOwJ2HXyYlnzLWRmp356xozTNZ78WNm8qPEqczakB+T6SewMXWhSFdA/8DHj2dF0Df85k5Ux1Yz1NRbsD4aBZomg4j1PSdIdOnfDPTyeuu6Zuzus1zr5MgNh76E8NAu4J6tGjZHn1okpupUb+3X4l+Xfb8I43ta/K77OGcU2/OGnZEz0Zc1KLw7yC/XGrtZ2o6w2iMhDjfgWYHHsdtxK18SXDeq3zjdN+LK0xBXKLWZWvx4A6s1Ffdd0hfNtnarE8+/XM62ban8PUt2uymAZiCeN/y+V/doNila14wAAAABJRU5ErkJggg==",
      "text/latex": [
       "$\\displaystyle \\left( 12, \\  1\\right)$"
      ],
      "text/plain": [
       "(12, 1)"
      ]
     },
     "execution_count": 106,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# get L as the greatest common divisor of integers c*L\n",
    "from math import gcd\n",
    "L = reduce(gcd, (int_from_bin_seq(x_seq) for x_seq in x_samples))\n",
    "s = Q // L\n",
    "L, s"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cfc51e35-a2f2-4bc0-a6b5-3d46cc3e9973",
   "metadata": {},
   "source": [
    "### continued fraction"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1b41f234-0dbd-4c7b-bddb-dd32096d5044",
   "metadata": {},
   "source": [
    "code sample from [sympy documentation](https://docs.sympy.org/latest/tutorials/intro-tutorial/simplification.html#example-continued-fractions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 107,
   "id": "96ccbb02-afcd-4a47-a17b-210a0441ca86",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sympy as sy"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 108,
   "id": "8f5065e4-9b1b-4a07-a8ab-41a21d664f16",
   "metadata": {},
   "outputs": [],
   "source": [
    "def list_to_frac(coeff_list):\n",
    "    \"\"\"Returns continued fraction with the given coefficients.\"\"\"\n",
    "    expr = sy.Integer(0)\n",
    "    for i in reversed(coeff_list[1:]):\n",
    "        expr += i\n",
    "        expr = 1/expr\n",
    "    return coeff_list[0] + expr"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf188e84-9d4a-4bc9-9acd-16920c9233fc",
   "metadata": {},
   "source": [
    "continued fraction approximation of $\\sqrt{2} = [1; 2, 2, \\cdots]$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 109,
   "id": "a39fcc3d-5efa-4dbd-ba94-937b84958237",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( \\frac{3}{2}, \\  \\frac{7}{5}, \\  \\frac{17}{12}\\right)$"
      ],
      "text/plain": [
       "⎛          17⎞\n",
       "⎜3/2, 7/5, ──⎟\n",
       "⎝          12⎠"
      ]
     },
     "execution_count": 109,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "list_to_frac([1, 2]), list_to_frac([1, 2, 2]), \\\n",
    "  list_to_frac([1, 2, 2, 2])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "id": "e680d797-1818-4c63-840b-08ce8b77688c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def sqrt2_approx_error(k):\n",
    "    \"\"\"Approximation error of continued fracton of sqrt(2).\"\"\"\n",
    "    frac_at_k = list_to_frac([1] + [2] * k)\n",
    "    error = sy.N(sy.sqrt(2) - frac_at_k)\n",
    "    err_abs = error if error > 0 else -error\n",
    "    return err_abs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 111,
   "id": "0e919932-0f63-4177-a74b-9b2fa9ac6879",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x1227a34d0>]"
      ]
     },
     "execution_count": 111,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAjUAAAHOCAYAAABgqPjfAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAATaNJREFUeJzt3Ql4VNX9//Fv9gRIwhLIDgFBIgbDlgQQBZWK/tC/uLJZkFJtrVIVrQWroK0tolKphZ+I7U+sJYKgIiJSEdwqSICw78qSQHYgCwlZZ/7POWHGBJKQSTK5s7xfz3Ofmblz5s6Zm8B8crbrYTabzQIAAODkPI2uAAAAQEsg1AAAAJdAqAEAAC6BUAMAAFwCoQYAALgEQg0AAHAJhBoAAOASCDUAAMAlEGoAAIBLINQAAACXQKgBAAAugVADAABcAqEGTuGFF14QDw+PZm0AANfmbXQFgMspLCyUNm3aCBeUBwA0hJYaOLzPPvtMbr31VqOrAQBwcIQaGCImJkaef/75RpXdu3evxMXFiaN54IEHrF1bjlg/ZzB//vxaXYR5eXlN+h3ZunWrDB06VNq2bauPs3PnTjGCqu/Fn8Nezp07J56envLaa685Zf2NVt/vjKP8LqFpCDVwaJWVleLr63vJfvUfz6OPPipXX321/s+na9euct9998nhw4dbtX4hISHy7rvvyksvvVTry2b27Nlyyy23SMeOHfV/jEuWLGnyezjKZ7VHfdU5UufvzjvvbPL7VVRUyL333itnzpzRX/DqeN26dRN72bRpk/7yz8/PFyOpsK+6ZG0N1I5SfyPrWd/vTGv/LqHlMaYGDu3rr7+W66+//pL9c+fOle+++07/B3TNNddIVlaWLFiwQAYMGCDff/99q7WcqC/t+++/v9Y+9VfuH//4R/1lHh8fL1999VWz3sNRPqs96hsbG6u3H374QT766KMmvd+PP/4oJ06ckLfeekt++ctfSmt82aqB66qlrn379mKUPXv26Nu+ffs6Zf2NrGd9vzMHDx5s1d8ltDxCDRzaf//7X3n22Wcv2T99+nRJTk6u1YozduxY/R+8ajX597//LUYJDw+XzMxMCQsLk23btklCQkKzjufIn9UR6puTk6NvW/KLr7i4WAdWR6ZCjWopVL9naJnfGXv8LqF10f0Eh+9+8vLyumS/6vO+uFuqV69eusvjwIEDYiQ/Pz+bvmjUX4dpaWn1Pu/In9Xo+qq/4ocPH67vq5Yh1dU3YsQI6/M7duzQg8yDgoKkXbt2ctNNN+nWorrGkezfv18mTJggHTp0kGHDhtX5fqrs7373O32/e/fu1rFAx48fr1VOdZlYWhiCg4NlypQpUlJSUqvMqVOn5Be/+IWEhobq3xl1fv7v//7PplCjXlOTamFQ5/7xxx+Xqqoqu9a/PupzTZ06VSIiIvTnUu/z8MMPS3l5uX5eHVeNl6qrbpalFxpbz7pc7mde3+/M5X6X4BxoqYFDyMjIkL/85S+6m8JCDdDr169fo4+hxhdkZ2df8h+9o7vqqqv0f6a2dFM522e1V31/9atfSWRkpP7d+e1vf6tbxVRIUPbt2yfXXXed/nJ7+umnxcfHR9588039RaW6NZOSkmodS32RqfCljlXf8gF33XWXHhv03nvv6TEXqqVE6dy5c61yagyR+jKeM2eOpKamyj/+8Q/p0qWL7ppT1LkYPHiw/uJU44/U69UsPxUG1BIGKpQ0JtSMHz/eGv7VaxYvXiwLFy6UBx980K71b+jfcWJiog5FDz30kO5aVCFn5cqVOhTVNT6uOfW8WGN+5vX9zqgAVN/vEpyIGTBAt27dzLNnz7Y+Xrt2rbl79+7mw4cPW/e99NJL5qKiokYf891331XfROZ//vOf5tYwefJk/TkasnXrVl2nt99+u94y6vnhw4fb9N6t/Vmb63L1Vb8L6vnc3Nx6f0fq8+WXX+rXrlixotb+MWPGmH19fc0//vijdV9GRoY5MDDQfP3111/y3uPHj2/UZ3nllVd0+WPHjtX7OX7xi1/U2n/nnXeaO3XqZH08depUc3h4uDkvL69WuXHjxpmDg4PNJSUlDdZBfQ71PosWLTKfPn3afOONN5o7duyoz0Vr1L8+kyZNMnt6eurf+4uZTKYG/91Y3rsx9axPY3/m9f3O1LcfzoPuJzgE1Vw8adIkWbVqVa1ZROqvp8Z24TzyyCMyZMgQmTx58mXLm0wmKS0tbdRm70X/1PFtaaWx9bNe/LlPnjwpZ8+erbVf/WWt9tfssqivrK2aU9+mUp/j888/lzFjxkiPHj1qjXdSXUxqrJZqEanp17/+dYu9/8XHUq0Hp0+f1u+pft4ffPCB3H777fq+Glhu2UaNGiUFBQW6daQhu3fv1reqpUe1KKgWki1btrRYd0lD9a+P+n1R/37V5xo0aNAlz9t7Ve+m/Mzhegg1cBjqPyNLqElPT9ezhxpDza4ZPXq07vtXzdx1jcG52DfffCMBAQGN2g4dOiSOoimf9eKBkNHR0fLEE0/U2j9jxgy9X533y5Vtzfo2VW5uru7u6N27d53dfeoLuOZnVVR3S0u5+HdXjdNRVEBUdVMhUnUVqe6Umpsau1JzwOrlZj6privVRbJ582bp2bOn9Xk1fkWd+5pbXWNsmlL/+qjPpUKDvWfj1ffZmvIzh+thTA0chho/o/6DUuMN1qxZo0PO5ai/alUrj/qS+Pbbb/XgxMZQff1vv/12o8qqv/QcQVM/q1Gcrb4qwLaU+sKbaplRX66KWgqgvpYrNRX+cqFGrZ9yxRVX6PVqVKtmzRk7ajr0DTfcUOs1x44dq3OArq31b676WmwaG7rq+2z+/v7NrhucH6EGDkU1XX/yySe6Of1yYUJ1DanyakDhF198IX369Gn0+6jZSWq2g7Nozme9+HPX9cW0aNEivTWmbGvWt6lUq4e6XlhdrWyqO0ytxKtaoZqiud0oqm6BgYH6S3zkyJFNOoYKNeqPADXbSXX1qMULVXC0fLGr9ZHWr19f6zWWGXn26gZSn0sN0FUhqyGq1aeuBfXU+jA11VfP+j6bGhRsr585nAfdT3Aod9xxh7zzzjuXXSdCfSGotU9Us/uKFSv0eA1X5Wyf1RHqq1oabr75Zvn4449rTQNWrYBqDR01ZVt9ATeFZf2apq50q+p2991363E1dQUA1Y1yufOrpsardX9UkPjwww/1cdS06ZrBQQWmmpsl8DS3/vVRoUG1rqo/StT6TBezBGTVuqRa8SzjghS1rtPFiy/WV8/6Pps9f+ZwHrTUwKGo1YPVeiFqGmZDnnzySVm9erVuDVBLml+8oNvFq/y2NjU1Xf1nrFqcFPUfvRp0q0ybNk2PMan5F2lDU7pt+ayXO1ZLaeh9HOVn8+KLL+q/6NWX2W9+8xvx9vbWv1dlZWXy8ssvN/m4AwcO1Ld/+MMfZNy4cbqFQH1WWxbrU4sQfvnll3qKsZp+rVqy1LlSA4RVy5a6X58jR47oljDLSsKqPm+88YYej6Puq3E29q5/fdR0aDVYV/1uqCndaiyLCiwq3KqBuuqPFfWev//973Xrkpo6rcbBqPpfeeWVtQZIN6We9vqZw4kYPf0K7qmh6br/+Mc/Lvt6NQVa/frWtxk9pVvtr69uNaeoqinrap+aytvcz9qYY7WEy71PU3429pjSraSmpppHjRplbteunblNmzbmG264wbxp06bLvvfl/OlPfzJHRkbq6cs1f6b1HUtN6b/4Z5+dnW1+5JFHzNHR0WYfHx9zWFiY+aabbjIvXry4wfd+//339bH27dtXa/9vfvMbfZyvv/66VepfnxMnTuip3Z07dzb7+fmZe/TooT9nWVmZtcznn39ujouL09Ove/fubf73v/99yZTuhurZkMb8zJnS7boINTBEY7+wHJkKNeoLSX0BnD17tknH+PTTT80eHh7m3bt3N7s+LXms1nqf8+fP6/P3u9/9rsmhBgAsGFMDNIOaIqrGNdS3rP7lqC4I1bRu60UJ7X2s1nofNThZnb9XXnmlReoGwL0xpgZoIrUMu2V8SGMXCbxYS36Zt1YwaMn3UQNma65rUnOsEQDYilADNJEa3NnaU5VdjZpiyzRbAC3FQ/VBtdjRAAAADMKYGgAA4BIINQAAwCW4zZgadb0VtRCaWp7c3leLBQAALUONkikqKtLXj1MrVzfEbUKNCjQMSAQAwHmX0IiKimqwjNuEGtVCYzkpXP8DAADnUFhYqBslLN/jDXGbUGPpclKBhlADAIBzaczQEQYKAwAAl0CoAQAALoFQAwAAXAKhBgAAuARCDQAAcAmEGgAA4BIINQAAwCUQagAAgEsg1AAAAJdAqAEAAC6BUAMAAFwCoQYAALgEQk0LyCw4L0eyi1riUAAAoIkINc20cvtJufaljfKnTw8091AAAKAZCDXNlBjTUUxmkW8O50ra6ZLmHg4AALRmqFm4cKHExMSIv7+/JCUlSUpKSoPlV6xYIbGxsbp83759Ze3atbWe//DDD+Xmm2+WTp06iYeHh+zcubPeY5nNZrn11lt1uVWrVonRunZqI9df2VnfT05JM7o6AAC4LZtDzfLly2X69Okye/ZsSU1Nlfj4eBk1apTk5OTUWX7Tpk0yfvx4mTp1quzYsUPGjBmjt71791rLFBcXy7Bhw2Tu3LmXff/58+frQONIJiR21bcrtqVLeaXJ6OoAAOCWPMyq6cMGqmUmISFBFixYoB+bTCaJjo6WadOmyYwZMy4pP3bsWB1a1qxZY903ePBg6devnyxatKhW2ePHj0v37t11+FHPX0y14Nx2222ybds2CQ8Pl48++kgHpMYoLCyU4OBgKSgokKCgIGlJFVUmGTZ3o2QXlsnfx/eX2+MjWvT4AAC4q0Ibvr9taqkpLy+X7du3y8iRI386gKenfrx58+Y6X6P21yyvqJad+srXp6SkRCZMmKC7vsLCwi5bvqysTJ+Impu9+Hh5ytiE6taapVtO2O19AABAC4WavLw8qaqqktDQ0Fr71eOsrKw6X6P221K+Pk888YQMHTpU7rjjjkaVnzNnjk52lk21JtnTuIRo8fQQ+f7oGfkh55xd3wsAADjp7KfVq1fLxo0b9Xiaxpo5c6ZuqrJs6enpdq1jRPsAuTG2i77/HgOGAQBw7FATEhIiXl5ekp2dXWu/elxfl5Dab0v5uqhA8+OPP0r79u3F29tbb8rdd98tI0aMqPM1fn5+uu+t5mZvE5O6WdeuKa2osvv7AQCAJoYaX19fGThwoGzYsMG6Tw0UVo+HDBlS52vU/prllfXr19dbvi5qAPLu3bv1QGHLprz22mvy9ttvi6NQU7sj2wdIwfkK+XR3ptHVAQDArVQ3edhATeeePHmyDBo0SBITE3WXkJrdNGXKFP38pEmTJDIyUo9pUR577DEZPny4zJs3T0aPHi3Lli3Ts5cWL15sPeaZM2ckLS1NMjIy9ONDhw7pW9WaU3O7WNeuXfVsKUfh5ekh4xOj5dXPD+sBw3cPjDK6SgAAuA2bx9SoKdqvvvqqzJo1S0+7Vq0m69atsw4GVuEkM/OnVgo1uDc5OVmHGLWmzcqVK/WieXFxcbXGzPTv31+HHmXcuHH68cVTvp3BfYOixdvTQ1LT8uVApv1mXAEAgGauU+Os7LlOzcV+s3S7rN2TJT8f3E3+NOan8AYAABxknRo0zoTE6gHDH+04JcVllZw2AABaAaHGDoZe0UliOrWRc2WV8smu6nFCAADAvgg19jipnh4yIcmywjAXuQQAoDUQauzknoHR4uvlKXtOFcjuk/n2ehsAAHABocZOOrb1lVv7Vk9DX/o9rTUAANgboaYVVhhevStDCksr7PlWAAC4PUKNHSXEdJBeXdrJ+YoqWbXjlNv/sgEAYE+EGjvy8KgxYPj7NHGTJYEAADAEocbO7uofJf4+nnIou0hS087a++0AAHBbhBo7C27jI7dfE6HvM2AYAAD7IdS0gomDqwcMr9mTKWeLy1vjLQEAcDuEmlYQHxUsfcKDpLzSJB+knmyNtwQAwO0QalppwPDEwdUDhpO3MGAYAAB7INS0kjv6RUpbXy85mlcsm4+ebq23BQDAbRBqWkk7P2+5o3+kvs/1oAAAaHmEmlY0IbG6C+rzfVmSW1TWmm8NAIDLI9S0orjIYOkX3V4qqsyyYnt6a741AAAuj1DTyiYm/TRg2GRihWEAAFoKoaaV3XZNhAT6e8vJs+flmyO5rf32AAC4LEJNKwvw9ZK7B0RZW2sAAEDLINQY2AW14WCOZBacN6IKAAC4HEKNAXqFBkpiTEepMpll+VYGDAMA0BIINQaxrDCsQk1llcmoagAA4DIINQa5JS5MOrb1lcyCUvnyEAOGAQBoLkKNQfy8veTegdUDhpduOWFUNQAAcBmEGgONv7DC8NeHcyX9TImRVQEAwOkRagwUE9JWhvUMEbNZZNlWpncDANAchBoHmd69fOtJKa9kwDAAAE1FqDHYyD6h0jnQT/LOlcn6/dlGVwcAAKdFqDGYj5enjB0Ure8npzBgGACApiLUOIBxidHi4SHy3Q+n5WjuOaOrAwCAUyLUOICoDm3kht5d9P33UhgwDABAUxBqHMSEC9O7V2w/KaUVVUZXBwAAp0OocRA3xHaRiGB/yS+pkHV7s4yuDgAATodQ4yC8PD1k3IXWGlYYBgDAdoQaBzI2IVqHm63Hz8rh7CKjqwMAgOuHmoULF0pMTIz4+/tLUlKSpKSkNFh+xYoVEhsbq8v37dtX1q5dW+v5Dz/8UG6++Wbp1KmTeHh4yM6dO2s9f+bMGZk2bZr07t1bAgICpGvXrvLb3/5WCgoKxJWEBvnLyKuqBwwnb2HAMAAAdg01y5cvl+nTp8vs2bMlNTVV4uPjZdSoUZKTk1Nn+U2bNsn48eNl6tSpsmPHDhkzZoze9u7day1TXFwsw4YNk7lz59Z5jIyMDL29+uqr+nVLliyRdevW6WO6molJ3fTtB6knpaS80ujqAADgNDzMZnXlocZTLTMJCQmyYMEC/dhkMkl0dLRuSZkxY8Yl5ceOHatDy5o1a6z7Bg8eLP369ZNFixbVKnv8+HHp3r27Dj/q+cu1/tx///362N7e3petd2FhoQQHB+vWnaCgIHFUJpNZRrz6laSdKZGX775G7kuoXpgPAAB3VGjD97dNLTXl5eWyfft2GTly5E8H8PTUjzdv3lzna9T+muUV1bJTX/nGsny4+gJNWVmZPhE1N2fg6elhvXr3UtasAQCg0WwKNXl5eVJVVSWhoaG19qvHWVl1T0NW+20p39h6/OlPf5KHHnqo3jJz5szRyc6yqdYkZ3HvoCjx8fKQXen5sveUa40bAgDAXpxu9pNqcRk9erT06dNHnn/++XrLzZw5U7fmWLb09HRxFiHt/OSWuHB9fykDhgEAaPlQExISIl5eXpKdXftq0upxWFhYna9R+20p35CioiK55ZZbJDAwUD766CPx8fGpt6yfn5/unqq5OeMKwx/vPCVFpRVGVwcAANcKNb6+vjJw4EDZsGGDdZ8aKKweDxkypM7XqP01yyvr16+vt3xDLTRq2reqw+rVq/X0cFc2uEdH6dG5rZSUV8nHOzOMrg4AAK7X/aSmc7/11lvyzjvvyIEDB+Thhx/WM5CmTJmin580aZLu+rF47LHH9PTrefPmycGDB3WX0bZt2+TRRx+ttQ6NWptm//79+vGhQ4f0Y8u4G0ugUe/zz3/+Uz9Wz6lNjfFxRWq9Hsv0btUFZeMkNQAA3I7NoUZN0VbrxcyaNUtPu1bhQ4UWy2DgtLQ0yczMtJYfOnSoJCcny+LFi/WaNitXrpRVq1ZJXFyctYxqeenfv78eK6OMGzdOP7ZM+Vbr4WzZskX27NkjPXv2lPDwcOvmTGNlbHX3gEjx8/aUA5mFsiM93+jqAADgWuvUOCtnWafmYtPf3ykfpp6SewZGyav3xhtdHQAAXGOdGrQ+SxfUJ7sypKCEAcMAANSHUOPgBnRtL7FhgVJWadKXTgAAAHUj1DjFgOELKwxvOcGAYQAA6kGocQJj+kdKG18v+TG3WFKOnTG6OgAAOCRCjRMI9PeRO/pF6PusMAwAQN0INU5iQmL1gOHP9mbK6XNlRlcHAACHQ6hxEn2jguWaqGCpqDLLyu0MGAYA4GKEGidiGTCcnJImJpNbLC8EAECjEWqcyO3xERLo5y0nTpfIdz/mGV0dAAAcCqHGibTx9ZY7B0Tq+0u/TzO6OgAAOBRCjZOZcKELav2BbMkuLDW6OgAAOAxCjZOJDQuSQd06SJXJLO9vdd2LeQIAYCtCjROaOLi6tea9lDQdbgAAAKHGKd0aFy7t2/hIRkGpfH04x+jqAADgEGipcUL+Pl5yz4AofZ8BwwAAVCPUOKnxFwYMbzyUIyfPlhhdHQAADEeocVJXdG4nQ3p0ErNZZDkDhgEAINS4woBhFWoqqkxGVwcAAEPRUuPEbu4TJiHtfCWnqEw2HMg2ujoAABiKUOPEfL095b5B0fr+0i2sMAwAcG+EGic3PrGreHiIfHskT06cLja6OgAAGIZQ4+SiO7aR63t1tl69GwAAd0WocQETL0zvXrHtpJRVVhldHQAADEGocQE3xnaRsCB/OVNcLuv2ZhldHQAADEGocQHeXp4yNqF6wHAyA4YBAG6KUOMixiVGi6eHyJZjZ+SHnCKjqwMAQKsj1LiI8OAAuemqUH2f6d0AAHdEqHEhEy4MGP5g+0kprWDAMADAvRBqXIia2h3VIUAKSytlze5Mo6sDAECrItS4EC9PD70Yn7J0ywmjqwMAQKsi1LiYewdFibenh+xIy5f9GYVGVwcAgFZDqHExXQL9ZdTVYfp+cgqtNQAA90GoceEVhj9KPSXnyiqNrg4AAK2CUOOChlzRSXqEtJXi8ipZvTPD6OoAANAqCDUuyMOj9oBhs9lsdJUAALA7Qo2LuntglPh6e8q+jELZfbLA6OoAAOCYoWbhwoUSExMj/v7+kpSUJCkpKQ2WX7FihcTGxuryffv2lbVr19Z6/sMPP5Sbb75ZOnXqpFsZdu7ceckxSktL5ZFHHtFl2rVrJ3fffbdkZ2c3pfpuoWNbXxndN1zfZ3o3AMAd2Bxqli9fLtOnT5fZs2dLamqqxMfHy6hRoyQnJ6fO8ps2bZLx48fL1KlTZceOHTJmzBi97d2711qmuLhYhg0bJnPnzq33fZ944gn55JNPdED6+uuvJSMjQ+666y5bq++WKwyv3pUhBecrjK4OAAB25WG2ccCFaplJSEiQBQsW6Mcmk0mio6Nl2rRpMmPGjEvKjx07VoeWNWvWWPcNHjxY+vXrJ4sWLapV9vjx49K9e3cdftTzFgUFBdK5c2dJTk6We+65R+87ePCgXHXVVbJ582Z9vMspLCyU4OBgfaygoCBxB+pHO2r+N3I4+5y88P+ulslDY4yuEgAANrHl+9umlpry8nLZvn27jBw58qcDeHrqxypc1EXtr1leUS079ZWvi3rPioqKWsdR3Vldu3a16TjuRnXlTUzqpu8zYBgA4OpsCjV5eXlSVVUloaHVV4O2UI+zsrLqfI3ab0v5+o7h6+sr7du3b/RxysrKdLqrubmjOwdESoCPl26t2XbirNHVAQDAblx29tOcOXN0c5VlU11k7ijI30duj68eMJy8Jc3o6gAA4BihJiQkRLy8vC6ZdaQeh4VVL81/MbXflvL1HUN1feXn5zf6ODNnztT9b5YtPT1d3JWlC+rTPZlyprjc6OoAAGB8qFFdQAMHDpQNGzZY96mBwurxkCFD6nyN2l+zvLJ+/fp6y9dFvaePj0+t4xw6dEjS0tLqPY6fn58eUFRzc1fXRAVLXGSQlFea5IPtJ42uDgAAduFt6wvUdO7JkyfLoEGDJDExUebPn69nN02ZMkU/P2nSJImMjNTdP8pjjz0mw4cPl3nz5sno0aNl2bJlsm3bNlm8eLH1mGfOnNEBRU3TtgQWRbXCqE11H6kp4eq9O3bsqAOKmm2lAk1jZj65OzVgeEJiN3nmoz2SnJImv7yuu94HAIBbj6lRU7RfffVVmTVrlp52rRbKW7dunXUwsAonmZmZ1vJDhw7VU7FViFFr2qxcuVJWrVolcXFx1jKrV6+W/v3769CjjBs3Tj+uOeX7tddek9tuu00vunf99dfrsKMW7UPj/L9+EdLOz1uO5RXL5h9Pc9oAAC7H5nVqnJU7rlNzsWdX7ZF/f5+mVxpeOHGA0dUBAMC4dWrg3FQXlPKffVmSU1RqdHUAAGhRhBo30iciSPp3bS+VJrOs2MaAYQCAayHUuOn07vdS0qTK5BY9jwAAN0GocTO3XRMuQf7ecvLsefnmSK7R1QEAoMUQatyMv4+X3D0wSt9nhWEAgCsh1LihiUld9e2GA9mSWXDe6OoAANAiCDVuqGeXQEnq3lHUkJplKe57+QgAgGsh1LipiYOrBwwv25omlVUmo6sDAECzEWrc1KirQ6VjW1/JLiyTjQdzjK4OAADNRqhxU37eXnLvoOoBw0u3pBldHQAAmo1Q48YmJFYPGFZTu9PPlBhdHQAAmoVQ48a6dWor1/UKEXX1L7UYHwAAzoxQ4+Ys07vf35Yu5ZUMGAYAOC9CjZu76apQ6RLoJ3nnyuXz/VlGVwcAgCYj1Lg5Hy9PGZcQre8v/Z4uKACA8yLUQMYmdhVPD5HNR0/Lj7nnOCMAAKdEqIFEtg+QG3p30WfiPaZ3AwCcFKEG2sTB1QOGV6aelNKKKs4KAMDpEGqgDb+yi26xyS+pkM/2ZnJWAABOh1ADzcvTgwHDAACnRqiB1diEaB1utp04KwezCjkzAACnQqiBVZcgf7m5T6i+n8yAYQCAkyHUoJYJF1YY/ij1lJSUV3J2AABOg1CDWq69IkS6dWojRWWV8smuDM4OAMBpEGpQ+xfC08N69e6ldEEBAJwIoQaXuGdglPh6ecrukwWy52QBZwgA4BQINbhEp3Z+cktcmL6fnHKCMwQAcAqEGtRp4oUBwx/vzJDC0grOEgDA4RFqUKfE7h2lZ5d2UlJeJR/vOMVZAgA4PEIN6uThUXvAsNls5kwBABwaoQb1untAlPh5e8rBrCJJTcvnTAEAHBqhBvUKbuMjt8dH6PtLtzBgGADg2Ag1aNQKw5/uzpT8knLOFgDAYRFq0KD+0e3lqvAgKas0yQepDBgGADguQg0uO2DYMr1bdUExYBgA4KgINbisMf0jpa2vlxzNLZbvj57hjAEAHBKhBpfVzs9b/l+/SH0/OSWNMwYAcJ1Qs3DhQomJiRF/f39JSkqSlJSUBsuvWLFCYmNjdfm+ffvK2rVraz2vujRmzZol4eHhEhAQICNHjpQjR47UKnP48GG54447JCQkRIKCgmTYsGHy5ZdfNqX6aAJLF9S6vZmSd66McwgAcP5Qs3z5cpk+fbrMnj1bUlNTJT4+XkaNGiU5OTl1lt+0aZOMHz9epk6dKjt27JAxY8bobe/evdYyL7/8srz++uuyaNEi2bJli7Rt21Yfs7S01Frmtttuk8rKStm4caNs375dv6/al5WV1dTPDhvERQZLfHR7qagyy4ptJzl3AACH42G2ceSnaplJSEiQBQsW6Mcmk0mio6Nl2rRpMmPGjEvKjx07VoqLi2XNmjXWfYMHD5Z+/frpEKPePiIiQp588kl56qmn9PMFBQUSGhoqS5YskXHjxkleXp507txZvvnmG7nuuut0maKiIt1is379et2yczmFhYUSHBysj61eB9u9vzVdnv5gt3Tt2Ea+emqEeHp6cBoBAHZly/e3TS015eXlupWkZojw9PTUjzdv3lzna9T+i0OHaoWxlD927JhubalZRlVehSdLmU6dOknv3r3lX//6lw5IqsXmzTfflC5dusjAgQPrfN+ysjJ9ImpuaJ7b4sMl0N9b0s6UyH9/yON0AgAcik2hRrWYVFVV6VaUmtTj+rqB1P6GyltuGyqjphV/8cUXuvsqMDBQj83561//KuvWrZMOHTrU+b5z5szR4ciyqdYkNE8bX2996QSFFYYBAI7GKWY/qS6qRx55RLfMfPvtt3pgshqXc/vtt0tmZmadr5k5c6ZuqrJs6enprV5vV15h+IsDOZJd+NOYJwAAnCrUqJlHXl5ekp2dXWu/ehwWFlbna9T+hspbbhsqowYHqzE5y5Ytk2uvvVYGDBgg//u//6tnSr3zzjt1vq+fn5/ue6u5ofmuDA2UhJgOUmUyy/KtBEUAgJOGGl9fXz2GZcOGDdZ9aqCwejxkyJA6X6P21yyvqMG9lvLdu3fX4aVmGTX+Rc2CspQpKSmprqxn7eqqx+r90bomJnXTt++lpEllFecfAOCk3U9qOvdbb72lW0gOHDggDz/8sB68O2XKFP38pEmTdNePxWOPPabHvsybN08OHjwozz//vGzbtk0effRR63iZxx9/XF588UVZvXq17NmzRx9DzYhSXUyKCjdq7MzkyZNl165des2a3/3ud3qQ8ejRo1vubKBRbokLkw5tfCSzoFS+OpTLWQMAOARvW1+gpmjn5ubqxfLUQF41NVuFFstA37S0tFotKkOHDpXk5GR59tln5ZlnnpFevXrJqlWrJC4uzlrm6aef1sHooYcekvz8fL2wnjqmGhBs6fZSj//whz/IjTfeKBUVFXL11VfLxx9/rNerQevy9/GSewZGyVvfHtMrDI/sU3uQNwAATrFOjbNinZqWdTT3nNw472vx8BD59ukbJKpDmxZ+BwAAxH7r1AAWPTq3k2t7dhIViZelMGAYAGA8Qg2aPWB4+bZ0qWDAMADAYIQaNNnP+oRKSDs/yS0qky/2156SDwBAayPUoMl8vDxlbIJlheE0ziQAwFCEGjTLuISuerCwuhbU8bxiziYAwDCEGjRLdMc2MvzKztbF+AAAMAqhBi02YPj9belSVlnFGQUAGIJQg2a7oXdnCQ/2l7MlFbJub91XawcAwN4INWg2by9PPbZGWfo9XVAAAGMQatAixiZEi5enh6QcPyNHsos4qwCAVkeoQYsIC/aXm2K76PtM7wYAGIFQgxYzcXD1gOEPUk/K+XIGDAMAWhehBi3mup4hEt0xQIpKK2XN7gzOLACgVRFq0HK/TJ4eMj7xwoBhVhgGALQyQg1a1L0Do8XHy0N2pufL3lMFnF0AQKsh1KBFdQ70k1FXh+n7yawwDABoRYQatLgJSdVdUB/vOCXnyio5wwCAVkGoQYsb0qOT9AhpK8XlVfLxzlOcYQBAqyDUoMV5eHhYW2vUCsNms5mzDACwO0IN7OKegVHi6+0p+zMLZddJBgwDAOyPUAO7aN/GV27rG67vL/3+BGcZAGB3hBrYzcTB1V1Qn+zOkILzFZxpAIBdEWpgNwO6dpDYsEAprTDJR6knOdMAALsi1KB1BgxvYcAwAMC+CDWwqzH9IyXAx0uO5JyTrcfPcrYBAHZDqIFdBfn7yB39IvT9pVsYMAwAsB9CDezO0gX12Z4sOVNczhkHANgFoQZ2d01Ue+kbGSzlVSZZuT2dMw4AsAtCDVrFxAutNclb0sRkYoVhAEDLI9SgVdweHyGBft5y/HSJbD56mrMOAGhxhBq0irZ+3nomlMKAYQCAPRBq0OoDhj/fly05haWceQBAiyLUoNVcFR4kA7t1kEqTWd7fxoBhAEDLItSgVU1IrG6teS8lXaoYMAwAaEGEGrSq0deES3CAj5zKPy/fHM7l7AMAWgyhBq3K38dL7hkYpe8zYBgAYHioWbhwocTExIi/v78kJSVJSkpKg+VXrFghsbGxunzfvn1l7dq1tZ43m80ya9YsCQ8Pl4CAABk5cqQcOXLkkuN8+umn+v1UmQ4dOsiYMWOaUn04yIDhjQdzJCP/vNHVAQC4a6hZvny5TJ8+XWbPni2pqakSHx8vo0aNkpycnDrLb9q0ScaPHy9Tp06VHTt26CCitr1791rLvPzyy/L666/LokWLZMuWLdK2bVt9zNLSn2bIfPDBB/Lzn/9cpkyZIrt27ZLvvvtOJkyY0NTPDQNd0bmdDO7RUdSQmmVbGTAMAGgZHmbVTGID1VKSkJAgCxYs0I9NJpNER0fLtGnTZMaMGZeUHzt2rBQXF8uaNWus+wYPHiz9+vXTIUa9fUREhDz55JPy1FNP6ecLCgokNDRUlixZIuPGjZPKykrdMvTCCy/ocNQUhYWFEhwcrI8dFBTUpGOg5XyyK0OmvbdDugT6yXczbhQfL3pCAQDN+/626ZukvLxctm/frruHrAfw9NSPN2/eXOdr1P6a5RXVCmMpf+zYMcnKyqpVRlVehSdLGdUidOrUKf1e/fv3191Ut956a63WnouVlZXpE1Fzg+MYdXWYdGrrKzlFZbLhQN2tfAAA2MKmUJOXlydVVVW6FaUm9VgFk7qo/Q2Vt9w2VObo0aP69vnnn5dnn31Wt/qoMTUjRoyQM2fO1Pm+c+bM0eHIsqnWJDgOX29PuXdQ9c8kOSXN6OoAAFyAU7T5qy4u5Q9/+IPcfffdMnDgQHn77bfFw8NDD0Kuy8yZM3VTlWVLT2fshqOuWaOmdqedLjG6OgAAdwo1ISEh4uXlJdnZ2bX2q8dhYWF1vkbtb6i85bahMqq7SenTp4/1eT8/P+nRo4ekpdX9V756XvW91dzgWLp2aiPXX9lZ36e1BgDQqqHG19dXt5Js2LChViuKejxkyJA6X6P21yyvrF+/3lq+e/fuOrzULKPGv6hZUJYy6j1VSDl06JC1TEVFhRw/fly6detmy0eAg5l4YXr3im3pUl5Z3SIHAEBTeNv6AjWde/LkyTJo0CBJTEyU+fPn69lNaqq1MmnSJImMjNRjWpTHHntMhg8fLvPmzZPRo0fLsmXLZNu2bbJ48WL9vOpCevzxx+XFF1+UXr166ZDz3HPP6RlRlnVoVCvLr3/9az2NXI2NUUHmlVde0c/de++9TfrgcAw3xXaR0CA/yS4sk//sy5Lb4yOMrhIAwF1CjZqinZubqxfLUwN51dTsdevWWQf6qu4gNUvJYujQoZKcnKwH+D7zzDM6uKxatUri4uKsZZ5++mkdjB566CHJz8+XYcOG6WOqxfosVIjx9vbWa9WcP39ez47auHGjHjAM5+Xt5SljE7rK6xuO6BWGCTUAgFZbp8ZZsU6N41KrCg+bu1EvxvfF9OHSs0s7o6sEAHD1dWoAe4hoHyA3xnbR999jejcAoIkINXAIE5OqB3yv3H5SSiuqjK4OAMAJEWrgENTU7sj2AVJwvkI+3Z1pdHUAAE6IUAOH4OXpYb16N2vWAACaglADh3HvoCjx9vSQ7SfOyoFMrtUFALANoQYOo0ugv9x8dfXSAMlbuB4UAMA2hBo45IDhj3ackuKySqOrAwBwIoQaOJQhPTpJTKc2cq6sUj7ZlWF0dQAAToRQA4fiWWPA8FK6oAAANiDUwOHcMzBafL08Zc+pAtl9Mt/o6gAAnAShBg6nY1tf+Z++Yfo+A4YBAI1FqIFDmnBhwPDHOzOksLTC6OoAAJwAoQYOKSGmg/Tq0k7OV1TJqh2njK4OAMAJEGrgkDw8PGSiZcDw92niJheTBwA0A6EGDuvOAVHi7+Mph7KLJDXtrNHVAQA4OEINHFZwgI/cfk2EtbUGAICGEGrg0CYOrh4wvGZPppwtLje6OgAAB0aogUOLjwqWqyOCpLzSJB+knjS6OgAAB0aogcMPGLasMKzWrGHAMACgPoQaOLw7+kVKW18vOZpXLJuPnja6OgAAB0WogcNr5+ctY/pH6vtcDwoAUB9CDZyCpQvq831ZkltUZnR1AAAOiFADp3B1RLD0i24vFVVmWbE93ejqAAAcEKEGTsOywvB7KWliMrHCMACgNkINnMZt10RIkL+3pJ85L9/+kGd0dQAADoZQA6cR4Osldw2I0veXfn/C6OoAABwMoQZO2QW14WCOZBacN7o6AAAHQqiBU+kVGiiJ3TtKlcksy7cyYBgA8BNCDZy2tUaFmsoqk9HVAQA4CEINnM4tcWHSsa2vZBaUypeHco2uDgDAQRBq4HT8vL3k3oEXBgxvYcAwAKAaoQZOaXxidRfU14dzJf1MidHVAQA4AEINnFJMSFsZ1jNEzGaRZVvTjK4OAMABEGrgAgOGT0oFA4YBwO0RauC0RvYJlc6BfpJ3rkzW7882ujoAAIMRauC0fLw8ZeygaH2fAcMAgCaFmoULF0pMTIz4+/tLUlKSpKSkNFh+xYoVEhsbq8v37dtX1q5dW+t5s9kss2bNkvDwcAkICJCRI0fKkSNH6jxWWVmZ9OvXTzw8PGTnzp38BN3cuMRo8fAQ+e6H03I095zR1QEAOFOoWb58uUyfPl1mz54tqampEh8fL6NGjZKcnJw6y2/atEnGjx8vU6dOlR07dsiYMWP0tnfvXmuZl19+WV5//XVZtGiRbNmyRdq2bauPWVpaesnxnn76aYmIiLC12nBRUR3ayA29u1iv3g0AcGNmGyUmJpofeeQR6+OqqipzRESEec6cOXWWv++++8yjR4+utS8pKcn8q1/9St83mUzmsLAw8yuvvGJ9Pj8/3+zn52d+7733ar1u7dq15tjYWPO+ffvMquo7duxodL0LCgr0a9QtXMsX+7PM3X6/xtzvhf+Yz5dXGl0dAEALsuX726aWmvLyctm+fbvuHrLw9PTUjzdv3lzna9T+muUV1QpjKX/s2DHJysqqVSY4OFh3a9U8ZnZ2tjz44IPy7rvvSps2bWypNlzciN5dJCLYX86WVMi6vVlGVwcAYBCbQk1eXp5UVVVJaGhorf3qsQomdVH7GypvuW2ojBpz88ADD8ivf/1rGTRoUKPqqsbeFBYW1trgmrw8PWTchcX4GDAMAO7LKWY//f3vf5eioiKZOXNmo18zZ84c3eJj2aKjq2fJwDWNTYjW4Wbr8bNyOLvI6OoAABw91ISEhIiXl5fuCqpJPQ4LC6vzNWp/Q+Uttw2V2bhxo+6K8vPzE29vb+nZs6fer1ptJk+eXOf7qgBUUFBg3dLT0235qHAyoUH+MvKq6gHDyVsYMAwA7simUOPr6ysDBw6UDRs2WPeZTCb9eMiQIXW+Ru2vWV5Zv369tXz37t11eKlZRnUVqVlQljJqZtSuXbv0FG61WaaEq5lYf/7zn+t8XxWAgoKCam1wbROTuunbD1JPSkl5pdHVAQC0Mm9bX6Cmc6vWEdVKkpiYKPPnz5fi4mKZMmWKfn7SpEkSGRmpu3+Uxx57TIYPHy7z5s2T0aNHy7Jly2Tbtm2yePFi/bxab+bxxx+XF198UXr16qVDznPPPaenbaup30rXrtXjJSzatWunb6+44gqJiqq+WjOgrgXVtWMbSTtTImt2Zcp9CXQ5AoA7sTnUjB07VnJzc/VieWogr1oIb926ddaBvmlpaXpGlMXQoUMlOTlZnn32WXnmmWd0cFm1apXExcXVWntGBaOHHnpI8vPzZdiwYfqYarE+oLE8PT1kQlJXeemzg7I0JY1QAwBuxkPN6xY3oLq01IBhNb6GrijXpa4DNWTOBqmoMsuaacMkLjLY6CoBAFrp+9spZj8BjRXSzk9uiQvX95cyYBgA3AqhBi5nYlL1GKzVO0/JuTIGDAOAuyDUwOUkde8oV3RuK8XlVbJqxymjqwMAaCWEGrgcNaNuwoXp3aoLyk2GjQGA2yPUwCXdPSBS/Lw95UBmoexIzze6OgCAVkCogUtq38ZXbrsmQt9nhWEAcA+EGrgstWaN8smuDCkoqTC6OgAAOyPUwGUN6NpeYsMCpazSpC+dAABwbYQauPSA4YmDqwcMJ6cwYBgAXB2hBi5tTL8IaePrJT/knJOUY2eMrg4AwI4INXBpgf4+cke/6gHDrDAMAK6NUAOXNyGxugvqs72ZcvpcmdHVAQDYCaEGLq9vVLDERwXri1yu3M6AYQBwVYQauNX0bjVg2GRihWEAcEWEGriF2+MjJNDPW06cLpHvfswzujoAADsg1MAttPH1lrsGROr7rDAMAK6JUAO3YbnI5ef7syW7sNTo6gAAWhihBm6jd1igDOrWQapMZnl/a7rR1QEAtDBCDdzKxMHVA4bfS0nT4QYA4DoINXArt8aFS/s2PpJRUCpfH84xujoAgBZEqIFb8ffxknsGROn7S79PM7o6AIAWRKiB2xl/Yc2aLw/lyKn880ZXBwDQQgg1cDtXdG4nQ6/oJGpIzfIUWmsAwFUQauDWKwwv25ouFVUmo6sDAGgBhBq4pZv7hElIO1/JKSqTDQeyja4OAKAFEGrglny9PeW+QdH6/tItdEEBgCsg1MBtjU/sKh4eIt8eyZMTp4uNrg4AoJkINXBb0R3byPW9Oluv3g0AcG6EGri1iRcGDK/YdlLKKquMrg4AoBkINXBrN8Z2kbAgfzlTXC7Pr97PujUA4MQINXBr3l6e8svruluvB3Xd3I3yq3e3yeYfT4vZzLWhAMCZeJjd5H/uwsJCCQ4OloKCAgkKCjK6OnAg6p/A+v3Z8s7m4/LdD6et+3uHBsrkoTEypn+EtPH1NrSOAOCuCm34/ibUADUczi6SdzYdlw9TT8n5iuoxNkH+3jI2IVomDYnRg4sBAK2HUNPMkwIUnK+QFdvS5V+bT0jamRJ9QtT075tiQ+WBoTFybc9O4qF2AADsilDTzJMCWFSZzPLVoRxZsum4Xs/GomeXdrpr6q7+kdLWj64pALAXQk0zTwpQlx9yzsm/Nh+XD7aflOLy6q6pQH9vuXeg6prqJjEhbTlxANDCCDXNPClAQ4pKK2Tl9pO6a+pYXvVKxKonasSVneWBa7vLdT1DxNOTrikAaO3v7yZN6V64cKHExMSIv7+/JCUlSUpKSoPlV6xYIbGxsbp83759Ze3atZfMPpk1a5aEh4dLQECAjBw5Uo4cOWJ9/vjx4zJ16lTp3r27fv6KK66Q2bNnS3l5eVOqDzRLoL+PTLm2u2yYPlyWTEmQEb07i5pD+OWhXJn8fyky8q9fy5LvjunwAwBoPTaHmuXLl8v06dN1qEhNTZX4+HgZNWqU5OTk1Fl+06ZNMn78eB1KduzYIWPGjNHb3r17rWVefvllef3112XRokWyZcsWadu2rT5maWmpfv7gwYNiMpnkzTfflH379slrr72myz7zzDPN+exAs6jWmBG9u8iSKYny5VMjZMq1MRLo5y1H84rl+U/2y5A5G+X51fvkaO45zjQAtAKbp3SrlpmEhARZsGCBfqzCRnR0tEybNk1mzJhxSfmxY8dKcXGxrFmzxrpv8ODB0q9fPx1M1NtHRETIk08+KU899ZR+XjUxhYaGypIlS2TcuHF11uOVV16RN954Q44ePdqoetP9hNZwrqxSPkw9qaeF/5j700Uyr1ddU0O7yYgru9A1BQCO0P2kunu2b9+uu4esB/D01I83b95c52vU/prlFdUKYyl/7NgxycrKqlVGVV6Fp/qOqagP17Fjx3qfLysr0yei5gbYWzs/b72ezRfTh8u7UxNl5FVd9Hibbw7nyi+WbJMb530l//zvMSmkawoAWpxNoSYvL0+qqqp0K0pN6rEKJnVR+xsqb7m15Zg//PCD/P3vf5df/epX9dZ1zpw5OhxZNtWaBLQWtYbNdb06yz8mJ8jXT90gvxzWXc+UOn66RP60Zr8M/ssGeXbVHjmSXcQPBQDc9dpPp06dkltuuUXuvfdeefDBB+stN3PmTN2aY9nS09NbtZ6ARddObeTZ2/rIlmdukj/fGSdXhraTkvIq+ff3afKz176R+/+xRV+mQa2JAwBoOptWDQsJCREvLy/Jzs6utV89DgsLq/M1an9D5S23ap+a/VSzjBp3U1NGRobccMMNMnToUFm8eHGDdfXz89Mb4CjU9aMmJnWTCYld9QUz1YJ+XxzIlv/+kKe36I4B8vPB3WTsoK4S3MbH6OoCgGu31Pj6+srAgQNlw4YN1n1qoLB6PGTIkDpfo/bXLK+sX7/eWl5N01bBpmYZNf5FzYKqeUzVQjNixAj9/m+//bYeywM4a9fU0J4hsnjSIPn6dzfIr4b3kOAAH0k/c17+svagDJ6zQWZ+uEcOZdE1BQB2nf2kpnRPnjxZT69OTEyU+fPny/vvv6+nXatxMJMmTZLIyEg9psUypXv48OHy0ksvyejRo2XZsmXyl7/8RU8Hj4uL02Xmzp2rn3/nnXd0yHnuuedk9+7dsn//fr22jSXQdOvWTZdRrUUW9bUQXYzZT3Bk58ur5OOdp3TrzcEaYWZwj476WlMjrwoVby+CPAD3U2jD7CebL1qjpmjn5ubqxfLUQF7VRbRu3TrrQN+0tLRarSiqqyg5OVmeffZZva5Mr169ZNWqVdZAozz99NN62vdDDz0k+fn5MmzYMH1MFWgsLTtqcLDaoqKiatXHxkwGOKQAXy8Zl9hVXw085dgZeWfzcfnPvmz5/ugZvUW2D5D7B3eTcQnR0qGtr9HVBQDXaKlxVrTUwNlk5J+Xf39/Qt5LSZOzJdWrE/t5e8od/SL0xTSvjgg2uooAYHdc+6mZJwVwJKUVVfLJrgzdNbUv46f1lhJiOsgDQ7vLzVeHig9dUwBcFKGmmScFcESqUXX7ibM63KzbmyWVF6aAhwX5y/2Du8r4xK7SqR0z/gC4FkJNM08K4OiyCkolecsJSU5Jk7xz1Rd29fXylNvjI/TA4r5RdE0BcA2EmmaeFMBZlFVWydo9mbLku+Oy62SBdf+Aru31uJtb48LF15tZUwCcF6GmmScFcEY70s7qC2l+uidTKqqqu6a6BPrpBf/GJ0VLl8Dq2YQA4EwINc08KYAzyylSXVNpsnRLmuQWlel9Pl4eMrpvuG696d+1g9FVBIBGI9Q086QArqC80iSf7c3UrTepafnW/fHR7eWBod3kf/qGi5/3TwtZAoAjItQ086QArmb3yXw9a2rNrkwprzLpfSHtfPV1qCYO7iahQXRNAXBMhJpmnhTAVeWdK5NlKWn6CuFZhaV6n7enh9zaN1y33gzo2kFfmwoAHAWhppknBXB1FVUm+c++LN01tfX4Wev+uMggmTwkRk8N9/ehawqA8Qg1zTwpgDvZe6pA/rX5uKzamaHH4Sgd2/rK+MRofb2p8OAAo6sIwI0V2vD9zbWfAGhnistl2dY0+ffmE5JRUN015eXpIaOuDtWXY1CXZaBrCkBrI9Q086QA7qyyyiRfHMjWA4vVFcItrgoP0uNu7ugXSdcUgFZDqGnmSQFQ7UBmoe6a+mjHKSmtqO6aat/GR8YmRMvPB3eTqA5tOFUA7IpQ08yTAqC2/JJyeX9buvxr8wk5efa83ufpIfKzPqF6Qb8hPTrRNQXALgg1zTwpAOpWZTLLhgPZ8s7m4/LdD6et+3uHBupwM6Z/hLTx9eb0AWgxhJpmnhQAl3c4u0hPCf8w9ZScr6jS+4L8vXXX1KQhMRLdka4pAM1HqGnmSQHQeAXnK2TFha6ptDMlep9av++m2C669WZYzxC6pgA0GaGmmScFgO1MJrN8dThH3v7uuHx7JM+6v2eXdjJ5SDe5a0CUtPWjawqAbQg1zTwpAJrnx9xz8q9Nx2Xl9pNSXF7dNRXo5y33DIrSKxbHhLTlFANoFEJNM08KgJZRVFqhg43qmjqWV2zdf0Pvzrpr6vpencVTTaMCgHoQapp5UgC0fNfUN0dy9cDiLw/lWvd3D2krk4Z0k3sGRkmgvw+nHcAlCDV1INQAjkG12KgF/VZuOylFZZV6X1tfLx1sJg2NkSs6tzO6igAcCKGmmScFgP2dK6uUj1JP6ssx/Jj7U9fUdb1C5IGhMXJD7y50TQEQQk0dCDWAYzKbzfLfH/J019SGgzliNlfv79apjb4Uw72DoiU4gK4pwF0VcpXu5p0UAMZIO10i735/XJZvTZfC0uquqTa+XnJn/0jdetMrNJAfDeBmCgk1zTspAIxVUl6pL6KpWm8OZ5+z7r+2Zyc9Jfymq0LFi1lTgFsoJNQ076QAcJyuqc1HT+tws35/tpgudE1FdQjQXVPqkgzt2/gaXU0AdkSoaeZJAeB4Tp5VXVMndNdUfkmF3ufv46m7ptSaN7Fh/LsGXBGhppknBYDjOl9eJat3nZIlm07IgcxC6/6+kcESFuwvHdr4SIc2vroFp72+71PjfvWtn7eXoZ8BQOMRapp5UgA4R9fU1uNnZcmmY/KffdlSZembagQ1+Lh9QHXY6dDWR9oH1A49ev9FYUjNwGIcD+DY399cXQ6AU/Lw8JDE7h31lllwXlJP5Ev++XLdNXW2uFzyz1dIfkm5nFWPS8qloKRC71Php6S8Sm8ZBaU2vJ9IkL9P7dBjCUYXwpEKPvq+NRz5SDs/b65SDrQSQg0ApxceHCCjrwlo1OUa1CrGBReCjtpUCLKEH+utNRBVP19UWqnXzyk4X6G3E6dLGl03Hy8PCba2BNXdClSz1cjSKuTvQxcZYCtCDQC3oS6eqQKD2rp2atPo11VUmXSYUUFHtwTVaP2pDkc1n/vptqzSJBVVZsk7V6Y3WwT4eOnwE3whBNVs/bGOGVItQ6r77ELrUJC/t3h7eTbhzACugVADAJfh4+UpIe389GaL0oqq6tBTrFp/aoeemq1DtfZf6CI7X1El5wts6yJTVLDp0LZG608dA6Ut+y2P6SKDqyDUAICdqC4k1TWmNlsGQKsusvziC0HH0hVWXB2CVItRXa1DqotMUSsxq+2EDfX09vSwhp2aYUiFo5/GCalWo9pjhugig0uEmoULF8orr7wiWVlZEh8fL3//+98lMTGx3vIrVqyQ5557To4fPy69evWSuXPnyv/8z//U+kc8e/ZseeuttyQ/P1+uvfZaeeONN3RZizNnzsi0adPkk08+EU9PT7n77rvlb3/7m7RrxxV9AbjWAGg1IFlttnSRVV7oIjtbXyuQNRxd6Dq7EI5KK0xSaVJdZOV6s4VaJ6jurrDaM8qqB1FfCEYBPnSRwXFCzfLly2X69OmyaNEiSUpKkvnz58uoUaPk0KFD0qVLl0vKb9q0ScaPHy9z5syR2267TZKTk2XMmDGSmpoqcXFxuszLL78sr7/+urzzzjvSvXt3HYDUMffv3y/+/v66zMSJEyUzM1PWr18vFRUVMmXKFHnooYf08QDA3amxNJ3a+enN1i4yS/D5qWvM0h1mCUc/DZy2BCfVRaYCUWZBqd5sEai6yOroGgvw9RIvDw899kndent5iKeHh6hhQtW3HrpVyfL8peU86imnWqM8ax3H88Lrah5H7bdslnJ6v7WcWMup8AnH42FWzSQ2UEEmISFBFixYoB+bTCaJjo7WrSgzZsy4pPzYsWOluLhY1qxZY903ePBg6devnw5G6u0jIiLkySeflKeeeko/r+aih4aGypIlS2TcuHFy4MAB6dOnj2zdulUGDRqky6xbt0639pw8eVK//nJYpwYAWkbNLjI1Vqi+gdI1xw7V7CJzBerSY7XCj2WrNyRVl/e6EK4aClM1A5neZwlpFwJe48p51FGn2nVofN0t99XrPX8KeBe9n7pVa0DZGqwNW6emvLxctm/fLjNnzrTuU11BI0eOlM2bN9f5GrVftezUpFphVq1ape8fO3ZMd2OpY1ioyqvwpF6rQo26bd++vTXQKKq8eu8tW7bInXfeecn7lpWV6a3mSQEAtHAXmTSti6xAhSHLuKELoUe1/JjMZt0KVKVuq6pvTZbHpp82S7nKGvdNJtHl9L6LylmPaarveHJJuYaop01VqoxN7QIu77peIfLu1CTD3t+mUJOXlydVVVW6FaUm9fjgwYN1vkYFlrrKq/2W5y37GipzcdeWt7e3dOzY0VrmYqq764UXXrDl4wEAHLCLzCiXhJ8LAcsammqEqUqTJZBVhyN1v/KiYGV5rTWo1VlO6izXYCizlhOpMpkuPFd//S85nqpDVY0AqALbRZ/TcluzXK1geeF5oy9B4rKzn1RrUs0WItVSo7rJAABoDNUV4ykewjqIzsOmVZpCQkLEy8tLsrOza+1Xj8PCwup8jdrfUHnL7eXK5OTk1Hq+srJSz4iq7339/Px031vNDQAAuC6bQo2vr68MHDhQNmzYYN2nBgqrx0OGDKnzNWp/zfKKmsFkKa9mO6lgUrOMalVRY2UsZdStmuqtxvNYbNy4Ub+3GnsDAABgc/eT6tKZPHmyHrSr1qZRU7rV7CY1xVqZNGmSREZG6jEtymOPPSbDhw+XefPmyejRo2XZsmWybds2Wbx4sXXA2eOPPy4vvviiXpfGMqVbzWhSU7+Vq666Sm655RZ58MEH9YwpNaX70Ucf1YOIGzPzCQAAuD6bQ42aop2bmyuzZs3Sg3TV1Gw1vdoy0DctLU3PSrIYOnSoXkvm2WeflWeeeUYHFzXzybJGjfL000/rYKTWnVEtMsOGDdPHtKxRoyxdulQHmZtuusm6+J5a2wYAAKBJ69Q4K9apAQDAtb+/uZwrAABwCYQaAADgEgg1AADAJRBqAACASyDUAAAAl0CoAQAALoFQAwAAXAKhBgAAuARCDQAAcM/LJDgry8LJamVCAADgHCzf2425AILbhJqioiJ9Gx0dbXRVAABAE77H1eUSGuI2134ymUySkZEhgYGB+srgLZ0iVVhKT0+/7HUp4Hj4+Tk/fobOj5+h8yu003ehiikq0ERERNS6YLZbt9SoExEVFWXX91A/REKN8+Ln5/z4GTo/fobOL8gO34WXa6GxYKAwAABwCYQaAADgEgg1LcDPz09mz56tb+F8+Pk5P36Gzo+fofPzc4DvQrcZKAwAAFwbLTUAAMAlEGoAAIBLINQAAACXQKgBAAAugVDTRHPmzJGEhAS9QnGXLl1kzJgxcujQoZb96cCu3njjDbnmmmusC0UNGTJEPvvsM866E3vppZf0iuGPP/640VVBIz3//PP6Z1Zzi42N5fw5kVOnTsn9998vnTp1koCAAOnbt69s27bNkLoQapro66+/lkceeUS+//57Wb9+vVRUVMjNN98sxcXFLfsTgt2oFabVl+D27dv1P8Abb7xR7rjjDtm3bx9n3Qlt3bpV3nzzTR1U4VyuvvpqyczMtG7//e9/ja4SGuns2bNy7bXXio+Pj/6jcP/+/TJv3jzp0KGDGMFtLpPQ0tatW1fr8ZIlS3SLjfqCvP766w2rFxrv9ttvr/X4z3/+s269UUFV/ScL53Hu3DmZOHGivPXWW/Liiy8aXR3YyNvbW8LCwjhvTmju3Ln6ek9vv/22dV/37t0Nqw8tNS2koKBA33bs2LGlDolWVFVVJcuWLdMtbaobCs5FtZqOHj1aRo4caXRV0ARHjhzRFyvs0aOHDqdpaWmcRyexevVqGTRokNx77736D/v+/fvrPy6MQktNC10BXPXhqya4uLi4ljgkWsmePXt0iCktLZV27drJRx99JH369OH8OxEVRlNTU3X3E5xPUlKSbunu3bu37np64YUX5LrrrpO9e/fqMYtwbEePHtUt3NOnT5dnnnlG/zv87W9/K76+vjJ58uRWrw8rCreAhx9+WPclqn5ge18JHC2rvLxc/1WoWtpWrlwp//jHP/R4KYKNc0hPT9d/JapxbZaxNCNGjJB+/frJ/Pnzja4emiA/P1+6desmf/3rX2Xq1KmcQwfn6+ur/w1u2rTJuk+FGhVuNm/e3Or1ofupmR599FFZs2aNfPnllwQaJ/0H2bNnTxk4cKCe0RYfHy9/+9vfjK4WGkmNYcvJyZEBAwbocRlqU6H09ddf1/dVtyKcS/v27eXKK6+UH374weiqoBHCw8Mv+SPwqquuMqwLke6nJlKXzJo2bZrurvjqq68MHRiFlu1KLCsr45Q6iZtuukl3IdY0ZcoUPSX497//vXh5eRlWNzR90PePP/4oP//5zzmFTuDaa6+9ZDmTw4cP69Y2IxBqmjEwMTk5WT7++GPd75uVlaX3BwcH63n6cHwzZ86UW2+9Vbp27SpFRUX656kC6n/+8x+jq4ZGUv/2Lh7H1rZtW71eBuPbnMNTTz2lZyKqL8GMjAx9lWcVRsePH2901dAITzzxhAwdOlT+8pe/yH333ScpKSmyePFivRmBUNNEamCUpf++JjWt7YEHHmj+TwZ2p7otJk2apAcnqjCqxmSoQPOzn/2Msw+0kpMnT+oAc/r0aencubMMGzZML6ug7sPxJSQk6B4L9UfiH//4R91rocazqVlsRmCgMAAAcAkMFAYAAC6BUAMAAFwCoQYAALgEQg0AAHAJhBoAAOASCDUAAMAlEGoAAIBLINQAAACXQKgB4PTL7I8ZM8boagBwAIQaAE5t586d0q9fP6OrAcABEGoAOLVdu3YRagBohBoATn0xxLy8PGuoyc/P11d8VhdFzMrKMrp6AFoZoQaAU3c9tW/fXmJiYmTPnj36isGRkZHy5ZdfSlhYmNHVA9DKCDUAnDrUxMfHS3JysgwfPlyefvppWbRokfj4+BhdNQAG8DCbzWYj3hgAmuuee+6RjRs36vuffvqpDBkyhJMKuDFaagA4dUvNXXfdJaWlpXo8DQD3RksNAKdUVFQkwcHBsn37dtmxY4c88cQTsmnTJrn66quNrhoAg3gb9cYA0Nyp3F5eXtKnTx/p37+/7N27V898SklJkZCQEE4u4IbofgLgtF1PsbGx4ufnpx+/8sor0rt3b90dVV5ebnT1ABiA7icAAOASaKkBAAAugVADAABcAqEGAAC4BEINAABwCYQaAADgEgg1AADAJRBqAACASyDUAAAAl0CoAQAALoFQAwAAXAKhBgAAuARCDQAAEFfw/wFxM0KozsshLgAAAABJRU5ErkJggg==",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "k_seq = [k for k in range(2, 7)]\n",
    "y_seq = [sqrt2_approx_error(k) for k in k_seq]\n",
    "ax.set_title(\"$|\\\\sqrt{2}-[1;2,\\\\cdots,2]|$ for the $k$-th cut-off\")\n",
    "ax.set_xticks(k_seq)\n",
    "ax.set_xlabel(\"$k$\")\n",
    "ax.plot(k_seq, y_seq)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6f250585-97cf-4fee-91d7-7ce6628cf6fc",
   "metadata": {},
   "source": [
    "error estimate with signs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 112,
   "id": "9254b434-c3f6-4bed-8a2b-347ed9be013c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAABUAAAArCAYAAACXZ8NLAAAACXBIWXMAAA7EAAAOxAGVKw4bAAACUklEQVRIDe2X7VHbQBCGZYYCHKgA0wFJOkg6MCUESmDyy/5LCYQKGKeD0AEfHSQlMO7AeR6h1ejOkix5wq+wM+u929t9b7X3tZ5sNpuiScvl8oz+Cv5Ie90cs41uhris9FOk/Wv095WuOLSBwsFb+AX+BGu4RZXdFTJA9Z1j+At5Dv/U6cAfOutKqfGdug66QH+BrUBBEeH3UJSg0Rkgn7ExJXVaDCj3Kz8/V3b1ATCqD83xRtQ3oR8bafiVEsAvNK7hS9o/YnBUpOEEgDtEwM+wKXmEa9oXVCC5YAIX7QmZrr6D+xJgbiMXa0V7Ks6onOJ0JuuYUXy+KRkHiv2THBEJ0EZjc+pn3gOa701PoVQehDbQ49fx4giZO19VY7VgAhfKXLqtSvtJXCgovEQk86KRq/sH9lzXe5C24+dw0IxGcqHUoGHxL+So1R864Tvo0EwNt/vPczpZLBbpGz08dZ2W7yeqMzV7D7zJPm27pJMIuT+9W+uShrZ9H7kodxL7suMl3cdsuZt8HN0Knuf66Pd+PtFYkPnY5fQNRTP6ZLwXFMtT+GviMaCzC/QBjDkR+06ZyyDrp7ogC2XInScKMB/EqEd9TY3eScoCN4CaclekBc6+nPGaGqGvaVlHNYGa7SGgRrmGjdBtNIN/M1lETzel3s/H0dX3D0Ve45sSJzphTJnQrkj93KQqAcRcGrULZyq2qBMUZ52mbZGgs3IR3NJoi/pA/Sz/tZjDNnLS1qPaCVqhuPJ1MRvITGSu3VZGvEW9C6U1jha5HskX+xX1Xih/ATR5/lS4K5uLAAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle \\frac{13}{8}$"
      ],
      "text/plain": [
       "13/8"
      ]
     },
     "execution_count": 112,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "c_seq = [1, 1, 1, 1, 1, 1]\n",
    "cont_frac = list_to_frac(c_seq)\n",
    "cont_frac"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 113,
   "id": "fd3e4c56-46a2-497c-bde2-565ecd832046",
   "metadata": {},
   "outputs": [],
   "source": [
    "def error_from_cutoff(k):\n",
    "    \"\"\"Difference between the cut-off at the k-th term (inclusive).\"\"\"\n",
    "    cut_off = list_to_frac(c_seq[0:k+1])\n",
    "    return cont_frac - cut_off"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 114,
   "id": "2edfb177-5f4e-4f13-a607-056233d6eb9b",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[<matplotlib.lines.Line2D at 0x122a4fcb0>]"
      ]
     },
     "execution_count": 114,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig2, ax2 = plt.subplots()\n",
    "k_seq = [k for k in range(1, len(c_seq))]\n",
    "y_seq = [error_from_cutoff(k) for k in k_seq]\n",
    "ax2.set_title(\"difference with the $k$-th cut-off\")\n",
    "ax2.set_xticks(k_seq)\n",
    "ax2.set_xlabel(\"$k$\")\n",
    "sec_xax = ax2.secondary_xaxis(0, transform=ax2.transData)\n",
    "sec_xax.set_xticks([])\n",
    "ax2.plot(k_seq, y_seq)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "id": "cced615d-b478-4799-8976-f64d4c3466ec",
   "metadata": {},
   "outputs": [],
   "source": [
    "def coeff_for_cont_frac(rat: sy.Rational):\n",
    "    \"\"\"Returns the coefficients for continued fraction.\n",
    "\n",
    "    Parameters\n",
    "    ----------\n",
    "    rat : sympy.Rational\n",
    "\n",
    "    Returns\n",
    "    -------\n",
    "    list\n",
    "        list of integers c0, c1, ..., cM satisfying\n",
    "        rat = [c0; c1, ..., cM].\n",
    "    \"\"\"\n",
    "    rat = sy.simplify(rat)\n",
    "    num, denom = rat.as_numer_denom()\n",
    "    if denom == 1:\n",
    "        return [num]\n",
    "    c0 = num // denom\n",
    "    next_rat = 1 / (rat - c0)\n",
    "    return [c0] + coeff_for_cont_frac(next_rat)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 116,
   "id": "67b0991b-0a61-47ce-89b9-75ffd6606ba3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAACwAAAAVCAYAAAA98QxkAAAACXBIWXMAAA7EAAAOxAGVKw4bAAABvElEQVRIDc2X23HCMBBFRSYFMJQAHeTRAekgboF0kHzi36QDakg6gBZCB9BBHiXkXMfWEGPZu9iZQTOyZGm992i9ksej5XL5GEJ4oKpkeZ5vf7vncYXnD98I4GfQ1kxszgOxmQK+gvOyeToEDKbMPZXzN7Sfumd80Dfg1WkELp2saO9K4EBfK3zXGHWQt4EfBcWlc1EB1VrBVXldTOFc0f6mvhYDw1zcOingOTw7IMc1LkV2XEamNnXSrVsnBSywPWCKaFOpL6TJxjLm1knlcJZQu9I4Cxlk4+HHrZOK8BEvzgU7pVYnx5HNEANdOmZgYLTZ3nD4MgRYi49WHRMwkCsElNOpV9iib5+y6HQC42SB5IQ2nsl2BLulVacVGCf3SM5oY2TpT1XtKN2WHp0kME60yW5p65tMi9BnuijM9zrirDqVXupYUwSV/BscKn8Py5yxYuOVsF+0W+r1oZGlzzMmnUNfqQivMZIz5W+9xo8Jgurvqfr6nRJpkw7+Y0lFeBYtOjqAKseVJu6iZ70PpSLs9aNcj5H3Puyx7w0MqFLhwyPax7Y3MOILoP/76xfXWP0iVTl4zv90E6izH9Y+w76fIsLcAAAAAElFTkSuQmCC",
      "text/latex": [
       "$\\displaystyle \\left[ 2, \\  2\\right]$"
      ],
      "text/plain": [
       "[2, 2]"
      ]
     },
     "execution_count": 116,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "coeff_for_cont_frac(sy.Rational(5, 2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1aea9b97-11f2-4ae8-b151-cb87e548ebc6",
   "metadata": {},
   "source": [
    "In Shor's algorithm, we want to estimate $b = \\frac{c}{s}$ from $a = \\frac{x}{Q}$ using $s < N$, $N^2 < Q$, and $|a-b| \\le \\frac{1}{Q} < \\frac{1}{N^2}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 117,
   "id": "0962bee0-1b82-424b-b791-701642108779",
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 21  # 3 * 7\n",
    "Q = 2**9  # 512 > 21 * 21 = 441"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37b098d9-6933-45ed-b7fc-ed2d70ec5105",
   "metadata": {},
   "source": [
    "if we choose $z = 2$ (coprime to $N = 21$), we actually know that the period is $s = 6$ from $2^6 = 64 = 3 \\times 21 + 1$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 118,
   "id": "5659547f-44df-4407-aa56-119fdb8be30e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left[ 2, \\  4, \\  8, \\  16, \\  11, \\  1, \\  2, \\  4, \\  8, \\  16, \\  11, \\  1, \\  2, \\  4, \\  8, \\  16, \\  11, \\  1, \\  2, \\  4\\right]$"
      ],
      "text/plain": [
       "[2, 4, 8, 16, 11, 1, 2, 4, 8, 16, 11, 1, 2, 4, 8, 16, 11, 1, 2, 4]"
      ]
     },
     "execution_count": 118,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "[2**a % N for a in range(1, N)]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c012d29b-371a-40c0-96ec-e8e283d0728e",
   "metadata": {},
   "source": [
    "we can get $x = 85$, that makes $a$ closer to $\\frac{1}{6}$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 119,
   "id": "4286fdea-6bd5-49d0-9dcb-c5399c642c64",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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",
      "text/latex": [
       "$\\displaystyle \\left( -0.000651041666666667, \\  0.00226757369614512\\right)$"
      ],
      "text/plain": [
       "(-0.000651041666666667, 0.00226757369614512)"
      ]
     },
     "execution_count": 119,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "x = 85\n",
    "a = sy.Rational(x, Q)\n",
    "sy.N(a - sy.Rational(1, 6)), sy.N(sy.Rational(1, N**2))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "748452fe-8e46-4eb6-b0b9-ba9e8bfaae0a",
   "metadata": {},
   "source": [
    "To work out $s$ from $a$, we get the continued fraction coefficients of $a$, and find a candidate of $b$ that has small denominator ($s < N$) but close enough to $a$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 120,
   "id": "6749ecc9-07ca-4201-aee6-18c7aaf5150e",
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "cutoff at k=0 gives b=0 with denominator=1\n",
      "👎 the error 0.166015625000000 is too big against 1/N**2 = 0.00226757369614512\n",
      "cutoff at k=1 gives b=1/6 with denominator=6\n",
      "👍 this is a good candidate!\n",
      "cutoff at k=2 gives b=42/253 with denominator=253\n",
      "👎 the denominator is too big against N=21.\n",
      "cutoff at k=3 gives b=85/512 with denominator=512\n",
      "👎 the denominator is too big against N=21.\n"
     ]
    }
   ],
   "source": [
    "a_coeff = coeff_for_cont_frac(a)\n",
    "\n",
    "\n",
    "def cutoff_denom(k):\n",
    "    cutoff_rat = sy.simplify(list_to_frac(a_coeff[:k+1]))\n",
    "    _, denom = cutoff_rat.as_numer_denom()\n",
    "    return denom\n",
    "\n",
    "\n",
    "for k in range(len(a_coeff)):\n",
    "    b_cand = sy.simplify(list_to_frac(a_coeff[:k+1]))\n",
    "    b_cand_denom = cutoff_denom(k)\n",
    "    print(f\"cutoff at k={k} gives b={b_cand} with denominator={b_cand_denom}\")\n",
    "    error = sy.N(a - b_cand)\n",
    "    if b_cand_denom > N:\n",
    "        print(f\"👎 the denominator is too big against N={N}.\")\n",
    "    elif error > sy.N(1/N**2):\n",
    "        print(f\"👎 the error {error} is too big against 1/N**2 = {sy.N(1/N**2)}\")\n",
    "    else:\n",
    "        print(\"👍 this is a good candidate!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cb22ce47-7276-4676-bd4f-525aef2ee0aa",
   "metadata": {},
   "source": [
    "## Lecture 17"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c45bf5b0-910b-484e-a8d5-96a4c0fd97c6",
   "metadata": {},
   "source": [
    "Recursive relation for the Fourier transform\n",
    "$$\n",
    "F_Q = \\frac{1}{\\sqrt{2}} \\begin{bmatrix}\n",
    "\tF_{Q/2} & B_{Q/2} F_{Q/2} \\\\\n",
    "\tF_{Q/2} & - B_{Q/2} F_{Q/2}\n",
    "\\end{bmatrix} S_k\n",
    "$$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "54790a46-1d1c-4196-ab09-b2d7fce7a2b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sympy as sy\n",
    "from sympy.physics.quantum.dagger import Dagger"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "615d5882-9e39-411a-932c-8cf5d58463fb",
   "metadata": {},
   "outputs": [],
   "source": [
    "k = 4\n",
    "Q = 2**k\n",
    "Q2 = 2**(k-1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "ce821cfc-84a7-4e5d-b77d-3f15ddc0eaf3",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from Lecture 16\n",
    "def bin_seq_for_int(k, x: int):\n",
    "    \"\"\"Returns binary sequence representation of the integer x.\n",
    "\n",
    "    Parameters\n",
    "    ==========\n",
    "        x: int\n",
    "\n",
    "    Returns\n",
    "    =======\n",
    "        tuple of int\n",
    "          (x0, x1, ..., x(k-1)) with xi = 0, 1 and\n",
    "          x = x0 + 2 * x1 + ... + 2**(k-1) * x(k-1)\n",
    "    \"\"\"\n",
    "    # for i=0,..., k-1, do bit shift by i and bitwise AND with 1\n",
    "    return tuple((x >> i) & 1 for i in range(k))\n",
    "\n",
    "\n",
    "def int_from_bin_seq(k, x_seq):\n",
    "    \"\"\"Returns integer for the binary sequence of length k.\"\"\"\n",
    "    return sum(2**n * x_seq[n] for n in range(k))\n",
    "\n",
    "\n",
    "# convert k-bit integer to another with reversed bit sequence\n",
    "def converted_index(k, x):\n",
    "    \"\"\"Returns j = 2^{k-1} x_0 + 2^{k-2} x_1 + ... + x_{k-1}.\"\"\"\n",
    "    x_seq = bin_seq_for_int(k, x)\n",
    "    j = sum(2**(k-1-n) * x_seq[n] for n in range(k))\n",
    "    return j"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "93e2b5db-33e7-405f-ba45-c6d8ebb78418",
   "metadata": {},
   "outputs": [],
   "source": [
    "# from Lecture 16\n",
    "def Fourier(n, j, l):\n",
    "    \"\"\"Returns the coefficient of Fourier transform matrix of size n.\"\"\"\n",
    "    return sy.exp(2 * sy.I * sy.pi * j * l / n) / sy.sqrt(n)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "8d7e3833-b59d-448c-b6a9-8f38e002e4d3",
   "metadata": {},
   "outputs": [],
   "source": [
    "F_Q = sy.Matrix([[Fourier(Q, j, converted_index(k,l))\n",
    "                           for l in range(Q)] for j in range(Q)])\n",
    "F_Q2 = sy.Matrix([[Fourier(Q2, j, converted_index(k-1,l))\n",
    "                           for l in range(Q2)] for j in range(Q2)])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "9c94eb03-aff9-4bd9-bbad-968045756a2c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$\\displaystyle \\left[\\begin{array}{cccccccccccccccc}\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4}\\end{array}\\right]$"
      ],
      "text/plain": [
       "Matrix([\n",
       "[1/4,  1/4,  1/4,  1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4],\n",
       "[1/4, -1/4,  I/4, -I/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/8)/4, exp(-7*I*pi/8)/4,  exp(5*I*pi/8)/4, exp(-3*I*pi/8)/4,  exp(3*I*pi/8)/4, exp(-5*I*pi/8)/4,  exp(7*I*pi/8)/4,   exp(-I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,              I/4,              I/4,             -I/4,             -I/4,    exp(I*pi/4)/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/4)/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,   exp(-I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/8)/4, exp(-5*I*pi/8)/4,   exp(-I*pi/8)/4,  exp(7*I*pi/8)/4, exp(-7*I*pi/8)/4,    exp(I*pi/8)/4,  exp(5*I*pi/8)/4, exp(-3*I*pi/8)/4],\n",
       "[1/4,  1/4,  1/4,  1/4,             -1/4,             -1/4,             -1/4,             -1/4,              I/4,              I/4,              I/4,              I/4,             -I/4,             -I/4,             -I/4,             -I/4],\n",
       "[1/4, -1/4,  I/4, -I/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4,  exp(5*I*pi/8)/4, exp(-3*I*pi/8)/4, exp(-7*I*pi/8)/4,    exp(I*pi/8)/4,   exp(-I*pi/8)/4,  exp(7*I*pi/8)/4,  exp(3*I*pi/8)/4, exp(-5*I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,             -I/4,             -I/4,              I/4,              I/4,  exp(3*I*pi/4)/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/4)/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4, exp(-3*I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,  exp(7*I*pi/8)/4,   exp(-I*pi/8)/4,  exp(3*I*pi/8)/4, exp(-5*I*pi/8)/4,  exp(5*I*pi/8)/4, exp(-3*I*pi/8)/4,    exp(I*pi/8)/4, exp(-7*I*pi/8)/4],\n",
       "[1/4,  1/4,  1/4,  1/4,              1/4,              1/4,              1/4,              1/4,             -1/4,             -1/4,             -1/4,             -1/4,             -1/4,             -1/4,             -1/4,             -1/4],\n",
       "[1/4, -1/4,  I/4, -I/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4, exp(-7*I*pi/8)/4,    exp(I*pi/8)/4, exp(-3*I*pi/8)/4,  exp(5*I*pi/8)/4, exp(-5*I*pi/8)/4,  exp(3*I*pi/8)/4,   exp(-I*pi/8)/4,  exp(7*I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,              I/4,              I/4,             -I/4,             -I/4, exp(-3*I*pi/4)/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,    exp(I*pi/4)/4,   exp(-I*pi/4)/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4,  exp(3*I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4, exp(-5*I*pi/8)/4,  exp(3*I*pi/8)/4,  exp(7*I*pi/8)/4,   exp(-I*pi/8)/4,    exp(I*pi/8)/4, exp(-7*I*pi/8)/4, exp(-3*I*pi/8)/4,  exp(5*I*pi/8)/4],\n",
       "[1/4,  1/4,  1/4,  1/4,             -1/4,             -1/4,             -1/4,             -1/4,             -I/4,             -I/4,             -I/4,             -I/4,              I/4,              I/4,              I/4,              I/4],\n",
       "[1/4, -1/4,  I/4, -I/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4, exp(-3*I*pi/8)/4,  exp(5*I*pi/8)/4,    exp(I*pi/8)/4, exp(-7*I*pi/8)/4,  exp(7*I*pi/8)/4,   exp(-I*pi/8)/4, exp(-5*I*pi/8)/4,  exp(3*I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,             -I/4,             -I/4,              I/4,              I/4,   exp(-I*pi/4)/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4,  exp(3*I*pi/4)/4, exp(-3*I*pi/4)/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,    exp(I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,   exp(-I*pi/8)/4,  exp(7*I*pi/8)/4, exp(-5*I*pi/8)/4,  exp(3*I*pi/8)/4, exp(-3*I*pi/8)/4,  exp(5*I*pi/8)/4, exp(-7*I*pi/8)/4,    exp(I*pi/8)/4]])"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "F_Q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "013bee23-219d-4838-9055-030915f7c828",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}\\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2} i}{4} & - \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} e^{\\frac{i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{- \\frac{i \\pi}{4}}}{4}\\\\\\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} i}{4} & - \\frac{\\sqrt{2} i}{4} & - \\frac{\\sqrt{2} i}{4}\\\\\\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{- \\frac{i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{\\frac{i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{- \\frac{3 i \\pi}{4}}}{4}\\\\\\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4}\\\\\\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2} i}{4} & - \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{\\frac{i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{- \\frac{i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{\\frac{3 i \\pi}{4}}}{4}\\\\\\frac{\\sqrt{2}}{4} & \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2} i}{4} & - \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} i}{4}\\\\\\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2}}{4} & - \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} i}{4} & \\frac{\\sqrt{2} e^{- \\frac{i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{\\sqrt{2} e^{\\frac{i \\pi}{4}}}{4}\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "Matrix([\n",
       "[sqrt(2)/4,  sqrt(2)/4,    sqrt(2)/4,    sqrt(2)/4,                sqrt(2)/4,                sqrt(2)/4,                sqrt(2)/4,                sqrt(2)/4],\n",
       "[sqrt(2)/4, -sqrt(2)/4,  sqrt(2)*I/4, -sqrt(2)*I/4,    sqrt(2)*exp(I*pi/4)/4, sqrt(2)*exp(-3*I*pi/4)/4,  sqrt(2)*exp(3*I*pi/4)/4,   sqrt(2)*exp(-I*pi/4)/4],\n",
       "[sqrt(2)/4,  sqrt(2)/4,   -sqrt(2)/4,   -sqrt(2)/4,              sqrt(2)*I/4,              sqrt(2)*I/4,             -sqrt(2)*I/4,             -sqrt(2)*I/4],\n",
       "[sqrt(2)/4, -sqrt(2)/4, -sqrt(2)*I/4,  sqrt(2)*I/4,  sqrt(2)*exp(3*I*pi/4)/4,   sqrt(2)*exp(-I*pi/4)/4,    sqrt(2)*exp(I*pi/4)/4, sqrt(2)*exp(-3*I*pi/4)/4],\n",
       "[sqrt(2)/4,  sqrt(2)/4,    sqrt(2)/4,    sqrt(2)/4,               -sqrt(2)/4,               -sqrt(2)/4,               -sqrt(2)/4,               -sqrt(2)/4],\n",
       "[sqrt(2)/4, -sqrt(2)/4,  sqrt(2)*I/4, -sqrt(2)*I/4, sqrt(2)*exp(-3*I*pi/4)/4,    sqrt(2)*exp(I*pi/4)/4,   sqrt(2)*exp(-I*pi/4)/4,  sqrt(2)*exp(3*I*pi/4)/4],\n",
       "[sqrt(2)/4,  sqrt(2)/4,   -sqrt(2)/4,   -sqrt(2)/4,             -sqrt(2)*I/4,             -sqrt(2)*I/4,              sqrt(2)*I/4,              sqrt(2)*I/4],\n",
       "[sqrt(2)/4, -sqrt(2)/4, -sqrt(2)*I/4,  sqrt(2)*I/4,   sqrt(2)*exp(-I*pi/4)/4,  sqrt(2)*exp(3*I*pi/4)/4, sqrt(2)*exp(-3*I*pi/4)/4,    sqrt(2)*exp(I*pi/4)/4]])"
      ]
     },
     "execution_count": 28,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "F_Q2"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eeb7c441-84e3-4e5d-9a87-3c655b392b45",
   "metadata": {},
   "source": [
    "$B_{Q/2}$ is a diagonal matrix of size $2^{k-1} = Q/2$, with components $1, \\exp(\\frac{2\\pi i}{Q}), \\exp(2\\frac{2\\pi i}{Q}), \\cdots, \\exp((2^{k-1}-1)\\frac{2\\pi i}{Q})$"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "id": "01a81933-3222-4e51-90e0-3219ce2d111a",
   "metadata": {},
   "outputs": [],
   "source": [
    "diag_list = [sy.exp(j * 2 * sy.pi * sy.I / Q) for j in range(2**(k-1))]\n",
    "B_Q2 = sy.diag(*diag_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "id": "7f78cd66-3a8b-4980-af0c-a8974bb68aca",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$\\displaystyle \\left[\\begin{matrix}1 & 0 & 0 & 0 & 0 & 0 & 0 & 0\\\\0 & e^{\\frac{i \\pi}{8}} & 0 & 0 & 0 & 0 & 0 & 0\\\\0 & 0 & e^{\\frac{i \\pi}{4}} & 0 & 0 & 0 & 0 & 0\\\\0 & 0 & 0 & e^{\\frac{3 i \\pi}{8}} & 0 & 0 & 0 & 0\\\\0 & 0 & 0 & 0 & i & 0 & 0 & 0\\\\0 & 0 & 0 & 0 & 0 & e^{\\frac{5 i \\pi}{8}} & 0 & 0\\\\0 & 0 & 0 & 0 & 0 & 0 & e^{\\frac{3 i \\pi}{4}} & 0\\\\0 & 0 & 0 & 0 & 0 & 0 & 0 & e^{\\frac{7 i \\pi}{8}}\\end{matrix}\\right]$"
      ],
      "text/plain": [
       "Matrix([\n",
       "[1,           0,           0,             0, 0,             0,             0,             0],\n",
       "[0, exp(I*pi/8),           0,             0, 0,             0,             0,             0],\n",
       "[0,           0, exp(I*pi/4),             0, 0,             0,             0,             0],\n",
       "[0,           0,           0, exp(3*I*pi/8), 0,             0,             0,             0],\n",
       "[0,           0,           0,             0, I,             0,             0,             0],\n",
       "[0,           0,           0,             0, 0, exp(5*I*pi/8),             0,             0],\n",
       "[0,           0,           0,             0, 0,             0, exp(3*I*pi/4),             0],\n",
       "[0,           0,           0,             0, 0,             0,             0, exp(7*I*pi/8)]])"
      ]
     },
     "execution_count": 30,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "B_Q2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "id": "350b5a16-abbb-41cf-9e6d-65ea798c4fee",
   "metadata": {},
   "outputs": [],
   "source": [
    "A = sy.Matrix(sy.BlockMatrix([[F_Q2, B_Q2 * F_Q2],\n",
    "                             [F_Q2, -B_Q2 * F_Q2]])) * (1/sy.sqrt(2))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "id": "d8d28c56-3cca-47cc-a858-c4c7311800ee",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/latex": [
       "$\\displaystyle \\left[\\begin{array}{cccccccccccccccc}\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & - \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{i e^{\\frac{i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & - \\frac{e^{\\frac{i \\pi}{4}}}{4} & - \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{i e^{\\frac{i \\pi}{4}}}{4} & \\frac{i e^{\\frac{i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{i e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{i e^{\\frac{5 i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{5 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{i e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{i e^{\\frac{3 i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{i e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{- \\frac{7 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & - \\frac{e^{\\frac{i \\pi}{8}}}{4} & \\frac{e^{\\frac{i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{i \\pi}{8}}}{4} & \\frac{i e^{\\frac{i \\pi}{8}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{5 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{e^{\\frac{i \\pi}{4}}}{4} & - \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{i \\pi}{4}}}{4} & \\frac{i e^{\\frac{i \\pi}{4}}}{4} & \\frac{i e^{\\frac{i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & \\frac{i e^{\\frac{3 i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{3 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{7 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{i \\pi}{8}}}{4} & - \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{3 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & \\frac{i}{4} & - \\frac{i}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{5 i \\pi}{8}}}{4} & \\frac{i e^{\\frac{5 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{i \\pi}{8}}}{4} & - \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{5 i \\pi}{8}}}{4}\\\\\\frac{1}{4} & \\frac{1}{4} & - \\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{i}{4} & - \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{i e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{i e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{3 i \\pi}{4}}}{4} & - \\frac{i e^{\\frac{3 i \\pi}{4}}}{4}\\\\\\frac{1}{4} & - \\frac{1}{4} & - \\frac{i}{4} & \\frac{i}{4} & \\frac{e^{- \\frac{i \\pi}{4}}}{4} & \\frac{e^{\\frac{3 i \\pi}{4}}}{4} & \\frac{e^{- \\frac{3 i \\pi}{4}}}{4} & \\frac{e^{\\frac{i \\pi}{4}}}{4} & - \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{e^{\\frac{7 i \\pi}{8}}}{4} & \\frac{i e^{\\frac{7 i \\pi}{8}}}{4} & - \\frac{i e^{\\frac{7 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{5 i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{3 i \\pi}{8}}}{4} & - \\frac{e^{\\frac{i \\pi}{8}}}{4} & - \\frac{e^{- \\frac{7 i \\pi}{8}}}{4}\\end{array}\\right]$"
      ],
      "text/plain": [
       "Matrix([\n",
       "[1/4,  1/4,  1/4,  1/4,              1/4,              1/4,              1/4,              1/4,              1/4,              1/4,                1/4,                1/4,                1/4,                1/4,                1/4,                1/4],\n",
       "[1/4, -1/4,  I/4, -I/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/8)/4,   -exp(I*pi/8)/4,    I*exp(I*pi/8)/4,   -I*exp(I*pi/8)/4,    exp(3*I*pi/8)/4,   exp(-5*I*pi/8)/4,    exp(7*I*pi/8)/4,     exp(-I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,              I/4,              I/4,             -I/4,             -I/4,    exp(I*pi/4)/4,    exp(I*pi/4)/4,     -exp(I*pi/4)/4,     -exp(I*pi/4)/4,    I*exp(I*pi/4)/4,    I*exp(I*pi/4)/4,   -I*exp(I*pi/4)/4,   -I*exp(I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/8)/4, -exp(3*I*pi/8)/4, -I*exp(3*I*pi/8)/4,  I*exp(3*I*pi/8)/4,   exp(-7*I*pi/8)/4,      exp(I*pi/8)/4,    exp(5*I*pi/8)/4,   exp(-3*I*pi/8)/4],\n",
       "[1/4,  1/4,  1/4,  1/4,             -1/4,             -1/4,             -1/4,             -1/4,              I/4,              I/4,                I/4,                I/4,               -I/4,               -I/4,               -I/4,               -I/4],\n",
       "[1/4, -1/4,  I/4, -I/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4,  exp(5*I*pi/8)/4, -exp(5*I*pi/8)/4,  I*exp(5*I*pi/8)/4, -I*exp(5*I*pi/8)/4,     exp(-I*pi/8)/4,    exp(7*I*pi/8)/4,    exp(3*I*pi/8)/4,   exp(-5*I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,             -I/4,             -I/4,              I/4,              I/4,  exp(3*I*pi/4)/4,  exp(3*I*pi/4)/4,   -exp(3*I*pi/4)/4,   -exp(3*I*pi/4)/4, -I*exp(3*I*pi/4)/4, -I*exp(3*I*pi/4)/4,  I*exp(3*I*pi/4)/4,  I*exp(3*I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,  exp(7*I*pi/8)/4, -exp(7*I*pi/8)/4, -I*exp(7*I*pi/8)/4,  I*exp(7*I*pi/8)/4,    exp(5*I*pi/8)/4,   exp(-3*I*pi/8)/4,      exp(I*pi/8)/4,   exp(-7*I*pi/8)/4],\n",
       "[1/4,  1/4,  1/4,  1/4,              1/4,              1/4,              1/4,              1/4,             -1/4,             -1/4,               -1/4,               -1/4,               -1/4,               -1/4,               -1/4,               -1/4],\n",
       "[1/4, -1/4,  I/4, -I/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,   -exp(I*pi/8)/4,    exp(I*pi/8)/4,   -I*exp(I*pi/8)/4,    I*exp(I*pi/8)/4,   -exp(3*I*pi/8)/4,  -exp(-5*I*pi/8)/4,   -exp(7*I*pi/8)/4,    -exp(-I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,              I/4,              I/4,             -I/4,             -I/4,   -exp(I*pi/4)/4,   -exp(I*pi/4)/4,      exp(I*pi/4)/4,      exp(I*pi/4)/4,   -I*exp(I*pi/4)/4,   -I*exp(I*pi/4)/4,    I*exp(I*pi/4)/4,    I*exp(I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,  exp(3*I*pi/4)/4,   exp(-I*pi/4)/4,    exp(I*pi/4)/4, exp(-3*I*pi/4)/4, -exp(3*I*pi/8)/4,  exp(3*I*pi/8)/4,  I*exp(3*I*pi/8)/4, -I*exp(3*I*pi/8)/4,  -exp(-7*I*pi/8)/4,     -exp(I*pi/8)/4,   -exp(5*I*pi/8)/4,  -exp(-3*I*pi/8)/4],\n",
       "[1/4,  1/4,  1/4,  1/4,             -1/4,             -1/4,             -1/4,             -1/4,             -I/4,             -I/4,               -I/4,               -I/4,                I/4,                I/4,                I/4,                I/4],\n",
       "[1/4, -1/4,  I/4, -I/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4, -exp(5*I*pi/8)/4,  exp(5*I*pi/8)/4, -I*exp(5*I*pi/8)/4,  I*exp(5*I*pi/8)/4,    -exp(-I*pi/8)/4,   -exp(7*I*pi/8)/4,   -exp(3*I*pi/8)/4,  -exp(-5*I*pi/8)/4],\n",
       "[1/4,  1/4, -1/4, -1/4,             -I/4,             -I/4,              I/4,              I/4, -exp(3*I*pi/4)/4, -exp(3*I*pi/4)/4,    exp(3*I*pi/4)/4,    exp(3*I*pi/4)/4,  I*exp(3*I*pi/4)/4,  I*exp(3*I*pi/4)/4, -I*exp(3*I*pi/4)/4, -I*exp(3*I*pi/4)/4],\n",
       "[1/4, -1/4, -I/4,  I/4,   exp(-I*pi/4)/4,  exp(3*I*pi/4)/4, exp(-3*I*pi/4)/4,    exp(I*pi/4)/4, -exp(7*I*pi/8)/4,  exp(7*I*pi/8)/4,  I*exp(7*I*pi/8)/4, -I*exp(7*I*pi/8)/4,   -exp(5*I*pi/8)/4,  -exp(-3*I*pi/8)/4,     -exp(I*pi/8)/4,  -exp(-7*I*pi/8)/4]])"
      ]
     },
     "execution_count": 32,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "A"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "3217db7b-5d3c-4795-8067-94b9310088eb",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sy.simplify(A) == sy.simplify(F_Q)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9a5b1900-2866-496f-9957-80f4cddbf5fd",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.14.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
