Quantum Optimization example for Tensorflow Quantum and Pennylane

Patrick Huembeli
4 min readMar 16, 2020

--

After the introduction into a “Hello World” example in Cirq and Pennylane I would like to compare TFQ and Pennylane in an actual Quantum optimization task. Again I focus here on a simple task to get a bit a better understanding of the two packages. The task is to train a variational quantum circuit V(θ) to rotate the initial state |0000> into the equal superposition state ∑|σ>, where σ are all the possible qubit configurations.

One layer of the variational circuit contains a general rotation 𝑅(𝜙,𝜃,𝜔)=𝑅𝑧(𝜔)𝑅𝑧(𝜃)𝑅𝑧(𝜙) on each qubit, followed by entangling gates. We apply 2 layers and use 4 qubits.

Variational circuit V

For me it is somehow natural to try this example on any new platform. Simply to check, how the gradients and optimizers work and to get a bit of intuition for the platform. I was therefore quite surprised how difficult it was to implement this task in TFQ. I ended up doing the gradient descent update manually, because all their optimizers didn’t work without classical input data. (If someone has an easy solution, let me know.)
Two things that I realized when I compared the implementations in Pennylane and TFQ:

  • Pennylane’s documentation is very consistent and guides you through more and more complex examples. There is a learning curve.
  • This structure is not really given in TFQ. I did not figure out for example, how I can solve this task with the keras optimizers. Because e.g. the model.fit() function in their documentation is always used with classical data and it seems non-trivial to adapt this to a simple qubit rotation, where the loss only depends on the output.
  • I must admit, I did not spend very much time on solving this problem, because I did not expect it to be that tricky in TFQ. And I have not used TF in years.
  • TFQ will show its strength when we learn on classical data. It seems that it is mostly built around these task. We will see an example soon for this in my next post.

Due to this complications, the tfq code is a bit clunky and the pennylane ended up to be quite compact. In pennylane we are allowed to define arbitrary hermitian matrices, therefore we can define a density matrix of the target state and calculate its expectation value as a figure of merit, this gives us directly the overlap. For the complete code see my GitHub.

target_state = np.ones(2**4)/np.sqrt(2**4) # Def target state
density = np.outer(target_state, target_state)
@qml.qnode(dev1)
def circuit(params):
for j in range(2): # 2 layers
for i in range(4): # 4 qubits
qml.Rot(*params[j][i], wires=i)
qml.CNOT(wires=[0,1])
qml.CNOT(wires=[2,3])
qml.CNOT(wires=[1,2])
return qml.expval(qml.Hermitian(density, wires=[0,1,2,3]))

For the training we define a cost function

def cost(var):
return 1-circuit(var)

and with the following lines we can train the circuit.

# initialise the optimizer
opt = qml.GradientDescentOptimizer(stepsize=0.4)
# stepsize is the learning rate
# set the number of steps
steps = 100
# set the initial parameter values
params = init_params
for i in tqdm(range(steps)):
# update the circuit parameters
params = opt.step(cost, params)
if (i + 1) % 10 == 0:
print("Cost after step {:5d}: {: .7f}".format(i + 1, cost(params)))

That was already it. If we extract the wavefunction we should see a vector (1,1,1,1…) with some arbitrary global phase.

TFQ version

(UPDATE: The code in the GitHub repository was updated to a more keras friendly version. The original code for this blog post for TFQ can be found here. For more detail of the code check out my other post)

The TFQ version is in the end very similar

def generate_circuit(nr_of_qubits, layers):
qubits = cirq.GridQubit.rect(1, nr_of_qubits)
nr_parameters = 3*nr_of_qubits*layers
# 3 params for each qubit and layer
symb = sympy.symbols('theta0:'+str(nr_parameters))
symbols = np.array(symb)
symbols = symbols.reshape(layers, nr_of_qubits, 3)
circuit = cirq.Circuit()
for l in range(layers):
# Add a series of single qubit rotations.
for i, qubit in enumerate(qubits):
circuit += cirq.rz(symbols[l][i][0])(qubit)
circuit += cirq.rx(symbols[l][i][1])(qubit)
circuit += cirq.rz(symbols[l][i][2])(qubit)
circuit += cirq.CZ(qubits[0], qubits[1])
circuit += cirq.CZ(qubits[2], qubits[3])
circuit += cirq.CZ(qubits[1], qubits[2]

return circuit, list(symb)
nr_of_qubits = 4
layers = 2
tf_circuit, (symbols) = generate_circuit(nr_of_qubits, layers)
SVGCircuit(tf_circuit)

In Cirq we cannot define an arbitrary hermitain matrix as an observable therefore we need to define the operator 𝑀=1/4∗(𝑋1+𝑋2+𝑋3+𝑋4) if ⟨𝑀⟩=1 we are as well in the equal superposition state.

op = 1/4*(cirq.X(qubits[0]) + cirq.X(qubits[1]) 
+ cirq.X(qubits[2]) + cirq.X(qubits[3]))

Unfortunately for an arbitrary target state it would be difficult to calculate the overlap in TFQ.

The training routine is very similar to pennylane, with the big exception that I could not figure out, how to use the keras optimizers and I do the parameter update by hand. If someone knows a nicer solution send me a pull request on GitHub.

circuit_tensor = tfq.convert_to_tensor([tf_circuit])expectation = tfq.layers.Expectation()values_tensor = tf.convert_to_tensor(np.random.uniform(0, 2 * np.pi, (1, layers* nr_of_qubits*3 )).astype(np.float32))eta = 0.1for i in range(200):
with tf.GradientTape() as g:
g.watch(values_tensor)
forward = expectation(circuit_tensor,
operators=1-op,
symbol_names=symbols,
symbol_values=values_tensor)

if i%10==0:
print(forward.numpy()[0][0])
# Return variance of gradients across all circuits.
grads = g.gradient(forward, values_tensor)
values_tensor -= eta*grads

The key in this part is the function tfq.layers.Expectation(). One of its arguments is our circuit, which has to be converted to a tf tensor. Another argument is the operator that we want to optimize, which is in our case 1−M.
We also need to feed the list of symbols and their current values.

TF finds the grad of this circuit with respect to the parameters in ‘symbols’.

The gradient descent update rule is 𝜃←𝜃−𝜂∇𝑓(𝜃)

Conclusion

There is room to improve for the TFQ code. I am sure, there is a better solution than this but it might be useful to add something similar like this qubit rotation example to the documentation, because the optimization examples seem to be a bit too much focused on classical data processing, which is in my opinion only one part of QML.

--

--

Patrick Huembeli

I am postdoctoral reseracher in the Computational Quantum Science Lab at the École Polytechnique Fédérale de Lausanne (EPFL).