After implementing the basic level task of creating a 2×2 diagonal unitary operator (i.e. only possible values in the diagonal are +1 and -1), today I am generalizing the algorithm to create arbitrary diagonal unitary operators, with complex eigenvalues. For this purpose I used the multi-controlled P-gate, which differs slightly from the RZ-gate:
\[
RZ(\phi) =
\left(
\begin{array}{rr}
e^{-i \frac{\phi}{2}} & 0 \\
0 & e^{i \frac{\phi}{2}}
\end{array}
\right)
\text{, and} \\
P(\phi) =
\left(
\begin{array}{rr}
1 & 0 \\
0 & e^{i \phi}
\end{array}
\right)
\]
In code, most remained the same to last day’s solution, I just had to modify the marking function from a CZ to a multi-controlled P-gate.
import pennylane as qml
from pennylane import numpy as np
num_wires = 3
dev = qml.device("default.qubit", wires=num_wires)
def prepare_address(address):
"""Flip bits, to prepare addressation by oracle, or the like.
"""
bin_address = np.binary_repr(address, width=num_wires)
for i, addr_bit in enumerate(bin_address):
if addr_bit == "0":
qml.PauliX(i)
@qml.qnode(dev)
def prepare_unitary(u_eigenvalues):
"""Prepare a circuit, that realizes a unitary transformation, with a diagonal matrix representation, with the provided eigenvalues.
Args:
- u_eigenvalues (np.array(complex)): Array that holds a list of unitary eigenvalues of the form np.exp(1.j*alpha), i.e. complex, norm 1
Return:
- qml.State
"""
assert np.all(np.isclose(np.absolute(u_eigenvalues), 1.)), "Invalid eigenvalues provided, do not have norm of 1"
angles = np.angle(u_eigenvalues)
for i, angle in enumerate(angles):
prepare_address(i)
qml.ctrl(qml.PhaseShift(angle, num_wires-1), range(num_wires-1))
prepare_address(i)
return qml.state()
def adjust_global_phase_of_diag_unitary(trg_arr, ref_value=1.+0.j):
"""Rotate the target diagonal matrix such, that it's global aligns with that of a source matrix.
If no source matrix is provided, the target matrix is rotated such, that the first element is real.
Args:
- trg_arr (np.array): approx. diagonalized unitary matrix (i.e. the norm of the diagonal elements approximate to 1, all other elements approx. to 0)
- ref_value (complex): the reference value, holding the target phase; defaults to 1.+0.j
Return:
"""
assert np.all(np.isclose(np.absolute(trg_arr), np.identity(len(trg_arr)))), "Invalid diagonalized unitary provided."
assert np.isclose(np.absolute(ref_value), 1.), "Invalid reference value provided; must be complex with norm 1"
angle_trg = np.angle(trg_arr.flat[0])
angle_src = np.angle(ref_value)
return trg_arr * np.exp(1.j*(angle_src - angle_trg))
.. and a small adjustment, to the test data + checking procedure.
# prepare test data
rng = np.random.default_rng(seed=314159)
rand_angles = rng.random(2**num_wires) * 2. * np.pi
eigenvalues = np.exp(1.j*rand_angles)
get_matrix = qml.matrix(prepare_unitary)
matrix = get_matrix(eigenvalues)
phase_adjusted_matrix = adjust_global_phase_of_diag_unitary(matrix, eigenvalues[0])
result_str = "correct!" if np.all(np.isclose(phase_adjusted_matrix, np.diagflat(eigenvalues))) else "INCORRECT! "
print("The result is", result_str)
The tests check out, all fine. It remains to exchange the MCP-gate with a series of two qubit gates to solve the challenge in it’s entirety.