“Hello world” in Pennylane and Tensorflow-Quantum compared

Patrick Huembeli
4 min readMar 15, 2020

--

Google releases a few days ago its tensorflow package for quantum machine learning tf-quantum. The main idea behind this package is to integrate quantum circuit (QC) nodes into classical machine learning (ML), to train the the whole hybrid QC-ML combination together. Since Xanadu was already doing something very similar with its quantum machine learning (QML) platform pennylane I would like to give a parallel introduction into the two packages and show how easy it already is to do QML.

This post is a very basic introduction for a “Hello world” example for both platforms. Further blog post about QML applications will follow. All the code can be found on my GitHub.

Hello quantum world

Let’s start with a simple “Hello world” example for quantum computing. It is not yet very well defined, what the quantum print("Hello world")example should be. Therefore, I just choose a rather arbitrary task which is to define a quantum circuit with 2 qubits, with a RX(θ) rotations on each qubit, followed by a CNOT gate. This quantum circuit will be measured in the computational basis, the expectation value will be evaluated and the wavefunction should be returned.

Hello world circuit drawn by Pennylane-Qiskit on the left and Cirq on the right

I will show here only snippets of the code. If you want to see the full code, go to this GitHub page.

Since Tensorflow Quantum (tfq) does all the quantum part on google’s own quantum platform Cirq, the “Hello world” example can be done directly in Cirq, without using tfq. Pennylane on the other side is hardware agnostic and we are free to choose which device we want to use. Therefore in pennylane we can just define a general circuit, totally independent of the device or the platform.

def circuit(params):
qml.RX(params[0], wires=0)
qml.RX(params[1], wires=1)
qml.CNOT(wires=[0,1])
return qml.expval(qml.PauliZ(0))

And the device is defined independently:

dev1 = qml.device("qiskit.aer", wires=2)
dev2 = qml.device("default.qubit", wires=2)

Here we define the qiskit.aer and the default.qubit device as an example.

In Cirq we simply stick to the Cirq syntax:

x1, x2 = sympy.symbols('x1 x2')
q0, q1 = cirq.GridQubit.rect(1,2) # Define two qubits on grid
# Define a circuit
tf_circuit = cirq.Circuit(
cirq.rx(x1).on(q0),
cirq.rx(x2).on(q1),
cirq.CNOT(control=q0, target=q1)
)

Cirq allows us to use sympy symbolic variables, which is a nice feature, but probably not very useful, if we have circuits of dozens of parameters.

Run a quantum circuit

In Pennylane we evaluate a circuit for a certain set of parameters by defining a QNode and running it. A QNode is simply a circuit function paired with a device.

q_circuit = qml.QNode(circuit, dev1) # define QNode
params = np.array([0.5, -0.5]) # define parameters
q_circuit(params) # run circuit with given params

Dependent on how we defined the circuit, this can return the expectation value of a measurement, or the actual measurements. If the circuit() function returns qml.expval() we get the expectation value foor the actual measurement outcomes we return qml.sample(). The wavefunction can be accessed via the device dev.state.

In Cirq we basically do the same. We have to define a simulator and a resolver, which converts the symbolic parameters into real values.

simulator = cirq.Simulator()
resolver = cirq.ParamResolver({x1: 0.5, x2: -0.5})

We first run the resolver to fix the parameters. We add the output that we wish to observe (here it is a measurement of the qubit q0) and then we run the simulator.

resolved_circuit = cirq.resolve_parameters(tf_circuit, resolver) 
resolved_circuit.append(cirq.measure(q0, key='some_key'))
results = simulator.run(resolved_circuit, repetitions=100)

If we want to obtain the wavefunction instead of measurements, we simply change the simulator from simulator.run() to simulator.simulate().

A nice feature from Cirq is that the observable can be easier changed than in pennylane. Remember, in pennylane we have to redefine the circuit() every time when we want to return a different observable. In Cirq we just to the following: Simulate the the state vector, define an observable / measurement direction, define which qubits are involved and calculate the expectation value.

output_state_vector = simulator.simulate(tf_circuit, resolver).final_state

z0 = cirq.X(q0) # Define measurement direction
qubit_map = {q0: 0, q1: 1} # Define which qubits are involved inz0.expectation_from_wavefunction(output_state_vector, qubit_map).real

Conclusion

So far the two platforms are very similar. And I also must say that we have not used any part of TFQ. I basically compared the Hello world example of Pennylane with the one in Cirq. Furthermore Pennylane could also work with Cirq as a backend.

Nevertheless, there are some differences.

  • Pennylane can run on any quantum computing platform, which is a really nice feature, if you are interested in trying out other hardware. The change between platforms is one line of code, which makes it amazingly easy to change.
  • We have not done any optimization yet, but in the next article you can find an introduction into a simple QML problem and Pennylane can be used with pytorch and tensorflow. So there is no restriction on that side.
  • Cirq has the advantage of being a specialized tool. Small features like sympy support and their native circuit drawing function make it very attractive to use.
  • Cirq also has a slight advantage because it is simpler to change the quantum node after defining it. I can just change the measurement for example, where for Pennylane I have to redefine the circuit. This might be relevant if we want to optimize not only the measurement outcome, but also the measurement direction.
  • If someone knows how I can change the return qml.expval() of a QNode in an easier way than by redefining the circuit() please let me know. :)

--

--

Patrick Huembeli

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