author | description | ms.author | ms.date | ms.service | ms.subservice | ms.topic | no-loc | title | uid | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
SoniaLopezBravo |
In this tutorial, you will build a Q# project that demonstrates Grover's search algorithm, one of the canonical quantum algorithms. |
sonialopez |
09/10/2024 |
azure-quantum |
qdk |
tutorial |
|
Tutorial: Implement Grover's Algorithm in Q# |
microsoft.quantum.tutorial-qdk.grovers |
In this tutorial, you implement Grover's algorithm in Q# to solve search-based problems. For an in-depth explanation of the theory behind Grover's algorithm, see the Theory of Grover's algorithm.
In this tutorial, you:
[!div class="checklist"]
- Define the Grover's algorithm for a search problem
- Implement Grover's algorithm in Q#
[!INCLUDE Copilot in Azure Quantum banner]
-
To run the code sample in the Copilot in Azure Quantum:
- A Microsoft (MSA) email account.
-
To develop and run the code sample in Visual Studio Code:
- The latest version of Visual Studio Code or open VS Code on the Web.
- The latest version of the Azure Quantum Development Kit extension. For installation details, see Installing the QDK on VS Code.
Grover's algorithm is one of the most famous algorithms in quantum computing. The type of problem it solves is often referred to as "searching a database", but it's more accurate to think of it in terms of the search problem.
Any search problem can be mathematically formulated with an abstract function
Thus, you can formulate the any search problem as: given a classical function
To implement Grover's algorithm to solve a search problem, you need to:
- Transform the problem to the form of a Grover's task. For example, suppose you want to find the factors of an integer
$M$ using Grover's algorithm. You can transform the integer factorization problem to a Grover's task by creating a function$$f_M(x)=1[r],$$ where$1[r]=1$ if$r=0$ and$1[r]=0$ if$r\neq0$ and$r$ is the remainder of$M/x$ . This way, the integers$x_i$ that make$f_M(x_i)=1$ are the factors of$M$ and you have transformed the problem to a Grover's task. - Implement the function of the Grover's task as a quantum oracle. To implement Grover's algorithm, you need to implement the function
$f(x)$ of your Grover's task as a quantum oracle. - Use Grover's algorithm with your oracle to solve the task. Once you have a quantum oracle, you can plug it into your Grover's algorithm implementation to solve the problem and interpret the output.
Suppose there are
- Start with a register of
$n$ qubits initialized in the state$\ket{0}$ . - Prepare the register into a uniform superposition by applying
$H$ to each qubit in the register:$$|\psi\rangle=\frac{1}{N^{1 / 2}} \sum_{x=0}^{N-1}|x\rangle$$ - Apply the following operations to the register
$N_{\text{optimal}}$ times:- The phase oracle
$O_f$ that applies a conditional phase shift of$-1$ for the solution items. - Apply
$H$ to each qubit in the register. - Apply
$-O_0$ , a conditional phase shift of$-1$ to every computational basis state except$\ket{0}$ . - Apply
$H$ to each qubit in the register.
- The phase oracle
- Measure the register to obtain the index of an item that's a solution with very high probability.
- Check the item to see if it's a valid solution. If not, start again.
This section discusses how to implement the algorithm in Q#. There are few things to consider when implementing Grover's algorithm. You need to define what is your marked state, how to reflect about it, and how many iterations to run the algorithm for. You also need to define the oracle that implements the function of the Grover's task.
First, you define what input you are trying to find in the search. To do so, write an operation that applies the steps b, c and d from the Grover's algorithm.
Together, these steps are also known as the Grover'S diffusion operator
operation ReflectAboutMarked(inputQubits : Qubit[]) : Unit {
Message("Reflecting about marked state...");
use outputQubit = Qubit();
within {
// We initialize the outputQubit to (|0⟩ - |1⟩) / √2, so that
// toggling it results in a (-1) phase.
X(outputQubit);
H(outputQubit);
// Flip the outputQubit for marked states.
// Here, we get the state with alternating 0s and 1s by using the X
// operation on every other qubit.
for q in inputQubits[...2...] {
X(q);
}
} apply {
Controlled X(inputQubits, outputQubit);
}
}
The ReflectAboutMarked
operation reflects about the basis state marked by alternating zeros and ones. It does so by applying the Grover's diffusion operator to the input qubits. The operation uses an auxiliary qubit, outputQubit
, which is initialized in the state
Grover's search has an optimal number of iterations that yields the highest probability of measuring a valid output. If the problem has
Continuing to iterate past the optimal number of iterations starts reducing that probability until you reach nearly-zero success probability on iteration
In practical applications, you don't usually know how many solutions your problem has before you solve it. An efficient strategy to handle this issue is to "guess" the number of solutions
The following Q# function calculates the optimal number of iterations for a given number of qubits in a register.
function CalculateOptimalIterations(nQubits : Int) : Int {
if nQubits > 63 {
fail "This sample supports at most 63 qubits.";
}
let nItems = 1 <<< nQubits; // 2^nQubits
let angle = ArcSin(1. / Sqrt(IntAsDouble(nItems)));
let iterations = Round(0.25 * PI() / angle - 0.5);
return iterations;
}
The CalculateOptimalIterations
function uses the formula above to calculate the number of iterations, and then rounds it to the nearest integer.
The Q# operation for Grover's search algorithm has three inputs:
- The number of qubits,
nQubits : Int
, in the qubit register. This register will encode the tentative solution to the search problem. After the operation, it will be measured. - The number of optimal iterations,
iterations : Int
. - An operation,
phaseOracle : Qubit[] => Unit) : Result[]
, that represents the phase oracle for the Grover's task. This operation applies an unitary transformation over a generic qubit register.
operation GroverSearch( nQubits : Int, iterations : Int, phaseOracle : Qubit[] => Unit) : Result[] {
use qubits = Qubit[nQubits];
PrepareUniform(qubits);
for _ in 1..iterations {
phaseOracle(qubits);
ReflectAboutUniform(qubits);
}
// Measure and return the answer.
return MResetEachZ(qubits);
}
The GroverSearch
operation initializes a register of
The code makes use of three helper operations: PrepareUniform
, ReflectAboutUniform
, and ReflectAboutAllOnes
.
Given a register in the all-zeros state, the PrepareUniform
operation prepares a uniform superposition over all basis states.
operation PrepareUniform(inputQubits : Qubit[]) : Unit is Adj + Ctl {
for q in inputQubits {
H(q);
}
}
The ``ReflectAboutAllOnes` operation reflects about the all-ones state.
operation ReflectAboutAllOnes(inputQubits : Qubit[]) : Unit {
Controlled Z(Most(inputQubits), Tail(inputQubits));
}
The operation ReflectAboutUniform
reflects about the uniform superposition state. First, it transforms the uniform superposition to all-zero. Then, it transforms the all-zero state to all-ones. Finally, it reflects about the all-ones state. The operation is called ReflectAboutUniform
because it can be geometrically interpreted as a reflection in the vector space about the uniform superposition state.
operation ReflectAboutUniform(inputQubits : Qubit[]) : Unit {
within {
Adjoint PrepareUniform(inputQubits);
// Transform the all-zero state to all-ones
for q in inputQubits {
X(q);
}
} apply {
ReflectAboutAllOnes(inputQubits);
}
}
Now you have all the ingredients to implement a particular instance of Grover's search algorithm and solve the factoring problem. To finish, the Main
operation sets up the problem by specifying the number of qubits and the number of iterations
operation Main() : Result[] {
let nQubits = 5;
let iterations = CalculateOptimalIterations(nQubits);
Message($"Number of iterations: {iterations}");
// Use Grover's algorithm to find a particular marked state.
let results = GroverSearch(nQubits, iterations, ReflectAboutMarked);
return results;
}
Select the desired platform to run your program.
You can test your Q# code with the Copilot in Azure Quantum free of charge - all you need is a Microsoft (MSA) email account. For more information about the Copilot in Azure Quantum, see Explore Azure Quantum.
-
Open the Copilot in Azure Quantum in your browser.
-
Copy and paste the following code into the code editor.
import Microsoft.Quantum.Convert.*; import Microsoft.Quantum.Math.*; import Microsoft.Quantum.Arrays.*; import Microsoft.Quantum.Measurement.*; import Microsoft.Quantum.Diagnostics.*; operation Main() : Result[] { let nQubits = 5; let iterations = CalculateOptimalIterations(nQubits); Message($"Number of iterations: {iterations}"); // Use Grover's algorithm to find a particular marked state. let results = GroverSearch(nQubits, iterations, ReflectAboutMarked); return results; } operation GroverSearch( nQubits : Int, iterations : Int, phaseOracle : Qubit[] => Unit) : Result[] { use qubits = Qubit[nQubits]; PrepareUniform(qubits); for _ in 1..iterations { phaseOracle(qubits); ReflectAboutUniform(qubits); } // Measure and return the answer. return MResetEachZ(qubits); } function CalculateOptimalIterations(nQubits : Int) : Int { if nQubits > 63 { fail "This sample supports at most 63 qubits."; } let nItems = 1 <<< nQubits; // 2^nQubits let angle = ArcSin(1. / Sqrt(IntAsDouble(nItems))); let iterations = Round(0.25 * PI() / angle - 0.5); return iterations; } operation ReflectAboutMarked(inputQubits : Qubit[]) : Unit { Message("Reflecting about marked state..."); use outputQubit = Qubit(); within { // We initialize the outputQubit to (|0⟩ - |1⟩) / √2, so that // toggling it results in a (-1) phase. X(outputQubit); H(outputQubit); // Flip the outputQubit for marked states. // Here, we get the state with alternating 0s and 1s by using the X // operation on every other qubit. for q in inputQubits[...2...] { X(q); } } apply { Controlled X(inputQubits, outputQubit); } } operation PrepareUniform(inputQubits : Qubit[]) : Unit is Adj + Ctl { for q in inputQubits { H(q); } } operation ReflectAboutAllOnes(inputQubits : Qubit[]) : Unit { Controlled Z(Most(inputQubits), Tail(inputQubits)); } operation ReflectAboutUniform(inputQubits : Qubit[]) : Unit { within { // Transform the uniform superposition to all-zero. Adjoint PrepareUniform(inputQubits); // Transform the all-zero state to all-ones for q in inputQubits { X(q); } } apply { // Now that we've transformed the uniform superposition to the // all-ones state, reflect about the all-ones state, then let the // within/apply block transform us back. ReflectAboutAllOnes(inputQubits); } }
Tip
From Copilot in Azure Quantum, you can open your program in VS Code for the Web by selecting the VS Code logo button in the right-hand corner of the code editor.
- Select In-memory Simulator.
- Select the number of shots to run, and select Run.
- The results are displayed in the histogram and in the Results fields.
- Select Explain code to prompt Copilot to explain the code to you.
You can also submit your program to the free Quantinuum H-Series Emulator. The emulator simulates a quantum computer with 20 qubits.
- Select the In-Memory Simulator dropdown and select Quantinuum H-Series Emulator.
- Select the number of shots (currently limited to 20) and select Run.
-
Open Visual Studio Code and select File > New Text File to create a new file.
-
Save the file as
GroversAlgorithm.qs
. This file will contain the Q# code for your program. -
Copy the following code into the
GroversAlgorithm.qs
file.import Microsoft.Quantum.Convert.*; import Microsoft.Quantum.Math.*; import Microsoft.Quantum.Arrays.*; import Microsoft.Quantum.Measurement.*; import Microsoft.Quantum.Diagnostics.*; operation Main() : Result[] { let nQubits = 5; let iterations = CalculateOptimalIterations(nQubits); Message($"Number of iterations: {iterations}"); // Use Grover's algorithm to find a particular marked state. let results = GroverSearch(nQubits, iterations, ReflectAboutMarked); return results; } operation GroverSearch( nQubits : Int, iterations : Int, phaseOracle : Qubit[] => Unit) : Result[] { use qubits = Qubit[nQubits]; PrepareUniform(qubits); for _ in 1..iterations { phaseOracle(qubits); ReflectAboutUniform(qubits); } // Measure and return the answer. return MResetEachZ(qubits); } function CalculateOptimalIterations(nQubits : Int) : Int { if nQubits > 63 { fail "This sample supports at most 63 qubits."; } let nItems = 1 <<< nQubits; // 2^nQubits let angle = ArcSin(1. / Sqrt(IntAsDouble(nItems))); let iterations = Round(0.25 * PI() / angle - 0.5); return iterations; } operation ReflectAboutMarked(inputQubits : Qubit[]) : Unit { Message("Reflecting about marked state..."); use outputQubit = Qubit(); within { // We initialize the outputQubit to (|0⟩ - |1⟩) / √2, so that // toggling it results in a (-1) phase. X(outputQubit); H(outputQubit); // Flip the outputQubit for marked states. // Here, we get the state with alternating 0s and 1s by using the X // operation on every other qubit. for q in inputQubits[...2...] { X(q); } } apply { Controlled X(inputQubits, outputQubit); } } operation PrepareUniform(inputQubits : Qubit[]) : Unit is Adj + Ctl { for q in inputQubits { H(q); } } operation ReflectAboutAllOnes(inputQubits : Qubit[]) : Unit { Controlled Z(Most(inputQubits), Tail(inputQubits)); } operation ReflectAboutUniform(inputQubits : Qubit[]) : Unit { within { // Transform the uniform superposition to all-zero. Adjoint PrepareUniform(inputQubits); // Transform the all-zero state to all-ones for q in inputQubits { X(q); } } apply { // Now that we've transformed the uniform superposition to the // all-ones state, reflect about the all-ones state, then let the // within/apply block transform us back. ReflectAboutAllOnes(inputQubits); } }
-
Before running the program, ensure the target profile is set to Unrestricted. Select View -> Command Palette, search for QIR, select Q#: Set the Azure Quantum QIR target profile, and then select Q#: unrestricted.
-
To run your program, select Run from the list of commands above the
Main
operation, or press Ctrl+F5. By default, the compiler runs theMain
operation or function on the default simulator. -
Your output will appear in the debug console in the terminal.
Note
If the target profile is not set to Unrestricted, you will get an error when you run the program.
Explore other Q# tutorials:
- Quantum entanglement shows how to write a Q# program that manipulates and measures qubits and demonstrates the effects of superposition and entanglement.
- Quantum random number generator shows how to write a Q# program that generates random numbers out of qubits in superposition.
- Quantum Fourier Transform explores how to write a Q# program that directly addresses specific qubits.
- The Quantum Katas are self-paced tutorials and programming exercises aimed at teaching the elements of quantum computing and Q# programming at the same time.