{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8e8554e7",
   "metadata": {
    "id": "8e8554e7"
   },
   "source": "# Classical Data → Quantum Data Encoding\n## A Step-by-Step Self-Learning Notebook\n\n**Goal:** Learn how classical data is transformed into quantum states, understand the major encoding strategies, implement them in Python, and develop intuition for choosing an encoding method in QML.\n\n### Learning path\n1. Quantum states and the classical-to-quantum interface\n2. Qubits, amplitudes, normalization, and measurement\n3. Basis encoding\n4. Angle encoding\n5. Amplitude encoding\n6. Phase encoding\n7. Data re-uploading\n8. Feature maps and quantum similarity\n9. Encoding design rules\n10. A mini end-to-end QML example\n11. Exercises and a final challenge\n\n> **Important:** This notebook emphasizes understanding before abstraction. Run each cell, inspect the output, and modify the examples yourself."
  },
  {
   "cell_type": "markdown",
   "id": "cef421ac",
   "metadata": {
    "id": "cef421ac"
   },
   "source": "## 0. Prerequisites\n\nYou should be comfortable with:\n- Python basics\n- NumPy arrays\n- vectors and matrices\n- basic probability\n- the idea of a function and a parameter\n\nYou do **not** need prior quantum-computing experience.\n\n### Core idea\n\nFor classical data\n\n\\[\nx=(x_1,x_2,\\ldots,x_d),\n\\]\n\nwe construct a quantum state\n\n\\[\n|\\psi(x)\\rangle=U_{enc}(x)|0\\rangle^{\\otimes n}.\n\\]\n\nThe quantum algorithm then operates on this state and measurement produces classical information.\n\nThe complete interface is:\n\n**Classical data → preprocessing → encoding → quantum circuit → measurement → classical result**"
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "b0915bd2",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "b0915bd2",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774487369,
     "user_tz": -180,
     "elapsed": 9302,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "93de26f6-d7d8-4fbd-e4ab-911dc4f633a5"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m9.8/9.8 MB\u001b[0m \u001b[31m61.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m12.4/12.4 MB\u001b[0m \u001b[31m86.1 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.4/2.4 MB\u001b[0m \u001b[31m52.0 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m54.9/54.9 kB\u001b[0m \u001b[31m2.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[?25h"
     ]
    }
   ],
   "source": "# Cell 1 — Install the quantum-computing libraries\n# Run this cell once in Google Colab.\n\n!pip -q install qiskit qiskit-aer matplotlib numpy pandas scikit-learn"
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "100772b2",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "100772b2",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774502508,
     "user_tz": -180,
     "elapsed": 7885,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "aeb3f92e-bccb-4cbb-a6e3-1cb123150d0a"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Libraries loaded.\n"
     ]
    }
   ],
   "source": "# Cell 2 — Imports\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nfrom qiskit import QuantumCircuit\nfrom qiskit_aer import AerSimulator\nfrom qiskit.quantum_info import Statevector, Operator, state_fidelity\nfrom sklearn.preprocessing import MinMaxScaler, StandardScaler\nfrom sklearn.decomposition import PCA\n\nnp.set_printoptions(precision=4, suppress=True)\n\nprint(\"Libraries loaded.\")"
  },
  {
   "cell_type": "markdown",
   "id": "d67ffaca",
   "metadata": {
    "id": "d67ffaca"
   },
   "source": "# 1. What is a qubit?\n\nA classical bit is either:\n\n\\[\n0 \\quad\\text{or}\\quad 1.\n\\]\n\nA qubit can be in a superposition:\n\n\\[\n|\\psi\\rangle=\\alpha|0\\rangle+\\beta|1\\rangle\n\\]\n\nwith\n\n\\[\n|\\alpha|^2+|\\beta|^2=1.\n\\]\n\nThe numbers \\(\\alpha\\) and \\(\\beta\\) are generally complex amplitudes.\n\nWhen measured:\n\n\\[\nP(0)=|\\alpha|^2,\n\\qquad\nP(1)=|\\beta|^2.\n\\]\n\n### Self-check\n\nIf\n\n\\[\n|\\psi\\rangle=\\frac{1}{\\sqrt2}|0\\rangle+\n\\frac{1}{\\sqrt2}|1\\rangle,\n\\]\n\nwhat are the probabilities of measuring 0 and 1?"
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "1d76e6ff",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "1d76e6ff",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774507442,
     "user_tz": -180,
     "elapsed": 40,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "d72a0ac6-1832-477d-93ee-f88be5bd40cd"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Statevector([0.7071+0.j, 0.7071+0.j],\n",
      "            dims=(2,))\n",
      "Probabilities: {np.str_('0'): np.float64(0.4999999999999999), np.str_('1'): np.float64(0.4999999999999999)}\n",
      "Normalization: 0.9999999999999998\n"
     ]
    }
   ],
   "source": "# Cell 3 — The |+> state\n\nplus = Statevector.from_label(\"+\")\nprint(plus)\n\nprint(\"Probabilities:\", plus.probabilities_dict())\n\n# Exercise:\n# Change the amplitudes below and verify that they are normalized.\nalpha = 1 / np.sqrt(2)\nbeta = 1 / np.sqrt(2)\n\nprint(\"Normalization:\", abs(alpha)**2 + abs(beta)**2)"
  },
  {
   "cell_type": "markdown",
   "id": "8cb5043a",
   "metadata": {
    "id": "8cb5043a"
   },
   "source": "# 2. Why do we need data encoding?\n\nA classical number such as\n\n\\[\nx=0.73\n\\]\n\nis not itself a quantum state.\n\nWe need a rule that maps the classical value to a valid quantum operation.\n\nA general encoding is:\n\n\\[\nx \\longrightarrow U_{enc}(x) \\longrightarrow |\\psi(x)\\rangle.\n\\]\n\nThis mapping is the **quantum feature map** or **data encoding**.\n\nThe encoding is not merely a technical step. In QML, it is part of the model because it determines how classical data is represented in Hilbert space."
  },
  {
   "cell_type": "markdown",
   "id": "feb37372",
   "metadata": {
    "id": "feb37372"
   },
   "source": "# 3. Preprocessing classical data\n\nQuantum gates often work naturally with angles or normalized quantities.\n\nFor min-max scaling:\n\n\\[\nx'=\n\\frac{x-x_{min}}{x_{max}-x_{min}}.\n\\]\n\nThis maps a feature into \\([0,1]\\).\n\nFor standardization:\n\n\\[\nx'=\\frac{x-\\mu}{\\sigma}.\n\\]\n\n### Important\n\nThere is no universal \"best\" scaling. The transformation should preserve the information relevant to the application."
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "13c78c8f",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "13c78c8f",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774520411,
     "user_tz": -180,
     "elapsed": 17,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "38da83fc-b0ee-4823-97ab-79b26748162f"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Original data:\n",
      " [[  10. 1000.]\n",
      " [  20. 1500.]\n",
      " [  30. 2200.]\n",
      " [  40. 5000.]\n",
      " [  50. 8000.]]\n",
      "\n",
      "Min-max scaled:\n",
      " [[0.     0.    ]\n",
      " [0.25   0.0714]\n",
      " [0.5    0.1714]\n",
      " [0.75   0.5714]\n",
      " [1.     1.    ]]\n",
      "\n",
      "Standardized:\n",
      " [[-1.4142 -0.9679]\n",
      " [-0.7071 -0.7774]\n",
      " [ 0.     -0.5106]\n",
      " [ 0.7071  0.5564]\n",
      " [ 1.4142  1.6996]]\n"
     ]
    }
   ],
   "source": "# Cell 4 — Compare two common preprocessing methods\n\nX = np.array([\n    [10, 1000],\n    [20, 1500],\n    [30, 2200],\n    [40, 5000],\n    [50, 8000]\n], dtype=float)\n\nminmax = MinMaxScaler()\nstandard = StandardScaler()\n\nX_minmax = minmax.fit_transform(X)\nX_standard = standard.fit_transform(X)\n\nprint(\"Original data:\\n\", X)\nprint(\"\\nMin-max scaled:\\n\", X_minmax)\nprint(\"\\nStandardized:\\n\", X_standard)"
  },
  {
   "cell_type": "markdown",
   "id": "5d203a5b",
   "metadata": {
    "id": "5d203a5b"
   },
   "source": "# 4. Basis encoding\n\nBasis encoding represents binary information directly:\n\n\\[\n0\\rightarrow|0\\rangle,\n\\qquad\n1\\rightarrow|1\\rangle.\n\\]\n\nFor example:\n\n\\[\n[0,1,1,0]\n\\rightarrow\n|0110\\rangle.\n\\]\n\nThis is intuitive and useful for discrete/binary data.\n\n### Rule\n\nBasis encoding is not naturally suited to arbitrary continuous values. Continuous data must first be quantized or transformed."
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "80ad3678",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "80ad3678",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774527186,
     "user_tz": -180,
     "elapsed": 28,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "e1b46021-95b7-425d-938d-ec79382290a6"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "          \n",
      "q_0: ─────\n",
      "     ┌───┐\n",
      "q_1: ┤ X ├\n",
      "     ├───┤\n",
      "q_2: ┤ X ├\n",
      "     └───┘\n",
      "q_3: ─────\n",
      "          \n",
      "Statevector([0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j,\n",
      "             0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j,\n",
      "             0.+0.j, 0.+0.j],\n",
      "            dims=(2, 2, 2, 2))\n"
     ]
    }
   ],
   "source": "# Cell 5 — Basis encoding\n\nbits = \"0110\"\nqc_basis = QuantumCircuit(len(bits))\n\nfor i, bit in enumerate(bits):\n    if bit == \"1\":\n        qc_basis.x(i)\n\nprint(qc_basis)\nprint(Statevector.from_instruction(qc_basis))"
  },
  {
   "cell_type": "markdown",
   "id": "770eb38f",
   "metadata": {
    "id": "770eb38f"
   },
   "source": "## Exercise 1\n\nModify the previous cell to encode:\n\n\\[\n[1,0,1,1,0,0].\n\\]\n\nThen answer:\n\n1. How many qubits are required?\n2. What computational-basis state is produced?\n3. What happens if the classical values are not binary?"
  },
  {
   "cell_type": "markdown",
   "id": "07a6ee2c",
   "metadata": {
    "id": "07a6ee2c"
   },
   "source": "# 5. Angle encoding\n\nAngle encoding maps a classical feature to a rotation angle.\n\nFor one feature:\n\n\\[\nx\\rightarrow\\theta=f(x)\n\\]\n\nand then\n\n\\[\n|0\\rangle\\rightarrow R_y(\\theta)|0\\rangle.\n\\]\n\nSince\n\n\\[\nR_y(\\theta)|0\\rangle\n=\n\\cos(\\theta/2)|0\\rangle+\n\\sin(\\theta/2)|1\\rangle,\n\\]\n\nthe feature changes the amplitudes.\n\nFor normalized \\(x\\in[0,1]\\), a simple mapping is:\n\n\\[\n\\theta=\\pi x.\n\\]\n\nFor a vector \\(x=(x_1,x_2,\\ldots,x_d)\\), one simple architecture uses one qubit per feature."
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c466777c",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "c466777c",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774532996,
     "user_tz": -180,
     "elapsed": 27,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "8022ec67-dfbb-41dd-cc38-ace71e3b992f"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "x = 0.73\n",
      "theta = 2.293362637120549\n",
      "state = Statevector([0.4115+0.j, 0.9114+0.j],\n",
      "            dims=(2,))\n",
      "probabilities = {np.str_('0'): np.float64(0.16934406733817414), np.str_('1'): np.float64(0.8306559326618258)}\n"
     ]
    }
   ],
   "source": "# Cell 6 — One-feature angle encoding\n\nx = 0.73\ntheta = np.pi * x\n\nqc_angle = QuantumCircuit(1)\nqc_angle.ry(theta, 0)\n\nstate = Statevector.from_instruction(qc_angle)\n\nprint(\"x =\", x)\nprint(\"theta =\", theta)\nprint(\"state =\", state)\nprint(\"probabilities =\", state.probabilities_dict())"
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1b299995",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "1b299995",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774536489,
     "user_tz": -180,
     "elapsed": 56,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "3b648879-eb61-405a-d492-9bf5ed252a8d"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "      ┌─────────┐ \n",
      "q_0: ─┤ Ry(π/5) ├─\n",
      "     ┌┴─────────┴┐\n",
      "q_1: ┤ Ry(7π/10) ├\n",
      "     └┬──────────┤\n",
      "q_2: ─┤ Ry(2π/5) ├\n",
      "     ┌┴──────────┤\n",
      "q_3: ┤ Ry(9π/10) ├\n",
      "     └───────────┘\n",
      "\n",
      "Statevector dimension: 16\n",
      "Number of qubits: 4\n"
     ]
    }
   ],
   "source": "# Cell 7 — Multi-feature angle encoding\n\nx = np.array([0.2, 0.7, 0.4, 0.9])\n\nqc = QuantumCircuit(len(x))\n\nfor i, feature in enumerate(x):\n    qc.ry(np.pi * feature, i)\n\nprint(qc)\n\nstate = Statevector.from_instruction(qc)\nprint(\"\\nStatevector dimension:\", len(state.data))\nprint(\"Number of qubits:\", qc.num_qubits)"
  },
  {
   "cell_type": "markdown",
   "id": "49f27329",
   "metadata": {
    "id": "49f27329"
   },
   "source": "## Exercise 2 — Explore the encoding\n\nTry:\n\n- `x = [0, 0, 0, 0]`\n- `x = [1, 1, 1, 1]`\n- `x = [0.5, 0.5, 0.5, 0.5]`\n- random values in `[0,1]`\n\nObserve how the quantum state changes.\n\n### Key lesson\n\nFor simple angle encoding:\n\n\\[\n\\text{number of features}\\approx\\text{number of qubits}.\n\\]\n\nBut this is a design choice, not a fundamental law."
  },
  {
   "cell_type": "markdown",
   "id": "080ca9d2",
   "metadata": {
    "id": "080ca9d2"
   },
   "source": "# 6. Amplitude encoding\n\nAmplitude encoding stores classical values in the amplitudes of a quantum state.\n\nFor four normalized values:\n\n\\[\nx=(x_0,x_1,x_2,x_3),\n\\]\n\nwe can prepare:\n\n\\[\n|\\psi_x\\rangle=\nx_0|00\\rangle+x_1|01\\rangle+x_2|10\\rangle+x_3|11\\rangle.\n\\]\n\nFour amplitudes require only:\n\n\\[\n\\log_2(4)=2\n\\]\n\nqubits.\n\nMore generally:\n\n\\[\n2^n\\text{ amplitudes}\\rightarrow n\\text{ qubits}.\n\\]\n\n### Critical caveat\n\nThe state-preparation circuit can be expensive.\n\nTherefore:\n\n**fewer qubits does not automatically mean lower total computational cost.**"
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "f09e267a",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "f09e267a",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774542177,
     "user_tz": -180,
     "elapsed": 51,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "f4e21acd-87c7-4839-9ec5-3cd9b6753ca8"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "Normalized vector: [0.1826 0.3651 0.5477 0.7303]\n",
      "Norm: 0.9999999999999999\n",
      "\n",
      "Statevector:\n",
      "Statevector([0.1826+0.j, 0.3651+0.j, 0.5477+0.j, 0.7303+0.j],\n",
      "            dims=(2, 2))\n",
      "\n",
      "Probabilities:\n",
      "{np.str_('00'): np.float64(0.033333333333333326), np.str_('01'): np.float64(0.1333333333333333), np.str_('10'): np.float64(0.29999999999999993), np.str_('11'): np.float64(0.5333333333333332)}\n"
     ]
    }
   ],
   "source": "# Cell 8 — Amplitude encoding with a simple vector\n\nx = np.array([1, 2, 3, 4], dtype=float)\nx_normalized = x / np.linalg.norm(x)\n\nprint(\"Normalized vector:\", x_normalized)\nprint(\"Norm:\", np.linalg.norm(x_normalized))\n\nstate = Statevector(x_normalized)\n\nprint(\"\\nStatevector:\")\nprint(state)\n\nprint(\"\\nProbabilities:\")\nprint(state.probabilities_dict())"
  },
  {
   "cell_type": "markdown",
   "id": "1291f759",
   "metadata": {
    "id": "1291f759"
   },
   "source": "## Exercise 3\n\nChange the vector to:\n\n\\[\n[3,1,-2,4].\n\\]\n\nNormalize it and create the corresponding two-qubit state.\n\nThen verify:\n\n\\[\n\\sum_i |a_i|^2=1.\n\\]\n\nNotice that negative values are allowed because amplitudes can carry sign/phase information."
  },
  {
   "cell_type": "markdown",
   "id": "27f26953",
   "metadata": {
    "id": "27f26953"
   },
   "source": "# 7. Phase encoding\n\nInformation can also be stored in quantum phase.\n\nA simple example is:\n\n\\[\n|\\psi(x)\\rangle=\n\\frac{1}{\\sqrt2}\n\\left(\n|0\\rangle+e^{i\\phi(x)}|1\\rangle\n\\right).\n\\]\n\nThe phase can be generated using an \\(R_z\\) rotation.\n\n### Why phase matters\n\nGlobal phase cannot be observed directly, but **relative phase** affects interference and therefore can influence measurement outcomes after additional quantum operations."
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "afc840e6",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "afc840e6",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774545999,
     "user_tz": -180,
     "elapsed": 53,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "d4f36f04-fc5d-4f7f-8ab5-19e9ac2f4544"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "x = 0.6\n",
      "phase = 1.8849555921538759\n",
      "state = Statevector([0.4156-0.5721j, 0.4156+0.5721j],\n",
      "            dims=(2,))\n",
      "\n",
      "After interference:\n",
      "{np.str_('0'): np.float64(0.34549150281252616), np.str_('1'): np.float64(0.6545084971874736)}\n"
     ]
    }
   ],
   "source": "# Cell 9 — Phase encoding\n\nx = 0.6\nphi = np.pi * x\n\nqc_phase = QuantumCircuit(1)\nqc_phase.h(0)\nqc_phase.rz(phi, 0)\n\nstate_phase = Statevector.from_instruction(qc_phase)\n\nprint(\"x =\", x)\nprint(\"phase =\", phi)\nprint(\"state =\", state_phase)\n\n# Apply H again to convert phase information into measurable probability differences\nqc_measure = QuantumCircuit(1)\nqc_measure.h(0)\nqc_measure.rz(phi, 0)\nqc_measure.h(0)\n\nfinal_state = Statevector.from_instruction(qc_measure)\n\nprint(\"\\nAfter interference:\")\nprint(final_state.probabilities_dict())"
  },
  {
   "cell_type": "markdown",
   "id": "b669181f",
   "metadata": {
    "id": "b669181f"
   },
   "source": "# 8. Why encoding and interference belong together\n\nA phase may not change measurement probabilities immediately.\n\nFor example, states with different relative phases can initially have similar computational-basis probabilities.\n\nA later gate can convert phase information into measurable amplitude differences.\n\nThis is a central quantum-computing idea:\n\n\\[\n\\boxed{\\text{Encoding} + \\text{Interference} \\rightarrow \\text{Observable information}}\n\\]\n\nQuantum algorithms are not useful simply because data is placed into a quantum state. The circuit must exploit the representation."
  },
  {
   "cell_type": "markdown",
   "id": "5283361b",
   "metadata": {
    "id": "5283361b"
   },
   "source": "# 9. Data re-uploading\n\nInstead of encoding data once, a variational circuit can encode the same input repeatedly:\n\n\\[\nU(x,\\theta)=\nU_L(\\theta_L)U_{enc}(x)\\cdots\nU_2(\\theta_2)U_{enc}(x)U_1(\\theta_1)U_{enc}(x).\n\\]\n\nThis is called **data re-uploading**.\n\nIt is useful when:\n- the number of available qubits is small,\n- the model needs more expressive feature interactions,\n- repeated feature injection is beneficial.\n\nThe trade-off is increased circuit depth and potentially harder optimization."
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "8cf3d048",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "8cf3d048",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774549491,
     "user_tz": -180,
     "elapsed": 26,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "63b96157-9778-4161-dfff-12905db2b0bb"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "   ┌────────────┐┌─────────┐┌────────────┐┌─────────┐┌────────────┐┌─────────┐\n",
      "q: ┤ Ry(1.0996) ├┤ Rz(0.2) ├┤ Ry(1.0996) ├┤ Rz(0.4) ├┤ Ry(1.0996) ├┤ Rz(0.6) ├\n",
      "   └────────────┘└─────────┘└────────────┘└─────────┘└────────────┘└─────────┘\n",
      "Statevector([-0.1637-0.2131j,  0.9034+0.3341j],\n",
      "            dims=(2,))\n"
     ]
    }
   ],
   "source": "# Cell 10 — A simple data re-uploading circuit\n\nx = 0.35\n\nqc_upload = QuantumCircuit(1)\n\nfor layer in range(3):\n    qc_upload.ry(np.pi * x, 0)\n    qc_upload.rz(0.2 * (layer + 1), 0)\n\nprint(qc_upload)\n\nstate_upload = Statevector.from_instruction(qc_upload)\nprint(state_upload)"
  },
  {
   "cell_type": "markdown",
   "id": "50fbcd9e",
   "metadata": {
    "id": "50fbcd9e"
   },
   "source": "# 10. Entanglement and feature interactions\n\nEncoding does not automatically imply entanglement.\n\nA simple product encoding is:\n\n\\[\n|\\psi(x)\\rangle=\n\\bigotimes_i |\\psi(x_i)\\rangle.\n\\]\n\nWe can then add an entangling circuit:\n\n\\[\n\\text{Encoding}\\rightarrow\\text{Entanglement}\\rightarrow\\text{Trainable layers}.\n\\]\n\nFor example, a chain of CNOT gates can couple neighboring features."
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "693b8765",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "693b8765",
    "executionInfo": {
     "status": "ok",
     "timestamp": 1788774552385,
     "user_tz": -180,
     "elapsed": 37,
     "user": {
      "displayName": "Joseph Volkan ÖZCAN",
      "userId": "11389530967736397480"
     }
    },
    "outputId": "e85b0ec9-e12f-4ad3-85be-950ba4c3202d"
   },
   "outputs": [
    {
     "output_type": "stream",
     "name": "stdout",
     "text": [
      "      ┌─────────┐                \n",
      "q_0: ─┤ Ry(π/5) ├───■────────────\n",
      "     ┌┴─────────┴┐┌─┴─┐          \n",
      "q_1: ┤ Ry(7π/10) ├┤ X ├──■───────\n",
      "     └┬──────────┤└───┘┌─┴─┐     \n",
      "q_2: ─┤ Ry(2π/5) ├─────┤ X ├──■──\n",
      "     ┌┴──────────┤     └───┘┌─┴─┐\n",
      "q_3: ┤ Ry(9π/10) ├──────────┤ X ├\n",
      "     └───────────┘          └───┘\n",
      "\n",
      "Number of amplitudes: 16\n"
     ]
    }
   ],
   "source": "# Cell 11 — Angle encoding + entanglement\n\nx = np.array([0.2, 0.7, 0.4, 0.9])\n\nqc_ent = QuantumCircuit(4)\n\n# Encode\nfor i, feature in enumerate(x):\n    qc_ent.ry(np.pi * feature, i)\n\n# Entangle\nfor i in range(3):\n    qc_ent.cx(i, i + 1)\n\nprint(qc_ent)\n\nstate_ent = Statevector.from_instruction(qc_ent)\nprint(\"\\nNumber of amplitudes:\", len(state_ent.data))"
  },
  {
   "cell_type": "markdown",
   "id": "aab1f26d",
   "metadata": {
    "id": "aab1f26d"
   },
   "source": "# 11. Quantum feature maps and similarity\n\nOne important reason to encode classical data into quantum states is to define a new notion of similarity.\n\nGiven two encoded states:\n\n\\[\n|\\psi(x)\\rangle\n\\quad\\text{and}\\quad\n|\\psi(z)\\rangle,\n\\]\n\nwe can examine their overlap:\n\n\\[\nK(x,z)=|\\langle\\psi(x)|\\psi(z)\\rangle|^2.\n\\]\n\nThis quantity can act as a **quantum kernel**.\n\nThe encoding therefore defines the geometry of the quantum feature space."
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7368fff",
   "metadata": {
    "id": "a7368fff"
   },
   "outputs": [],
   "source": "# Cell 12 — Compare two encoded samples\n\ndef angle_state(x):\n    qc = QuantumCircuit(len(x))\n    for i, feature in enumerate(x):\n        qc.ry(np.pi * feature, i)\n    return Statevector.from_instruction(qc)\n\nx1 = np.array([0.2, 0.7])\nx2 = np.array([0.25, 0.65])\nx3 = np.array([0.9, 0.1])\n\npsi1 = angle_state(x1)\npsi2 = angle_state(x2)\npsi3 = angle_state(x3)\n\nK12 = abs(np.vdot(psi1.data, psi2.data))**2\nK13 = abs(np.vdot(psi1.data, psi3.data))**2\n\nprint(\"K(x1,x2) =\", K12)\nprint(\"K(x1,x3) =\", K13)\n\nprint(\"\\nInterpretation:\")\nprint(\"Higher overlap means the encoded quantum states are more similar under this feature map.\")"
  },
  {
   "cell_type": "markdown",
   "id": "20ff30d1",
   "metadata": {
    "id": "20ff30d1"
   },
   "source": "## Exercise 4 — Investigate quantum similarity\n\nGenerate 20 random two-dimensional samples.\n\n1. Encode them with angle encoding.\n2. Compute pairwise quantum-state similarities.\n3. Compare those similarities with Euclidean distances.\n4. Ask: **Does the quantum feature map preserve classical closeness?**\n\nThere is no requirement that it must. The interesting question is whether the resulting geometry is useful for the task."
  },
  {
   "cell_type": "markdown",
   "id": "fc711687",
   "metadata": {
    "id": "fc711687"
   },
   "source": "# 12. High-dimensional classical data\n\nSuppose:\n\n\\[\nx\\in\\mathbb{R}^{10,000}.\n\\]\n\nNaive one-feature-per-qubit angle encoding would require approximately:\n\n\\[\n10,000\\text{ qubits}.\n\\]\n\nThat is usually impractical.\n\nA common workflow is:\n\n\\[\n10,000\n\\rightarrow\n\\text{feature selection / PCA}\n\\rightarrow\n8\n\\rightarrow\n8\\text{ qubits}.\n\\]\n\nBut dimensionality reduction can discard information.\n\nTherefore the design problem becomes:\n\n\\[\n\\boxed{\n\\text{How much classical information should enter the quantum circuit?}\n}\n\\]"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83a9c9a6",
   "metadata": {
    "id": "83a9c9a6"
   },
   "outputs": [],
   "source": "# Cell 13 — PCA before quantum encoding\n\nrng = np.random.default_rng(42)\n\n# Example: 100 samples with 20 classical features\nX = rng.normal(size=(100, 20))\n\npca = PCA(n_components=4)\nX_reduced = pca.fit_transform(X)\n\nprint(\"Original shape:\", X.shape)\nprint(\"Reduced shape:\", X_reduced.shape)\nprint(\"Explained variance ratio:\", pca.explained_variance_ratio_)\nprint(\"Total retained variance:\", pca.explained_variance_ratio_.sum())"
  },
  {
   "cell_type": "markdown",
   "id": "29e54641",
   "metadata": {
    "id": "29e54641"
   },
   "source": "# 13. Encoding design rules\n\nUse these rules as a practical checklist.\n\n### Rule 1 — Match encoding to data\nBinary/discrete data often fits basis encoding. Continuous data often fits angle or other continuous feature maps.\n\n### Rule 2 — Scale deliberately\nGate angles are periodic. Poor scaling can map very different classical values to similar quantum representations.\n\n### Rule 3 — Watch qubit count\nOne feature per qubit is simple but may not scale.\n\n### Rule 4 — Watch state-preparation cost\nAmplitude encoding is qubit-efficient but can be expensive to prepare.\n\n### Rule 5 — Preserve useful geometry\nThe encoding should create a feature space useful for the downstream task.\n\n### Rule 6 — Control circuit depth\nDeeper encoding circuits increase noise and execution cost.\n\n### Rule 7 — Consider hardware connectivity\nEntanglement patterns should reflect the available hardware topology.\n\n### Rule 8 — Separate data and trainable parameters\nUse notation such as:\n\n\\[\nU(x,\\theta)\n\\]\n\nwhere \\(x\\) is the input and \\(\\theta\\) is learned.\n\n### Rule 9 — Account for measurement cost\nQuantum states cannot simply be read out completely. Expect repeated measurements.\n\n### Rule 10 — Benchmark classically\nA quantum encoding by itself does not establish quantum advantage."
  },
  {
   "cell_type": "markdown",
   "id": "bb6fd23a",
   "metadata": {
    "id": "bb6fd23a"
   },
   "source": "# 14. A mini end-to-end QML pipeline\n\nWe now combine the ideas.\n\nTask:\n\n**Classify two-dimensional points.**\n\nPipeline:\n\n\\[\nX\n\\rightarrow\n\\text{scale}\n\\rightarrow\n\\text{angle encoding}\n\\rightarrow\n\\text{entanglement}\n\\rightarrow\n\\text{measurement}.\n\\]\n\nThis is not yet a fully trained QNN. It is a deliberately simple experiment to understand the data-to-quantum interface."
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "68c2a85e",
   "metadata": {
    "id": "68c2a85e"
   },
   "outputs": [],
   "source": "# Cell 14 — Create a tiny dataset\n\nfrom sklearn.datasets import make_blobs\n\nX, y = make_blobs(\n    n_samples=20,\n    centers=2,\n    n_features=2,\n    cluster_std=1.0,\n    random_state=7\n)\n\nscaler = MinMaxScaler()\nX_scaled = scaler.fit_transform(X)\n\ndf = pd.DataFrame(X_scaled, columns=[\"feature_1\", \"feature_2\"])\ndf[\"label\"] = y\n\ndf.head()"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "764610dc",
   "metadata": {
    "id": "764610dc"
   },
   "outputs": [],
   "source": "# Cell 15 — Visualize the classical data\n\nplt.figure(figsize=(6, 5))\nplt.scatter(X_scaled[:, 0], X_scaled[:, 1], c=y)\nplt.xlabel(\"Feature 1\")\nplt.ylabel(\"Feature 2\")\nplt.title(\"Classical data before quantum encoding\")\nplt.show()"
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e7919ec",
   "metadata": {
    "id": "9e7919ec"
   },
   "outputs": [],
   "source": "# Cell 16 — Encode one sample\n\nsample = X_scaled[0]\n\nqc_sample = QuantumCircuit(2)\nqc_sample.ry(np.pi * sample[0], 0)\nqc_sample.ry(np.pi * sample[1], 1)\nqc_sample.cx(0, 1)\n\nprint(\"Classical sample:\", sample)\nprint(\"\\nQuantum circuit:\")\nprint(qc_sample)\n\npsi = Statevector.from_instruction(qc_sample)\n\nprint(\"\\nQuantum state:\")\nprint(psi)\n\nprint(\"\\nMeasurement probabilities:\")\nprint(psi.probabilities_dict())"
  },
  {
   "cell_type": "markdown",
   "id": "f73d51b4",
   "metadata": {
    "id": "f73d51b4"
   },
   "source": "# 15. The complete mathematical picture\n\nFor a data point \\(x\\), a hybrid QML model can be written as:\n\n\\[\n|\\psi(x,\\theta)\\rangle\n=\nU(\\theta)U_{enc}(x)|0\\rangle^{\\otimes n}.\n\\]\n\nThe model output can be an expectation value:\n\n\\[\nf(x,\\theta)\n=\n\\langle\\psi(x,\\theta)|\nO\n|\\psi(x,\\theta)\\rangle.\n\\]\n\nHere:\n\n- \\(x\\): classical input\n- \\(U_{enc}(x)\\): data encoding\n- \\(U(\\theta)\\): trainable quantum circuit\n- \\(\\theta\\): learnable parameters\n- \\(O\\): observable\n- \\(f(x,\\theta)\\): classical prediction\n\nThis equation is the bridge between classical machine learning and variational quantum machine learning."
  },
  {
   "cell_type": "markdown",
   "id": "3eb316e6",
   "metadata": {
    "id": "3eb316e6"
   },
   "source": "# 16. Encoding comparison — decision framework\n\n| Situation | Candidate encoding | Main issue |\n|---|---|---|\n| Binary data | Basis | Limited to discrete representation |\n| Small continuous vector | Angle | Often needs one qubit/feature |\n| Very high-dimensional vector | Amplitude | State preparation cost |\n| Phase-sensitive representation | Phase | Requires interference to exploit phase |\n| Few qubits, expressive model | Data re-uploading | Greater circuit depth |\n| Probability distribution | Amplitude/probability encoding | Preparation and readout cost |\n| Image/sensor data | Feature/patch + angle/amplitude | Dimensionality reduction often needed |\n\n### The central design question\n\nDo not ask only:\n\n> \"Which encoding uses the fewest qubits?\"\n\nAsk:\n\n> **\"Which encoding creates a useful, trainable, hardware-feasible representation of my data?\"**"
  },
  {
   "cell_type": "markdown",
   "id": "0f82ba56",
   "metadata": {
    "id": "0f82ba56"
   },
   "source": "# 17. Final self-test\n\nTry answering these without looking back.\n\n### Q1\nWhy can't we directly write a classical number `0.73` as a qubit state?\n\n### Q2\nWhat is the normalization requirement for quantum-state amplitudes?\n\n### Q3\nIf you have 8 continuous features and use one-qubit-per-feature angle encoding, how many qubits do you need?\n\n### Q4\nHow many qubits can theoretically represent 1024 amplitudes using amplitude encoding?\n\n### Q5\nWhy does amplitude encoding not automatically imply quantum advantage?\n\n### Q6\nWhat is the difference between a data parameter `x` and a trainable parameter `theta`?\n\n### Q7\nWhy might PCA be useful before quantum encoding?\n\n### Q8\nWhy can phase information become useful only after interference?\n\n### Q9\nWhat does a quantum kernel such as\n\n\\[\nK(x,z)=|\\langle\\psi(x)|\\psi(z)\\rangle|^2\n\\]\n\nmeasure?\n\n### Q10\nWhat are the two most important practical costs besides the number of qubits?\n\n**Suggested answers:** quantum states require normalized amplitudes; angle encoding usually uses one qubit per feature; 1024 amplitudes require 10 qubits; amplitude-state preparation can be costly; PCA reduces dimensionality; interference converts relative phase into observable probability differences; quantum kernels quantify state overlap; circuit depth/noise and measurement/state-preparation costs matter."
  },
  {
   "cell_type": "markdown",
   "id": "b950f629",
   "metadata": {
    "id": "b950f629"
   },
   "source": "# 18. Final challenge — build your own encoder\n\nChoose a dataset you care about:\n\n- predictive maintenance\n- finance\n- chemistry\n- image classification\n- sensor monitoring\n- customer analytics\n- another application\n\nThen complete this design:\n\n### Step A — Classical data\nWhat are the features?\n\n### Step B — Preprocessing\nHow will you scale, normalize, select, or reduce them?\n\n### Step C — Encoding\nChoose:\n- basis\n- angle\n- amplitude\n- phase\n- data re-uploading\n- hybrid encoding\n\n### Step D — Quantum circuit\nChoose:\n- number of qubits\n- encoding gates\n- entanglement pattern\n- trainable gates\n- circuit depth\n\n### Step E — Measurement\nWhat observable will produce the prediction?\n\n### Step F — Evaluation\nCompare:\n- classical baseline\n- quantum model\n- accuracy/performance\n- circuit depth\n- number of qubits\n- number of measurements/shots\n- training stability\n\n## Golden rule\n\n> **A good quantum data encoding is not the one that looks most quantum. It is the one that produces a useful representation while respecting information, hardware, computational, and measurement constraints.**"
  },
  {
   "cell_type": "markdown",
   "id": "20629aeb",
   "metadata": {
    "id": "20629aeb"
   },
   "source": "# Next module\n\nAfter completing this notebook, the natural next step is:\n\n**Classical Data Encoding → Quantum Feature Maps → Variational Quantum Circuits → Quantum Neural Networks → Training with a Classical Optimizer**\n\nThe next notebook should implement a complete QML classifier from scratch, including a loss function, parameterized circuit, gradient/optimization loop, training curves, and comparison with a classical model."
  }
 ],
 "metadata": {
  "colab": {
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.x"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}