{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "3c79b13d",
   "metadata": {},
   "source": [
    "Install qiskit\n",
    "and also matplotlib and pylatexenc (for nicer circuit plots)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2d3ef1d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install qiskit qiskit_aer qiskit_ibm_runtime matplotlib pylatexenc"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b107bd8e",
   "metadata": {},
   "source": [
    "### Import Qiskit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "d4648d89",
   "metadata": {},
   "outputs": [],
   "source": [
    "import qiskit\n",
    "import numpy as np\n",
    "from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister, transpile\n",
    "from qiskit_aer import QasmSimulator, StatevectorSimulator\n",
    "from qiskit.visualization import plot_histogram"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e34d35f",
   "metadata": {},
   "source": [
    "## Problem 1: A first quantum circuit in Qiskit"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bc7d4805",
   "metadata": {},
   "source": [
    "Initialise a simple circuit with 1 qubit and 1 classical bit for the output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "cd73c91a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Every quantum circuit is initialised with every qubit in |0>\n",
    "qc1a = QuantumCircuit(QuantumRegister(1), ClassicalRegister(1))\n",
    "# qc1a = QuantumCircuit(1,1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12906a0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "# In Qiskit, standard gates are methods of the circuit object\n",
    "\n",
    "\n",
    "# TODO apply a single qubit gate to qubit 0\n",
    "\n",
    "qc1a.measure(qubit=0, cbit=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2a8c90ee",
   "metadata": {},
   "source": [
    "we can also draw the circuit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6faf0fc7",
   "metadata": {},
   "outputs": [],
   "source": [
    "qc1a.draw('mpl')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3aad0b14",
   "metadata": {},
   "source": [
    "Finially we run the circuit on a simulator (1000 times)\n",
    "and plot the measurement results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9808e5cf",
   "metadata": {},
   "outputs": [],
   "source": [
    "simulator = QasmSimulator()\n",
    "results1a = simulator.run(qc1a, shots=1000).result()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1dad0b4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_histogram(results1a.get_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "df8dd8a4",
   "metadata": {},
   "source": [
    "#### Preparing Bell states"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "50b0a0ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Shortcut for QuantumCircuit(QuantumRegister(2), ClassicalRegister(2))\n",
    "qc1b = QuantumCircuit(2,2)\n",
    "\n",
    "\n",
    "# TODO prepare a Bell state\n",
    "# e.g. by doing a h gate on qubit 0 followed by a cnot on qubits 0 and 1\n",
    "\n",
    "\n",
    "qc1b.measure([0,1], [0,1])\n",
    "# Shortcut:\n",
    "#qc1b.measure_all()\n",
    "\n",
    "qc1b.draw('mpl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86b65581",
   "metadata": {},
   "outputs": [],
   "source": [
    "results1b = simulator.run(qc1b, shots=1000000).result()\n",
    "plot_histogram(results1b.get_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "90c36842",
   "metadata": {},
   "source": [
    "## Problem 2: Use different simulators in Qiskit\n",
    "\n",
    "We are going to see how the use of different simulators affects the final result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "03d7c581",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit_aer import QasmSimulator, StatevectorSimulator"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5dd71ff7",
   "metadata": {},
   "source": [
    "We'll use a fake backend from qiskit to simulate realistic noise without needing an IBM account.\n",
    "\n",
    "Fake backends provide realistic device configurations and noise models based on real IBM quantum devices."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "57ce9274",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Use a fake backend instead of connecting to IBM\n",
    "from qiskit_aer.noise import NoiseModel\n",
    "from qiskit_ibm_runtime.fake_provider import FakeManilaV2\n",
    "\n",
    "# Create a fake backend (simulates IBM Manila quantum computer)\n",
    "fake_backend = FakeManilaV2()\n",
    "print(f\"Using fake backend: {fake_backend.name}\")\n",
    "print(f\"Number of qubits: {fake_backend.num_qubits}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6c159aac",
   "metadata": {},
   "source": [
    "Get the noise model from the fake backend\n",
    "\n",
    "This noise model approximates the real device's behavior"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7dd98a3a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generate noise model from the fake backend\n",
    "noise_model = NoiseModel.from_backend(fake_backend)\n",
    "print(noise_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "befa91ad",
   "metadata": {},
   "source": [
    "You can also try other fake backends like FakeLimaV2, FakeQuitoV2, etc."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "00c11484",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Optional: List available fake backends\n",
    "from qiskit_ibm_runtime.fake_provider import FakeProviderForBackendV2\n",
    "fake_provider = FakeProviderForBackendV2()\n",
    "print(\"Available fake backends:\")\n",
    "for backend in fake_provider.backends():\n",
    "    print(f\"  - {backend.name}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f8defbb4",
   "metadata": {},
   "source": [
    "prepare the simulators"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "461307b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "statevector_simulator = StatevectorSimulator()\n",
    "qasm_simulator        = QasmSimulator()\n",
    "noisy_qasm_simulator  = QasmSimulator(noise_model=noise_model)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d5172db",
   "metadata": {},
   "outputs": [],
   "source": [
    "qc2 = QuantumCircuit(4)\n",
    "\n",
    "\n",
    "# TODO implement a circuit that prepares 1/√2 (|0000⟩ + |1111⟩)\n",
    "\n",
    "\n",
    "qc2.draw('mpl')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1e37305",
   "metadata": {},
   "source": [
    "If we use the statevector_simulator we can directly extract the coefficients in the computational basis:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4426647",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Statevector simulator is the exact state at the end of the circuit, then shots=1 by default\n",
    "results2 = statevector_simulator.run(qc2).result()\n",
    "results2.get_statevector()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3da708cc",
   "metadata": {},
   "source": [
    "As we can see, we obtain the vector describing the state of the quantum computer.\n",
    "\n",
    "Now we add the missing measurements of the end of the circuit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36aa97d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "qc2.measure_all()\n",
    "qc2.draw('mpl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8af80fe9",
   "metadata": {},
   "outputs": [],
   "source": [
    "results2 = qasm_simulator.run(qc2, shots=1000).result()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "003c0b44",
   "metadata": {},
   "source": [
    "plot the result of 1000 runs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e05dc913",
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_histogram(results2.get_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "913f5de3",
   "metadata": {},
   "source": [
    "and finally we run it using the simulated noise model from the device we selected"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6c003714",
   "metadata": {},
   "outputs": [],
   "source": [
    "results2 = noisy_qasm_simulator.run(qc2, shots=1000).result()\n",
    "plot_histogram(results2.get_counts())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f03473c4",
   "metadata": {},
   "source": [
    "## Problem 3: Transpile a quantum Circuit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "97f01b31",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit.compiler import transpile"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67381eb6",
   "metadata": {},
   "source": [
    "First we select a backend to transpile for"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee8fec02",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit.providers.fake_provider import GenericBackendV2\n",
    "fake_hardware_backend = GenericBackendV2(num_qubits=5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0151c396",
   "metadata": {},
   "outputs": [],
   "source": [
    "qc3 = QuantumCircuit(5)\n",
    "\n",
    "\n",
    "# TODO implement a cirquit of your choice using 5 qubits\n",
    "\n",
    "qc3.measure_all()\n",
    "qc3.draw('mpl')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef81602a",
   "metadata": {},
   "source": [
    "then we transpile the circuit for the selected backend"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ad31e9ee",
   "metadata": {},
   "outputs": [],
   "source": [
    "transpiled_qc3 = transpile(qc3, backend = fake_hardware_backend)\n",
    "transpiled_qc3.draw('mpl')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "49d08854",
   "metadata": {},
   "source": [
    "As you can see, transpilation greatly extend the depth of your circuit. You can use the options in the transpile function to reduce the depth of the transpiled circuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6dabc27c",
   "metadata": {},
   "outputs": [],
   "source": [
    "results = fake_hardware_backend.run(transpiled_qc3, shots=1024).result()\n",
    "plot_histogram(results.get_counts(transpiled_qc3))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "321810c5",
   "metadata": {},
   "source": [
    "## Problem 4:  Quantum Fourier Transform"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6f424516",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qiskit.circuit.library import SGate, TGate\n",
    "CS = SGate().control()\n",
    "CT = TGate().control()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0295f1ac",
   "metadata": {},
   "outputs": [],
   "source": [
    "def qft(qc):\n",
    "    # TODO implement the QFT for 3 qubits\n",
    "    pass\n",
    "\n",
    "\n",
    "def initialize(qc):\n",
    "    # TODO initialize to states different from |000⟩ here\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d55e367",
   "metadata": {},
   "outputs": [],
   "source": [
    "# HINT:\n",
    "\n",
    "# you can use\n",
    "\n",
    "#from qiskit.circuit.library import SGate, TGate\n",
    "#CS = SGate().control()\n",
    "#CT = TGate().control()\n",
    "#qc.append(CS, [control,target])\n",
    "#qc.append(CT, [control,target])\n",
    "\n",
    "# or a controlled phase gate specifying the angles for S and T in terms of π\n",
    "\n",
    "# from math import pi\n",
    "# qc.cp(angle, control, target)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3eab3525",
   "metadata": {},
   "outputs": [],
   "source": [
    "qc4 = QuantumCircuit(3)\n",
    "\n",
    "initialize(qc4)\n",
    "qft(qc4)\n",
    "qc4.measure_all()\n",
    "\n",
    "qc4.draw(output='mpl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4b260df-9485-4833-b858-9198ea5118fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "qc4_transpiled = transpile(qc4, simulator)\n",
    "qc4_transpiled.draw(output='mpl')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12a1da71",
   "metadata": {},
   "outputs": [],
   "source": [
    "results4 = simulator.run(qc4_transpiled, shots=1000).result()\n",
    "plot_histogram(results4.get_counts())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "efb82f7c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO also run the qft cirquit with a noise model like in Problem 3 and plot the results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8126d5c3-4ac4-42a5-b859-2b72b05c7e8f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "formats": "ipynb,md"
  },
  "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.11.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
