The latest release of PennyLane is now out and available for everyone to use. It comes with many new additions, including executing large circuits with fewer qubits, differentiable mid-circuit measurements, a new high-performance GPU simulator, tools for quantum debugging, better batching support, and more.
Check out the table of contents below, or keep reading to find out more.
# This code block gets replaced with the table of contents. # Documentation: https://www.gatsbyjs.com/plugins/gatsby-remark-table-of-contents/
Cut your circuits into fragments for execution with fewer qubits β
When building new quantum algorithms, we often want to push the limits of what is possible, and test or deploy our algorithms on more and more wires. Unfortunately, all too often we end up constrained in our quest for more qubits by the underlying hardware or simulator device we are using.
With PennyLane v0.22, you can now execute a quantum algorithm that requires N wires on fewer than N wires, by taking advantage of circuit cutting.
Simply βcutβ wires within your QNode, and PennyLane will partition your algorithm into smaller fragments for execution, before combining and post-processing the results.
Circuit cutting is enabled by decorating a QNode with the @qml.cut_circuit
transform. For example, to execute a three-wire circuit on a two-wire device:
dev = qml.device("default.qubit", wires=2) @qml.cut_circuit @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) qml.RY(0.9, wires=1) qml.RX(0.3, wires=2) qml.CZ(wires=[0, 1]) qml.RY(-0.4, wires=0) # cut the circuit into two fragments at wire 1 qml.WireCut(wires=1) qml.CZ(wires=[1, 2]) return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2))
Instead of executing the circuit directly, it will be partitioned into smaller fragments according to the qml.WireCut
locations, and each fragment executed multiple times. Combining the results of the fragment executions will recover the expected output of the original uncut circuit:
>>> x = np.array(0.531, requires_grad=True) >>> circuit(0.531) 0.47165198882111165
As always, circuit cutting support is also differentiable:
>>> qml.grad(circuit)(x) -0.276982865449393
Note that while circuit cutting allows for executing a circuit on a device with fewer qubits, cutting a circuit can be expensive in terms of the number of executions required as well as additional classical postprocessing. For more details on circuit cutting, check out the qml.cut_circuit
documentation page.
Quantum teleport with mid-circuit measurements π
One of the most highly requested features over the years has been the ability to perform quantum teleportation with PennyLane, and this is now unlocked π in version 0.22, with the introduction of mid-circuit measurements and conditional operations.
Conditional operations
qml.measure()
allows circuit measurements to be placed in the middle of a quantum function.- The new
qml.cond()
transform allows operations to be conditioned on the result of a previous measurement.
Use mid-circuit measurements and conditional operations to build and run algorithms such as quantum teleportation, quantum error correction, and quantum error mitigation.
For example, the code below shows how to teleport a qubit from wire 0 to wire 2:
dev = qml.device("default.qubit", wires=3) input_state = np.array([1, -1], requires_grad=False) / np.sqrt(2) @qml.qnode(dev) def teleport(state): # Prepare input state qml.QubitStateVector(state, wires=0) # Prepare Bell state qml.Hadamard(wires=1) qml.CNOT(wires=[1, 2]) # Apply gates qml.CNOT(wires=[0, 1]) qml.Hadamard(wires=0) # Measure first two wires m1 = qml.measure(0) m2 = qml.measure(1) # Condition final wire on results qml.cond(m2 == 1, qml.PauliX)(wires=2) qml.cond(m1 == 1, qml.PauliZ)(wires=2) # Return state on final wire return qml.density_matrix(wires=2) >>> output_state = teleport(input_state) >>> output_state tensor([[ 0.5+0.j, -0.5+0.j], [-0.5+0.j, 0.5+0.j]], requires_grad=True)
We can double-check that the qubit has been teleported by computing the overlap between the input state and the resulting state on wire 2:
>>> input_state.conj() @ output_state @ input_state tensor(1.+0.j, requires_grad=True)
Train mid-circuit measurements by deferring them
If a device doesnβt natively support mid-circuit measurements, the @qml.defer_measurements
transform can be applied to the QNode to transform the QNode into one with terminal measurements and controlled operations:
@qml.qnode(dev) @qml.defer_measurements def circuit(x): qml.Hadamard(wires=0) m = qml.measure(0) qml.cond( m == 1, # measurement condition lambda: qml.RX(x**2, wires=1), # qfunc to apply if condition is True (m==1) lambda: qml.RY(x, wires=1) # qfunc to apply if condition is False (m!=1) )() return qml.expval(qml.PauliZ(1)) >>> x = np.array(0.7, requires_grad=True) >>> print(qml.draw(circuit, expansion_strategy="device")(x)) 0: ββHββCβββββββββXββCβββββββββXββ€ 1: βββββ°RX(0.49)βββββ°RY(0.70)βββββ€ <Z> >>> circuit(x) tensor(0.82358752, requires_grad=True)
Deferring mid-circuit measurements also enables differentiation: donβt just evaluate your mid-circuit algorithms, but train π them as well!
>>> qml.grad(circuit)(x) -0.651546965338656
For a full description of new capabilities, refer to the Mid-circuit measurements and conditional operations section in the documentation.
Accelerate your simulations with cuQuantum GPU support β‘
We are excited to announce the release of GPU support for our high-performance lightning.qubit
simulator: lightning.gpu
. This new device utilizes the NVIDIA cuQuantum library under-the-hood, and includes efficient computation of quantum gradients via adjoint differentiation.
Use lightning.gpu
for a significant speed-up for large quantum circuit evaluations, even when using multiple CPU-threads:
The lightning.gpu
device can be installed via pip:
pip install pennylane-lightning[gpu]
Once installed, it can be loaded and used with any PennyLane QNodes:
dev = qml.device("lightning.gpu", wires=22) @qml.qnode(dev, diff_method="adjoint") def circuit(weights): qml.StronglyEntanglingLayers(weights, wires=list(range(n_wires))) return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] param_shape = qml.StronglyEntanglingLayers.shape(n_layers=2, n_wires=22) params = np.random.random(param_shape) jac = qml.jacobian(circuit)(params)
For more details on installing and using lightning.gpu
, check out the device documentation.
Debug with mid-circuit quantum snapshots π·
Designing new quantum algorithms is tough, and debugging quantum algorithms is sometimes even harder!
To help alleviate the frustration of (quantum) debugging, v0.22 of PennyLane introduces the qml.Snapshot
operation, which saves the internal state of simulator devices at arbitrary points within the quantum execution.
dev = qml.device("default.qubit", wires=2) @qml.qnode(dev, interface=None) def circuit(): qml.Snapshot() qml.Hadamard(wires=0) qml.Snapshot("very_important_state") qml.CNOT(wires=[0, 1]) qml.Snapshot() return qml.expval(qml.PauliX(0))
During normal execution, the snapshots are ignored:
>>> circuit() array(0.)
However, when using the qml.snapshots
transform, intermediate values of the statevector will be stored and returned alongside the results:
>>> qml.snapshots(circuit)() {0: array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]), 'very_important_state': array([0.70710678+0.j, 0. +0.j, 0.70710678+0.j, 0. +0.j]), 2: array([0.70710678+0.j, 0. +0.j, 0. +0.j, 0.70710678+0.j]), 'execution_results': array(0.)}
Currently, snapshots are supported on default.qubit
, default.mixed
, and default.gaussian
, returning representations of the quantum state. Over time, we plan to add more options for snapshots, across both simulator and hardware devices.
Better batching π¦
We previously added the @qml.batch_params
transform to enable batching over trainable parameters in your quantum algorithms. In this release, we extend this support to all gate parameters, including embeddings and state preparation, via @qml.batch_input
.
dev = qml.device("default.qubit", wires=2) @qml.batch_input(argnum=0) @qml.qnode(dev, diff_method="parameter-shift", interface="tf") def circuit(inputs, weights): # add a batch dimension to the embedding data qml.AngleEmbedding(inputs, wires=range(2), rotation="Y") qml.RY(weights[0], wires=0) qml.RY(weights[1], wires=1) return qml.expval(qml.PauliZ(1))
Batched input parameters can then be passed during QNode evaluation:
>>> import tensorflow as tf >>> x = tf.random.uniform((10, 2), 0, 1) >>> w = tf.random.uniform((2,), 0, 1) >>> circuit(x, w) <tf.Tensor: shape=(10,), dtype=float64, numpy= array([0.46230079, 0.73971315, 0.95666004, 0.5355225 , 0.66180948, 0.44519553, 0.93874261, 0.9483197 , 0.78737918, 0.90866411])>
Even more mighty quantum transforms πβ‘π¦
From circuit cutting, to conditional operations, snapshots, deferred measurements, and input batching, you must be thinking βWow! This release couldnβt possibly fit even more transforms!β
And yet! We have barely scratched the surface. Here is a quick rundown of more quantum transforms that are debuting in version 0.22:
qml.matrix()
for computing the matrix representation of one or more unitary operators.qml.eigvals()
for computing the eigenvalues of one or more operators.qml.generator()
for computing the generator of a single-parameter unitary operation.qml.commutation_dag()
to construct the pairwise-commutation directed acyclic graph (DAG) representation of a quantum circuit.
As always, all transforms support a functional user interface with differentiation support (where possible):
>>> def circuit(theta): ... qml.RX(theta, wires=1) ... qml.PauliZ(wires=0) >>> qml.matrix(circuit)(np.pi / 4) array([[ 0.92387953+0.j, 0.+0.j , 0.-0.38268343j, 0.+0.j], [ 0.+0.j, -0.92387953+0.j, 0.+0.j, 0. +0.38268343j], [ 0. -0.38268343j, 0.+0.j, 0.92387953+0.j, 0.+0.j], [ 0.+0.j, 0.+0.38268343j, 0.+0.j, -0.92387953+0.j]])
Improvements
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
- Most compilation transforms, and relevant subroutines, have been updated to support just-in-time compilation with
@jax.jit
. - The frequencies of gate parameters are now accessible as an operation property, and are used for circuit analysis, optimization via the
RotosolveOptimizer
, and differentiation with the parameter-shift rule (including the general shift rule). - When computing gradients on quantum hardware, the parameter-shift rule now uses parameter frequencies to compute general shift rules when operation gradient recipes are not defined. This enables hardware gradient support on a wider set of operations.
- The text-based drawer accessed via
qml.draw()
has been improved, with new options for displaying parameters and matrices, an improved algorithm for determining gate positions, and cosmetic improvements.
For the full list of improvements, please refer to the release notes.
Deprecations and breaking changes
As new things are added, outdated features are removed. Hereβs what will be changing in this release.
Deprecations
qml.transforms.get_unitary_matrix()
has been deprecated and will be removed in a future release. For extracting matrices of operations and quantum functions, please useqml.matrix()
.- The
qml.finite_diff()
function has been deprecated and will be removed in an upcoming release. Instead,qml.gradients.finite_diff()
can be used to compute purely quantum gradients (that is, gradients of tapes or QNodes). - The
MultiControlledX
operation now accepts a singlewires
keyword argument for bothcontrol_wires
andwires
. The singlewires
keyword should contain all control wires followed by a single target wire. - Executing tapes using
tape.execute(dev)
is deprecated. Please use theqml.execute([tape], dev)
function instead. - The subclasses of the quantum tape, including
JacobianTape
, are deprecated. Instead of callingJacobianTape.jacobian()
please use a standardQuantumTape
, and apply gradient transforms using theqml.gradients
module.
In addition, there are several important changes when creating custom operations:
- The
Operator.matrix
method has been deprecated andOperator.compute_matrix
should be defined instead. Operator matrices can be accessed usingqml.matrix(op)
. - The
Operator.decomposition
method has been deprecated andOperator.compute_decomposition
should be defined instead. Operator decompositions can be accessed usingOperator.decomposition()
. - The
Operator.eigvals
method has been deprecated andOperator.compute_eigvals
should be defined instead. Operator eigenvalues can be accessed usingqml.eigvals(op)
. - The
Operator.generator
property is now a method, and should return an operator instance representing the generator. Operator generators can be accessed usingqml.generator(op)
.
For more details on adding custom operations in version 0.22, please see the Adding new operators page in the documentation.
Breaking changes
- The
pennylane.measure
module has been renamed topennylane.measurements
.
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:
Catalina Albornoz, Jack Y. Araz, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Olivia Di Matteo, Christian Gogolin, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Angus Lowe, Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Lee James OβRiordan, Chae-Yeun Park, Maria Schuld, Jay Soni, Antal SzΓ‘va, Trevor Vincent, and 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.