{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Python Tutorial"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quantum states"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Generate quantum states"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Generate $n$ qubit quantum states using `QuantumState` class and initialize it as $\\left|0\\right>^{\\otimes n}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      "(1,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "# Generate 2 qubit states\n",
    "n = 2\n",
    "state = QuantumState(n)\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can not generate the quantum states if the memory is not sufficient.\n",
    "\n",
    "With the memory of a typical laptop or desktop computer, the limit of qubits you can create is about 26, 27 qubits."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Initialize quantum states"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The generated quantum state can be initialized to a computational basis using the `set_computational_basis` or to a random state using the `set_Harr_random_state`.\n",
    "\n",
    "Note that in Qulacs, the subscripts of the qubits start from 0, and the rightmost bit is the 0-th qubit when written as $\\ket{0000}$ (In other libraries and textbooks, the leftmost bit may be the 0-th qubit)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      " (0.431665,0.175902)\n",
      " (-0.5087,-0.239707)\n",
      "(0.151328,-0.478811)\n",
      "(0.0969414,0.452692)\n",
      "\n",
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      " (-0.13535,-0.226344)\n",
      " (-0.214236,0.181293)\n",
      "(-0.360441,-0.264813)\n",
      " (-0.241755,0.770192)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "\n",
    "n = 2\n",
    "state = QuantumState(n)\n",
    "\n",
    "# Initialize as |00>\n",
    "state.set_zero_state()\n",
    "\n",
    "# Initialize as |01>\n",
    "state.set_computational_basis(0b01)\n",
    "\n",
    "# Generate random initial state\n",
    "state.set_Haar_random_state()\n",
    "print(state)\n",
    "\n",
    "# Generate random initial state with specifying seed\n",
    "seed = 0\n",
    "state.set_Haar_random_state(seed)\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Obtain data of quantum state"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A state vector of a quantum state can be obtained as a numpy array with `get_vector` function. And you can set a quantum state by giving a numpy array or a list in the `load` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'numpy.ndarray'> complex128\n",
      "[1.+0.j 0.+0.j 0.+0.j 0.+0.j]\n",
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      "(0.5,0)\n",
      "(0.5,0)\n",
      "(0.5,0)\n",
      "(0.5,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from qulacs import QuantumState\n",
    "\n",
    "n = 2\n",
    "state = QuantumState(n)\n",
    "\n",
    "# Get the state vector\n",
    "vec = state.get_vector()\n",
    "print(type(vec), vec.dtype)\n",
    "print(vec)\n",
    "\n",
    "# Set the state vector\n",
    "myvec = np.array([0.5,0.5,0.5,0.5])\n",
    "state.load(myvec)\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Copy and load quantum state data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The quantum state can be copied and loaded from other quantum state data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j\n",
      " 0.+0.j 0.+0.j 0.+0.j 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 0.+0.j 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",
      "[0.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j\n",
      " 0.+0.j 0.+0.j 0.+0.j 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 0.+0.j 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"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "n = 5\n",
    "state = QuantumState(n)\n",
    "state.set_computational_basis(0b00101)\n",
    "# Copy to generate another quantum state\n",
    "second_state = state.copy()\n",
    "print(second_state.get_vector())\n",
    "# Generate a new quantum state, and copy from an existing quantum state\n",
    "third_state = QuantumState(n)\n",
    "third_state.load(state)\n",
    "print(third_state.get_vector())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Operate classic registers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The Quantum state can be read and written as a classic register."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "n = 5\n",
    "state = QuantumState(n)\n",
    "state.set_zero_state()\n",
    "# Set the 3rd classical register as 1\n",
    "register_position = 3\n",
    "register_value = 1\n",
    "state.set_classical_value(register_position, register_value)\n",
    "# Obtain the value of the 3rd classical register\n",
    "obtained_value = state.get_classical_value(register_position)\n",
    "print(obtained_value)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Calculate quantum states"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For a quantum state, information about the quantum state can be computed without changing the state of the quantum state. For example, the probability of getting 0 when measuring a qubit with a given index can be calculated with the `get_zero_probability` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "prob_meas_3rd :  0.5199371283324116\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "\n",
    "n = 5\n",
    "state = QuantumState(n)\n",
    "state.set_Haar_random_state(0)\n",
    "\n",
    "# Calculate the probability to get 0 in measurement of the qubit in the given index at Z-basis\n",
    "index = 3\n",
    "zero_probability = state.get_zero_probability(index)\n",
    "print(\"prob_meas_3rd : \",zero_probability)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `sampling` function can be used to sample the results of a quantum state measurement. The argument of the function is the number of data to sample."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3, 0, 2, 2, 3, 2, 2, 0, 0, 0]\n",
      "['11', '00', '10', '10', '11', '10', '10', '00', '00', '00']\n"
     ]
    }
   ],
   "source": [
    "import numpy as np\n",
    "from qulacs import QuantumState\n",
    "\n",
    "n = 2\n",
    "state = QuantumState(n)\n",
    "state.load([1/np.sqrt(2), 0, 0.5, 0.5])\n",
    "data = state.sampling(10)\n",
    "print(data)\n",
    "# Show in binary format\n",
    "print([format(value, \"b\").zfill(2) for value in data])\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You can find many other functions in \"Advanced\" section. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Calculate the inner product of quantum states"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The inner product of a quantum states can be calculated by the `inner_product` function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0.03141883589278555-0.015629285255538153j)\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "from qulacs.state import inner_product\n",
    "n = 5\n",
    "state_bra = QuantumState(n)\n",
    "state_ket = QuantumState(n)\n",
    "state_bra.set_Haar_random_state()\n",
    "state_ket.set_computational_basis(0)\n",
    "# Calculate the inner product\n",
    "value = inner_product(state_bra, state_ket)\n",
    "print(value)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quantum gate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Generate quantum gates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quantum gates are defined in the `qulacs.gate` module. Several typical quantum gates are already defined in this module. For example, X gate can be generated as follows. By `printing` a quantum gate, you can print information about the gate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** gate info *** \n",
      " * gate name : X\n",
      " * target    : \n",
      " 1 : commute X     \n",
      " * control   : \n",
      " * Pauli     : yes\n",
      " * Clifford  : yes\n",
      " * Gaussian  : no\n",
      " * Parametric: no\n",
      " * Diagonal  : no\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import X\n",
    "\n",
    "target_index = 1\n",
    "x_gate = X(target_index)\n",
    "print(x_gate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Operation of quantum gates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quantum gates can update a quantum state with the `update_quantum_state` function. The following example shows the X gate act on the 1st qubit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      "(1,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "\n",
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      "(0,0)\n",
      "(0,0)\n",
      "(1,0)\n",
      "(0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "from qulacs.gate import X\n",
    "\n",
    "n = 2\n",
    "state = QuantumState(n)\n",
    "print(state)\n",
    "\n",
    "index = 1\n",
    "x_gate = X(index)\n",
    "x_gate.update_quantum_state(state)\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Various quantum gates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below is a list of named gates that are often used. Any of the gates can update its quantum state using the `update_quantum_state` function. For other gates, see the Advanced chapter."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "# Pauli gate, Hadamard gate, T gate\n",
    "from qulacs.gate import X, Y, Z, H, T\n",
    "target = 2\n",
    "x_gate = X(target)\n",
    "y_gate = Y(target)\n",
    "z_gate = Z(target)\n",
    "h_gate = H(target)\n",
    "t_gate = T(target)\n",
    "\n",
    "# Pauli rotation gate\n",
    "from qulacs.gate import RX, RY, RZ\n",
    "angle = np.pi / 4.0\n",
    "rx_gate = RX(target, angle)\n",
    "ry_gate = RY(target, angle)\n",
    "rz_gate = RZ(target, angle)\n",
    "\n",
    "# CNOT, CZ, SWAP gate\n",
    "from qulacs.gate import CNOT, CZ, SWAP\n",
    "control = 1\n",
    "target2 = 1\n",
    "cnot_gate = CNOT(control, target)\n",
    "cz_gate = CZ(control, target)\n",
    "swap_gate = SWAP(target, target2)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is an introduction of some of the named gates with examples.\n",
    "\n",
    "#### TOFFOLI gate\n",
    "\n",
    "TOFFOLI gate has control indices in the first and second arguments, and the target index in the third argument.\n",
    "When the qubits corresponding to the control indices given in the first and second arguments are both $\\ket{1}$, the qubits corresponding to the target index given in the third argument is flipped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qulacs.gate import TOFFOLI\n",
    "control1 = 0\n",
    "control2 = 1\n",
    "target1 = 2\n",
    "toffoli_gate = TOFFOLI(control1, control2, target1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is an example of the operation of the TOFFOLI gate. Since 0 and 1 are given as control indices and 2 is given as the target index, the qubit of the 2-nd qubit is flipped when the 0-th qubit and the 1-st qubit are $\\ket{1}$.\n",
    "You can see that the quantum states $\\ket{011}$ and $\\ket{111}$ are swapped before and after the action of the TOFFOLI gate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 3\n",
      " * Dimension   : 8\n",
      " * State vector : \n",
      "(-0.0845729,-0.14143)\n",
      "  (-0.133864,0.11328)\n",
      " (-0.22522,-0.165467)\n",
      " (-0.151059,0.481251)\n",
      " (-0.450874,0.172713)\n",
      " (-0.0585584,0.32498)\n",
      " (0.359721,0.0264336)\n",
      "(-0.101035,-0.356517)\n",
      "\n",
      " *** Quantum State ***\n",
      " * Qubit Count : 3\n",
      " * Dimension   : 8\n",
      " * State vector : \n",
      "(-0.0845729,-0.14143)\n",
      "  (-0.133864,0.11328)\n",
      " (-0.22522,-0.165467)\n",
      "(-0.101035,-0.356517)\n",
      " (-0.450874,0.172713)\n",
      " (-0.0585584,0.32498)\n",
      " (0.359721,0.0264336)\n",
      " (-0.151059,0.481251)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "from qulacs.gate import TOFFOLI\n",
    "\n",
    "n = 3\n",
    "state = QuantumState(n)\n",
    "\n",
    "state.set_Haar_random_state(0)\n",
    "print(state)\n",
    "\n",
    "toffoli_gate = TOFFOLI(0, 1, 2)\n",
    "\n",
    "toffoli_gate.update_quantum_state(state)\n",
    "\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### FREDKIN gate\n",
    "\n",
    "FREDKIN gate has control indices in the first argument, and target indices in the second and third arguments.\n",
    "When the qubit corresponding to the control index given in the first argument is $\\ket{1}$, the qubits corresponding to the target indices given in the second and third arguments are swapped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "from qulacs.gate import FREDKIN\n",
    "control1 = 0\n",
    "target1 = 1\n",
    "target2 = 2\n",
    "fredkin_gate = FREDKIN(control1, target1, target2)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here is an example of the operation of the FREDKIN gate. Since 0 is given as the control index and 1 and 2 are given as the target indices, the qubits of the 1-st qubit and the 2-nd qubit are swapped when the 0-th qubit is $\\ket{1}$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 3\n",
      " * Dimension   : 8\n",
      " * State vector : \n",
      "(-0.0845729,-0.14143)\n",
      "  (-0.133864,0.11328)\n",
      " (-0.22522,-0.165467)\n",
      " (-0.151059,0.481251)\n",
      " (-0.450874,0.172713)\n",
      " (-0.0585584,0.32498)\n",
      " (0.359721,0.0264336)\n",
      "(-0.101035,-0.356517)\n",
      "\n",
      " *** Quantum State ***\n",
      " * Qubit Count : 3\n",
      " * Dimension   : 8\n",
      " * State vector : \n",
      "(-0.0845729,-0.14143)\n",
      "  (-0.133864,0.11328)\n",
      " (-0.22522,-0.165467)\n",
      " (-0.0585584,0.32498)\n",
      " (-0.450874,0.172713)\n",
      " (-0.151059,0.481251)\n",
      " (0.359721,0.0264336)\n",
      "(-0.101035,-0.356517)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "from qulacs.gate import FREDKIN\n",
    "\n",
    "n = 3\n",
    "state = QuantumState(n)\n",
    "\n",
    "state.set_Haar_random_state(0)\n",
    "print(state)\n",
    "\n",
    "fredkin_gate = FREDKIN(0, 1, 2)\n",
    "\n",
    "fredkin_gate.update_quantum_state(state)\n",
    "\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### General quantum gates"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To generate a gate by specifying the matrix of a quantum gate as a numpy array, use the class `DenseMatrix`. The first argument is the index to act on and the second is the matrix. For a single qubit gate, give an integer and a 2 x 2 matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 54,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** gate info *** \n",
      " * gate name : DenseMatrix\n",
      " * target    : \n",
      " 1 : commute       \n",
      " * control   : \n",
      " * Pauli     : no\n",
      " * Clifford  : no\n",
      " * Gaussian  : no\n",
      " * Parametric: no\n",
      " * Diagonal  : no\n",
      " * Matrix\n",
      "(0,0) (1,0)\n",
      "(1,0) (0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import DenseMatrix\n",
    "\n",
    "gate = DenseMatrix(1, [[0,1],[1,0]])\n",
    "print(gate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To create a gate of size larger than 2 qubits, give a list of target subscripts as the first argument and a matrix as the second. When creating an $n$-qubit gate, the matrix must be of dimension $2^n$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** gate info *** \n",
      " * gate name : DenseMatrix\n",
      " * target    : \n",
      " 0 : commute       \n",
      " 1 : commute       \n",
      " * control   : \n",
      " * Pauli     : no\n",
      " * Clifford  : no\n",
      " * Gaussian  : no\n",
      " * Parametric: no\n",
      " * Diagonal  : no\n",
      " * Matrix\n",
      "(0,0) (1,0) (0,0) (0,0)\n",
      "(1,0) (0,0) (0,0) (0,0)\n",
      "(0,0) (0,0) (0,0) (1,0)\n",
      "(0,0) (0,0) (1,0) (0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import DenseMatrix\n",
    "\n",
    "gate = DenseMatrix([0,1], [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]])\n",
    "print(gate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that the indices that are lower bits when counting the columns and rows of a gated matrix correspond to the order of the subscripts given during gate generation, so the list of subscripts acting in the above example has a different meaning for `[0,1]` and `[1,0]`. The following shows the difference when the indices are swapped."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.+0.j 1.+0.j 0.+0.j 0.+0.j]\n",
      "[0.+0.j 0.+0.j 1.+0.j 0.+0.j]\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "from qulacs.gate import DenseMatrix\n",
    "\n",
    "gate1 = DenseMatrix([0,1], [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]])\n",
    "gate2 = DenseMatrix([1,0], [[0,1,0,0],[1,0,0,0],[0,0,0,1],[0,0,1,0]])\n",
    "state = QuantumState(2)\n",
    "\n",
    "state.set_zero_state()\n",
    "gate1.update_quantum_state(state)\n",
    "print(state.get_vector())\n",
    "\n",
    "state.set_zero_state()\n",
    "gate2.update_quantum_state(state)\n",
    "print(state.get_vector())\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Append a control bit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A control bit can be added to the matrix gate using the `add_control_qubit` function. The first argument is the index of the control bit, the second argument is 0 or 1, and the operation is performed on target when the control bit has that value. For example, the CNOT gate performs a scan on target when the control bit has a value of 1, so the second argument is 1. Note that special named gates, such as the X gate, do not allow the control bit to be added. To add control bits to these, see \"Conversion to General Matrix Gates\" in the next section."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** gate info *** \n",
      " * gate name : DenseMatrix\n",
      " * target    : \n",
      " 1 : commute       \n",
      " * control   : \n",
      " 3 : value 1\n",
      " * Pauli     : no\n",
      " * Clifford  : no\n",
      " * Gaussian  : no\n",
      " * Parametric: no\n",
      " * Diagonal  : no\n",
      " * Matrix\n",
      "(0,0) (1,0)\n",
      "(1,0) (0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import DenseMatrix\n",
    "\n",
    "gate = DenseMatrix(1, [[0,1],[1,0]])\n",
    "gate.add_control_qubit(3,1)\n",
    "print(gate)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conversion to General Matrix Gates\n",
    "While special named gates such as X gates can update quantum states faster than general matrix gates, they cannot be modified with functions like the `add_control_qubit`. To process a gate based on a special gate, use the `to_matrix_gate` function to convert the special gate to a general gate."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** gate info *** \n",
      " * gate name : X\n",
      " * target    : \n",
      " 1 : commute X     \n",
      " * control   : \n",
      " * Pauli     : yes\n",
      " * Clifford  : yes\n",
      " * Gaussian  : no\n",
      " * Parametric: no\n",
      " * Diagonal  : no\n",
      "\n",
      " *** gate info *** \n",
      " * gate name : DenseMatrix\n",
      " * target    : \n",
      " 1 : commute X     \n",
      " * control   : \n",
      " * Pauli     : no\n",
      " * Clifford  : no\n",
      " * Gaussian  : no\n",
      " * Parametric: no\n",
      " * Diagonal  : no\n",
      " * Matrix\n",
      "(0,0) (1,0)\n",
      "(1,0) (0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import X, to_matrix_gate\n",
    "\n",
    "gate = X(1)\n",
    "print(gate)\n",
    "gate = to_matrix_gate(gate)\n",
    "print(gate)\n",
    "gate.add_control_qubit(3,1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Merge quantum gates\n",
    "\n",
    "You can merge multiple gates using the `merge` function in the `qulacs.gate` module. The merged gate behaves the same as when each gate is applied in order."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      "(0,-1)\n",
      "(0,-0)\n",
      " (0,0)\n",
      " (0,0)\n",
      "\n",
      " *** Quantum State ***\n",
      " * Qubit Count : 2\n",
      " * Dimension   : 4\n",
      " * State vector : \n",
      "(0,-1)\n",
      " (0,0)\n",
      " (0,0)\n",
      " (0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumState\n",
    "from qulacs.gate import X, Y, merge\n",
    "\n",
    "n = 2\n",
    "state1 = QuantumState(n)\n",
    "\n",
    "index = 1\n",
    "x_gate = X(index)\n",
    "y_gate = Y(index)\n",
    "\n",
    "x_gate.update_quantum_state(state1)\n",
    "y_gate.update_quantum_state(state1)\n",
    "print(state1)\n",
    "\n",
    "state2 = QuantumState(n)\n",
    "\n",
    "merged_gate = merge([x_gate, y_gate])\n",
    "merged_gate.update_quantum_state(state2)\n",
    "print(state2)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Obtain a gate matrix from a quantum gate"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The gate matrix of the generated quantum gate can be obtained with the `get_matrix` function. An important note is that for gates with controlled-qubit, the controlled-qubit is not included in the gate matrix. Thus, for example, the gate matrix of a CNOT gate is a 2x2 matrix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0.70710678+0.j  0.70710678+0.j]\n",
      " [ 0.70710678+0.j -0.70710678+0.j]]\n",
      "[[0.+0.j 1.+0.j]\n",
      " [1.+0.j 0.+0.j]]\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import H, CNOT\n",
    "\n",
    "h_gate = H(2)\n",
    "matrix = h_gate.get_matrix()\n",
    "print(matrix)\n",
    "cnot_gate = CNOT(1,2)\n",
    "matrix = cnot_gate.get_matrix()\n",
    "print(matrix)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Obtain a inverse gate from a quantum gate\n",
    "\n",
    "The hermitian conjugate `U†` of the generated quantum gate `U` can be obtained using a `get_inverse` function. The hermitian conjugate `U†` represents the inverse operation of the generated quantum gate `U`. If the hermitian conjugate function is not implemented, an exception is thrown."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "original:\n",
      " [[1.+0.j 0.+0.j]\n",
      " [0.+0.j 0.+1.j]]\n",
      "inversed:\n",
      " [[ 1.+0.j  0.+0.j]\n",
      " [ 0.+0.j -0.-1.j]]\n",
      "Exception occured as expected: this gate don't have get_inverse function\n"
     ]
    }
   ],
   "source": [
    "from qulacs.gate import S, DepolarizingNoise\n",
    "\n",
    "s_gate = S(2)\n",
    "s_dagger_gate = s_gate.get_inverse()\n",
    "print(\"original:\\n\", s_gate.get_matrix())\n",
    "print(\"inversed:\\n\", s_dagger_gate.get_matrix())\n",
    "\n",
    "noise_gate = DepolarizingNoise(0, 0.05)\n",
    "try:\n",
    "        noise_gate.get_inverse()\n",
    "        assert 0, \"No exception occured. should not reach here.\"\n",
    "except Exception as err:\n",
    "        print(f\"Exception occured as expected: {err}\")\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Quantum circuit"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Construct the quantum circuit"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quantum circuits are defined as the `QuantumCircuit` class. You can add a gate to the `QuantumCircuit` class as `add_<gatename>_gate` or add a gate instance using the `add_gate` function. You can `print` the quantum circuit to see the information about the quantum circuit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 60,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "*** Quantum Circuit Info ***\n",
      "# of qubit: 5\n",
      "# of step : 2\n",
      "# of gate : 3\n",
      "# of 1 qubit gate: 3\n",
      "Clifford  : yes\n",
      "Gaussian  : no\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumCircuit\n",
    "\n",
    "n = 5\n",
    "circuit = QuantumCircuit(n)\n",
    "circuit.add_H_gate(0)\n",
    "circuit.add_X_gate(2)\n",
    "\n",
    "from qulacs.gate import X\n",
    "gate = X(2)\n",
    "circuit.add_gate(gate)\n",
    "\n",
    "print(circuit)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note: The quantum circuit added by `add_gate` is released from memory when the quantum circuit is released. Therefore, the assigned gate cannot be reused. If you want to reuse the gate given as an argument, make a copy of itself using `gate.copy` or use the `add_gate_copy` function."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Operation of quantum circuits"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Quantum circuits can also update quantum states with `update_quantum_state` function like quantum gates."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 3\n",
      " * Dimension   : 8\n",
      " * State vector : \n",
      " (0.706223,0)\n",
      "        (0,0)\n",
      " (0.706223,0)\n",
      "        (0,0)\n",
      "(0,0.0353406)\n",
      "        (0,0)\n",
      "(0,0.0353406)\n",
      "        (0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumCircuit\n",
    "\n",
    "n=3\n",
    "circuit = QuantumCircuit(n)\n",
    "circuit.add_H_gate(1)\n",
    "circuit.add_RX_gate(2,0.1)\n",
    "\n",
    "from qulacs import QuantumState\n",
    "state = QuantumState(n)\n",
    "circuit.update_quantum_state(state)\n",
    "print(state)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Obtain a inverse circuit from a quantum circuit\n",
    "\n",
    "An inverse circuit of the quantum circuit can be obtained using a `get_inverse` function, as with the quantum gate.\n",
    "If the quantum circuit contains quantum gates such that hermitian transpose is not implemented, an exception is thrown."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      " *** Quantum State ***\n",
      " * Qubit Count : 3\n",
      " * Dimension   : 8\n",
      " * State vector : \n",
      "(1,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "(0,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "from qulacs import QuantumCircuit\n",
    "\n",
    "n=3\n",
    "circuit = QuantumCircuit(n)\n",
    "circuit.add_H_gate(1)\n",
    "circuit.add_RX_gate(2,0.1)\n",
    "\n",
    "inverse_circuit = circuit.get_inverse()\n",
    "\n",
    "from qulacs import QuantumState\n",
    "state = QuantumState(n)\n",
    "circuit.update_quantum_state(state)\n",
    "inverse_circuit.update_quantum_state(state)\n",
    "print(state)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3.9.10 64-bit",
   "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.4"
  },
  "vscode": {
   "interpreter": {
    "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
