Use this file to discover all available pages before exploring further.
View on GitHub
Open this notebook in GitHub to run it yourself
Grover’s Search Algorithm, introduced by Lov Grover in 1996 [1], is one of the canonical quantum algorithms. It provides a quadratic speedup for searching an unstructured database and is often considered alongside Shor’s algorithm as a cornerstone of quantum computing. The search algorithm has applications in various fields, such as in cybersecurity.
Input: A Boolean function (oracle) f:{0,1}n→{0,1} marking “solutions” among N=2n possible items.
Promise: At least one marked element exists in the search space.
Output: With high probability, the algorithm outputs a marked element x (i.e., f(x)=1).
Complexity: The algorithm requires O(N) oracle queries to obtain the result, while the query complexity for classical search is O(N).Keywords: Search and optimization, Unstructured search, Quadratic speedup, Amplitude amplification, Graph problems, SAT problems, Oracle/Query complexity.
Grover’s Search algorithm starts with preparing the search space ∣s⟩, and then repeats over a combination of two reflection operations:Uf∣x⟩=(−1)f(x)∣x⟩usually called an "Oracle", adds a minus phase to marked states, andUs=2∣s⟩⟨s∣−1usually called a "Diffuser", reflects around the full search space.The number of times we need to repeat the combination of these two reflections (sometimes called a “Grover Operator”) depends on the number of solutions: for k solutions in a search space of size N, we shall perform r∼πN/k iterations. In practice, however, the number of solutions is often unknown. In that case, one can perform a quantum counting algorithm prior to the search, or run the search algorithm repeatedly, with an increasing number of repetitions ri∼4π2i for i=0,1,….Both approaches do not affect the complexity of the search algorithm. In this notebook we take the second approach.For a given problem, Grover’s algorithm depends on two functions, one for preparing the search space and another for applying the oracle, as well as on the number of repetitions.Below we address two canonical search problems, a 3-SAT problem and a Max-Cut problem on a graph. In this notebook we work with the grover_operator function, and define a classical postprocess function for iterating over different r values and obtianing the marked states.Using the phase_oracle quantum function from the Classiq open-library together with the Qmod language for high-level problem definitions helps avoid low-level implementation details that are typically required on other platforms.
Below we define a function that runs a quantum program of the Grover’s search algorithm, for an increasing number of repetitions, until a marked state is found.
import numpy as npfrom classiq import *from classiq.qmod.symbolic import pidef repeat_grover_until_success( grover_search_qprog, classical_formula, num_shots=1000, threshold=0.45, max_iter=4): """ Runs a grover_search_qprog with different powers, given by a CInt parameter `r`. """ i = 0 r_previous = None with ExecutionSession( grover_search_qprog, ExecutionPreferences(num_shots=num_shots) ) as es: while i < max_iter: r = int(np.ceil(np.pi / 4 * np.sqrt(2**i))) if r == r_previous: # skip duplicate r i += 1 continue print(f"running Grover with {r} repetitions") res = es.sample({"r": r}) for sample in res.parsed_counts: if ( classical_formula(**sample.state) and sample.shots / num_shots > threshold ): print( f"Success! a solution was found with probability larger than {threshold}, using {r} repetitions" ) return res, r r_previous = r i += 1 print( f"Could not find a solution, try to in increase max_iter or decrease the threshold, returning last run." ) return res, r
The 3-SAT problem [2] is a famous NP-Complete problem, a solution of which allows solving any problem in the complexity class NP. We treat two different 3-SAT problems, a small one and a larger one.For illustration, we define a function for printing the truth table of SAT problems.
import itertoolsdef print_truth_table(num_variables, boolean_func): variables = [f"x{i}" for i in range(num_variables)] combinations = list(itertools.product([0, 1], repeat=num_variables)) header = " ".join([f"{var:<5}" for var in variables]) + " | Result" print(header) print("-" * len(header)) for combination in combinations: result = boolean_func(list(combination)) != 0 # pass as array values_str = " ".join([f"{val:<5}" for val in combination]) print(f"{values_str} | {result:<5}")
We specify a 3-SAT formula in the so-called Conjunctive Normal Form (CNF), that requires a solution:(x1∨x2∨x3)∧(¬x1∨x2∨x3)∧(¬x1∨¬x2∨¬x3)∧(¬x1∨¬x2∨x3)∧(x1∨x2∨¬x3)∧(¬x1∨x2∨¬x3)
We define the Grover search model for finding the solution. To specify the model, we use the standard phase_oracle that transforms ‘digital’ oracle; i.e., ∣x⟩∣0⟩→∣x⟩∣f(x)⟩ to a phase oracle ∣x⟩→(−1)f(x)∣x⟩.The predicate that we pass to the phase oracle is simply given by the 3-CNF formula defined above.
Quantum program link: https://platform.classiq.io/circuit/3CIkkbehGecuMHmQCrZjWQdl8fg running Grover with 1 repetitions running Grover with 2 repetitions Success! a solution was found with probability larger than 0.45, using 2 repetitions
The “Maximum Cut Problem” (MaxCut) [3] is an example of a combinatorial optimization problem. It refers to finding a partition of a graph into two sets, such that the number of edges between the two sets is the maximum.The MaxCut problem is defined as follows:
Given a graph G=(V,E) with ∣V∣=n nodes and E edges, a cut is defined as a partition of the graph into two complementary subsets of nodes.The gaol is to find a cut where the number of edges between the two subsets is the maximum. We can represent a cut, and the number of its connecting edges as follows:
x∈{0,1}n is a binary vector of size n that represents a cut: assigning 0 and 1 to nodes in the first and second subsets, respectively.
C(x)=∑(i,j)xi(1−xj)+xj(1−xi)=∑(i,j)xi⊕xj gives the number of connecting edges for a given cut.
The MaxCut problem cannot be cast directly into a Grover’s search algorithm, as we task to find the maximum of a function, rather than some “marked” solutions.One approach is to apply Grover’s algorithms for formulas of the form C(x)≥T, for some fixed value of T: We initialize a threshold
T, then run Grover’s algorithm with an oracle that marks cuts with C(x)≥T to amplify promising candidates. If a better cut is found, we update
T and repeat until no improvement is likely (or a preset budget is reached).In this notebook we show a solution for a single instance of this procedure, i.e., taking one example with some value for T.We initiate a specific graph whose maximum cut is 5:
Constructing a Grover’s search algorithm is done in similar to the 3-SAT examples, we only require to define a predicate formula. In this example we set the cut size (the value of T) to
Quantum program link: https://platform.classiq.io/circuit/3CIknLQwkWvbPUceQC1xGH0fZWK
# In this example we reduce the threshold, since typically, more than one solution is expectedres_max_cut, r = repeat_grover_until_success( qprog_max_cut, lambda nodes: cut_predicate(CUT_SIZE, nodes), threshold=0.1)
Output:
running Grover with 1 repetitions Success! a solution was found with probability larger than 0.1, using 1 repetitions
Upon printing the result, we see that our execution of Grover’s algorithm successfully found the satisfying assignments for the input formula:
The satisfying assignments are ~100 times more probable than the unsatisfying assignments. We print the corresponding graph for one of them:
result_parsed = df_new["nodes"][0]
import matplotlib.pyplot as pltedge_widths = [ is_cross_cut_edge( int(result_parsed[i]), int(result_parsed[j]), ) + 0.5 for i, j in G.edges]node_colors = [int(c) for c in result_parsed]nx.draw_networkx( G, pos=pos, with_labels=True, alpha=0.8, node_size=500, node_color=node_colors, width=edge_widths, cmap=plt.cm.rainbow,)