From 9a1631b282154074587c306d0960dcfe69a2ff6a Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Wed, 7 Sep 2022 12:41:42 -0500 Subject: [PATCH 1/7] Add Grover's search Sudoku solver Azure Quantum sample --- .../Grovers-sudoku-quantinuum.csproj | 9 + .../Grovers-sudoku-quantinuum.ipynb | 1425 +++++++++++++++++ samples/azure-quantum/grover-sudoku/README.md | 41 + 3 files changed, 1475 insertions(+) create mode 100644 samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj create mode 100644 samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb create mode 100644 samples/azure-quantum/grover-sudoku/README.md diff --git a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj b/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj new file mode 100644 index 000000000000..4467d0e4e1d4 --- /dev/null +++ b/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj @@ -0,0 +1,9 @@ + + + + Exe + net6.0 + Any + + + diff --git a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb b/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb new file mode 100644 index 000000000000..25e0fd5546ad --- /dev/null +++ b/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb @@ -0,0 +1,1425 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Solving Sudoku Puzzles Using Grover's Search\n", + "\n", + "In this notebook we will be solving the classic puzzle Sudoku using Grover's search.\n", + "\n", + "We will be basing our algorithm off [this sample](https://github.com/microsoft/Quantum/tree/main/samples/algorithms/sudoku-grover).\n", + "Here we adapt the sample to run on actual hardware.\n", + "Given that quantum hardware is in the NISQ period right now, we need to minimize qubit count and circuit depth (number of gates) required by the algorithm.\n", + "\n", + "Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Q# notebooks integrating with python.\n", + "This will further enable us to have some convenience in the data structures we build, such as classical validation of Sudoku puzzles.\n", + "\n", + "\n", + "**IMPORTANT IF RUNNING FROM VSCODE**: Please set the setting `notebook.output.textLineLimit` to at least `2000` to see correct printings of Sudoku puzzles" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "shellscript" + } + }, + "outputs": [], + "source": [ + "import qsharp # Enable Q#-Python integration" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Defining a Classical Data Structure for Sudoku Puzzles\n", + "Let us first write the code to define, validate, and print Sudoku puzzles. This code will be entirely classical and written in Python, serving as an example of integration of classical and quantum code. \n", + "Later we will expand the functionality of the Sudoku class we are defining with quantum computation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "from copy import deepcopy\n", + "\n", + "\n", + "class Sudoku:\n", + " data: list = [] # list[list[int]]\n", + "\n", + " def size(self) -> int:\n", + " \"\"\"The width/height of the puzzle\"\"\"\n", + " return len(self.data)\n", + "\n", + " def empty_at(self, i: int, j: int) -> bool:\n", + " \"\"\"Checks if the cell at a given location is empty\"\"\"\n", + " return self.at(i, j) == 0\n", + "\n", + " def at(self, i: int, j: int) -> int:\n", + " \"\"\"Return the value of a cell at a given location (0 if the cell is empty)\"\"\"\n", + " return self.data[i][j]\n", + "\n", + " def set(self, i: int, j: int, val: int) -> None:\n", + " \"\"\"Sets the value of a cell at a given location (val=0 will empty the cell)\"\"\"\n", + " self.data[i][j] = val\n", + "\n", + " def __init__(self, data: list) -> None:\n", + " \"\"\"Initializes the puzzle. \n", + " data has to be a 2D array of size 4x4 or 9x9 with row-column indexing. \n", + " Cells marked 0 will be considered empty\"\"\"\n", + " size = len(data)\n", + " assert (size in {4, 9})\n", + " # We currently only support Sudoku puzzles up to size 9\n", + " # Larger Sudoku puzzles would require an unreasonable amount of RAM for quantum simulation\n", + " for row in data:\n", + " assert(len(row) == size)\n", + " self.data = deepcopy(data)\n", + "\n", + " def bit_length(self) -> int:\n", + " \"\"\"The number of bits required to represent a number in the puzzle\"\"\"\n", + " if self.size() == 4:\n", + " return 2\n", + " return 4\n", + "\n", + " def __str__(self) -> str:\n", + " \"\"\"Creates a human-readable representation of the puzzle\"\"\"\n", + " str = ''\n", + " for row in self.data:\n", + " str += ('-' * (4 * self.size() + 1)) + '\\n'\n", + " for el in row:\n", + " if el == 0:\n", + " str += \"| \"\n", + " else:\n", + " str += f\"| {el} \"\n", + " str += '|\\n'\n", + " str += ('-' * (4 * self.size() + 1)) + '\\n'\n", + " return str\n", + "\n", + " def __eq__(self, __o: object) -> bool:\n", + " if not isinstance(__o, Sudoku):\n", + " return False\n", + " # Compare flattened data\n", + " self_data_flat = [el for row in self.data for el in row]\n", + " o_data_flat = [el for row in __o.data for el in row]\n", + " return self_data_flat == o_data_flat\n", + "\n", + " def is_valid(self) -> bool:\n", + " \"\"\"Checks whether the puzzle is complete and meets all Sudoku constraints\"\"\"\n", + " # Check for empty cells\n", + " for i in range(self.size()):\n", + " for j in range(self.size()):\n", + " if self.empty_at(i, j):\n", + " return False\n", + "\n", + " # Check rows\n", + " for row in range(self.size()):\n", + " values_in_row = set()\n", + " for i in range(self.size()):\n", + " curr = self.at(row, i)\n", + " if curr in values_in_row:\n", + " return False\n", + " values_in_row.add(curr)\n", + " # Check cols\n", + " for col in range(self.size()):\n", + " values_in_col = set()\n", + " for j in range(self.size()):\n", + " curr = self.at(j, col)\n", + " if curr in values_in_col:\n", + " return False\n", + " values_in_col.add(curr)\n", + " # Check subgrids\n", + " sub_size = math.floor(math.sqrt(self.size()))\n", + "\n", + " for sub_grid_i in range(sub_size):\n", + " for sub_grid_j in range(sub_size):\n", + " sub_start_i = sub_grid_i * sub_size\n", + " sub_start_j = sub_grid_j * sub_size\n", + " values_in_sub_grid = set()\n", + " for i in range(sub_start_i, sub_start_i + sub_size):\n", + " for j in range(sub_start_j, sub_start_j + sub_size):\n", + " curr = self.at(i, j)\n", + " if curr in values_in_sub_grid:\n", + " return False\n", + " values_in_sub_grid.add(curr)\n", + " # No violations found\n", + " return True\n", + "\n", + " def count_empty_squares(self) -> int:\n", + " \"\"\"Returns the number of empty squares in the puzzle\"\"\"\n", + " empty = 0\n", + " for i in range(self.size()):\n", + " for j in range(self.size()):\n", + " if self.empty_at(i, j):\n", + " empty += 1\n", + " return empty\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's test our code so far." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sudoku = Sudoku([\n", + " [1, 3, 4, 2],\n", + " [2, 4, 3, 1],\n", + " [3, 2, 1, 4],\n", + " [4, 1, 2, 3]\n", + " ])\n", + "print(sudoku)\n", + "print(\"Valid!\" if sudoku.is_valid() else \"Invalid\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sudoku Puzzles as Vertex Coloring Problems\n", + "\n", + "We will use Grover's search to solve Sudoku puzzles by viewing them as a vertex coloring problem: each empty cell of the puzzle is a vertex that needs to have a color assigned based on the constraints imposed by the other cells.\n", + "\n", + "Python pre-processing code converts the input puzzle into a set of constraints and passes it to the quantum part of the program.\n", + "Q# code solves the problem defined by the constraints and returns a bitstring that represents the numbers assigned to the empty cells. \n", + "Finally, Python post-processing code parses the solution and validates its correctness.\n", + "\n", + "We represent the integer that will be placed in each empty cell as a bitstring of either two (if $n=2$) or four (if $n=3$) bits, where bitstrings are interpreted as binary integers. \n", + "We then concatenate each cell’s bitstrings, remembering the indices of each empty cell. This means that for a $4 \\times 4$ puzzle with $k$ empty cells, we need $2k$ bits for the representation\n", + "of the problem.\n", + "\n", + "The algorithm in the quantum component is Grover's search algorithm, an algorithm that prepares a search space and then uses an oracle to perform \"Grover iterations\".\n", + "A \"Grover iteration\" involves applying an oracle and then diffusion the state.\n", + "\n", + "The amazing thing is that we find the correct results in $\\mathcal{O}(\\sqrt{k})$\n", + "\n", + "## Classical Precomputation\n", + "\n", + "Let's get building: First we classically create the constraints and then translate them into quantum states.\n", + "\n", + "We classically convert the puzzle into two types of constraints:\n", + "- Starting number constraints specify the numbers that cannot be assigned to a cell based on the current nonempty cells.\n", + "- Edge constraints specify that any two empty cells that are in the same row, column, or subgrid cannot be assigned the same number.\n", + "\n", + "The figure below shows the constraints of our running example Sudoku puzzle. \n", + "The red borders represent sub-grid borders, the orange numbers are the predefined numbers. \n", + "The black sets are the sets of possible numbers under the starting number constraints - the numbers 1 through 4, except the numbers found in the same row, column, or subgrid as the cell. \n", + "The arrows show the edge constraints." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "\n", + "\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will encode the constraints as lists. Each empty square of the grid will be assigned an index.\n", + "For the pair of empty squares with indices $i, j$ the edge constraint will be expressed by the tuple $(i,j)$ or $(j,i)$.\n", + "For an empty square with index $i$ that cannot have value $x$, the starting number constraint will be expressed as $(i,x)$.\n", + "\n", + "The constraint definition code returns a tuple of arrays representing the constraints and the indexed list for mapping indices to empty squares, where the $i^{\\text{th}}$ element is the coordinate of the $i^{\\text{th}}$ empty square." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def get_constraints(self) -> tuple: # tuple of: [list[(int, int)], # Empty square edges\n", + " # list[(int, int)], # Starting constraints\n", + " # list[(int, int)]]: # Empty squares\n", + " sub_size = math.floor(math.sqrt(self.size()))\n", + " empty_indices = dict()\n", + " empty_squares = list()\n", + " empty_square_edges = list()\n", + " starting_number_constraints = set() # We want to avoid duplicates\n", + " empty_index = 0\n", + "\n", + " for i in range(self.size()):\n", + " for j in range(self.size()):\n", + "\n", + " # Only consider empty_squares for constraint calculation\n", + " \n", + " if not self.empty_at(i, j):\n", + " continue\n", + " empty_indices[i, j] = empty_index\n", + " empty_squares.append((i, j))\n", + "\n", + " # Introspect subgrid for constraints \n", + " # (i.e. the 2x2/3x3 square of the current element)\n", + "\n", + " i_sub_grid = (i // sub_size) * sub_size\n", + " j_sub_grid = (j // sub_size) * sub_size\n", + "\n", + " for i_sub in range(i_sub_grid, i_sub_grid + sub_size):\n", + " for j_sub in range(j_sub_grid, j_sub_grid + sub_size):\n", + " if i_sub == i and j_sub == j:\n", + " continue\n", + " if not self.empty_at(i_sub, j_sub):\n", + " starting_number_constraints.add(\n", + " (empty_index, self.at(i_sub, j_sub) - 1))\n", + " elif j_sub < i_sub and i_sub < i and j_sub < j:\n", + " empty_square_edges.append(\n", + " (empty_index, empty_indices[(i_sub, j_sub)]))\n", + "\n", + " # Check for column constraints\n", + "\n", + " for row_index in range(self.size()):\n", + " if not self.empty_at(row_index, j):\n", + " starting_number_constraints.add(\n", + " (empty_index, self.at(row_index, j) - 1))\n", + " elif row_index < i:\n", + " empty_square_edges.append(\n", + " (empty_index, empty_indices[(row_index, j)]))\n", + "\n", + " # Check for row constraints\n", + "\n", + " for col_index in range(self.size()):\n", + " if not self.empty_at(i, col_index):\n", + " starting_number_constraints.add(\n", + " (empty_index, self.at(i, col_index) - 1))\n", + " elif col_index < j:\n", + " empty_square_edges.append(\n", + " (empty_index, empty_indices[(i, col_index)]))\n", + "\n", + " # Exclude illegal values on a 9x9 puzzle\n", + " # Not needed for 4x4 since 4x4 has bit width 2, which only represents legal values\n", + "\n", + " if self.size() == 9:\n", + " for invalid in range(9, 16):\n", + " starting_number_constraints.add((empty_index, invalid))\n", + "\n", + " empty_index += 1\n", + " return (empty_square_edges, list(starting_number_constraints), empty_squares)\n", + "\n", + "\n", + "Sudoku.get_constraints = get_constraints\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quantum Code\n", + "We have now created our constraints classically. \n", + "Let's convert them into the elements of quantum program.\n", + "\n", + "In the most straightforward approach, we can search through all assignments of integers $[1, n^2]$ to each of the empty cells. In this case, the oracle has to check that the assignment of numbers satisfies both the starting number and the edge constraints.\n", + "In the optimized approach, we handle edge constraints and starting number constraints separately. During state preparation, for each cell, we use the starting number constraints to calculate the allowed values and set its qubit representation into uniform superposition of only these values.\n", + "The oracle will then only include the checks of the edge constraints.\n", + "\n", + "This drastically reduces search space size. \n", + "In the example above, the search space size shrinks from $4^4=256$ to $4$.\n", + "The reduction allows us to use fewer search iterations, resulting in fewer oracle calls, less noise-prone computation, increased performance, and therefore more difficult puzzles we can solve!\n", + "\n", + "Specifically, the optimal number of iterations is given by the formula $i(s) = \\lfloor \\frac{\\pi}{4\\arcsin{\\sqrt{s^{-1}}}} - \\frac{1}{2} \\rceil$, where $s$ is the search space size. \n", + "In the example the optimization reduces the number of iterations from $i(256) = 12$ to $i(4) = 1$.\n", + "Further, we do not need to encode starting constraints the oracle, significantly lowering the number of qubits required.\n", + "\n", + "But since this is a large project, we'll need to break it down into components:\n", + "- Prepare data for the algorithm classically and encode constraints\n", + "- Prepare a search state\n", + "- An oracle\n", + "- Loop over individual iterations\n", + "- Measure and extract the information we need\n", + "\n", + "\n", + "Q# gives us a lot of library features to help us in the process of build our algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "microsoft": { + "language": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "open Microsoft.Quantum.Arithmetic;\n", + "open Microsoft.Quantum.Arrays;\n", + "open Microsoft.Quantum.Convert;\n", + "open Microsoft.Quantum.Intrinsic;\n", + "open Microsoft.Quantum.Logical;\n", + "open Microsoft.Quantum.Math;\n", + "open Microsoft.Quantum.Measurement;\n", + "open Microsoft.Quantum.Preparation;" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "microsoft": { + "language": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "/// # Summary\n", + "/// Encodes stating number constraints into amplitudes.\n", + "///\n", + "/// # Inputs\n", + "/// ## nVertices\n", + "/// The number of vertices in the graph.\n", + "/// ## bitsPerColor\n", + "/// The bit width for number of colors.\n", + "/// ## startingNumberConstraints\n", + "/// The array of (Vertex#, Color) specifying the disallowed colors for vertices.\n", + "///\n", + "/// # Examples\n", + "/// Consider the case where we have 2 vertices, 2 bits per color, and the constraints (0,1),(0,2),(0,3),(1,2).\n", + "/// Then we would get the result where all non-disallowed values have a 1.0 amplitude:\n", + "/// [[1.0, 0.0, 0.0, 0.0], \n", + "/// [1.0, 1.0, 0.0, 1.0]]\n", + "///\n", + "///\n", + "/// # Output\n", + "/// A 2D array of amplitudes where the first index is the cell and the second index is the value of a basis state (i.e., value) for the cell. =\n", + "/// Allowed amplitudes will have a value 1.0, disallowed amplitudes 0.0\n", + "function AllowedAmplitudes(\n", + " nVertices : Int,\n", + " bitsPerColor : Int,\n", + " startingNumberConstraints : (Int, Int)[]\n", + ") : Double[][] {\n", + " mutable amplitudes = [[1.0, size=1 <<< bitsPerColor], size=nVertices];\n", + " for (cell, value) in startingNumberConstraints {\n", + " set amplitudes w/= cell <- (amplitudes[cell] w/ value <- 0.0);\n", + " }\n", + " return amplitudes;\n", + "}\n", + "\n", + "/// # Summary\n", + "/// Prepare an equal superposition of all basis states that satisfy the constraints\n", + "/// imposed by the digits already placed in the grid.\n", + "///\n", + "/// # Inputs\n", + "/// ## nVertices\n", + "/// The number of vertices in the graph.\n", + "/// ## bitsPerColor\n", + "/// The bit width for number of colors.\n", + "/// ## startingNumberConstraints\n", + "/// The array of (Vertex#, Color) specifying the disallowed colors for vertices.\n", + "///\n", + "/// # Remarks\n", + "/// Prepares the search space. Using the allowed amplitudes prepares uniform superposition of all allowed values for each cell\n", + "operation PrepareSearchStatesSuperposition(\n", + " nVertices : Int,\n", + " bitsPerColor : Int,\n", + " startingNumberConstraints : (Int, Int)[],\n", + " register : Qubit[]\n", + ") : Unit is Adj + Ctl {\n", + " // Split the given register into nVertices chunks of size bitsPerColor.\n", + " let colorRegisters = Chunks(bitsPerColor, register);\n", + " // For each vertex, create an array of possible states we're looking at.\n", + " let amplitudes = AllowedAmplitudes(nVertices, bitsPerColor, startingNumberConstraints);\n", + " // For each vertex, prepare a superposition of its possible states on the chunk storing its color.\n", + " for (amps, chunk) in Zipped(amplitudes, colorRegisters) {\n", + " PrepareArbitraryStateD(amps, LittleEndian(chunk));\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We further use our starting number constraints to calculate the size of the search space, which is just the total number of possible combinations.\n", + "With that information we can calculate the number of Grover's iterations we need." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "/// # Summary\n", + "/// Show the size of the search space, i.e. the number of possible combinations\n", + "///\n", + "/// # Inputs\n", + "/// ## nVertices\n", + "/// The number of vertices in the graph.\n", + "/// ## bitsPerColor\n", + "/// The bit width for number of colors.\n", + "/// ## startingNumberConstraints\n", + "/// The array of (Vertex#, Color) specifying the disallowed colors for vertices.\n", + "///\n", + "/// # Output\n", + "/// The size of the search space (i.e., number of possible combinations)\n", + "function SearchSpaceSize(\n", + " nVertices : Int,\n", + " bitsPerColor : Int,\n", + " startingNumberConstraints : (Int, Int)[]\n", + ") : Int {\n", + " mutable colorOptions = [1 <<< bitsPerColor, size=nVertices];\n", + " for (cell, _) in startingNumberConstraints {\n", + " set colorOptions w/= cell <- colorOptions[cell] - 1;\n", + " }\n", + " return Fold(TimesI, 1, colorOptions);\n", + "}\n", + "\n", + "/// # Summary\n", + "/// Estimate the number of iterations required for solution.\n", + "///\n", + "/// # Input\n", + "/// ## searchSpaceSize\n", + "/// The size of the search space.\n", + "///\n", + "/// # Remarks\n", + "/// This is correct for an amplitude amplification problem with a single \n", + "/// correct solution, but would need to be adapted when there are multiple\n", + "/// solutions\n", + "function NIterations(searchSpaceSize : Int) : Int {\n", + " let angle = ArcSin(1. / Sqrt(IntAsDouble(searchSpaceSize)));\n", + " let nIterations = Round(0.25 * PI() / angle - 0.5);\n", + " return nIterations;\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us now build our oracle. We will first build a marking oracle based on our edge constraints and then transform it to a phase oracle.\n", + "\n", + "Our marking oracle operates by marking the states for which each pair of colors connected by an edge constraint is different.\n", + "Since the colors are represented by bit strings, we need a separate operation `ApplyColorEqualityOracle` for comparing the colors and marking their equality." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "microsoft": { + "language": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "/// # Summary\n", + "/// N-bit color equality oracle (no extra qubits.)\n", + "///\n", + "/// # Input\n", + "/// ## color0\n", + "/// First color.\n", + "/// ## color1\n", + "/// Second color.\n", + "/// ## target\n", + "/// Will be flipped if colors are the same.\n", + "operation ApplyColorEqualityOracle(\n", + " color0 : Qubit[], color1 : Qubit[],\n", + " target : Qubit\n", + ")\n", + ": Unit is Adj + Ctl {\n", + " within {\n", + " // compute XOR of q0 and q1 in place (storing it in q1).\n", + " ApplyToEachCA(CNOT, Zipped(color0, color1));\n", + " } apply {\n", + " // if all XORs are 0, the bit strings are equal.\n", + " ControlledOnInt(0, X)(color1, target);\n", + " }\n", + "}\n", + "\n", + "\n", + "/// # Summary\n", + "/// Oracle for verifying vertex coloring. Checks that vertices that are related by an edge constraint do not share a value.\n", + "/// \n", + "/// \n", + "/// # Input\n", + "/// ## nVertices\n", + "/// The number of vertices in the graph.\n", + "/// ## bitsPerColor\n", + "/// The bits per color e.g. 2 bits per color allows for 4 colors.\n", + "/// ## edges\n", + "/// The array of (Vertex#,Vertex#) specifying the Vertices that can not be\n", + "/// the same color.\n", + "///\n", + "/// # Output\n", + "/// An marking oracle that marks as allowed those states in which the colors of qubits related by an edge constraint are not equal.\n", + "operation ApplyVertexColoringOracle (\n", + " nVertices : Int, \n", + " bitsPerColor : Int, \n", + " edges : (Int, Int)[],\n", + " colorsRegister : Qubit[],\n", + " target : Qubit\n", + ")\n", + ": Unit is Adj + Ctl {\n", + " let nEdges = Length(edges);\n", + " // we are looking for a solution that has no edge with same color at both ends\n", + " use edgeConflictQubits = Qubit[nEdges];\n", + " within {\n", + " for ((start, end), conflictQubit) in Zipped(edges, edgeConflictQubits) {\n", + " // Check that endpoints of the edge have different colors:\n", + " // apply ApplyColorEqualityOracle oracle;\n", + " // if the colors are the same the result will be 1, indicating a conflict\n", + " ApplyColorEqualityOracle(\n", + " colorsRegister[start * bitsPerColor .. (start + 1) * bitsPerColor - 1],\n", + " colorsRegister[end * bitsPerColor .. (end + 1) * bitsPerColor - 1],\n", + " conflictQubit\n", + " );\n", + " }\n", + " } apply {\n", + " // If there are no conflicts (all qubits are in 0 state), the vertex coloring is valid.\n", + " ControlledOnInt(0, X)(edgeConflictQubits, target);\n", + " }\n", + "}\n", + "\n", + "/// # Summary\n", + "/// Converts a marking oracle into a phase oracle.\n", + "///\n", + "/// # Input\n", + "/// ## oracle\n", + "/// The oracle which will mark the valid solutions.\n", + "///\n", + "/// # Output\n", + "/// A phase oracle that flips the phase of a state, iff the marking oracle marks a state.\n", + "operation ApplyPhaseOracle (oracle : ((Qubit[], Qubit) => Unit is Adj),\n", + " register : Qubit[]\n", + ")\n", + ": Unit is Adj {\n", + " use target = Qubit();\n", + " within {\n", + " // Put the target into the |-⟩ state.\n", + " X(target);\n", + " H(target);\n", + " } apply {\n", + " // Apply the marking oracle; since the target is in the |-⟩ state,\n", + " // flipping the target if the register satisfies the oracle condition\n", + " // will apply a -1 factor to the state.\n", + " oracle(register, target);\n", + " }\n", + " // We put the target back into |0⟩ so we can return it.\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With what we have now, we can build the main Grover's search algorithm loop." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "microsoft": { + "language": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "/// # Summary\n", + "/// Grover's Algorithm loop.\n", + "///\n", + "/// # Input\n", + "/// ## register\n", + "/// The register of qubits.\n", + "/// ## oracle\n", + "/// The oracle defining the solution we want.\n", + "/// ## iterations\n", + "/// The number of iterations to try.\n", + "///\n", + "/// # Remarks\n", + "/// Unitary implementing Grover's search algorithm.\n", + "operation ApplyGroversAlgorithmLoop(\n", + " register : Qubit[],\n", + " oracle : ((Qubit[], Qubit) => Unit is Adj),\n", + " statePrep : (Qubit[] => Unit is Adj),\n", + " iterations : Int\n", + ")\n", + ": Unit {\n", + " let applyPhaseOracle = ApplyPhaseOracle(oracle, _);\n", + " statePrep(register);\n", + "\n", + " for _ in 1 .. iterations {\n", + " applyPhaseOracle(register);\n", + " within {\n", + " Adjoint statePrep(register);\n", + " ApplyToEachA(X, register);\n", + " } apply {\n", + " Controlled Z(Most(register), Tail(register));\n", + " }\n", + " }\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And the combination of all these components will be the operation `SolvePuzzle` - the entry point to the quantum part of the program.\n", + "\n", + "> To be able to run our code on Azure Quantum, we have to return raw measurement results and convert them to the grid numbers later with classical post-processing code.\n", + ">\n", + "> We also cannot pass arrays of tuples to our operation using Azure Quantum job arguments. Therefore, we split our tuples arrays into pairs of arrays in the classical code, pass them as input parameters of type `Int[]`, and then zip them again in the Q# code.\n", + ">\n", + "> If we were just targeting simulation these compromises would not be necessary." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "microsoft": { + "language": "qsharp" + } + }, + "outputs": [], + "source": [ + "%%qsharp\n", + "\n", + "/// # Summary\n", + "/// Using Grover's search to find vertex coloring.\n", + "///\n", + "/// # Input\n", + "/// ## nVertices\n", + "/// The number of Vertices in the graph.\n", + "/// ## bitsPerColor\n", + "/// The number of bits per color.\n", + "/// ## nIterations\n", + "/// An estimate of the maximum iterations needed.\n", + "/// ## oracle\n", + "/// The Oracle used to find solution.\n", + "/// ## statePrep\n", + "/// An operation that prepares an equal superposition of all basis states in the search space.\n", + "///\n", + "/// # Output\n", + "/// An array giving the color of each vertex.\n", + "operation FindColorsWithGrover(\n", + " nVertices : Int, bitsPerColor : Int, nIterations : Int,\n", + " oracle : ((Qubit[], Qubit) => Unit is Adj),\n", + " statePrep : (Qubit[] => Unit is Adj)) : Result[] {\n", + "\n", + " // Coloring register has bitsPerColor qubits for each vertex\n", + " use register = Qubit[bitsPerColor * nVertices];\n", + "\n", + " Message($\"Trying search with {nIterations} iterations...\");\n", + " if (nIterations > 75) {\n", + " Message($\"Warning: This might take a while\");\n", + " }\n", + " ApplyGroversAlgorithmLoop(register, oracle, statePrep, nIterations);\n", + " return MultiM(register);\n", + "}\n", + "\n", + "/// # Summary\n", + "/// Solve a Sudoku puzzle using Grover's algorithm.\n", + "///\n", + "///\n", + "/// # Input\n", + "/// ## nVertices\n", + "/// number of blank squares.\n", + "/// ## bitsPerColor\n", + "/// The bits per color e.g. 2 bits per color allows for 4 colors.\n", + "/// ## emptySquareEdges{1,2}\n", + "/// The traditional edges passed to the graph coloring algorithm which, \n", + "/// in our case, are empty puzzle squares.\n", + "/// These edges define any \"same row\", \"same column\", \"same sub-grid\" \n", + "/// relationships between empty cells. Due to limitations the tuple list is split.\n", + "///\n", + "/// ## startingNumberConstraints{1,2}\n", + "/// The constraints on the empty squares due to numbers already in the \n", + "/// puzzle when we start. Due to limitations the tuple list is split.\n", + "///\n", + "/// # Output\n", + "/// An array of numbers for each empty square.\n", + "///\n", + "/// # Remarks\n", + "/// The inputs and outputs for the following 4x4 puzzle are:\n", + "/// -----------------\n", + "/// | | 1 | 2 | 3 | <--- empty square #0\n", + "/// -----------------\n", + "/// | 2 | | 0 | 1 | <--- empty square #1\n", + "/// -----------------\n", + "/// | 1 | 2 | 3 | 0 |\n", + "/// -----------------\n", + "/// | 3 | | 1 | 2 | <--- empty square #2\n", + "/// -----------------\n", + "/// emptySquareEdges = [(1, 0),(2, 1)] \n", + "/// empty square #0 can not have the same color/number as empty call #1.\n", + "/// empty square #1 and #2 can not have the same color/number (same column).\n", + "/// startingNumberConstraints = [(0, 2),(0, 1),(0, 3),(1, 1),(1, 2),(1, 0),(2, 1),(2, 2),(2, 3)]\n", + "/// empty square #0 can not have values 2,1,3 because same row/column/2x2grid.\n", + "/// empty square #1 can not have values 1,2,0 because same row/column/2x2grid.\n", + "/// Results = [0,3,0] i.e. Empty Square #0 = 0, Empty Square #1 = 3, Empty Square #2 = 0.\n", + "@EntryPoint()\n", + "operation SolvePuzzle(\n", + " nVertices : Int, bitsPerColor : Int,\n", + " emptySquareEdges1 : Int[],\n", + " emptySquareEdges2 : Int[],\n", + " startingNumberConstraints1: Int[],\n", + " startingNumberConstraints2: Int[]\n", + ") : Result[] {\n", + " let emptySquareEdges = Zipped(emptySquareEdges1, emptySquareEdges2);\n", + " let startingNumberConstraints = Zipped(startingNumberConstraints1, startingNumberConstraints2);\n", + " let oracle = ApplyVertexColoringOracle(nVertices, bitsPerColor, emptySquareEdges, _, _);\n", + " let statePrep = PrepareSearchStatesSuperposition(nVertices, bitsPerColor, startingNumberConstraints, _);\n", + " let searchSpaceSize = SearchSpaceSize(nVertices, bitsPerColor, startingNumberConstraints);\n", + " let numIterations = NIterations(searchSpaceSize);\n", + " Message($\"Solving Sudoku puzzle with #Vertex = {nVertices}\");\n", + " Message($\" Bits Per Color = {bitsPerColor}\");\n", + " Message($\" emptySquareEdges = {emptySquareEdges}\");\n", + " Message($\" startingNumberConstraints = {startingNumberConstraints}\");\n", + " Message($\" Estimated #iterations needed = {numIterations}\");\n", + " Message($\" Search Space Size = {searchSpaceSize}\");\n", + " return FindColorsWithGrover(nVertices, bitsPerColor, numIterations, oracle, statePrep);\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Classical Postprocessing\n", + "Since we can't get the integer results directly from Q#, we will write classical post-processing code to convert the bitstrings we get into integers." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def chunks(lst: list, n: int) -> list:\n", + " \"\"\"\n", + " Yield successive n-sized chunks from lst\n", + " \"\"\"\n", + " for i in range(0, len(lst), n):\n", + " yield lst[i:i + n]\n", + "\n", + "\n", + "def parse_measured_integer(arr: list) -> int:\n", + " \"\"\"\n", + " Little endian bit list to integer\n", + " [1,0,1] -> 7\n", + " [0,1,1] -> 6\n", + " [] -> 0\n", + " \"\"\"\n", + " res = 0\n", + " for i in range(len(arr)):\n", + " if arr[i] != 0:\n", + " res += (2 ** i)\n", + " return res\n", + "\n", + "\n", + "def parse_measured_integers(arr: list, bit_length: int) -> list:\n", + " \"\"\"\n", + " Takes a resulting state vector from `SolvePuzzle`, \n", + " chunks it up into `bit_length` sized chunks and converts each chunk to an integer.\n", + " Returns list of resulting integers\n", + " \"\"\"\n", + " return list(map(parse_measured_integer, chunks(arr, bit_length)))\n", + "\n", + "\n", + "def prepare_constraints(self) -> tuple:\n", + " \"\"\"\n", + " Reshapes constraints to adapt to the limitations of running on Azure Quantum by splitting tuple lists.\n", + " \"\"\"\n", + " (empty_square_edges, starting_number_constraints,\n", + " empty_squares) = self.get_constraints()\n", + " empty_square_edges_1 = list(map(lambda x: x[0], empty_square_edges))\n", + " empty_square_edges_2 = list(map(lambda x: x[1], empty_square_edges))\n", + " starting_number_constraints_1 = list(\n", + " map(lambda x: x[0], starting_number_constraints))\n", + " starting_number_constraints_2 = list(\n", + " map(lambda x: x[1], starting_number_constraints))\n", + " return (empty_square_edges_1, empty_square_edges_2, starting_number_constraints_1, starting_number_constraints_2, empty_squares)\n", + "\n", + "\n", + "Sudoku.prepare_constraints = prepare_constraints\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly we'll create a function that puts our classical preprocessing, our quantum algorithm, and classical postprocessing together to solve Sudoku puzzles." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "def solve_quantum(self, solve_fn):\n", + " \"\"\"\n", + " Solve a Sudoku puzzle based on a quantum execution function that \n", + " takes the parameters for SolveSudoku from the Q# code above \n", + " and returns an array of the resulting bits\n", + " \"\"\"\n", + " (empty_square_edges_1, empty_square_edges_2,\n", + " starting_number_constraints_1, starting_number_constraints_2,\n", + " empty_squares) = self.prepare_constraints()\n", + " measurements = solve_fn(nVertices=len(empty_squares),\n", + " bitsPerColor=self.bit_length(),\n", + " emptySquareEdges1=empty_square_edges_1,\n", + " emptySquareEdges2=empty_square_edges_2,\n", + " startingNumberConstraints1=starting_number_constraints_1,\n", + " startingNumberConstraints2=starting_number_constraints_2)\n", + " found_solution = isinstance(measurements, list)\n", + " if not found_solution:\n", + " # This will be hit when resource estimation is the case\n", + " print(\"No solution computed. Did you use resource estimation?\")\n", + " return measurements\n", + " print(\"Solved puzzle!\")\n", + " solution = parse_measured_integers(measurements, self.bit_length())\n", + " print(f\"Raw solution: {solution}\")\n", + " for empty_idx in range(len(empty_squares)):\n", + " (i, j) = empty_squares[empty_idx]\n", + " # Solution range is [0,3] or [0,8], whereas human-readable puzzles work with [1,4] or [1,9], respectively.\n", + " solved_val = solution[empty_idx] + 1\n", + " self.set(i, j, solved_val)\n", + "\n", + "\n", + "Sudoku.solve_quantum = solve_quantum\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Full State Simulator vs Sparse Simulator\n", + "\n", + "Q# provides two main options for simulating the programs locally: regular (\"full state\") simulation and sparse simulation.\n", + "\n", + "Since the quantum states used in Grover's search inherently have many zero amplitudes and a lot of entanglement, sparse simulation should perform a lot better. \n", + "Let's try both options and see whether this is the case!\n", + "\n", + "To simulate a Q# operation from Python, you can call `QSharpFunctionName.simulate(params)` or `QSharpFunctionName.simulate_sparse(params)}`, where `params` are named parameters for your Q# operation. \n", + "We handle parameter passing in the `solve_quantum` function above, so all that's left to do is to specify which of the simulation functions we want to use as the `solve_fn` parameter.\n", + "\n", + "Let's use regular and sparse simulation on 4x4 puzzles with few empty spaces to compare the performance of both simulators on this type of problems." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "from IPython.display import clear_output\n", + "\n", + "\n", + "def run_puzzles(simulator):\n", + " puzzles = [\n", + " [\n", + " [0, 2, 0, 4],\n", + " [3, 0, 0, 2],\n", + " [0, 0, 4, 1],\n", + " [4, 0, 2, 0]\n", + " ],\n", + " [\n", + " [0, 2, 3, 4],\n", + " [3, 4, 1, 2],\n", + " [2, 3, 4, 1],\n", + " [4, 1, 2, 3]\n", + " ],\n", + " [\n", + " [0, 2, 3, 4],\n", + " [3, 0, 1, 2],\n", + " [2, 3, 4, 1],\n", + " [4, 0, 2, 3]\n", + " ],\n", + " [\n", + " [0, 0, 3, 4],\n", + " [0, 0, 1, 2],\n", + " [2, 3, 4, 1],\n", + " [4, 1, 2, 3]\n", + " ]\n", + " ]\n", + " for puzzle in puzzles:\n", + " sudoku = Sudoku(puzzle)\n", + " sudoku.solve_quantum(simulator)\n", + " print(sudoku)\n", + " print(\"Valid!\" if sudoku.is_valid() else \"Invalid!\")\n", + " if not sudoku.is_valid():\n", + " raise Exception(\"Invalid solution!\")\n", + "\n", + "\n", + "time_reg_sim = time.time()\n", + "run_puzzles(SolvePuzzle.simulate)\n", + "time_reg_sim = time.time() - time_reg_sim\n", + "\n", + "time_sparse_sim = time.time()\n", + "run_puzzles(SolvePuzzle.simulate_sparse)\n", + "time_sparse_sim = time.time() - time_sparse_sim\n", + "\n", + "clear_output(wait=True) # Remove all the execution printing\n", + "\n", + "print(f\"Regular simulator time: {time_reg_sim}s\", flush=True)\n", + "print(f\"Sparse simulator time: {time_sparse_sim}s\", flush=True)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can clearly see that the sparse simulator is two orders of magnitude faster, and this difference will only grow as the program size increases. \n", + "So going forward we will be using it to simulate our code for the larger puzzles. \n", + "Generally speaking, the sparse simulator is often faster than the full state simulator, so make sure to try it when working on your own programs.\n", + "\n", + "## Solving Puzzles With the Sparse Simulator\n", + "Below we have a few puzzles of varying difficulty, including a 9x9 puzzle with over 20 empty squares. \n", + "Most puzzles take very little time thanks to the sparse simulator, so you can run them without waiting too much.\n", + "The `hard` variable turns on running the \"difficult\" $4x4$ and $9x9$ puzzles (the ones with 13 or more empty cells), so if you have some spare time, set it to true and see that our solution works for these challenging problems!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "hard = False\n", + "\n", + "\n", + "puzzles = [\n", + " [ # A base case problem requiring no iterations (3 empty cells)\n", + " [0, 2, 3, 4],\n", + " [3, 0, 1, 2],\n", + " [2, 3, 4, 1],\n", + " [4, 0, 2, 3]\n", + " ],\n", + " [ # This is the most difficult 4x4 puzzle we simulate reasonable quickly (12 empty cells)\n", + " [0, 0, 3, 0],\n", + " [0, 0, 0, 2],\n", + " [0, 0, 4, 0],\n", + " [0, 1, 0, 0]\n", + " ],\n", + " [ # This is the most difficult 4x4 puzzle we can simulate (13 empty cells)\n", + " [0, 0, 0, 0],\n", + " [0, 0, 0, 2],\n", + " [0, 0, 4, 0],\n", + " [0, 1, 0, 0]\n", + " ],\n", + " [ # This is the most difficult 4x4 puzzle we can run on Quantinuum hardware (4 empty cells)\n", + " [0, 0, 3, 4],\n", + " [3, 4, 1, 2],\n", + " [0, 3, 4, 1],\n", + " [4, 0, 2, 3]\n", + " ],\n", + " [ # This is the most difficult 9x9 puzzle we can run on Quantinuum hardware (3 empty cells)\n", + " [6, 0, 3, 8, 9, 4, 5, 1, 2],\n", + " [9, 0, 2, 7, 3, 5, 4, 8, 6],\n", + " [8, 4, 0, 6, 1, 2, 9, 7, 3],\n", + " [7, 0, 8, 2, 6, 1, 3, 5, 4],\n", + " [5, 2, 6, 4, 7, 3, 8, 9, 1],\n", + " [1, 3, 4, 5, 8, 9, 2, 6, 7],\n", + " [4, 6, 9, 1, 2, 8, 7, 0, 5],\n", + " [2, 8, 7, 3, 5, 6, 1, 4, 9],\n", + " [3, 5, 1, 9, 4, 7, 6, 2, 8]\n", + " ],\n", + " [ # This is the most difficult 9x9 puzzle we can simulate (21 empty cells)\n", + " [0, 7, 3, 8, 0, 0, 5, 1, 2],\n", + " [9, 0, 2, 7, 0, 5, 4, 8, 6],\n", + " [8, 4, 5, 0, 0, 0, 0, 0, 0],\n", + " [7, 0, 8, 2, 0, 1, 3, 5, 0],\n", + " [5, 2, 6, 4, 0, 3, 8, 9, 1],\n", + " [1, 3, 4, 5, 0, 0, 0, 0, 0],\n", + " [4, 6, 9, 1, 2, 8, 7, 3, 5],\n", + " [2, 8, 7, 3, 5, 6, 1, 4, 9],\n", + " [3, 5, 1, 9, 4, 7, 6, 0, 8]\n", + " ]\n", + "]\n", + "\n", + "for puzzle in puzzles:\n", + " sudoku = Sudoku(puzzle)\n", + " print(sudoku)\n", + " if (not hard) and sudoku.count_empty_squares() > 12:\n", + " print(\"Skipping hard puzzles\")\n", + " continue\n", + " sudoku.solve_quantum(SolvePuzzle.simulate_sparse)\n", + " print(sudoku)\n", + " print(\"Valid!\" if sudoku.is_valid() else \"Invalid!\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Resource Estimation\n", + "Seeing that our code solves puzzles correctly, let's get our work ready to run on Azure Quantum targets!\n", + "To do that, we need to see what instances would be feasible to run.\n", + "For that we will use the resource estimator to see how many resources our computation requires. \n", + "\n", + "The resources estimator is one of the local simulators provided by the QDK. \n", + "We can reuse our previous code to estimate resources for each puzzle automatically.\n", + "The resources consumption will depend on the puzzle instance, its size, and the number and the locations of empty squares.\n", + "To see if we can solve a specific puzzle instance on Quantinuum, we check that the number of qubits it requires fits within the number of qubits of the Quantinuum H1-1 system." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def runs_on_h11(res) -> bool:\n", + " return res['QubitCount'] <= 20\n", + "\n", + "\n", + "# We'll keep track of which puzzles we can run where!\n", + "h11_puzzles = []\n", + "\n", + "\n", + "for puzzle in puzzles:\n", + " sudoku = Sudoku(puzzle)\n", + " print(sudoku)\n", + " resources = sudoku.solve_quantum(SolvePuzzle.estimate_resources)\n", + " print(resources)\n", + " if runs_on_h11(resources):\n", + " h11_puzzles.append(puzzle)\n", + " print(\"Can run on Quantinuum H1-1!\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that our puzzles have vastly differing qubit requirements. \n", + "These are made up of the following:\n", + "* We need $2k$ qubits for our representation of $k$ empty cells\n", + "* We need $m$ auxiliary qubits in the oracle for $m$ edge constraints, and one more to convert the marking oracle into the phase one\n", + "* We could need some auxiliary qubits to decompose multi-controlled gates into sequences of smaller gates\n", + "\n", + "So, if we see a puzzle with $k$ missing numbers that requires exactly $2k$ qubits, such as the first puzzle from our list, we know that the oracle is never called, and the solution hence amounts to state preparation and measurement. \n", + "The larger puzzles will actually use the oracle to find the best states. \n", + "We can confirm these findings based on the search space sizes that we got during simulation.\n", + "\n", + "With that, let's run some puzzles on Quantinuum through Azure Quantum!\n", + "\n", + "\n", + "## Running on Quantinuum targets\n", + "To run jobs on Azure Quantum we have to connect to our Azure Quantum workspace and locate the Quantinuum targets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import qsharp.azure\n", + "import datetime" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "targets = qsharp.azure.connect(\n", + " resourceId=\"\",\n", + " location=\"\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "print(f\"This workspace's {len(targets)} targets:\")\n", + "for target in targets:\n", + " print(f\"- {target.id} (average queue time {datetime.timedelta(seconds=target.average_queue_time)})\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now define a function to execute our code on Azure Quantum.\n", + "For this we call we will use the `qsharp.azure.submit` function that accepts as input the Q# function, the number of shots, job name, and parameters that will then return a job which we can later get results from.\n", + "\n", + "We will also define a method, where we can get the job results and use those for our computation. \n", + "This is for when we submit to real hardware/emulators which can have hours of queuing time. \n", + "Here we also need a submission function to have our job ready." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import time\n", + "import json\n", + "from qsharp.azure import AzureJob\n", + "\n", + "def get_job(job_id) -> tuple:\n", + " current_job = qsharp.azure.status(jobId=job.id)\n", + " succeeded = current_job.status == 'Succeeded'\n", + " if succeeded:\n", + " print(f'Job completed')\n", + " elif current_job.status == 'Failed':\n", + " print(f'Job for this puzzle failed. Please review reason at {job.uri}')\n", + " else:\n", + " print(f'Job for given puzzle is still running. Please check back later!')\n", + " return (succeeded, current_job)\n", + "\n", + "def get_highest_probability_output(output: dict) -> list:\n", + " \"\"\"\n", + " Goes through outputs from an Azure Quantum job and returns the list with the highest probability\n", + " Returns a list of integers representing the ket notation of the output \n", + " \"\"\"\n", + " max_prob = -1\n", + " max_val = ''\n", + " for (val, prob) in output.items():\n", + " if (prob > max_prob):\n", + " max_prob = prob\n", + " max_val = val\n", + " ret = json.loads(max_val) # convert string rep of list into list type\n", + " return ret\n", + "\n", + "\n", + "def job_name(self: Sudoku) -> str:\n", + " return f\"{self.size()}x{self.size()} Solve Sudoku Puzzle w/ {self.count_empty_squares()} unknowns:\\n{str(self)}\"\n", + "\n", + "\n", + "def get_results_from_azure(job_id: str):\n", + " \"\"\"\n", + " Wrapper function that generates a function pretends execution\n", + " but instead get results from previous execution of a job with id `job_id`\n", + " \"\"\"\n", + " def get_results_from_azure_with_param(**kwargs) -> list:\n", + " \"\"\"\n", + " Function to hook into `solve_quantum` method of a `Sudoku` \n", + " where it gets the job results and return the highest probability result\n", + " \"\"\"\n", + " output = qsharp.azure.output(jobId=job_id)\n", + " return get_highest_probability_output(output)\n", + " return get_results_from_azure_with_param\n", + "\n", + "\n", + "def job_msg(job) -> str:\n", + " return f\"Submitted job with id {job.id}. Track at {job.uri}\"\n", + "\n", + "\n", + "def submit_job_to_azure(self: Sudoku) -> AzureJob:\n", + " \"\"\"\n", + " Submits a Sudoku puzzle to be run on Azure asynchronously.\n", + " Return the resulting Azure Quantum job\n", + " \"\"\"\n", + " (empty_square_edges_1, empty_square_edges_2,\n", + " starting_number_constraints_1, starting_number_constraints_2,\n", + " empty_squares) = self.prepare_constraints()\n", + " nVertices = len(empty_squares)\n", + " job = qsharp.azure.submit(\n", + " SolvePuzzle,\n", + " shots=250,\n", + " jobName=self.job_name(),\n", + " nVertices=nVertices,\n", + " bitsPerColor=self.bit_length(),\n", + " emptySquareEdges1=empty_square_edges_1,\n", + " emptySquareEdges2=empty_square_edges_2,\n", + " startingNumberConstraints1=starting_number_constraints_1,\n", + " startingNumberConstraints2=starting_number_constraints_2\n", + " )\n", + " print(job_msg(job))\n", + " return job\n", + "\n", + "\n", + "Sudoku.job_name = job_name\n", + "Sudoku.submit_job_to_azure = submit_job_to_azure" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's now run our code on Quantinuum targets. To check that it is compatible with Quantinuum machines, let us first run it on the API validator." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.azure.target('quantinuum.hqs-lt-s1-apival')\n", + "\n", + "api_validation_jobs = list()\n", + "for puzzle in h11_puzzles: # We'll be using the puzzles we found viable earlier!\n", + " sudoku = Sudoku(puzzle)\n", + " if not sudoku.count_empty_squares() >= 4:\n", + " continue\n", + " print(sudoku)\n", + " job = sudoku.submit_job_to_azure()\n", + " api_validation_jobs.append(job)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "all_completed = True\n", + "for job in api_validation_jobs:\n", + " (completed, _) = get_job(job)\n", + " if not completed:\n", + " all_completed = False\n", + "if all_completed:\n", + " # Previous call would throw exception if invalid\n", + " print(\"API validation succeeded!\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "After seeing that everything works smoothly, we will run on the H1-1 emulator.\n", + " \n", + "The H1-1 emulator can accurately model the noise of the H1-1 quantum machine, allowing us get good results while staying within our free credits. \n", + "To save on credits we only run the interesting examples of 5 unknowns. \n", + "One of the examples has a search space size of 1, essentially making it state preparation and measurement.\n", + " The other however, has a search space size of 4, causing us to access the Oracle.\n", + "\n", + "**Please note that this sample makes use of paid services on Azure Quantum. \n", + "The cost of running this sample with the provided parameters on Quantinuum in a free trial subscription is approximately 360EHQC (equivalent to 1125$). \n", + "This quantity is only an approximate estimate and should not be used as a binding reference. \n", + "The cost of the service might vary depending on your region, demand and other factors.**\n", + "\n", + "Please note that the jobs might take a while. \n", + "If you are running in the browser on Azure Quantum, please keep the tab open.\n", + "If you are running on VSCode, feel free to come back later and load the pickle file.\n", + "Feel free to come back later (we saw the average queue time when connecting to Azure Quantum)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pickle # Used to persist current jobs\n", + "pkl_filename = \"sudoku-quantinuum_jobs.pkl\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.azure.target('quantinuum.hqs-lt-s1-sim')\n", + "hw_jobs = []\n", + "for puzzle in h11_puzzles:\n", + " sudoku = Sudoku(puzzle)\n", + " if not sudoku.count_empty_squares() >= 4:\n", + " continue\n", + " print(sudoku)\n", + " job = sudoku.submit_job_to_azure()\n", + " hw_jobs.append((puzzle, job))\n", + " print(job_msg(job))\n", + "\n", + "# Save current jobs to file, in case you want to come back later\n", + "pklfile = open(pkl_filename, 'wb')\n", + "pickle.dump(hw_jobs, pklfile)\n", + "pklfile.close()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pklfile = open(pkl_filename, 'rb')\n", + "hw_jobs = pickle.load(pklfile)\n", + "pklfile.close()\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for (puzzle, job) in hw_jobs:\n", + " sudoku = Sudoku(puzzle)\n", + " print(sudoku)\n", + "\n", + " sudoku.solve_quantum(get_results_from_azure(job.id))\n", + " print(sudoku)\n", + " print(\"Valid!\" if sudoku.is_valid() else \"Invalid\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernel_info": { + "name": "python3" + }, + "kernelspec": { + "display_name": "Python 3.7.13 ('qsharp-env-37')", + "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.13" + }, + "nteract": { + "version": "nteract-front-end@1.0.0" + }, + "orig_nbformat": 4, + "vscode": { + "interpreter": { + "hash": "2ae822f0f83fed12107faf85edb2b742fdce767c971cd51f224ccfc7e38aba9d" + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/samples/azure-quantum/grover-sudoku/README.md b/samples/azure-quantum/grover-sudoku/README.md new file mode 100644 index 000000000000..5e8d6e029469 --- /dev/null +++ b/samples/azure-quantum/grover-sudoku/README.md @@ -0,0 +1,41 @@ +--- +page_type: sample +author: adrianleh +description: Solves Sudoku Puzzle using Grover's Search, using the Azure Quantum service +ms.author: t-alehmann@microsoft.com +ms.date: 08/16/2021 +languages: +- qsharp +- python +products: +- qdk +- azure-quantum +--- + +# Solving Sudoku with Grover's search + +In this sample we will be solving the classic puzzle Sudoku using Grover's search. + +We will be basing our algorithm off the [official sample on GitHub](https://github.com/microsoft/Quantum/tree/main/samples/algorithms/sudoku-grover). +In the following we will adapt the sample to run on actual hardware. +Given that quantum hardware is in its infancy right now, we need to minimize qubit count, circuit depth (think number of gates), and limit hybrid interactions. + +Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Q# notebooks integrating with python. +This will further enable us to have some convenience in the data structures we build, such as classical validation of Sudoku puzzles. + +This sample is a Q# jupyter notebook targeted at IonQ and Quantinuum machines. + +## Q# with Jupyter Notebook + +Make sure that you have followed the [Q# + Jupyter Notebook quickstart](https://docs.microsoft.com/azure/quantum/install-jupyter-qdk) for the Quantum Development Kit, and then start a new Jupyter Notebook session from the folder containing this sample: + +```shell +cd grover-sudoku +jupyter notebook +``` + +Once Jupyter starts, open the `Grovers-sudoku-quantinuum.ipynb` notebook and follow the instructions there. + +## Manifest + +- [Grovers-sudoku-quantinuum.ipynb](https://github.com/microsoft/quantum/blob/main/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb): IQ# notebook for this sample targetting Quantinuum. From c086460c042584b25ff8f7198f0286c235f3c43f Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Wed, 7 Sep 2022 13:04:51 -0500 Subject: [PATCH 2/7] Add sample to binder index --- binder-index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/binder-index.md b/binder-index.md index 06b87df6042c..714d48d789ac 100644 --- a/binder-index.md +++ b/binder-index.md @@ -251,6 +251,14 @@ These are noted in the README.md files for each sample, along with complete inst Q# standalone + + + Solving Sudoku with Grover's Search + + + Q# standalone + + Characterization: Bayesian Phase Estimation From f2a53b717526dd5fb02de95571a4ecabfd5aac20 Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Thu, 8 Sep 2022 16:38:41 -0500 Subject: [PATCH 3/7] Apply PR feedback --- .../Grovers-sudoku-quantinuum.ipynb | 435 ++++++++++++------ 1 file changed, 305 insertions(+), 130 deletions(-) diff --git a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb b/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb index 25e0fd5546ad..9c3b7cf3061f 100644 --- a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb +++ b/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb @@ -6,136 +6,186 @@ "source": [ "# Solving Sudoku Puzzles Using Grover's Search\n", "\n", - "In this notebook we will be solving the classic puzzle Sudoku using Grover's search.\n", + "In this sample, we will be solving Sudoku puzzles using Grover's search.\n", "\n", - "We will be basing our algorithm off [this sample](https://github.com/microsoft/Quantum/tree/main/samples/algorithms/sudoku-grover).\n", - "Here we adapt the sample to run on actual hardware.\n", - "Given that quantum hardware is in the NISQ period right now, we need to minimize qubit count and circuit depth (number of gates) required by the algorithm.\n", "\n", - "Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Q# notebooks integrating with python.\n", - "This will further enable us to have some convenience in the data structures we build, such as classical validation of Sudoku puzzles.\n", + "Given that we will run our algorithm on current quantum hardware, we need to minimize qubit count and circuit depth (number of gates) required by the algorithm.\n", "\n", - "\n", - "**IMPORTANT IF RUNNING FROM VSCODE**: Please set the setting `notebook.output.textLineLimit` to at least `2000` to see correct printings of Sudoku puzzles" + "Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Python notebooks integrating with Q#.\n", + "This will further enable us to have some convenience in the data structures we build, such as classical validation of Sudoku puzzles." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "metadata": { "vscode": { "languageId": "shellscript" } }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Preparing Q# environment...\n", + "." + ] + } + ], "source": [ "import qsharp # Enable Q#-Python integration" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's connect to Azure Quantum and set our target!" + ] + }, + { + "cell_type": "code", + "execution_count": 63, + "metadata": {}, + "outputs": [], + "source": [ + "import qsharp.azure\n", + "import datetime" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "targets = qsharp.azure.connect(\n", + " resourceId=\"\",\n", + " location=\"\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"This workspace's {len(targets)} targets:\")\n", + "for target in targets:\n", + " print(f\"- {target.id} (average queue time {datetime.timedelta(seconds=target.average_queue_time)})\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "qsharp.azure.target('quantinuum.hqs-lt-s1-sim')" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Defining a Classical Data Structure for Sudoku Puzzles\n", "Let us first write the code to define, validate, and print Sudoku puzzles. This code will be entirely classical and written in Python, serving as an example of integration of classical and quantum code. \n", - "Later we will expand the functionality of the Sudoku class we are defining with quantum computation." + "Later, we will expand the functionality of the `Sudoku` class to include quantum computation." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 19, "metadata": {}, "outputs": [], "source": [ "import math\n", "from copy import deepcopy\n", - "\n", + "from typing import List, Dict, Tuple\n", "\n", "class Sudoku:\n", - " data: list = [] # list[list[int]]\n", + " data: List[List[int]] \n", "\n", - " def size(self) -> int:\n", + " def get_size(self) -> int:\n", " \"\"\"The width/height of the puzzle\"\"\"\n", " return len(self.data)\n", "\n", - " def empty_at(self, i: int, j: int) -> bool:\n", - " \"\"\"Checks if the cell at a given location is empty\"\"\"\n", - " return self.at(i, j) == 0\n", + " size = property(get_size)\n", "\n", - " def at(self, i: int, j: int) -> int:\n", + " def __getitem__(self, pos : Tuple[int, int]) -> int:\n", " \"\"\"Return the value of a cell at a given location (0 if the cell is empty)\"\"\"\n", + " (i, j) = pos\n", " return self.data[i][j]\n", "\n", - " def set(self, i: int, j: int, val: int) -> None:\n", + " def __setitem__(self, pos : Tuple[int, int], val: int) -> None:\n", " \"\"\"Sets the value of a cell at a given location (val=0 will empty the cell)\"\"\"\n", + " (i, j) = pos\n", " self.data[i][j] = val\n", "\n", - " def __init__(self, data: list) -> None:\n", + " def __init__(self, data: List[List[int]]) -> None:\n", " \"\"\"Initializes the puzzle. \n", " data has to be a 2D array of size 4x4 or 9x9 with row-column indexing. \n", " Cells marked 0 will be considered empty\"\"\"\n", " size = len(data)\n", - " assert (size in {4, 9})\n", + " if size not in {4, 9}:\n", + " raise ValueError(\"Must be 4x4 or 9x9 array\")\n", " # We currently only support Sudoku puzzles up to size 9\n", " # Larger Sudoku puzzles would require an unreasonable amount of RAM for quantum simulation\n", " for row in data:\n", - " assert(len(row) == size)\n", + " if len(row) != size:\n", + " raise ValueError(\"Must be 4x4 or 9x9 array\")\n", " self.data = deepcopy(data)\n", "\n", - " def bit_length(self) -> int:\n", + " def get_bit_length(self) -> int:\n", " \"\"\"The number of bits required to represent a number in the puzzle\"\"\"\n", - " if self.size() == 4:\n", + " if self.size == 4:\n", " return 2\n", " return 4\n", "\n", + " bit_length = property(get_bit_length)\n", + "\n", " def __str__(self) -> str:\n", " \"\"\"Creates a human-readable representation of the puzzle\"\"\"\n", " str = ''\n", " for row in self.data:\n", - " str += ('-' * (4 * self.size() + 1)) + '\\n'\n", + " str += ('-' * (4 * self.size + 1)) + '\\n'\n", " for el in row:\n", " if el == 0:\n", " str += \"| \"\n", " else:\n", " str += f\"| {el} \"\n", " str += '|\\n'\n", - " str += ('-' * (4 * self.size() + 1)) + '\\n'\n", + " str += ('-' * (4 * self.size + 1)) + '\\n'\n", " return str\n", "\n", - " def __eq__(self, __o: object) -> bool:\n", - " if not isinstance(__o, Sudoku):\n", - " return False\n", - " # Compare flattened data\n", - " self_data_flat = [el for row in self.data for el in row]\n", - " o_data_flat = [el for row in __o.data for el in row]\n", - " return self_data_flat == o_data_flat\n", - "\n", " def is_valid(self) -> bool:\n", " \"\"\"Checks whether the puzzle is complete and meets all Sudoku constraints\"\"\"\n", " # Check for empty cells\n", - " for i in range(self.size()):\n", - " for j in range(self.size()):\n", - " if self.empty_at(i, j):\n", + " for i in range(self.size):\n", + " for j in range(self.size):\n", + " if not self[i, j]:\n", " return False\n", "\n", " # Check rows\n", - " for row in range(self.size()):\n", + " for row in range(self.size):\n", " values_in_row = set()\n", - " for i in range(self.size()):\n", - " curr = self.at(row, i)\n", + " for i in range(self.size):\n", + " curr = self[row, i]\n", " if curr in values_in_row:\n", " return False\n", " values_in_row.add(curr)\n", " # Check cols\n", - " for col in range(self.size()):\n", + " for col in range(self.size):\n", " values_in_col = set()\n", - " for j in range(self.size()):\n", - " curr = self.at(j, col)\n", + " for j in range(self.size):\n", + " curr = self[j, col]\n", " if curr in values_in_col:\n", " return False\n", " values_in_col.add(curr)\n", " # Check subgrids\n", - " sub_size = math.floor(math.sqrt(self.size()))\n", + " sub_size = math.floor(math.sqrt(self.size))\n", "\n", " for sub_grid_i in range(sub_size):\n", " for sub_grid_j in range(sub_size):\n", @@ -144,7 +194,7 @@ " values_in_sub_grid = set()\n", " for i in range(sub_start_i, sub_start_i + sub_size):\n", " for j in range(sub_start_j, sub_start_j + sub_size):\n", - " curr = self.at(i, j)\n", + " curr = self[i, j]\n", " if curr in values_in_sub_grid:\n", " return False\n", " values_in_sub_grid.add(curr)\n", @@ -154,9 +204,9 @@ " def count_empty_squares(self) -> int:\n", " \"\"\"Returns the number of empty squares in the puzzle\"\"\"\n", " empty = 0\n", - " for i in range(self.size()):\n", - " for j in range(self.size()):\n", - " if self.empty_at(i, j):\n", + " for i in range(self.size):\n", + " for j in range(self.size):\n", + " if not self[i, j]:\n", " empty += 1\n", " return empty\n" ] @@ -170,9 +220,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 20, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-----------------\n", + "| 1 | 3 | 4 | 2 |\n", + "-----------------\n", + "| 2 | 4 | 3 | 1 |\n", + "-----------------\n", + "| 3 | 2 | 1 | 4 |\n", + "-----------------\n", + "| 4 | 1 | 2 | 3 |\n", + "-----------------\n", + "\n", + "Valid!\n" + ] + } + ], "source": [ "sudoku = Sudoku([\n", " [1, 3, 4, 2],\n", @@ -190,26 +258,53 @@ "source": [ "## Sudoku Puzzles as Vertex Coloring Problems\n", "\n", - "We will use Grover's search to solve Sudoku puzzles by viewing them as a vertex coloring problem: each empty cell of the puzzle is a vertex that needs to have a color assigned based on the constraints imposed by the other cells.\n", + "We will use Grover's search to solve Sudoku puzzles by viewing them as a vertex coloring problem: each empty cell of the puzzle is a vertex that needs to have a color assigned based on the constraints imposed by the other cells (we will describe this in more detail and provide an example in the section on Classical precomputation).\n", "\n", "Python pre-processing code converts the input puzzle into a set of constraints and passes it to the quantum part of the program.\n", "Q# code solves the problem defined by the constraints and returns a bitstring that represents the numbers assigned to the empty cells. \n", "Finally, Python post-processing code parses the solution and validates its correctness.\n", "\n", "We represent the integer that will be placed in each empty cell as a bitstring of either two (if $n=2$) or four (if $n=3$) bits, where bitstrings are interpreted as binary integers. \n", - "We then concatenate each cell’s bitstrings, remembering the indices of each empty cell. This means that for a $4 \\times 4$ puzzle with $k$ empty cells, we need $2k$ bits for the representation\n", - "of the problem.\n", + "We then concatenate each cell’s bitstrings, remembering the indices of each empty cell. This means that for a $4 \\times 4$ puzzle with $k$ empty cells, we need $2k$ bits for the representation of the problem.\n", + "\n", + "For example for the board with 3 empty squares:\n", + "```\n", + "-----------------\n", + "| 1 | | 4 | 2 |\n", + "-----------------\n", + "| 2 | 4 | 3 | |\n", + "-----------------\n", + "| 3 | 2 | 1 | 4 |\n", + "-----------------\n", + "| 4 | 1 | | 3 |\n", + "-----------------\n", + "```\n", + "we'd use 6 qubits for our representation, where for example $\\ket{100001}$ would result in the solutions being $3,1,2$ resulting in the board\n", + "```\n", + "-----------------\n", + "| 1 | 3 | 4 | 2 |\n", + "-----------------\n", + "| 2 | 4 | 3 | 1 |\n", + "-----------------\n", + "| 3 | 2 | 1 | 4 |\n", + "-----------------\n", + "| 4 | 1 | 2 | 3 |\n", + "-----------------\n", + "```\n", + "\n", "\n", "The algorithm in the quantum component is Grover's search algorithm, an algorithm that prepares a search space and then uses an oracle to perform \"Grover iterations\".\n", "A \"Grover iteration\" involves applying an oracle and then diffusion the state.\n", + "The amazing thing is that we find the correct results in $\\mathcal{O}(\\sqrt{k})$.\n", + "If you'd like to learn more about Grover's search, please see [this article](https://docs.microsoft.com/en-us/azure/quantum/concepts-grovers).\n", "\n", - "The amazing thing is that we find the correct results in $\\mathcal{O}(\\sqrt{k})$\n", "\n", "## Classical Precomputation\n", "\n", - "Let's get building: First we classically create the constraints and then translate them into quantum states.\n", + "Let's get building! First, we classically create the constraints and then translate them into quantum states.\n", "\n", "We classically convert the puzzle into two types of constraints:\n", + "\n", "- Starting number constraints specify the numbers that cannot be assigned to a cell based on the current nonempty cells.\n", "- Edge constraints specify that any two empty cells that are in the same row, column, or subgrid cannot be assigned the same number.\n", "\n", @@ -242,26 +337,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "metadata": {}, "outputs": [], "source": [ - "def get_constraints(self) -> tuple: # tuple of: [list[(int, int)], # Empty square edges\n", - " # list[(int, int)], # Starting constraints\n", - " # list[(int, int)]]: # Empty squares\n", - " sub_size = math.floor(math.sqrt(self.size()))\n", + "def get_constraints(self) -> Tuple[List[Tuple[int, int]], # Empty square edges\n", + " List[Tuple[int, int]], # Starting constraints\n", + " List[Tuple[int, int]] # Empty squares\n", + " ]:\n", + " sub_size = math.floor(math.sqrt(self.size))\n", " empty_indices = dict()\n", " empty_squares = list()\n", " empty_square_edges = list()\n", " starting_number_constraints = set() # We want to avoid duplicates\n", " empty_index = 0\n", "\n", - " for i in range(self.size()):\n", - " for j in range(self.size()):\n", + " for i in range(self.size):\n", + " for j in range(self.size):\n", "\n", " # Only consider empty_squares for constraint calculation\n", " \n", - " if not self.empty_at(i, j):\n", + " if self[i, j]:\n", " continue\n", " empty_indices[i, j] = empty_index\n", " empty_squares.append((i, j))\n", @@ -285,7 +381,7 @@ "\n", " # Check for column constraints\n", "\n", - " for row_index in range(self.size()):\n", + " for row_index in range(self.size):\n", " if not self.empty_at(row_index, j):\n", " starting_number_constraints.add(\n", " (empty_index, self.at(row_index, j) - 1))\n", @@ -295,7 +391,7 @@ "\n", " # Check for row constraints\n", "\n", - " for col_index in range(self.size()):\n", + " for col_index in range(self.size):\n", " if not self.empty_at(i, col_index):\n", " starting_number_constraints.add(\n", " (empty_index, self.at(i, col_index) - 1))\n", @@ -306,7 +402,7 @@ " # Exclude illegal values on a 9x9 puzzle\n", " # Not needed for 4x4 since 4x4 has bit width 2, which only represents legal values\n", "\n", - " if self.size() == 9:\n", + " if self.size == 9:\n", " for invalid in range(9, 16):\n", " starting_number_constraints.add((empty_index, invalid))\n", "\n", @@ -333,8 +429,8 @@ "In the example above, the search space size shrinks from $4^4=256$ to $4$.\n", "The reduction allows us to use fewer search iterations, resulting in fewer oracle calls, less noise-prone computation, increased performance, and therefore more difficult puzzles we can solve!\n", "\n", - "Specifically, the optimal number of iterations is given by the formula $i(s) = \\lfloor \\frac{\\pi}{4\\arcsin{\\sqrt{s^{-1}}}} - \\frac{1}{2} \\rceil$, where $s$ is the search space size. \n", - "In the example the optimization reduces the number of iterations from $i(256) = 12$ to $i(4) = 1$.\n", + "Specifically, the optimal number of iterations is given by the formula $n_\\textrm{iter}(s) = \\lfloor \\frac{\\pi}{4\\arcsin{\\sqrt{s^{-1}}}} - \\frac{1}{2} \\rceil$, where $s$ is the search space size. \n", + "In the example, the optimization reduces the number of iterations from $n_\\textrm{iter}(256) = 12$ to $n_\\textrm{iter}(4) = 1$.\n", "Further, we do not need to encode starting constraints the oracle, significantly lowering the number of qubits required.\n", "\n", "But since this is a large project, we'll need to break it down into components:\n", @@ -345,12 +441,12 @@ "- Measure and extract the information we need\n", "\n", "\n", - "Q# gives us a lot of library features to help us in the process of build our algorithm." + "Q# gives us a lot of library features to help us in the process of building our algorithm." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 22, "metadata": { "microsoft": { "language": "qsharp" @@ -371,7 +467,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "metadata": { "microsoft": { "language": "qsharp" @@ -412,8 +508,64 @@ " set amplitudes w/= cell <- (amplitudes[cell] w/ value <- 0.0);\n", " }\n", " return amplitudes;\n", - "}\n", - "\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us take a look at how this function works: From the starting number constraints we build an array that for each cell represents the amplitudes of each value.\n", + "So given a diagram, where we only have two empty square, one of which can have the values $2$ and $4$ and the other can be $1,2,3$." + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "import matplotlib.pyplot as plt\n", + "allowed_amplitudes = AllowedAmplitudes.simulate(nVertices=2, bitsPerColor=2, startingNumberConstraints=[(0,0), (0,2), (1,3)])\n", + "for i in range(len(allowed_amplitudes)):\n", + " plt.bar(range(1,5), allowed_amplitudes[i])\n", + " plt.title(f\"Allowed amplitudes for empty cell with index {i}\")\n", + " plt.xticks(range(1,5))\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", "/// # Summary\n", "/// Prepare an equal superposition of all basis states that satisfy the constraints\n", "/// imposed by the digits already placed in the grid.\n", @@ -455,7 +607,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 51, "metadata": {}, "outputs": [], "source": [ @@ -484,8 +636,44 @@ " set colorOptions w/= cell <- colorOptions[cell] - 1;\n", " }\n", " return Fold(TimesI, 1, colorOptions);\n", - "}\n", - "\n", + "}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us try calculating the search space size for the example above, where we had two empty cells." + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Search space size with two cells, one where three values are allowed and another where two values are allowed: \\n6'" + ] + }, + "execution_count": 62, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f\"Search space size with two cells, one where three values are allowed and another where two values are allowed: \\\n", + "{SearchSpaceSize.simulate(nVertices=2, bitsPerColor=2, startingNumberConstraints=[(0,0), (0,2), (1,3)])}\"" + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [], + "source": [ + "%%qsharp\n", "/// # Summary\n", "/// Estimate the number of iterations required for solution.\n", "///\n", @@ -504,6 +692,33 @@ "}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can now validate our ealier findings of search space size by running the NInterations. As a reminder, we predicted that for a puzzle with search space size $4$ we need $1$ iterations." + ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'Number of iterations for search space size 4: 1'" + ] + }, + "execution_count": 57, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "f\"Number of iterations for search space size 4: {NIterations.simulate(searchSpaceSize=4)}\"" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -527,7 +742,7 @@ "%%qsharp\n", "\n", "/// # Summary\n", - "/// N-bit color equality oracle (no extra qubits.)\n", + "/// N-bit color equality oracle\n", "///\n", "/// # Input\n", "/// ## color0\n", @@ -846,6 +1061,8 @@ "def prepare_constraints(self) -> tuple:\n", " \"\"\"\n", " Reshapes constraints to adapt to the limitations of running on Azure Quantum by splitting tuple lists.\n", + " Azure Quantum does not allow for parameters that are composite types of composite types, \n", + " i.e., lists of tuples are not allowed parameters.\n", " \"\"\"\n", " (empty_square_edges, starting_number_constraints,\n", " empty_squares) = self.get_constraints()\n", @@ -885,7 +1102,7 @@ " starting_number_constraints_1, starting_number_constraints_2,\n", " empty_squares) = self.prepare_constraints()\n", " measurements = solve_fn(nVertices=len(empty_squares),\n", - " bitsPerColor=self.bit_length(),\n", + " bitsPerColor=self.bit_length,\n", " emptySquareEdges1=empty_square_edges_1,\n", " emptySquareEdges2=empty_square_edges_2,\n", " startingNumberConstraints1=starting_number_constraints_1,\n", @@ -896,7 +1113,7 @@ " print(\"No solution computed. Did you use resource estimation?\")\n", " return measurements\n", " print(\"Solved puzzle!\")\n", - " solution = parse_measured_integers(measurements, self.bit_length())\n", + " solution = parse_measured_integers(measurements, self.bit_length)\n", " print(f\"Raw solution: {solution}\")\n", " for empty_idx in range(len(empty_squares)):\n", " (i, j) = empty_squares[empty_idx]\n", @@ -914,7 +1131,7 @@ "source": [ "## Full State Simulator vs Sparse Simulator\n", "\n", - "Q# provides two main options for simulating the programs locally: regular (\"full state\") simulation and sparse simulation.\n", + "The QDK provides two main options for simulating the programs locally: regular (\"full state\") simulation and sparse simulation.\n", "\n", "Since the quantum states used in Grover's search inherently have many zero amplitudes and a lot of entanglement, sparse simulation should perform a lot better. \n", "Let's try both options and see whether this is the case!\n", @@ -922,7 +1139,7 @@ "To simulate a Q# operation from Python, you can call `QSharpFunctionName.simulate(params)` or `QSharpFunctionName.simulate_sparse(params)}`, where `params` are named parameters for your Q# operation. \n", "We handle parameter passing in the `solve_quantum` function above, so all that's left to do is to specify which of the simulation functions we want to use as the `solve_fn` parameter.\n", "\n", - "Let's use regular and sparse simulation on 4x4 puzzles with few empty spaces to compare the performance of both simulators on this type of problems." + "Let's use full-state and sparse simulation on 4x4 puzzles with few empty spaces to compare the performance of both simulators on this type of problems." ] }, { @@ -1060,7 +1277,7 @@ "for puzzle in puzzles:\n", " sudoku = Sudoku(puzzle)\n", " print(sudoku)\n", - " if (not hard) and sudoku.count_empty_squares() > 12:\n", + " if not hard and sudoku.count_empty_squares() > 12:\n", " print(\"Skipping hard puzzles\")\n", " continue\n", " sudoku.solve_quantum(SolvePuzzle.simulate_sparse)\n", @@ -1125,47 +1342,6 @@ "\n", "\n", "## Running on Quantinuum targets\n", - "To run jobs on Azure Quantum we have to connect to our Azure Quantum workspace and locate the Quantinuum targets." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import qsharp.azure\n", - "import datetime" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "targets = qsharp.azure.connect(\n", - " resourceId=\"\",\n", - " location=\"\"\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "\n", - "print(f\"This workspace's {len(targets)} targets:\")\n", - "for target in targets:\n", - " print(f\"- {target.id} (average queue time {datetime.timedelta(seconds=target.average_queue_time)})\")\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ "We will now define a function to execute our code on Azure Quantum.\n", "For this we call we will use the `qsharp.azure.submit` function that accepts as input the Q# function, the number of shots, job name, and parameters that will then return a job which we can later get results from.\n", "\n", @@ -1211,7 +1387,7 @@ "\n", "\n", "def job_name(self: Sudoku) -> str:\n", - " return f\"{self.size()}x{self.size()} Solve Sudoku Puzzle w/ {self.count_empty_squares()} unknowns:\\n{str(self)}\"\n", + " return f\"{self.size}x{self.size} Solve Sudoku Puzzle w/ {self.count_empty_squares()} unknowns:\\n{str(self)}\"\n", "\n", "\n", "def get_results_from_azure(job_id: str):\n", @@ -1241,13 +1417,12 @@ " (empty_square_edges_1, empty_square_edges_2,\n", " starting_number_constraints_1, starting_number_constraints_2,\n", " empty_squares) = self.prepare_constraints()\n", - " nVertices = len(empty_squares)\n", " job = qsharp.azure.submit(\n", " SolvePuzzle,\n", " shots=250,\n", " jobName=self.job_name(),\n", - " nVertices=nVertices,\n", - " bitsPerColor=self.bit_length(),\n", + " nVertices=len(empty_squares),\n", + " bitsPerColor=self.bit_length,\n", " emptySquareEdges1=empty_square_edges_1,\n", " emptySquareEdges2=empty_square_edges_2,\n", " startingNumberConstraints1=starting_number_constraints_1,\n", @@ -1314,14 +1489,14 @@ " The other however, has a search space size of 4, causing us to access the Oracle.\n", "\n", "**Please note that this sample makes use of paid services on Azure Quantum. \n", - "The cost of running this sample with the provided parameters on Quantinuum in a free trial subscription is approximately 360EHQC (equivalent to 1125$). \n", + "The cost of running this sample with the provided parameters on Quantinuum in a free trial subscription is approximately 360EHQC.\n", "This quantity is only an approximate estimate and should not be used as a binding reference. \n", "The cost of the service might vary depending on your region, demand and other factors.**\n", "\n", "Please note that the jobs might take a while. \n", "If you are running in the browser on Azure Quantum, please keep the tab open.\n", "If you are running on VSCode, feel free to come back later and load the pickle file.\n", - "Feel free to come back later (we saw the average queue time when connecting to Azure Quantum)." + "Feel free to come back later (you can use the average job time as a point of reference that we saw when connecting to Azure Quantum)." ] }, { From cf9219940d3c2a9f1d6faecece7e388646f20e6f Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Thu, 8 Sep 2022 17:06:13 -0500 Subject: [PATCH 4/7] Apply PR feedback --- ...inuum.csproj => GroversSudokuQuantinuum.csproj} | 2 +- ...ntinuum.ipynb => GroversSudokuQuantinuum.ipynb} | 7 ++----- samples/azure-quantum/grover-sudoku/README.md | 14 +++++--------- 3 files changed, 8 insertions(+), 15 deletions(-) rename samples/azure-quantum/grover-sudoku/{Grovers-sudoku-quantinuum.csproj => GroversSudokuQuantinuum.csproj} (73%) rename samples/azure-quantum/grover-sudoku/{Grovers-sudoku-quantinuum.ipynb => GroversSudokuQuantinuum.ipynb} (99%) diff --git a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.csproj similarity index 73% rename from samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj rename to samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.csproj index 4467d0e4e1d4..cc1cf5882c6d 100644 --- a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.csproj +++ b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.csproj @@ -3,7 +3,7 @@ Exe net6.0 - Any + quantinuum.hqs-lt-s1-sim diff --git a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb similarity index 99% rename from samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb rename to samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb index 9c3b7cf3061f..df57f081ec86 100644 --- a/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb +++ b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb @@ -8,7 +8,6 @@ "\n", "In this sample, we will be solving Sudoku puzzles using Grover's search.\n", "\n", - "\n", "Given that we will run our algorithm on current quantum hardware, we need to minimize qubit count and circuit depth (number of gates) required by the algorithm.\n", "\n", "Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Python notebooks integrating with Q#.\n", @@ -441,7 +440,7 @@ "- Measure and extract the information we need\n", "\n", "\n", - "Q# gives us a lot of library features to help us in the process of building our algorithm." + "Q# gives us a lot of library features to help us in the process of building our algorithm. You can learn more about the libraries in the [documentation](https://docs.microsoft.com/en-us/qsharp/api/qsharp/) or the [Quantum Katas](https://docs.microsoft.com/en-us/azure/quantum/tutorial-qdk-intro-to-katas)." ] }, { @@ -1060,9 +1059,7 @@ "\n", "def prepare_constraints(self) -> tuple:\n", " \"\"\"\n", - " Reshapes constraints to adapt to the limitations of running on Azure Quantum by splitting tuple lists.\n", - " Azure Quantum does not allow for parameters that are composite types of composite types, \n", - " i.e., lists of tuples are not allowed parameters.\n", + " The Azure Quantum service only allows lists of basic types so we will split up our list of tuples into tuples of lists\n", " \"\"\"\n", " (empty_square_edges, starting_number_constraints,\n", " empty_squares) = self.get_constraints()\n", diff --git a/samples/azure-quantum/grover-sudoku/README.md b/samples/azure-quantum/grover-sudoku/README.md index 5e8d6e029469..7cd752cd6914 100644 --- a/samples/azure-quantum/grover-sudoku/README.md +++ b/samples/azure-quantum/grover-sudoku/README.md @@ -1,7 +1,7 @@ --- page_type: sample author: adrianleh -description: Solves Sudoku Puzzle using Grover's Search, using the Azure Quantum service +description: Solving Sudoku with Grover's search, using the Azure Quantum service ms.author: t-alehmann@microsoft.com ms.date: 08/16/2021 languages: @@ -14,17 +14,13 @@ products: # Solving Sudoku with Grover's search -In this sample we will be solving the classic puzzle Sudoku using Grover's search. +In this sample, we will be solving Sudoku puzzles using Grover's search. -We will be basing our algorithm off the [official sample on GitHub](https://github.com/microsoft/Quantum/tree/main/samples/algorithms/sudoku-grover). -In the following we will adapt the sample to run on actual hardware. -Given that quantum hardware is in its infancy right now, we need to minimize qubit count, circuit depth (think number of gates), and limit hybrid interactions. +Given that we will run our algorithm on current quantum hardware, we need to minimize qubit count and circuit depth (number of gates) required by the algorithm. -Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Q# notebooks integrating with python. +Since Grover's search is fundamentally a quantum algorithm requiring classical preprocessing, we will use the feature of Python notebooks integrating with Q#. This will further enable us to have some convenience in the data structures we build, such as classical validation of Sudoku puzzles. -This sample is a Q# jupyter notebook targeted at IonQ and Quantinuum machines. - ## Q# with Jupyter Notebook Make sure that you have followed the [Q# + Jupyter Notebook quickstart](https://docs.microsoft.com/azure/quantum/install-jupyter-qdk) for the Quantum Development Kit, and then start a new Jupyter Notebook session from the folder containing this sample: @@ -38,4 +34,4 @@ Once Jupyter starts, open the `Grovers-sudoku-quantinuum.ipynb` notebook and fol ## Manifest -- [Grovers-sudoku-quantinuum.ipynb](https://github.com/microsoft/quantum/blob/main/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb): IQ# notebook for this sample targetting Quantinuum. +- [GroversSudokuQuantinuum.ipynb](https://github.com/microsoft/quantum/blob/main/samples/azure-quantum/grover-sudoku/Grovers-sudoku-quantinuum.ipynb): IQ# notebook for this sample targetting Quantinuum. From 03dcc7e3bf20dd4f9f5aee277ed1d4a55725d92e Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Fri, 9 Sep 2022 15:39:49 -0500 Subject: [PATCH 5/7] Apply PR feedback --- .../GroversSudokuQuantinuum.ipynb | 244 ++++++++++++------ 1 file changed, 164 insertions(+), 80 deletions(-) diff --git a/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb index df57f081ec86..3dec77d3abbf 100644 --- a/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb +++ b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb @@ -16,22 +16,13 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 26, "metadata": { "vscode": { "languageId": "shellscript" } }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Preparing Q# environment...\n", - "." - ] - } - ], + "outputs": [], "source": [ "import qsharp # Enable Q#-Python integration" ] @@ -45,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": 63, + "execution_count": 27, "metadata": {}, "outputs": [], "source": [ @@ -55,21 +46,57 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/x-qsharp-data": "\"Connecting to Azure Quantum...\"", + "text/plain": [ + "Connecting to Azure Quantum..." + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Connected to Azure Quantum workspace AdriansProjectWorkspace in location westus.\n" + ] + } + ], "source": [ "targets = qsharp.azure.connect(\n", - " resourceId=\"\",\n", - " location=\"\"\n", + " resourceId=\"/subscriptions/2cc419b3-3ace-4156-b256-44663dd90190/resourceGroups/AzureQuantum/providers/Microsoft.Quantum/Workspaces/AdriansProjectWorkspace\",\n", + " location=\"westus\"\n", ")" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "This workspace's 10 targets:\n", + "- ionq.qpu (average queue time 0:00:32)\n", + "- ionq.qpu.aria-1 (average queue time 6:13:22)\n", + "- ionq.simulator (average queue time 0:00:03)\n", + "- quantinuum.hqs-lt-s1 (average queue time 0:00:00)\n", + "- quantinuum.hqs-lt-s1-apival (average queue time 0:00:56)\n", + "- quantinuum.hqs-lt-s2 (average queue time 0:11:27)\n", + "- quantinuum.hqs-lt-s2-apival (average queue time 0:00:01)\n", + "- quantinuum.hqs-lt-s1-sim (average queue time 0:01:59)\n", + "- quantinuum.hqs-lt-s2-sim (average queue time 0:02:40)\n", + "- quantinuum.hqs-lt (average queue time 0:00:00)\n" + ] + } + ], "source": [ "print(f\"This workspace's {len(targets)} targets:\")\n", "for target in targets:\n", @@ -78,9 +105,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading package Microsoft.Quantum.Providers.Honeywell and dependencies...\n", + "Active target is now quantinuum.hqs-lt-s1-sim\n" + ] + }, + { + "data": { + "text/plain": [ + "{'id': 'quantinuum.hqs-lt-s1-sim', 'current_availability': {}, 'average_queue_time': 119}" + ] + }, + "execution_count": 30, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "qsharp.azure.target('quantinuum.hqs-lt-s1-sim')" ] @@ -96,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 53, "metadata": {}, "outputs": [], "source": [ @@ -207,7 +253,16 @@ " for j in range(self.size):\n", " if not self[i, j]:\n", " empty += 1\n", - " return empty\n" + " return empty\n", + " \n", + " def prepare_constraints(self):\n", + " return _prepare_constraints(self)\n", + "\n", + " def solve_quantum(self, solve_fn):\n", + " return _solve_quantum(self, solve_fn)\n", + " \n", + " def get_constraints(self):\n", + " return _get_constraints(self)" ] }, { @@ -219,7 +274,7 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 32, "metadata": {}, "outputs": [ { @@ -295,8 +350,13 @@ "The algorithm in the quantum component is Grover's search algorithm, an algorithm that prepares a search space and then uses an oracle to perform \"Grover iterations\".\n", "A \"Grover iteration\" involves applying an oracle and then diffusion the state.\n", "The amazing thing is that we find the correct results in $\\mathcal{O}(\\sqrt{k})$.\n", - "If you'd like to learn more about Grover's search, please see [this article](https://docs.microsoft.com/en-us/azure/quantum/concepts-grovers).\n", - "\n", + "If you'd like to learn more about Grover's search, please see [this article](https://docs.microsoft.com/en-us/azure/quantum/concepts-grovers)." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "## Classical Precomputation\n", "\n", @@ -336,27 +396,27 @@ }, { "cell_type": "code", - "execution_count": 21, + "execution_count": 33, "metadata": {}, "outputs": [], "source": [ - "def get_constraints(self) -> Tuple[List[Tuple[int, int]], # Empty square edges\n", + "def _get_constraints(sudoku : Sudoku) -> Tuple[List[Tuple[int, int]], # Empty square edges\n", " List[Tuple[int, int]], # Starting constraints\n", " List[Tuple[int, int]] # Empty squares\n", " ]:\n", - " sub_size = math.floor(math.sqrt(self.size))\n", + " sub_size = math.floor(math.sqrt(sudoku.size))\n", " empty_indices = dict()\n", " empty_squares = list()\n", " empty_square_edges = list()\n", " starting_number_constraints = set() # We want to avoid duplicates\n", " empty_index = 0\n", "\n", - " for i in range(self.size):\n", - " for j in range(self.size):\n", + " for i in range(sudoku.size):\n", + " for j in range(sudoku.size):\n", "\n", " # Only consider empty_squares for constraint calculation\n", " \n", - " if self[i, j]:\n", + " if sudoku[i, j]:\n", " continue\n", " empty_indices[i, j] = empty_index\n", " empty_squares.append((i, j))\n", @@ -371,45 +431,44 @@ " for j_sub in range(j_sub_grid, j_sub_grid + sub_size):\n", " if i_sub == i and j_sub == j:\n", " continue\n", - " if not self.empty_at(i_sub, j_sub):\n", + " if sudoku[i_sub, j_sub]:\n", " starting_number_constraints.add(\n", - " (empty_index, self.at(i_sub, j_sub) - 1))\n", + " (empty_index, sudoku[i_sub, j_sub] - 1))\n", " elif j_sub < i_sub and i_sub < i and j_sub < j:\n", " empty_square_edges.append(\n", " (empty_index, empty_indices[(i_sub, j_sub)]))\n", "\n", " # Check for column constraints\n", "\n", - " for row_index in range(self.size):\n", - " if not self.empty_at(row_index, j):\n", + " for row_index in range(sudoku.size):\n", + " if sudoku[row_index, j]:\n", " starting_number_constraints.add(\n", - " (empty_index, self.at(row_index, j) - 1))\n", + " (empty_index, sudoku[row_index, j] - 1))\n", " elif row_index < i:\n", " empty_square_edges.append(\n", - " (empty_index, empty_indices[(row_index, j)]))\n", + " (empty_index, empty_indices[row_index, j]))\n", "\n", " # Check for row constraints\n", "\n", - " for col_index in range(self.size):\n", - " if not self.empty_at(i, col_index):\n", + " for col_index in range(sudoku.size):\n", + " if sudoku[i, col_index]:\n", " starting_number_constraints.add(\n", - " (empty_index, self.at(i, col_index) - 1))\n", + " (empty_index, sudoku[i, col_index] - 1))\n", " elif col_index < j:\n", " empty_square_edges.append(\n", - " (empty_index, empty_indices[(i, col_index)]))\n", + " (empty_index, empty_indices[i, col_index]))\n", "\n", " # Exclude illegal values on a 9x9 puzzle\n", " # Not needed for 4x4 since 4x4 has bit width 2, which only represents legal values\n", "\n", - " if self.size == 9:\n", + " if sudoku.size == 9:\n", " for invalid in range(9, 16):\n", " starting_number_constraints.add((empty_index, invalid))\n", "\n", " empty_index += 1\n", " return (empty_square_edges, list(starting_number_constraints), empty_squares)\n", "\n", - "\n", - "Sudoku.get_constraints = get_constraints\n" + "\n" ] }, { @@ -445,7 +504,7 @@ }, { "cell_type": "code", - "execution_count": 22, + "execution_count": 34, "metadata": { "microsoft": { "language": "qsharp" @@ -466,7 +525,7 @@ }, { "cell_type": "code", - "execution_count": 23, + "execution_count": 35, "metadata": { "microsoft": { "language": "qsharp" @@ -520,7 +579,7 @@ }, { "cell_type": "code", - "execution_count": 47, + "execution_count": 36, "metadata": {}, "outputs": [ { @@ -560,7 +619,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 37, "metadata": {}, "outputs": [], "source": [ @@ -606,7 +665,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 38, "metadata": {}, "outputs": [], "source": [ @@ -647,16 +706,16 @@ }, { "cell_type": "code", - "execution_count": 62, + "execution_count": 39, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'Search space size with two cells, one where three values are allowed and another where two values are allowed: \\n6'" + "'Search space size with two cells, one where three values are allowed and another where two values are allowed: 6'" ] }, - "execution_count": 62, + "execution_count": 39, "metadata": {}, "output_type": "execute_result" } @@ -668,7 +727,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 40, "metadata": {}, "outputs": [], "source": [ @@ -700,7 +759,7 @@ }, { "cell_type": "code", - "execution_count": 57, + "execution_count": 41, "metadata": {}, "outputs": [ { @@ -709,7 +768,7 @@ "'Number of iterations for search space size 4: 1'" ] }, - "execution_count": 57, + "execution_count": 41, "metadata": {}, "output_type": "execute_result" } @@ -730,7 +789,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 42, "metadata": { "microsoft": { "language": "qsharp" @@ -845,7 +904,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 43, "metadata": { "microsoft": { "language": "qsharp" @@ -905,7 +964,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 44, + "metadata": {}, + "outputs": [], + "source": [ + "# Not required, but can be helpful to provide type information to some IDEs.\n", + "SolvePuzzle: qsharp.QSharpCallable = None" + ] + }, + { + "cell_type": "code", + "execution_count": 45, "metadata": { "microsoft": { "language": "qsharp" @@ -1022,7 +1091,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 50, "metadata": {}, "outputs": [], "source": [ @@ -1034,7 +1103,7 @@ " yield lst[i:i + n]\n", "\n", "\n", - "def parse_measured_integer(arr: list) -> int:\n", + "def parse_measured_integer(arr: List[int]) -> int:\n", " \"\"\"\n", " Little endian bit list to integer\n", " [1,0,1] -> 7\n", @@ -1048,7 +1117,7 @@ " return res\n", "\n", "\n", - "def parse_measured_integers(arr: list, bit_length: int) -> list:\n", + "def parse_measured_integers(arr: List[int], bit_length: int) -> list:\n", " \"\"\"\n", " Takes a resulting state vector from `SolvePuzzle`, \n", " chunks it up into `bit_length` sized chunks and converts each chunk to an integer.\n", @@ -1057,12 +1126,12 @@ " return list(map(parse_measured_integer, chunks(arr, bit_length)))\n", "\n", "\n", - "def prepare_constraints(self) -> tuple:\n", + "def _prepare_constraints(sudoku : Sudoku) -> tuple:\n", " \"\"\"\n", " The Azure Quantum service only allows lists of basic types so we will split up our list of tuples into tuples of lists\n", " \"\"\"\n", " (empty_square_edges, starting_number_constraints,\n", - " empty_squares) = self.get_constraints()\n", + " empty_squares) = sudoku.get_constraints()\n", " empty_square_edges_1 = list(map(lambda x: x[0], empty_square_edges))\n", " empty_square_edges_2 = list(map(lambda x: x[1], empty_square_edges))\n", " starting_number_constraints_1 = list(\n", @@ -1071,8 +1140,7 @@ " map(lambda x: x[1], starting_number_constraints))\n", " return (empty_square_edges_1, empty_square_edges_2, starting_number_constraints_1, starting_number_constraints_2, empty_squares)\n", "\n", - "\n", - "Sudoku.prepare_constraints = prepare_constraints\n" + "\n" ] }, { @@ -1084,12 +1152,11 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ - "\n", - "def solve_quantum(self, solve_fn):\n", + "def _solve_quantum(sudoku : Sudoku, solve_fn):\n", " \"\"\"\n", " Solve a Sudoku puzzle based on a quantum execution function that \n", " takes the parameters for SolveSudoku from the Q# code above \n", @@ -1097,9 +1164,9 @@ " \"\"\"\n", " (empty_square_edges_1, empty_square_edges_2,\n", " starting_number_constraints_1, starting_number_constraints_2,\n", - " empty_squares) = self.prepare_constraints()\n", + " empty_squares) = sudoku.prepare_constraints()\n", " measurements = solve_fn(nVertices=len(empty_squares),\n", - " bitsPerColor=self.bit_length,\n", + " bitsPerColor=sudoku.bit_length,\n", " emptySquareEdges1=empty_square_edges_1,\n", " emptySquareEdges2=empty_square_edges_2,\n", " startingNumberConstraints1=starting_number_constraints_1,\n", @@ -1110,16 +1177,12 @@ " print(\"No solution computed. Did you use resource estimation?\")\n", " return measurements\n", " print(\"Solved puzzle!\")\n", - " solution = parse_measured_integers(measurements, self.bit_length)\n", + " solution = parse_measured_integers(measurements, sudoku.bit_length)\n", " print(f\"Raw solution: {solution}\")\n", " for empty_idx in range(len(empty_squares)):\n", " (i, j) = empty_squares[empty_idx]\n", " # Solution range is [0,3] or [0,8], whereas human-readable puzzles work with [1,4] or [1,9], respectively.\n", - " solved_val = solution[empty_idx] + 1\n", - " self.set(i, j, solved_val)\n", - "\n", - "\n", - "Sudoku.solve_quantum = solve_quantum\n" + " sudoku[i, j] = solution[empty_idx] + 1" ] }, { @@ -1141,9 +1204,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 54, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Regular simulator time: 36.06927990913391s\n", + "Sparse simulator time: 0.050690412521362305s\n" + ] + } + ], "source": [ "import time\n", "from IPython.display import clear_output\n", @@ -1205,7 +1277,13 @@ "source": [ "We can clearly see that the sparse simulator is two orders of magnitude faster, and this difference will only grow as the program size increases. \n", "So going forward we will be using it to simulate our code for the larger puzzles. \n", - "Generally speaking, the sparse simulator is often faster than the full state simulator, so make sure to try it when working on your own programs.\n", + "Generally speaking, the sparse simulator is often faster than the full state simulator, so make sure to try it when working on your own programs." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "## Solving Puzzles With the Sparse Simulator\n", "Below we have a few puzzles of varying difficulty, including a 9x9 puzzle with over 20 empty squares. \n", @@ -1335,7 +1413,13 @@ "The larger puzzles will actually use the oracle to find the best states. \n", "We can confirm these findings based on the search space sizes that we got during simulation.\n", "\n", - "With that, let's run some puzzles on Quantinuum through Azure Quantum!\n", + "With that, let's run some puzzles on Quantinuum through Azure Quantum!\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ "\n", "\n", "## Running on Quantinuum targets\n", From f8a70fb9eb78e67b2347bd4ec5fb77e22524fff1 Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Fri, 9 Sep 2022 15:41:04 -0500 Subject: [PATCH 6/7] Apply PR feedback --- .../GroversSudokuQuantinuum.ipynb | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb index 3dec77d3abbf..1241dfe4066e 100644 --- a/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb +++ b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb @@ -523,6 +523,16 @@ "open Microsoft.Quantum.Preparation;" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Not required, but can be helpful to provide type information to some IDEs.\n", + "AllowedAmplitudes: qsharp.QSharpCallable = None" + ] + }, { "cell_type": "code", "execution_count": 35, @@ -725,6 +735,16 @@ "{SearchSpaceSize.simulate(nVertices=2, bitsPerColor=2, startingNumberConstraints=[(0,0), (0,2), (1,3)])}\"" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Not required, but can be helpful to provide type information to some IDEs.\n", + "NIterations: qsharp.QSharpCallable = None" + ] + }, { "cell_type": "code", "execution_count": 40, From ac10f29db14f4b451a7cb1ad827dbc2ff4b0f3f1 Mon Sep 17 00:00:00 2001 From: Adrian Lehmann Date: Fri, 9 Sep 2022 15:43:43 -0500 Subject: [PATCH 7/7] Clear output --- .../GroversSudokuQuantinuum.ipynb | 191 +++--------------- 1 file changed, 31 insertions(+), 160 deletions(-) diff --git a/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb index 1241dfe4066e..c6f2064272d8 100644 --- a/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb +++ b/samples/azure-quantum/grover-sudoku/GroversSudokuQuantinuum.ipynb @@ -16,7 +16,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": null, "metadata": { "vscode": { "languageId": "shellscript" @@ -36,7 +36,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -46,27 +46,9 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/x-qsharp-data": "\"Connecting to Azure Quantum...\"", - "text/plain": [ - "Connecting to Azure Quantum..." - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Connected to Azure Quantum workspace AdriansProjectWorkspace in location westus.\n" - ] - } - ], + "outputs": [], "source": [ "targets = qsharp.azure.connect(\n", " resourceId=\"/subscriptions/2cc419b3-3ace-4156-b256-44663dd90190/resourceGroups/AzureQuantum/providers/Microsoft.Quantum/Workspaces/AdriansProjectWorkspace\",\n", @@ -76,27 +58,9 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "This workspace's 10 targets:\n", - "- ionq.qpu (average queue time 0:00:32)\n", - "- ionq.qpu.aria-1 (average queue time 6:13:22)\n", - "- ionq.simulator (average queue time 0:00:03)\n", - "- quantinuum.hqs-lt-s1 (average queue time 0:00:00)\n", - "- quantinuum.hqs-lt-s1-apival (average queue time 0:00:56)\n", - "- quantinuum.hqs-lt-s2 (average queue time 0:11:27)\n", - "- quantinuum.hqs-lt-s2-apival (average queue time 0:00:01)\n", - "- quantinuum.hqs-lt-s1-sim (average queue time 0:01:59)\n", - "- quantinuum.hqs-lt-s2-sim (average queue time 0:02:40)\n", - "- quantinuum.hqs-lt (average queue time 0:00:00)\n" - ] - } - ], + "outputs": [], "source": [ "print(f\"This workspace's {len(targets)} targets:\")\n", "for target in targets:\n", @@ -105,28 +69,9 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loading package Microsoft.Quantum.Providers.Honeywell and dependencies...\n", - "Active target is now quantinuum.hqs-lt-s1-sim\n" - ] - }, - { - "data": { - "text/plain": [ - "{'id': 'quantinuum.hqs-lt-s1-sim', 'current_availability': {}, 'average_queue_time': 119}" - ] - }, - "execution_count": 30, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "qsharp.azure.target('quantinuum.hqs-lt-s1-sim')" ] @@ -142,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -274,27 +219,9 @@ }, { "cell_type": "code", - "execution_count": 32, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "-----------------\n", - "| 1 | 3 | 4 | 2 |\n", - "-----------------\n", - "| 2 | 4 | 3 | 1 |\n", - "-----------------\n", - "| 3 | 2 | 1 | 4 |\n", - "-----------------\n", - "| 4 | 1 | 2 | 3 |\n", - "-----------------\n", - "\n", - "Valid!\n" - ] - } - ], + "outputs": [], "source": [ "sudoku = Sudoku([\n", " [1, 3, 4, 2],\n", @@ -396,7 +323,7 @@ }, { "cell_type": "code", - "execution_count": 33, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -504,7 +431,7 @@ }, { "cell_type": "code", - "execution_count": 34, + "execution_count": null, "metadata": { "microsoft": { "language": "qsharp" @@ -535,7 +462,7 @@ }, { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": { "microsoft": { "language": "qsharp" @@ -589,34 +516,9 @@ }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "import matplotlib.pyplot as plt\n", "allowed_amplitudes = AllowedAmplitudes.simulate(nVertices=2, bitsPerColor=2, startingNumberConstraints=[(0,0), (0,2), (1,3)])\n", @@ -629,7 +531,7 @@ }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -675,7 +577,7 @@ }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -716,20 +618,9 @@ }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Search space size with two cells, one where three values are allowed and another where two values are allowed: 6'" - ] - }, - "execution_count": 39, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "f\"Search space size with two cells, one where three values are allowed and another where two values are allowed: \\\n", "{SearchSpaceSize.simulate(nVertices=2, bitsPerColor=2, startingNumberConstraints=[(0,0), (0,2), (1,3)])}\"" @@ -747,7 +638,7 @@ }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -779,20 +670,9 @@ }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "'Number of iterations for search space size 4: 1'" - ] - }, - "execution_count": 41, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "f\"Number of iterations for search space size 4: {NIterations.simulate(searchSpaceSize=4)}\"" ] @@ -809,7 +689,7 @@ }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": { "microsoft": { "language": "qsharp" @@ -924,7 +804,7 @@ }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": { "microsoft": { "language": "qsharp" @@ -984,7 +864,7 @@ }, { "cell_type": "code", - "execution_count": 44, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -994,7 +874,7 @@ }, { "cell_type": "code", - "execution_count": 45, + "execution_count": null, "metadata": { "microsoft": { "language": "qsharp" @@ -1111,7 +991,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1172,7 +1052,7 @@ }, { "cell_type": "code", - "execution_count": 49, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -1224,18 +1104,9 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Regular simulator time: 36.06927990913391s\n", - "Sparse simulator time: 0.050690412521362305s\n" - ] - } - ], + "outputs": [], "source": [ "import time\n", "from IPython.display import clear_output\n",