PennyLane
  • Why PennyLane
  • Getting Started
  • Documentation
  • Ecosystem
Install
Install
  1. Blog/
  2. Releases/
  3. PennyLane v0.31 released

June 27, 2023

PennyLane v0.31 released

Isaac De Vlugt

Isaac De Vlugt

Thomas Bromley

Thomas Bromley

31 is kind of a special number — it belongs to the smallest non-palindromic emirp pair: 13, 31 🤓. That, and it's a pretty sweet version of PennyLane! PennyLane v0.31 is out today! Check out all of the awesome new functionality below.

Contents

  • Seamlessly create and combine fermionic operators 🔬
  • GPU-distributed statevector support 🤖🤖
  • Workflow-level quantum circuit resource estimation 🧮
    • PennyLane's Tracker
    • Custom operations
  • Community contributions from unitaryHACK 🤝
    • Trace distance
    • Qutrit basis state preparation
    • A unified decomposition transform
  • Improvements 🛠
  • Deprecations and breaking changes 💔
  • Contributors ✍️

Seamlessly create and combine fermionic operators 🔬

We stand fermily behind this new feature 🤠. Fermionic operators and arithmetic are now available in PennyLane!

With these new features, you can create fermionic operator Hamiltonians in a couple of different ways:

  • qml.FermiC and qml.FermiA: the fermionic creation and annihilation operators, respectively. These operators are defined by passing the index of the orbital that the fermionic operator acts on. For instance, the operators a^{\dagger}_0 and a_3 are respectively constructed as

    >>> qml.FermiC(0) a⁺(0) >>> qml.FermiA(3) a(3)

    These operators can be linearly combined (+ and -) with, and multiplied (*) by other Fermi operators to create arbitrary fermionic Hamiltonians. Multiplying several Fermi operators together creates an operator that we call a Fermi word:

    >>> word = qml.FermiC(0) * qml.FermiA(0) * qml.FermiC(3) * qml.FermiA(3) >>> word a⁺(0) a(0) a⁺(3) a(3)

    Fermi words can be linearly combined to create a fermionic operator that we call a Fermi sentence:

    >>> sentence = 1.2 * word + 0.345 * qml.FermiC(3) * qml.FermiA(3) >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) + 0.345 * a⁺(3) a(3)
  • qml.fermi.from_string: create a fermionic operator that represents multiple creation and annihilation operators being multiplied by each other (a Fermi word).

    >>> qml.fermi.from_string('0+ 1- 0+ 1-') a⁺(0) a(1) a⁺(0) a(1) >>> qml.fermi.from_string('0^ 1 0^ 1') a⁺(0) a(1) a⁺(0) a(1)

    Fermi words created with from_string can be linearly combined to create a Fermi sentence:

    >>> word1 = qml.fermi.from_string('0+ 0- 3+ 3-') >>> word2 = qml.fermi.from_string('3+ 3-') >>> sentence = 1.2 * word1 + 0.345 * word2 >>> sentence 1.2 * a⁺(0) a(0) a⁺(3) a(3) + 0.345 * a⁺(3) a(3)

Fermi words and sentences can also be created via qml.fermi.FermiWord and qml.fermi.FermiSentence, respectively.

Additionally, any fermionic operator, be it a single fermionic creation/annihilation operator, a Fermi word, or a Fermi sentence, can be mapped to the qubit basis by using qml.jordan_wigner:

>>> qml.jordan_wigner(sentence) ((0.4725+0j)*(Identity(wires=[0]))) + ((-0.4725+0j)*(PauliZ(wires=[3]))) + ((-0.3+0j)*(PauliZ(wires=[0]))) + ((0.3+0j)*(PauliZ(wires=[0]) @ PauliZ(wires=[3])))

Learn how to create fermionic Hamiltonians describing some simple chemical systems by checking out our fermionic operators demo!

GPU-distributed statevector support 🤖🤖

