Pennylane
Xanadu's framework for quantum machine learning and differentiable quantum computing
Quick install
pip install pennylane Background and History
PennyLane was created by Xanadu, a Canadian quantum computing company founded in 2016 by Christian Weedbrook. The framework was first released in November 2018 with a specific focus that distinguished it from existing tools: differentiable quantum computing. PennyLane treats quantum circuits as differentiable computational graphs, enabling automatic gradient computation through quantum operations using techniques like the parameter-shift rule. This design made it the first framework purpose-built for quantum machine learning and variational quantum algorithms.
Xanadu’s initial research focus was photonic quantum computing, and PennyLane was designed to be hardware-agnostic from the outset. Its plugin architecture allows the same quantum circuit code to run on backends from IBM (via pennylane-qiskit), Google (via pennylane-cirq), Amazon (via pennylane-braket), and Xanadu’s own photonic hardware. This cross-platform approach filled an important gap in the ecosystem, since most other frameworks at the time were tightly coupled to a single vendor’s hardware.
PennyLane’s integration with classical machine learning frameworks has been a central feature. It supports automatic differentiation through NumPy (via autograd), PyTorch, TensorFlow, and JAX, allowing quantum circuits to be embedded as layers within standard deep learning pipelines. The lightning.qubit and lightning.gpu high-performance simulators, developed alongside PennyLane, provide fast local execution for development and research.
The project has grown steadily since its release. As of 2025, PennyLane has over 2,200 GitHub stars and a large library of tutorials, demos, and educational content hosted at pennylane.ai. Xanadu has invested heavily in community building, running the QHack quantum hackathon series and maintaining an active discussion forum. PennyLane is widely used in academic quantum machine learning research and has become one of the most commonly taught quantum frameworks in university courses alongside Qiskit. Development continues at a rapid pace, with frequent releases adding new features, optimizations, and hardware backend support.
Installation
pip install pennylane
Backend plugins (optional - PennyLane’s default.qubit covers most needs):
pip install pennylane-qiskit # IBM backends
pip install amazon-braket-default-simulator pennylane-amazon-braket
pip install pennylane-lightning-gpu # GPU-accelerated
Key Imports
import pennylane as qml
import pennylane.numpy as np # use this instead of plain numpy for gradients
Core Concepts
PennyLane differs from Qiskit/Cirq in a key way: circuits are defined inside Python functions decorated with @qml.qnode, and they support automatic differentiation for training.
Device
A device is the backend that executes your circuit - simulator or real hardware.
dev = qml.device('default.qubit', wires=2) # built-in simulator
dev = qml.device('default.qubit', wires=['a', 'b']) # named wires
dev = qml.device('lightning.qubit', wires=4) # fast C++ simulator
dev = qml.device('qiskit.aer', wires=2) # Qiskit Aer backend
QNode
A QNode wraps a quantum function and binds it to a device. This is PennyLane’s equivalent of a circuit + execution combined.
@qml.qnode(dev)
def circuit(params):
qml.RY(params[0], wires=0)
qml.CNOT(wires=[0, 1])
return qml.expval(qml.PauliZ(0))
result = circuit([0.5])
Gates
qml.Hadamard(wires=0) # H
qml.PauliX(wires=0) # X (NOT)
qml.PauliY(wires=0) # Y
qml.PauliZ(wires=0) # Z
qml.S(wires=0) # S gate
qml.T(wires=0) # T gate
qml.CNOT(wires=[0, 1]) # Controlled-NOT
qml.CZ(wires=[0, 1]) # Controlled-Z
qml.SWAP(wires=[0, 1]) # SWAP
qml.Toffoli(wires=[0, 1, 2]) # Toffoli
# Rotation gates (angles in radians)
qml.RX(theta, wires=0)
qml.RY(theta, wires=0)
qml.RZ(theta, wires=0)
# Arbitrary single-qubit unitary
qml.Rot(phi, theta, omega, wires=0) # Z-Y-Z Euler decomposition
Measurements
PennyLane measurements are return values from QNodes.
return qml.expval(qml.PauliZ(0)) # expectation value
return qml.var(qml.PauliZ(0)) # variance
return qml.probs(wires=[0, 1]) # probability distribution
return qml.sample(qml.PauliZ(0)) # raw samples
return qml.counts(wires=[0, 1]) # measurement counts
return qml.state() # full statevector
Gradients
This is PennyLane’s main advantage - circuits are differentiable.
# Automatic gradient via parameter-shift rule
grad_fn = qml.grad(circuit)
gradient = grad_fn([0.5])
# JAX interface
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev, interface='jax')
def circuit(params):
qml.RY(params[0], wires=0)
return qml.expval(qml.PauliZ(0))
import jax
gradient = jax.grad(circuit)(jnp.array([0.5]))
Variational Quantum Eigensolver (VQE)
import pennylane as qml
from pennylane import numpy as np
# Define a Hamiltonian
H = qml.Hamiltonian(
[0.5, 0.5],
[qml.PauliZ(0), qml.PauliZ(1)]
)
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev)
def ansatz(params):
qml.RY(params[0], wires=0)
qml.RY(params[1], wires=1)
qml.CNOT(wires=[0, 1])
return qml.expval(H)
params = np.array([0.1, 0.2], requires_grad=True)
opt = qml.GradientDescentOptimizer(0.1)
for _ in range(100):
params = opt.step(ansatz, params)
print("Ground state energy:", ansatz(params))
Templates
PennyLane provides pre-built circuit templates.
qml.templates.BasicEntanglerLayers(weights, wires=range(4))
qml.templates.StronglyEntanglingLayers(weights, wires=range(4))
qml.templates.AngleEmbedding(features, wires=range(4))
qml.templates.AmplitudeEmbedding(features, wires=range(4), normalize=True)
qml.templates.QAOAEmbedding(features, weights, wires=range(4))
Common Patterns
Bell state
dev = qml.device('default.qubit', wires=2)
@qml.qnode(dev)
def bell():
qml.Hadamard(wires=0)
qml.CNOT(wires=[0, 1])
return qml.probs(wires=[0, 1])
print(bell()) # [0.5, 0, 0, 0.5]
Circuit drawing
print(qml.draw(circuit)([0.5]))
qml.draw_mpl(circuit)([0.5]) # matplotlib figure
Quantum transfer learning
@qml.qnode(dev)
def hybrid_model(x, weights):
qml.AmplitudeEmbedding(x, wires=range(4), normalize=True)
qml.BasicEntanglerLayers(weights, wires=range(4))
return qml.expval(qml.PauliZ(0))