PennyLane v0.23 released

PennyLane team

The latest release of PennyLane is now out and available for everyone to use. It comes with many new additions, including automatic circuit cutting, a unification of our quantum chemistry packages, new templates and compilation transforms, and more.

Check out the table of contents below, or keep reading to find out more.

More powerful circuit-cutting ✂

In the previous release of PennyLane, we announced support for circuit cutting — the ability to execute a quantum algorithm that requires N wires on fewer than N wires.

Simply ‘cut’ wires within your QNode using qml.WireCut, apply the @qml.cut_circuit transform, and PennyLane will partition your algorithm into smaller fragments for execution, before combining and post-processing the results.

We are excited to now extend circuit cutting to support automatic placement of cut locations, saving you the worry of working out how to split up your circuit! In addition, we have also built in support for cutting circuits that return sample-based measurements.

Automatic circuit cuts

In addition to manually placing where you would like your circuit to be cut, the @qml.cut_circuit transform now supports automatic cutting via the auto_cutter argument:

>>> dev = qml.device("default.qubit", wires=2)
>>> @qml.cut_circuit(auto_cutter=True)
... @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)
...     qml.CZ(wires=[1, 2])
...     return qml.expval(qml.grouping.string_to_pauli_word("ZZZ"))
>>> x = np.array(0.531, requires_grad=True)
>>> circuit(x)
>>> qml.grad(circuit)(x)

Note that the default automatic cutter algorithm utilizes the KaHyPar package to determine the best cut locations, and will require KaHyPar be separately installed. Alternatively, custom cutting functions can be passed to auto_cutter; these functions should accept a NetworkX MultiDiGraph representing the circuit, and return the list of edges to cut. Additional arguments to such custom cutting functions can also be directly supplied as keyword arguments to @qml.cut_circuit.

For more details on the built-in KaHyPar automatic cut placement algorithm, please see qml.transforms.qcut.find_and_place_cuts().

Cut your sampling circuits into fragments

Using Monte-Carlo methods, the @qml.cut_circuit_mc transform allows you to cut sample-based circuits into smaller fragments for execution.

In addition, processing functions can be provided which allow the ‘cut’ samples to be processed into expectation values:

dev = qml.device("default.qubit", wires=2, shots=10000)

def ZZ_expectation(bitstring):
    return (-1) ** np.sum(bitstring)

def circuit(x):
    qml.RX(0.89, wires=0)
    qml.RY(0.5, wires=1)
    qml.RX(1.3, wires=2)
    qml.CNOT(wires=[0, 1])

    # place a cut in the circuit

    qml.CNOT(wires=[1, 2])
    qml.RX(x, wires=0)
    qml.RY(0.7, wires=1)
    qml.RX(2.3, wires=2)
    return qml.sample(wires=[0, 2])
>>> circuit(x)
tensor(-0.776, requires_grad=True)

In addition, @qml.cut_circuit_mc also supports automatic cut placements with KaHyPar by passing the auto_cutter=True keyword argument.

Grand quantum chemistry unification ⚛️🏰

In previous releases, PennyLane provided two modules for quantum chemistry workflows:

  • qml.qchem, provided by the external package pennylane-qchem, for integrating external packages such as PySCF and OpenFermion with PennyLane, as well as
  • qml.hf, a fully differentiable Hartree-Fock solver.

With PennyLane version 0.23, both packages have been unified into a single module, qml.qchem, directly built in to PennyLane — no external package installations needed!

The new qml.qchem module provides all the same features and capabilities you love from the previous version of PennyLane — including a few new ones! — but unified under a single set of functions.

For example, you can continue to generate molecular Hamiltonians using qml.qchem.molecular_hamiltonian:

symbols = ["H", "H"]
geometry = np.array([[0., 0., -0.66140414], [0., 0., 0.66140414]])
hamiltonian, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, method="dhf")

By default, this will use the differentiable Hartree-Fock solver; however, simply set method="pyscf" to continue to use PySCF for Hartree-Fock calculations.

In addition to this unification, new features now available in the qml.qchem module include:

  • Support for the 6-31G basis set, allowing differentiable Hartree-Fock calculations with basis sets beyond the minimal sto-3g basis set for atoms with atomic number 1-10.

For more details, please see the quantum chemistry quickstart.

Even more quantum compilation transforms 🐛➡🦋

SWAP-based transpilation

When working with quantum hardware devices with restricted connectivity, it can be important to transpile our circuits to ensure we are only applying entangling gates on wires that are connected.

While this has always been supported automatically when using PennyLane with hardware, the new qml.transforms.transpile() compilation transform provides direct access to SWAP-based transpilation:

dev = qml.device('default.qubit', wires=4)

@qml.transforms.transpile(coupling_map=[(0, 1), (1, 2), (2, 3)])
def circuit(param):
    qml.CNOT(wires=[0, 1])
    qml.CNOT(wires=[0, 2])
    qml.CNOT(wires=[0, 3])
    qml.PhaseShift(param, wires=0)
    return qml.probs(wires=[0, 1, 2, 3])
