Switch to side-by-side view

--- a
+++ b/Code/All PennyLane QML Demos/36 GANs Cirq TensorFlow 29.5s kkawchak.ipynb
@@ -0,0 +1,714 @@
+{
+  "cells": [
+    {
+      "cell_type": "code",
+      "execution_count": 35,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "URenBt8iB4_G",
+        "outputId": "50111bc4-f5da-428b-c7c6-6d08fd86841d"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Time in seconds since beginning of run: 1693409355.3239555\n",
+            "Wed Aug 30 15:29:15 2023\n"
+          ]
+        }
+      ],
+      "source": [
+        "# This cell is added by sphinx-gallery\n",
+        "# It can be customized to whatever you like\n",
+        "%matplotlib inline\n",
+        "# !pip install pennylane\n",
+        "# !pip install pennylane-cirq\n",
+        "# !pip install tensorflow==2.8.1\n",
+        "# !pip install qsimcirq\n",
+        "import time\n",
+        "seconds = time.time()\n",
+        "print(\"Time in seconds since beginning of run:\", seconds)\n",
+        "local_time = time.ctime(seconds)\n",
+        "print(local_time)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "1xodSdeDB4_G"
+      },
+      "source": [
+        "Quantum generative adversarial networks with Cirq + TensorFlow {#quantum_GAN}\n",
+        "==============================================================\n",
+        "\n",
+        "::: {.meta}\n",
+        ":property=\\\"og:description\\\": This demo constructs and trains a Quantum\n",
+        "Generative Adversarial Network (QGAN) using PennyLane, Cirq, and\n",
+        "TensorFlow. :property=\\\"og:image\\\":\n",
+        "<https://pennylane.ai/qml/_images/qgan3.png>\n",
+        ":::\n",
+        "\n",
+        "*Author: Nathan Killoran --- Posted: 11 October 2019. Last updated: 30\n",
+        "January 2023.*\n",
+        "\n",
+        "This demo constructs a Quantum Generative Adversarial Network (QGAN)\n",
+        "([Lloyd and Weedbrook\n",
+        "(2018)](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.121.040502),\n",
+        "[Dallaire-Demers and Killoran\n",
+        "(2018)](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.012324))\n",
+        "using two subcircuits, a *generator* and a *discriminator*. The\n",
+        "generator attempts to generate synthetic quantum data to match a pattern\n",
+        "of \\\"real\\\" data, while the discriminator tries to discern real data\n",
+        "from fake data (see image below). The gradient of the discriminator's\n",
+        "output provides a training signal for the generator to improve its fake\n",
+        "generated data.\n",
+        "\n",
+        "|\n",
+        "\n",
+        "![](../demonstrations/QGAN/qgan.png){.align-center width=\"75.0%\"}\n",
+        "\n",
+        "|\n"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "Q8AMvcT-B4_H"
+      },
+      "source": [
+        "Using Cirq + TensorFlow\n",
+        "=======================\n",
+        "\n",
+        "PennyLane allows us to mix and match quantum devices and classical\n",
+        "machine learning software. For this demo, we will link together\n",
+        "Google\\'s [Cirq](https://cirq.readthedocs.io/en/stable/) and\n",
+        "[TensorFlow](https://www.tensorflow.org/) libraries.\n",
+        "\n",
+        "We begin by importing PennyLane, NumPy, and TensorFlow.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 36,
+      "metadata": {
+        "id": "jZnEogOMB4_H"
+      },
+      "outputs": [],
+      "source": [
+        "import numpy as np\n",
+        "import pennylane as qml\n",
+        "import tensorflow as tf"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "VE_Ig3zoB4_H"
+      },
+      "source": [
+        "We also declare a 3-qubit simulator device running in Cirq.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 37,
+      "metadata": {
+        "id": "qgDmLFu5B4_I"
+      },
+      "outputs": [],
+      "source": [
+        "dev = qml.device('cirq.qsim', wires=3)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "_6MqJwPsB4_I"
+      },
+      "source": [
+        "Generator and Discriminator\n",
+        "===========================\n",
+        "\n",
+        "In classical GANs, the starting point is to draw samples either from\n",
+        "some \\\"real data\\\" distribution, or from the generator, and feed them to\n",
+        "the discriminator. In this QGAN example, we will use a quantum circuit\n",
+        "to generate the real data.\n",
+        "\n",
+        "For this simple example, our real data will be a qubit that has been\n",
+        "rotated (from the starting state $\\left|0\\right\\rangle$) to some\n",
+        "arbitrary, but fixed, state.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 38,
+      "metadata": {
+        "id": "gjBLrnOnB4_I"
+      },
+      "outputs": [],
+      "source": [
+        "def real(angles, **kwargs):\n",
+        "    qml.Hadamard(wires=0)\n",
+        "    qml.Rot(*angles, wires=0)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "bPLhFqDEB4_I"
+      },
+      "source": [
+        "For the generator and discriminator, we will choose the same basic\n",
+        "circuit structure, but acting on different wires.\n",
+        "\n",
+        "Both the real data circuit and the generator will output on wire 0,\n",
+        "which will be connected as an input to the discriminator. Wire 1 is\n",
+        "provided as a workspace for the generator, while the discriminator's\n",
+        "output will be on wire 2.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 39,
+      "metadata": {
+        "id": "UoOK7QA9B4_I"
+      },
+      "outputs": [],
+      "source": [
+        "def generator(w, **kwargs):\n",
+        "    qml.Hadamard(wires=0)\n",
+        "    qml.RX(w[0], wires=0)\n",
+        "    qml.RX(w[1], wires=1)\n",
+        "    qml.RY(w[2], wires=0)\n",
+        "    qml.RY(w[3], wires=1)\n",
+        "    qml.RZ(w[4], wires=0)\n",
+        "    qml.RZ(w[5], wires=1)\n",
+        "    qml.CNOT(wires=[0, 1])\n",
+        "    qml.RX(w[6], wires=0)\n",
+        "    qml.RY(w[7], wires=0)\n",
+        "    qml.RZ(w[8], wires=0)\n",
+        "\n",
+        "\n",
+        "def discriminator(w):\n",
+        "    qml.Hadamard(wires=0)\n",
+        "    qml.RX(w[0], wires=0)\n",
+        "    qml.RX(w[1], wires=2)\n",
+        "    qml.RY(w[2], wires=0)\n",
+        "    qml.RY(w[3], wires=2)\n",
+        "    qml.RZ(w[4], wires=0)\n",
+        "    qml.RZ(w[5], wires=2)\n",
+        "    qml.CNOT(wires=[0, 2])\n",
+        "    qml.RX(w[6], wires=2)\n",
+        "    qml.RY(w[7], wires=2)\n",
+        "    qml.RZ(w[8], wires=2)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "BA6j3YglB4_I"
+      },
+      "source": [
+        "We create two QNodes. One where the real data source is wired up to the\n",
+        "discriminator, and one where the generator is connected to the\n",
+        "discriminator. In order to pass TensorFlow Variables into the quantum\n",
+        "circuits, we specify the `\"tf\"` interface.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 40,
+      "metadata": {
+        "id": "OLk5Bxs9B4_I"
+      },
+      "outputs": [],
+      "source": [
+        "@qml.qnode(dev, interface=\"tf\")\n",
+        "def real_disc_circuit(phi, theta, omega, disc_weights):\n",
+        "    real([phi, theta, omega])\n",
+        "    discriminator(disc_weights)\n",
+        "    return qml.expval(qml.PauliZ(2))\n",
+        "\n",
+        "\n",
+        "@qml.qnode(dev, interface=\"tf\")\n",
+        "def gen_disc_circuit(gen_weights, disc_weights):\n",
+        "    generator(gen_weights)\n",
+        "    discriminator(disc_weights)\n",
+        "    return qml.expval(qml.PauliZ(2))"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "VbnRyFpjB4_I"
+      },
+      "source": [
+        "QGAN cost functions\n",
+        "===================\n",
+        "\n",
+        "There are two cost functions of interest, corresponding to the two\n",
+        "stages of QGAN training. These cost functions are built from two pieces:\n",
+        "the first piece is the probability that the discriminator correctly\n",
+        "classifies real data as real. The second piece is the probability that\n",
+        "the discriminator classifies fake data (i.e., a state prepared by the\n",
+        "generator) as real.\n",
+        "\n",
+        "The discriminator is trained to maximize the probability of correctly\n",
+        "classifying real data, while minimizing the probability of mistakenly\n",
+        "classifying fake data.\n",
+        "\n",
+        "$$Cost_D = \\mathrm{Pr}(real|\\mathrm{fake}) - \\mathrm{Pr}(real|\\mathrm{real})$$\n",
+        "\n",
+        "The generator is trained to maximize the probability that the\n",
+        "discriminator accepts fake data as real.\n",
+        "\n",
+        "$$Cost_G = - \\mathrm{Pr}(real|\\mathrm{fake})$$\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 41,
+      "metadata": {
+        "id": "qZOU-2G1B4_I"
+      },
+      "outputs": [],
+      "source": [
+        "def prob_real_true(disc_weights):\n",
+        "    true_disc_output = real_disc_circuit(phi, theta, omega, disc_weights)\n",
+        "    # convert to probability\n",
+        "    prob_real_true = (true_disc_output + 1) / 2\n",
+        "    return prob_real_true\n",
+        "\n",
+        "\n",
+        "def prob_fake_true(gen_weights, disc_weights):\n",
+        "    fake_disc_output = gen_disc_circuit(gen_weights, disc_weights)\n",
+        "    # convert to probability\n",
+        "    prob_fake_true = (fake_disc_output + 1) / 2\n",
+        "    return prob_fake_true\n",
+        "\n",
+        "\n",
+        "def disc_cost(disc_weights):\n",
+        "    cost = prob_fake_true(gen_weights, disc_weights) - prob_real_true(disc_weights)\n",
+        "    return cost\n",
+        "\n",
+        "\n",
+        "def gen_cost(gen_weights):\n",
+        "    return -prob_fake_true(gen_weights, disc_weights)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "uf3SAVEMB4_J"
+      },
+      "source": [
+        "Training the QGAN\n",
+        "=================\n",
+        "\n",
+        "We initialize the fixed angles of the \\\"real data\\\" circuit, as well as\n",
+        "the initial parameters for both generator and discriminator. These are\n",
+        "chosen so that the generator initially prepares a state on wire 0 that\n",
+        "is very close to the $\\left| 1 \\right\\rangle$ state.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 42,
+      "metadata": {
+        "id": "IWGiPa4dB4_J"
+      },
+      "outputs": [],
+      "source": [
+        "phi = np.pi / 6\n",
+        "theta = np.pi / 2\n",
+        "omega = np.pi / 7\n",
+        "np.random.seed(0)\n",
+        "eps = 1e-2\n",
+        "init_gen_weights = np.array([np.pi] + [0] * 8) + \\\n",
+        "                   np.random.normal(scale=eps, size=(9,))\n",
+        "init_disc_weights = np.random.normal(size=(9,))\n",
+        "\n",
+        "gen_weights = tf.Variable(init_gen_weights)\n",
+        "disc_weights = tf.Variable(init_disc_weights)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "NVz7yaXGB4_J"
+      },
+      "source": [
+        "We begin by creating the optimizer:\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 43,
+      "metadata": {
+        "id": "8XQoiYjiB4_J"
+      },
+      "outputs": [],
+      "source": [
+        "opt = tf.keras.optimizers.SGD(0.4)"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "BrUfmFDWB4_J"
+      },
+      "source": [
+        "In the first stage of training, we optimize the discriminator while\n",
+        "keeping the generator parameters fixed.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 44,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "6_diImNYB4_J",
+        "outputId": "36b1db3c-3faf-4572-e281-08ea47a280ed"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Step 0: cost = -0.057277023792266846\n",
+            "Step 5: cost = -0.26348109543323517\n",
+            "Step 10: cost = -0.4273916482925415\n",
+            "Step 15: cost = -0.47261591255664825\n",
+            "Step 20: cost = -0.4840688854455948\n",
+            "Step 25: cost = -0.48946413397789\n",
+            "Step 30: cost = -0.4928188920021057\n",
+            "Step 35: cost = -0.49494941532611847\n",
+            "Step 40: cost = -0.4962703734636307\n",
+            "Step 45: cost = -0.49707190692424774\n"
+          ]
+        }
+      ],
+      "source": [
+        "cost = lambda: disc_cost(disc_weights)\n",
+        "\n",
+        "for step in range(50):\n",
+        "    opt.minimize(cost, disc_weights)\n",
+        "    if step % 5 == 0:\n",
+        "        cost_val = cost().numpy()\n",
+        "        print(\"Step {}: cost = {}\".format(step, cost_val))"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "dbm3F7-IB4_J"
+      },
+      "source": [
+        "At the discriminator's optimum, the probability for the discriminator to\n",
+        "correctly classify the real data should be close to one.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 45,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "URMw-4uBB4_J",
+        "outputId": "89ac7516-d3b1-497a-a98b-6b3609bdab29"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Prob(real classified as real):  0.9985873699188232\n"
+          ]
+        }
+      ],
+      "source": [
+        "print(\"Prob(real classified as real): \", prob_real_true(disc_weights).numpy())"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "7GO0YwksB4_J"
+      },
+      "source": [
+        "For comparison, we check how the discriminator classifies the\n",
+        "generator's (still unoptimized) fake data:\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 46,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "b4pWtXVHB4_J",
+        "outputId": "5d1c85ca-52f0-4f19-b670-859033c36c9b"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Prob(fake classified as real):  0.5011128038167953\n"
+          ]
+        }
+      ],
+      "source": [
+        "print(\"Prob(fake classified as real): \", prob_fake_true(gen_weights, disc_weights).numpy())"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "JGr9atT9B4_J"
+      },
+      "source": [
+        "In the adversarial game we now have to train the generator to better\n",
+        "fool the discriminator. For this demo, we only perform one stage of the\n",
+        "game. For more complex models, we would continue training the models in\n",
+        "an alternating fashion until we reach the optimum point of the\n",
+        "two-player adversarial game.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 47,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "NVrGCFK5B4_J",
+        "outputId": "01fd9a61-e30b-4cc8-ebc9-9977e48698fb"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Step 0: cost = -0.5833387225866318\n",
+            "Step 5: cost = -0.8915734887123108\n",
+            "Step 10: cost = -0.978424459695816\n",
+            "Step 15: cost = -0.9946485459804535\n",
+            "Step 20: cost = -0.9984995722770691\n",
+            "Step 25: cost = -0.9995637834072113\n",
+            "Step 30: cost = -0.999871701002121\n",
+            "Step 35: cost = -0.9999619722366333\n",
+            "Step 40: cost = -0.9999887645244598\n",
+            "Step 45: cost = -0.9999967217445374\n"
+          ]
+        }
+      ],
+      "source": [
+        "cost = lambda: gen_cost(gen_weights)\n",
+        "\n",
+        "for step in range(50):\n",
+        "    opt.minimize(cost, gen_weights)\n",
+        "    if step % 5 == 0:\n",
+        "        cost_val = cost().numpy()\n",
+        "        print(\"Step {}: cost = {}\".format(step, cost_val))"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "0K59ud11B4_J"
+      },
+      "source": [
+        "At the optimum of the generator, the probability for the discriminator\n",
+        "to be fooled should be close to 1.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 48,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "4_3Co4HYB4_J",
+        "outputId": "abc8f514-58eb-4ef6-aac2-943c7a116e5d"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Prob(fake classified as real):  0.9999988079071045\n"
+          ]
+        }
+      ],
+      "source": [
+        "print(\"Prob(fake classified as real): \", prob_fake_true(gen_weights, disc_weights).numpy())"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "uJR4z50uB4_J"
+      },
+      "source": [
+        "At the joint optimum the discriminator cost will be close to zero,\n",
+        "indicating that the discriminator assigns equal probability to both real\n",
+        "and generated data.\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 49,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "byWE_IY1B4_J",
+        "outputId": "9cc7eefa-7880-430b-8a10-5f6ad8b152a6"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Discriminator cost:  0.00141143798828125\n"
+          ]
+        }
+      ],
+      "source": [
+        "print(\"Discriminator cost: \", disc_cost(disc_weights).numpy())"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "Ubhr8ZSsB4_J"
+      },
+      "source": [
+        "The generator has successfully learned how to simulate the real data\n",
+        "enough to fool the discriminator.\n",
+        "\n",
+        "Let\\'s conclude by comparing the states of the real data circuit and the\n",
+        "generator. We expect the generator to have learned to be in a state that\n",
+        "is very close to the one prepared in the real data circuit. An easy way\n",
+        "to access the state of the first qubit is through its [Bloch\n",
+        "sphere](https://en.wikipedia.org/wiki/Bloch_sphere) representation:\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "execution_count": 50,
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "QZ2TkvG4B4_J",
+        "outputId": "e4f09de4-0ea1-4745-8d4b-543e597138f6"
+      },
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Real Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.21694186329841614>, <tf.Tensor: shape=(), dtype=float64, numpy=0.45048439502716064>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8660252690315247>]\n",
+            "Generator Bloch vector: [<tf.Tensor: shape=(), dtype=float64, numpy=-0.28404662013053894>, <tf.Tensor: shape=(), dtype=float64, numpy=0.4189322292804718>, <tf.Tensor: shape=(), dtype=float64, numpy=-0.8624440431594849>]\n"
+          ]
+        }
+      ],
+      "source": [
+        "obs = [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)]\n",
+        "\n",
+        "@qml.qnode(dev, interface=\"tf\")\n",
+        "def bloch_vector_real(angles):\n",
+        "    real(angles)\n",
+        "    return [qml.expval(o) for o in obs]\n",
+        "\n",
+        "@qml.qnode(dev, interface=\"tf\")\n",
+        "def bloch_vector_generator(angles):\n",
+        "    generator(angles)\n",
+        "    return [qml.expval(o) for o in obs]\n",
+        "\n",
+        "print(f\"Real Bloch vector: {bloch_vector_real([phi, theta, omega])}\")\n",
+        "print(f\"Generator Bloch vector: {bloch_vector_generator(gen_weights)}\")"
+      ]
+    },
+    {
+      "cell_type": "markdown",
+      "metadata": {
+        "id": "zAlOXLekB4_K"
+      },
+      "source": [
+        "About the author\n",
+        "================\n"
+      ]
+    },
+    {
+      "cell_type": "code",
+      "source": [
+        "seconds = time.time()\n",
+        "print(\"Time in seconds since end of run:\", seconds)\n",
+        "local_time = time.ctime(seconds)\n",
+        "print(local_time)"
+      ],
+      "metadata": {
+        "colab": {
+          "base_uri": "https://localhost:8080/",
+          "height": 0
+        },
+        "id": "lypK7PwdDujD",
+        "outputId": "976cd2cf-5439-44c4-fb14-cdcfd85d92b0"
+      },
+      "execution_count": 51,
+      "outputs": [
+        {
+          "output_type": "stream",
+          "name": "stdout",
+          "text": [
+            "Time in seconds since end of run: 1693409384.8585372\n",
+            "Wed Aug 30 15:29:44 2023\n"
+          ]
+        }
+      ]
+    }
+  ],
+  "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.9.17"
+    },
+    "colab": {
+      "provenance": []
+    }
+  },
+  "nbformat": 4,
+  "nbformat_minor": 0
+}