PennyLane v0.28 — the release so good, not even ChatGPT can fathom it 🤖! Check out all of the awesome new functionality below.
# This code block gets replaced with the table of contents. # Documentation: https://www.gatsbyjs.com/plugins/gatsby-remark-table-of-contents/
Custom measurement processes 📐
Spread your wings and get creative with the ability to create custom measurements using the new qml.measurements
module!
Previously, PennyLane only came with built-in measurements that you can return from QNodes: qml.expval
, qml.probs
, etc., to name a few. Over time, we have added more, but now you can define your own measurement process function to use in QNodes.
Within qml.measurements
are new subclasses that allow for the possibility to create measurements:
StateMeasurement
: represents a state-based measurementSampleMeasurement
: represents a sample-based measurement
Creating a custom measurement involves making a class that inherits from one of the classes above. Here is an example that inherits from SampleMeasurement
, where the new measurement, CountState
, computes the number of obtained samples for a given state:
from pennylane.measurements import SampleMeasurement class CountState(SampleMeasurement): def __init__(self, state: str): self.state = state # string identifying the state, e.g. "0101" wires = list(range(len(state))) super().__init__(wires=wires) def process_samples(self, samples, wire_order, shot_range, bin_size): counts_mp = qml.counts(wires=self._wires) counts = counts_mp.process_samples(samples, wire_order, shot_range, bin_size) return counts.get(self.state, 0) def __copy__(self): return CountState(state=self.state)
The method process_samples
must be defined in order to instruct how CountState
processes the samples. Within it, a call to the familiar qml.counts
measurement is made, its process_samples
method is called to obtain measurement counts, and only counts for the specified state
are returned.
We can now execute the new measurement in a QNode as follows.
dev = qml.device("default.qubit", wires=1, shots=10000) @qml.qnode(dev) def circuit(x): qml.RX(x, wires=0) return CountState(state="1") >>> circuit(1.23) tensor(3303., requires_grad=True)
Differentiability is also conserved for this new measurement process:
>>> x = qml.numpy.array(1.23, requires_grad=True) >>> qml.grad(circuit)(x) 4715.000000000001
MeasurementTransform
: represents a measurement process that requires the application of a batch transform
For more information about these new features, see the documentation for qml.measurements
.
ZX-calculus 🧮
The magic of ZX-calculus is now at your fingertips! Envision quantum protocols like a boss. 😤
ZX-calculus is a graphical formalism designed to provide an intuitive way to look at quantum protocols as linear maps between qubits, which are called ZX-diagrams. A great example of how intuitive a ZX-diagram can be is the one representing a SWAP gate — consisting of three CNOT gates — as seen above.
With the help of the PyZX framework, QNodes decorated with @qml.transforms.to_zx
will return a PyZX graph that represents the computation at hand in the language of ZX-calculus.
dev = qml.device("default.qubit", wires=2) @qml.transforms.to_zx @qml.qnode(device=dev) def circuit(p): qml.RZ(p[0], wires=1), qml.RZ(p[1], wires=1), qml.RX(p[2], wires=0), qml.PauliZ(wires=0), qml.RZ(p[3], wires=1), qml.PauliX(wires=1), qml.CNOT(wires=[0, 1]), qml.CNOT(wires=[1, 0]), qml.SWAP(wires=[0, 1]), return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)) >>> params = [5 / 4 * np.pi, 3 / 4 * np.pi, 0.1, 0.3] >>> circuit(params) Graph(20 vertices, 23 edges)
Information about PyZX graphs can be found in the PyZX Graphs API.
PubChem database access and new basis sets ⚛
PubChem database access
PennyLane, meet PubChem. PubChem, meet PennyLane. 🤝
The symbols and geometry of compounds from the PubChem database can now be accessed via qchem.mol_data()
.
>>> import pennylane as qml >>> from pennylane.qchem import mol_data >>> mol_data("BeH2") (['Be', 'H', 'H'], tensor([[ 4.79404621, 0.29290755, 0. ], [ 3.77945225, -0.29290755, 0. ], [ 5.80882913, -0.29290755, 0. ]], requires_grad=True)) >>> mol_data(223, "CID") (['N', 'H', 'H', 'H', 'H'], tensor([[ 0. , 0. , 0. ], [ 1.82264085, 0.52836742, 0.40402345], [ 0.01417295, -1.67429735, -0.98038991], [-0.98927163, -0.22714508, 1.65369933], [-0.84773114, 1.373075 , -1.07733286]], requires_grad=True))
New basis sets
On the basis of basis sets, PennyLane basically has it covered. 🙌
Enjoy the ability to use the 6-311g
and CC-PVDZ
basis sets in your qml.qchem
code!
>>> symbols = ["H", "He"] >>> geometry = np.array([[1.0, 0.0, 0.0], [0.0, 0.0, 0.0]], requires_grad=False) >>> charge = 1 >>> basis_names = ["6-311G", "CC-PVDZ"] >>> for basis_name in basis_names: ... mol = qml.qchem.Molecule(symbols, geometry, charge=charge, basis_name=basis_name) ... print(qml.qchem.hf_energy(mol)()) [-2.84429531] [-2.84061284]
New transforms, operators, and more 😯
Enjoy this roux of the finest functionalities in town! 🧑🍳🍲
Purity
Calculating the purity of arbitrary quantum states is now supported. Purity can now be calculated in an analogous fashion to, say, the Von Neumann entropy.
qml.math.purity
can be used as an in-line functionqml.qinfo.transforms.purity
can transform a QNode returning a state to a function that returns the purity
>>> theta = np.array(np.pi/3, requires_grad=True) >>> x = np.array([np.cos(theta), 0, 0, np.sin(theta)]) >>> qml.math.purity(x, [0, 1]) 1.0 >>> qml.math.purity(np.outer(x, np.conj(x)), [0]) 0.625 >>> dev = qml.device("default.mixed", wires=2) >>> @qml.qnode(dev) ... def circuit(theta): ... qml.QubitStateVector(np.array([np.cos(theta), 0, 0, np.sin(theta)]), wires=[0, 1]) ... return qml.state() ... >>> qml.qinfo.transforms.purity(circuit, wires=[0])(theta) 0.625 >>> qml.qinfo.transforms.purity(circuit, wires=[0, 1])(theta) 1.0
As with the other methods in qml.qinfo
, purity is fully differentiable:
>>> qml.grad(qml.qinfo.transforms.purity(circuit, wires=[0]))(theta) (0.8660254037844384+0j)
Combine mid-circuit measurements
- Multiple mid-circuit measurements can now be combined arithmetically to create new conditionals.
dev = qml.device("default.qubit", wires=3) @qml.qnode(dev) def circuit(): qml.Hadamard(wires=0) qml.Hadamard(wires=1) m0 = qml.measure(wires=0) m1 = qml.measure(wires=1) combined = 2 * m1 + m0 qml.cond(combined == 2, qml.RX)(1.3, wires=2) return qml.probs(wires=2) >>> circuit() [0.90843735 0.09156265]
New operators
Parameteric operators
Three new parametric operators, qml.CPhaseShift00
, qml.CPhaseShift01
, and qml.CPhaseShift10
, are now available. Each of these operators performs a phase shift akin to qml.ControlledPhaseShift
but on different positions of the state vector.
>>> dev = qml.device("default.qubit", wires=2) >>> @qml.qnode(dev) >>> def circuit(): ... qml.PauliX(wires=1) ... qml.CPhaseShift01(phi=1.23, wires=[0,1]) ... return qml.state() ... >>> circuit() tensor([0. +0.j , 0.33423773+0.9424888j, 1. +0.j , 0. +0.j ], requires_grad=True)
Create operators from a generator
Create operators defined from a generator via qml.ops.op_math.Evolution
. qml.ops.op_math.Evolution
defines the exponential of an operator \hat{O}, of the form e^{ix\hat{O}}, with a single trainable parameter, x. Limiting to a single trainable parameter allows the use of qml.gradients.param_shift
to find the gradient with respect to the parameter x.
dev = qml.device('default.qubit', wires=2) @qml.qnode(dev, diff_method=qml.gradients.param_shift) def circuit(phi): qml.ops.op_math.Evolution(qml.PauliX(0), -.5 * phi) return qml.expval(qml.PauliZ(0)) >>> phi = np.array(1.2) >>> circuit(phi) tensor(0.36235775, requires_grad=True) >>> qml.grad(circuit)(phi) -0.9320390495504149
Qutrit Hadamard
The qutrit Hadamard gate, qml.THadamard
, is now available. This operation accepts a subspace
keyword argument, which determines which variant of the qutrit Hadamard to use.
>>> th = qml.THadamard(wires=0, subspace=[0, 1]) >>> qml.matrix(th) array([[ 0.70710678+0.j, 0.70710678+0.j, 0. +0.j], [ 0.70710678+0.j, -0.70710678+0.j, 0. +0.j], [ 0. +0.j, 0. +0.j, 1. +0.j]])
Improvements 🛠
In addition to the new features listed above, the release contains a wide array of improvements and optimizations:
- PennyLane now supports Python 3.11 and Torch v1.13. Support for Python 3.7 has been dropped.
qml.pauli.is_pauli_word
now supports instances ofqml.Hamiltonian
.- When
qml.probs
,qml.counts
, andqml.sample
are called with no arguments, they measure all wires. Calling any of the aforementioned measurements with an empty wire list (e.g.,qml.sample(wires=[]))
will raise an error. - The
qml.ISWAP
gate is now natively supported ondefault.mixed
, improving on its efficiency. qml.equal
now supports operators created viaqml.s_prod
,qml.pow
,qml.exp
, andqml.adjoint
.- Start-up times for
lightning.gpu
have been improved.
Deprecations and breaking changes 💔
As new things are added, outdated features are removed. To keep track of things in the deprecation pipeline, we’ve created a deprecation page.
Here’s what has changed in this release:
- Python 3.7 support is no longer maintained. PennyLane will be maintained for versions 3.8 and up.
qml.utils.decompose_hamiltonian()
has been removed. Please useqml.pauli.pauli_decompose()
instead.
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:
Guillermo Alonso, Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Thomas Bromley, Astral Cai, Albert Mitjans Coma, Ahmed Darwish, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Pieter Eendebak, Tarik El-Khateeb, Lillian M. A. Frederiksen, Diego Guala, Katharine Hyatt, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O’Riordan, Mudit Pandey, Kevin Shen, Matthew Silverman, Jay Soni, Antal Száva, David Wierichs, Moritz Willmann, and Filippo Vicentini.
About the author
Isaac De Vlugt
My job is to help manage the PennyLane and Catalyst feature roadmap... and spam lots of emojis in the chat 🤠