>>> print(qml.draw(circuit)(0.6))
0: ─╭C───────╭C──────────╭C──Rϕ(0.60)─┤ ╭Probs
1: ─╰X─╭SWAP─╰X────╭SWAP─╰X───────────┤ ├Probs
2: ────╰SWAP─╭SWAP─╰SWAP──────────────┤ ├Probs
3: ──────────╰SWAP────────────────────┤ ╰Probs

Pattern matching optimization

In addition, qml.transforms.pattern_matching_optimization optimizes a QNode given an identity relation:

with qml.tape.QuantumTape() as identity:

def circuit():
    qml.CZ(wires=[0, 1])
    qml.CZ(wires=[1, 2])
    return qml.expval(qml.PauliX(wires=0))
>>> print(qml.draw(circuit)())
0: ──S⁻¹─╭C────┤  <X>
1: ──Z───╰Z─╭C─┤
2: ──Z──────╰Z─┤

Note that the pattern matching optimizer is able to match every sub-pattern of our identity template, resulting in simplifications such as \(SZ = (S^{-1}S)SZ = S^{-1}(SSZ) = S^{-1}\).

A growing collection of templates 🧩

Alongside the great new features above, we also have new (differentiable!) templates to share for tensor networks and distance measures.

  • Tensor network templates: qml.MERA, the Multi-scale Entanglement Renormalization Ansatz (MERA), joins existing circuit templates that represent the shape and connectivity of tensor networks, including qml.MPS and qml.TTN.
  • Distance measures: qml.HilbertSchmidt and qml.LocalHilbertSchmidt, can be used to compute the distance between two unitaries \(|\text{Tr}(V^\dagger U)|^2\). This provides a convenient cost function for training variational quantum compilation algorithms.


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

  • The parameter-shift Hessian can now be computed for arbitrary operations that support the general parameter-shift rule for gradients, using qml.gradients.param_shift_hessian. Operations have multiple ways of ensuring they are compatible with the parameter-shift Hessian, including by providing a custom grad_recipe, specifying the parameter_frequencies, or defining the generator().
  • lightning.qubit now computes samples natively in C++, leading to performance improvements when using finite shots.
  • The qml.ctrl transform now accepts the optional argument control_values, which can be passed an integer or a list of integers corresponding to the binary value that activates each control.
  • default.qubit, default.mixed, and lightning.qubit now skip over identity operators instead of applying the identity operation to the circuit, resulting in a performance boost.
  • qml.eigvals() now uses the efficient scipy.sparse.linalg.eigsh method for obtaining the eigenvalues of qml.SparseHamiltonian.
  • QuantumTape objects now support iteration over the contained operations and measurements:

    >>> for op in tape:
    ...     # iterate over the tape
    ...     print(op)
    >>> list(tape) # will create a list of all operations
    >>> tape[2]    # operations can be accessed via indexing

    In addition, the QuantumTape.numeric_type() and QuantumTape.shape() return information about the type and shape of numeric results expected after tape execution.

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.


  • The ObservableReturnTypes enumerations, Sample, Variance, Expectation, Probability, State, and MidMeasure, have been moved from pennylane.operation to pennylane.measurements.

Breaking changes

  • The ability to cache execution results on the Device has been removed. The recommended alternative going forward is to use QNode caching via the cache argument.

    Any object that implements the special methods __getitem__(), __setitem__(), and __delitem__(), such as a dictionary, may be used as a QNode cache:

    import pennylane as qml
    cache = {}
    @qml.qnode(dev, cache=cache)
    def circuit1(weights):
    @qml.qnode(dev, cache=cache)
    def circuit2(weights):
  • The deprecated QNode infrastructure, used in PennyLane versions < 0.20 and previously available via qml.qnode_old.QNode, has been removed. Please transition to using the standard qml.QNode.

    In addition, several other components which powered the deprecated QNode have been removed:

    • The deprecated, non-batch compatible interfaces, have been removed.

    • The deprecated tape subclasses QubitParamShiftTape, JacobianTape, CVParamShiftTape, and ReversibleTape have been removed.
  • The deprecated tape execution method tape.execute(device) has been removed. Please use qml.execute([tape], device) instead.
  • The qml.finite_diff function has been removed. Please use qml.gradients.finite_diff to compute the gradient of tapes of QNodes.
  • The get_unitary_matrix transform has been removed, please use qml.matrix instead.
  • The update_stepsize method has been removed from qml.GradientDescentOptimizer and its child optimizers. The stepsize property can be interacted with directly instead.
  • Most optimizers no longer flatten and unflatten arguments during computation. Due to this change, custom gradient functions must return the same shape as qml.grad.
  • The old text-based circuit drawer has been overhauled. Please use qml.drawer.tape_text instead of qml.drawer.CircuitDrawer. For specifying the text-based representation of custom operations, the Operator.label method should be defined. As part of the improvements, the decimals and show_matrices keyword arguments have been added for additional flexibility, and the max_length argument now defaults to 100 characters.

These highlights are just scratching the surface — check out the full release notes for more details.


As always, this release would not have been possible without the hard work of our development team and contributors:

Karim Alaa El-Din, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Alain Delgado, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Angus Lowe, Romain Moyard, Zeyue Niu, Matthew Silverman, Lee James O’Riordan, Chae-Yeun Park, Maria Schuld, Jay Soni, Antal Száva, Trevor Vincent, Maurice Weber, and David Wierichs.