QHack 2023: 4 week prep-challenge, day 22, Constructing diagonal operators, pt. 2

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.