From 83351a0e27ae8e70083ddcef8bb297caf081e552 Mon Sep 17 00:00:00 2001 From: Katherine Date: Sun, 19 Apr 2020 19:00:09 -0700 Subject: [PATCH] adding simons algorithm tutorial --- .../ExploringSimonsAlgorithm.csproj | 25 ++ ...xploringSimonsAlgorithmClassicalPart.ipynb | 218 +++++++++++ .../ExploringSimonsAlgorithmQuantumPart.ipynb | 338 ++++++++++++++++++ tutorials/ExploringSimonsAlgorithm/README.md | 12 + .../ReferenceImplementation.qs | 67 ++++ tutorials/ExploringSimonsAlgorithm/Tasks.qs | 75 ++++ .../TestSuiteRunner.cs | 41 +++ tutorials/ExploringSimonsAlgorithm/Tests.qs | 43 +++ tutorials/ExploringSimonsAlgorithm/tests.py | 139 +++++++ 9 files changed, 958 insertions(+) create mode 100644 tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithm.csproj create mode 100644 tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmClassicalPart.ipynb create mode 100644 tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmQuantumPart.ipynb create mode 100644 tutorials/ExploringSimonsAlgorithm/README.md create mode 100644 tutorials/ExploringSimonsAlgorithm/ReferenceImplementation.qs create mode 100644 tutorials/ExploringSimonsAlgorithm/Tasks.qs create mode 100644 tutorials/ExploringSimonsAlgorithm/TestSuiteRunner.cs create mode 100644 tutorials/ExploringSimonsAlgorithm/Tests.qs create mode 100644 tutorials/ExploringSimonsAlgorithm/tests.py diff --git a/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithm.csproj b/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithm.csproj new file mode 100644 index 00000000000..d282a094a2b --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithm.csproj @@ -0,0 +1,25 @@ + + + netcoreapp3.1 + x64 + Quantum.Kata.ExploringSimonsAlgorithm + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmClassicalPart.ipynb b/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmClassicalPart.ipynb new file mode 100644 index 00000000000..9aac9332e1c --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmClassicalPart.ipynb @@ -0,0 +1,218 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exploring Simon's Periodicity Algorithm: Classical Component\n", + "\n", + "This is a companion notebook to the [Exploring Simon's Periodicity Algorithm: Quantum Component](./ExploringSimonsAlgorithmQuantumPart.ipynb) tutorial. It will teach you about the classical post-processing component of Simon's Algorithm." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Classical Post-Processing for Simon's Algorithm\n", + "\n", + "The quantum circuit we ran at the end of the companion tutorial output the binary string $\\mathbf{j}$ with length $n$, where $\\mathbf{j} \\in\\ \\{0, 1\\}^n$ and satisfies $\\mathbf{s} \\cdot\\ \\mathbf{j} = 0$. Is this enough information to solve for $\\mathbf{s}$?\n", + "\n", + "The answer is yes, but with caveats. We need to run the quantum circuit $n - 1$ times to produce $n - 1$ binary strings $\\mathbf{j_{1}}, ... , \\mathbf{j_{n-1}}$ such that\n", + "\n", + "$$\\mathbf{j_{1}} \\cdot\\ \\mathbf{s} = 0$$\n", + "$$\\mathbf{j_{2}} \\cdot\\ \\mathbf{s} = 0$$\n", + "$$.$$\n", + "$$.$$\n", + "$$.$$\n", + "$$\\mathbf{j_{n-1}} \\cdot\\ \\mathbf{s} = 0$$\n", + "\n", + "However, we still have a non-trivial chance of failure. To arrive at a solution, $\\mathbf{j_{1}}, ... , \\mathbf{j_{n-1}}$ must be linearly independent and we must also be lucky.\n", + "\n", + "If we have linear independence, we can solve the under-determined system of $n-1$ equations and $n$ unknowns to get a candidate solution $\\mathbf{s'}$. We can then test this candidate solution, and if we find that $f(0^n) = f(\\mathbf{s'})$, we know that $\\mathbf{s} = \\mathbf{s'}$. The problem is solved!\n", + "\n", + "A standard way to solve this system employs [Gaussian elimination with back substitution](http://mathonline.wikidot.com/gaussian-elimination-and-back-substitution). Links to resources are provided if you need a refresher on concepts like [Gaussian elimination](https://en.wikipedia.org/wiki/Gaussian_elimination). Let's practice writing functions in Python to help us achieve our goal!\n", + "\n", + "> **Note:** The [linear algebra tutorial](https://github.com/microsoft/QuantumKatas/blob/master/tutorials/LinearAlgebra/LinearAlgebra.ipynb) notebook contains example implementations of some linear algebra functions in Python.\n", + "\n", + "Let's start by setting up a few things necessary for testing the exercises. **Do not skip this step.**\n", + "\n", + "Click the cell with code below this block of text and press `Ctrl+Enter` (`shift+return` on Mac)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Success!\n" + ] + } + ], + "source": [ + "# Run this cell using Ctrl+Enter (shift+return on Mac).\n", + "from tests import exercise\n", + "from typing import List\n", + "\n", + "Matrix = List[List[float]]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Recall that the goal of Gaussian elimination is to transform a matrix to [row echelon form](https://en.wikipedia.org/wiki/Row_echelon_form); i.e. into an [upper triangular matrix](https://en.wikipedia.org/wiki/Triangular_matrix). To achieve this form, there are three types of elementary row operations we can perform:\n", + "* Swapping two rows,\n", + "* Multiplying a row by a nonzero number, and\n", + "* Adding a multiple of one row to another row.\n", + "\n", + "Let's try using these operations to transform a matrix to row echelon form in Python.\n", + "\n", + "## Exercise 3. Row echelon form of a matrix\n", + "\n", + "**Input:**\n", + "\n", + "* A matrix $\\mathbf{J}$ of dimensions $n-1$ x $n$\n", + "\n", + "**Output:**\n", + "\n", + "* [Upper triangular form](https://en.wikipedia.org/wiki/Triangular_matrix) of the matrix $\\mathbf{J}$\n", + "\n", + "**Example:**\n", + "\n", + "$\\begin{bmatrix} 0 & 1 & 1 \\\\ 1 & 1 & 1 \\end{bmatrix}$ should be transformed to $\\begin{bmatrix} 1 & 1 & 1 \\\\ 0 & 1 & 1 \\end{bmatrix}$\n", + "\n", + "> **Note:** Beware of [singular matrices](https://en.wikipedia.org/wiki/Singular_matrix)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@exercise\n", + "def row_echelon_form(J : Matrix) -> Matrix: \n", + " # ...\n", + " \n", + " return ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The final step in Gaussian elimination is to solve the system using [back substitution](https://algowiki-project.org/en/Backward_substitution).\n", + "\n", + "The matrix $\\begin{bmatrix} 1 & 1 & 1 \\\\ 0 & 1 & 1 \\end{bmatrix}$ can be represented as the system of equations\n", + "\n", + "$$x_0 + x_1 = 1$$\n", + "$$ x_1 = 1$$\n", + "\n", + "Substituting $x_1 = 1$ into the first equation yields the result $x_0 = 0$ and our system is solved. We can return $[x_0, x_1] = [0, 1]$.\n", + "\n", + "Next, try implementing a Python function to perform back substitution.\n", + "\n", + "## Exercise 4. Back substitution\n", + "\n", + "**Input:**\n", + "\n", + "* A matrix $\\mathbf{J}$ in row echelon form that contains a unique solution\n", + "\n", + "**Output:**\n", + "\n", + "* The unique solution as a list $[x_0, ..., x_n]$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@exercise\n", + "def back_substitution(J : Matrix) -> List: \n", + " # ...\n", + " \n", + " return ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We learned earlier that the output of our quantum circuit is some $\\mathbf{j}$ that satisfied the property $\\mathbf{s} \\cdot\\ \\mathbf{j} = 0$. This property is more accurately described as $\\mathbf{s} \\cdot\\ \\mathbf{j} = 0$ modulo $2$.\n", + "\n", + "Now let's implement a Python function to compute the [dot product](https://en.wikipedia.org/wiki/Dot_product) mod 2.\n", + "\n", + "## Exercise 5: Dot product modulo 2\n", + "\n", + "**Inputs:**\n", + "\n", + "1. A vector $\\mathbf{a} = [a_0, ... , a_n]$\n", + "2. A vector $\\mathbf{b} = [b_0, ... , b_n]$\n", + "\n", + "**Output:**\n", + "\n", + "The scalar value that represents the dot product of $\\mathbf{a}$ and $\\mathbf{b}$ mod $2$\n", + "\n", + "**Examples:**\n", + "\n", + "* If $\\mathbf{a} = [1, 0, 0, 1]$ and $\\mathbf{b} = [1, 0, 1, 0]$, the function should output $1$\n", + "* If $\\mathbf{a} = [1, 1, 1]$ and $\\mathbf{b} = [0, 1, 1]$, the function should output $0$\n", + "\n", + "> **Hint:** This function should only output 0 or 1" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "@exercise\n", + "def dot_product(a : Matrix, b : Matrix) -> float:\n", + " # ...\n", + " \n", + " return ..." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this stage you have learned the tools necessary for the classical post-processing part of Simon's algorithm. This concludes the discussion of Simon's peridocity algorithm.\n", + "\n", + "# What's next?\n", + "\n", + "We hope you've enjoyed this tutorial and learned a lot from it! If you're looking to learn more about quantum computing and Q#, here are some suggestions:\n", + "* The [Quantum Katas](https://github.com/microsoft/QuantumKatas/) are sets of programming exercises on quantum computing that can be solved using Q#. They cover a variety of topics, from the basics like the concepts of superposition and measurements to more interesting algorithms like Grover's search.\n", + "* In particular, [SimonsAlgorithm quantum kata](https://github.com/microsoft/QuantumKatas/tree/master/SimonsAlgorithm) offers more exercises on quantum oracles and a different presentation of Simon's algorithm." + ] + } + ], + "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.7.1" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmQuantumPart.ipynb b/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmQuantumPart.ipynb new file mode 100644 index 00000000000..5a0ae18d32a --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/ExploringSimonsAlgorithmQuantumPart.ipynb @@ -0,0 +1,338 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Exploring Simon's Periodicity Algorithm: Quantum Component\n", + "\n", + "**Simon's periodicity algorithm** is a well-known oracle-based quantum computing algorithm about finding patterns in functions. It is famous for solving [Simon's problem](https://en.wikipedia.org/wiki/Simon%27s_problem) exponentially faster than any known classical algorithm. Simon's algorithm contains both quantum and classical procedures. While the algorithm itself has little practical value, it is a useful learning tool for illustrating important concepts like quantum oracles. As such, Simon's algorithm is part of many introductory courses on quantum computing.\n", + "\n", + "This tutorial will:\n", + "* introduce you to the problem solved by Simon's algorithm,\n", + "* walk you through the overview of the algorithm,\n", + "* give you practice implementing relevant quantum oracles using the Q# programming language, and\n", + "* give you practice implementing the quantum circuit for Simon's algorithm.\n", + "\n", + "In the last section of the tutorial you will continue exploration of Simon's algorithm in a [companion Python notebook](./ExploringSimonsAlgorithmClassicalPart.ipynb) that will introduce you to the classical component of the algorithm.\n", + "\n", + "Let's go!\n", + "\n", + "To begin, first prepare this notebook for execution (if you skip the first step, you'll get the \"Syntax does not match any known patterns\" error when you try to execute Q# code in the next cells; if you skip the second step, you'll get the \"Invalid test name\" error).\n", + "\n", + "Click the cell with code below this block of text and press `Ctrl+Enter` (`shift+return` on Mac)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "[\"Microsoft.Quantum.Standard::0.10.1912.0501\",\"Microsoft.Quantum.Katas::0.10.1912.0501\"]", + "text/html": [ + "" + ], + "text/plain": [ + "Microsoft.Quantum.Standard::0.10.1912.0501, Microsoft.Quantum.Katas::0.10.1912.0501" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%package Microsoft.Quantum.Katas::0.11.2003.3107" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> **Note:** The package versions in the output of the cell above should always match. If you are running the Notebooks locally and the versions do not match, please install the IQ# version that matches the version of the Microsoft.Quantum.Katas package." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "[\"Quantum.Kata.ExploringSimonsAlgorithm.ApplyOracleWithOutputArrA\",\"Quantum.Kata.ExploringSimonsAlgorithm.AssertTwoOraclesWithOutputArrAreEqual\",\"Quantum.Kata.ExploringSimonsAlgorithm.Bitwise_LeftShift_Oracle\",\"Quantum.Kata.ExploringSimonsAlgorithm.Bitwise_LeftShift_Oracle_Reference\",\"Quantum.Kata.ExploringSimonsAlgorithm.E1_QuantumOracle_Test\",\"Quantum.Kata.ExploringSimonsAlgorithm.Simon_Algorithm\",\"Quantum.Kata.ExploringSimonsAlgorithm.Simons_Algorithm_Reference\"]", + "text/html": [ + "" + ], + "text/plain": [ + "Quantum.Kata.ExploringSimonsAlgorithm.ApplyOracleWithOutputArrA, Quantum.Kata.ExploringSimonsAlgorithm.AssertTwoOraclesWithOutputArrAreEqual, Quantum.Kata.ExploringSimonsAlgorithm.Bitwise_LeftShift_Oracle, Quantum.Kata.ExploringSimonsAlgorithm.Bitwise_LeftShift_Oracle_Reference, Quantum.Kata.ExploringSimonsAlgorithm.E1_QuantumOracle_Test, Quantum.Kata.ExploringSimonsAlgorithm.Simon_Algorithm, Quantum.Kata.ExploringSimonsAlgorithm.Simons_Algorithm_Reference" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "%workspace reload" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part I. Problem Statement\n", + "\n", + "## Simon's Problem\n", + "\n", + "**You are given a classical function $f: \\{0, 1\\}^n\\ \\to\\ \\{0, 1\\}^n$ as a black box. The task is to determine $\\mathbf{s}$, the period of $f$.**\n", + "\n", + "Further, you are assured that there exists a secret set of $n$ boolean variables $\\mathbf{s} = (s_0, s_1, ..., s_{n-1})$ such that for all $\\mathbf{x}$, $\\mathbf{y} \\in\\ \\{0, 1\\}^n$,\n", + "\n", + "$$f(\\mathbf{x}) = f(\\mathbf{y}) \\text{ if and only if } \\mathbf{x} = \\mathbf{y} \\oplus \\mathbf{s},$$\n", + "where $\\oplus$ is the bitwise exclusive-or operation.\n", + "\n", + "In other words, the values of $f$ repeat themselves in some pattern and the pattern is determined by $\\mathbf{s}$.\n", + "\n", + "## Example\n", + "Let $n=3$. The following function satisfies the required and just mentioned property:\n", + "\n", + "| $x$ | $f(x)$ |\n", + "| ------- | ----- |\n", + "| 000 | 000 |\n", + "| 001 | 010 |\n", + "| 010 | 100 |\n", + "| 011 | 110 |\n", + "| 100 | 000 |\n", + "| 101 | 010 |\n", + "| 110 | 100 |\n", + "| 111 | 110 |\n", + "\n", + "It can be seen that each output $f(x)$ occurs twice. As such, $f$ is two-to-one. We can obtain $\\mathbf{s}$ by performing the bitwise XOR operation between both inputs that are mapped to the same output. That is,\n", + "\n", + "$000 \\oplus 100 = 100 = \\mathbf{s}$,\n", + "\n", + "$001 \\oplus 101 = 100 = \\mathbf{s}$, and so on.\n", + "\n", + "## Classical solution\n", + "\n", + "If we solve this problem classically, how many evaluations of $f$ will we need?\n", + "\n", + "We need to find two different inputs $x$ and $y$ such that $f(\\mathbf{x}) = f(\\mathbf{y})$. If we do not know anything about the internal structure of $f$, we would have to evaluate the function on different inputs. After each evaluation, we would check to see if that output has already been found. If we find two such inputs, we know that\n", + "\n", + "$\\mathbf{x} = \\mathbf{y} \\oplus \\mathbf{s}$.\n", + "\n", + "We can obtain $\\mathbf{s}$ by $\\oplus$-ing both sides with $\\mathbf{y}$:\n", + "\n", + "$\\mathbf{x} \\oplus \\mathbf{y} = \\mathbf{y} \\oplus \\mathbf{s} \\oplus \\mathbf{y} = \\mathbf{s}$.\n", + "\n", + "To find a pair for which $f$ takes the same output, we would likely need to guess $\\Omega(\\sqrt{2^n})$ different inputs.\n", + "\n", + "# Part II. Oracles\n", + "\n", + "Like the Deutsch-Jozsa algorithm you learned previously in the [Exploring Deutsch-Jozsa Algorithm tutorial](https://github.com/microsoft/QuantumKatas/tree/24002be02c034354356c92a31ba4a99ac897cbe6/tutorials/ExploringDeutschJozsaAlgorithm), Simon's algorithm utilizes quantum oracles. Unlike D-J, however, these are *not* phase oracles and they act on multiple qubits. \n", + "\n", + "Let's see an example of how a multi-qubit quantum oracle is implemented in Q#.\n", + "\n", + "## Example: Multi-qubit quantum oracle\n", + "\n", + ">**Note:** This code snippet is an example. It does not need to be modified and is not covered by tests.\n", + "\n", + "**Inputs:**\n", + "\n", + "1. $2$ qubits in an arbitrary state $|x\\rangle$ (input/query register)\n", + "2. $2$ qubits in the state $|00\\rangle$ (target register)\n", + "\n", + "**Goal:**\n", + "\n", + "Set the target qubits by applying a function such that they represent the following transformation on the input qubits:\n", + "* If the query register is in the state $|00\\rangle$, the target register should become $|11\\rangle$\n", + "* If the query register is in the state $|01\\rangle$, the target register should become $|10\\rangle$\n", + "* If the query register is in the state $|10\\rangle$, the target register should become $|01\\rangle$\n", + "* If the query register is in the state $|11\\rangle$, the target register should become $|00\\rangle$" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "application/json": "[\"MultiQubit_Oracle\"]", + "text/html": [ + "" + ], + "text/plain": [ + "MultiQubit_Oracle" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "operation MultiQubit_Oracle (x : Qubit[], y: Qubit[]) : Unit is Adj{\n", + " // Can also hardcode N equal to 2 for this example\n", + " let N = Length(x);\n", + " \n", + " // This is a Not gate controlled on 0\n", + " for (i in 0 .. N - 1) {\n", + " (ControlledOnBitString([false], X))([x[i]], y[i]);\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "> Note that the previous oracle implementation didn't change the state of the qubits in the query register; it only changed those in the target register.\n", + "\n", + "Now that you have seen an example of a multi-qubit oracle implemenatation, try implementing the quantum oracle for the example problem above where $\\mathbf{s} = 100$. In other words, how can you go from each state represented in the $x$ column to its corresponding state in the $f(x)$ column?\n", + "\n", + "## Exercise 1: Oracle for example where $\\mathbf{s} = 100$, $n = 3$\n", + "\n", + "**Inputs:**\n", + "\n", + "1. $3$ qubits in an arbitrary state $|x\\rangle$ (input/query register)\n", + "2. $3$ qubits in an arbitrary state $|y\\rangle$ (target register)\n", + "\n", + "**Goal:**\n", + "\n", + "Transform state $|x, y\\rangle$ into $|x, y \\oplus f(x)\\rangle$, where $f$ is the bitwise left shift function; i.e.\n", + "$|y \\oplus f(x)\\rangle = |y_0 \\oplus x_1, y_1 \\oplus x_2, ..., y_{n-1} \\oplus x_{n}\\rangle$.\n", + "\n", + "**Hint:** As in the multi-qubit oracle example, you should only change the state of the qubits in the target register." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%kata E1_QuantumOracle_Test\n", + "\n", + "operation Bitwise_LeftShift_Oracle (x : Qubit[], y: Qubit[]) : Unit is Adj{\n", + " //...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part III. Simon's Algorithm\n", + "\n", + "### Input\n", + "\n", + "We are given two multi-dimensional qubit registers as input in the familiar form $|0^n\\rangle \\otimes |0^n\\rangle = |0^n\\rangle|0^n\\rangle$, where one is a data register and the other a target register.\n", + "\n", + "### Algorithm summary\n", + "\n", + "Simon's algorithm consists of a quantum procedure followed by classical post-processing. The post-processing needed for step 5 is discussed in the accompanying tutorial. The steps of the algorithm can be summarized as follows:\n", + "\n", + "1. Apply the Hadamard transform to the first $n$ qubits.\n", + "2. Call the oracle \"black box\" $U_{f}$ to compute $f$ on the transformed input.\n", + "3. Apply a second Hadamard transform to the states of the first $n$ qubits.\n", + "4. Measure all qubits of the input register.\n", + "5. Repeat the previous four steps $n-1$ times.\n", + "5. Solve system of linear equations in step 4 to obtain $\\mathbf{s}$.\n", + "\n", + "> **Note:** If you need an introduction to or refresher on performing measurements, check out the [Measurements kata](https://github.com/microsoft/QuantumKatas/tree/master/Measurements).\n", + "\n", + "### Complexity\n", + "\n", + "Where the classical solution required $\\Omega(\\sqrt{2^n})$ guesses, Simon's algorithm only requires $O(n)$ queries to the oracle - an exponential speedup!\n", + "\n", + "While the quantum circuit for Simon's algorithm is quite straightforward, the classical post-processing summarized in Step 5 is much less so. For this we have a separate tutorial notebook!\n", + "\n", + "## Exercise 2: Implement the quantum circuit for Simon's algorithm\n", + "\n", + "Using the algorithm steps summarized above, try implementing the quantum part of Simon's algorithm.\n", + "\n", + "**Inputs:**\n", + "\n", + "1. Input register of $N$ qubits for the function $f$\n", + "2. A quantum operation which implements the oracle $|x\\rangle |y\\rangle$ -> $|x\\rangle |y \\oplus f(x)\\rangle$, where $x$ is the N-qubit input register, $y$ the N-qubit answer register, and $f$ a function from N-bit strings to N-bit strings\n", + "\n", + "The function $f$ is guaranteed to satisfy the following property:\n", + "\n", + ">There exists some N-bit string $s$ such that for all N-bit strings $j$ and $k$ ($j$ != $k$), we have $f(j) = f(k)$ if and only if $j = k \\oplus s$. In other words, $f$ is a two-to-one function.\n", + "\n", + "**Output:**\n", + "\n", + "Any bit string $j$ such that $\\sum_i j_i \\cdot s_i = 0$ modulo $2$\n", + "\n", + "> **Note:** the quantum part of Simon's algorithm will only produce some vector orthogonal to the bit string $\\mathbf{s}$ that we are ultimately after." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "operation Simons_Algorithm (N : Int, Uf : ((Qubit[], Qubit[]) => Unit)) : Int[] {\n", + "\n", + " // Allocate input and answer registers with N qubits each\n", + " // ...\n", + "\n", + " // Declare an Int array in which the result will be stored;\n", + " // the variable has to be mutable to allow updating it.\n", + " mutable j = new Int[N];\n", + " \n", + " // Prepare qubits in the right state\n", + " // ...\n", + " \n", + " // Apply oracle\n", + " // ...\n", + " \n", + " // Apply Hadamard to each qubit of input register\n", + " // ...\n", + " \n", + " // Measure all qubits of the input register;\n", + " // the result of each measurement is converted to an Int\n", + " // ...\n", + " \n", + " // Release qubits, ensuring they are in |0⟩ states\n", + " // ...\n", + " \n", + " return j;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "At this stage you have learned what Simon's problem is, seen a high-level view of how Simon's algorithm works, and practiced implementing the quantum component of the algorithm. To continue our exploration of Simon's algorithm, we'll switch to a [companion Python notebook](./ExploringSimonsAlgorithmClassicalPart.ipynb) to learn how the classical post-processing is implemented.\n", + "\n", + "# Part IV. What's next?\n", + "\n", + "We hope you've enjoyed this tutorial and learned a lot from it! If you're looking to learn more about quantum computing and Q#, here are some suggestions:\n", + "* Continue to the [companion Python notebook](./ExploringSimonsAlgorithmClassicalPart.ipynb) to learn the classical part of the implementation of Simon's algorithm using the Python language.\n", + "* The [Quantum Katas](https://github.com/microsoft/QuantumKatas/) are sets of programming exercises on quantum computing that can be solved using Q#. They cover a variety of topics, from the basics like the concepts of superposition and measurements to more interesting algorithms like Grover's search.\n", + "* In particular, [SimonsAlgorithm quantum kata](https://github.com/microsoft/QuantumKatas/tree/master/SimonsAlgorithm) offers more exercises on quantum oracles and a different presentation of Simon's algorithm." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Q#", + "language": "qsharp", + "name": "iqsharp" + }, + "language_info": { + "file_extension": ".qs", + "mimetype": "text/x-qsharp", + "name": "qsharp", + "version": "0.4" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/ExploringSimonsAlgorithm/README.md b/tutorials/ExploringSimonsAlgorithm/README.md new file mode 100644 index 00000000000..e9f167354eb --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/README.md @@ -0,0 +1,12 @@ +# Welcome! + +This tutorial covers Simon's algorithm. This algorithm solves Simon's problem - an oracle problem of finding a hidden bit vector. This problem is an example of an oracle problem that can be solved exponentially faster by a quantum algorithm than any known classical algorithm. + +Simon's algorithm consists of two parts - a quantum circuit and a classical post-processing routine which calls the quantum circuit repeatedly and extracts the answer from the results of the runs. This tutorial includes two companion notebooks: a Q# notebook for the quantum part, and a Python notebook for the classical part. + +You can run the tutorials online here: + + +Alternatively, you can install Jupyter and Q# on your machine, as described [here](https://docs.microsoft.com/quantum/install-guide#develop-with-jupyter-notebooks), and run the tutorial locally by navigating to this folder and starting the notebook from the command line using the following command: + + jupyter notebook MultiQubitGates.ipynb \ No newline at end of file diff --git a/tutorials/ExploringSimonsAlgorithm/ReferenceImplementation.qs b/tutorials/ExploringSimonsAlgorithm/ReferenceImplementation.qs new file mode 100644 index 00000000000..d4c7362c130 --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/ReferenceImplementation.qs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +////////////////////////////////////////////////////////////////////// +// This file contains reference solutions to all tasks. +// The tasks themselves can be found in Tasks.qs file. +// We recommend that you try to solve the tasks yourself first, +// but feel free to look up the solution if you get stuck. +////////////////////////////////////////////////////////////////////// + +namespace Quantum.Kata.ExploringSimonsAlgorithm { + + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + + + ////////////////////////////////////////////////////////////////// + // Part II. Oracles + ////////////////////////////////////////////////////////////////// + + // Exercise 1. Bitwise left shift + operation Bitwise_LeftShift_Oracle_Reference (x : Qubit[], y : Qubit[]) : Unit + is Adj { + + let N = Length(x); + + for (i in 1 .. N - 1) { + CNOT(x[i], y[i - 1]); + } + } + + + ////////////////////////////////////////////////////////////////// + // Part III. Simon's Algorithm + ////////////////////////////////////////////////////////////////// + + // Exercise 2. Quantum part of Simon's algorithm + operation Simons_Algorithm_Reference (N : Int, Uf : ((Qubit[], Qubit[]) => Unit)) : Int[] { + + // allocate input and answer registers with N qubits each + using ((x, y) = (Qubit[N], Qubit[N])) { + // prepare qubits in the right state + ApplyToEach(H, x); + + // apply oracle + Uf(x, y); + + // apply Hadamard to each qubit of the input register + ApplyToEach(H, x); + + // measure all qubits of the input register; + // the result of each measurement is converted to an Int + mutable j = new Int[N]; + for (i in 0 .. N - 1) { + if (M(x[i]) == One) { + set j w/= i <- 1; + } + } + + // before releasing the qubits make sure they are all in |0⟩ states + ResetAll(x); + ResetAll(y); + return j; + } + } + +} diff --git a/tutorials/ExploringSimonsAlgorithm/Tasks.qs b/tutorials/ExploringSimonsAlgorithm/Tasks.qs new file mode 100644 index 00000000000..d8fecd537a6 --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/Tasks.qs @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +namespace Quantum.Kata.ExploringSimonsAlgorithm { + + open Microsoft.Quantum.Diagnostics; + open Microsoft.Quantum.Intrinsic; + open Microsoft.Quantum.Canon; + + + ////////////////////////////////////////////////////////////////// + // Welcome! + ////////////////////////////////////////////////////////////////// + + // "Simon's Algorithm" kata is a series of exercises designed to teach a quantum algorithm for + // a problem of identifying a bit string that is implicitly defined (or, in other words, "hidden") by + // some oracle that satisfies certain conditions. It is arguably the most simple case of an (oracle) + // problem for which a quantum algorithm has a *provable* exponential advantage over any classical algorithm. + + // Each task is wrapped in one operation preceded by the description of the task. Each task (except tasks in + // which you have to write a test) has a unit test associated with it, which initially fails. Your goal is to + // fill in the blank (marked with // ... comment) with some Q# code to make the failing test pass. + + ////////////////////////////////////////////////////////////////// + // Part II. Oracles + ////////////////////////////////////////////////////////////////// + + // Exercise 1. Bitwise left shift + // Inputs: + // 1) N qubits in an arbitrary state |x⟩ + // 2) N qubits in an arbitrary state |y⟩ + // Goal: Transform state |x, y⟩ into |x, y ⊕ f(x)⟩, where f is bitwise left shift function, i.e., + // |y ⊕ f(x)⟩ = |y_0 ⊕ x_1, y_1 ⊕ x_2, ..., y_{n-1} ⊕ x_{n}⟩ (⊕ is addition modulo 2). + operation Bitwise_LeftShift_Oracle (x : Qubit[], y : Qubit[]) : Unit + is Adj { + // ... + } + + + ////////////////////////////////////////////////////////////////// + // Part III. Simon's Algorithm + ////////////////////////////////////////////////////////////////// + + // Exercise 2. Quantum part of Simon's algorithm + // Inputs: + // 1) the number of qubits in the input register N for the function f + // 2) a quantum operation which implements the oracle |x⟩|y⟩ -> |x⟩|y ⊕ f(x)⟩, where + // x is N-qubit input register, y is N-qubit answer register, and f is a function + // from N-bit strings into N-bit strings + // + // The function f is guaranteed to satisfy the following property: + // there exists some N-bit string s such that for all N-bit strings b and c (b != c) + // we have f(b) = f(c) if and only if b = c ⊕ s. In other words, f is a two-to-one function. + // + // An example of such function is bitwise right shift function from task 1.2; + // the bit string s for it is [0, ..., 0, 1]. + // + // Output: + // Any bit string b such that Σᵢ bᵢ sᵢ = 0 modulo 2. + // + // Note that the whole algorithm will reconstruct the bit string s itself, but the quantum part of the + // algorithm will only find some vector orthogonal to the bit string s. The classical post-processing + // part is already implemented, so once you implement the quantum part, the tests will pass. + operation Simon_Algorithm (N : Int, Uf : ((Qubit[], Qubit[]) => Unit)) : Int[] { + + // Declare an Int array in which the result will be stored; + // the variable has to be mutable to allow updating it. + mutable b = new Int[N]; + + // ... + + return b; + } + +} diff --git a/tutorials/ExploringSimonsAlgorithm/TestSuiteRunner.cs b/tutorials/ExploringSimonsAlgorithm/TestSuiteRunner.cs new file mode 100644 index 00000000000..61b39266ef8 --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/TestSuiteRunner.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +////////////////////////////////////////////////////////////////////// +// This file contains parts of the testing harness. +// You should not modify anything in this file. +// The tasks themselves can be found in Tasks.qs file. +////////////////////////////////////////////////////////////////////// + +using Microsoft.Quantum.Simulation.XUnit; +using Xunit.Abstractions; +using System.Diagnostics; + +namespace Quantum.Kata.ExploringSimonsAlgorithm +{ + public class TestSuiteRunner + { + private readonly ITestOutputHelper output; + + public TestSuiteRunner(ITestOutputHelper output) + { + this.output = output; + } + + /// + /// This driver will run all Q# tests (operations named "...Test") + /// that belong to namespace Quantum.Kata.ExploringSimonsAlgorithmQuantumPart. + /// + [OperationDriver(TestNamespace = "Quantum.Kata.ExploringSimonsAlgorithm")] + public void TestTarget(TestOperation op) + { + using (var sim = new QuantumSimulator()) + { + // OnLog defines action(s) performed when Q# test calls function Message + sim.OnLog += (msg) => { output.WriteLine(msg); }; + sim.OnLog += (msg) => { Debug.WriteLine(msg); }; + op.TestOperationRunner(sim); + } + } + } +} diff --git a/tutorials/ExploringSimonsAlgorithm/Tests.qs b/tutorials/ExploringSimonsAlgorithm/Tests.qs new file mode 100644 index 00000000000..1fe1b7004a4 --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/Tests.qs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. + +////////////////////////////////////////////////////////////////////// +// This file contains testing harness for all tasks. +// You should not modify anything in this file. +////////////////////////////////////////////////////////////////////// + +namespace Quantum.Kata.ExploringSimonsAlgorithm { + + open Microsoft.Quantum.Diagnostics; + + open Quantum.Kata.Utils; + + + ////////////////////////////////////////////////////////////////// + // Part II. Oracles + ////////////////////////////////////////////////////////////////// + + operation ApplyOracleWithOutputArrA (qs : Qubit[], oracle : ((Qubit[], Qubit[]) => Unit is Adj), outputSize : Int) : Unit + is Adj { + let N = Length(qs); + oracle(qs[0 .. (N - 1) - outputSize], qs[N - outputSize .. N - 1]); + } + + + operation AssertTwoOraclesWithOutputArrAreEqual ( + inputSize : Int, + outputSize : Int, + oracle1 : ((Qubit[], Qubit[]) => Unit is Adj), + oracle2 : ((Qubit[], Qubit[]) => Unit is Adj)) : Unit { + let sol = ApplyOracleWithOutputArrA(_, oracle1, outputSize); + let refSol = ApplyOracleWithOutputArrA(_, oracle2, outputSize); + AssertOperationsEqualReferenced(inputSize + outputSize, sol, refSol); + } + + // Exercise 1. + operation E1_QuantumOracle_Test () : Unit { + for (n in 2 .. 6) { + AssertTwoOraclesWithOutputArrAreEqual(n, n, Bitwise_LeftShift_Oracle, Bitwise_LeftShift_Oracle_Reference); + } + } +} \ No newline at end of file diff --git a/tutorials/ExploringSimonsAlgorithm/tests.py b/tutorials/ExploringSimonsAlgorithm/tests.py new file mode 100644 index 00000000000..3b31e3c4da7 --- /dev/null +++ b/tutorials/ExploringSimonsAlgorithm/tests.py @@ -0,0 +1,139 @@ +from pytest import approx + +tests = {} + + +# Exercise decorator, specifying that this function needs to be tested +def exercise(fun): + tests[fun.__name__](fun) + return fun + + +# Test decorator, specifying that this is a test for an exercise +def test(fun): + tests[fun.__name__[:-5]] = fun + return fun + + +# ------------------------------------------------------ +# Checks that two matrices are (approximately) equal to each other +def matrix_equal(act, exp): + if act == ... or exp == ...: + return False + + h = len(act) + w = len(act[0]) + # Check that sizes match + if h != len(exp) or w != len(exp[0]): + return False + + for i in range(h): + # Check that the length of each row matches the expectation + if w != len(act[i]): + return False + for j in range(w): + if act[i][j] == ... or act[i][j] != approx(exp[i][j]): + return False + return True + + +# ------------------------------------------------------ +def row_echelon_form_ref(A): + m = len(A) + n = m + 1 + + for k in range(m): + pivots = [abs(A[i][k]) for i in range(k, m)] + i_max = pivots.index(max(pivots)) + k + + # Check for singular matrix + assert A[i_max][k] != 0, "Matrix is singular!" + + # Swap rows + A[k], A[i_max] = A[i_max], A[k] + + for i in range(k + 1, m): + f = A[i][k] / A[k][k] + for j in range(k + 1, n): + A[i][j] -= A[k][j] * f + + # Fill lower triangular matrix with zeros: + A[i][k] = 0 + return A + + +@test +def row_echelon_form_test(fun): + M_1 = [[0, 1, 1], [1, 1, 1]] + M_2 = [[0, 1, 1], [1, 1, 1]] + + expected = row_echelon_form_ref(M_1) + # M has been mutated + actual = fun(M_2) + if actual is None: + print("Your function must return a matrix!") + return + if not matrix_equal(actual, expected): + print("Unexpected result. Try again!") + return + print("Success!") + + +# ------------------------------------------------------ +def back_substitution_ref(A): + # Solve equation Ax=b for an upper triangular matrix A + x = [] + m = len(A) + for i in range(m - 1, -1, -1): + x.insert(0, A[i][m] / A[i][i]) + for k in range(i - 1, -1, -1): + A[k][m] -= A[k][i] * x[0] + return x + + +@test +def back_substitution_test(fun): + N_1 = [[1, 1, 1], [0, 1, 1]] + N_2 = [[1, 1, 1], [0, 1, 1]] + + expected = back_substitution_ref(N_1) + # N has been mutated + actual = fun(N_2) + if actual is None: + print("Your function must return a list!") + return + if actual != expected: + print("Unexpected result. Try again!") + return + print("Success!") + + +# ------------------------------------------------------ +def dot_product_ref(j, s): + accum = 0 + for i in range(len(j)): + accum += int(j[i]) * int(s[i]) + sol = accum % 2 + + return sol + + +@test +def dot_product_test(fun): + a_1 = [1, 1, 1] + b_1 = [1, 0, 1] + a_2 = [1, 1, 1] + b_2 = [1, 0, 1] + + expected = dot_product_ref(a_1, b_1) + actual = fun(a_2, b_2) + if actual is None: + print("Your function must return a value!") + return + if actual != expected: + print("Unexpected result. Try again!") + return + print("Success!") + + +print("Success!")