PyTorch interface

Module name: pennylane.interfaces.torch

Warning

This interface is experimental. If you find any bugs, please report them on our GitHub issues page: https://github.com/XanaduAI/pennylane

Using the PyTorch interface

Note

To use the PyTorch interface in PennyLane, you must first install PyTorch.

Using the PyTorch interface is easy in PennyLane — let’s consider a few ways it can be done.

Via the QNode decorator

The QNode decorator is the recommended way for creating QNodes in PennyLane. The only change required to construct a PyTorch-capable QNode is to specify the interface='torch' keyword argument:

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, interface='torch')
def circuit(phi, theta):
    qml.RX(phi[0], wires=0)
    qml.RY(phi[1], wires=1)
    qml.CNOT(wires=[0, 1])
    qml.PhaseShift(theta, wires=0)
    return qml.expval.PauliZ(0), qml.expval.Hadamard(1)

The QNode circuit() is now a PyTorch-capable QNode, accepting torch.tensor objects as input, and returning torch.tensor objects. Subclassing from torch.autograd.Function, it can now be used like any other PyTorch function:

>>> phi = torch.tensor([0.5, 0.1])
>>> theta = torch.tensor(0.2)
>>> circuit(phi, theta)
tensor([0.8776, 0.6880], dtype=torch.float64)

Via the QNode class

Sometimes, it is more convenient to instantiate a QNode object directly, for example, if you would like to reuse the same quantum function across multiple devices, or even using different classical interfaces:

dev1 = qml.device('default.qubit', wires=2)
dev2 = qml.device('forest.wavefunction', wires=2)

def circuit(phi, theta):
    qml.RX(phi[0], wires=0)
    qml.RY(phi[1], wires=1)
    qml.CNOT(wires=[0, 1])
    qml.PhaseShift(theta, wires=0)
    return qml.expval.PauliZ(0), qml.expval.Hadamard(1)

qnode1 = qml.QNode(circuit, dev1)
qnode2 = qml.QNode(circuit, dev2)

We can convert the default NumPy-interfacing QNode to a PyTorch-interfacing QNode by using the to_torch() method:

>>> qnode1_torch = qnode1.to_torch()
>>> qnode1_torch
<QNode: device='default.qubit', func=circuit, wires=2, interface=PyTorch>

Internally, the to_torch() method uses the TorchQNode() function to do the conversion.

Quantum gradients using PyTorch

Since a PyTorch-interfacing QNode acts like any other torch.autograd.Function, the standard method used to calculate gradients with PyTorch can be used.

For example:

import pennylane as qml
import torch
from torch.autograd import Variable

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, interface='torch')
def circuit(phi, theta):
    qml.RX(phi[0], wires=0)
    qml.RY(phi[1], wires=1)
    qml.CNOT(wires=[0, 1])
    qml.PhaseShift(theta, wires=0)
    return qml.expval.PauliZ(0)

phi = Variable(torch.tensor([0.5, 0.1]), requires_grad=True)
theta = Variable(torch.tensor(0.2), requires_grad=True)
result = circuit(phi, theta)

Now, performing the backpropagation and accumulating the gradients:

>>> result.backward()
>>> phi.grad
tensor([-0.4794,  0.0000])
>>> theta.grad
tensor(-5.5511e-17)

Optimization using PyTorch

To optimize your hybrid classical-quantum model using the Torch interface, you must make use of the PyTorch provided optimizers, or your own custom PyTorch optimizer. The PennyLane optimizers cannot be used with the Torch interface, only the NumPy interface.

For example, to optimize a Torch-interfacing QNode (below) such that the weights x result in an expectation value of 0.5, with the classical nodes processed on a GPU, we can do the following:

import torch
from torch.autograd import Variable
import pennylane as qml

dev = qml.device('default.qubit', wires=2)

@qml.qnode(dev, interface='torch')
def circuit(phi, theta):
    qml.RX(phi[0], wires=0)
    qml.RZ(phi[1], wires=1)
    qml.CNOT(wires=[0, 1])
    qml.RX(theta, wires=0)
    return qml.expval.PauliZ(0)

def cost(phi, theta):
    return torch.abs(circuit(phi, theta) - 0.5)**2

phi = Variable(torch.tensor([0.011, 0.012], device='cuda'), requires_grad=True)
theta = Variable(torch.tensor(0.05, device='cuda'), requires_grad=True)

opt = torch.optim.Adam([phi, theta], lr = 0.1)

steps = 200

def closure():
    opt.zero_grad()
    loss = cost(phi, theta)
    loss.backward()
    return loss

for i in range(steps):
    opt.step(closure)

The final weights and circuit value are:

>>> phi_final, theta_final = opt.param_groups[0]['params']
>>> phi_final, theta_final
(tensor([0.7345, 0.0120], device='cuda:0', requires_grad=True), tensor(0.8316, device='cuda:0', requires_grad=True))
>>> circuit(phi_final, theta_final)
tensor(0.5000, device='cuda:0', dtype=torch.float64, grad_fn=<_TorchQNodeBackward>)

Note

For more advanced PyTorch models, Torch-interfacing QNodes can be used to construct layers in custom PyTorch modules (torch.nn.Module).

See https://pytorch.org/docs/stable/notes/extending.html#adding-a-module for more details.

Code details

_get_default_args(func)[source]

Get the default arguments of a function.

Parameters:func (function) – a valid Python function
Returns:dictionary containing the argument name and tuple (positional idx, default value)
Return type:dict
args_to_numpy(args)[source]

Converts all Torch tensors in a list to NumPy arrays

Parameters:args (list) – list containing QNode arguments, including Torch tensors
Returns:returns the same list, with all Torch tensors converted to NumPy arrays
Return type:list
kwargs_to_numpy(kwargs)[source]

Converts all Torch tensors in a dictionary to NumPy arrays

Parameters:args (dict) – dictionary containing QNode keyword arguments, including Torch tensors
Returns:returns the same dictionary, with all Torch tensors converted to NumPy arrays
Return type:dict
TorchQNode(qnode)[source]

Function that accepts a QNode, and returns a PyTorch-compatible QNode.

Parameters:qnode (QNode) – a PennyLane QNode
Returns:the QNode as a PyTorch autograd function
Return type:torch.autograd.Function