Write a function that prepares the plus state ∣+⟩⊗n=[21(∣0⟩+∣1⟩)]⊗n, assuming the state of the input quantum variable is ∣x⟩⊗n
HINT
Use either apply_to_all() with H(x), or hadamard_transform().
from classiq import *from classiq.qmod.symbolic import pi
Now we will test our code:
n = 5@qfuncdef main(x: Output[QArray[QBit]]): allocate(5, x) hadamard_transform(x) # Prepare the plus state
qprog = synthesize(main)
show(qprog)
Output:
Quantum program link: https://platform.classiq.io/circuit/3BDZ0CrFpw76x6WbQ3GSz1oh9aD
Some basic explanations about the high-level functional design with Classiq:
There should always be a main (def main(...)) function - the model that captures your algortihm is described there
The model is always generated out of the main function
The model is sent to the synthesis engine (compiler) that return a quantum program which contains the quantum circuit
Some basic guidelines about the modeling language (Qmod):
Every quantum variable should be declared, either as a parameter of a funciton e.g. def main(x: Output[QBit]) or within the function itself with x = QBit()
Some quantum variables need to be initalized with the allocate function.
This is required in 2 cases:
A variable is a parameter of a function with the declaration Output like def main(x: Output[QNum])
A variable that is declared within a function like a = QNum()
For the main function, you should always use Output for all variables, as the function does not receive any input
Important tip!You can see all the declarations of the functions with their parameters in the functions.py file within the classiq package (or by just right clicking a function and presing Go To Defintion)
Before diving into Grover’s algorithm implementation, it is important to analyze its different building blocks. We start by looking at the overall quantum algorithm and then start build it step-by-step.
Initial state preparation: The algorithm starts by preparing a uniform superposition ∣+⟩⊗n using Hadamard gates.
This ensures that all possible states are explored simultaneously. At this stage, each state has equal probability amplitude.
Grover Oracle: The oracle encodes the problem by marking the solution state(s). It does this by applying a phase flip, effectively distinguishing “good” states from “bad” ones. Importantly, it does not reveal the solution directly, only modifies its phase.
Grover Diffuser: The diffuser amplifies the probability of the marked states through a reflection about the average amplitude.
This step increases the likelihood of measuring the correct solution.Repeating the oracle and diffuser gradually concentrates probability on the target state.
Overall we can understand the Grover operator as composed of two reflection operators:
about the superposition of ‘bad states’ (i.e. not the solutions)
about the initial guess state
In this section we will build the first reflection operator which is also the implementation of the oracle function.Geometrically it can be understood in the 2D vector space of Span{∣ψgood⟩,∣ψbad⟩}.The above figure describe geometrically the reflection of some state ∣ψ⟩=α∣ψgood⟩+β∣ψbad⟩ about the state ∣ψbad⟩ such thatR(α∣ψgood⟩+β∣ψbad⟩)=−α∣ψgood⟩+β∣ψbad⟩This operator can also be written asR∣x⟩=(−1)(x==good solution)∣x⟩so if the state of x is a solution it gets a (−) phase.
Now we turn to actually implementing the oracle.With Qmod quantum expressions, capturing the intent becomes straightforward.The compiler does the heavy-lifting of synthesizing the reversible circuits for us.For our purposes, we want to find all the states that obey 2a+b=c so there are 3 quantum variables. In addition, we want to store our results in the relative phase of such states. In other words, we want:∣a,b,c⟩→(−1)(2a+b==c)∣a,b,c⟩In a visual representation, this is what we want:Now we can implement it by defining the oracle_function:
@qpermdef oracle_function(a: Const[QNum], b: Const[QNum], c: Const[QNum]): control((2 * a + b == c), phase(pi))
The second part of the Grover operator is the diffuser, which can be viewed as the reflection operator about our initial guess.As with the oracle reflection operator, we can describe any state ∣ψ⟩ as a superposition of the initial state ∣ψ0⟩ such that and the orthogoanl state to it ∣ψ0⊥⟩∣ψ⟩=α∣ψ0⟩+β∣ψ0⊥⟩Here we want to apply a π phase to all states that are not equal our initial guess.The reflection operator (our diffuser) is defined as:R(α∣ψ0⟩+β∣ψ0⊥⟩)=α∣ψ0⟩−β∣ψ0⊥⟩To implement a reflection about the initial state ∣ψ0⟩, we instead perform a reflection about the computational zero state ∣0⟩, conjugated by our state-preparation unitary for the initial state ∣ψ0⟩.That is, if Uψ0∣0⟩=∣ψ0⟩ then we will implement the desired R operator with:R=Uψ0R0Uψ0†where R0 is the reflection operator about the zero state:R0∣x⟩=(−1)(x=0)∣x⟩=(2∣0⟩⟨0∣−I)∣x⟩
That’s it! Complete your grover operator by implementing the two functions that you’ve built, first the oracle_function and then the grover_diffuser:
@qfuncdef my_grover_operator(a: QNum, b: QNum, c: QNum): # TODO complete here pass
Now that we have our Grover operator, we can run it within our code. We have 3 steps here:
Initalize a,b and c within the scope of the main function using the allocate operation
Create the initial states for a,b and c
Apply your Grover operator
size_a = 2size_b = 2size_c = 3@qfuncdef main(a: Output[QNum], b: Output[QNum], c: Output[QNum]): allocate(size_a, a) allocate(size_b, b) allocate(size_c, c) # TODO complete here pass
Synthesize your model:
qprog = synthesize(main)
And view it within the IDE:
show(qprog)
Output:
Quantum program link: https://platform.classiq.io/circuit/3BDZ0Oj00fjmqsR9QKcsGqmJ0HB
Is it what you were expecting?
Now we can play with the constraints as we did in the IDE:
qprog_depth_optimized = synthesize( main, constraints=Constraints(optimization_parameter="depth")) # or 'width'show(qprog_depth_optimized)
Output:
Quantum program link: https://platform.classiq.io/circuit/3BDZ0fTM0RneZZeGNTJ9XxpiAx6
You have completed your own Grover algorithm implementation from functional building blocks without sweeping under the rug any details, really impressive work!
from classiq import *from classiq.qmod.symbolic import pi@qpermdef oracle_function(a: Const[QNum], b: Const[QNum], c: Const[QNum]): control((2 * a + b == c), lambda: phase(pi))@qfuncdef grover_diffuser(state: QNum) -> None: within_apply( lambda: hadamard_transform(state), lambda: control(state != 0, lambda: phase(pi)), )@qfuncdef my_grover_operator(a: QNum, b: QNum, c: QNum): oracle_function(a, b, c) grover_diffuser([a, b, c])@qfuncdef main(a: Output[QNum], b: Output[QNum], c: Output[QNum]): allocate(size_a, a) allocate(size_b, b) allocate(size_c, c) hadamard_transform([a, b, c]) my_grover_operator(a, b, c)qprog = synthesize(main)show(qprog)constraints = Constraints(optimization_parameter="depth") # or 'width'qprog_depth_optimized = synthesize(main, constraints=constraints)show(qprog_depth_optimized)
Output:
Quantum program link: https://platform.classiq.io/circuit/3BDZ1HvwH1GQxmlAVeElrmEtqZm Quantum program link: https://platform.classiq.io/circuit/3BDZ2l0vxNe8XvU6ZbqoUsEvTnN