The final exercise on the QML track, included some bug-hunting for me, but this made it much more rewarding, to eventually solve it.
The basic task was conceivably easy: given some data, create a hamiltonian and use a QAOA optimization scheme to obtain a ground state approximation. The problem described some special graph covering problem. The mistakes that took some efforts to resolve where
- Mistake 1: PennyLane is exceptionally greedy, when arrays appear along the optimization scheme. The way I constructed the hamiltonian included numpy arrays for indexing qubits, PennyLane tried to differentiate the problem by them and vary them subsequently – that lead to exceptions, and it was a real pain to find the source of the error.
- Mistake 2: I tried to construct operators, instead of decomposing the occupation operator into Pauli matrices. This might work somehow, but I had an easier time decomposing the operators eventually.
- Mistake 3: I omitted the constant terms, the optimal configuration is not affected by the constant term, but my tests relied on the final energy.
The code for the Hamiltonian
def hamiltonian_coeffs_and_obs(graph):
"""Creates an ordered list of coefficients and observables used to construct
the UDMIS Hamiltonian.
Args:
- graph (list((float, float))): A list of x,y coordinates. e.g. graph = [(1.0, 1.1), (4.5, 3.1)]
Returns:
- coeffs (list): List of coefficients for elementary parts of the UDMIS Hamiltonian
- obs (list(qml.ops)): List of qml.ops
"""
num_vertices = len(graph)
E, num_edges = edges(graph)
u = 1.35
obs = []
coeffs = []
# single terms
for i in range(num_vertices):
obs.append(qml.PauliZ(wires=i))
coeffs.append(-.5)
# constant term
obs.append(qml.Identity(wires=0))
coeffs.append(-.5*num_vertices)
# interaction terms
pairs = np.argwhere(E>0)
for pair in pairs:
i = int(pair[0])
j = int(pair[1])
obs.append(qml.PauliZ(wires=i) @ qml.PauliZ(wires=j))
coeffs.append(.25*u)
coeffs[i] += .25*u
coeffs[j] += .25*u
coeffs[num_vertices] += .25*u
return coeffs, obs
The tests check out, success!