QHack 2023: 4 week prep challenge, day 11, A Quantum Adder (QFT)

Today, I’m continuing with the next exercise from the algorithms track. The task is to build a number adder using the Fourier space.

Firstly, a Fourier transform is applied on the initial state, i.e. a state |0> or |1> in the computational basis is flipped into the X-Y-plane. The qubits are then rotated around the Z-axis, with an angle according to the number to add. Eventually, the state is transformed back from the Fourier space.

The transformation rule for the QFT reads

\[ QFT|x\rangle = \frac{1}{\sqrt{M}} \sum^{M-1}_{k=0} \exp(\frac{2 \pi i}{M} x k) |k\rangle \]

If you now want to add to a state in the Fourier space, you have to rotate the qubits accordingly. It is important to notice, that a rotation only effects the |1> portion, but not the |0> portion of a qubit’s state.

Having that in mind, it was relatively straight forward to come up with the solution…

def qfunc_adder(m, wires):
    """Quantum function capable of adding m units to a basic state given as input.

    Args:
        - m (int): units to add.
        - wires (list(int)): list of wires in which the function will be executed on.
    """

    qml.QFT(wires=wires)

    for w, _ in enumerate(wires):
        angle = m*np.pi/(2**w)
        qml.RZ(angle, wires=w)

    qml.QFT(wires=wires).inv()

… the tests check out, success!

QHack 2023: 4 week prep challenge, day 2, Composing Hamiltonians

Continuing on the quantum chemistry problem path, another entry level challenge. One takeaway from today’s exercise: when you are a beginner, you can even learn from the very easy problems. The problem posed no real challenge to solve it, but I learned how complex cost functions (a main ingredient in VQE algorithms) are computed.

Running the circuit multiple times, and doing the simple accounting tasks on the classical device – it’s so simple, of course! The Xanadu team provided a wonderful drawing as always

Complex cost functions, can be readily calculated by running the algorithm multiple times and doing the arithmetic on a classical device. Image taken from the exercise instructions.

Running a quantum algorithm is costly, so you want to minimize the number of runs by combining measurements. Non-intersecting measurements, e.g. <X1> and <Z2> can be performed at the same time. Now this exercise asks to do just that, compress a hamiltonian with some number of additive terms into a smaller set of measurements.

A very simple greedy procedure is provided as a base. This approach is simple enough, not globally optimal, but I guess there are more advanced techniques in the Pennylane codebase.

Compression rules

Two measurements are compatible, if they either measure the same Pauli operators, or at least one of them is a null-measurement, i.e. the identity operator.

def check_simplification(op1, op2):
"""As we have seen in the problem statement, given two Pauli operators, you could obtain the expected value
of each of them by running a single circuit following the two defined rules. This function will determine whether,
given two Pauli operators, such a simplification can be produced.
Args:
    - op1 (list(str)): First Pauli word (list of Pauli operators), e.g., ["Y", "I", "Z", "I"].
    - op2 (list(str)): Second Pauli word (list of Pauli operators), e.g., ["Y", "I", "X", "I"].

Returns:
    - (bool): 'True' if we can simplify them, 'False' otherwise. For the example args above, the third qubit does not allow simplification, so the function would return `False`.
"""

result = True

for i, op in enumerate(op1):
    if op == op2[i] or op == "I" or op2[i] == "I":
        continue
    else:
        result = False
        break

return result

Compression rate

The compression rate was calculated as percentage improved over initial measurement count.

def compression_ratio(obs_hamiltonian, final_solution):
    """Function that calculates the compression ratio of the procedure.

    Args:
        - obs_hamiltonian (list(list(str))): Groups of Pauli operators making up the Hamiltonian.
        - final_solution (list(list(str))): Your final selection of observables.

    Returns:
        - (float): Compression ratio your solution.
    """

    return 1. - len(final_solution)/len(obs_hamiltonian)