PennyLane v0.26 released

PennyLane team

Version 0.26 of PennyLane is upon us! Brace yourselves for amazing new features, including classical shadows, and qutrits, the QNSPSA optimizer, and more.

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

Classical shadows 👤

The popular classical shadows measurement protocol is now directly built-in to PennyLane. Return classical shadows from your quantum functions, use them to estimate expectation values, and even perform state tomography on hardware.

The classical-shadow measurement protocol is described in detail in the paper Predicting Many Properties of a Quantum System from Very Few Measurements. As part of the support for classical shadows in this release, two new finite-shot (and of course, fully-differentiable) measurements are available.

Measuring classical shadows

The new qml.classical_shadow() measurement will return both bits (0 or 1 if the 1 or -1 eigenvalue is sampled, respectively) and recipes (the randomized Pauli measurements that are performed for each qubit, labelled by integer):

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

@qml.qnode(dev)
def circuit():
    qml.Hadamard(wires=0)
    qml.CNOT(wires=[0, 1])
    return qml.classical_shadow(wires=[0, 1])
>>> bits, recipes = circuit()
>>> bits
tensor([[0, 0],
        [1, 0],
        [0, 1]], dtype=uint8, requires_grad=True)
>>> recipes
tensor([[2, 2],
        [0, 2],
        [0, 2]], dtype=uint8, requires_grad=True)

Estimating expectation values with shadows

The new measurement qml.shadow_expval() returns an estimate of the expectation value estimation using classical shadows:

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

@qml.qnode(dev)
def circuit(x, H):
    qml.Hadamard(0)
    qml.CNOT((0,1))
    qml.RX(x, wires=0)
    return qml.shadow_expval(H)

x = np.array(0.5, requires_grad=True) 
H = qml.Hamiltonian(
        [1., 1.], 
        [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]
    )  
>>> circuit(x, H), 
tensor(1.8486, requires_grad=True) 
>>> qml.grad(circuit)(x, H))
-0.4797000000000001

Fully-differentiable QNode transforms for both new classical-shadows measurements are also available via qml.shadows.shadow_state and qml.shadows.shadow_expval, respectively.

For convenient post-processing, we’ve also added the ability to calculate general Renyi entropies by way of the ClassicalShadow class’ entropy method, which requires the wires of the subsystem of interest and the Renyi entropy order:

>>> shadow = qml.ClassicalShadow(bits, recipes)
>>> vN_entropy = shadow.entropy(wires=[0, 3], alpha=1)

Qutrits: quantum circuits for tertiary degrees of freedom ☘

Good things come in threes!

An entirely new framework for quantum computing is now simulatable with the addition of qutrit functionalities.

Qutrits are like qubits, but they aren’t binary degrees of freedom, they are tertiary. The advent of qutrits allows for all sorts of interesting theoretical, practical, and algorithmic capabilities that have yet to be discovered.

To facilitate qutrit circuits requires a new device: default.qutrit. The default.qutrit device is a Python-based simulator, akin to default.qubit, and is defined as per usual:

dev = qml.device("default.qutrit", wires=2)

@qml.qnode(dev)
def circuit(U, obs):
    qml.TShift(0)
    qml.TClock(0)
    qml.QutritUnitary(U, wires=0)
    return qml.expval(qml.THermitian(obs, wires=0))
>>> U = np.ones([3, 3]) / np.sqrt(3)
>>> obs = np.array([[1, 1, 0], [1, -1, 0], [0, 0, np.sqrt(2)]]) / np.sqrt(2)
>>> circuit(U, obs)
tensor(0.80473785, requires_grad=True)

The following operations are supported on default.qutrit devices:

  • The qutrit shift operator, qml.TShift, and the ternary clock operator, qml.TClock, as defined in this paper by Yeh et al. (2022), which are the qutrit analogs of the Pauli X and Pauli Z operations, respectively.
  • The qml.TAdd and qml.TSWAP operations, which are the qutrit analogs of the CNOT and SWAP operations, respectively.
  • qml.state and qml.probs measurements.
  • Measuring user-specified Hermitian matrix observables via qml.THermitian.

We will continue to add more and more support for qutrits in future releases.

Simplifying just got… simpler 😌

To put it simply, we’ve simplified how qml.simplify simplifies your circuits. Simple as that.