You know what's better than using one GPU? Using multiple GPUs 😀. PennyLane-Lightning-GPU now supports multi-node/multi-GPU calculations!

Use of PennyLane-Lightning-GPU with multi-node/multi-GPU support requires explicit installation of the NVIDIA cuQuantum SDK (current supported cuQuantum version: cuquantum-cu11), mpi4py and CUDA-aware MPI (Message Passing Interface). Check out the instructions in the PennyLane-Lightning-GPU documentation for more details.

With that out of the way, let's go through an example calculation. We need to define a way for the parallel GPUs to communicate with each other. The protocol for doing so is the Message Passing Interface (MPI) — taken care of with the mpi4py package. Let's define a script called mpi_gpu.py:

from mpi4py import MPI import pennylane as qml from pennylane import numpy as np

The object that represents the processes that communicate with each other is called a communicator (comm in the example below):

comm = MPI.COMM_WORLD rank = comm.Get_rank()

To demonstrate the power of this new feature, let's simulate 31 qubits. The MPI backend will be called if mpi=True when defining a device.

n_wires = 31 n_layers = 1 dev = qml.device('lightning.gpu', wires= n_wires, mpi=True) @qml.qnode(dev, diff_method="adjoint") def circuit_adj(weights): qml.StronglyEntanglingLayers(weights, wires=list(range(n_wires))) return qml.expval(qml.PauliZ(0)) if rank == 0: params = np.random.random(qml.StronglyEntanglingLayers.shape(n_layers=n_layers, n_wires=n_wires)) else: params = None

Lastly, we need to ensure that every GPU being used receives the same parameters with bcast:

params = comm.bcast(params, root=0) jac = qml.jacobian(circuit_adj)(params) if rank==0: print(jac)
[[[ 1.74049378e-17 -1.24668737e-19 1.75018936e-17] ... [ 9.78915458e-19 -3.75031201e-03 -1.52691202e-18]]]

With our circuit defined (and in this case 4 GPUs available) we can run the above circuit with:

mpirun -np 4 python mpi_gpu.py

We hope you enjoy this truly awesome feature!

Workflow-level quantum circuit resource estimation 🧮

Work smarter 🧠, not harder, by utilizing a new set of quantum circuit resource estimation functionalities!

PennyLane's Tracker

PennyLane's Tracker now monitors the resource requirements of circuits being executed by the device. Suppose we have a workflow that involves executing circuits with different qubit numbers. We can obtain the resource requirements as a function of the number of qubits by executing the workflow with the Tracker context:

dev = qml.device("default.qubit", wires=4) @qml.qnode(dev) def circuit(n_wires): for i in range(n_wires): qml.Hadamard(i) return qml.probs(range(n_wires)) with qml.Tracker(dev) as tracker: for i in range(1, 5): circuit(i)

The resource requirements of individual circuits can then be inspected as follows:

>>> resources = tracker.history["resources"] >>> resources[0] wires: 1 gates: 1 depth: 1 shots: Shots(total=None) gate_types: {'Hadamard': 1} gate_sizes: {1: 1} >>> [r.num_wires for r in resources] [1, 2, 3, 4]

Moreover, it is possible to predict the resource requirements without evaluating circuits using the null.qubit device, which follows the standard execution pipeline but returns numeric zeros. Check out the full v0.31 release notes to learn more!

Custom operations

Custom operations can now be defined that solely include resource requirements — an explicit decomposition or matrix representation is not needed, allowing you to estimate requirements for high-level algorithms composed of abstract subroutines. These operations can be defined by inheriting from ResourcesOperation and overriding the resources() method to return an appropriate Resources object:

class CustomOp(qml.resource.ResourcesOperation): def resources(self): n = len(self.wires) r = qml.resource.Resources( num_wires=n, num_gates=n ** 2, depth=5, ) return r
>>> wires = [0, 1, 2] >>> c = CustomOp(wires) >>> c.resources() wires: 3 gates: 9 depth: 5 shots: Shots(total=None) gate_types: {} gate_sizes: {}

