How to build a model with chained QNodes

Josh Izaac (PennyLane team)

In PennyLane, we can easily construct VQE and QAOA-like models. However, the flexibility of PennyLane allows much more interesting models to be constructed. In this how-to, we’ll show you how to chain two QNodes together.

The ‘standard-bearer’ variational quantum algorithm, the Variational Quantum Eigensolver, kind of looks like this, if we were to draw it:

So even though we are executing multiple quantum functions (or ‘QNodes’) to compute the cost function, there is a lot of structure to the model:

  • All QNodes have the same quantum circuit (or ‘ansatz‘).
  • All QNodes take the same parameter values, and are executed in parallel.
  • For each QNode, a different measurement is performed. A linear combination of the measurement values is then taken to compute the final cost function value.

This model structure is so common across variational quantum algorithms that we even have a function available in PennyLane to easily construct such hybrid quantum-classical models: qml.ExpvalCost (check out the docstring for some examples).

However, PennyLane does not restrict you to models of this form; you can play around and construct models that arbitrarily mix quantum and classical processing! Here, lets create a simple model that does two things:

  1. It uses two QNodes, evaluated on different devices.
  2. The output of one QNode is used as the input to the other QNode.

Once our simple chained QNode model is created, we can include classical processing, and then train the model using gradient descent.

Let’s create our first QNode. For this QNode, lets use Amazon Braket, courtesy of the PennyLane-Braket plugin: 1

import pennylane as qml
from pennylane import numpy as np

device_braket = qml.device("braket.local.qubit", wires=2, shots=1000)

def circuit_qubit(params):
    qml.RX(params[0], wires=0)
    qml.RY(params[1], wires=1)
    qml.CNOT(wires=[0, 1])
    return qml.expval(qml.PauliY(1))

Evaluating this QNode:

>>> circuit_qubit([0.2, 0.3])
tensor(0.058, requires_grad=True)

Next, we’ll create another QNode that runs on the default.gaussian simulator. This is a photonic simulator, and as such, it uses a slightly different gate set compared to qubit devices. Instead of Pauli rotations and CNOT gates, we instead use squeezers and beamsplitters!

dev_photonic = qml.device("default.gaussian", wires=2)

def circuit_photonics(x, y):
    qml.Squeezing(x, 0, wires=0)
    qml.Beamsplitter(y, 0, wires=[0, 1])
    return qml.expval(qml.NumberOperator(0))

Let’s evaluate this QNode:

>>> circuit_photonics(0.2, 0.6)

Now, let’s build a cost function where the qubit circuit running on Braket is first evaluated, exponentiated, and then used as the input to the photonic circuit:

def cost(params):
    out1 = np.exp(circuit_qubit(params))
    out2 = circuit_photonics(out1, out1 ** 3)
    return np.sum(out2 - out1)

We can now evaluate this cost function, and use PennyLane to compute the gradient:

>>> params = np.array([0.2, 0.1, 0.3], requires_grad=True)
>>> cost(params)
tensor(-0.89920575, requires_grad=True)
>>> qml.grad(cost)(params)
array([-0.03401907,  0.07144004,  0.        ])

There you go — a hybrid quantum-classical model a bit more complicated than the style used in the VQE! Have a go playing around; what type of models can you make using PennyLane?

  1. If you don’t have access to Amazon Braket, feel free to replace this device with any other PennyLane-compatible device, such as default.qubit.

Tags: gradients, autograd, qml
Last modified: 13:04, 15 Apr 2021