diff --git a/README.md b/README.md index e762a0fec0e..e61c28bf018 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Here is the learning path we suggest you to follow if you are starting to learn * **[Quantum oracles (tutorial)](./tutorials/Oracles/)**. Learn to implement classical functions as equivalent quantum oracles. -* **[Exploring Deutsch–Jozsa algorithm (tutorial)](./tutorials/ExploringDeutschJozsaAlgorithm/)**. +* **[Exploring Deutsch and Deutsch–Jozsa algorithms (tutorial)](./tutorials/ExploringDeutschJozsaAlgorithm/)**. Learn to implement classical functions and equivalent quantum oracles, and compare the quantum solution to the Deutsch–Jozsa problem to a classical one. * **[Deutsch–Jozsa algorithm](./DeutschJozsaAlgorithm/)**. diff --git a/index.ipynb b/index.ipynb index 169147aeb17..be9450d45d0 100644 --- a/index.ipynb +++ b/index.ipynb @@ -71,7 +71,7 @@ "\n", "* **[Quantum oracles (tutorial)](./tutorials/Oracles/Oracles.ipynb)**.\n", " Learn to implement classical functions as equivalent quantum oracles. \n", - "* **[Exploring Deutsch–Jozsa algorithm (tutorial)](./tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial.ipynb)**.\n", + "* **[Exploring Deutsch and Deutsch–Jozsa algorithms (tutorial)](./tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P1.ipynb)**.\n", " Learn to implement classical functions and equivalent quantum oracles, \n", " and compare the quantum solution to the Deutsch–Jozsa problem to a classical one.\n", "* **[Deutsch–Jozsa algorithm](./DeutschJozsaAlgorithm/DeutschJozsaAlgorithm.ipynb)**.\n", diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P1.ipynb b/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P1.ipynb new file mode 100644 index 00000000000..d62b876328d --- /dev/null +++ b/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P1.ipynb @@ -0,0 +1,189 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deutsch–Jozsa Algorithm Tutorial (Part I)\n", + "\n", + "The **Deutsch–Jozsa algorithm** is one of the most famous algorithms in quantum computing. The problem it solves has little practical value, but the algorithm itself is one of the earliest examples of a quantum algorithm that is exponentially faster than any possible deterministic algorithm for the same problem. It is also relatively simple to explain and illustrates several very important concepts (such as quantum oracles). As such, Deutsch–Jozsa algorithm is part of almost every introductory course on quantum computing.\n", + "\n", + "This tutorial will:\n", + "* introduce you to the problems solved by the Deutsch and Deutsch–Jozsa algorithms and walk you through the classical solution to them,\n", + "* give you a brief introduction to quantum oracles (for a more detailed introduction, see the [Oracles tutorial](../Oracles/Oracles.ipynb)),\n", + "* describe the idea behind the Deutsch and Deutsch–Jozsa algorithms and walk you through the math for them,\n", + "* teach you how to implement the algorithms in the Q# programming language,\n", + "* and finally help you to run your implementation of the algorithm on several quantum oracles to see for yourself how the algorithm works!\n", + "\n", + "Let's go!\n", + "\n", + "This tutorial consists of several notebooks:\n", + "1. Part I covers the problem statement and the classical algorithm for solving it.\n", + "2. [Part II](./DeutschJozsaAlgorithmTutorial_P2.ipynb) introduces single-qubit quantum oracles and Deutsch algorithm for solving the problem for one-bit input functions.\n", + "3. [Part III](./DeutschJozsaAlgorithmTutorial_P3.ipynb) introduces multi-qubit quantum oracles and Deutsch-Jozsa algorithm for solving the general case of the problem." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part I. Problem statement and classical algorithm\n", + "\n", + "## The problem\n", + "\n", + "You are given a classical function $f(x): \\{0, 1\\}^N \\to \\{0, 1\\}$. You are guaranteed that the function $f$ is\n", + "* either *constant* (has the same value for all inputs) \n", + "* or *balanced* (has value 0 for half of the inputs and 1 for the other half of the inputs). \n", + "\n", + "The task is to figure out whether the function is constant or balanced.\n", + "\n", + "For the single-bit functions ($N = 1$) the problem can be formulated in a simpler way: given a classical function $f(x): \\{0, 1\\} \\to \\{0, 1\\}$, figure out whether $f(0) = f(1)$. \n", + "Indeed, $f(0) = f(1)$ is an equivalent definition of a constant single-bit function, and $f(0) \\neq f(1)$ - of a balanced single-bit function.\n", + "\n", + "> ## Examples\n", + ">\n", + "> * $f(x) \\equiv 0$ or $f(x) \\equiv 1$ are examples of constant functions (and they are actually the only constant functions in existence).\n", + "> * For single-bit functions, $f(x) = x$ and $f(x) = 1 - x$ are the only existing balanced functions.\n", + "> * $f(x) = x \\bmod 2$ (the least significant bit of $x$) or $f(x) = 1 \\text{ if the binary notation of }x \\text{ has odd number of 1s and 0 otherwise}$ are examples of multi-bit balanced functions. \n", + "> Indeed, for both these functions you can check that for every possible input $x$ for which $f(x) = 0$ there exists an input $x^\\prime$ (equal to $x$ with the least significant bit flipped) such that $f(x^\\prime) = 1$, and vice versa, which means that the function is balanced.\n", + ">\n", + "> There exist more complicated examples of balanced functions, but we will not need to consider them for this tutorial.\n", + "\n", + "## Implementing classical functions in Q#\n", + "\n", + "Here is the implementation of these functions in Q#; it is pretty self-descriptory, since the functions are not only very simple but also classical.\n", + "\n", + "> **Note**: These are just code samples, you don't have to modify the code and they are not covered by tests." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "// Function 1. f(x) = 0\n", + "function Function_Zero (x : Int) : Int {\n", + " return 0;\n", + "}\n", + "\n", + "\n", + "// Function 2. f(x) = 1\n", + "function Function_One (x : Int) : Int {\n", + " return 1;\n", + "}\n", + "\n", + "\n", + "// Function 3. f(x) = x mod 2 (least significant bit of x)\n", + "function Function_Xmod2 (x : Int) : Int {\n", + " return x % 2;\n", + "}\n", + "\n", + "\n", + "// Function 4. f(x) = 1 if the binary notation of x has odd number of 1s, and 0 otherwise\n", + "function Function_OddNumberOfOnes (x : Int) : Int {\n", + " mutable nOnes = 0;\n", + " mutable xBits = x;\n", + " while (xBits > 0) {\n", + " if (xBits % 2 > 0) {\n", + " set nOnes += 1;\n", + " }\n", + " set xBits /= 2;\n", + " }\n", + " return nOnes % 2;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise 1: Implement a classical function in Q#\n", + "\n", + "Try to implement a similar classical function in Q#!\n", + "\n", + "**Inputs:** \n", + "1. An integer $x$.\n", + "2. The number of bits in the input $N$ ($1 \\le N \\le 5$, $0 \\le x \\le 2^N-1$).\n", + "\n", + "**Output:** Return $f(x) = \\text{the most significant bit of }x$.\n", + "\n", + "> Useful documentation: [Q# Bitwise Expressions](https://docs.microsoft.com/azure/quantum/user-guide/language/expressions/bitwiseexpressions)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%kata T1_ClassicalFunction \n", + "\n", + "function Function_MostSignificantBit (x : Int, N : Int) : Int {\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Classical algorithm\n", + "\n", + "If we solve this problem classically, how many calls to the given function will we need? \n", + "\n", + "The first call will give us no information - regardless of whether it returns 0 or 1, the function could still be constant or balanced.\n", + "In the best case scenario the second call will return a different value and we'll be able to conclude that the function is balanced in just $2$ calls. \n", + "However, if we get the same value for the first two calls, we'll have to keep querying the function until either we get a different value or until we do $2^{N-1}+1$ queries that will return the same value - in this case we'll know for certain that the function will be constant.\n", + "\n", + "## Exercise 2: Implement the classical algorithm!\n", + "\n", + "Q# is a domain-specific language, so it is not designed to handle arbitrary classical computations. However, this classical algorithm is so simple that you can easily implement it in Q#. Try it!\n", + "\n", + "**Inputs:** \n", + "1. The number of bits in the input $N$ ($1 \\le N \\le 5$).\n", + "2. The \"black box\" function that evaluates $f(x)$ on any given input $x \\in [0, 2^N-1]$. \n", + " You are guaranteed that the function implemented by the black box is either constant or balanced.\n", + "\n", + "**Goal:** Return `true` if the function is constant, or `false` if it is balanced.\n", + "\n", + "> Useful documentation: [Q# statements](https://docs.microsoft.com/azure/quantum/user-guide/language/statements/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%kata T2_ClassicalAlgorithm \n", + "\n", + "operation IsFunctionConstant_Classical (N : Int, f : (Int -> Int)) : Bool {\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Continue to [part II](./DeutschJozsaAlgorithmTutorial_P2.ipynb) to learn about single-qubit quantum oracles and Deutsch algorithm for solving the problem for one-bit input functions." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Q#", + "language": "qsharp", + "name": "iqsharp" + }, + "language_info": { + "file_extension": ".qs", + "mimetype": "text/x-qsharp", + "name": "qsharp", + "version": "0.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P2.ipynb b/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P2.ipynb new file mode 100644 index 00000000000..d2632f0af31 --- /dev/null +++ b/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P2.ipynb @@ -0,0 +1,313 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Deutsch–Jozsa Algorithm Tutorial (Part II)\n", + "\n", + "The **Deutsch–Jozsa algorithm** is one of the most famous algorithms in quantum computing. The problem it solves has little practical value, but the algorithm itself is one of the earliest examples of a quantum algorithm that is exponentially faster than any possible deterministic algorithm for the same problem. It is also relatively simple to explain and illustrates several very important concepts (such as quantum oracles). As such, Deutsch–Jozsa algorithm is part of almost every introductory course on quantum computing.\n", + "\n", + "This tutorial consists of several notebooks:\n", + "1. [Part I](./DeutschJozsaAlgorithmTutorial_P1.ipynb) covers the problem statement and the classical algorithm for solving it.\n", + "2. Part II introduces single-qubit quantum oracles and Deutsch algorithm for solving the problem for one-bit input functions.\n", + "3. [Part III](./DeutschJozsaAlgorithmTutorial_P3.ipynb) introduces multi-qubit quantum oracles and Deutsch-Jozsa algorithm for solving the general case of the problem." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Part II. Single-qubit quantum oracles and Deutsch algorithm\n", + "\n", + "## Single-qubit oracles: the definition\n", + "A quantum oracle is a \"black box\" operation which is used as input to another algorithm. This operation is implemented in a way which allows to perform calculations not only on individual inputs, but also on superpositions of inputs. \n", + "\n", + "> This is *not* the same as being able to evaluate the function on all inputs at once, since you will not be able to extract the evaluation results!\n", + "\n", + "Oracles are often defined using a classical function.\n", + "Single-qubit oracles implement single-bit classical functions of the form $f : \\{0, 1\\} \\to \\{0, 1\\}$.\n", + "\n", + "The oracle has to act on quantum states instead of classical values. \n", + "To enable this, integer input $x$ is represented as a qubit state $|x\\rangle$.\n", + "\n", + "The type of oracles used in this tutorial are called *phase oracles*. A phase oracle $U_f$ encodes the value of the classical function $f$ it implements in the phase of the qubit state as follows:\n", + "\n", + "$$U_f |x \\rangle = (-1)^{f(x)} |x \\rangle$$\n", + "\n", + "In our case $f$ can return only two values, 0 or 1, which result in no phase change or adding a relative phase $-1$, respectively.\n", + "\n", + "The effect of such an oracle on any single basis state is not particularly interesting: it just adds a global phase which is not something you can observe. However, if you apply this oracle to a *superposition* of basis states, its effect becomes noticeable. \n", + "Remember that quantum operations are linear: if you define the effect of an operation on the basis states, you'll be able to deduce its effect on superposition states (which are just linear combinations of the basis states) using its linearity. \n", + "\n", + "## Implementing single-bit oracles in Q#\n", + "\n", + "There are only 4 single-bit functions, so we can see how to implement them all as Q# oracles.\n", + "\n", + "> **Note:**\n", + "All code snippets before Exercise 3 are just examples. They don't need to be modified and are not covered by tests." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### $f(x) \\equiv 0$\n", + "\n", + "This is the easiest function to implement: if $f(x) \\equiv 0$, $U_f |x\\rangle \\equiv (-1)^0 |x\\rangle = |x\\rangle$. \n", + "This means that $U_f$ is an identity - a transformation which does absolutely nothing! \n", + "This is very easy to express in Q#:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "operation PhaseOracle_Zero (x : Qubit) : Unit {\n", + " // Do nothing...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### $f(x) \\equiv 1$\n", + "\n", + "The second constant function is slightly trickier: if $f(x) \\equiv 1$, $U_f |x\\rangle \\equiv (-1)^1 |x\\rangle = - |x\\rangle$. \n", + "Now $U_f$ is a negative identity, i.e., a transformation which applies a global phase of $-1$ to the state. \n", + "A lot of algorithms just ignore the global phase accumulated in them, since it is not observable. \n", + "However, if we want to be really meticulous, we can use Q# library operation [Microsoft.Quantum.Intrinsic.R](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.r) which performs a given rotation around the given axis. \n", + "When called with `PauliI` axis, this operation applies a global phase; since it doesn't take the input into account, it can be applied to any qubit, for example, the first qubit of the input." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "// Open namespace where the library function PI() is defined\n", + "open Microsoft.Quantum.Math;\n", + "\n", + "operation PhaseOracle_One (x : Qubit) : Unit {\n", + " // Apply a global phase of -1\n", + " R(PauliI, 2.0 * PI(), x);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### $f(x) = x$\n", + "\n", + "$$U_f |x\\rangle = (-1)^{f(x)} |x\\rangle = (-1)^{x} |x\\rangle$$\n", + "\n", + "This means that we need to do nothing if the qubit is in the $|0\\rangle$ state, and apply a phase of $-1$ if it is in the $|1\\rangle$ state. This is exactly the effect of the [Z gate](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.z); as a reminder, \n", + "\n", + "$$Z = \\begin{bmatrix} 1 & 0 \\\\ 0 & -1\\end{bmatrix}: Z |0\\rangle = |0\\rangle, Z |1\\rangle = -|1\\rangle$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "operation PhaseOracle_X (x : Qubit) : Unit {\n", + " Z(x);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise 3: Implement a quantum oracle for $f(x) = 1 - x$ in Q#\n", + "\n", + "You're ready to try and write some actual quantum code! Implement a quantum oracle that implements the last single-bit function, $f(x) = 1 - x$.\n", + "\n", + "**Input:** A qubit $x$.\n", + "\n", + "**Goal:** Apply a transformation $U_f$ to the qubit, defined as $U_f |x\\rangle = (-1)^{1-x} |x\\rangle$.\n", + "\n", + "
\n", + "\n", + "
\n", + " Need a hint? Click Here\n", + "We can represent the effect of the oracle as\n", + "\n", + "$$U_f |x\\rangle = (-1)^{1-x} |x\\rangle = (-1) \\cdot (-1)^x |x\\rangle$$\n", + "\n", + "Can you get this effect by combining some of the previous oracles implementations?\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": true + }, + "outputs": [], + "source": [ + "%kata T3_PhaseOracle_OneMinusX\n", + "\n", + "operation PhaseOracle_OneMinusX (x : Qubit) : Unit is Adj + Ctl {\n", + " // ...\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Solving the problem for single-bit functions: Deutsch algorithm\n", + "\n", + "Now let's return to the problem of figuring out whether the given function is constant or balanced for single-bit functions.\n", + "What can we do if we are given a quantum oracle $U_f$ implementing the function $f(x)$?\n", + "\n", + "There are two possible inputs to the function, $|0\\rangle$ and $|1\\rangle$. Let's see what happens if we apply the oracle to their superposition:\n", + "\n", + "$$U_f \\left( \\frac{1}{\\sqrt2} \\big( |0\\rangle + |1\\rangle \\big) \\right) \n", + "= \\frac{1}{\\sqrt2} \\big( U_f |0\\rangle + U_f |1\\rangle \\big) \n", + "= \\frac{1}{\\sqrt2} \\big( (-1)^{f(0)} |0\\rangle + (-1)^{f(1)} |1\\rangle \\big)$$.\n", + "\n", + "* If $f(0) = f(1)$, the relative phases of the two basis states are the same, and the resulting state is $|+\\rangle = \\frac{1}{\\sqrt2} \\big( |0\\rangle + |1\\rangle \\big)$ (up to a global phase). \n", + "* If $f(0) \\neq f(1)$, the relative phases of the two basis states differ by a factor of $-1$, and the resulting state is $|-\\rangle = \\frac{1}{\\sqrt2} \\big( |0\\rangle - |1\\rangle \\big)$ (up to a global phase). \n", + "\n", + "Now, the states $|+\\rangle$ and $|-\\rangle$ can be distinguished using measurement: if you apply the H gate to each of them, you'll get $H|+\\rangle = |0\\rangle$ if $f(0) = f(1)$, or $H|-\\rangle = |1\\rangle$ if $f(0) \\neq f(1)$. This means that one oracle call does not let you calculate both $f(0)$ and $f(1)$, but it allows you to figure out whether $f(0) = f(1)$!\n", + "\n", + "This is a special case of the Deutsch–Jozsa algorithm, called the Deutsch algorithm. \n", + "The algorithm is very straightforward:\n", + "\n", + "1. Start with a qubit in the $|0\\rangle$ state.\n", + "2. Apply the H gate to the qubit.\n", + "3. Apply the oracle.\n", + "4. Apply the H gate to the qubit again.\n", + "5. Measure the qubit: if it is in the $|0\\rangle$ state, the function is constant, otherwise it is balanced.\n", + "\n", + "Note that this algorithm requires only $1$ oracle call, and always produces the correct result." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Exercise 4: Implement the Deutsch algorithm!\n", + "\n", + "**Input:** The \"black box\" oracle the implements $f(x)$. \n", + "\n", + "**Goal:** Return `true` if the function is constant ($f(0) = f(1)$), or `false` if it is balanced ($f(0) \\neq f(1)$).\n", + "You can use only one oracle call!\n", + "\n", + "> Useful documentation: [Q# conditional expressions](https://docs.microsoft.com/azure/quantum/user-guide/language/expressions/conditionalexpressions)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%kata T4_DeutschAlgorithm\n", + "\n", + "operation DeutschAlgorithm (oracle : (Qubit => Unit)) : Bool {\n", + " // Allocate a qubit for the input x.\n", + " use x = Qubit();\n", + " // Newly allocated qubits start in the |0⟩ state.\n", + "\n", + " // The first step is to prepare the qubit in the required state before calling the oracle.\n", + " // A qubit can be transformed from the |0⟩ state to the |+⟩ state by applying a Hadamard gate H.\n", + " // ...\n", + "\n", + " // Apply the oracle to the input qubit.\n", + " // The syntax is the same as for applying any function or operation.\n", + " // ...\n", + "\n", + " // Apply a Hadamard gate to the qubit again.\n", + " // ...\n", + "\n", + " // Measure the qubit in the computational basis using the M operation,\n", + " // and return the result using a conditional expression: cond ? ifTrue | ifFalse.\n", + " return ...;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Running the algorithm\n", + "\n", + "You have implemented the quantum version of the Deutsch algorithm - congratulations! The last step is to combine everything you've seen so far - run your code to check whether the oracles you've seen earlier in this tutorial implement constant or balanced functions.\n", + "\n", + "> This is an open-ended task, and is not covered by a unit test. To run the code, execute the cell with the definition of the `Run_DeutschAlgorithm` operation first; if it compiled successfully without any errors, you can run the operation by executing the next cell (`%simulate Run_DeutschAlgorithm`).\n", + "\n", + "> Note that this task relies on your implementations of the previous tasks. If you are getting the \"No variable with that name exists.\" error, you might have to execute previous code cells before retrying this task. Don't forget to execute Q# code cells that define oracles in part II!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "open Microsoft.Quantum.Diagnostics;\n", + "\n", + "operation RunDeutschAlgorithm () : String {\n", + " // You can use Fact function to check that the return value \n", + " // of DeutschAlgorithm operation matches the expected value.\n", + " // Uncomment the next line to run it.\n", + " \n", + " // Fact(DeutschAlgorithm(PhaseOracle_Zero) == true, \"f(x) = 0 not identified as constant\");\n", + " \n", + " // Run the algorithm for the rest of the oracles\n", + " // ...\n", + " \n", + " \n", + " // If all tests pass, report success!\n", + " return \"Success!\";\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%simulate RunDeutschAlgorithm" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Continue to [part III](./DeutschJozsaAlgorithmTutorial_P3.ipynb) to learn about multi-qubit quantum oracles and Deutsch-Jozsa algorithm for solving the general case of the problem." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Q#", + "language": "qsharp", + "name": "iqsharp" + }, + "language_info": { + "file_extension": ".qs", + "mimetype": "text/x-qsharp", + "name": "qsharp", + "version": "0.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial.ipynb b/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P3.ipynb similarity index 65% rename from tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial.ipynb rename to tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P3.ipynb index 8841b8c0122..2065ffa072a 100644 --- a/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial.ipynb +++ b/tutorials/ExploringDeutschJozsaAlgorithm/DeutschJozsaAlgorithmTutorial_P3.ipynb @@ -4,201 +4,39 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Deutsch–Jozsa Algorithm Tutorial\n", + "# Deutsch–Jozsa Algorithm Tutorial (Part III)\n", "\n", "The **Deutsch–Jozsa algorithm** is one of the most famous algorithms in quantum computing. The problem it solves has little practical value, but the algorithm itself is one of the earliest examples of a quantum algorithm that is exponentially faster than any possible deterministic algorithm for the same problem. It is also relatively simple to explain and illustrates several very important concepts (such as quantum oracles). As such, Deutsch–Jozsa algorithm is part of almost every introductory course on quantum computing.\n", "\n", - "This tutorial will:\n", - "* introduce you to the problem solved by the Deutsch–Jozsa algorithm and walk you through the classical solution to it,\n", - "* give you a brief introduction to quantum oracles (for a more detailed introduction, see the [Oracles tutorial](../Oracles/Oracles.ipynb)),\n", - "* describe the idea behind the Deutsch–Jozsa algorithm and walk you through the math for it,\n", - "* teach you how to implement the algorithm in the Q# programming language,\n", - "* and finally help you to run your implementation of the algorithm on several quantum oracles to see for yourself how the algorithm works!\n", - "\n", - "Let's go!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Part I. Problem Statement and Classical Algorithm\n", - "\n", - "## The problem\n", - "\n", - "You are given a classical function $f(x): \\{0, 1\\}^N \\to \\{0, 1\\}$. You are guaranteed that the function $f$ is\n", - "* either *constant* (has the same value for all inputs) \n", - "* or *balanced* (has value 0 for half of the inputs and 1 for the other half of the inputs). \n", - "\n", - "The task is to figure out whether the function is constant or balanced.\n", - "\n", - "> ## Examples\n", - ">\n", - "> * $f(x) \\equiv 0$ or $f(x) \\equiv 1$ are examples of constant functions (and they are actually the only constant functions in existence).\n", - "> * $f(x) = x \\text{ mod } 2$ (the least significant bit of $x$) or $f(x) = 1 \\text{ if the binary notation of }x \\text{ has odd number of 1s and 0 otherwise}$ are examples of balanced functions. \n", - "> Indeed, for both these functions you can check that for every possible input $x$ for which $f(x) = 0$ there exists an input $x^\\prime$ (equal to $x$ with the least significant bit flipped) such that $f(x^\\prime) = 1$, and vice versa, which means that the function is balanced. \n", - "> There exist more complicated examples of balanced functions, but we will not need to consider them for this tutorial.\n", - "\n", - "## Implementing classical functions in Q#\n", - "\n", - "Here is the implementation of these functions in Q#; it is pretty self-descriptory, since the functions are not only very simple but also classical.\n", - "\n", - "> **Note**: These are just code samples, you don't have to modify the code and they are not covered by tests." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "// Function 1. f(x) = 0\n", - "function Function_Zero (x : Int) : Int {\n", - " return 0;\n", - "}\n", - "\n", - "\n", - "// Function 2. f(x) = 1\n", - "function Function_One (x : Int) : Int {\n", - " return 1;\n", - "}\n", - "\n", - "\n", - "// Function 3. f(x) = x mod 2 (least significant bit of x)\n", - "function Function_Xmod2 (x : Int) : Int {\n", - " return x % 2;\n", - "}\n", - "\n", - "\n", - "// Function 4. f(x) = 1 if the binary notation of x has odd number of 1s, and 0 otherwise\n", - "function Function_OddNumberOfOnes (x : Int) : Int {\n", - " mutable nOnes = 0;\n", - " mutable xBits = x;\n", - " while (xBits > 0) {\n", - " if (xBits % 2 > 0) {\n", - " set nOnes += 1;\n", - " }\n", - " set xBits /= 2;\n", - " }\n", - " return nOnes % 2;\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Exercise 1: Implement a classical function in Q#\n", - "\n", - "Try to implement a similar classical function in Q#!\n", - "\n", - "**Inputs:** \n", - "1. An integer $x$.\n", - "2. The number of bits in the input $N$ ($1 \\le N \\le 5$, $0 \\le x \\le 2^N-1$).\n", - "\n", - "**Output:** Return $f(x) = \\text{the most significant bit of }x$.\n", - "\n", - "> Useful documentation: [Q# Bitwise Expressions](https://docs.microsoft.com/azure/quantum/user-guide/language/expressions/bitwiseexpressions)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%kata T1_ClassicalFunction \n", - "\n", - "function Function_MostSignificantBit (x : Int, N : Int) : Int {\n", - " // ...\n", - "}" + "This tutorial consists of several notebooks:\n", + "1. [Part I](./DeutschJozsaAlgorithmTutorial_P1.ipynb) covers the problem statement and the classical algorithm for solving it.\n", + "2. [Part II](./DeutschJozsaAlgorithmTutorial_P2.ipynb) introduces single-qubit quantum oracles and Deutsch algorithm for solving the problem for one-bit input functions.\n", + "3. Part III introduces multi-qubit quantum oracles and Deutsch-Jozsa algorithm for solving the general case of the problem." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Classical algorithm\n", - "\n", - "If we solve this problem classically, how many calls to the given function will we need? \n", - "\n", - "The first call will give us no information - regardless of whether it returns 0 or 1, the function could still be constant or balanced.\n", - "In the best case scenario the second call will return a different value and we'll be able to conclude that the function is balanced in just $2$ calls. \n", - "However, if we get the same value for the first two calls, we'll have to keep querying the function until either we get a different value or until we do $2^{N-1}+1$ queries that will return the same value - in this case we'll know for certain that the function will be constant.\n", - "\n", - "## Exercise 2: Implement the classical algorithm!\n", - "\n", - "Q# is a domain-specific language, so it is not designed to handle arbitrary classical computations. However, this classical algorithm is so simple that you can easily implement it in Q#. Try it!\n", + "# Part III. Multi-qubit quantum oracles and Deutsch-Jozsa algorithm\n", "\n", - "**Inputs:** \n", - "1. The number of bits in the input $N$ ($1 \\le N \\le 5$).\n", - "2. The \"black box\" function that evaluates $f(x)$ on any given input $x \\in [0, 2^N-1]$. \n", - " You are guaranteed that the function implemented by the black box is either constant or balanced.\n", + "## Multi-qubit oracles: the definition\n", "\n", - "**Goal:** Return `true` if the function is constant, or `false` if it is balanced.\n", + "Expanding on the oracle definition from [part II of this tutorial](./DeutschJozsaAlgorithmTutorial_P2.ipynb#Single-qubit-oracles:-the-definition),\n", + "the oracle for the general Deutsch-Jozsa problem is defined using a classical function $f : \\{0, 1\\}^N \\to \\{0, 1\\}$ which takes an $N$-bit binary input and produces an 1-bit binary output.\n", "\n", - "> Useful documentation: [Q# statements](https://docs.microsoft.com/azure/quantum/user-guide/language/statements/)." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%kata T2_ClassicalAlgorithm \n", - "\n", - "operation IsFunctionConstant_Classical (N : Int, f : (Int -> Int)) : Bool {\n", - " // ...\n", - "}" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Part II. Quantum Oracles\n", - "\n", - "## Definition\n", - "A quantum oracle is a \"black box\" operation which is used as input to another algorithm. This operation is implemented in a way which allows to perform calculations not only on individual inputs, but also on superpositions of inputs. \n", - "\n", - "> This is *not* the same as being able to evaluate the function on all inputs at once, since you will not be able to extract the evaluation results!\n", - "\n", - "Oracles are often defined using a classical function, in the case of Deutsch-Jozsa algorithm the function $f : \\{0, 1\\}^N \\to \\{0, 1\\}$ takes an $N$-bit binary input and produces an 1-bit binary output.\n", - "\n", - "The oracle has to act on quantum states instead of classical values. \n", - "To enable this, integer input $x$ is represented in binary $x = (x_{0}, x_{1}, \\dots, x_{N-1})$, \n", + "To enable the oracle to act on quantum states instead of classical values, the integer input $x$ is represented in binary $x = (x_{0}, x_{1}, \\dots, x_{N-1})$, \n", "and encoded into an $N$-qubit register: $|\\vec{x} \\rangle = |x_{0} \\rangle \\otimes |x_{1} \\rangle \\otimes \\cdots \\otimes |x_{N-1} \\rangle$.\n", - "\n", - "The type of oracles used in this tutorial are called *phase oracles*. A phase oracle $U_f$ encodes the value of the classical function $f$ it implements in the phase of the qubit state as follows:\n", + "The phase oracle $U_f$ for this function is defined as follows:\n", "\n", "$$U_f |\\vec{x} \\rangle = (-1)^{f(x)} |\\vec{x} \\rangle$$\n", "\n", - "In our case $f$ can return only two values, 0 or 1, which result in no phase change or adding a $-1$ phase, respectively.\n", + "## Implementing multi-qubit oracles in Q#\n", "\n", - "The effect of such an oracle on any single basis state is not particularly interesting: it just adds a global phase which is not something you can observe. However, if you apply this oracle to a *superposition* of basis states, its effect becomes noticeable. \n", - "Remember that quantum operations are linear: if you define the effect of an operation on the basis states, you'll be able to deduce its effect on superposition states (which are just linear combinations of the basis states) using its linearity. \n", - "\n", - "> ## Example: Deutsch algorithm\n", - ">\n", - "> Consider, for example, the case of $N = 1$: there are two possible inputs to the function, $|0\\rangle$ and $|1\\rangle$, and we can apply the oracle to their superposition:\n", - ">\n", - "> $$U_f \\left( \\frac{1}{\\sqrt2} \\big( |0\\rangle + |1\\rangle \\big) \\right) \n", - "= \\frac{1}{\\sqrt2} \\big( U_f |0\\rangle + U_f |1\\rangle \\big) \n", - "= \\frac{1}{\\sqrt2} \\big( (-1)^{f(0)} |0\\rangle + (-1)^{f(1)} |1\\rangle \\big)$$.\n", - ">\n", - "> * If $f(0) = f(1)$, the phases of the two basis states are the same, and the resulting state is $|+\\rangle = \\frac{1}{\\sqrt2} \\big( |0\\rangle + |1\\rangle \\big)$ (up to a global phase). \n", - "> * If $f(0) \\neq f(1)$, the phases of the two basis states differ by a factor of $-1$, and the resulting state is $|-\\rangle = \\frac{1}{\\sqrt2} \\big( |0\\rangle - |1\\rangle \\big)$ (up to a global phase). \n", - "> * The states $|+\\rangle$ and $|-\\rangle$ can be distinguished using measurement: if you apply the H gate to each of them, you'll get $H|+\\rangle = |0\\rangle$ if $f(0) = f(1)$, or $H|-\\rangle = |1\\rangle$ if $f(0) \\neq f(1)$. This means that one oracle call does not let you calculate both $f(0)$ and $f(1)$, but it allows you to figure out whether $f(0) = f(1)$.\n", - ">\n", - "> This is a special case of the Deutsch–Jozsa algorithm, called the Deutsch algorithm. \n", - "\n", - "## Implementing oracles in Q#\n", - "\n", - "Now that we've discussed the mathematical definition of the oracles, let's take a look at how to implement oracles for some classical functions in Q#. We'll consider the same 4 functions we used as an example in the first section.\n", + "Let's take a look at how to implement multi-qubit oracles for some classical functions in Q#. We'll consider the same functions we used as an example in part I of the tutorial.\n", "\n", "> **Note:**\n", - "All code snippets before Exercise 3 are just examples. They don't need to be modified and are not covered by tests." + "All code snippets before Exercise 5 are just examples. They don't need to be modified and are not covered by tests." ] }, { @@ -207,9 +45,8 @@ "source": [ "### $f(x) \\equiv 0$\n", "\n", - "This is the easiest function to implement: if $f(x) \\equiv 0$, $U_f |x\\rangle \\equiv (-1)^0 |x\\rangle = |x\\rangle$. \n", - "This means that $U_f$ is an identity - a transformation which does absolutely nothing! \n", - "This is very easy to express in Q#:" + "This is the easiest function to implement: same as in the single-qubit oracle case, if $f(x) \\equiv 0$, $U_f |x\\rangle \\equiv (-1)^0 |x\\rangle = |x\\rangle$. \n", + "This means that $U_f$ is an identity - a transformation which does absolutely nothing!" ] }, { @@ -229,11 +66,7 @@ "source": [ "### $f(x) \\equiv 1$\n", "\n", - "The second constant function is slightly trickier: if $f(x) \\equiv 1$, $U_f |x\\rangle \\equiv (-1)^1 |x\\rangle = - |x\\rangle$. \n", - "Now $U_f$ is a negative identity, i.e., a transformation which applies a global phase of $-1$ to the state. \n", - "A lot of algorithms just ignore the global phase accumulated in them, since it is not observable. \n", - "However, if we want to be really meticulous, we can use Q# library operation [Microsoft.Quantum.Intrinsic.R](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.r) which performs a given rotation around the given axis. \n", - "When called with `PauliI` axis, this operation applies a global phase; since it doesn't take the input into account, it can be applied to any qubit, for example, the first qubit of the input." + "The second constant function is also similar to the single-qubit oracle case: if $f(x) \\equiv 1$, $U_f |x\\rangle \\equiv (-1)^1 |x\\rangle = - |x\\rangle$, the oracle applies a global phase of $-1$ to the state which can be done using Q# library operation [Microsoft.Quantum.Intrinsic.R](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.r):" ] }, { @@ -257,15 +90,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### $f(x) = x \\text{ mod } 2$\n", + "### $f(x) = x \\bmod 2$\n", "\n", "In this oracle we will finally need to use the input! The binary representation of $x$ is $x = (x_{0}, x_{1}, \\dots, x_{N-1})$, with the least significant bit encoded in the last bit (stored in the last qubit of the input array): $f(x) = x_{N-1}$. Let's use this in the oracle effect expression:\n", "\n", "$$U_f |x\\rangle = (-1)^{f(x)} |x\\rangle = (-1)^{x_{N-1}} |x\\rangle = |x_{0} \\rangle \\otimes \\cdots \\otimes |x_{N-2} \\rangle \\otimes (-1)^{x_{N-1}} |x_{N-1}\\rangle$$\n", "\n", - "This means that we only need to use the last qubit in the implementation: do nothing if it is $|0\\rangle$ and apply a phase of $-1$ if it is $|1\\rangle$. This is exactly the effect of the [Z gate](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.z); as a reminder, \n", - "\n", - "$$Z = \\begin{bmatrix} 1 & 0 \\\\ 0 & -1\\end{bmatrix}: Z |0\\rangle = |0\\rangle, Z |1\\rangle = -|1\\rangle$$\n", + "This means that we only need to use the last qubit in the implementation: do nothing if it is $|0\\rangle$ and apply a phase of $-1$ if it is $|1\\rangle$. This is exactly the effect of the [Z gate](https://docs.microsoft.com/qsharp/api/qsharp/microsoft.quantum.intrinsic.z).\n", "\n", "Finally, the expression for the oracle is:\n", "\n", @@ -333,7 +164,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exercise 3: Implement a quantum oracle in Q#\n", + "## Exercise 5: Implement a quantum oracle in Q#\n", "\n", "You're ready to try and write some actual quantum code! Implement a quantum oracle that corresponds to the classical function from exercise 1.\n", "\n", @@ -351,7 +182,7 @@ }, "outputs": [], "source": [ - "%kata T3_QuantumOracle\n", + "%kata T5_MostSignificantBitOracle\n", "\n", "operation PhaseOracle_MostSignificantBit (x : Qubit[]) : Unit {\n", " // ...\n", @@ -362,9 +193,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Part III. Quantum Algorithm\n", + "## Deutsch-Jozsa Algorithm\n", "\n", - "Now that we have figured out how the oracles work and looked at the Deutsch algorithm, we can get back to solving the big problem. We'll present the algorithm in detail step-by-step and summarize it in the end. Finally, we'll demonstrate the behavior of the algorithm visually for two different oracles.\n", + "Now that we have looked at the Deutsch algorithm and practiced implementing multi-qubit oracles, we can get back to solving the big problem. We'll present the algorithm in detail step-by-step and summarize it in the end. Finally, we'll demonstrate the behavior of the algorithm visually for two different oracles.\n", "\n", "### Inputs\n", "\n", @@ -464,7 +295,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Second, consider the `PhaseOracle_Xmod2` oracle discussed above, which implements a balanced function $f(x) = x \\text{ mod } 2$. Observe how the final step of the algorithm converges on a non-zero measurement, indicating that the function is not constant.\n", + "Second, consider the `PhaseOracle_Xmod2` oracle discussed above, which implements a balanced function $f(x) = x \\bmod 2$. Observe how the final step of the algorithm converges on a non-zero measurement, indicating that the function is not constant.\n", "\n", "" ] @@ -473,7 +304,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Exercise 4: Implement the quantum algorithm!\n", + "## Exercise 6: Implement the Deutsch-Jozsa algorithm!\n", "\n", "**Inputs:** \n", "1. The number of bits in the input $N$ ($1 \\le N \\le 5$).\n", @@ -491,7 +322,7 @@ "metadata": {}, "outputs": [], "source": [ - "%kata T4_QuantumAlgorithm\n", + "%kata T6_DeutschJozsaAlgorithm\n", "\n", "operation DeutschJozsaAlgorithm (N : Int, oracle : (Qubit[] => Unit)) : Bool {\n", " // Create a boolean variable for storing the return value.\n", @@ -530,11 +361,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Part IV. Running the Algorithm\n", + "## Running the Deutsch-Jozsa algorithm\n", "\n", - "You have implemented the quantum version of the algorithm - congratulations! The last step is to combine everything you've seen so far - run your code to check whether the oracles you've seen in part II implement constant or balanced functions.\n", + "You have implemented the quantum version of the Deutsch-Jozsa algorithm - congratulations! The last step is to combine everything you've seen so far - run your code to check whether the oracles you've seen in part II implement constant or balanced functions.\n", "\n", - "> This is an open-ended task, and is not covered by a unit test. To run the code, execute the cell with the definition of the `Run_DeutschJozsaAlgorithm` operation first; if it compiled successfully without any errors, you can run the operation by executing the next cell (`%simulate Run_DeutschJozsaAlgorithm`).\n", + "> This is an open-ended task, and is not covered by a unit test. To run the code, execute the cell with the definition of the `RunDeutschJozsaAlgorithm` operation first; if it compiled successfully without any errors, you can run the operation by executing the next cell (`%simulate RunDeutschJozsaAlgorithm`).\n", "\n", "> Note that this task relies on your implementations of the previous tasks. If you are getting the \"No variable with that name exists.\" error, you might have to execute previous code cells before retrying this task. Don't forget to execute Q# code cells that define oracles in part II!" ] @@ -575,7 +406,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Part V. Different function types - what if our function is neither balanced nor constant?\n", + "## Different function types - what if our function is neither balanced nor constant?\n", "\n", "The setup for the Deutsch-Josza algorithm presupposes that the function we're trying to identify is perfectly constant or perfectly balanced in its output (indeed, we haven't even considered the possibility that an input outside of this space could be used). Although the algorithm will not converge to a solution in the same manner that we've seen so far when selecting another type of function as input, we can still derive some intuition for how quantum states behave probabilistically by considering some outside-the-box cases.\n", "\n", @@ -651,7 +482,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Part VI. What's Next?\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", "\n", diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/README.md b/tutorials/ExploringDeutschJozsaAlgorithm/README.md index e77d40a65f3..f44ace0ec14 100644 --- a/tutorials/ExploringDeutschJozsaAlgorithm/README.md +++ b/tutorials/ExploringDeutschJozsaAlgorithm/README.md @@ -2,8 +2,8 @@ This folder contains a Notebook tutorial on the Deutsch-Jozsa algorithm - a quantum computing algorithm that has no practical use, but is famous for being one of the first examples of a quantum algorithm that is exponentially faster than any deterministic classical algorithm. -You can run the tutorial online [here](https://mybinder.org/v2/gh/Microsoft/QuantumKatas/main?urlpath=/notebooks/tutorials/ExploringDeutschJozsaAlgorithm%2FDeutschJozsaAlgorithmTutorial.ipynb). Alternatively, you can install Jupyter and Q# on your machine, as described [here](https://docs.microsoft.com/azure/quantum/install-jupyter-qdk), and run the tutorial locally by navigating to this folder and starting the notebook from command line using the following command: +You can run the tutorial online [here](https://mybinder.org/v2/gh/Microsoft/QuantumKatas/main?urlpath=/notebooks/tutorials/ExploringDeutschJozsaAlgorithm%2FDeutschJozsaAlgorithmTutorial_P1.ipynb). Alternatively, you can install Jupyter and Q# on your machine, as described [here](https://docs.microsoft.com/azure/quantum/install-jupyter-qdk), and run the tutorial locally by navigating to this folder and starting the notebook from command line using the following command: - jupyter notebook DeutschJozsaAlgorithmTutorial.ipynb + jupyter notebook DeutschJozsaAlgorithmTutorial_P1.ipynb The Q# project in this folder contains the back-end of the tutorial and is not designed for direct use. diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/ReferenceImplementation.qs b/tutorials/ExploringDeutschJozsaAlgorithm/ReferenceImplementation.qs index b05a24c0abf..fc1b3f622bc 100644 --- a/tutorials/ExploringDeutschJozsaAlgorithm/ReferenceImplementation.qs +++ b/tutorials/ExploringDeutschJozsaAlgorithm/ReferenceImplementation.qs @@ -10,6 +10,7 @@ namespace Quantum.Kata.DeutschJozsaAlgorithm { + open Microsoft.Quantum.Math; open Microsoft.Quantum.Arrays; open Microsoft.Quantum.Diagnostics; open Microsoft.Quantum.Intrinsic; @@ -44,20 +45,37 @@ namespace Quantum.Kata.DeutschJozsaAlgorithm { ////////////////////////////////////////////////////////////////// - // Part II. Quantum oracles + // Part II. Single-bit problem ////////////////////////////////////////////////////////////////// - + // Exercise 3. - operation PhaseOracle_MostSignificantBit_Reference (x : Qubit[]) : Unit is Adj { - Z(x[0]); + operation PhaseOracle_OneMinusX_Reference (x : Qubit) : Unit is Adj + Ctl { + Z(x); + R(PauliI, 2.0 * PI(), x); + } + + + // Exercise 4. + operation DeutschAlgorithm_Reference (oracle : (Qubit => Unit)) : Bool { + use x = Qubit(); + H(x); + oracle(x); + H(x); + return M(x) == Zero; } ////////////////////////////////////////////////////////////////// - // Part III. Quantum algorithm + // Part III. Multi-bit problem ////////////////////////////////////////////////////////////////// - // Exercise 4. + // Exercise 5. + operation PhaseOracle_MostSignificantBit_Reference (x : Qubit[]) : Unit is Adj { + Z(x[0]); + } + + + // Exercise 6. operation DeutschJozsaAlgorithm_Reference (N : Int, oracle : (Qubit[] => Unit)) : Bool { mutable isConstantFunction = true; use x = Qubit[N]; diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/Tasks.qs b/tutorials/ExploringDeutschJozsaAlgorithm/Tasks.qs index 223811fab1f..1ab08160447 100644 --- a/tutorials/ExploringDeutschJozsaAlgorithm/Tasks.qs +++ b/tutorials/ExploringDeutschJozsaAlgorithm/Tasks.qs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. ////////////////////////////////////////////////////////////////// @@ -33,20 +33,32 @@ namespace Quantum.Kata.DeutschJozsaAlgorithm { ////////////////////////////////////////////////////////////////// - // Part II. Quantum oracles + // Part II. Single-bit problem ////////////////////////////////////////////////////////////////// // Exercise 3. - operation PhaseOracle_MostSignificantBit (x : Qubit[]) : Unit { + operation PhaseOracle_OneMinusX (x : Qubit) : Unit is Adj + Ctl { // ... } + // Exercise 4. + operation DeutschAlgorithm (oracle : (Qubit => Unit)) : Bool { + return false; + } + + ////////////////////////////////////////////////////////////////// - // Part III. Quantum algorithm + // Part III. Multi-bit problem ////////////////////////////////////////////////////////////////// - // Exercise 4. + // Exercise 5. + operation PhaseOracle_MostSignificantBit (x : Qubit[]) : Unit { + // ... + } + + + // Exercise 6. operation DeutschJozsaAlgorithm (N : Int, oracle : (Qubit[] => Unit)) : Bool { // ... return false; diff --git a/tutorials/ExploringDeutschJozsaAlgorithm/Tests.qs b/tutorials/ExploringDeutschJozsaAlgorithm/Tests.qs index 23f398acc62..9868a0bc4d3 100644 --- a/tutorials/ExploringDeutschJozsaAlgorithm/Tests.qs +++ b/tutorials/ExploringDeutschJozsaAlgorithm/Tests.qs @@ -8,6 +8,8 @@ namespace Quantum.Kata.DeutschJozsaAlgorithm { + open Microsoft.Quantum.Canon; + open Microsoft.Quantum.Math; open Microsoft.Quantum.Intrinsic; open Microsoft.Quantum.Diagnostics; @@ -65,23 +67,73 @@ namespace Quantum.Kata.DeutschJozsaAlgorithm { ////////////////////////////////////////////////////////////////// - // Part II. Quantum oracles + // Part II. Single-bit problem ////////////////////////////////////////////////////////////////// - + + // We need to compare phase oracles as their controlled versions to account for the global phase. + operation ControlledWrapper (qs : Qubit[], phaseOracle : (Qubit => Unit is Adj + Ctl)) : Unit is Adj + Ctl { + Controlled phaseOracle([qs[0]], qs[1]); + } + // Exercise 3. @Test("QuantumSimulator") - operation T3_QuantumOracle () : Unit { - for N in 1 .. 5 { - AssertOperationsEqualReferenced(N, PhaseOracle_MostSignificantBit, PhaseOracle_MostSignificantBit_Reference); + operation T3_PhaseOracle_OneMinusX () : Unit { + AssertOperationsEqualReferenced(2, ControlledWrapper(_, PhaseOracle_OneMinusX), + ControlledWrapper(_, PhaseOracle_OneMinusX_Reference)); + } + + + // Exercise 4. + operation CheckDeutschAlgorithm (oracle : (Qubit => Unit), expected : Bool, functionName : String) : Unit { + Message($"Testing {functionName}..."); + + let actual = DeutschAlgorithm(oracle); + + // check that the return value is correct + if (actual != expected) { + let actualStr = ConstantOrBalanced(actual); + let expectedStr = ConstantOrBalanced(expected); + fail $" identified as {actualStr} but it is {expectedStr}."; + } + + let nu = GetOracleCallsCount(oracle); + if (nu > 1) { + fail $" took {nu} oracle calls to decide; you are only allowed to call the oracle once"; + } + + Message(" correct!"); + } + + + @Test("Microsoft.Quantum.Katas.CounterSimulator") + operation T4_DeutschAlgorithm () : Unit { + ResetOracleCallsCount(); + + for (oracle, expectedVerdict, name) in [(I, true, "f(x) = 0"), + (R(PauliI, 2.0 * PI(), _), true, "f(x) = 1"), + (Z, false, "f(x) = x"), + (BoundCA([X, Z, X]), false, "f(x) = 1 - x")] { + CheckDeutschAlgorithm(oracle, expectedVerdict, name); } } ////////////////////////////////////////////////////////////////// - // Part III. Quantum algorithm + // Part III. Multi-bit problem ////////////////////////////////////////////////////////////////// - // Exercise 4. + // TODO: switch exercise 5 to a controlled wrapper as well + + // Exercise 5. + @Test("QuantumSimulator") + operation T5_MostSignificantBitOracle () : Unit { + for N in 1 .. 5 { + AssertOperationsEqualReferenced(N, PhaseOracle_MostSignificantBit, PhaseOracle_MostSignificantBit_Reference); + } + } + + + // Exercise 6. operation CheckQuantumAlgorithm (N : Int, oracle : (Qubit[] => Unit), expected : Bool, functionName : String) : Unit { Message($"Testing {functionName}..."); @@ -104,7 +156,7 @@ namespace Quantum.Kata.DeutschJozsaAlgorithm { @Test("Microsoft.Quantum.Katas.CounterSimulator") - operation T4_QuantumAlgorithm () : Unit { + operation T6_DeutschJozsaAlgorithm () : Unit { ResetOracleCallsCount(); CheckQuantumAlgorithm(4, PhaseOracle_Zero_Reference, true, "f(x) = 0");