A quantum circuit that contains CustomOp can be created and inspected using qml.specs:

dev = qml.device("default.qubit", wires=wires) @qml.qnode(dev) def circ(): qml.PauliZ(wires=0) CustomOp(wires) return qml.state()
>>> specs = qml.specs(circ)() >>> specs["resources"].depth 6

Community contributions from unitaryHACK 🤝

This year's unitaryHACK proved that PennyLane's community is as strong as ever 💪! Here's a glimpse of what the community contributed.

Trace distance

The quantum information module now supports trace distance in a couple of ways:

  • A QNode transform via qml.qinfo.trace_distance:

    dev = qml.device('default.qubit', wires=2) @qml.qnode(dev) def circuit(param): qml.RY(param, wires=0) qml.CNOT(wires=[0, 1]) return qml.state()
    >>> trace_distance_circuit = qml.qinfo.trace_distance(circuit, circuit, wires0=[0], wires1=[0]) >>> x, y = np.array(0.4), np.array(0.6) >>> trace_distance_circuit((x,), (y,)) 0.047862689546603415
  • Flexible post-processing via qml.math.trace_distance:

    >>> rho = np.array([[0.3, 0], [0, 0.7]]) >>> sigma = np.array([[0.5, 0], [0, 0.5]]) >>> qml.math.trace_distance(rho, sigma) 0.19999999999999998

Qutrit basis state preparation

It is now possible to prepare qutrit basis states with qml.QutritBasisState.

wires = range(2) dev = qml.device("default.qutrit", wires=wires) @qml.qnode(dev) def qutrit_circuit(): qml.QutritBasisState([1, 1], wires=wires) qml.TAdd(wires=wires) return qml.probs(wires=1)
>>> qutrit_circuit() array([0., 0., 1.])

A unified decomposition transform

A new transform called one_qubit_decomposition has been added that provides a unified interface for decompositions of a single-qubit unitary matrix into sequences of X, Y, and Z rotations. All decompositions simplify the rotations angles to be between 0 and 4π.

>>> from pennylane.transforms import one_qubit_decomposition >>> U = np.array([[-0.28829348-0.78829734j, 0.30364367+0.45085995j], ... [ 0.53396245-0.10177564j, 0.76279558-0.35024096j]]) >>> one_qubit_decomposition(U, 0, "ZYZ") [RZ(tensor(12.32427531, requires_grad=True), wires=[0]), RY(tensor(1.14938178, requires_grad=True), wires=[0]), RZ(tensor(1.73305815, requires_grad=True), wires=[0])] >>> one_qubit_decomposition(U, 0, "XYX", return_global_phase=True) [RX(tensor(10.84535137, requires_grad=True), wires=[0]), RY(tensor(1.39749741, requires_grad=True), wires=[0]), RX(tensor(0.45246584, requires_grad=True), wires=[0]), (0.38469215914523336-0.9230449299422961j)*(Identity(wires=[0]))]

For a complete list of contributions, check out the full release notes.

Improvements 🛠

