Deutsch Algorithm (Part 3): Implementation of oracle-based algorithms
Because the Deutsch algorithm is just a simple instance of the Deutsch-Jozsa algorithm where $n=2$, we’ll only discuss how to implement the general one. Let’s go.
Difficulty of reconstructing gates from matrices
This is the first time we encounter a so-called black-box function in an algorithm. It’s called a black box since we don’t how it calculate the output from the input, but we do know the output it returns. We can query the oracle if it is provided beforehand. Querying is easy but you shouldn’t take it for granted. In fact, creating an oracle from fundamental gate is, generally, extremely hard and a soft underbelly of quantum computing. This huge challenge tends to be overlooked because it’s ignored in the complexity evaluation of computation, which focuses on “classical” actions instead.
It’s known that oracles are indeed hidden operators, thus represented by hidden matrices. If we know the purpose of the operator we want to build, it’s not hard to write it out mathematically as a matrix. However, retaining a respective set of fundamental gates from the matrix is a monsterous challenge. There is yet a general process to perform reverse engineering to be discovered while verifying a matrix resulting from gates is a no difficult task. If needed, we have to deduce gates from matrices manually. For example, if we want to reverse engineer the matrix $$\frac{1}{2} \begin{bmatrix} 1 & 0 & 1 & 0 & 0 & 1 & 0 & -1 \\ 0 & 1 & 0 & 1 & 1 & 0 & -1 & 0 \\ 0 & 1 & 0 & -1 & 1 & 0 & 1 & 0 \\ 1 & 0 & -1 & 0 & 0 & 1 & 0 & 1 \\ 1 & 0 & 1 & 0 & 0 & -1 & 0 & 1 \\ 0 & 1 & 0 & 1 & -1 & 0 & 1 & 0 \\ 0 & 1 & 0 & -1 & -1 & 0 & -1 & 0 \\ 1 & 0 & -1 & 0 & 0 & -1 & 0 & -1 \end{bmatrix},$$ we can input it into Quirk, diagonalizing the matrix by adding some operations, then simplying them while hoping that the matrix of our interest isn’t created by any complex operations; “complex” means that it don’t contain rotation gates with odd angle parameters or any other kind of complicated transformations. In this case, we by luck retain the circuit for the mentioned matrix as follows
Deutsch-Jozsa oracle
As I want to implement and verify the Deutsch-Jozsa algorithm for various functions $f$, it’s not effective to construct gate-based oracles with each function reverse engineered. Now I want to illustrate a coding approach, more exploratory, that differs from my previous codes. I’ll use Python instead of Microsoft Q# to perform it.
Users are now allowed to input their own function $f$ as long as it’s either constant or balanced. The function maps each integer value from $0$ to $2^n$ to a binary value. For example, a function $f = [0,0,1,1]$ maps $00$ to $0$, $01$ to $0$, $10$ to $1$, and $11$ to $1$. This specific function is apparently balanced because the number of $0$ is equal the number of $1$. However, we’ll pretend not to know this and perform Deutsch-Jozsa algorithm to determine its constant-balanced property.
Manual coding
Since we cannot reconstruct operators equivalent to an arbitrary function, we have to implement the algorithm manually. First, I create the entry point for inputing and executing function.
|
|
|
|
|
|
Most important is creating a matrix $U_f$ for the entered $f$.
|
|
Let me elaborate on the above definition. Because of the ancilla qubit, the oracle is a $2^{n+1} \times 2^{n+1}$. We initial it as a zero matrix. Because all quantum states now is in the form of $|x_1x_1\dots x_n y\rangle$, we can get the input string $x_1x_1\dots x_n$ by shifting the state to the right by $1$ place. Next we can obtain $y$ by bitwise ANDing $x_1x_1\dots x_n y$ with $00\dots1$, then XOR $y$ with $f(x_1x_1\dots x_n)$ to get $y \oplus f(\mathbf{x})$. The result is pasted to the ancilla’s position by adding it with the input string shifted to the left $1$ place: $x_1x_1\dots x_n 0 + \left(y \oplus f(\mathbf{x})\right) = x_1x_1\dots x_n \left(y \oplus f(\mathbf{x}) \right)$. This is the state $|\mathbf{x}\rangle|y\oplus f(\mathbf{x})\rangle$ we want to create using the oracle. To finish, we assign U[input_state, output_state] = 1 to indicate that the “input state” is transformed into the “output state” with absolute certainty. The matrix $U$ is completed by the time we finish repeating the procedure traversing through all quantum states possibly created by $n+1$ qubits.
The final subroutine in our program is used for measuring and displaying result.
|
|
The definitions listed above are sufficient for us to perform the algorithm. You may need to see the circuit for the algorithm before getting your hand in. Please note that there are some trivial differences between the states on the circuit and in the implementation code.
|
|
It’s all done. Now let’s check if everything is okay.
It works as expected. Just free to experiment with other functions $f$ on yourself. Check out the source code archived in my GitHub directory.