QHack 2023: 4 week prep-challenge, day 23, Draper adder

I went for another QOSF task (Oct. 2022), there I tackled the seemingly easy task of implementing a Draper adder, as an intermediate step towards a multiplier. However, I got lost in index engineering. At this point, I am not satisfied with the solution, but the tests check out, so it is usable.

I added two quantum registers; instead of the much easier multiplication of a quantum with a classical register.

import pennylane as qml
from pennylane import numpy as np

reg_size = 4
n_wires = 2*reg_size

dev = qml.device("default.qubit", wires=n_wires)

def base_encode(a, wires_offset=0):
    a_bin = np.binary_repr(a, reg_size)
    a_bin = a_bin if wires_offset else reversed(a_bin)
    
    for i, ai in enumerate(a_bin):
        if ai == "1":
            qml.PauliX(i + wires_offset)

@qml.qnode(dev)
def circuit(a, b):
    
    # encode basis state
    base_encode(a)
    base_encode(b, wires_offset=reg_size)
    
    # apply QFT to one register
    qml.QFT(wires=range(reg_size, n_wires))
    
    # in this implementation, I pretend that I do not know the value of b, 
    # this could be the case, if this addition was amidst a longer calculation
    for i in range(reg_size): # ith significant bit (target)
        for j in range(reg_size-i): # control bits
            trgt_idx = n_wires - 1 - i
            ctrl_idx = trgt_idx - reg_size - j
            angle = np.pi/(2**j)
            qml.ControlledPhaseShift(angle, wires=[ctrl_idx, trgt_idx])
    
    # invert QFT
    qml.adjoint(qml.QFT)(wires=range(reg_size, n_wires))
    
    return qml.probs(wires=range(reg_size, n_wires))# [qml.expval(qml.PauliZ(i)) for i in range(reg_size, n_wires)]

As already mentioned, I wouldn’t trust the implementation, if the tests didn’t check out, but they do.

circ_draw = qml.draw(circuit)

res = np.array([])
for a in range(reg_size**2 - 1):
    for b in range(reg_size**2 - 1 -a):
        sample = circuit(a, b)
        expected_result = a+b
        if np.argmax(sample)!=expected_result:
            print(f"a: {a}, b: {b}, sample: {np.argmax(sample)}")
            print(circ_draw(a, b))
            break
        
        res = np.append(res, [np.argmax(sample)==a+b])
        
print(f"The algorithm is correct! {len(res)} tests successful!" if np.all(res) else "There is an error")