qml.simplify can now perform the following:

  • simplify parametrized operations
  • simplify the adjoint and power of specific operators
  • group like terms in a sum
  • resolve products of Pauli operators
  • combine rotation angles of identical rotation gates

Here is an example of qml.simplify in action with parameterized rotation gates. In this case, the angles of rotation are simplified to be modulo \(4\pi\).

>>> op1 = qml.RX(30.0, wires=0)
>>> qml.simplify(op1)
RX(4.867258771281655, wires=[0])
>>> op2 = qml.RX(4 * np.pi, wires=0)
>>> qml.simplify(op2)
Identity(wires=[0])

All of these simplification features can be applied directly to quantum functions, QNodes, and tapes via decorating with @qml.simplify, as well:

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

@qml.simplify
@qml.qnode(dev)
def circuit():
    qml.adjoint(qml.prod(qml.RX(1, 0) ** 1, qml.RY(1, 0), qml.RZ(1, 0)))
    return qml.probs(wires=0)
>>> print(qml.draw(circuit)())
0: ──RZ(11.57)@RY(11.57)@RX(11.57)─┤  Probs

Enjoy the simple things!

QNSPSA optimizer 💪

Good golly! Another optimizer!

A new optimizer called qml.QNSPSAOptimizer that implements the quantum natural simultaneous perturbation stochastic approximation (QNSPSA) method based on Simultaneous Perturbation Stochastic Approximation of the Quantum Fisher Information is available.

qml.QNSPSAOptimizer is a second-order SPSA algorithm, which combines the convergence power of the quantum-aware Quantum Natural Gradient (QNG) optimization method with the reduced quantum evaluations of SPSA methods.

While the QNSPSA optimizer requires additional circuit executions (10 executions per step) compared to standard SPSA optimization (3 executions per step), these additional evaluations are used to provide a stochastic estimation of a second-order metric tensor, which often helps the optimizer to achieve faster convergence.

Use qml.QNSPSAOptimizer like you would any other optimizer:

max_iterations = 50
opt = qml.QNSPSAOptimizer() 

for _ in range(max_iterations):
    params, cost = opt.step_and_cost(cost, params)

Check out our demo on the QNSPSA optimizer for more information.

Improvements 🛠

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

  • By default, qml.counts only returns the outcomes observed in sampling. Optionally, specifying qml.counts(all_outcomes=True) will return a dictionary containing all possible outcomes.
  • qml.matrix can now compute the matrix of tapes and QNodes that contain multiple broadcasted operations or non-broadcasted operations after broadcasted ones. A common scenario in which this becomes relevant is the decomposition of broadcasted operations.
  • Products and sums of operators created using qml.prod and qml.op_sum now support the sparse_matrix() method, and their matrix computation, sparse or not, is now more efficient.
  • You can now iterate over products and sums of operators created from qml.op_sum and qml.prod.
  • Both lightning.qubit and lightning.gpu now natively support qml.Hamiltonian expectation values in the forward pass. In addition, lightning.gpu also supports qml.Hamiltonian expectation values with the adjoint Jacobian gradient method.
  • The qml.math.expand_matrix() method now allows the sparse matrix representation of an operator to be extended to a larger Hilbert space.

Deprecations and breaking changes 💔

As new things are added, outdated features are removed. Here’s what will be changing in this release:

  • In-place inversion is now deprecated. This includes op.inv() and op.inverse=value. Please use qml.adjoint(op), qml.pow(op, -1), or op ** -1 instead. Note that for unitary operations, qml.adjoint(op) is the most efficient approach.
  • Measuring an operator that might not be hermitian now raises a warning instead of an error. To definitively determine whether or not an operator is hermitian, use qml.is_hermitian.
  • qml.grouping.utils.is_commuting has been removed, and its Pauli word logic is now part of qml.is_commuting.
  • qml.is_commuting has been moved from pennylane.transforms.commutation_dag to pennylane.ops.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:

Ali Asadi, Juan Miguel Arrazola, Utkarsh Azad, Tom Bromley, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Yiheng Duan, Lillian Marie Austin Frederiksen, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Albert Mitjans Coma, Romain Moyard, Rashid N. H. M., Zeyue Niu, Lee James O’Riordan, Mudit Pandey, Chae-Yeun Park, Matthew Silverman, Jay Soni, Antal Száva, Trevor Vincent, Cody Wang, David Wierichs