{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "13858fc6-edc7-49cb-8b56-152ebec2f729",
   "metadata": {},
   "source": [
    "# Building Kernels\n",
    "\n",
    "This section will cover the most basic CUDA-Q construct, a quantum kernel. Topics include, building kernels, initializing states, and applying gate operations."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb9f7912-cb38-463c-b604-4657848b6f68",
   "metadata": {},
   "source": [
    "### Defining Kernels\n",
    "\n",
    "Kernels are the building blocks of quantum algorithms in CUDA-Q. A kernel is specified by using the following syntax. `cudaq.qubit` builds a register consisting of a single qubit, while `cudaq.qvector` builds a register of $N$ qubits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0c312467-3ba7-43fb-b820-d1ee2ced7fc3",
   "metadata": {},
   "outputs": [],
   "source": [
    "import cudaq"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "d9af7484-a69b-4239-b8b9-1abb47ee9421",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    A = cudaq.qubit()\n",
    "    B = cudaq.qvector(3)\n",
    "    C = cudaq.qvector(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "83459bfa-b0b8-4d6b-bb31-2e708da5a30e",
   "metadata": {},
   "source": [
    "Inputs to kernels are defined by specifying a parameter in the kernel definition along with the appropriate type. The kernel below takes an integer to define a register of N qubits."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "58af9585-a2c7-4059-983b-670962bdf65c",
   "metadata": {},
   "outputs": [],
   "source": [
    "N = 2\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel(N: int):\n",
    "    register = cudaq.qvector(N)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7866f4c5-4b8c-408b-8980-8ebca60f28b8",
   "metadata": {},
   "source": [
    "### Initializing states\n",
    "\n",
    "It is often helpful to define an initial state for a kernel. There are a few ways to do this in CUDA-Q. Note, method 5 is particularly useful for cases where the state of one kernel is passed into a second kernel to prepare its initial state.\n",
    "\n",
    "1. Passing complex vectors as parameters\n",
    "2. Capturing complex vectors\n",
    "3. Precision-agnostic API\n",
    "4. Define as CUDA-Q amplitudes\n",
    "5. Pass in a state from another kernel"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "57b5d6c5-b0b7-449d-ac0c-7166e2127c00",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(0.707107,0)\n",
      "       (0,0)\n",
      "       (0,0)\n",
      "(0.707107,0)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "# Passing complex vectors as parameters\n",
    "c = [.707 +0j, 0-.707j]\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel(vec: list[complex]):\n",
    "    q = cudaq.qvector(vec)\n",
    "\n",
    "\n",
    "# Capturing complex vectors\n",
    "c = [0.70710678 + 0j, 0., 0., 0.70710678]\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    q = cudaq.qvector(c)\n",
    "\n",
    "\n",
    "# Precision-Agnostic API\n",
    "import numpy as np\n",
    "\n",
    "c = np.array([0.70710678 + 0j, 0., 0., 0.70710678], dtype=cudaq.complex())\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    q = cudaq.qvector(c)\n",
    "\n",
    "# Define as CUDA-Q amplitudes\n",
    "c = cudaq.amplitudes([0.70710678 + 0j, 0., 0., 0.70710678])\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    q = cudaq.qvector(c)\n",
    "\n",
    "# Pass in a state from another kernel\n",
    "c = [0.70710678 + 0j, 0., 0., 0.70710678]\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel_initial():\n",
    "    q = cudaq.qvector(c)\n",
    "\n",
    "state_to_pass = cudaq.get_state(kernel_initial)\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel(state: cudaq.State):\n",
    "    q = cudaq.qvector(state)\n",
    "\n",
    "state = cudaq.get_state(kernel, state_to_pass)\n",
    "print(state)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7d5f68d7-80cf-4e2d-861c-7f945e17b6de",
   "metadata": {},
   "source": [
    "### Applying Gates\n",
    "\n",
    "\n",
    "After a kernel is constructed, gates can be applied to start building out a quantum circuit. All the predefined gates in CUDA-Q can be found [here](https://nvidia.github.io/cuda-quantum/latest/api/default_ops).\n",
    "\n",
    "\n",
    "Gates can be applied to all qubits in a register:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "8b93d86d-f6a1-4fb8-9efb-c7039ff4b383",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    register = cudaq.qvector(10)\n",
    "    h(register)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "490cb6c7-d9a2-4861-bd6d-8fddd764039c",
   "metadata": {},
   "source": [
    "Or, to individual qubits in a register:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "5491190b-fdaa-4203-9143-fbeb1519b074",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    register = cudaq.qvector(10)\n",
    "    h(register[0])  # first qubit\n",
    "    h(register[-1])  # last qubit"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22c0e379-80fc-43b0-bee0-41d1c6492585",
   "metadata": {},
   "source": [
    "### Controlled Operations\n",
    "\n",
    "Controlled operations are available for any gate and can be used by adding `.ctrl` to the end of any gate, followed by specification of the control qubit and the target qubit."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "68b48107-e326-4a8a-986b-eb2df6207538",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    register = cudaq.qvector(10)\n",
    "    x.ctrl(register[0], register[1])  # CNOT gate applied with qubit 0 as control"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b88044d5-f0d2-429a-824e-6e11617d5e75",
   "metadata": {},
   "source": [
    "### Multi-Controlled Operations\n",
    "\n",
    "It is valid for more than one qubit to be used for multi-controlled gates. The control qubits are specified as a list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "03843595-5b4b-4684-ad41-974f7c84470a",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    register = cudaq.qvector(10)\n",
    "    x.ctrl([register[0], register[1]], register[2])  # X applied to qubit two controlled by qubit 0 and 1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "66044f3f",
   "metadata": {},
   "source": [
    "You can also call a controlled kernel within a kernel: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "032e4bb3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{ 101:1000 }\n",
      "\n"
     ]
    }
   ],
   "source": [
    "@cudaq.kernel\n",
    "def x_kernel(qubit: cudaq.qubit):\n",
    "    x(qubit)\n",
    "    \n",
    "# A kernel that will call `x_kernel` as a controlled operation.\n",
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    \n",
    "    control_vector = cudaq.qvector(2)\n",
    "    target = cudaq.qubit()\n",
    "    \n",
    "    x(control_vector)\n",
    "    x(target)\n",
    "    x(control_vector[1])\n",
    "    cudaq.control(x_kernel, control_vector, target)\n",
    "\n",
    "# The above is equivalent to: \n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    qvector = cudaq.qvector(3)\n",
    "    x(qvector)\n",
    "    x(qvector[1])\n",
    "    x.ctrl([qvector[0], qvector[1]], qvector[2])\n",
    "    mz(qvector)\n",
    "\n",
    "\n",
    "results = cudaq.sample(kernel)\n",
    "print(results)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "47d6a1de-f659-4c58-b2ed-91d1682dad18",
   "metadata": {},
   "source": [
    "### Adjoint Operations\n",
    "\n",
    "The adjoint of a gate can be applied by appending the gate with the `adj` designation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "74db71e1-4046-454e-a434-2bd27fda2336",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    register = cudaq.qvector(10)\n",
    "    t.adj(register[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e61ba842-c969-4ad0-95ee-0916ec753b4f",
   "metadata": {},
   "source": [
    "### Custom Operations\n",
    "\n",
    "Custom gate operations can be specified using `cudaq.register_operation`. A one-dimensional Numpy array specifies the unitary matrix to be applied. The entries of the array read from top to bottom through the rows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "d51340c1-d211-4ddc-aaf4-ba59a91ba9cb",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "cudaq.register_operation(\"custom_x\", np.array([0, 1, 1, 0]))\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel():\n",
    "    qubits = cudaq.qvector(2)\n",
    "    h(qubits[0])\n",
    "    custom_x(qubits[0])\n",
    "    custom_x.ctrl(qubits[0], qubits[1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ef38ddbb-433c-49d6-a124-8da3ef034c04",
   "metadata": {},
   "source": [
    "### Building Kernels with Kernels\n",
    "\n",
    "For many complex applications, it is helpful for a kernel to call another kernel to perform a specific subroutine. The example blow shows how `kernel_A` can be caled within `kernel_B` to perform CNOT operations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "7d298eaf-08a4-4905-89d2-f52b79dc1484",
   "metadata": {},
   "outputs": [],
   "source": [
    "@cudaq.kernel\n",
    "def kernel_A(qubit_0: cudaq.qubit, qubit_1: cudaq.qubit):\n",
    "    x.ctrl(qubit_0, qubit_1)\n",
    "\n",
    "@cudaq.kernel\n",
    "def kernel_B():\n",
    "    reg = cudaq.qvector(10)\n",
    "    for i in range(5):\n",
    "        kernel_A(reg[i], reg[i + 1])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c7b3cb6b-74ca-49e2-9ea5-c9f69b0316ed",
   "metadata": {},
   "source": [
    "### Parameterized Kernels\n",
    "\n",
    "It is often useful to define parameterized circuit kernels which can be used for applications like VQE."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "d05544d4-fb57-49d7-84ff-d9dd090f49fb",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "     (0.9633,0)\n",
      " (0,-0.0115602)\n",
      "   (0.268157,0)\n",
      "(0,-0.00321804)\n",
      "\n"
     ]
    }
   ],
   "source": [
    "@cudaq.kernel\n",
    "def kernel(thetas: list[float]):\n",
    "    qubits = cudaq.qvector(2)\n",
    "    rx(thetas[0], qubits[0])\n",
    "    ry(thetas[1], qubits[1])\n",
    "\n",
    "thetas = [.024, .543]\n",
    "\n",
    "state = cudaq.get_state(kernel, thetas)\n",
    "print(state)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