In addition to the new features listed above, the release contains a wide array of improvements and optimizations:

  • The TorchLayer and KerasLayer integrations with torch.nn and Keras have been upgraded with the following new features:

    • Native support for parameter broadcasting.

      n_qubits = 2 dev = qml.device("default.qubit", wires=n_qubits) @qml.qnode(dev) def qnode(inputs, weights): qml.AngleEmbedding(inputs, wires=range(n_qubits)) qml.BasicEntanglerLayers(weights, wires=range(n_qubits)) return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)] n_layers = 6 weight_shapes = {"weights": (n_layers, n_qubits)} qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)
      >>> batch_size = 10 >>> inputs = torch.rand((batch_size, n_qubits)) >>> qlayer(inputs) >>> dev.num_executions == 1 True
    • The ability to draw a TorchLayer and KerasLayer using qml.draw() and qml.draw_mpl().

    • Support for KerasLayer model saving and clearer instructions on TorchLayer model saving (this includes loading/saving hybrid models).

  • The stochastic parameter-shift gradient method can now be used with hardware-compatible Hamiltonians. This new feature generalizes stoch_pulse_grad to support Hermitian generating terms beyond just Pauli words in pulse Hamiltonians, which makes it hardware-compatible.

  • A new differentiation method called qml.gradients.pulse_generator is available, which combines classical processing with the parameter-shift rule for multivariate gates to differentiate pulse programs. Access it in your pulse programs by setting diff_method=qml.gradients.pulse_generator.

  • Reduced density matrix functionality has been added via qml.math.reduce_dm and qml.math.reduce_statevector. Both functions have broadcasting support.

  • A whole host of existing operators, transforms, and more now support parameter broadcasting:

    • Qutrit devices
    • The following functions in qml.qinfo: purity, vn_entropy, mutual_info, fidelity, relative_entropy, trace_distance
    • The following functions in qml.math: purity, vn_entropy, mutual_info, fidelity, relative_entropy, max_entropy, sqrt_matrix

Deprecations and breaking changes 💔

As new things are added, outdated features are removed. To keep track of things in the deprecation pipeline, check out the deprecations page.

Here's a summary of what has changed in this release:

  • qml.collections, qml.op_sum, and qml.utils.sparse_hamiltonian have been removed.

  • qml.math.reduced_dm has been deprecated. Please use qml.math.reduce_dm or qml.math.reduce_statevector instead.

  • zyz_decomposition and xyx_decomposition are now deprecated in favour of one_qubit_decomposition.

  • qml.math.purity, qml.math.vn_entropy, qml.math.mutual_info, qml.math.fidelity, qml.math.relative_entropy, and qml.math.max_entropy no longer support state vectors as input. Please call qml.math.dm_from_state_vector on the input before passing to any of these functions.


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:

Venkatakrishnan AnushKrishna, Thomas Bromley, Isaac De Vlugt, Amintor Dusko, Tarik El-Khateeb, Lillian M. A. Frederiksen, Emiliano Godinez Ramirez, Nikhil Harle, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Ivana Kurečić, Christina Lee, Vincent Michaud-Rioux, Romain Moyard, Tristan Nemoz, Lee James O'Riordan, Mudit Pandey, Chae-Yeun Park, Manul Patel, Borja Requena, Modjtaba Shokrian-Zini, Mainak Roy, Shuli Shu, Matthew Silverman, Jay Soni, Edward Thomas, David Wierichs, and Frederik Wilde.

About the authors

Isaac De Vlugt
Isaac De Vlugt

Isaac De Vlugt

My job is to help manage the PennyLane and Catalyst feature roadmap... and spam lots of emojis in the chat 🤠

Thomas Bromley
Thomas Bromley

Thomas Bromley

Last modified: August 14, 2024

Related Blog Posts

PennyLane

PennyLane is an open-source software framework for quantum machine learning, quantum chemistry, and quantum computing, with the ability to run on all hardware. Built with ❤️ by Xanadu.

Stay updated with our newsletter

For researchers

  • Research
  • Features
  • Demos
  • Compilation
  • Datasets
  • Performance
  • Learn
  • Videos
  • Documentation
  • Teach

For learners

  • Learn
  • Codebook
  • Teach
  • Videos
  • Challenges
  • Demos
  • Compilation
  • Glossary

For developers

  • Features
  • Documentation
  • API
  • GitHub
  • Datasets
  • Demos
  • Compilation
  • Performance
  • Devices
  • Catalyst

© Copyright 2025 | Xanadu | All rights reserved

TensorFlow, the TensorFlow logo and any related marks are trademarks of Google Inc.

Privacy Policy|Terms of Service|Cookie Policy|Code of Conduct