The latest release of PennyLane is now out and available for everyone to use. It comes with powerful new additions, such as support for circuit compilation routines, new gradient transforms, and a quantum device resource tracker. It also contains improvements and quality-of-life updates, including provided Docker images, new templates, and improved optimizers.
Quantum circuit optimization using compilation transforms
PennyLane can now perform quantum circuit optimization using the top-level transform qml.compile
. The compile transform allows you to chain together sequences of specific transforms into custom circuit optimization pipelines.
The following optimization transforms are now available to use, either independently or within a compilation pipeline:
commute_controlled
: push commuting single-qubit gates through controlled operationscancel_inverses
: remove adjacent pairs of operations that cancel outmerge_rotations
: combine adjacent rotation gates of the same type into a single gatesingle_qubit_fusion
: convert each sequence of single-qubit operations into a single Rot gate
The default qml.compile
behaviour shown below applies a single sequence of commute_controlled
, cancel_inverses
, and merge_rotations
, though users can supply an ordered list of transforms to the pipeline
keyword.
dev = qml.device('default.qubit', wires=[0, 1, 2]) @qml.qnode(dev) @qml.compile() def qfunc(x, y, z): qml.Hadamard(wires=0) qml.Hadamard(wires=1) qml.Hadamard(wires=2) qml.RZ(z, wires=2) qml.CNOT(wires=[2, 1]) qml.RX(z, wires=0) qml.CNOT(wires=[1, 0]) qml.RX(x, wires=0) qml.CNOT(wires=[1, 0]) qml.RZ(-z, wires=2) qml.RX(y, wires=2) qml.PauliY(wires=2) qml.CZ(wires=[1, 2]) return qml.expval(qml.PauliZ(wires=0))
Using qml.compile()
, the above QNode is compiled into the following circuit:
>>> print(qml.draw(qfunc)(0.2, 0.3, 0.4)) 0: ──H───RX(0.6)───────────────────┤ ⟨Z⟩ 1: ──H──╭X─────────────────╭CY─────┤ 2: ──H──╰C────────RX(0.3)──╰CY──Y──┤
You can read more about these transformations in the compilation documentation.
Faster and more intuitive VQE simulations
You can now leverage sparse Hamiltonians, and gain significant speed-ups in your simulations! Furthermore, the expectation values of Hamiltonians can be directly returned in your quantum circuits:
dev = qml.device("default.qubit", wires=2) H = qml.Hamiltonian([1., 2., 3.], [qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)]) w = qml.init.strong_ent_layers_uniform(1, 2, seed=1967) @qml.qnode(dev) def circuit(w): qml.templates.StronglyEntanglingLayers(w, wires=range(2)) return qml.expval(H) >>> print(circuit(w)) -1.5133943637878295 >>> print(qml.grad(circuit)(w)) [[[-8.32667268e-17 1.39122955e+00 -9.12462052e-02] [ 1.02348685e-16 -7.77143238e-01 -1.74708049e-01]]]
QNodes are even more powerful
- Return samples in the computational basis directly via
qml.sample()
in a QNode:
dev = qml.device("default.qubit", wires=2, shots=5) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.CNOT(wires=[0, 1]) return qml.sample() >>> circuit() array([[0, 0], [1, 1], [0, 0], [0, 0], [1, 1]])
- Operations that have been instantiated elsewhere can now be easily added to QNodes and other queuing contexts using qml.apply:
op = qml.RX(0.4, wires=0) dev = qml.device("default.qubit", wires=2) @qml.qnode(dev) def circuit(x): qml.RY(x, wires=0) qml.apply(op) return qml.expval(qml.PauliZ(0))
Device Resource Tracker
Use the new device tracker to track executions of a QNode, even when computing parameter-shift gradients. This functionality will improve the ease of monitoring large batches and remote jobs by providing easy access to the number of executions, batches, shots, and more.
dev = qml.device('default.qubit', wires=1, shots=100) @qml.qnode(dev, diff_method="parameter-shift") def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) x = np.array(0.1) with qml.Tracker(circuit.device) as tracker: qml.grad(circuit)(x) >>> tracker.totals {'executions': 3, 'shots': 300, 'batches': 1, 'batch_len': 2} >>> tracker.history {'executions': [1, 1, 1], 'shots': [100, 100, 100], 'batches': [1], 'batch_len': [2]} >>> tracker.latest {'batches': 1, 'batch_len': 2}
Custom callback functions can also be provided; these callback functions are called whenever the tracker is updated. This makes it possible to monitor remote jobs or large parameter-shift batches.
>>> def shots_info(totals, history, latest): ... print("Total shots: ", totals['shots']) >>> with qml.Tracker(circuit.device, callback=shots_info) as tracker: ... qml.grad(circuit)(0.1) Total shots: 100 Total shots: 200 Total shots: 300 Total shots: 300
Docker containerization support
Getting started using or contributing to PennyLane is now even easier with provided Docker PennyLane images. There’s support for all interfaces (TensorFlow, Torch, and Jax), as well as plugins and QChem.
In addition, both CPU and GPU (Nvidia CUDA 11.1+) images are provided. See a more detailed description here.
Module for differentiable quantum gradient transforms
A new gradients module qml.gradients
has been added, providing the new quantum gradient transforms:
qml.gradients.finite_diff
qml.gradients.param_shift
qml.gradients.param_shift_cv
These quantum gradient transforms act directly on low-level quantum tape datastructures, returning tapes to be executed on hardware, and a corresponding post-processing function.
>>> params = np.array([0.3,0.4,0.5], requires_grad=True) >>> with qml.tape.JacobianTape() as tape: ... qml.RX(params[0], wires=0) ... qml.RY(params[1], wires=0) ... qml.RX(params[2], wires=0) ... qml.expval(qml.PauliZ(0)) ... qml.var(qml.PauliZ(0)) >>> tape.trainable_params = {0, 1, 2} >>> gradient_tapes, fn = qml.gradients.param_shift(tape)
These gradient tapes can be executed on quantum devices and post-processed to compute the gradient:
>>> res = dev.batch_execute(gradient_tapes) >>> fn(res) array([[-0.69688381, -0.32648317, -0.68120105], [ 0.8788057 , 0.41171179, 0.85902895]])
All gradient transforms are differentiable, unlocking higher-order derivatives on hardware.
New operations and templates
- Play around with the new Grover Diffusion Operator template
qml.templates.GroversOperator
. For example, use it to perform Grover’s Search Algorithm with an oracle function that marks the “all ones” state with a negative sign.
n_wires = 3 wires = list(range(n_wires)) def oracle(): qml.Hadamard(wires[-1]) qml.Toffoli(wires=wires) qml.Hadamard(wires[-1]) dev = qml.device('default.qubit', wires=wires) @qml.qnode(dev) def GroverSearch(num_iterations=1): for wire in wires: qml.Hadamard(wire) for _ in range(num_iterations): oracle() qml.templates.GroverOperator(wires=wires) return qml.probs(wires)
Running the above QNOde will yield the marked state with high probability:
>>> GroverSearch(num_iterations=1) tensor([0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.78125], requires_grad=True) >>> GroverSearch(num_iterations=2) tensor([0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.9453125], requires_grad=True)
- Instances of
QubitUnitary
may now be decomposed directly toRot
operations, orRZ
operations if the input matrix is diagonal. This can be achieved using the new decomposition method added toQubitUnitary
or the quantum function transformunitary_to_rot()
.
See the documentation for more details on this new quantum transform.
- The
qml.IsingYY
has been added.
Improvements
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
- The
QNGOptimizer
now accepts a customgrad_fn
keyword argument to use for gradient computations, allowing it to be used with additional interfaces such as JAX. - The precision used by JAX simulations now matches the float precision indicated by the JAX configuration.
- Quantum tape parameters can now be easily converted to NumPy arrays via the new
qml.tape.Unwrap()
context manager.
Breaking changes and deprecations
As new things are added, old things can be pruned away. Here’s what will be disappearing in the future:
- The deprecated tape methods
get_resources
andget_depth
have been removed, as they are superseded by theqml.specs
function. - Specifying
shots=None
withqml.sample
was previously deprecated. From this release onwards, settingshots=None
when sampling will raise an error. - The existing
pennylane.collections.apply
function is no longer accessible viaqml.apply
, and needs to be imported directly from the collections package.
These highlights are just scratching the surface — check out the full release notes for more details.
Contributors
As always, this release would not have been possible without the hard work of our development team and contributors:
Juan Miguel Arrazola, Olivia Di Matteo, Anthony Hayes, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Arshpreet Singh Khangura, Leonhard Kunczik, Christina Lee, Romain Moyard, Lee James O’Riordan, Ashish Panigrahi, Nahum Sá, Maria Schuld, Jay Soni, Antal Száva, David Wierichs.
About the author
Josh Izaac
Josh is a theoretical physicist, software tinkerer, and occasional baker. At Xanadu, he contributes to the development and growth of Xanadu’s open-source quantum software products.