diff --git a/docs/unitary/getting_started.ipynb b/docs/unitary/getting_started.ipynb new file mode 100644 index 00000000..6f36fd00 --- /dev/null +++ b/docs/unitary/getting_started.ipynb @@ -0,0 +1,792 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [] + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "source": [ + "#Getting Started with Unitary\n", + "\n", + "The Unitary library enables a developer to create games based on quantum computing concepts.\n", + "\n", + "This API is under active design and development and is subject to change, perhaps radically, without notice.\n", + "\n", + "##Installation\n", + "\n", + "Unitary is not available as a PyPI package. You can either clone the repository and install the library from source, or install the library by using `pip` directly.\n", + "\n", + "###Install from source\n", + "\n", + "1. Clone the repository.\n", + "```python\n", + "git clone https://github.com/quantumlib/unitary.git\n", + "```\n", + "1. Change directory to the source code.\n", + "```python\n", + "cd unitary/\n", + "```\n", + "1. Run `pip install` in `unitary/`\n", + "```python\n", + "pip install .\n", + "```\n", + "\n", + "###Install through pip\n", + "_Before you begin: create a virtual environment for your project_\n", + "\n", + "Run `pip install` and use the `git+` option to pull the source code from GitHub.\n", + "```python\n", + "pip install --quiet git+https://github.com/quantumlib/unitary.git\n", + "```\n", + "If you’re using Google Colab, add an exclamation mark before your `pip` command:\n" + ], + "metadata": { + "id": "irZdBiE4SKuk" + } + }, + { + "cell_type": "code", + "source": [ + "!pip install --quiet git+https://github.com/quantumlib/unitary.git" + ], + "metadata": { + "id": "5l1LgX3McVqL", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "cde870b0-4aa5-4902-9f64-bad2bafc8235" + }, + "execution_count": 1, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "##Start using Unitary\n", + "Unitary is now ready for you to use. Import the alpha library into the relevant source file.\n" + ], + "metadata": { + "id": "MDYklwf1SVtU" + } + }, + { + "cell_type": "code", + "source": [ + "import unitary.alpha as alpha" + ], + "metadata": { + "id": "mqIsfUtbS1Li" + }, + "execution_count": 2, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "###Create a quantum object\n", + "A Quantum Object represents an object in a game that can have a quantum state. Almost all objects have some state: position, color, orientation in game space, and so on. Some game objects can change their state during game play. A quantum state means that the classical state of the object (e.g. Color.GREEN) is uncertain until the game needs to use the classical state. At that point we observe (measure) the quantum state to retrieve a classical state.\n", + "\n", + "For example, let’s create a 5x5 game board, 25 squares in total. Each square can have one of two states: empty or full. If we use quantum objects to create our game board squares, we can enable richer game play by deferring the decision about whether a piece occupies a square or not until the game needs to generate a score.\n", + "\n", + "You can use an enumeration to easily track the state of each square.\n", + "\n" + ], + "metadata": { + "id": "rq1c6metS1hB" + } + }, + { + "cell_type": "code", + "source": [ + "import enum\n", + "\n", + "class Square(enum.Enum):\n", + " EMPTY=0\n", + " FULL=1" + ], + "metadata": { + "id": "ZXfIc2WSS_bc" + }, + "execution_count": 3, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "A `QuantumObject` requires a name and an initial state.\n" + ], + "metadata": { + "id": "F3rdnB4fS_tk" + } + }, + { + "cell_type": "code", + "source": [ + "example_square = alpha.QuantumObject('b1', Square.EMPTY)" + ], + "metadata": { + "id": "TIabDZqxTKUz" + }, + "execution_count": 4, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now let's create a collection of quantum objects to represent our game board. An empty game board isn't very useful, so we also populate our board with a single row of tokens at the near edge (rank 1) and far edge (rank 5).\n" + ], + "metadata": { + "id": "Gb294SuiTKij" + } + }, + { + "cell_type": "code", + "source": [ + "board = {}\n", + "for col in \"abcde\":\n", + " for rank in \"234\":\n", + " square_name = col + rank\n", + " board[square_name] = alpha.QuantumObject(square_name, Square.EMPTY)\n", + " for rank in \"15\":\n", + " square_name = col + rank\n", + " board[square_name] = alpha.QuantumObject(square_name, Square.FULL)" + ], + "metadata": { + "id": "Nj3noY0yTjsM" + }, + "execution_count": 5, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "###Create a Quantum World\n", + "A Quantum World is a container that enables you to define a scope for Quantum Objects. All quantum objects in your Quantum World have the opportunity to interact with each other. The scope enables us to reason about the probability of a Quantum Object having one state versus another.\n", + "\n", + "A `QuantumObject` must belong to a `QuantumWorld` object before you can use it. A `QuantumObject` can only belong to a single `QuantumWorld`.\n", + "\n", + "Let’s create a `QuantumWorld` object to hold our game board.\n" + ], + "metadata": { + "id": "dkRzcgm_TkEI" + } + }, + { + "cell_type": "code", + "source": [ + "game_board = alpha.QuantumWorld(board.values())" + ], + "metadata": { + "id": "PnEEpH_2TzhP" + }, + "execution_count": 6, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "Now you can see the status of the Quantum Objects in a `QuantumWorld` by using the `peek` method." + ], + "metadata": { + "id": "cDr6bhstTz0m" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Square a1 is the near edge-square; it should be full:\")\n", + "print(game_board.peek([board[\"a1\"]]))\n", + "print(\"Square c3 is the center square on the board; it should be empty:\")\n", + "print(game_board.peek([board[\"c3\"]]))" + ], + "metadata": { + "id": "IgwUCMvyT4_3", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "8dc6d4f8-db52-4c07-8f9a-bc8e4db0c868" + }, + "execution_count": 7, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Square a1 is the near edge-square; it should be full:\n", + "[[]]\n", + "Square c3 is the center square on the board; it should be empty:\n", + "[[]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Note: If you use the `peek` method with no arguments, the `QuantumWorld` returns a sample of all the `QuantumObjects` that belong to the Quantum World.\n", + "\n" + ], + "metadata": { + "id": "n7TzaYjRT5WI" + } + }, + { + "cell_type": "code", + "source": [ + "print(game_board.peek())" + ], + "metadata": { + "id": "jC8Ov-KJWGsT", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "80c366e8-55f3-42c6-81c5-cd637f6563dd" + }, + "execution_count": 8, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[, , , , , , , , , , , , , , , , , , , , , , , , ]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "###Apply Quantum Effects\n", + "When you want to change the quantum state to reflect game play, you apply a Quantum Effect to one or more Quantum Objects. For example, you can change the state of any square by using the `Flip` effect (to “flip” the bit, or state; on to off, green to red, and so on)." + ], + "metadata": { + "id": "QjaMdrJkXyhq" + } + }, + { + "cell_type": "code", + "source": [ + "flip = alpha.Flip()\n", + "flip(board[\"c3\"])" + ], + "metadata": { + "id": "-CGbx5AVXzB2" + }, + "execution_count": 9, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "If we `peek` at our game board, the c3 square should be in the `Square.FULL` state.\n" + ], + "metadata": { + "id": "M49zFLsgYV7g" + } + }, + { + "cell_type": "code", + "source": [ + "print(game_board.peek([board[\"c3\"]]))" + ], + "metadata": { + "id": "Po-AimKRYWRz", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "f6ec636f-d673-4526-cb5e-095c8c2bd5d3" + }, + "execution_count": 10, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "We could manually implement basic movement by flipping the state of our source square and our target square." + ], + "metadata": { + "id": "Tb0z8RqwYWn5" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Square a1 and a2 before the move:\")\n", + "print(game_board.peek([board[\"a1\"], board[\"a2\"]]))\n", + "\n", + "flip(board[\"a1\"])\n", + "flip(board[\"a2\"])\n", + "\n", + "print(\"Square a1 and a2 after the move:\")\n", + "print(game_board.peek([board[\"a1\"], board[\"a2\"]]))" + ], + "metadata": { + "id": "JmmqRGybbPvo", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "12fccf2f-6a74-4210-aff5-c456834e8264" + }, + "execution_count": 11, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Square a1 and a2 before the move:\n", + "[[, ]]\n", + "Square a1 and a2 after the move:\n", + "[[, ]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "However, the `QuantumEffects` API provides a number of convenience methods for basic game actions. For example, we can do a basic move by applying the `Move` effect." + ], + "metadata": { + "id": "G2hmFRrVcG0w" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Square b1 and b2 before the move:\")\n", + "print(game_board.peek([board[\"b1\"], board[\"b2\"]]))\n", + "\n", + "move = alpha.Move()\n", + "move(board[\"b1\"], board[\"b2\"])\n", + "\n", + "print(\"Square b1 and b2 after the move:\")\n", + "print(game_board.peek([board[\"b1\"], board[\"b2\"]]))" + ], + "metadata": { + "id": "aCDT_tV8cHFH", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "940d45e5-bb07-41dc-9d33-042897dd7741" + }, + "execution_count": 12, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Square b1 and b2 before the move:\n", + "[[, ]]\n", + "Square b1 and b2 after the move:\n", + "[[, ]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "###Retrieve the classical state\n", + "Similarly, you can use a `Split` effect to create a superposition. In our example, we can create uncertainty about the actual location of our token by creating a superposition.\n", + "\n", + "Let’s create a superposition by splitting the token from square d1 to squares c2 and e2.\n", + "\n", + "\n" + ], + "metadata": { + "id": "PEUkhB7QgK3u" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Squares d1, c2, and e2 before the split:\")\n", + "print(game_board.peek([board[\"d1\"], board[\"c2\"], board[\"e2\"]]))\n", + "\n", + "split = alpha.Split()\n", + "split(board[\"d1\"], board[\"c2\"], board[\"e2\"])" + ], + "metadata": { + "id": "A42fDEkcfVi7", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "bcc622e0-26a7-4a92-c61c-e7b22d35aa12" + }, + "execution_count": 13, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Squares d1, c2, and e2 before the split:\n", + "[[, , ]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Before we validate whether the `Split` effect worked, let’s consider what we expect to see.\n", + "\n", + "Each square has a 50% probability of hosting our token. When we look at our target squares, we observe their classical state at the moment that we observe them, not a probability. If you `peek` at one or both squares once, you see a definite classical state.\n" + ], + "metadata": { + "id": "ovCCuKtvgvfK" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Squares d1, c2, and e2 after the split:\")\n", + "print(game_board.peek([board[\"d1\"], board[\"c2\"], board[\"e2\"]]))" + ], + "metadata": { + "id": "LCenmPnswx9i", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "ce17f95a-28cc-4560-ecf6-5c0a3fdbc4a4" + }, + "execution_count": 14, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Squares d1, c2, and e2 after the split:\n", + "[[, , ]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "If you `peek` again, you might see a different state (or, you might see the same state).\n" + ], + "metadata": { + "id": "dKj5pgFlw4p6" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Squares c2 and e2 again:\")\n", + "print(game_board.peek([board[\"c2\"], board[\"e2\"]], count=10))" + ], + "metadata": { + "id": "CWIx1vRTw5pR", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "6b07ac36-325a-42f9-8805-aae3d4611ebf" + }, + "execution_count": 15, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Squares c2 and e2 again:\n", + "[[, ], [, ], [, ], [, ], [, ], [, ], [, ], [, ], [, ], [, ]]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "The more often you `peek` at the squares, the closer you get to observing the token in each square about half the time. That’s how we calculate the probability of the outcomes.\n" + ], + "metadata": { + "id": "gsXPVKa5xB7d" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Square c2, observed 100 times:\")\n", + "print(game_board.peek([board[\"c2\"]], 100))" + ], + "metadata": { + "id": "MQsjoCPExEUB", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "ad9d10cd-560a-49e4-92ab-e55814daf52f" + }, + "execution_count": 16, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Square c2, observed 100 times:\n", + "[[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "You can use the `get_histogram` method to retrieve a histogram of the states of any `QuantumObject` in your `QuantumWorld`." + ], + "metadata": { + "id": "BRZT7yc4xHXB" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Histogram of square c2:\")\n", + "print(game_board.get_histogram([board[\"c2\"]]))\n", + "\n", + "print(\"Historgram of square c2 and e2:\")\n", + "print(game_board.get_histogram([board[\"c2\"]]), game_board.get_histogram([board[\"e2\"]]))" + ], + "metadata": { + "id": "hIlsAmH2xKv_", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "bfff635c-d5e3-4de4-f02d-b2c37fad70d7" + }, + "execution_count": 17, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Histogram of square c2:\n", + "[{0: 44, 1: 56}]\n", + "Historgram of square c2 and e2:\n", + "[{0: 46, 1: 54}] [{0: 48, 1: 52}]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "By default, the `get_histogram` method samples your `QuantumWorld` 100 times. You can use the `count` option to change the number of samples that `get_histogram` uses to calculate the probability distribution.\n", + "\n", + "When you’re ready to use the classical state, use the `pop` method to retrieve the classical state from your `QuantumWorld`. After you `pop` the classical state, subsequent calls to the `peek` method gives you a stable answer. " + ], + "metadata": { + "id": "49gRYvF-xTQL" + } + }, + { + "cell_type": "code", + "source": [ + "print(\"Final state of square c2 and e2:\")\n", + "game_board.pop([board[\"c2\"], board[\"e2\"]])\n", + "\n", + "print(\"Histogram of square c2, after a call to `pop`:\")\n", + "print(game_board.get_histogram([board[\"c2\"]], count=10))" + ], + "metadata": { + "id": "ROjBpmL5gMot", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "6299194e-a244-4736-983e-e3bb64d3762c" + }, + "execution_count": 18, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Final state of square c2 and e2:\n", + "Histogram of square c2, after a call to `pop`:\n", + "[{0: 0, 1: 10}]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "###Control flow in quantum and classical states separately\n", + "You cannot control the flow of your quantum state from your classical game logic, and vice versa. However, Unitary provides a `quantum_if` method to modify the state of one `QuantumObject` based on the state of another.\n", + "\n", + "For an example, we start with a scenario where we know the outcome, then explore a scenario where the outcome is conditional.\n", + "\n", + "Before we begin, let’s destroy the token on c3 that we created earlier. Set the square to the `Square.EMPTY` state." + ], + "metadata": { + "id": "ula44YXHfUjZ" + } + }, + { + "cell_type": "code", + "source": [ + "flip(board[\"c3\"])" + ], + "metadata": { + "id": "QRXxbsAr5gRk" + }, + "execution_count": 19, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "On our board, let’s assume a rule that a token can slide to another square only when the path is empty. If we move the token from b5 to b4, the token on a5 cannot reach square c3 (blocked by token on b4)." + ], + "metadata": { + "id": "GF75_4cxxjgr" + } + }, + { + "cell_type": "code", + "source": [ + "move(board[\"b5\"], board[\"b4\"])\n", + "\n", + "alpha.quantum_if(board[\"b4\"]).equals(Square.EMPTY).apply(move)(board[\"a5\"], board[\"c3\"])\n", + "\n", + "print(game_board.peek([board[\"a5\"]]))\n", + "print(game_board.peek([board[\"b4\"]]))\n", + "print(game_board.peek([board[\"c3\"]]),100)" + ], + "metadata": { + "id": "UJqtNGEVxj1M", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "96cd58e7-3db3-4ca3-f079-e97969a08cbe" + }, + "execution_count": 20, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[]]\n", + "[[]]\n", + "[[]] 100\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "Let’s move the token on b4 back to b5, then try again." + ], + "metadata": { + "id": "a0Uwde3fxsoj" + } + }, + { + "cell_type": "code", + "source": [ + "move(board[\"b4\"], board[\"b5\"])\n", + "\n", + "alpha.quantum_if(board[\"b4\"]).equals(Square.EMPTY).apply(move)(board[\"a5\"], board[\"c3\"])\n", + "\n", + "print(game_board.peek([board[\"a5\"]]))\n", + "print(game_board.peek([board[\"b4\"]]))\n", + "print(game_board.peek([board[\"c3\"]],100))" + ], + "metadata": { + "id": "MVcehSy-xs2D", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "ee562798-9e2e-4854-e6a1-74d68d18f6ad" + }, + "execution_count": 21, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[]]\n", + "[[]]\n", + "[[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "The token on the B file was in a classical state, so it blocked the progress of the token on the A file. After we removed the token on the B file, the token on the A-file was free to move.\n", + "\n", + "Let’s put the token on the B file in a superposition, and observe what happens to the token on the A file." + ], + "metadata": { + "id": "qSUrYi7pxy47" + } + }, + { + "cell_type": "code", + "source": [ + "\n", + "split(board[\"b5\"], board[\"b4\"], board[\"c4\"])\n", + "\n", + "alpha.quantum_if(board[\"b4\"]).equals(Square.EMPTY).apply(move)(board[\"a5\"], board[\"c3\"])\n", + "\n", + "print(game_board.peek([board[\"a5\"]]))\n", + "print(game_board.get_histogram([board[\"b4\"]]))\n", + "print(game_board.get_histogram([board[\"c3\"]]))" + ], + "metadata": { + "id": "8TzmADhqxzLa", + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "a6e630b9-9733-4086-bfa8-79368080f1c8" + }, + "execution_count": 22, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[[]]\n", + "[{0: 44, 1: 56}]\n", + "[{0: 50, 1: 50}]\n" + ] + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "By passing a token in a classical state (a5) through a token in a quantum state (b4), we entangled the classical token and made its outcome quantum. When you `pop` the classical state of the b4/c4 token, the Quantum World also calculates the classical state of the a5/c3 token. You find the token on a5 if the b4/c4 token is on b4, or on c3 if the b4/c4 token is on c4.\n", + "\n", + "\n", + "\n", + "Note that the `apply` method only accepts a `QuantumEffect` object. You cannot provide classical control flow to the quantum state." + ], + "metadata": { + "id": "1jTXi7clx6SF" + } + } + ] +}