Release notesยถ

This page contains the release notes for PennyLane.

orphan

Release 0.39.0 (current release)ยถ

New features since last release

Creating spin Hamiltonians on lattices ๐Ÿ’ž

  • Functionality for creating custom Hamiltonians on arbitrary lattices has been added. (#6226) (#6237)

    Hamiltonians beyond the available boiler-plate ones in the qml.spin module can be created with the addition of three new functions:

    • qml.spin.Lattice: a new object for instantiating customized lattices via primitive translation vectors and unit cell parameters,

    • qml.spin.generate_lattice: a utility function for creating standard Lattice objects, including 'chain', 'square', 'rectangle', 'triangle', 'honeycomb', 'kagome', 'lieb', 'cubic', 'bcc', 'fcc', and 'diamond',

    • qml.spin.spin_hamiltonian: generates a spin Hamiltonian object given a Lattice object with custom edges/nodes.

    An example is shown below for a \(3 \times 3\) triangular lattice with open boundary conditions.

    lattice = qml.spin.Lattice(
        n_cells=[3, 3],
        vectors=[[1, 0], [np.cos(np.pi/3), np.sin(np.pi/3)]],
        positions=[[0, 0]],
        boundary_condition=False
    )
    

    We can validate this lattice against qml.spin.generate_lattice('triangle', ...) by checking the lattice_points (the \((x, y)\) coordinates of all sites in the lattice):

    >>> lp = lattice.lattice_points
    >>> triangular_lattice = qml.spin.generate_lattice('triangle', n_cells=[3, 3])
    >>> np.allclose(lp, triangular_lattice.lattice_points)
    True
    

    The edges of the Lattice object are nearest-neighbour by default, where we can add edges by using its add_edge method.

    Optionally, a Lattice object can have interactions and fields endowed to it by specifying values for its custom_edges and custom_nodes keyword arguments. The Hamiltonian can then be extracted with the qml.spin.spin_hamiltonian function. An example is shown below for the transverse-field Ising model Hamiltonian on a \(3 \times 3\) triangular lattice. Note that the custom_edges and custom_nodes keyword arguments only need to be defined for one unit cell repetition.

    edges = [
        (0, 1), (0, 3), (1, 3)
    ]
    
    lattice = qml.spin.Lattice(
        n_cells=[3, 3],
        vectors=[[1, 0], [np.cos(np.pi/3), np.sin(np.pi/3)]],
        positions=[[0, 0]],
        boundary_condition=False,
        custom_edges=[[edge, ("ZZ", -1.0)] for edge in edges],
        custom_nodes=[[i, ("X", -0.5)] for i in range(3*3)],
    )
    
    >>> tfim_ham = qml.spin.transverse_ising('triangle', [3, 3], coupling=1.0, h=0.5)
    >>> tfim_ham == qml.spin.spin_hamiltonian(lattice=lattice)
    True
    
  • More industry-standard spin Hamiltonians have been added in the qml.spin module. (#6174) (#6201)

    Three new industry-standard spin Hamiltonians are now available with PennyLane v0.39:

    These additions accompany qml.spin.heisenberg, qml.spin.transverse_ising, and qml.spin.fermi_hubbard, which were introduced in v0.38.

Calculating Polynomials ๐Ÿ”ข

  • Polynomial functions can now be easily encoded into quantum circuits with qml.OutPoly. (#6320)

    A new template called qml.OutPoly is available, which provides the ability to encode a polynomial function in a quantum circuit. Given a polynomial function \(f(x_1, x_2, \cdots, x_N)\), qml.OutPoly requires:

    • f : a standard Python function that represents \(f(x_1, x_2, \cdots, x_N)\),

    • input_registers (\(\vert x_1 \rangle\), \(\vert x_2 \rangle\), โ€ฆ, \(\vert x_N \rangle\)) : a list/tuple containing Wires objects that correspond to the embedded numeric values of \(x_1, x_2, \cdots, x_N\),

    • output_wires : the Wires for which the numeric value of \(f(x_1, x_2, \cdots, x_N)\) is stored.

    Here is an example of using qml.OutPoly to calculate \(f(x_1, x_2) = 3x_1^2 - x_1x_2\) for \(f(1, 2) = 1\).

    wires = qml.registers({"x1": 1, "x2": 2, "output": 2})
    
    def f(x1, x2):
        return 3 * x1 ** 2 - x1 * x2
    
    @qml.qnode(qml.device("default.qubit", shots = 1))
    def circuit():
        # load values of x1 and x2
        qml.BasisEmbedding(1, wires=wires["x1"])
        qml.BasisEmbedding(2, wires=wires["x2"])
    
        # apply the polynomial
        qml.OutPoly(
            f,
            input_registers = [wires["x1"], wires["x2"]],
            output_wires = wires["output"])
    
        return qml.sample(wires=wires["output"])
    
    >>> circuit()
    array([0, 1])
    

    The result, [0, 1], is the binary representation of \(1\). By default, the result is calculated modulo \(2^\text{len(output_wires)}\) but can be overridden with the mod keyword argument.

Readout Noise ๐Ÿ“ 

  • Readout errors can now be included in qml.NoiseModel and qml.add_noise with the new qml.noise.meas_eq function. (#6321)

    Measurement/readout errors can be specified in a similar fashion to regular gate noise in PennyLane: a newly added Boolean function called qml.noise.meas_eq that accepts a measurement function (e.g., qml.expval, qml.sample, or any other function that can be returned from a QNode) that, when present in the QNode, inserts a noisy operation via qml.noise.partial_wires or a custom noise function. Readout noise in PennyLane also follows the insertion convention, where the specified noise is inserted before the measurement.

    Here is an example of adding qml.PhaseFlip noise to any qml.expval measurement:

    c0 = qml.noise.meas_eq(qml.expval)
    n0 = qml.noise.partial_wires(qml.PhaseFlip, 0.2)
    

    To include this in a qml.NoiseModel, use its meas_map keyword argument:

    # gate-based noise
    c1 = qml.noise.wires_in([0, 2])
    n1 = qml.noise.partial_wires(qml.RY, -0.42)
    
    noise_model = qml.NoiseModel({c1: n1}, meas_map={c0: n0})
    
    >>> noise_model
    NoiseModel({
      WiresIn([0, 2]): RY(phi=-0.42)
    },
    meas_map = {
        MeasEq(expval): PhaseFlip(p=0.2)
    })
    

    qml.noise.meas_eq can also be combined with other Boolean functions in qml.noise via bitwise operators for more versatility.

    To add this noise_model to a circuit, use the qml.add_noise transform as per usual. For example,

    @qml.qnode(qml.device("default.mixed", wires=3))
    def circuit():
        qml.RX(0.1967, wires=0)
        for i in range(3):
            qml.Hadamard(i)
    
        return qml.expval(qml.X(0) @ qml.X(1))
    
    >>> noisy_circuit = qml.add_noise(circuit, noise_model)
    >>> print(qml.draw(noisy_circuit)())
    0: โ”€โ”€RX(0.20)โ”€โ”€RY(-0.42)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€Hโ”€โ”€RY(-0.42)โ”€โ”€PhaseFlip(0.20)โ”€โ”ค โ•ญ<X@X>
    1: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€PhaseFlip(0.20)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ•ฐ<X@X>
    2: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€RY(-0.42)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    >>> print(circuit(), noisy_circuit())
    0.9807168489852615 0.35305806563469433
    

User-friendly decompositions ๐Ÿ“ 

  • A new transform called qml.transforms.decompose has been added to better facilitate the custom decomposition of operators in PennyLane circuits. (#6334)

    Previous to the addition of qml.transforms.decompose, decomposing operators in PennyLane had to be done by specifying a stopping_condition in qml.device.preprocess.decompose. With qml.transforms.decompose, the user-interface for specifying decompositions is much simpler and more versatile.

    Decomposing gates in a circuit can be done a few ways:

    • Specifying a gate_set comprising PennyLane Operators to decompose into:

      from functools import partial
      
      dev = qml.device('default.qubit')
      allowed_gates = {qml.Toffoli, qml.RX, qml.RZ}
      
      @partial(qml.transforms.decompose, gate_set=allowed_gates)
      @qml.qnode(dev)
      def circuit():
          qml.Hadamard(wires=[0])
          qml.Toffoli(wires=[0, 1, 2])
          return qml.expval(qml.Z(0))
      
      >>> print(qml.draw(circuit)())
      0: โ”€โ”€RZ(1.57)โ”€โ”€RX(1.57)โ”€โ”€RZ(1.57)โ”€โ•ญโ—โ”€โ”ค  <Z>
      1: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”ค
      2: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”ค
      
    • Specifying a gate_set that is defined by a rule (Boolean function). For example, one can specify an arbitrary gate set to decompose into, so long as the resulting gates only act on one or two qubits:

      @partial(qml.transforms.decompose, gate_set = lambda op: len(op.wires) <= 2)
      @qml.qnode(dev)
      def circuit():
          qml.Toffoli(wires=[0, 1, 2])
          return qml.expval(qml.Z(0))
      
      >>> print(qml.draw(circuit)())
      0: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€Tโ”€โ”€โ•ญโ—โ”€โ”ค  <Z>
      1: โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€Tโ”€โ•ฐXโ”€โ”€Tโ€ โ”€โ•ฐXโ”€โ”ค
      2: โ”€โ”€Hโ”€โ•ฐXโ”€โ”€Tโ€ โ”€โ•ฐXโ”€โ”€Tโ”€โ•ฐXโ”€โ”€Tโ€ โ”€โ•ฐXโ”€โ”€Tโ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
      
    • Specifying a value for max_expansion. By default, decomposition occurs recursively until the desired gate set is reached, but this can be overridden to control the number of passes.

      phase = 1.0
      target_wires = [0]
      unitary = qml.RX(phase, wires=0).matrix()
      n_estimation_wires = 1
      estimation_wires = range(1, n_estimation_wires + 1)
      
      def qfunc():
          qml.QuantumPhaseEstimation(
              unitary,
              target_wires=target_wires,
              estimation_wires=estimation_wires,
          )
      
      decompose_once = qml.transforms.decompose(qfunc, max_expansion=1)
      decompose_twice = qml.transforms.decompose(qfunc, max_expansion=2)
      
      >>> print(qml.draw(decompose_once)())
      0: โ”€โ”€โ”€โ”€โ•ญU(M0)ยนโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
      1: โ”€โ”€Hโ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€QFTโ€ โ”€โ”ค
      M0 =
      [[0.87758256+0.j         0.        -0.47942554j]
      [0.        -0.47942554j 0.87758256+0.j        ]]
      >>> print(qml.draw(decompose_twice)())
      0: โ”€โ”€RZ(1.57)โ”€โ”€RY(0.50)โ”€โ•ญXโ”€โ”€RY(-0.50)โ”€โ”€RZ(-6.28)โ”€โ•ญXโ”€โ”€RZ(4.71)โ”€โ”ค
      1: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€Hโ€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
      

Improvements ๐Ÿ› 

qjit/jit compatibility and improvements

Quantum just-in-time (qjit) compilation is the compilation of hybrid quantum-classical programs during execution of a program, rather than before execution. PennyLane provides just-in-time compilation with its @qml.qjit decorator, powered by the Catalyst compiler.

  • Several templates are now compatible with qjit:

  • The sample-based measurements now support samples that are JAX tracers, allowing compatiblity with qjit workflows. (#6211)

  • The qml.FABLE template now returns the correct value when jit is enabled. (#6263)

  • qml.metric_tensor is now jit compatible. (#6468)

  • qml.QutritBasisStatePreparation is now jit compatible. (#6308)

  • All PennyLane templates are now unit tested to ensure jit compatibility. (#6309)

Various improvements to fermionic operators

  • qml.fermi.FermiWord and qml.fermi.FermiSentence are now compatible with JAX arrays. (#6324)

    Fermionic operators interacting, in some way, with a JAX array will no longer raise an error. For example:

    >>> import jax.numpy as jnp
    >>> w = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
    >>> jnp.array([3]) * w
    FermiSentence({FermiWord({(0, 0): '+', (1, 1): '-'}): Array([3], dtype=int32)})
    
  • The qml.fermi.FermiWord class now has a shift_operator method to apply anti-commutator relations. (#6196)

    >>> w = qml.fermi.FermiWord({(0, 0): '+', (1, 1): '-'})
    >>> w
    FermiWord({(0, 0): '+', (1, 1): '-'})
    >>> w.shift_operator(0, 1)
    FermiSentence({FermiWord({(0, 1): '-', (1, 0): '+'}): -1})
    
  • The qml.fermi.FermiWord and qml.fermi.FermiSentence classes now an adjoint method. (#6166)

    >>> w = qml.fermi.FermiWord({(0, 0): '+', (1, 1): '-'})
    >>> w.adjoint()
    FermiWord({(0, 1): '+', (1, 0): '-'})
    
  • The to_mat methods for qml.fermi.FermiWord and qml.fermi.FermiSentence now optionally return a sparse matrix with the format keyword argument. (#6173)

    >>> w = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
    >>> w.to_mat(format="dense")
    array([[0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
          [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],
          [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],
          [0.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]])
    >>> w.to_mat(format="csr")
    <4x4 sparse matrix of type '<class 'numpy.complex128'>'
      with 1 stored elements in Compressed Sparse Row format>
    
  • When printed, qml.fermi.FermiWord and qml.fermi.FermiSentence now return a unique representation of the object. (#6167)

    >>> w = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
    >>> print(w)
    aโบ(0) a(1)
    
  • qml.qchem.excitations now optionally returns fermionic operators with a new fermionic keyword argument (defaults to False). (#6171)

    >>> singles, doubles = excitations(electrons, orbitals, fermionic=True)
    >>> print(singles)
    [FermiWord({(0, 0): '+', (1, 2): '-'}), FermiWord({(0, 1): '+', (1, 3): '-'})]
    

A new optimizer

  • A new optimizer called qml.MomentumQNGOptimizer has been added. It inherits from the basic qml.QNGOptimizer optimizer and requires one additional hyperparameter: the momentum coefficient, \(0 \leq \rho < 1\), the default value being \(\rho=0.9\). For \(\rho=0\), qml.MomentumQNGOptimizer reduces down to qml.QNGOptimizer. (#6240)

Other Improvements

  • default.tensor can now handle mid-circuit measurements via the deferred measurement principle. (#6408)

  • process_density_matrix was implemented in 5 StateMeasurement subclasses: ExpVal, Var, Purity, MutualInformation, and VnEntropy. This facilitates future support for mixed-state devices and expanded density matrix operations. Also, a fix was made in the ProbabilityMP class to use qml.math.sqrt instead of np.sqrt. (#6330)

  • The decomposition for qml.Qubitization has been improved to use qml.PrepSelPrep. (#6182)

  • A ReferenceQubit device (shortname: "reference.qubit") has been introduced for testing purposes and referencing for future plugin development. (#6181)

  • qml.transforms.mitigate_with_zne now gives a clearer error message when being applied on circuits, not devices, that include channel noise. (#6346)

  • A new function called get_best_diff_method has been added to qml.workflow. (#6399)

  • A new method called construct_tape has been added to qml.workflow for users to construct single tapes from a QNode. (#6419)

  • An infrastructural improvement was made to PennyLane datasets that allows us to upload datasets more easily. (#6126)

  • qml.Hadamard now has a more friendly alias: qml.H. (#6450)

  • A Boolean keyword argument called show_wire_labels has been added to draw and draw_mpl, which hides wire labels when set to False (the default is True). (#6410)

  • A new function called sample_probs has been added to the qml.devices.qubit and qml.devices.qutrit_mixed modules. This function takes probability distributions as input and returns sampled outcomes, simplifying the sampling process by separating it from other operations in the measurement chain and improving modularity. (#6354)

  • qml.labs has been added to the PennyLane documentation. (#6397) (#6369)

  • A has_sparse_matrix property has been added to Operator to indicate whether a sparse matrix is defined. (#6278) (#6310)

  • qml.matrix now works with empty objects (e.g., empty tapes, QNodes and quantum functions that do not call operations, and single operators with empty decompositions). (#6347)

    dev = qml.device("default.qubit", wires=1)
    
    @qml.qnode(dev)
    def node():
        return qml.expval(qml.Z(0))
    
    >>> qml.matrix(node)()
    array([[1., 0.],
           [0., 1.]])
    
  • PennyLane is now compatible with NumPy 2.0. (#6061) (#6258) (#6342)

  • PennyLane is now compatible with Jax 0.4.28. (#6255)

  • The diagonalize_measurements transform now uses a more efficient method of diagonalization when possible, based on the pauli_rep of the relevant observables. (#6113)

  • The QuantumScript.copy method now takes operations, measurements, shots and trainable_params as keyword arguments. If any of these are passed when copying a tape, the specified attributes will replace the copied attributes on the new tape. (#6285) (#6363)

  • The Hermitian operator now has a compute_sparse_matrix implementation. (#6225)

  • When an observable is repeated on a tape, tape.diagonalizing_gates no longer returns the diagonalizing gates for each instance of the observable. Instead, the diagonalizing gates of each observable on the tape are included just once. (#6288)

  • The number of diagonalizing gates returned in qml.specs now follows the level keyword argument regarding whether the diagonalizing gates are modified by device, instead of always counting unprocessed diagonalizing gates. (#6290)

  • A more sensible error message is raised from a RecursionError encountered when accessing properties and methods of a nested CompositeOp or SProd. (#6375)

    By default, qml.sum and qml.prod set lazy=True, which keeps its operands nested. Given the recursive nature of such structures, if there are too many levels of nesting, a RecursionError would occur when accessing many of the properties and methods.

  • The performance of the decomposition of qml.QFT has been improved. (#6434)

Capturing and representing hybrid programs

  • qml.wires.Wires now accepts JAX arrays as input. In many workflows with JAX, PennyLane may attempt to assign a JAX array to a Wires object, which will cause an error since JAX arrays are not hashable. Now, JAX arrays are valid Wires types. Furthermore, a FutureWarning is no longer raised in JAX 0.4.30+ when providing JAX tracers as input to qml.wires.Wires. (#6312)

  • A new function called qml.capture.make_plxpr has been added to take a function and create a Callable that, when called, will return a PLxPR representation of the input function. (#6326)`

  • Differentiation of hybrid programs via qml.grad and qml.jacobian can now be captured with PLxPR. When evaluating a captured qml.grad (qml.jacobian) instruction, it will dispatch to jax.grad (jax.jacobian), which differs from the Autograd implementation without capture. Pytree inputs and outputs are supported. (#6120) (#6127) (#6134)

  • Unit testing for capturing nested control flow has been improved. (#6111)

  • Some custom primitives for the capture project can now be imported via from pennylane.capture.primitives import *. (#6129)

  • All higher order primitives now use jax.core.Jaxpr as metadata instead of sometimes using jax.core.ClosedJaxpr or jax.core.Jaxpr. (#6319)

Breaking changes ๐Ÿ’”

  • Red-herring validation in QNode.construct has been removed, which fixed a bug with qml.GlobalPhase. (#6373)

    Removing the AllWires validation in QNode.construct was addressed as a solution to the following example not being able to run:

    @qml.qnode(qml.device('default.qubit', wires=2))
    def circuit(x):
        qml.GlobalPhase(x, wires=0)
        return qml.state()
    
    >>> circuit(0.5)
    array([0.87758256-0.47942554j, 0.        +0.j        ,
         1.        +0.j        , 0.        +0.j        ])
    
  • The simplify argument in qml.Hamiltonian and qml.ops.LinearCombination has been removed. Instead, qml.simplify() can be called on the constructed operator. (#6279)

  • The functions qml.qinfo.classical_fisher and qml.qinfo.quantum_fisher have been removed and migrated to the qml.gradients module. qml.gradients.classical_fisher and qml.gradients.quantum_fisher should be used instead. (#5911)

  • Python 3.9 is no longer supported. Please update to 3.10 or newer. (#6223)

  • default.qubit.legacy, default.qubit.tf, default.qubit.torch, default.qubit.jax, and default.qubit.autograd have been removed. Please use default.qubit for all interfaces. (#6207) (#6208) (#6209) (#6210) (#6266)

  • expand_fn, max_expansion, override_shots, and device_batch_transform have been removed from the signature of qml.execute. (#6203)

  • max_expansion and expansion_strategy have been removed from the QNode. (#6203)

  • expansion_strategy has been removed from qml.draw, qml.draw_mpl, and qml.specs. max_expansion has been removed from qml.specs, as it had no impact on the output. (#6203)

  • qml.transforms.hamiltonian_expand and qml.transforms.sum_expand have been removed. Please use qml.transforms.split_non_commuting instead. (#6204)

  • The decomp_depth keyword argument to qml.device has been removed. (#6234)

  • Operator.expand has been removed. Please use qml.tape.QuantumScript(op.decomposition()) instead. (#6227)

  • The native folding method qml.transforms.fold_global for the qml.transforms.mitigate_with_zne transform no longer expands the circuit automatically. Instead, the user should apply qml.transforms.decompose to decompose a circuit into a target gate set before applying fold_global or mitigate_with_zne. (#6382)

  • The LightningVJPs class has been removed, as all lightning devices now follow the new device interface. (#6420)

Deprecations ๐Ÿ‘‹

  • The expand_depth and max_expansion arguments for qml.transforms.compile and qml.transforms.decompositions.clifford_t_decomposition respectively have been deprecated. (#6404)

  • Legacy operator arithmetic has been deprecated. This includes qml.ops.Hamiltonian, qml.operation.Tensor, qml.operation.enable_new_opmath, qml.operation.disable_new_opmath, and qml.operation.convert_to_legacy_H. Note that when new operator arithmetic is enabled, qml.Hamiltonian will continue to dispatch to qml.ops.LinearCombination; this behaviour is not deprecated. For more information, check out the updated operator troubleshooting page. (#6287) (#6365)

  • qml.pauli.PauliSentence.hamiltonian and qml.pauli.PauliWord.hamiltonian have been deprecated. Instead, please use qml.pauli.PauliSentence.operation and qml.pauli.PauliWord.operation, respectively. (#6287)

  • qml.pauli.simplify() has been deprecated. Instead, please use qml.simplify(op) or op.simplify(). (#6287)

  • The qml.BasisStatePreparation template has been deprecated. Instead, use qml.BasisState. (#6021)

  • The 'ancilla' argument for qml.iterative_qpe has been deprecated. Instead, use the 'aux_wire' argument. (#6277)

  • qml.shadows.shadow_expval has been deprecated. Instead, use the qml.shadow_expval measurement process. (#6277)

  • qml.broadcast has been deprecated. Please use Python for loops instead. (#6277)

  • The qml.QubitStateVector template has been deprecated. Instead, use qml.StatePrep. (#6172)

  • The qml.qinfo module has been deprecated. Please see the respective functions in the qml.math and qml.measurements modules instead. (#5911)

  • Device, QubitDevice, and QutritDevice will no longer be accessible via top-level import in v0.40. They will still be accessible as qml.devices.LegacyDevice, qml.devices.QubitDevice, and qml.devices.QutritDevice respectively. (#6238)

  • QNode.gradient_fn has been deprecated. Please use QNode.diff_method and QNode.get_gradient_fn instead. (#6244)

Documentation ๐Ÿ“

  • Updated qml.spin documentation. (#6387)

  • Updated links to PennyLane.ai in the documentation to use the latest URL format, which excludes the .html prefix. (#6412)

  • Update qml.Qubitization documentation based on new decomposition. (#6276)

  • Fixed examples in the documentation of a few optimizers. (#6303) (#6315)

  • Corrected examples in the documentation of qml.jacobian. (#6283) (#6315)

  • Fixed spelling in a number of places across the documentation. (#6280)

  • Add work_wires parameter to qml.MultiControlledX docstring signature. (#6271)

  • Removed ambiguity in error raised by the PauliRot class. (#6298)

  • Renamed an incorrectly named test in test_pow_ops.py. (#6388)

  • Removed outdated Docker description from installation page. (#6487)

Bug fixes ๐Ÿ›

  • The wire order for Snapshotโ€˜s now matches the wire order of the device, rather than the simulation. (#6461)

  • Fixed a bug where QNSPSAOptimizer, QNGOptimizer and MomentumQNGOptimizer calculate invalid parameter updates if the metric tensor becomes singular. (#6471)

  • The default.qubit device now supports parameter broadcasting with qml.classical_shadow and qml.shadow_expval. (#6301)

  • Fixed unnecessary call of eigvals in qml.ops.op_math.decompositions.two_qubit_unitary.py that was causing an error in VJP. Raises warnings to users if this essentially nondifferentiable module is used. (#6437)

  • Patches the math module to function with autoray 0.7.0. (#6429)

  • Fixed incorrect differentiation of PrepSelPrep when using diff_method="parameter-shift". (#6423)

  • The validate_device_wires transform now raises an error if abstract wires are provided. (#6405)

  • Fixed qml.math.expand_matrix for qutrit and arbitrary qudit operators. (#6398)

  • MeasurementValue now raises an error when it is used as a boolean. (#6386)

  • default.qutrit now returns integer samples. (#6385)

  • adjoint_metric_tensor now works with circuits containing state preparation operations. (#6358)

  • quantum_fisher now respects the classical Jacobian of QNodes. (#6350)

  • qml.map_wires can now be applied to a batch of tapes. (#6295)

  • Fixed float-to-complex casting in various places across PennyLane. (#6260) (#6268)

  • Fixed a bug where zero-valued JVPs were calculated wrongly in the presence of shot vectors. (#6219)

  • Fixed qml.PrepSelPrep template to work with torch. (#6191)

  • Fixed a bug where qml.equal now correctly compares qml.PrepSelPrep operators. (#6182)

  • The qml.QSVT template now orders the projector wires first and the UA wires second, which is the expected order of the decomposition. (#6212)

  • The qml.Qubitization template now orders the control wires first and the hamiltonian wires second, which is the expected according to other templates. (#6229)

  • Fixed a bug where a circuit using the autograd interface sometimes returns nested values that are not of the autograd interface. (#6225)

  • Fixed a bug where a simple circuit with no parameters or only builtin/NumPy arrays as parameters returns autograd tensors. (#6225)

  • qml.pauli.PauliVSpace now uses a more stable SVD-based linear independence check to avoid running into LinAlgError: Singular matrix. This stabilizes the usage of qml.lie_closure. It also introduces normalization of the basis vectorโ€™s internal representation _M to avoid exploding coefficients. (#6232)

  • Fixed a bug where csc_dot_product is used during measurement for Sum/Hamiltonian that contains observables that does not define a sparse matrix. (#6278) (#6310)

  • Fixed a bug where None was added to the wires in qml.PhaseAdder, qml.Adder and qml.OutAdder. (#6360)

  • Fixed a test after updating to the nightly version of Catalyst. (#6362)

  • Fixed a bug where CommutingEvolution with a trainable Hamiltonian cannot be differentiated using parameter shift. (#6372)

  • Fixed a bug where mitigate_with_zne loses the shots information of the original tape. (#6444)

  • Fixed a bug where default.tensor raises an error when applying Identity/GlobalPhase on no wires, and PauliRot/MultiRZ on a single wire. (#6448)

  • Fixes a bug where applying qml.ctrl and qml.adjoint on an operator type instead of an operator instance results in extra operators in the queue. (#6284)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Utkarsh Azad, Oleksandr Borysenko, Astral Cai, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, Anthony Hayes, Austin Huang, Soran Jahangiri, Jacob Kitchen, Korbinian Kottmann, Christina Lee, William Maxwell, Erick Ochoa Lopez, Lee J. Oโ€™Riordan, Mudit Pandey, Andrija Paurevic, Alex Preciado, Ashish Kanwar Singh, David Wierichs,

orphan

Release 0.38.0ยถ

New features since last release

Registers of wires ๐Ÿงธ

  • A new function called qml.registers has been added that lets you seamlessly create registers of wires. (#5957) (#6102)

    Using registers, it is easier to build large algorithms and circuits by applying gates and operations to predefined collections of wires. With qml.registers, you can create registers of wires by providing a dictionary whose keys are register names and whose values are the number of wires in each register.

    >>> wire_reg = qml.registers({"alice": 4, "bob": 3})
    >>> wire_reg
    {'alice': Wires([0, 1, 2, 3]), 'bob': Wires([4, 5, 6])}
    

    The resulting data structure of qml.registers is a dictionary with the same register names as keys, but the values are qml.wires.Wires instances.

    Nesting registers within other registers can be done by providing a nested dictionary, where the ordering of wire labels is based on the order of appearance and nestedness.

    >>> wire_reg = qml.registers({"alice": {"alice1": 1, "alice2": 2}, "bob": {"bob1": 2, "bob2": 1}})
    >>> wire_reg
    {'alice1': Wires([0]), 'alice2': Wires([1, 2]), 'alice': Wires([0, 1, 2]), 'bob1': Wires([3, 4]), 'bob2': Wires([5]), 'bob': Wires([3, 4, 5])}
    

    Since the values of the dictionary are Wires instances, their use within quantum circuits is very similar to that of a list of integers.

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev)
    def circuit():
        for w in wire_reg["alice"]:
            qml.Hadamard(w)
    
        for w in wire_reg["bob1"]:
            qml.RX(0.1967, wires=w)
    
        qml.CNOT(wires=[wire_reg["alice1"][0], wire_reg["bob2"][0]])
    
        return [qml.expval(qml.Y(w)) for w in wire_reg["bob1"]]
    
    print(qml.draw(circuit)())
    
    0: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”ค
    1: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”ค
    2: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”ค
    3: โ”€โ”€RX(0.20)โ”€โ”‚โ”€โ”€โ”ค  <Y>
    4: โ”€โ”€RX(0.20)โ”€โ”‚โ”€โ”€โ”ค  <Y>
    5: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”ค
    

    In tandem with qml.registers, weโ€™ve also made the following improvements to qml.wires.Wires:

    • Wires instances now have a more copy-paste friendly representation when printed. (#5958)

      >>> from pennylane.wires import Wires
      >>> w = Wires([1, 2, 3])
      >>> w
      Wires([1, 2, 3])
      
    • Python set-based combinations are now supported by Wires. (#5983)

      This new feature unlocks the ability to combine Wires instances in the following ways:

      • intersection with & or intersection():

        >>> wires1 = Wires([1, 2, 3])
        >>> wires2 = Wires([2, 3, 4])
        >>> wires1.intersection(wires2) # or wires1 & wires2
        Wires([2, 3])
        
      • symmetric difference with ^ or symmetric_difference():

        >>> wires1.symmetric_difference(wires2) # or wires1 ^ wires2
        Wires([1, 4])
        
      • union with | or union():

        >>> wires1.union(wires2) # or wires1 | wires2
        Wires([1, 2, 3, 4])
        
      • difference with - or difference():

        >>> wires1.difference(wires2) # or wires1 - wires2
        Wires([1])
        

Quantum arithmetic operations ๐Ÿงฎ

  • Several new operator templates have been added to PennyLane that let you perform quantum arithmetic operations. (#6109) (#6112) (#6121)

    • qml.Adder performs in-place modular addition: \(\text{Adder}(k, m)\vert x \rangle = \vert x + k \; \text{mod} \; m\rangle\).

    • qml.PhaseAdder is similar to qml.Adder, but it performs in-place modular addition in the Fourier basis.

    • qml.Multiplier performs in-place multiplication: \(\text{Multiplier}(k, m)\vert x \rangle = \vert x \times k \; \text{mod} \; m \rangle\).

    • qml.OutAdder performs out-place modular addition: \(\text{OutAdder}(m)\vert x \rangle \vert y \rangle \vert b \rangle = \vert x \rangle \vert y \rangle \vert b + x + y \; \text{mod} \; m \rangle\).

    • qml.OutMultiplier performs out-place modular multiplication: \(\text{OutMultiplier}(m)\vert x \rangle \vert y \rangle \vert b \rangle = \vert x \rangle \vert y \rangle \vert b + x \times y \; \text{mod} \; m \rangle\).

    • qml.ModExp performs modular exponentiation: \(\text{ModExp}(base, m) \vert x \rangle \vert k \rangle = \vert x \rangle \vert k \times base^x \; \text{mod} \; m \rangle\).

    Here is a comprehensive example that performs the following calculation: (2 + 1) * 3 mod 7 = 2 (or 010 in binary).

    dev = qml.device("default.qubit", shots=1)
    
    wire_reg = qml.registers({
        "x_wires": 2, # |x>: stores the result of 2 + 1 = 3
        "y_wires": 2, # |y>: multiples x by 3
        "output_wires": 3, # stores the result of (2 + 1) * 3 m 7 = 2
        "work_wires": 2 # for qml.OutMultiplier
    })
    
    @qml.qnode(dev)
    def circuit():
        # In-place addition
        qml.BasisEmbedding(2, wires=wire_reg["x_wires"])
        qml.Adder(1, x_wires=wire_reg["x_wires"]) # add 1 to wires [0, 1]
    
        # Out-place multiplication
        qml.BasisEmbedding(3, wires=wire_reg["y_wires"])
        qml.OutMultiplier(
            wire_reg["x_wires"],
            wire_reg["y_wires"],
            wire_reg["output_wires"],
            work_wires=wire_reg["work_wires"],
            mod=7
        )
    
        return qml.sample(wires=wire_reg["output_wires"])
    
    >>> circuit()
    array([0, 1, 0])
    

Converting noise models from Qiskit โ™ป๏ธ

  • Convert Qiskit noise models into a PennyLane NoiseModel with qml.from_qiskit_noise. (#5996)

    In the last few releases, weโ€™ve added substantial improvements and new features to the Pennylane-Qiskit plugin. With this release, a new qml.from_qiskit_noise function allows you to convert a Qiskit noise model into a PennyLane NoiseModel. Here is a simple example with two quantum errors that add two different depolarizing errors based on the presence of different gates in the circuit:

    import pennylane as qml
    import qiskit_aer.noise as noise
    
    error_1 = noise.depolarizing_error(0.001, 1) # 1-qubit noise
    error_2 = noise.depolarizing_error(0.01, 2) # 2-qubit noise
    
    noise_model = noise.NoiseModel()
    
    noise_model.add_all_qubit_quantum_error(error_1, ['rz', 'ry'])
    noise_model.add_all_qubit_quantum_error(error_2, ['cx'])
    
    >>> qml.from_qiskit_noise(noise_model)
    NoiseModel({
      OpIn(['RZ', 'RY']): QubitChannel(num_kraus=4, num_wires=1)
      OpIn(['CNOT']): QubitChannel(num_kraus=16, num_wires=2)
    })
    

    Under the hood, PennyLane converts each quantum error in the Qiskit noise model into an equivalent qml.QubitChannel operator with the same canonical Kraus representation. Currently, noise models in PennyLane do not support readout errors. As such, those will be skipped during conversion if they are present in the Qiskit noise model.

    Make sure to pip install pennylane-qiskit to access this new feature!

Substantial upgrades to mid-circuit measurements using tree-traversal ๐ŸŒณ

  • The "tree-traversal" algorithm for mid-circuit measurements (MCMs) on default.qubit has been internally redesigned for better performance. (#5868)

    In the last release (v0.37), we introduced the tree-traversal MCM method, which was implemented in a recursive way for simplicity. However, this had the unintended consequence of very deep stack calls for circuits with many MCMs, resulting in stack overflows in some cases. With this release, weโ€™ve refactored the implementation of the tree-traversal method into an iterative approach, which solves those inefficiencies when many MCMs are present in a circuit.

  • The tree-traversal algorithm is now compatible with analytic-mode execution (shots=None). (#5868)

    dev = qml.device("default.qubit")
    
    n_qubits = 5
    
    @qml.qnode(dev, mcm_method="tree-traversal")
    def circuit():
        for w in range(n_qubits):
            qml.Hadamard(w)
    
        for w in range(n_qubits - 1):
            qml.CNOT(wires=[w, w+1])
    
        for w in range(n_qubits):
            m = qml.measure(w)
            qml.cond(m == 1, qml.RX)(0.1967 * (w + 1), w)
    
        return [qml.expval(qml.Z(w)) for w in range(n_qubits)]
    
    >>> circuit()
    [tensor(0.00964158, requires_grad=True),
     tensor(0.03819446, requires_grad=True),
     tensor(0.08455748, requires_grad=True),
     tensor(0.14694258, requires_grad=True),
     tensor(0.2229438, requires_grad=True)]
    

Improvements ๐Ÿ› 

Creating spin Hamiltonians

  • Three new functions are now available for creating commonly-used spin Hamiltonians in PennyLane: (#6106) (#6128)

    Each Hamiltonian can be instantiated by specifying a lattice, the number of unit cells, n_cells, and the Hamiltonian parameters as keyword arguments. Here is an example with the transverse-field Ising model:

    >>> tfim_ham = qml.spin.transverse_ising(lattice="square", n_cells=[2, 2], coupling=0.5, h=0.2)
    >>> tfim_ham
    (
        -0.5 * (Z(0) @ Z(1))
      + -0.5 * (Z(0) @ Z(2))
      + -0.5 * (Z(1) @ Z(3))
      + -0.5 * (Z(2) @ Z(3))
      + -0.2 * X(0)
      + -0.2 * X(1)
      + -0.2 * X(2)
      + -0.2 * X(3)
    )
    

    The resulting object is a qml.Hamiltonian instance, making it easy to use in circuits like the following.

    dev = qml.device("default.qubit", shots=1)
    
    @qml.qnode(dev)
    def circuit():
        return qml.expval(tfim_ham)
    
    >>> circuit()
    -2.0
    

    More features will be added to the qml.spin module in the coming releases, so stay tuned!

A Prep-Select-Prep template

  • A new template called qml.PrepSelPrep has been added that implements a block-encoding of a linear combination of unitaries. (#5756) (#5987)

    This operator acts as a nice wrapper for having to perform qml.StatePrep, qml.Select, and qml.adjoint(qml.StatePrep) in succession, which is quite common in many quantum algorithms (e.g., LCU and block encoding). Here is an example showing the equivalence between using qml.PrepSelPrep and qml.StatePrep, qml.Select, and qml.adjoint(qml.StatePrep).

    coeffs = [0.3, 0.1]
    alphas = (np.sqrt(coeffs) / np.linalg.norm(np.sqrt(coeffs)))
    unitaries = [qml.X(2), qml.Z(2)]
    
    lcu = qml.dot(coeffs, unitaries)
    control = [0, 1]
    
    def prep_sel_prep(alphas, unitaries):
        qml.StatePrep(alphas, wires=control, pad_with=0)
        qml.Select(unitaries, control=control)
        qml.adjoint(qml.StatePrep)(alphas, wires=control, pad_with=0)
    
    @qml.qnode(qml.device("default.qubit"))
    def circuit(lcu, control, alphas, unitaries):
        qml.PrepSelPrep(lcu, control)
        qml.adjoint(prep_sel_prep)(alphas, unitaries)
        return qml.state()
    
    >>> import numpy as np
    >>> np.round(circuit(lcu, control, alphas, unitaries), decimals=2)
    tensor([1.+0.j -0.+0.j -0.+0.j -0.+0.j  0.+0.j  0.+0.j  0.+0.j  0.+0.j], requires_grad=True)
    

QChem improvements

  • Molecules and Hamiltonians can now be constructed for all the elements present in the periodic table. (#5821)

    This new feature is made possible by integrating with the basis-set-exchange package. If loading basis sets from basis-set-exchange is needed for your molecule, make sure that you pip install basis-set-exchange and set load_data=True.

    symbols  = ['Ti', 'Ti']
    geometry = np.array([[0.0, 0.0, -1.1967],
                        [0.0, 0.0,  1.1967]], requires_grad=True)
    mol = qml.qchem.Molecule(symbols, geometry, load_data=True)
    
    >>> mol.n_electrons
    44
    
  • qml.UCCSD now accepts an additional optional argument, n_repeats, which defines the number of times the UCCSD template is repeated. This can improve the accuracy of the template by reducing the Trotter error, but would result in deeper circuits. (#5801)

  • The qml.qchem.qubit_observable function has been modified to return an ascending wire order for molecular Hamiltonians. (#5950)

  • A new method called to_mat has been added to the qml.FermiWord and qml.FermiSentence classes, which allows for computing the matrix representation of these Fermi operators. (#5920)

Improvements to operators

  • qml.GlobalPhase now supports parameter broadcasting. (#5923)

  • qml.Hermitian now has a compute_decomposition method. (#6062)

  • The implementation of qml.PhaseShift, qml.S, and qml.T has been improved, resulting in faster circuit execution times. (#5876)

  • The qml.CNOT operator no longer decomposes into itself. Instead, it raises a qml.DecompositionUndefinedError. (#6039)

Mid-circuit measurements

  • The qml.dynamic_one_shot transform now supports circuits using the "tensorflow" interface. (#5973)

  • If the conditional does not include a mid-circuit measurement, then qml.cond will automatically evaluate conditionals using standard Python control flow. (#6016)

    This allows qml.cond to be used to represent a wider range of conditionals:

    dev = qml.device("default.qubit", wires=1)
    
    @qml.qnode(dev)
    def circuit(x):
        c = qml.cond(x > 2.7, qml.RX, qml.RZ)
        c(x, wires=0)
        return qml.probs(wires=0)
    
    >>> print(qml.draw(circuit)(3.8))
    0: โ”€โ”€RX(3.80)โ”€โ”ค  Probs
    >>> print(qml.draw(circuit)(0.54))
    0: โ”€โ”€RZ(0.54)โ”€โ”ค  Probs
    

Transforms

  • qml.transforms.single_qubit_fusion and qml.transforms.merge_rotations now respect global phases. (#6031)

  • A new transform called qml.transforms.diagonalize_measurements has been added. This transform converts measurements to the computational basis by applying the relevant diagonalizing gates. It can be set to diagonalize only a subset of the base observables {qml.X, qml.Y, qml.Z, qml.Hadamard}. (#5829)

  • A new transform called split_to_single_terms has been added. This transform splits expectation values of sums into multiple single-term measurements on a single tape, providing better support for simulators that can handle non-commuting observables but donโ€™t natively support multi-term observables. (#5884)

  • New functionality has been added to natively support exponential extrapolation when using qml.transforms.mitigate_with_zne. This allows users to have more control over the error mitigation protocol without needing to add further dependencies. (#5972)

Capturing and representing hybrid programs

  • qml.for_loop now supports range-like syntax with default step=1. (#6068)

  • Applying adjoint and ctrl to a quantum function can now be captured into plxpr. Furthermore, the qml.cond function can be captured into plxpr. (#5966) (#5967) (#5999) (#6058)

  • During experimental program capture, functions that accept and/or return pytree structures can now be handled in the qml.QNode call, qml.cond, qml.for_loop and qml.while_loop. (#6081)

  • During experimental program capture, QNodes can now use closure variables. (#6052)

  • Mid-circuit measurements can now be captured with qml.capture enabled. (#6015)

  • qml.for_loop can now be captured into plxpr. (#6041) (#6064)

  • qml.for_loop and qml.while_loop now fall back to standard Python control flow if @qjit is not present, allowing the same code to work with and without @qjit without any rewrites. (#6014)

    dev = qml.device("lightning.qubit", wires=3)
    
    @qml.qnode(dev)
    def circuit(x, n):
    
        @qml.for_loop(0, n, 1)
        def init_state(i):
            qml.Hadamard(wires=i)
    
        init_state()
    
        @qml.for_loop(0, n, 1)
        def apply_operations(i, x):
            qml.RX(x, wires=i)
    
            @qml.for_loop(i + 1, n, 1)
            def inner(j):
                qml.CRY(x**2, [i, j])
    
            inner()
            return jnp.sin(x)
    
        apply_operations(x)
        return qml.probs()
    
    >>> print(qml.draw(circuit)(0.5, 3))
    0: โ”€โ”€Hโ”€โ”€RX(0.50)โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  Probs
    1: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐRY(0.25)โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€RX(0.48)โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  Probs
    2: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐRY(0.25)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐRY(0.23)โ”€โ”€RX(0.46)โ”€โ”ค  Probs
    >>> circuit(0.5, 3)
    array([0.125     , 0.125     , 0.09949758, 0.15050242, 0.07594666,
         0.11917543, 0.08942104, 0.21545687])
    >>> qml.qjit(circuit)(0.5, 3)
    Array([0.125     , 0.125     , 0.09949758, 0.15050242, 0.07594666,
         0.11917543, 0.08942104, 0.21545687], dtype=float64)
    

Community contributions ๐Ÿฅณ

  • Fixed a bug in qml.ThermalRelaxationError where there was a typo from tq to tg. (#5988)

  • Readout error has been added using parameters readout_relaxation_probs and readout_misclassification_probs on the default.qutrit.mixed device. These parameters add a qml.QutritAmplitudeDamping and a qml.TritFlip channel, respectively, after measurement diagonalization. The amplitude damping error represents the potential for relaxation to occur during longer measurements. The trit flip error represents misclassification during readout. (#5842)

  • qml.ops.qubit.BasisStateProjector now has a compute_sparse_matrix method that computes the sparse CSR matrix representation of the projector onto the given basis state. (#5790)

Other improvements

  • qml.pauli.group_observables now uses rustworkx colouring algorithms to solve the Minimum Clique Cover problem, resulting in orders of magnitude performance improvements. (#6043)

    This adds two new options for the method argument: dsatur (degree of saturation) and gis (independent set). In addition, the creation of the adjacency matrix now takes advantage of the symplectic representation of the Pauli observables.

    Additionally, a new function called qml.pauli.compute_partition_indices has been added to calculate the indices from the partitioned observables more efficiently. These changes improve the wall time of qml.LinearCombination.compute_grouping and the grouping_type='qwc' by orders of magnitude.

  • qml.counts measurements with all_outcomes=True can now be used with JAX jitting. Additionally, measurements broadcasted across all available wires (e.g., qml.probs()) can now be used with JAX jit and devices that allow dynamic numbers of wires (only 'default.qubit' currently). (#6108)

  • qml.ops.op_math.ctrl_decomp_zyz can now decompose special unitaries with multiple control wires. (#6042)

  • A new method called process_density_matrix has been added to the ProbabilityMP and DensityMatrixMP measurement processes, allowing for more efficient handling of quantum density matrices, particularly with batch processing support. This method simplifies the calculation of probabilities from quantum states represented as density matrices. (#5830)

  • SProd.terms now flattens out the terms if the base is a multi-term observable. (#5885)

  • qml.QNGOptimizer now supports cost functions with multiple arguments, updating each argument independently. (#5926)

  • semantic_version has been removed from the list of required packages in PennyLane. (#5836)

  • qml.devices.LegacyDeviceFacade has been added to map the legacy devices to the new device interface, making it easier for developers to develop legacy devices. (#5927)

  • StateMP.process_state now defines rules in cast_to_complex for complex casting, avoiding a superfluous statevector copy in PennyLane-Lightning simulations. (#5995)

  • QuantumScript.hash is now cached, leading to performance improvements. (#5919)

  • Observable validation for default.qubit is now based on execution mode (analytic vs. finite shots) and measurement type (sample measurement vs. state measurement). This improves our error handling when, for example, non-hermitian operators are given to qml.expval. (#5890)

  • A new is_leaf parameter has been added to the function flatten in the qml.pytrees module. This is to allow for node flattening to be stopped for any node where the is_leaf optional argument evaluates to being True. (#6107)

  • A progress bar has been added to qml.data.load() when downloading a dataset. (#5560)

  • Upgraded and simplified StatePrep and AmplitudeEmbedding templates. (#6034) (#6170)

  • Upgraded and simplified BasisState and BasisEmbedding templates. (#6021)

Breaking changes ๐Ÿ’”

  • MeasurementProcess.shape(shots: Shots, device:Device) is now MeasurementProcess.shape(shots: Optional[int], num_device_wires:int = 0). This has been done to allow for jitting when a measurement is broadcasted across all available wires, but the device does not specify wires. (#6108)

  • If the shape of a probability measurement is affected by a Device.cutoff property, it will no longer work with jitting. (#6108)

  • qml.GlobalPhase is considered non-differentiable with tape transforms. As a consequence, qml.gradients.finite_diff and qml.gradients.spsa_grad no longer support differentiating qml.GlobalPhase with state-based outputs. (#5620)

  • The CircuitGraph.graph rustworkx graph now stores indices into the circuit as the node labels, instead of the operator/ measurement itself. This allows the same operator to occur multiple times in the circuit. (#5907)

  • The queue_idx attribute has been removed from the Operator, CompositeOp, and SymbolicOp classes. (#6005)

  • qml.from_qasm no longer removes measurements from the QASM code. Use measurements=[] to remove measurements from the original circuit. (#5982)

  • qml.transforms.map_batch_transform has been removed, since transforms can be applied directly to a batch of tapes. See qml.transform for more information. (#5981)

  • QuantumScript.interface has been removed. (#5980)

Deprecations ๐Ÿ‘‹

  • The decomp_depth argument in qml.device has been deprecated. (#6026)

  • The max_expansion argument in qml.QNode has been deprecated. (#6026)

  • The expansion_strategy attribute qml.QNode has been deprecated. (#5989)

  • The expansion_strategy argument has been deprecated in all of qml.draw, qml.draw_mpl, and qml.specs. The level argument should be used instead. (#5989)

  • Operator.expand has been deprecated. Users should simply use qml.tape.QuantumScript(op.decomposition()) for equivalent behaviour. (#5994)

  • qml.transforms.sum_expand and qml.transforms.hamiltonian_expand have been deprecated. Users should instead use qml.transforms.split_non_commuting for equivalent behaviour. (#6003)

  • The expand_fn argument in qml.execute has been deprecated. Instead, please create a qml.transforms.core.TransformProgram with the desired preprocessing and pass it to the transform_program argument of qml.execute. (#5984)

  • The max_expansion argument in qml.execute has been deprecated. Instead, please use qml.devices.preprocess.decompose with the desired expansion level, add it to a qml.transforms.core.TransformProgram and pass it to the transform_program argument of qml.execute. (#5984)

  • The override_shots argument in qml.execute has been deprecated. Instead, please add the shots to the QuantumTapes to be executed. (#5984)

  • The device_batch_transform argument in qml.execute has been deprecated. Instead, please create a qml.transforms.core.TransformProgram with the desired preprocessing and pass it to the transform_program argument of qml.execute. (#5984)

  • qml.qinfo.classical_fisher and qml.qinfo.quantum_fisher have been deprecated. Instead, use qml.gradients.classical_fisher and qml.gradients.quantum_fisher. (#5985)

  • The legacy devices default.qubit.{autograd,torch,tf,jax,legacy} have been deprecated. Instead, use default.qubit, as it now supports backpropagation through the several backends. (#5997)

  • The logic for internally switching a device for a different backpropagation compatible device is now deprecated, as it was in place for the deprecated default.qubit.legacy. (#6032)

Documentation ๐Ÿ“

  • The docstring for qml.qinfo.quantum_fisher, regarding the internally used functions and potentially required auxiliary wires, has been improved. (#6074)

  • The docstring for QuantumScript.expand and qml.tape.tape.expand_tape has been improved. (#5974)

Bug fixes ๐Ÿ›

  • The sparse matrix can now be computed for a product operator when one operand is a GlobalPhase on no wires. (#6197)

  • For default.qubit, JAX is now used for sampling whenever the state is a JAX array. This fixes normalization issues that can occur when the state uses 32-bit precision. (#6190)

  • Fix Pytree serialization of operators with empty shot vectors (#6155)

  • Fixes an error in the dynamic_one_shot transform when used with sampling a single shot. (#6149)

  • qml.transforms.pattern_matching_optimization now preserves the tape measurements. (#6153)

  • qml.transforms.broadcast_expand no longer squeezes out batch sizes of size 1, as a batch size of 1 is still a batch size. (#6147)

  • Catalyst replaced argnum with argnums in gradient related functions, therefore we updated the Catalyst calls to those functions in PennyLane. (#6117)

  • fuse_rot_angles now returns NaN instead of incorrect derivatives at singular points. (#6031)

  • qml.GlobalPhase and qml.Identity can now be captured with plxpr when acting on no wires. (#6060)

  • Fixed jax.grad and jax.jit to work for qml.AmplitudeEmbedding, qml.StatePrep and qml.MottonenStatePreparation. (#5620)

  • Fixed a bug in qml.center that omitted elements from the center if they were linear combinations of input elements. (#6049)

  • Fix a bug where the global phase returned by one_qubit_decomposition gained a broadcasting dimension. (#5923)

  • Fixed a bug in qml.SPSAOptimizer that ignored keyword arguments in the objective function. (#6027)

  • Fixed dynamic_one_shot for use with devices using the old device API, since override_shots was deprecated. (#6024)

  • CircuitGraph can now handle circuits with the same operation instance occuring multiple times. (#5907)

  • qml.QSVT has been updated to store wire order correctly. (#5959)

  • qml.devices.qubit.measure_with_samples now returns the correct result if the provided measurements contain a sum of operators acting on the same wire. (#5978)

  • qml.AmplitudeEmbedding has better support for features using low precision integer data types. (#5969)

  • qml.BasisState and qml.BasisEmbedding now works with jax.jit, lightning.qubit, and give the correct decomposition. (#6021)

  • Jacobian shape has been fixed for measurements with dimension in qml.gradients.vjp.compute_vjp_single. (5986)

  • qml.lie_closure now works with sums of Paulis. (#6023)

  • Workflows that parameterize the coefficients of qml.exp are now jit-compatible. (#6082)

  • Fixed a bug where CompositeOp.overlapping_ops changes the original ordering of operators, causing an incorrect matrix to be generated for Prod with Sum as operands. (#6091)

  • qml.qsvt now works with โ€œWxโ€ convention and any number of angles. (#6105)

  • Basis set data from the Basis Set Exchange library can now be loaded for elements with SPD-type orbitals. (#6159)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Tarun Kumar Allamsetty, Guillermo Alonso, Ali Asadi, Utkarsh Azad, Tonmoy T. Bhattacharya, Gabriel Bottrill, Jack Brown, Ahmed Darwish, Astral Cai, Yushao Chen, Ahmed Darwish, Diksha Dhawan Maja Franz, Lillian M. A. Frederiksen, Pietropaolo Frisoni, Emiliano Godinez, Austin Huang, Renke Huang, Josh Izaac, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Jorge Martinez de Lejarza, William Maxwell, Vincent Michaud-Rioux, Anurav Modak, Mudit Pandey, Andrija Paurevic, Erik Schultheis, nate stemen, David Wierichs,

orphan

Release 0.37.0ยถ

New features since last release

Execute wide circuits with Default Tensor ๐Ÿ”—

  • A new default.tensor device is now available for performing tensor network and matrix product state simulations of quantum circuits using the quimb backend. (#5699) (#5744) (#5786) (#5795)

    Either method can be selected when instantiating the default.tensor device by setting the method keyword argument to "tn" (tensor network) or "mps" (matrix product state).

    There are several templates in PennyLane that are tensor-network focused, which are excellent candidates for the "tn" method for default.tensor. The following example shows how a circuit comprising gates in a tree tensor network architecture can be efficiently simulated using method="tn".

    import pennylane as qml
    
    n_wires = 16
    dev = qml.device("default.tensor", method="tn")
    
    def block(weights, wires):
        qml.CNOT(wires=[wires[0], wires[1]])
        qml.RY(weights[0], wires=wires[0])
        qml.RY(weights[1], wires=wires[1])
    
    n_block_wires = 2
    n_params_block = 2
    n_blocks = qml.TTN.get_n_blocks(range(n_wires), n_block_wires)
    template_weights = [[0.1, -0.3]] * n_blocks
    
    @qml.qnode(dev)
    def circuit(template_weights):
        for i in range(n_wires):
            qml.Hadamard(i)
        qml.TTN(range(n_wires), n_block_wires, block, n_params_block, template_weights)
        return qml.expval(qml.Z(n_wires - 1))
    
    >>> circuit(template_weights)
    0.3839174759751649
    

    For matrix product state simulations (method="mps"), we can make the execution be approximate by setting max_bond_dim (see the deviceโ€™s documentation for more details). The maximum bond dimension has implications for the speed of the simulation and lets us control the degree of the approximation, as shown in the following example. First, set up the circuit:

    import numpy as np
    
    n_layers = 10
    n_wires = 10
    
    initial_shape, weights_shape = qml.SimplifiedTwoDesign.shape(n_layers, n_wires)
    np.random.seed(1967)
    initial_layer_weights = np.random.random(initial_shape)
    weights = np.random.random(weights_shape)
    
    def f():
        qml.SimplifiedTwoDesign(initial_layer_weights, weights, range(n_wires))
        return qml.expval(qml.Z(0))
    

    The default.tensor device is instantiated with a max_bond_dim value:

    dev_dq = qml.device("default.qubit")
    value_dq = qml.QNode(f, dev_dq)()
    
    dev_mps = qml.device("default.tensor", max_bond_dim=5)
    value_mps = qml.QNode(f, dev_mps)()
    

    With this bond dimension, the expectation values calculated for default.qubit and default.tensor are different:

    >>> np.abs(value_dq - value_mps)
    tensor(0.0253213, requires_grad=True)
    

    Learn more about default.tensor and how to configure it by visiting the how-to guide.

Add noise models to your quantum circuits ๐Ÿ“บ

  • Support for building noise models and applying them to a quantum circuit has been added via the NoiseModel class and an add_noise transform. (#5674) (#5684) (#5718)

    Under the hood, PennyLaneโ€™s approach to noise models is insertion-based, meaning that noise is included by inserting additional operators (gates or channels) that describe the noise into the quantum circuit. Creating a NoiseModel boils down to defining Boolean conditions under which specific noisy operations are inserted. There are several ways to specify conditions for adding noisy operations:

    • qml.noise.op_eq(op): if the operator op is encountered in the circuit, add noise.

    • qml.noise.op_in(ops): if any operators in ops are encountered in the circuit, add noise.

    • qml.noise.wires_eq(wires): if an operator is applied to wires, add noise.

    • qml.noise.wires_in(wires): if an operator is applied to any wire in wires, add noise.

    • custom noise conditions: custom conditions can be defined as functions decorated with qml.BooleanFn that return a Boolean value. For example, the following function will insert noise if a qml.RY operator is encountered with an angle of rotation that is less than 0.5:

      @qml.BooleanFn
      def c0(op):
          return isinstance(op, qml.RY) and op.parameters[0] < 0.5
      

    Conditions can also be combined together with &, and, |, etc. Once the conditions under which noise is to be inserted have been stated, we can specify exactly what noise is inserted with the following:

    • qml.noise.partial_wires(op): insert op on the wires that are specified by the condition that triggers adding this noise

    • custom noise operations: custom noise can be specified by defining a standard quantum function like below.

      def n0(op, **kwargs):
          qml.RY(op.parameters[0] * 0.05, wires=op.wires)
      

    With that, we can create a qml.NoiseModel object whose argument must be a dictionary mapping conditions to noise:

    c1 = qml.noise.op_eq(qml.X) & qml.noise.wires_in([0, 1])
    n1 = qml.noise.partial_wires(qml.AmplitudeDamping, 0.4)
    
    noise_model = qml.NoiseModel({c0: n0, c1: n1})
    
    >>> noise_model
    NoiseModel({
        BooleanFn(c0): n0
        OpEq(PauliX) | WiresIn([0, 1]): AmplitudeDamping(gamma=0.4)
    })
    

    The noise model created can then be added to a QNode with qml.add_noise:

    dev = qml.device("lightning.qubit", wires=3)
    
    @qml.qnode(dev)
    def circuit():
        qml.Y(0)
        qml.CNOT([0, 1])
        qml.RY(0.3, wires=2) # triggers c0
        qml.X(1) # triggers c1
        return qml.state()
    
    >>> print(qml.draw(circuit)())
    0: โ”€โ”€Yโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”ค  State
    1: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”€Xโ”€โ”ค  State
    2: โ”€โ”€RY(0.30)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    >>> circuit = qml.add_noise(circuit, noise_model)
    >>> print(qml.draw(circuit)())
    0: โ”€โ”€Yโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    1: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€Xโ”€โ”€AmplitudeDamping(0.40)โ”€โ”ค  State
    2: โ”€โ”€RY(0.30)โ”€โ”€RY(0.01)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    

    If more than one transform is applied to a QNode, control over when/where the add_noise transform is applied in relation to the other transforms can be specified with the level keyword argument. By default, add_noise is applied after all the transforms that have been manually applied to the QNode until that point. To learn more about this new functionality, check out our noise module documentation and keep your eyes peeled for an in-depth demo!

Catch bugs with the PennyLane debugger ๐Ÿšซ๐Ÿž

  • The new PennyLane quantum debugger allows pausing simulation via the qml.breakpoint() command and provides tools for analyzing quantum circuits during execution. (#5680) (#5749) (#5789)

    This includes monitoring the circuit via measurements using qml.debug_state(), qml.debug_probs(), qml.debug_expval(), and qml.debug_tape(), stepping through the operations in a quantum circuit, and interactively adding operations during execution.

    Including qml.breakpoint() in a circuit will cause the simulation to pause during execution and bring up the interactive console. For example, consider the following code in a Python file called script.py:

    @qml.qnode(qml.device('default.qubit', wires=(0,1,2)))
    def circuit(x):
        qml.Hadamard(wires=0)
        qml.CNOT(wires=(0,2))
        qml.breakpoint()
    
        qml.RX(x, wires=1)
        qml.RY(x, wires=2)
        qml.breakpoint()
    
        return qml.sample()
    
    circuit(1.2345)
    

    Upon executing script.py, the simulation pauses at the first breakpoint:

    > /Users/your/path/to/script.py(8)circuit()
    -> qml.RX(x, wires=1)
    (pldb):
    

    While debugging, we can access circuit information. For example, qml.debug_tape() returns the tape of the circuit, giving access to its operations and drawing:

    [pldb] tape = qml.debug_tape()
    [pldb] print(tape.draw(wire_order=[0,1,2]))
    0: โ”€โ”€Hโ”€โ•ญโ—โ”€โ”ค
    2: โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”ค
    [pldb] tape.operations
    [Hadamard(wires=[0]), CNOT(wires=[0, 2])]
    

    While qml.debug_state() is equivalent to qml.state() and gives the current state:

    [pldb] print(qml.debug_state())
    [0.70710678+0.j 0.        +0.j 0.        +0.j 0.        +0.j
      1.        +0.j 0.70710678+0.j 0.        +0.j 0.        +0.j]
    

    Other debugger functions like qml.debug_probs() and qml.debug_expval() also function like their simulation counterparts (qml.probs and qml.expval, respectively) and are described in more detail in the debugger documentation

    Additionally, standard debugging commands are available to navigate through code, including list, longlist, next, continue, and quit, as described in the debugging documentation.

    Finally, to modify a circuit mid-run, simply call the desired PennyLane operations:

    [pldb] qml.CNOT(wires=(0,2))
    CNOT(wires=[0, 2])
    [pldb] print(qml.debug_tape().draw(wire_order=[0,1,2]))
    0: โ”€โ”€Hโ”€โ•ญโ—โ”€โ•ญโ—โ”€โ”ค
    2: โ”€โ”€โ”€โ”€โ•ฐXโ”€โ•ฐXโ”€โ”ค
    

Stay tuned for an in-depth demonstration on using this feature with real-world examples!

Convert between OpenFermion and PennyLane ๐Ÿค

  • Two new functions called qml.from_openfermion and qml.to_openfermion are now available to convert between OpenFermion and PennyLane objects. This includes both fermionic and qubit operators. (#5773) (#5808) (#5881)

    For fermionic operators:

    >>> import openfermion
    >>> of_fermionic = openfermion.FermionOperator('0^ 2')
    >>> type(of_fermionic)
    <class 'openfermion.ops.operators.fermion_operator.FermionOperator'>
    >>> pl_fermionic = qml.from_openfermion(of_fermionic)
    >>> type(pl_fermionic)
    <class 'pennylane.fermi.fermionic.FermiWord'>
    >>> print(pl_fermionic)
    aโบ(0) a(2)
    

    And for qubit operators:

    >>> of_qubit = 0.5 * openfermion.QubitOperator('X0 X5')
    >>> pl_qubit = qml.from_openfermion(of_qubit)
    >>> print(pl_qubit)
    0.5 * (X(0) @ X(5))
    

Better control over when drawing and specs take place ๐ŸŽš๏ธ

  • It is now possible to control the stage at which qml.draw, qml.draw_mpl, and qml.specs occur within a QNodeโ€™s transform program. (#5855) (#5781)

    Consider the following circuit which has multiple transforms applied:

    @qml.transforms.split_non_commuting
    @qml.transforms.cancel_inverses
    @qml.transforms.merge_rotations
    @qml.qnode(qml.device("default.qubit"))
    def f():
        qml.Hadamard(0)
        qml.Y(0)
        qml.RX(0.4, 0)
        qml.RX(-0.4, 0)
        qml.Y(0)
        return qml.expval(qml.X(0) + 2 * qml.Y(0))
    

    We can specify a level value when using qml.draw():

    >>> print(qml.draw(f, level=0)())  # input program
    0: โ”€โ”€Hโ”€โ”€Yโ”€โ”€RX(0.40)โ”€โ”€RX(-0.40)โ”€โ”€Yโ”€โ”ค  <X+(2.00*Y)>
    >>> print(qml.draw(f, level=1)())  # rotations merged
    0: โ”€โ”€Hโ”€โ”€Yโ”€โ”€Yโ”€โ”ค  <X+(2.00*Y)>
    >>>  print(qml.draw(f, level=2)())  # inverses cancelled
    0: โ”€โ”€Hโ”€โ”ค  <X+(2.00*Y)>
    >>>  print(qml.draw(f, level=3)())  # Hamiltonian expanded
    0: โ”€โ”€Hโ”€โ”ค  <X>
    
    0: โ”€โ”€Hโ”€โ”ค  <Y>
    

    The qml.workflow.get_transform_program function can be used to see the full transform program.

    >>> qml.workflow.get_transform_program(f)
    TransformProgram(merge_rotations, cancel_inverses, split_non_commuting, validate_device_wires, mid_circuit_measurements, decompose, validate_measurements, validate_observables, no_sampling)
    

    Note that additional transforms can be added automatically from device preprocessing or gradient calculations. Rather than providing an integer value to level, it is possible to target the "user", "gradient" or "device" stages:

    n_wires = 3
    x = np.random.random((2, n_wires))
    
    @qml.qnode(qml.device("default.qubit"))
    def f():
        qml.BasicEntanglerLayers(x, range(n_wires))
        return qml.expval(qml.X(0))
    
    >>> print(qml.draw(f, level="device")())
    0: โ”€โ”€RX(0.28)โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ•ญXโ”€โ”€RX(0.70)โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ•ญXโ”€โ”ค  <X>
    1: โ”€โ”€RX(0.52)โ”€โ•ฐXโ”€โ•ญโ—โ”€โ”‚โ”€โ”€โ”€RX(0.65)โ”€โ•ฐXโ”€โ•ญโ—โ”€โ”‚โ”€โ”€โ”ค
    2: โ”€โ”€RX(0.00)โ”€โ”€โ”€โ”€โ•ฐXโ”€โ•ฐโ—โ”€โ”€RX(0.03)โ”€โ”€โ”€โ”€โ•ฐXโ”€โ•ฐโ—โ”€โ”ค
    

Improvements ๐Ÿ› 

Community contributions, including UnitaryHACK ๐Ÿ’›

  • default.clifford now supports arbitrary state-based measurements with qml.Snapshot. (#5794)

  • qml.equal now properly handles Pow, Adjoint, Exp, and SProd operators as arguments across different interfaces and tolerances with the addition of four new keyword arguments: check_interface, check_trainability, atol and rtol. (#5668)

  • The implementation for qml.assert_equal has been updated for Operator, Controlled, Adjoint, Pow, Exp, SProd, ControlledSequence, Prod, Sum, Tensor and Hamiltonian instances. (#5780) (#5877)

  • qml.from_qasm now supports the ability to convert mid-circuit measurements from OpenQASM 2 code, and it can now also take an optional argument to specify a list of measurements to be performed at the end of the circuit, just like qml.from_qiskit. (#5818)

  • Four new operators have been added for simulating noise on the default.qutrit.mixed device: (#5502) (#5793) (#5503) (#5757) (#5799) (#5784)

    • qml.QutritDepolarizingChannel: a channel that adds depolarizing noise.

    • qml.QutritChannel: enables the specification of noise using a collection of (3x3) Kraus matrices.

    • qml.QutritAmplitudeDamping: a channel that adds noise processes modelled by amplitude damping.

    • qml.TritFlip: a channel that adds trit flip errors, such as misclassification.

Faster and more flexible mid-circuit measurements

  • The default.qubit device supports a depth-first tree-traversal algorithm to accelerate native mid-circuit measurement execution. Accessible through the QNode argument mcm_method="tree-traversal", this new implementation supports classical control, collecting statistics, and post-selection, along with all measurements enabled with qml.dynamic_one_shot. More information about this new mid-circuit measurement method can be found on our measurement documentation page. (#5180)

  • qml.QNode and the @qml.qnode decorator now accept two new keyword arguments: postselect_mode and mcm_method. These keyword arguments can be used to configure how the device should behave when running circuits with mid-circuit measurements. (#5679) (#5833) (#5850)

    • postselect_mode="hw-like" indicates to devices to discard invalid shots when postselecting mid-circuit measurements. Use postselect_mode="fill-shots" to unconditionally sample the postselected value, thus making all samples valid. This is equivalent to sampling until the number of valid samples matches the total number of shots.

    • mcm_method will indicate which strategy to use for running circuits with mid-circuit measurements. Use mcm_method="deferred" to use the deferred measurements principle, or mcm_method="one-shot" to execute once for each shot. If qml.qjit is being used (the Catalyst compiler), mcm_method="single-branch-statistics" is also available. Using this method, a single branch of the execution tree will be randomly explored.

  • The dynamic_one_shot transform received a few improvements:

    • dynamic_one_shot is now compatible with qml.qjit (the Catalyst compiler). (#5766) (#5888)

    • dynamic_one_shot now uses a single auxiliary tape with a shot vector and default.qubit implements the loop over shots with jax.vmap. (#5617)

    • dynamic_one_shot is now compatible with jax.jit. (#5557)

  • When using defer_measurements with postselection, operations that will never be active due to the postselected state are skipped in the transformed quantum circuit. In addition, postselected controls are skipped, as they are evaluated when the transform is applied. This optimization feature can be turned off by setting reduce_postselected=False. (#5558)

    Consider a simple circuit with three mid-circuit measurements, two of which are postselecting, and a single gate conditioned on those measurements:

    @qml.qnode(qml.device("default.qubit"))
    def node(x):
        qml.RX(x, 0)
        qml.RX(x, 1)
        qml.RX(x, 2)
        mcm0 = qml.measure(0, postselect=0, reset=False)
        mcm1 = qml.measure(1, postselect=None, reset=True)
        mcm2 = qml.measure(2, postselect=1, reset=False)
        qml.cond(mcm0 + mcm1 + mcm2 == 1, qml.RX)(0.5, 3)
        return qml.expval(qml.Z(0) @ qml.Z(3))
    

    Without the new optimization, we obtain three gates, each controlled on the three measured qubits. They correspond to the combinations of controls that satisfy the condition mcm0 + mcm1 + mcm2 == 1:

    >>> print(qml.draw(qml.defer_measurements(node, reduce_postselected=False))(0.6))
    0: โ”€โ”€RX(0.60)โ”€โ”€|0โŸฉโŸจ0|โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ•ญ<Z@Z>
    1: โ”€โ”€RX(0.60)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ•ญโ—โ”€โ•ญXโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚
    2: โ”€โ”€RX(0.60)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”€|1โŸฉโŸจ1|โ”€โ•ญโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚
    3: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œRX(0.50)โ”€โ”œRX(0.50)โ”€โ”œRX(0.50)โ”€โ”ค โ•ฐ<Z@Z>
    4: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    5: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    

    If we do not explicitly deactivate the optimization, we obtain a much simpler circuit:

    >>> print(qml.draw(qml.defer_measurements(node))(0.6))
    0: โ”€โ”€RX(0.60)โ”€โ”€|0โŸฉโŸจ0|โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ•ญ<Z@Z>
    1: โ”€โ”€RX(0.60)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ•ญโ—โ”€โ•ญXโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค โ”‚
    2: โ”€โ”€RX(0.60)โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”€|1โŸฉโŸจ1|โ”€โ”€โ”€โ”ค โ”‚
    3: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ•ญRX(0.50)โ”€โ”ค โ•ฐ<Z@Z>
    4: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    5: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐXโ”€โ•ฐโ—โ”€โ•ฐโ—‹โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    

    There is only one controlled gate with only one control wire.

  • Mid-circuit measurement tests have been streamlined and refactored, removing most end-to-end tests from the native MCM test file, but keeping one that validates multiple mid-circuit measurements with any allowed return and interface end-to-end tests. (#5787)

Access to QROM

  • The QROM algorithm is now available in PennyLane with qml.QROM. This template allows you to enter classical data in the form of bitstrings. (#5688)

    bitstrings = ["010", "111", "110", "000"]
    
    dev = qml.device("default.qubit", shots = 1)
    
    @qml.qnode(dev)
    def circuit():
        qml.BasisEmbedding(2, wires = [0,1])
    
        qml.QROM(bitstrings = bitstrings,
                control_wires = [0,1],
                target_wires = [2,3,4],
                work_wires = [5,6,7])
    
        return qml.sample(wires = [2,3,4])
    
    >>> print(circuit())
    [1 1 0]
    

Capturing and representing hybrid programs

  • A number of templates have been updated to be valid PyTrees and PennyLane operations. (#5698)

  • PennyLane operators, measurements, and QNodes can now automatically be captured as instructions in JAXPR. (#5564) (#5511) (#5708) (#5523) (#5686) (#5889)

  • The qml.PyTrees module now has flatten and unflatten methods for serializing PyTrees. (#5701)

  • qml.sample can now be used on Boolean values representing mid-circuit measurement results in traced quantum functions. This feature is used with Catalyst to enable the pattern m = measure(0); qml.sample(m). (#5673)

Quantum chemistry

  • The qml.qchem.Molecule object received a few improvements:

    • qml.qchem.Molecule is now the central object used by all qchem functions. (#5571)

    • qml.qchem.Molecule now supports Angstrom as a unit. (#5694)

    • qml.qchem.Molecule now supports open-shell systems. (#5655)

  • The qml.qchem.molecular_hamiltonian function now supports parity and Bravyi-Kitaev mappings. (#5657)

  • qml.qchem.molecular_dipole function has been added for calculating the dipole operator using the "dhf" and "openfermion" backends. (#5764)

  • The qchem module now has dedicated functions for calling the pyscf and openfermion backends and the molecular_hamiltonian and molecular_dipole functions have been moved to hamiltonian and dipole modules. (#5553) (#5863)

  • More fermionic-to-qubit tests have been added to cover cases when the mapped operator is different for various mapping schemes. (#5873)

Easier development

  • Logging now allows for an easier opt-in across the stack and support has been extended to Catalyst. (#5528)

  • Three new Pytest markers have been added for easier management of our test suite: unit, integration and system. (#5517)

Other improvements

  • qml.MultiControlledX can now be decomposed even when no work_wires are provided. The implementation returns \(\mathcal{O}(\mbox{len(control wires)}^2)\) operations and is applicable for any multi-controlled unitary gate. This decomposition is provided in arXiv:quant-ph/9503016. (#5735)

  • A new function called expectation_value has been added to qml.math to calculate the expectation value of a matrix for pure states. (#4484)

    >>> state_vector = [1/np.sqrt(2), 0, 1/np.sqrt(2), 0]
    >>> operator_matrix = qml.matrix(qml.PauliZ(0), wire_order=[0,1])
    >>> qml.math.expectation_value(operator_matrix, state_vector)
    tensor(-2.23711432e-17+0.j, requires_grad=True)
    
  • param_shift with the broadcast=True option now supports shot vectors and multiple measurements. (#5667)

  • qml.TrotterProduct is now compatible with resource tracking by inheriting from ResourcesOperation. (#5680)

  • packaging is now a required package in PennyLane. (#5769)

  • qml.ctrl now works with tuple-valued control_values when applied to any already controlled operation. (#5725)

  • The sorting order of parameter-shift terms is now guaranteed to resolve ties in the absolute value with the sign of the shifts. (#5582)

  • qml.transforms.split_non_commuting can now handle circuits containing measurements of multi-term observables. (#5729) (#5838) (#5828) (#5869) (#5939) (#5945)

  • qml.devices.LegacyDevice is now an alias for qml.Device, so it is easier to distinguish it from qml.devices.Device, which follows the new device API. (#5581)

  • The dtype for eigvals of X, Y, Z and Hadamard is changed from int to float, making them consistent with the other observables. The dtype of the returned values when sampling these observables (e.g. qml.sample(X(0))) is also changed to float. (#5607)

  • The framework for the development of an assert_equal function for testing operator comparison has been set up. (#5634) (#5858)

  • The decompose transform has an error keyword argument to specify the type of error that should be raised, allowing error types to be more consistent with the context the decompose function is used in. (#5669)

  • Empty initialization of PauliVSpace is permitted. (#5675)

  • qml.tape.QuantumScript properties are only calculated when needed, instead of on initialization. This decreases the classical overhead by over 20%. Also, par_info, obs_sharing_wires, and obs_sharing_wires_id are now public attributes. (#5696)

  • The qml.data module now supports PyTree data types as dataset attributes (#5732)

  • qml.ops.Conditional now inherits from qml.ops.SymbolicOp, thus it inherits several useful common functionalities. Other properties such as adjoint and diagonalizing gates have been added using the base properties. (##5772)

  • New dispatches for qml.ops.Conditional and qml.MeasurementValue have been added to qml.equal. (##5772)

  • The qml.snapshots transform now supports arbitrary devices by running a separate tape for each snapshot for unsupported devices. (#5805)

  • The qml.Snapshot operator now accepts sample-based measurements for finite-shot devices. (#5805)

  • Device preprocess transforms now happen inside the ML boundary. (#5791)

  • Transforms applied to callables now use functools.wraps to preserve the docstring and call signature of the original function. (#5857)

  • qml.qsvt() now supports JAX arrays with angle conversions. (#5853)

  • The sorting order of parameter-shift terms is now guaranteed to resolve ties in the absolute value with the sign of the shifts. (#5583)

Breaking changes ๐Ÿ’”

  • Passing shots as a keyword argument to a QNode initialization now raises an error instead of ignoring the input. (#5748)

  • A custom decomposition can no longer be provided to qml.QDrift. Instead, apply the operations in your custom operation directly with qml.apply. (#5698)

  • Sampling observables composed of X, Y, Z and Hadamard now returns values of type float instead of int. (#5607)

  • qml.is_commuting no longer accepts the wire_map argument, which does not bring any functionality. (#5660)

  • qml.from_qasm_file has been removed. The user can open files and load their content using qml.from_qasm. (#5659)

  • qml.load has been removed in favour of more specific functions, such as qml.from_qiskit, etc. (#5654)

  • qml.transforms.convert_to_numpy_parameters is now a proper transform and its output signature has changed, returning a list of QuantumScripts and a post-processing function instead of simply the transformed circuit. (#5693)

  • Controlled.wires does not include self.work_wires anymore. That can be accessed separately through Controlled.work_wires. Consequently, Controlled.active_wires has been removed in favour of the more common Controlled.wires. (#5728)

Deprecations ๐Ÿ‘‹

  • The simplify argument in qml.Hamiltonian and qml.ops.LinearCombination has been deprecated. Instead, qml.simplify() can be called on the constructed operator. (#5677)

  • qml.transforms.map_batch_transform has been deprecated, since a transform can be applied directly to a batch of tapes. (#5676)

  • The default behaviour of qml.from_qasm() to remove measurements in the QASM code has been deprecated. Use measurements=[] to keep this behaviour or measurements=None to keep the measurements from the QASM code. (#5882) (#5904)

Documentation ๐Ÿ“

  • The qml.qchem docs have been updated to showcase the new improvements. (#5758) (#5638)

  • Several links to other functions in measurement process docstrings have been fixed. (#5913)

  • Information about mid-circuit measurements has been moved from the measurements quickstart page to its own mid-circuit measurements quickstart page (#5870)

  • The documentation for the default.tensor device has been added. (#5719)

  • A small typo was fixed in the docstring for qml.sample. (#5685)

  • Typesetting for some of the documentation was fixed, (use of left/right delimiters, fractions, and fixing incorrectly set up commands) (#5804)

  • The qml.Tracker examples have been updated. (#5803)

  • The input types for coupling_map in qml.transpile have been updated to reflect all the allowed input types by nx.to_networkx_graph. (#5864)

  • The text in the qml.data module and datasets quickstart has been slightly modified to lead to the quickstart first and highlight list_datasets. (5484)

Bug fixes ๐Ÿ›

  • qml.compiler.active first checks whether Catalyst is imported at all to avoid changing jax_enable_x64 on module initialization. (#5960)

  • The __invert__ dunder method of the MeasurementValue class uses an array-valued function. (#5955)

  • Skip Projector-measurement tests on devices that do not support it. (#5951)

  • The default.tensor device now preserves the order of wires if the initial MPS is created from a dense state vector. (#5892)

  • Fixed a bug where hadamard_grad returned a wrong shape for qml.probs() without wires. (#5860)

  • An error is now raised on processing an AnnotatedQueue into a QuantumScript if the queue contains something other than an Operator, MeasurementProcess, or QuantumScript. (#5866)

  • Fixed a bug in the wire handling on special controlled ops. (#5856)

  • Fixed a bug where Sumโ€˜s with repeated identical operations ended up with the same hash as Sumโ€˜s with different numbers of repeats. (#5851)

  • qml.qaoa.cost_layer and qml.qaoa.mixer_layer can now be used with Sum operators. (#5846)

  • Fixed a bug where qml.MottonenStatePreparation produces wrong derivatives at special parameter values. (#5774)

  • Fixed a bug where fractional powers and adjoints of operators were commuted, which is not well-defined/correct in general. Adjoints of fractional powers can no longer be evaluated. (#5835)

  • qml.qnn.TorchLayer now works with tuple returns. (#5816)

  • An error is now raised if a transform is applied to a catalyst qjit object. (#5826)

  • qml.qnn.KerasLayer and qml.qnn.TorchLayer no longer mutate the input qml.QNodeโ€˜s interface. (#5800)

  • Docker builds on PR merging has been disabled. (#5777)

  • The validation of the adjoint method in DefaultQubit correctly handles device wires now. (#5761)

  • QuantumPhaseEstimation.map_wires on longer modifies the original operation instance. (#5698)

  • The decomposition of qml.AmplitudeAmplification now correctly queues all operations. (#5698)

  • Replaced semantic_version with packaging.version.Version, since the former cannot handle the metadata .post in the version string. (#5754)

  • The dynamic_one_shot transform now has expanded support for the jax and torch interfaces. (#5672)

  • The decomposition of StronglyEntanglingLayers is now compatible with broadcasting. (#5716)

  • qml.cond can now be applied to ControlledOp operations when deferring measurements. (#5725)

  • The legacy Tensor class can now handle a Projector with abstract tracer input. (#5720)

  • Fixed a bug that raised an error regarding expected versus actual dtype when using JAX-JIT on a circuit that returned samples of observables containing the qml.Identity operator. (#5607)

  • The signature of CaptureMeta objects (like Operator) now match the signature of the __init__ call. (#5727)

  • Vanilla NumPy arrays are now used in test_projector_expectation to avoid differentiating qml.Projector with respect to the state attribute. (#5683)

  • qml.Projector is now compatible with jax.jit. (#5595)

  • Finite-shot circuits with a qml.probs measurement, both with a wires or op argument, can now be compiled with jax.jit. (#5619)

  • param_shift, finite_diff, compile, insert, merge_rotations, and transpile now all work with circuits with non-commuting measurements. (#5424) (#5681)

  • A correction has been added to qml.bravyi_kitaev to call the correct function for a qml.FermiSentence input. (#5671)

  • Fixed a bug where sum_expand produces incorrect result dimensions when combined with shot vectors, multiple measurements, and parameter broadcasting. (#5702)

  • Fixed a bug in qml.math.dot that raises an error when only one of the operands is a scalar. (#5702)

  • qml.matrix is now compatible with QNodes compiled by qml.qjit. (#5753)

  • qml.snapshots raises an error when a measurement other than qml.state is requested from default.qubit.legacy instead of silently returning the statevector. (#5805)

  • Fixed a bug where default.qutrit was falsely determined to be natively compatible with qml.snapshots. (#5805)

  • Fixed a bug where the measurement of a qml.Snapshot instance was not passed on during the qml.adjoint and qml.ctrl operations. (#5805)

  • qml.CNOT and qml.Toffoli now have an arithmetic_depth of 1, as they are controlled operations. (#5797)

  • Fixed a bug where the gradient of ControlledSequence, Reflection, AmplitudeAmplification, and Qubitization was incorrect on default.qubit.legacy with parameter_shift. (#5806)

  • Fixed a bug where split_non_commuting raises an error when the circuit contains measurements of observables that are not Pauli words. (#5827)

  • The simplify method for qml.Exp now returns an operator with the correct number of Trotter steps, i.e. equal to the one from the pre-simplified operator. (#5831)

  • Fixed a bug where CompositeOp.overlapping_ops would put overlapping operators in different groups, leading to incorrect results returned by LinearCombination.eigvals(). (#5847)

  • The correct decomposition for a qml.PauliRot with an identity as pauli_word has been implemented, i.e. returns a qml.GlobalPhase with half the angle. (#5875)

  • qml.pauli_decompose now works in a jit-ted context, such as jax.jit and qml.qjit. (#5878)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Tarun Kumar Allamsetty, Guillermo Alonso-Linaje, Utkarsh Azad, Lillian M. A. Frederiksen, Ludmila Botelho, Gabriel Bottrill, Thomas Bromley, Jack Brown, Astral Cai, Ahmed Darwish, Isaac De Vlugt, Diksha Dhawan, Pietropaolo Frisoni, Emiliano Godinez, Diego Guala, Daria Van Hende, Austin Huang, David Ittah, Soran Jahangiri, Rohan Jain, Mashhood Khan, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Lee James Oโ€™Riordan, Mudit Pandey, Kenya Sakka, Jay Soni, Kazuki Tsuoka, Haochen Paul Wang, David Wierichs.

orphan

Release 0.36.0ยถ

New features since last release

Estimate errors in a quantum circuit ๐Ÿงฎ

  • This version of PennyLane lays the foundation for estimating the total error in a quantum circuit from the combination of individual gate errors. (#5154) (#5464) (#5465) (#5278) (#5384)

    Two new user-facing classes enable calculating and propagating gate errors in PennyLane:

    • qml.resource.SpectralNormError: the spectral norm error is defined as the distance, in spectral norm, between the true unitary we intend to apply and the approximate unitary that is actually applied.

    • qml.resource.ErrorOperation: a base class that inherits from qml.operation.Operation and represents quantum operations which carry some form of algorithmic error.

    SpectralNormError can be used for back-of-the-envelope type calculations like obtaining the spectral norm error between two unitaries via get_error:

    import pennylane as qml
    from pennylane.resource import ErrorOperation, SpectralNormError
    
    intended_op = qml.RY(0.40, 0)
    actual_op = qml.RY(0.41, 0) # angle of rotation is slightly off
    
    >>> SpectralNormError.get_error(intended_op, actual_op)
    0.004999994791668309
    

    SpectralNormError is also a key tool to specify errors in larger quantum circuits:

    • For operations representing a major building block of an algorithm, we can create a custom operation that inherits from ErrorOperation. This child class must override the error method and should return a SpectralNormError instance:

      class MyErrorOperation(ErrorOperation):
          def __init__(self, error_val, wires):
              self.error_val = error_val
              super().__init__(wires=wires)
      
          def error(self):
              return SpectralNormError(self.error_val)
      

      In this toy example, MyErrorOperation introduces an arbitrary SpectralNormError when called in a QNode. It does not require a decomposition or matrix representation when used with null.qubit (suggested for use with resource and error estimation since circuit executions are not required to calculate resources or errors).

      dev = qml.device("null.qubit")
      
      @qml.qnode(dev)
      def circuit():
          MyErrorOperation(0.1, wires=0)
          MyErrorOperation(0.2, wires=1)
          return qml.state()
      

      The total spectral norm error of the circuit can be calculated using qml.specs:

      >>> qml.specs(circuit)()['errors']
      {'SpectralNormError': SpectralNormError(0.30000000000000004)}
      
    • PennyLane already includes a number of built-in building blocks for algorithms like QuantumPhaseEstimation and TrotterProduct. TrotterProduct now propagates errors based on the number of steps performed in the Trotter product. QuantumPhaseEstimation now propagates errors based on the error of its input unitary.

      dev = qml.device('null.qubit')
      hamiltonian = qml.dot([1.0, 0.5, -0.25], [qml.X(0), qml.Y(0), qml.Z(0)])
      
      @qml.qnode(dev)
      def circuit():
          qml.TrotterProduct(hamiltonian, time=0.1, order=2)
          qml.QuantumPhaseEstimation(MyErrorOperation(0.01, wires=0), estimation_wires=[1, 2, 3])
          return qml.state()
      

      Again, the total spectral norm error of the circuit can be calculated using qml.specs:

      >>> qml.specs(circuit)()["errors"]
      {'SpectralNormError': SpectralNormError(0.07616666666666666)}
      

    Check out our error propagation demo to see how to use these new features in a real-world example!

Access an extended arsenal of quantum algorithms ๐Ÿน

  • The Fast Approximate BLock-Encodings (FABLE) algorithm for embedding a matrix into a quantum circuit as outlined in arXiv:2205.00081 is now accessible via the qml.FABLE template. (#5107)

    The usage of qml.FABLE is similar to qml.BlockEncode but provides a more efficient circuit construction at the cost of a user-defined approximation level, tol. The number of wires that qml.FABLE operates on is 2*n + 1, where n defines the dimension of the \(2^n \times 2^n\) matrix that we want to block-encode.

    import numpy as np
    
    A = np.array([[0.1, 0.2], [0.3, 0.4]])
    dev = qml.device('default.qubit', wires=3)
    
    @qml.qnode(dev)
    def circuit():
        qml.FABLE(A, tol = 0.001, wires=range(3))
        return qml.state()
    
    >>> mat = qml.matrix(circuit)()
    >>> 2 * mat[0:2, 0:2]
    array([[0.1+0.j, 0.2+0.j],
           [0.3+0.j, 0.4+0.j]])
    
  • A high-level interface for amplitude amplification and its variants is now available via the new qml.AmplitudeAmplification template. (#5160)

    Based on arXiv:quant-ph/0005055, given a state \(\vert \Psi \rangle = \alpha \vert \phi \rangle + \beta \vert \phi^{\perp} \rangle\), qml.AmplitudeAmplification amplifies the amplitude of \(\vert \phi \rangle\).

    Hereโ€™s an example with a target state \(\vert \phi \rangle = \vert 2 \rangle = \vert 010 \rangle\), an input state \(\vert \Psi \rangle = H^{\otimes 3} \vert 000 \rangle\), as well as an oracle that flips the sign of \(\vert \phi \rangle\) and does nothing to \(\vert \phi^{\perp} \rangle\), which can be achieved in this case through qml.FlipSign.

    @qml.prod
    def generator(wires):
        for wire in wires:
            qml.Hadamard(wires=wire)
    
    U = generator(wires=range(3))
    O = qml.FlipSign(2, wires=range(3))
    

    Here, U is a quantum operation that is created by decorating a quantum function with @qml.prod. This could alternatively be done by creating a user-defined custom operation with a decomposition. Amplitude amplification can then be set up within a circuit:

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev)
    def circuit():
        generator(wires=range(3)) # prepares |Psi> = U|0>
        qml.AmplitudeAmplification(U, O, iters=10)
    
        return qml.probs(wires=range(3))
    
    >>> print(np.round(circuit(), 3))
    [0.01  0.01  0.931 0.01  0.01  0.01  0.01  0.01 ]
    

    As expected, we amplify the \(\vert 2 \rangle\) state.

  • Reflecting about a given quantum state is now available via qml.Reflection. This operation is very useful in the amplitude amplification algorithm and offers a generalization of qml.FlipSign, which operates on basis states. (#5159)

    qml.Reflection works by providing an operation, \(U\), that prepares the desired state, \(\vert \psi \rangle\), that we want to reflect about. In other words, \(U\) is such that \(U \vert 0 \rangle = \vert \psi \rangle\). In PennyLane, \(U\) must be an Operator.

    For example, if we want to reflect about \(\vert \psi \rangle = \vert + \rangle\), then \(U = H\):

    U = qml.Hadamard(wires=0)
    
    dev = qml.device('default.qubit')
    @qml.qnode(dev)
    def circuit():
          qml.Reflection(U)
          return qml.state()
    
    >>> circuit()
    tensor([0.-6.123234e-17j, 1.+6.123234e-17j], requires_grad=True)
    
  • Performing qubitization is now easily accessible with the new qml.Qubitization operator. (#5500)

    qml.Qubitization encodes a Hamiltonian into a suitable unitary operator. When applied in conjunction with quantum phase estimation (QPE), it allows for computing the eigenvalue of an eigenvector of the given Hamiltonian.

    H = qml.dot([0.1, 0.3, -0.3], [qml.Z(0), qml.Z(1), qml.Z(0) @ qml.Z(2)])
    @qml.qnode(qml.device("default.qubit"))
    def circuit():
        # initialize the eigenvector
        qml.PauliX(2)
        # apply QPE
        measurements = qml.iterative_qpe(
            qml.Qubitization(H, control = [3,4]), ancilla = 5, iters = 3
        )
        return qml.probs(op = measurements)
    

Make use of more methods to map from molecules ๐Ÿ—บ๏ธ

  • A new function called qml.bravyi_kitaev has been added to perform the Bravyi-Kitaev mapping of fermionic Hamiltonians to qubit Hamiltonians. (#5390)

    This function presents an alternative mapping to qml.jordan_wigner or qml.parity_transform which can help us measure expectation values more efficiently on hardware. Simply provide a fermionic Hamiltonian (created from from_string, FermiA, FermiC, FermiSentence, or FermiWord) and the number of qubits / spin orbitals in the system, n:

    >>> fermi_ham = qml.fermi.from_string('0+ 1+ 1- 0-')
    >>> qubit_ham = qml.bravyi_kitaev(fermi_ham, n=6, tol=0.0)
    >>> print(qubit_ham)
    0.25 * I(0) + -0.25 * Z(0) + -0.25 * (Z(0) @ Z(1)) + 0.25 * Z(1)
    
  • The qml.qchem.hf_state function has been upgraded to be compatible with qml.parity_transform and the new Bravyi-Kitaev mapping (qml.bravyi_kitaev). (#5472) (#5472)

    >>> state_bk = qml.qchem.hf_state(2, 6, basis="bravyi_kitaev")
    >>> print(state_bk)
    [1 0 0 0 0 0]
    >>> state_parity = qml.qchem.hf_state(2, 6, basis="parity")
    >>> print(state_parity)
    [1 0 0 0 0 0]
    

Calculate dynamical Lie algebras ๐Ÿ‘พ

  • The dynamical Lie algebra (DLA) of a set of operators captures the range of unitary evolutions that the operators can generate. In v0.36 of PennyLane, we have added support for calculating important DLA concepts including:

    • A new qml.lie_closure function to compute the Lie closure of a list of operators, providing one way to obtain the DLA. (#5161) (#5169) (#5627)

      For a list of operators ops = [op1, op2, op3, ..], one computes all nested commutators between ops until no new operators are generated from commutation. All these operators together form the DLA, see e.g. section IIB of arXiv:2308.01432.

      Take for example the following operators:

      from pennylane import X, Y, Z
      ops = [X(0) @ X(1), Z(0), Z(1)]
      

      A first round of commutators between all elements yields the new operators Y(0) @ X(1) and X(0) @ Y(1) (omitting scalar prefactors).

      >>> qml.commutator(X(0) @ X(1), Z(0))
      -2j * (Y(0) @ X(1))
      >>> qml.commutator(X(0) @ X(1), Z(1))
      -2j * (X(0) @ Y(1))
      

      A next round of commutators between all elements further yields the new operator Y(0) @ Y(1).

      >>> qml.commutator(X(0) @ Y(1), Z(0))
      -2j * (Y(0) @ Y(1))
      

      After that, no new operators emerge from taking nested commutators and we have the resulting DLA. This can now be done in short via qml.lie_closure as follows.

      >>> ops = [X(0) @ X(1), Z(0), Z(1)]
      >>> dla = qml.lie_closure(ops)
      >>> dla
      [X(0) @ X(1),
       Z(0),
       Z(1),
       -1.0 * (Y(0) @ X(1)),
       -1.0 * (X(0) @ Y(1)),
       -1.0 * (Y(0) @ Y(1))]
      
    • Computing the structure constants (the adjoint representation) of a dynamical Lie algebra. (5406)

      For example, we can compute the adjoint representation of the transverse field Ising model DLA.

      >>> dla = [X(0) @ X(1), Z(0), Z(1), Y(0) @ X(1), X(0) @ Y(1), Y(0) @ Y(1)]
      >>> structure_const = qml.structure_constants(dla)
      >>> structure_const.shape
      (6, 6, 6)
      

      Visit the documentation of qml.structure_constants to understand how structure constants are a useful way to represent a DLA.

    • Computing the center of a dynamical Lie algebra. (#5477)

      Given a DLA g, we can now compute its centre. The center is the collection of operators that commute with all other operators in the DLA.

      >>> g = [X(0), X(1) @ X(0), Y(1), Z(1) @ X(0)]
      >>> qml.center(g)
      [X(0)]
      

    To help explain these concepts, check out the dynamical Lie algebras demo.

Improvements ๐Ÿ› 

Simulate mixed-state qutrit systems

  • Mixed qutrit states can now be simulated with the default.qutrit.mixed device. (#5495) (#5451) (#5186) (#5082) (#5213)

    Thanks to contributors from the University of British Columbia, a mixed-state qutrit device is now available for simulation, providing a noise-capable equivalent to default.qutrit.

    dev = qml.device("default.qutrit.mixed")
    
    def circuit():
        qml.TRY(0.1, wires=0)
    
    @qml.qnode(dev)
    def shots_circuit():
        circuit()
        return qml.sample(), qml.expval(qml.GellMann(wires=0, index=1))
    
    @qml.qnode(dev)
    def density_matrix_circuit():
        circuit()
        return qml.state()
    
    >>> shots_circuit(shots=5)
    (array([0, 0, 0, 0, 0]), 0.19999999999999996)
    >>> density_matrix_circuit()
    tensor([[0.99750208+0.j, 0.04991671+0.j, 0.        +0.j],
           [0.04991671+0.j, 0.00249792+0.j, 0.        +0.j],
           [0.        +0.j, 0.        +0.j, 0.        +0.j]], requires_grad=True)
    

    However, thereโ€™s one crucial ingredient that we still need to add: support for qutrit noise operations. Keep your eyes peeled for this to arrive in the coming releases!

Work easily and efficiently with operators

  • This release completes the main phase of PennyLaneโ€™s switchover to an updated approach for handling arithmetic operations between operators. The new approach is now enabled by default and is intended to realize a few objectives:

    1. To make it as easy to work with PennyLane operators as it would be with pen and paper.

    2. To improve the efficiency of operator arithmetic.

    In many cases, this update should not break code. If issues do arise, check out the updated operator troubleshooting page and donโ€™t hesitate to reach out to us on the PennyLane discussion forum. As a last resort the old behaviour can be enabled by calling qml.operation.disable_new_opmath(), but this is not recommended because support will not continue in future PennyLane versions (v0.36 and higher). (#5269)

  • A new class called qml.ops.LinearCombination has been introduced. In essence, this class is an updated equivalent of the now-deprecated qml.ops.Hamiltonian but for usage with the new operator arithmetic. (#5216)

  • qml.ops.Sum now supports storing grouping information. Grouping type and method can be specified during construction using the grouping_type and method keyword arguments of qml.dot, qml.sum, or qml.ops.Sum. The grouping indices are stored in Sum.grouping_indices. (#5179)

    a = qml.X(0)
    b = qml.prod(qml.X(0), qml.X(1))
    c = qml.Z(0)
    obs = [a, b, c]
    coeffs = [1.0, 2.0, 3.0]
    
    op = qml.dot(coeffs, obs, grouping_type="qwc")
    
    >>> op.grouping_indices
    ((2,), (0, 1))
    

    Additionally, grouping_type and method can be set or changed after construction using Sum.compute_grouping():

    a = qml.X(0)
    b = qml.prod(qml.X(0), qml.X(1))
    c = qml.Z(0)
    obs = [a, b, c]
    coeffs = [1.0, 2.0, 3.0]
    
    op = qml.dot(coeffs, obs)
    
    >>> op.grouping_indices is None
    True
    >>> op.compute_grouping(grouping_type="qwc")
    >>> op.grouping_indices
    ((2,), (0, 1))
    

    Note that the grouping indices refer to the lists returned by Sum.terms(), not Sum.operands.

  • A new function called qml.operation.convert_to_legacy_H that converts Sum, SProd, and Prod to Hamiltonian instances has been added. This function is intended for developers and will be removed in a future release without a deprecation cycle. (#5309)

  • The qml.is_commuting function now accepts Sum, SProd, and Prod instances. (#5351)

  • Operators can now be left-multiplied by NumPy arrays (i.e., arr * op). (#5361)

  • op.generator(), where op is an Operator instance, now returns operators consistent with the global setting for qml.operator.active_new_opmath() wherever possible. Sum, SProd and Prod instances will be returned even after disabling the new operator arithmetic in cases where they offer additional functionality not available using legacy operators. (#5253) (#5410) (#5411) (#5421)

  • Prod instances temporarily have a new obs property, which helps smoothen the transition of the new operator arithmetic system. In particular, this is aimed at preventing breaking code that uses Tensor.obs. The property has been immediately deprecated. Moving forward, we recommend using op.operands. (#5539)

  • qml.ApproxTimeEvolution is now compatible with any operator that has a defined pauli_rep. (#5362)

  • Hamiltonian.pauli_rep is now defined if the Hamiltonian is a linear combination of Pauli operators. (#5377)

  • Prod instances created with qutrit operators now have a defined eigvals() method. (#5400)

  • qml.transforms.hamiltonian_expand and qml.transforms.sum_expand can now handle multi-term observables with a constant offset (i.e., terms like qml.I()). (#5414) (#5543)

  • qml.qchem.taper_operation is now compatible with the new operator arithmetic. (#5326)

  • The warning for an observable that might not be hermitian in QNode executions has been removed. This enables jit-compilation. (#5506)

  • qml.transforms.split_non_commuting will now work with single-term operator arithmetic. (#5314)

  • LinearCombination and Sum now accept _grouping_indices on initialization. This addition is relevant to developers only. (#5524)

  • Calculating the dense, differentiable matrix for PauliSentence and operators with Pauli sentences is now faster. (#5578)

Community contributions ๐Ÿฅณ

  • ExpectationMP, VarianceMP, CountsMP, and SampleMP now have a process_counts method (similar to process_samples). This allows for calculating measurements given a counts dictionary. (#5256) (#5395)

  • Type-hinting has been added in the Operator class for better interpretability. (#5490)

  • An alternate strategy for sampling with multiple different shots values has been implemented via the shots.bins() method, which samples all shots at once and then processes each separately. (#5476)

Mid-circuit measurements and dynamic circuits

  • A new module called qml.capture that will contain PennyLaneโ€™s own capturing mechanism for hybrid quantum-classical programs has been added. (#5509)

  • The dynamic_one_shot transform has been introduced, enabling dynamic circuit execution on circuits with finite shots and devices that natively support mid-circuit measurements. (#5266)

  • The QubitDevice class and children classes support the dynamic_one_shot transform provided that they support mid-circuit measurement operations natively. (#5317)

  • default.qubit can now be provided a random seed for sampling mid-circuit measurements with finite shots. This (1) ensures that random behaviour is more consistent with dynamic_one_shot and defer_measurements and (2) makes our continuous-integration (CI) have less failures due to stochasticity. (#5337)

Performance and broadcasting

  • Gradient transforms may now be applied to batched/broadcasted QNodes as long as the broadcasting is in non-trainable parameters. (#5452)

  • The performance of computing the matrix of qml.QFT has been improved. (#5351)

  • qml.transforms.broadcast_expand now supports shot vectors when returning qml.sample(). (#5473)

  • LightningVJPs is now compatible with Lightning devices using the new device API. (#5469)

Device capabilities

  • Obtaining classical shadows using the default.clifford device is now compatible with stim v1.13.0. (#5409)

  • default.mixed has improved support for sampling-based measurements with non-NumPy interfaces. (#5514) (#5530)

  • default.mixed now supports arbitrary state-based measurements with qml.Snapshot. (#5552)

  • null.qubit has been upgraded to the new device API and has support for all measurements and various modes of differentiation. (#5211)

Other improvements

  • Entanglement entropy can now be calculated with qml.math.vn_entanglement_entropy, which computes the von Neumann entanglement entropy from a density matrix. A corresponding QNode transform, qml.qinfo.vn_entanglement_entropy, has also been added. (#5306)

  • qml.draw and qml.draw_mpl will now attempt to sort the wires if no wire order is provided by the user or the device. (#5576)

  • A clear error message is added in KerasLayer when using the newest version of TensorFlow with Keras 3 (which is not currently compatible with KerasLayer), linking to instructions to enable Keras 2. (#5488)

  • qml.ops.Conditional now stores the data, num_params, and ndim_param attributes of the operator it wraps. (#5473)

  • The molecular_hamiltonian function calls PySCF directly when method='pyscf' is selected. (#5118)

  • cache_execute has been replaced with an alternate implementation based on @transform. (#5318)

  • QNodes now defer diff_method validation to the device under the new device API. (#5176)

  • The device test suite has been extended to cover gradient methods, templates and arithmetic observables. (#5273) (#5518)

  • A typo and string formatting mistake have been fixed in the error message for ClassicalShadow._convert_to_pauli_words when the input is not a valid pauli_rep. (#5572)

  • Circuits running on lightning.qubit and that return qml.state() now preserve the dtype when specified. (#5547)

Breaking changes ๐Ÿ’”

  • qml.matrix() called on the following will now raise an error if wire_order is not specified:

    • tapes with more than one wire

    • quantum functions

    • Operator classes where num_wires does not equal to 1

    • QNodes if the device does not have wires specified.

    • PauliWords and PauliSentences with more than one wire. (#5328) (#5359)

  • single_tape_transform, batch_transform, qfunc_transform, op_transform, gradient_transform and hessian_transform have been removed. Instead, switch to using the new qml.transform function. Please refer to the transform docs to see how this can be done. (#5339)

  • Attempting to multiply PauliWord and PauliSentence with * will raise an error. Instead, use @ to conform with the PennyLane convention. (#5341)

  • DefaultQubit now uses a pre-emptive key-splitting strategy to avoid reusing JAX PRNG keys throughout a single execute call. (#5515)

  • qml.pauli.pauli_mult and qml.pauli.pauli_mult_with_phase have been removed. Instead, use qml.simplify(qml.prod(pauli_1, pauli_2)) to get the reduced operator. (#5324)

    >>> op = qml.simplify(qml.prod(qml.PauliX(0), qml.PauliZ(0)))
    >>> op
    -1j*(PauliY(wires=[0]))
    >>> [phase], [base] = op.terms()
    >>> phase, base
    (-1j, PauliY(wires=[0]))
    
  • The dynamic_one_shot transform now uses sampling (SampleMP) to get back the values of the mid-circuit measurements. (#5486)

  • Operator dunder methods now combine like-operator arithmetic classes via lazy=False. This reduces the chances of getting a RecursionError and makes nested operators easier to work with. (#5478)

  • The private functions _pauli_mult, _binary_matrix and _get_pauli_map from the pauli module have been removed. The same functionality can be achieved using newer features in the pauli module. (#5323)

  • MeasurementProcess.name and MeasurementProcess.data have been removed. Use MeasurementProcess.obs.name and MeasurementProcess.obs.data instead. (#5321)

  • Operator.validate_subspace(subspace) has been removed. Instead, use qml.ops.qutrit.validate_subspace(subspace). (#5311)

  • The contents of qml.interfaces has been moved inside qml.workflow. The old import path no longer exists. (#5329)

  • Since default.mixed does not support snapshots with measurements, attempting to do so will result in a DeviceError instead of getting the density matrix. (#5416)

  • LinearCombination._obs_data has been removed. You can still use LinearCombination.compare to check mathematical equivalence between a LinearCombination and another operator. (#5504)

Deprecations ๐Ÿ‘‹

  • Accessing qml.ops.Hamiltonian is deprecated because it points to the old version of the class that may not be compatible with the new approach to operator arithmetic. Instead, using qml.Hamiltonian is recommended because it dispatches to the LinearCombination class when the new approach to operator arithmetic is enabled. This will allow you to continue to use qml.Hamiltonian with existing code without needing to make any changes. (#5393)

  • qml.load has been deprecated. Instead, please use the functions outlined in the Importing workflows quickstart guide. (#5312)

  • Specifying control_values with a bit string in qml.MultiControlledX has been deprecated. Instead, use a list of booleans or 1s and 0s. (#5352)

  • qml.from_qasm_file has been deprecated. Instead, please open the file and then load its content using qml.from_qasm. (#5331)

    >>> with open("test.qasm", "r") as f:
    ...     circuit = qml.from_qasm(f.read())
    

Documentation ๐Ÿ“

  • A new page explaining the shapes and nesting of return types has been added. (#5418)

  • Redundant documentation for the evolve function has been removed. (#5347)

  • The final example in the compile docstring has been updated to use transforms correctly. (#5348)

  • A link to the demos for using qml.SpecialUnitary and qml.QNGOptimizer has been added to their respective docstrings. (#5376)

  • A code example in the qml.measure docstring has been added that showcases returning mid-circuit measurement statistics from QNodes. (#5441)

  • The computational basis convention used for qml.measure โ€” 0 and 1 rather than ยฑ1 โ€” has been clarified in its docstring. (#5474)

  • A new Release news section has been added to the table of contents, containing release notes, deprecations, and other pages focusing on recent changes. (#5548)

  • A summary of all changes has been added in the โ€œUpdated Operatorsโ€ page in the new โ€œRelease newsโ€ section in the docs. (#5483) (#5636)

Bug fixes ๐Ÿ›

  • Patches the QNode so that parameter-shift will be considered best with lightning if qml.metric_tensor is in the transform program. (#5624)

  • Stopped printing the ID of qcut.MeasureNode and qcut.PrepareNode in tape drawing. (#5613)

  • Improves the error message for setting shots on the new device interface, or trying to access a property that no longer exists. (#5616)

  • Fixed a bug where qml.draw and qml.draw_mpl incorrectly raised errors for circuits collecting statistics on mid-circuit measurements while using qml.defer_measurements. (#5610)

  • Using shot vectors with param_shift(... broadcast=True) caused a bug. This combination is no longer supported and will be added again in the next release. Fixed a bug with custom gradient recipes that only consist of unshifted terms. (#5612) (#5623)

  • qml.counts now returns the same keys with dynamic_one_shot and defer_measurements. (#5587)

  • null.qubit now automatically supports any operation without a decomposition. (#5582)

  • Fixed a bug where the shape and type of derivatives obtained by applying a gradient transform to a QNode differed based on whether the QNode uses classical coprocessing. (#4945)

  • ApproxTimeEvolution, CommutingEvolution, QDrift, and TrotterProduct now de-queue their input observable. (#5524)

  • (In)equality of qml.HilbertSchmidt instances is now reported correctly by qml.equal. (#5538)

  • qml.ParticleConservingU1 and qml.ParticleConservingU2 no longer raise an error when the initial state is not specified but default to the all-zeros state. (#5535)

  • qml.counts no longer returns negative samples when measuring 8 or more wires. (#5544) (#5556)

  • The dynamic_one_shot transform now works with broadcasting. (#5473)

  • Diagonalizing gates are now applied when measuring qml.probs on non-computational basis states on a Lightning device. (#5529)

  • two_qubit_decomposition no longer diverges at a special case of a unitary matrix. (#5448)

  • The qml.QNSPSAOptimizer now correctly handles optimization for legacy devices that do not follow the new device API. (#5497)

  • Operators applied to all wires are now drawn correctly in a circuit with mid-circuit measurements. (#5501)

  • Fixed a bug where certain unary mid-circuit measurement expressions would raise an uncaught error. (#5480)

  • Probabilities now sum to 1 when using the torch interface with default_dtype set to torch.float32. (#5462)

  • Tensorflow can now handle devices with float32 results but float64 input parameters. (#5446)

  • Fixed a bug where the argnum keyword argument of qml.gradients.stoch_pulse_grad references the wrong parameters in a tape, creating an inconsistency with other differentiation methods and preventing some use cases. (#5458)

  • Bounded value failures due to numerical noise with calls to np.random.binomial is now avoided. (#5447)

  • Using @ with legacy Hamiltonian instances now properly de-queues the previously existing operations. (#5455)

  • The QNSPSAOptimizer now properly handles differentiable parameters, resulting in being able to use it for more than one optimization step. (#5439)

  • The QNode interface now resets if an error occurs during execution. (#5449)

  • Failing tests due to changes with Lightningโ€™s adjoint diff pipeline have been fixed. (#5450)

  • Failures occurring when making autoray-dispatched calls to Torch with paired CPU data have been fixed. (#5438)

  • jax.jit now works with qml.sample with a multi-wire observable. (#5422)

  • qml.qinfo.quantum_fisher now works with non-default.qubit devices. (#5423)

  • We no longer perform unwanted dtype promotion in the pauli_rep of SProd instances when using Tensorflow. (#5246)

  • Fixed TestQubitIntegration.test_counts in tests/interfaces/test_jax_qnode.py to always produce counts for all outcomes. (#5336)

  • Fixed PauliSentence.to_mat(wire_order) to support identities with wires. (#5407)

  • CompositeOp.map_wires now correctly maps the overlapping_ops property. (#5430)

  • DefaultQubit.supports_derivatives has been updated to correctly handle circuits containing mid-circuit measurements and adjoint differentiation. (#5434)

  • SampleMP, ExpectationMP, CountsMP, and VarianceMP constructed with eigvals can now properly process samples. (#5463)

  • Fixed a bug in hamiltonian_expand that produces incorrect output dimensions when shot vectors are combined with parameter broadcasting. (#5494)

  • default.qubit now allows measuring Identity on no wires and observables containing Identity on no wires. (#5570)

  • Fixed a bug where TorchLayer does not work with shot vectors. (#5492)

  • Fixed a bug where the output shape of a QNode returning a list containing a single measurement is incorrect when combined with shot vectors. (#5492)

  • Fixed a bug in qml.math.kron that makes Torch incompatible with NumPy. (#5540)

  • Fixed a bug in _group_measurements that fails to group measurements with commuting observables when they are operands of Prod. (#5525)

  • qml.equal can now be used with sums and products that contain operators on no wires like I and GlobalPhase. (#5562)

  • CompositeOp.has_diagonalizing_gates now does a more complete check of the base operators to ensure consistency between op.has_diagonalzing_gates and op.diagonalizing_gates() (#5603)

  • Updated the method kwarg of qml.TrotterProduct().error() to be more clear that we are computing upper-bounds. (#5637)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Tarun Kumar Allamsetty, Guillermo Alonso, Mikhail Andrenkov, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Diksha Dhawan, Isaac De Vlugt, Amintor Dusko, Pietropaolo Frisoni, Lillian M. A. Frederiksen, Diego Guala, Austin Huang, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Mudit Pandey, Kenya Sakka, Jay Soni, Matthew Silverman, David Wierichs.

orphan

Release 0.35.0ยถ

New features since last release

Qiskit 1.0 integration ๐Ÿ”Œ

  • This version of PennyLane makes it easier to import circuits from Qiskit. (#5218) (#5168)

    The qml.from_qiskit function converts a Qiskit QuantumCircuit into a PennyLane quantum function. Although qml.from_qiskit already exists in PennyLane, we have made a number of improvements to make importing from Qiskit easier. And yes โ€” qml.from_qiskit functionality is compatible with both Qiskit 1.0 and earlier versions! Hereโ€™s a comprehensive list of the improvements:

    • You can now append PennyLane measurements onto the quantum function returned by qml.from_qiskit. Consider this simple Qiskit circuit:

      import pennylane as qml
      from qiskit import QuantumCircuit
      
      qc = QuantumCircuit(2)
      qc.rx(0.785, 0)
      qc.ry(1.57, 1)
      

      We can convert it into a PennyLane QNode in just a few lines, with PennyLane measurements easily included:

      >>> dev = qml.device("default.qubit")
      >>> measurements = qml.expval(qml.Z(0) @ qml.Z(1))
      >>> qfunc = qml.from_qiskit(qc, measurements=measurements)
      >>> qnode = qml.QNode(qfunc, dev)
      >>> qnode()
      tensor(0.00056331, requires_grad=True)
      
    • Quantum circuits that already contain Qiskit-side measurements can be faithfully converted with qml.from_qiskit. Consider this example Qiskit circuit:

      qc = QuantumCircuit(3, 2)  # Teleportation
      
      qc.rx(0.9, 0)  # Prepare input state on qubit 0
      
      qc.h(1)  # Prepare Bell state on qubits 1 and 2
      qc.cx(1, 2)
      
      qc.cx(0, 1)  # Perform teleportation
      qc.h(0)
      qc.measure(0, 0)
      qc.measure(1, 1)
      
      with qc.if_test((1, 1)):  # Perform first conditional
          qc.x(2)
      

      This circuit can be converted into PennyLane with the Qiskit measurements still accessible. For example, we can use those results as inputs to a mid-circuit measurement in PennyLane:

      @qml.qnode(dev)
      def teleport():
          m0, m1 = qml.from_qiskit(qc)()
          qml.cond(m0, qml.Z)(2)
          return qml.density_matrix(2)
      
      >>> teleport()
      tensor([[0.81080498+0.j        , 0.        +0.39166345j],
              [0.        -0.39166345j, 0.18919502+0.j        ]], requires_grad=True)
      
    • It is now more intuitive to handle and differentiate parametrized Qiskit circuits. Consider the following circuit:

      from qiskit.circuit import Parameter
      from pennylane import numpy as np
      
      angle0 = Parameter("x")
      angle1 = Parameter("y")
      
      qc = QuantumCircuit(2, 2)
      qc.rx(angle0, 0)
      qc.ry(angle1, 1)
      qc.cx(1, 0)
      

      We can convert this circuit into a QNode with two arguments, corresponding to x and y:

      measurements = qml.expval(qml.PauliZ(0))
      qfunc = qml.from_qiskit(qc, measurements)
      qnode = qml.QNode(qfunc, dev)
      

      The QNode can be evaluated and differentiated:

      >>> x, y = np.array([0.4, 0.5], requires_grad=True)
      >>> qnode(x, y)
      tensor(0.80830707, requires_grad=True)
      >>> qml.grad(qnode)(x, y)
      (tensor(-0.34174675, requires_grad=True),
       tensor(-0.44158016, requires_grad=True))
      

      This shows how easy it is to make a Qiskit circuit differentiable with PennyLane.

  • In addition to circuits, it is also possible to convert operators from Qiskit to PennyLane with a new function called qml.from_qiskit_op. (#5251)

    A Qiskit SparsePauliOp can be converted to a PennyLane operator using qml.from_qiskit_op:

    >>> from qiskit.quantum_info import SparsePauliOp
    >>> qiskit_op = SparsePauliOp(["II", "XY"])
    >>> qiskit_op
    SparsePauliOp(['II', 'XY'],
                  coeffs=[1.+0.j, 1.+0.j])
    >>> pl_op = qml.from_qiskit_op(qiskit_op)
    >>> pl_op
    I(0) + X(1) @ Y(0)
    

    Combined with qml.from_qiskit, it becomes easy to quickly calculate quantities like expectation values by converting the whole workflow to PennyLane:

    qc = QuantumCircuit(2)  # Create circuit
    qc.rx(0.785, 0)
    qc.ry(1.57, 1)
    
    measurements = qml.expval(pl_op)  # Create QNode
    qfunc = qml.from_qiskit(qc, measurements)
    qnode = qml.QNode(qfunc, dev)
    
    >>> qnode()  # Evaluate!
    tensor(0.29317504, requires_grad=True)
    

Native mid-circuit measurements on Default Qubit ๐Ÿ’ก

  • Mid-circuit measurements can now be more scalable and efficient in finite-shots mode with default.qubit by simulating them in a similar way to what happens on quantum hardware. (#5088) (#5120)

    Previously, mid-circuit measurements (MCMs) would be automatically replaced with an additional qubit using the @qml.defer_measurements transform. The circuit below would have required thousands of qubits to simulate.

    Now, MCMs are performed in a similar way to quantum hardware with finite shots on default.qubit. For each shot and each time an MCM is encountered, the device evaluates the probability of projecting onto |0> or |1> and makes a random choice to collapse the circuit state. This approach works well when there are a lot of MCMs and the number of shots is not too high.

    import pennylane as qml
    
    dev = qml.device("default.qubit", shots=10)
    
    @qml.qnode(dev)
    def f():
        for i in range(1967):
            qml.Hadamard(0)
            qml.measure(0)
        return qml.sample(qml.PauliX(0))
    
    >>> f()
    tensor([-1, -1, -1,  1,  1, -1,  1, -1,  1, -1], requires_grad=True)
    

Work easily and efficiently with operators ๐Ÿ”ง

  • Over the past few releases, PennyLaneโ€™s approach to operator arithmetic has been in the process of being overhauled. We have a few objectives:

    1. To make it as easy to work with PennyLane operators as it would be with pen and paper.

    2. To improve the efficiency of operator arithmetic.

    The updated operator arithmetic functionality is still being finalized, but can be activated using qml.operation.enable_new_opmath(). In the next release, the new behaviour will become the default, so we recommend enabling now to become familiar with the new system!

    The following updates have been made in this version of PennyLane:

    • You can now easily access Pauli operators via I, X, Y, and Z: (#5116)

      >>> from pennylane import I, X, Y, Z
      >>> X(0)
      X(0)
      

      The original long-form names Identity, PauliX, PauliY, and PauliZ remain available, but use of the short-form names is now recommended.

      The original long-form names Identity, PauliX, PauliY, and PauliZ remain available, but use of the short-form names is now recommended.

    • A new qml.commutator function is now available that allows you to compute commutators between PennyLane operators. (#5051) (#5052) (#5098)

      >>> qml.commutator(X(0), Y(0))
      2j * Z(0)
      
    • Operators in PennyLane can have a backend Pauli representation, which can be used to perform faster operator arithmetic. Now, the Pauli representation will be automatically used for calculations when available. (#4989) (#5001) (#5003) (#5017) (#5027)

      The Pauli representation can be optionally accessed via op.pauli_rep:

      >>> qml.operation.enable_new_opmath()
      >>> op = X(0) + Y(0)
      >>> op.pauli_rep
      1.0 * X(0)
      + 1.0 * Y(0)
      
    • Extensive improvements have been made to the string representations of PennyLane operators, making them shorter and possible to copy-paste as valid PennyLane code. (#5116) (#5138)

      >>> 0.5 * X(0)
      0.5 * X(0)
      >>> 0.5 * (X(0) + Y(1))
      0.5 * (X(0) + Y(1))
      

      Sums with many terms are broken up into multiple lines, but can still be copied back as valid code:

      >>> 0.5 * (X(0) @ X(1)) + 0.7 * (X(1) @ X(2)) + 0.8 * (X(2) @ X(3))
      (
          0.5 * (X(0) @ X(1))
        + 0.7 * (X(1) @ X(2))
        + 0.8 * (X(2) @ X(3))
      )
      
    • Linear combinations of operators and operator multiplication via Sum and Prod, respectively, have been updated to reach feature parity with Hamiltonian and Tensor, respectively. This should minimize the effort to port over any existing code. (#5070) (#5132) (#5133)

      Updates include support for grouping via the pauli module:

      >>> obs = [X(0) @ Y(1), Z(0), Y(0) @ Z(1), Y(1)]
      >>> qml.pauli.group_observables(obs)
      [[Y(0) @ Z(1)], [X(0) @ Y(1), Y(1)], [Z(0)]]
      

New Clifford device ๐Ÿฆพ

  • A new default.clifford device enables efficient simulation of large-scale Clifford circuits defined in PennyLane through the use of stim as a backend. (#4936) (#4954) (#5144)

    Given a circuit with only Clifford gates, one can use this device to obtain the usual range of PennyLane measurements as well as the state represented in the Tableau form of Aaronson & Gottesman (2004):

    import pennylane as qml
    
    dev = qml.device("default.clifford", tableau=True)
    @qml.qnode(dev)
    def circuit():
        qml.CNOT(wires=[0, 1])
        qml.PauliX(wires=[1])
        qml.ISWAP(wires=[0, 1])
        qml.Hadamard(wires=[0])
        return qml.state()
    
    >>> circuit()
    array([[0, 1, 1, 0, 0],
          [1, 0, 1, 1, 1],
          [0, 0, 0, 1, 0],
          [1, 0, 0, 1, 1]])
    

    The default.clifford device also supports the PauliError, DepolarizingChannel, BitFlip and PhaseFlip noise channels when operating in finite-shot mode.

Improvements ๐Ÿ› 

Faster gradients with VJPs and other performance improvements

  • Vector-Jacobian products (VJPs) can result in faster computations when the output of your quantum Node has a low dimension. They can be enabled by setting device_vjp=True when loading a QNode. In the next release of PennyLane, VJPs are planned to be used by default, when available.

    In this release, we have unlocked:

    • Adjoint device VJPs can be used with jax.jacobian, meaning that device_vjp=True is always faster when using JAX with default.qubit. (#4963)

    • PennyLane can now use lightning-provided VJPs. (#4914)

    • VJPs can be used with TensorFlow, though support has not yet been added for tf.Function and Tensorflow Autograph. (#4676)

  • Measuring qml.probs is now faster due to an optimization in converting samples to counts. (#5145)

  • The performance of circuit-cutting workloads with large numbers of generated tapes has been improved. (#5005)

  • Queueing (AnnotatedQueue) has been removed from qml.cut_circuit and qml.cut_circuit_mc to improve performance for large workflows. (#5108)

Community contributions ๐Ÿฅณ

  • A new function called qml.fermi.parity_transform has been added for parity mapping of a fermionic Hamiltonian. (#4928)

    It is now possible to transform a fermionic Hamiltonian to a qubit Hamiltonian with parity mapping.

    import pennylane as qml
    fermi_ham = qml.fermi.FermiWord({(0, 0) : '+', (1, 1) : '-'})
    
    qubit_ham = qml.fermi.parity_transform(fermi_ham, n=6)
    
    >>> print(qubit_ham)
    -0.25j * Y(0) + (-0.25+0j) * (X(0) @ Z(1)) + (0.25+0j) * X(0) + 0.25j * (Y(0) @ Z(1))
    
  • The transform split_non_commuting now accepts measurements of type probs, sample, and counts, which accept both wires and observables. (#4972)

  • The efficiency of matrix calculations when an operator is symmetric over a given set of wires has been improved. (#3601)

  • The pennylane/math/quantum.py module now has support for computing the minimum entropy of a density matrix. (#3959)

    >>> x = [1, 0, 0, 1] / np.sqrt(2)
    >>> x = qml.math.dm_from_state_vector(x)
    >>> qml.math.min_entropy(x, indices=[0])
    0.6931471805599455
    
  • A function called apply_operation that applies operations to device-compatible states has been added to the new qutrit_mixed module found in qml.devices. (#5032)

  • A function called measure has been added to the new qutrit_mixed module found in qml.devices that measures device-compatible states for a collection of measurement processes. (#5049)

  • A partial_trace function has been added to qml.math for taking the partial trace of matrices. (#5152)

Other operator arithmetic improvements

  • The following capabilities have been added for Pauli arithmetic: (#4989) (#5001) (#5003) (#5017) (#5027) (#5018)

    • You can now multiply PauliWord and PauliSentence instances by scalars (e.g., 0.5 * PauliWord({0: "X"}) or 0.5 * PauliSentence({PauliWord({0: "X"}): 1.})).

    • You can now intuitively add and subtract PauliWord and PauliSentence instances and scalars together (scalars are treated implicitly as multiples of the identity, I). For example, ps1 + pw1 + 1. for some Pauli word pw1 = PauliWord({0: "X", 1: "Y"}) and Pauli sentence ps1 = PauliSentence({pw1: 3.}).

    • You can now element-wise multiply PauliWord, PauliSentence, and operators together with qml.dot (e.g., qml.dot([0.5, -1.5, 2], [pw1, ps1, id_word]) with id_word = PauliWord({})).

    • qml.matrix now accepts PauliWord and PauliSentence instances (e.g., qml.matrix(PauliWord({0: "X"}))).

    • It is now possible to compute commutators with Pauli operators natively with the new commutator method.

      >>> op1 = PauliWord({0: "X", 1: "X"})
      >>> op2 = PauliWord({0: "Y"}) + PauliWord({1: "Y"})
      >>> op1.commutator(op2)
      2j * Z(0) @ X(1)
      + 2j * X(0) @ Z(1)
      
  • Composite operations (e.g., those made with qml.prod and qml.sum) and scalar-product operations convert Hamiltonian and Tensor operands to Sum and Prod types, respectively. This helps avoid the mixing of incompatible operator types. (#5031) (#5063)

  • qml.Identity() can be initialized without wires. Measuring it is currently not possible, though. (#5106)

  • qml.dot now returns a Sum class even when all the coefficients match. (#5143)

  • qml.pauli.group_observables now supports grouping Prod and SProd operators. (#5070)

  • The performance of converting a PauliSentence to a Sum has been improved. (#5141) (#5150)

  • Akin to qml.Hamiltonian features, the coefficients and operators that make up composite operators formed via Sum or Prod can now be accessed with the terms() method. (#5132) (#5133) (#5164)

    >>> qml.operation.enable_new_opmath()
    >>> op = X(0) @ (0.5 * X(1) + X(2))
    >>> op.terms()
    ([0.5, 1.0],
     [X(1) @ X(0),
      X(2) @ X(0)])
    
  • String representations of ParametrizedHamiltonian have been updated to match the style of other PL operators. (#5215)

Other improvements

  • The pl-device-test suite is now compatible with the qml.devices.Device interface. (#5229)

  • The QSVT operation now determines its data from the block encoding and projector operator data. (#5226) (#5248)

  • The BlockEncode operator is now JIT-compatible with JAX. (#5110)

  • The qml.qsvt function uses qml.GlobalPhase instead of qml.exp to define a global phase. (#5105)

  • The tests/ops/functions/conftest.py test has been updated to ensure that all operator types are tested for validity. (#4978)

  • A new pennylane.workflow module has been added. This module now contains qnode.py, execution.py, set_shots.py, jacobian_products.py, and the submodule interfaces. (#5023)

  • A more informative error is now raised when calling adjoint_jacobian with trainable state-prep operations. (#5026)

  • qml.workflow.get_transform_program and qml.workflow.construct_batch have been added to inspect the transform program and batch of tapes at different stages. (#5084)

  • All custom controlled operations such as CRX, CZ, CNOT, ControlledPhaseShift now inherit from ControlledOp, giving them additional properties such as control_wire and control_values. Calling qml.ctrl on RX, RY, RZ, Rot, and PhaseShift with a single control wire will return gates of types CRX, CRY, etc. as opposed to a general Controlled operator. (#5069) (#5199)

  • The CI will now fail if coverage data fails to upload to codecov. Previously, it would silently pass and the codecov check itself would never execute. (#5101)

  • qml.ctrl called on operators with custom controlled versions will now return instances of the custom class, and it will flatten nested controlled operators to a single multi-controlled operation. For PauliX, CNOT, Toffoli, and MultiControlledX, calling qml.ctrl will always resolve to the best option in CNOT, Toffoli, or MultiControlledX depending on the number of control wires and control values. (#5125)

  • Unwanted warning filters have been removed from tests and no PennyLaneDeprecationWarnings are being raised unexpectedly. (#5122)

  • New error tracking and propagation functionality has been added (#5115) (#5121)

  • The method map_batch_transform has been replaced with the method _batch_transform implemented in TransformDispatcher. (#5212)

  • TransformDispatcher can now dispatch onto a batch of tapes, making it easier to compose transforms when working in the tape paradigm. (#5163)

  • qml.ctrl is now a simple wrapper that either calls PennyLaneโ€™s built in create_controlled_op or uses the Catalyst implementation. (#5247)

  • Controlled composite operations can now be decomposed using ZYZ rotations. (#5242)

  • New functions called qml.devices.modifiers.simulator_tracking and qml.devices.modifiers.single_tape_support have been added to add basic default behavior onto a device class. (#5200)

Breaking changes ๐Ÿ’”

  • Passing additional arguments to a transform that decorates a QNode must now be done through the use of functools.partial. (#5046)

  • qml.ExpvalCost has been removed. Users should use qml.expval() moving forward. (#5097)

  • Caching of executions is now turned off by default when max_diff == 1, as the classical overhead cost outweighs the probability that duplicate circuits exists. (#5243)

  • The entry point convention registering compilers with PennyLane has changed. (#5140)

    To allow for packages to register multiple compilers with PennyLane, the entry_points convention under the designated group name pennylane.compilers has been modified.

    Previously, compilers would register qjit (JIT decorator), ops (compiler-specific operations), and context (for tracing and program capture).

    Now, compilers must register compiler_name.qjit, compiler_name.ops, and compiler_name.context, where compiler_name is replaced by the name of the provided compiler.

    For more information, please see the documentation on adding compilers.

  • PennyLane source code is now compatible with the latest version of black. (#5112) (#5119)

  • gradient_analysis_and_validation has been renamed to find_and_validate_gradient_methods. Instead of returning a list, it now returns a dictionary of gradient methods for each parameter index, and no longer mutates the tape. (#5035)

  • Multiplying two PauliWord instances no longer returns a tuple (new_word, coeff) but instead PauliSentence({new_word: coeff}). The old behavior is still available with the private method PauliWord._matmul(other) for faster processing. (#5045)

  • Observable.return_type has been removed. Instead, you should inspect the type of the surrounding measurement process. (#5044)

  • ClassicalShadow.entropy() no longer needs an atol keyword as a better method to estimate entropies from approximate density matrix reconstructions (with potentially negative eigenvalues). (#5048)

  • Controlled operators with a custom controlled version decompose like how their controlled counterpart decomposes as opposed to decomposing into their controlled version. (#5069) (#5125)

    For example:

    >>> qml.ctrl(qml.RX(0.123, wires=1), control=0).decomposition()
    [
      RZ(1.5707963267948966, wires=[1]),
      RY(0.0615, wires=[1]),
      CNOT(wires=[0, 1]),
      RY(-0.0615, wires=[1]),
      CNOT(wires=[0, 1]),
      RZ(-1.5707963267948966, wires=[1])
    ]
    
  • QuantumScript.is_sampled and QuantumScript.all_sampled have been removed. Users should now validate these properties manually. (#5072)

  • qml.transforms.one_qubit_decomposition and qml.transforms.two_qubit_decomposition have been removed. Instead, you should use qml.ops.one_qubit_decomposition and qml.ops.two_qubit_decomposition. (#5091)

Deprecations ๐Ÿ‘‹

  • Calling qml.matrix without providing a wire_order on objects where the wire order could be ambiguous now raises a warning. In the future, the wire_order argument will be required in these cases. (#5039)

  • Operator.validate_subspace(subspace) has been relocated to the qml.ops.qutrit.parametric_ops module and will be removed from the Operator class in an upcoming release. (#5067)

  • Matrix and tensor products between PauliWord and PauliSentence instances are done using the @ operator, * will be used only for scalar multiplication. Note also the breaking change that the product of two PauliWord instances now returns a PauliSentence instead of a tuple (new_word, coeff). (#4989) (#5054)

  • MeasurementProcess.name and MeasurementProcess.data are now deprecated, as they contain dummy values that are no longer needed. (#5047) (#5071) (#5076) (#5122)

  • qml.pauli.pauli_mult and qml.pauli.pauli_mult_with_phase are now deprecated. Instead, you should use qml.simplify(qml.prod(pauli_1, pauli_2)) to get the reduced operator. (#5057)

  • The private functions _pauli_mult, _binary_matrix and _get_pauli_map from the pauli module have been deprecated, as they are no longer used anywhere and the same functionality can be achieved using newer features in the pauli module. (#5057)

  • Sum.ops, Sum.coeffs, Prod.ops and Prod.coeffs will be deprecated in the future. (#5164)

Documentation ๐Ÿ“

  • The module documentation for pennylane.tape now explains the difference between QuantumTape and QuantumScript. (#5065)

  • A typo in a code example in the qml.transforms API has been fixed. (#5014)

  • Documentation for qml.data has been updated and now mentions a way to access the same dataset simultaneously from multiple environments. (#5029)

  • A clarification for the definition of argnum added to gradient methods has been made. (#5035)

  • A typo in the code example for qml.qchem.dipole_of has been fixed. (#5036)

  • A development guide on deprecations and removals has been added. (#5083)

  • A note about the eigenspectrum of second-quantized Hamiltonians has been added to qml.eigvals. (#5095)

  • A warning about two mathematically equivalent Hamiltonians undergoing different time evolutions has been added to qml.TrotterProduct and qml.ApproxTimeEvolution. (#5137)

  • A reference to the paper that provides the image of the qml.QAOAEmbedding template has been added. (#5130)

  • The docstring of qml.sample has been updated to advise the use of single-shot expectations instead when differentiating a circuit. (#5237)

  • A quick start page has been added called โ€œImporting Circuitsโ€. This explains how to import quantum circuits and operations defined outside of PennyLane. (#5281)

Bug fixes ๐Ÿ›

  • QubitChannel can now be used with jitting. (#5288)

  • Fixed a bug in the matplotlib drawer where the colour of Barrier did not match the requested style. (#5276)

  • qml.draw and qml.draw_mpl now apply all applied transforms before drawing. (#5277)

  • ctrl_decomp_zyz is now differentiable. (#5198)

  • qml.ops.Pow.matrix() is now differentiable with TensorFlow with integer exponents. (#5178)

  • The qml.MottonenStatePreparation template has been updated to include a global phase operation. (#5166)

  • Fixed a queuing bug when using qml.prod with a quantum function that queues a single operator. (#5170)

  • The qml.TrotterProduct template has been updated to accept scalar products of operators as an input Hamiltonian. (#5073)

  • Fixed a bug where caching together with JIT compilation and broadcasted tapes yielded wrong results Operator.hash now depends on the memory location, id, of a JAX tracer instead of its string representation. (#3917)

  • qml.transforms.undo_swaps can now work with operators with hyperparameters or nesting. (#5081)

  • qml.transforms.split_non_commuting will now pass the original shots along. (#5081)

  • If argnum is provided to a gradient transform, only the parameters specified in argnum will have their gradient methods validated. (#5035)

  • StatePrep operations expanded onto more wires are now compatible with backprop. (#5028)

  • qml.equal works well with qml.Sum operators when wire labels are a mix of integers and strings. (#5037)

  • The return value of Controlled.generator now contains a projector that projects onto the correct subspace based on the control value specified. (#5068)

  • CosineWindow no longer raises an unexpected error when used on a subset of wires at the beginning of a circuit. (#5080)

  • tf.function now works with TensorSpec(shape=None) by skipping batch size computation. (#5089)

  • PauliSentence.wires no longer imposes a false order. (#5041)

  • qml.qchem.import_state now applies the chemist-to-physicist sign convention when initializing a PennyLane state vector from classically pre-computed wavefunctions. That is, it interleaves spin-up/spin-down operators for the same spatial orbital index, as standard in PennyLane (instead of commuting all spin-up operators to the left, as is standard in quantum chemistry). (#5114)

  • Multi-wire controlled CNOT and PhaseShift are now be decomposed correctly. (#5125) (#5148)

  • draw_mpl no longer raises an error when drawing a circuit containing an adjoint of a controlled operation. (#5149)

  • default.mixed no longer throws ValueError when applying a state vector that is not of type complex128 when used with tensorflow. (#5155)

  • ctrl_decomp_zyz no longer raises a TypeError if the rotation parameters are of type torch.Tensor (#5183)

  • Comparing Prod and Sum objects now works regardless of nested structure with qml.equal if the operators have a valid pauli_rep property. (#5177)

  • Controlled GlobalPhase with non-zero control wires no longer throws an error. (#5194)

  • A QNode transformed with mitigate_with_zne now accepts batch parameters. (#5195)

  • The matrix of an empty PauliSentence instance is now correct (all-zeros). Further, matrices of empty PauliWord and PauliSentence instances can now be turned into matrices. (#5188)

  • PauliSentence instances can handle matrix multiplication with PauliWord instances. (#5208)

  • CompositeOp.eigendecomposition is now JIT-compatible. (#5207)

  • QubitDensityMatrix now works with JAX-JIT on the default.mixed device. (#5203) (#5236)

  • When a QNode specifies diff_method="adjoint", default.qubit no longer tries to decompose non-trainable operations with non-scalar parameters such as QubitUnitary. (#5233)

  • The overwriting of the class names of I, X, Y, and Z no longer happens in the initialization after causing problems with datasets. This now happens globally. (#5252)

  • The adjoint_metric_tensor transform now works with jax. (#5271)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Abhishek Abhishek, Mikhail Andrenkov, Utkarsh Azad, Trenten Babcock, Gabriel Bottrill, Thomas Bromley, Astral Cai, Skylar Chan, Isaac De Vlugt, Diksha Dhawan, Lillian Frederiksen, Pietropaolo Frisoni, Eugenio Gigante, Diego Guala, David Ittah, Soran Jahangiri, Jacky Jiang, Korbinian Kottmann, Christina Lee, Xiaoran Li, Vincent Michaud-Rioux, Romain Moyard, Pablo Antonio Moreno Casares, Erick Ochoa Lopez, Lee J. Oโ€™Riordan, Mudit Pandey, Alex Preciado, Matthew Silverman, Jay Soni.

orphan

Release 0.34.0ยถ

New features since last release

Statistics and drawing for mid-circuit measurements ๐ŸŽจ

  • It is now possible to return statistics of composite mid-circuit measurements. (#4888)

    Mid-circuit measurement results can be composed using basic arithmetic operations and then statistics can be calculated by putting the result within a PennyLane measurement like qml.expval(). For example:

    import pennylane as qml
    
    dev = qml.device("default.qubit")
    
    @qml.qnode(dev)
    def circuit(phi, theta):
        qml.RX(phi, wires=0)
        m0 = qml.measure(wires=0)
        qml.RY(theta, wires=1)
        m1 = qml.measure(wires=1)
        return qml.expval(~m0 + m1)
    
    print(circuit(1.23, 4.56))
    
    1.2430187928114291
    

    Another option, for ease-of-use when using qml.sample(), qml.probs(), or qml.counts(), is to provide a simple list of mid-circuit measurement results:

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev)
    def circuit(phi, theta):
        qml.RX(phi, wires=0)
        m0 = qml.measure(wires=0)
        qml.RY(theta, wires=1)
        m1 = qml.measure(wires=1)
        return qml.sample(op=[m0, m1])
    
    print(circuit(1.23, 4.56, shots=5))
    
    [[0 1]
     [0 1]
     [0 0]
     [1 0]
     [0 1]]
    

    Composite mid-circuit measurement statistics are supported on default.qubit and default.mixed. To learn more about which measurements and arithmetic operators are supported, refer to the measurements page and the documentation for qml.measure.

  • Mid-circuit measurements can now be visualized with the text-based qml.draw() and the graphical qml.draw_mpl() methods. (#4775) (#4803) (#4832) (#4901) (#4850) (#4917) (#4930) (#4957)

    Drawing of mid-circuit measurement capabilities including qubit reuse and reset, postselection, conditioning, and collecting statistics is now supported. Here is an all-encompassing example:

    def circuit():
        m0 = qml.measure(0, reset=True)
        m1 = qml.measure(1, postselect=1)
        qml.cond(m0 - m1 == 0, qml.S)(0)
        m2 = qml.measure(1)
        qml.cond(m0 + m1 == 2, qml.T)(0)
        qml.cond(m2, qml.PauliX)(1)
    

    The text-based drawer outputs:

    >>> print(qml.draw(circuit)())
    0: โ”€โ”€โ”คโ†—โ”‚  โ”‚0โŸฉโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€Sโ”€โ”€โ”€โ”€โ”€โ”€โ”€Tโ”€โ”€โ”€โ”€โ”ค
    1: โ”€โ”€โ”€โ•‘โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”คโ†—โ‚โ”œโ”€โ”€โ•‘โ”€โ”€โ”คโ†—โ”œโ”€โ”€โ•‘โ”€โ”€Xโ”€โ”ค
          โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•‘โ•โ•โ•โ•โ•ฌโ•โ•โ•โ•‘โ•โ•โ•โ•ฃ  โ•‘
                    โ•šโ•โ•โ•โ•โ•ฉโ•โ•โ•โ•‘โ•โ•โ•โ•  โ•‘
                             โ•šโ•โ•โ•โ•โ•โ•โ•
    

    The graphical drawer outputs:

    >>> print(qml.draw_mpl(circuit)())
    

Catalyst is seamlessly integrated with PennyLane โš—๏ธ

  • Catalyst, our next-generation compilation framework, is now accessible within PennyLane, allowing you to more easily benefit from hybrid just-in-time (JIT) compilation.

    To access these features, simply install pennylane-catalyst:

    pip install pennylane-catalyst
    

    The qml.compiler module provides support for hybrid quantum-classical compilation. (#4692) (#4979)

    Through the use of the qml.qjit decorator, entire workflows can be JIT compiled โ€” including both quantum and classical processing โ€” down to a machine binary on first-function execution. Subsequent calls to the compiled function will execute the previously-compiled binary, resulting in significant performance improvements.

    import pennylane as qml
    
    dev = qml.device("lightning.qubit", wires=2)
    
    @qml.qjit
    @qml.qnode(dev)
    def circuit(theta):
        qml.Hadamard(wires=0)
        qml.RX(theta, wires=1)
        qml.CNOT(wires=[0,1])
        return qml.expval(qml.PauliZ(wires=1))
    
    >>> circuit(0.5)  # the first call, compilation occurs here
    array(0.)
    >>> circuit(0.5)  # the precompiled quantum function is called
    array(0.)
    

    Currently, PennyLane supports the Catalyst hybrid compiler with the qml.qjit decorator. A significant benefit of Catalyst is the ability to preserve complex control flow around quantum operations โ€” such as if statements and for loops, and including measurement feedback โ€” during compilation, while continuing to support end-to-end autodifferentiation.

  • The following functions can now be used with the qml.qjit decorator: qml.grad, qml.jacobian, qml.vjp, qml.jvp, and qml.adjoint. (#4709) (#4724) (#4725) (#4726)

    When qml.grad or qml.jacobian are used with @qml.qjit, they are patched to catalyst.grad and catalyst.jacobian, respectively.

    dev = qml.device("lightning.qubit", wires=1)
    
    @qml.qjit
    def workflow(x):
    
        @qml.qnode(dev)
        def circuit(x):
            qml.RX(np.pi * x[0], wires=0)
            qml.RY(x[1], wires=0)
            return qml.probs()
    
        g = qml.jacobian(circuit)
    
        return g(x)
    
    >>> workflow(np.array([2.0, 1.0]))
    array([[ 3.48786850e-16, -4.20735492e-01],
           [-8.71967125e-17,  4.20735492e-01]])
    
  • JIT-compatible functionality for control flow has been added via qml.for_loop, qml.while_loop, and qml.cond. (#4698) (#5006)

    qml.for_loop and qml.while_loop can be deployed as decorators on functions that are the body of the loop. The arguments to both follow typical conventions:

    @qml.for_loop(lower_bound, upper_bound, step)
    
    @qml.while_loop(cond_function)
    

    Here is a concrete example with qml.for_loop:

    qml.for_loop and qml.while_loop can be deployed as decorators on functions that are the body of the loop. The arguments to both follow typical conventions:

    @qml.for_loop(lower_bound, upper_bound, step)
    
    @qml.while_loop(cond_function)
    

    Here is a concrete example with qml.for_loop:

    dev = qml.device("lightning.qubit", wires=1)
    
    @qml.qjit
    @qml.qnode(dev)
    def circuit(n: int, x: float):
    
        @qml.for_loop(0, n, 1)
        def loop_rx(i, x):
            # perform some work and update (some of) the arguments
            qml.RX(x, wires=0)
    
            # update the value of x for the next iteration
            return jnp.sin(x)
    
        # apply the for loop
        final_x = loop_rx(x)
    
        return qml.expval(qml.PauliZ(0)), final_x
    
    >>> circuit(7, 1.6)
    (array(0.97926626), array(0.55395718))
    

Decompose circuits into the Clifford+T gateset ๐Ÿงฉ

  • The new qml.clifford_t_decomposition() transform provides an approximate breakdown of an input circuit into the Clifford+T gateset. Behind the scenes, this decomposition is enacted via the sk_decomposition() function using the Solovay-Kitaev algorithm. (#4801) (#4802)

    The Solovay-Kitaev algorithm approximately decomposes a quantum circuit into the Clifford+T gateset. To account for this, a desired total circuit decomposition error, epsilon, must be specified when using qml.clifford_t_decomposition:

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev)
    def circuit():
        qml.RX(1.1, 0)
        return qml.state()
    
    circuit = qml.clifford_t_decomposition(circuit, epsilon=0.1)
    
    >>> print(qml.draw(circuit)())
    0: โ”€โ”€Tโ€ โ”€โ”€Hโ”€โ”€Tโ€ โ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ€ โ”€โ”€Hโ”€โ”€Tโ€ โ”€โ”€Tโ€ โ”€โ”€Hโ”€โ”€Tโ€ โ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€Tโ€ โ”€โ”€H
    
    โ”€โ”€โ”€Tโ€ โ”€โ”€Hโ”€โ”€Tโ”€โ”€Hโ”€โ”€GlobalPhase(0.39)โ”€โ”ค
    

    The resource requirements of this circuit can also be evaluated:

    >>> with qml.Tracker(dev) as tracker:
    ...     circuit()
    >>> resources_lst = tracker.history["resources"]
    >>> resources_lst[0]
    wires: 1
    gates: 34
    depth: 34
    shots: Shots(total=None)
    gate_types:
    {'Adjoint(T)': 8, 'Hadamard': 16, 'T': 9, 'GlobalPhase': 1}
    gate_sizes:
    {1: 33, 0: 1}
    

Use an iterative approach for quantum phase estimation ๐Ÿ”„

  • Iterative Quantum Phase Estimation is now available with qml.iterative_qpe. (#4804)

    The subroutine can be used similarly to mid-circuit measurements:

    import pennylane as qml
    
    dev = qml.device("default.qubit", shots=5)
    
    @qml.qnode(dev)
    def circuit():
    
      # Initial state
      qml.PauliX(wires=[0])
    
      # Iterative QPE
      measurements = qml.iterative_qpe(qml.RZ(2., wires=[0]), ancilla=[1], iters=3)
    
      return [qml.sample(op=meas) for meas in measurements]
    
    >>> print(circuit())
    [array([0, 0, 0, 0, 0]), array([1, 0, 0, 0, 0]), array([0, 1, 1, 1, 1])]
    

    The \(i\)-th element in the list refers to the 5 samples generated by the \(i\)-th measurement of the algorithm.

Improvements ๐Ÿ› 

Community contributions ๐Ÿฅณ

  • The += operand can now be used with a PauliSentence, which has also provides a performance boost. (#4662)

  • The Approximate Quantum Fourier Transform (AQFT) is now available with qml.AQFT. (#4715)

  • qml.draw and qml.draw_mpl now render operator IDs. (#4749)

    The ID can be specified as a keyword argument when instantiating an operator:

    >>> def circuit():
    ...     qml.RX(0.123, id="data", wires=0)
    >>> print(qml.draw(circuit)())
    0: โ”€โ”€RX(0.12,"data")โ”€โ”ค
    
  • Non-parametric operators such as Barrier, Snapshot, and Wirecut have been grouped together and moved to pennylane/ops/meta.py. Additionally, the relevant tests have been organized and placed in a new file, tests/ops/test_meta.py. (#4789)

  • The TRX, TRY, and TRZ operators are now differentiable via backpropagation on default.qutrit. (#4790)

  • The function qml.equal now supports ControlledSequence operators. (#4829)

  • XZX decomposition has been added to the list of supported single-qubit unitary decompositions. (#4862)

  • == and != operands can now be used with TransformProgram and TransformContainers instances. (#4858)

  • A qutrit_mixed module has been added to qml.devices to store helper functions for a future qutrit mixed-state device. A function called create_initial_state has been added to this module that creates device-compatible initial states. (#4861)

  • The function qml.Snapshot now supports arbitrary state-based measurements (i.e., measurements of type StateMeasurement). (#4876)

  • qml.equal now supports the comparison of QuantumScript and BasisRotation objects. (#4902) (#4919)

  • The function qml.draw_mpl now accept a keyword argument fig to specify the output figure window. (#4956)

Better support for batching

  • qml.AmplitudeEmbedding now supports batching when used with Tensorflow. (#4818)

  • default.qubit can now evolve already batched states with qml.pulse.ParametrizedEvolution. (#4863)

  • qml.ArbitraryUnitary now supports batching. (#4745)

  • Operator and tape batch sizes are evaluated lazily, helping run expensive computations less frequently and an issue with Tensorflow pre-computing batch sizes. (#4911)

Performance improvements and benchmarking

  • Autograd, PyTorch, and JAX can now use vector-Jacobian products (VJPs) provided by the device from the new device API. If a device provides a VJP, this can be selected by providing device_vjp=True to a QNode or qml.execute. (#4935) (#4557) (#4654) (#4878) (#4841)

    >>> dev = qml.device('default.qubit')
    >>> @qml.qnode(dev, diff_method="adjoint", device_vjp=True)
    >>> def circuit(x):
    ...     qml.RX(x, wires=0)
    ...     return qml.expval(qml.PauliZ(0))
    >>> with dev.tracker:
    ...     g = qml.grad(circuit)(qml.numpy.array(0.1))
    >>> dev.tracker.totals
    {'batches': 1, 'simulations': 1, 'executions': 1, 'vjp_batches': 1, 'vjps': 1}
    >>> g
    -0.09983341664682815
    
  • qml.expval with large Hamiltonian objects is now faster and has a significantly lower memory footprint (and constant with respect to the number of Hamiltonian terms) when the Hamiltonian is a PauliSentence. This is due to the introduction of a specialized dot method in the PauliSentence class which performs PauliSentence-state products. (#4839)

  • default.qubit no longer uses a dense matrix for MultiControlledX for more than 8 operation wires. (#4673)

  • Some relevant Pytests have been updated to enable its use as a suite of benchmarks. (#4703)

  • default.qubit now applies GroverOperator faster by not using its matrix representation but a custom rule for apply_operation. Also, the matrix representation of GroverOperator now runs faster. (#4666)

  • A new pipeline to run benchmarks and plot graphs comparing with a fixed reference has been added. This pipeline will run on a schedule and can be activated on a PR with the label ci:run_benchmarks. (#4741)

  • default.qubit now supports adjoint differentiation for arbitrary diagonal state-based measurements. (#4865)

  • The benchmarks pipeline has been expanded to export all benchmark data to a single JSON file and a CSV file with runtimes. This includes all references and local benchmarks. (#4873)

Final phase of updates to transforms

  • qml.quantum_monte_carlo and qml.simplify now use the new transform system. (#4708) (#4949)

  • The formal requirement that type hinting be provided when using the qml.transform decorator has been removed. Type hinting can still be used, but is now optional. Please use a type checker such as mypy if you wish to ensure types are being passed correctly. (#4942)

Other improvements

  • Add PyTree-serialization interface for the Wires class. (#4998)

  • PennyLane now supports Python 3.12. (#4985)

  • SampleMeasurement now has an optional method process_counts for computing the measurement results from a counts dictionary. (#4941)

  • A new function called ops.functions.assert_valid has been added for checking if an Operator class is defined correctly. (#4764)

  • Shots objects can now be multiplied by scalar values. (#4913)

  • GlobalPhase now decomposes to nothing in case devices do not support global phases. (#4855)

  • Custom operations can now provide their matrix directly through the Operator.matrix() method without needing to update the has_matrix property. has_matrix will now automatically be True if Operator.matrix is overridden, even if Operator.compute_matrix is not. (#4844)

  • The logic for re-arranging states before returning them has been improved. (#4817)

  • When multiplying SparseHamiltonians by a scalar value, the result now stays as a SparseHamiltonian. (#4828)

  • trainable_params can now be set upon initialization of a QuantumScript instead of having to set the parameter after initialization. (#4877)

  • default.qubit now calculates the expectation value of Hermitian operators in a differentiable manner. (#4866)

  • The rot decomposition now has support for returning a global phase. (#4869)

  • The "pennylane_sketch" MPL-drawer style has been added. This is the same as the "pennylane" style, but with sketch-style lines. (#4880)

  • Operators now define a pauli_rep property, an instance of PauliSentence, defaulting to None if the operator has not defined it (or has no definition in the Pauli basis). (#4915)

  • qml.ShotAdaptiveOptimizer can now use a multinomial distribution for spreading shots across the terms of a Hamiltonian measured in a QNode. Note that this is equivalent to what can be done with qml.ExpvalCost, but this is the preferred method because ExpvalCost is deprecated. (#4896)

  • Decomposition of qml.PhaseShift now uses qml.GlobalPhase for retaining the global phase information. (#4657) (#4947)

  • qml.equal for Controlled operators no longer returns False when equivalent but differently-ordered sets of control wires and control values are compared. (#4944)

  • All PennyLane Operator subclasses are automatically tested by ops.functions.assert_valid to ensure that they follow PennyLane Operator standards. (#4922)

  • Probability measurements can now be calculated from a counts dictionary with the addition of a process_counts method in the ProbabilityMP class. (#4952)

  • ClassicalShadow.entropy now uses the algorithm outlined in 1106.5458 to project the approximate density matrix (with potentially negative eigenvalues) onto the closest valid density matrix. (#4959)

  • The ControlledSequence.compute_decomposition default now decomposes the Pow operators, improving compatibility with machine learning interfaces. (#4995)

Breaking changes ๐Ÿ’”

  • The function qml.transforms.classical_jacobian has been moved to the gradients module and is now accessible as qml.gradients.classical_jacobian. (#4900)

  • The transforms submodule qml.transforms.qcut is now its own module: qml.qcut. (#4819)

  • The decomposition of GroverOperator now has an additional global phase operation. (#4666)

  • qml.cond and the Conditional operation have been moved from the transforms folder to the ops/op_math folder. qml.transforms.Conditional will now be available as qml.ops.Conditional. (#4860)

  • The prep keyword argument has been removed from QuantumScript and QuantumTape. StatePrepBase operations should be placed at the beginning of the ops list instead. (#4756)

  • qml.gradients.pulse_generator is now named qml.gradients.pulse_odegen to adhere to paper naming conventions. (#4769)

  • Specifying control_values passed to qml.ctrl as a string is no longer supported. (#4816)

  • The rot decomposition will now normalize its rotation angles to the range [0, 4pi] for consistency (#4869)

  • QuantumScript.graph is now built using tape.measurements instead of tape.observables because it depended on the now-deprecated Observable.return_type property. (#4762)

  • The "pennylane" MPL-drawer style now draws straight lines instead of sketch-style lines. (#4880)

  • The default value for the term_sampling argument of ShotAdaptiveOptimizer is now None instead of "weighted_random_sampling". (#4896)

Deprecations ๐Ÿ‘‹

  • single_tape_transform, batch_transform, qfunc_transform, and op_transform are deprecated. Use the new qml.transform function instead. (#4774)

  • Observable.return_type is deprecated. Instead, you should inspect the type of the surrounding measurement process. (#4762) (#4798)

  • All deprecations now raise a qml.PennyLaneDeprecationWarning instead of a UserWarning. (#4814)

  • QuantumScript.is_sampled and QuantumScript.all_sampled are deprecated. Users should now validate these properties manually. (#4773)

  • With an algorithmic improvement to ClassicalShadow.entropy, the keyword atol becomes obsolete and will be removed in v0.35. (#4959)

Documentation ๐Ÿ“

  • Documentation for unitaries and operationsโ€™ decompositions has been moved from qml.transforms to qml.ops.ops_math. (#4906)

  • Documentation for qml.metric_tensor and qml.adjoint_metric_tensor and qml.transforms.classical_jacobian is now accessible via the gradients API page qml.gradients in the documentation. (#4900)

  • Documentation for qml.specs has been moved to the resource module. (#4904)

  • Documentation for QCut has been moved to its own API page: qml.qcut. (#4819)

  • The documentation page for qml.measurements now links top-level accessible functions (e.g., qml.expval) to their top-level pages rather than their module-level pages (e.g., qml.measurements.expval). (#4750)

  • Information for the documentation of qml.matrix about wire ordering has been added for using qml.matrix on a QNode which uses a device with device.wires=None. (#4874)

Bug fixes ๐Ÿ›

  • TransformDispatcher now stops queuing when performing the transform when applying it to a qfunc. Only the output of the transform will be queued. (#4983)

  • qml.map_wires now works properly with qml.cond and qml.measure. (#4884)

  • Pow operators are now picklable. (#4966)

  • Finite differences and SPSA can now be used with tensorflow-autograph on setups that were seeing a bus error. (#4961)

  • qml.cond no longer incorrectly queues operators used arguments. (#4948)

  • Attribute objects now return False instead of raising a TypeError when checking if an object is inside the set. (#4933)

  • Fixed a bug where the parameter-shift rule of qml.ctrl(op) was wrong if op had a generator that has two or more eigenvalues and is stored as a SparseHamiltonian. (#4899)

  • Fixed a bug where trainable parameters in the post-processing of finite-differences were incorrect for JAX when applying the transform directly on a QNode. (#4879)

  • qml.grad and qml.jacobian now explicitly raise errors if trainable parameters are integers. (#4836)

  • JAX-JIT now works with shot vectors. (#4772)

  • JAX can now differentiate a batch of circuits where one tape does not have trainable parameters. (#4837)

  • The decomposition of GroverOperator now has the same global phase as its matrix. (#4666)

  • The tape.to_openqasm method no longer mistakenly includes interface information in the parameter string when converting tapes using non-NumPy interfaces. (#4849)

  • qml.defer_measurements now correctly transforms circuits when terminal measurements include wires used in mid-circuit measurements. (#4787)

  • Fixed a bug where the adjoint differentiation method would fail if an operation that has a parameter with grad_method=None is present. (#4820)

  • MottonenStatePreparation and BasisStatePreparation now raise an error when decomposing a broadcasted state vector. (#4767)

  • Gradient transforms now work with overridden shot vectors and default.qubit. (#4795)

  • Any ScalarSymbolicOp, like Evolution, now states that it has a matrix if the target is a Hamiltonian. (#4768)

  • In default.qubit, initial states are now initialized with the simulatorโ€™s wire order, not the circuitโ€™s wire order. (#4781)

  • qml.compile will now always decompose to expand_depth, even if a target basis set is not specified. (#4800)

  • qml.transforms.transpile can now handle measurements that are broadcasted onto all wires. (#4793)

  • Parametrized circuits whose operators do not act on all wires return PennyLane tensors instead of NumPy arrays, as expected. (#4811) (#4817)

  • qml.transforms.merge_amplitude_embedding no longer depends on queuing, allowing it to work as expected with QNodes. (#4831)

  • qml.pow(op) and qml.QubitUnitary.pow() now also work with Tensorflow data raised to an integer power. (#4827)

  • The text drawer has been fixed to correctly label qml.qinfo measurements, as well as qml.classical_shadow qml.shadow_expval. (#4803)

  • Removed an implicit assumption that an empty PauliSentence gets treated as identity under multiplication. (#4887)

  • Using a CNOT or PauliZ operation with large batched states and the Tensorflow interface no longer raises an unexpected error. (#4889)

  • qml.map_wires no longer fails when mapping nested quantum tapes. (#4901)

  • Conversion of circuits to openqasm now decomposes to a depth of 10, allowing support for operators requiring more than 2 iterations of decomposition, such as the ApproxTimeEvolution gate. (#4951)

  • MPLDrawer does not add the bonus space for classical wires when no classical wires are present. (#4987)

  • Projector now works with parameter-broadcasting. (#4993)

  • The jax-jit interface can now be used with float32 mode. (#4990)

  • Keras models with a qnn.KerasLayer no longer fail to save and load weights properly when they are named โ€œweightsโ€. (#5008)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Ali Asadi, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Minh Chau, Isaac De Vlugt, Amintor Dusko, Pieter Eendebak, Lillian Frederiksen, Pietropaolo Frisoni, Josh Izaac, Juan Giraldo, Emiliano Godinez Ramirez, Ankit Khandelwal, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Anurav Modak, Romain Moyard, Mudit Pandey, Matthew Silverman, Jay Soni, David Wierichs, Justin Woodring, Sergei Mironov.

orphan

Release 0.33.1ยถ

Bug fixes ๐Ÿ›

  • Fix gradient performance regression due to expansion of VJP products. (#4806)

  • qml.defer_measurements now correctly transforms circuits when terminal measurements include wires used in mid-circuit measurements. (#4787)

  • Any ScalarSymbolicOp, like Evolution, now states that it has a matrix if the target is a Hamiltonian. (#4768)

  • In default.qubit, initial states are now initialized with the simulatorโ€™s wire order, not the circuitโ€™s wire order. (#4781)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Christina Lee, Lee James Oโ€™Riordan, Mudit Pandey

orphan

Release 0.33.0ยถ

New features since last release

Postselection and statistics in mid-circuit measurements ๐Ÿ“Œ

  • It is now possible to request postselection on a mid-circuit measurement. (#4604)

    This can be achieved by specifying the postselect keyword argument in qml.measure as either 0 or 1, corresponding to the basis states.

    import pennylane as qml
    
    dev = qml.device("default.qubit")
    
    @qml.qnode(dev, interface=None)
    def circuit():
        qml.Hadamard(wires=0)
        qml.CNOT(wires=[0, 1])
        qml.measure(0, postselect=1)
        return qml.expval(qml.PauliZ(1)), qml.sample(wires=1)
    

    This circuit prepares the \(| \Phi^{+} \rangle\) Bell state and postselects on measuring \(|1\rangle\) in wire 0. The output of wire 1 is then also \(|1\rangle\) at all times:

    >>> circuit(shots=10)
    (-1.0, array([1, 1, 1, 1, 1, 1]))
    

    Note that the number of shots is less than the requested amount because we have thrown away the samples where \(|0\rangle\) was measured in wire 0.

  • Measurement statistics can now be collected for mid-circuit measurements. (#4544)

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev)
    def circ(x, y):
        qml.RX(x, wires=0)
        qml.RY(y, wires=1)
        m0 = qml.measure(1)
        return qml.expval(qml.PauliZ(0)), qml.expval(m0), qml.sample(m0)
    
    >>> circ(1.0, 2.0, shots=10000)
    (0.5606, 0.7089, array([0, 1, 1, ..., 1, 1, 1]))
    

    Support is provided for both finite-shot and analytic modes and devices default to using the deferred measurement principle to enact the mid-circuit measurements.

Exponentiate Hamiltonians with flexible Trotter products ๐Ÿ–

  • Higher-order Trotter-Suzuki methods are now easily accessible through a new operation called TrotterProduct. (#4661)

    Trotterization techniques are an affective route towards accurate and efficient Hamiltonian simulation. The Suzuki-Trotter product formula allows for the ability to express higher-order approximations to the matrix exponential of a Hamiltonian, and it is now available to use in PennyLane via the TrotterProduct operation. Simply specify the order of the approximation and the evolution time.

    coeffs = [0.25, 0.75]
    ops = [qml.PauliX(0), qml.PauliZ(0)]
    H = qml.dot(coeffs, ops)
    
    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev)
    def circuit():
        qml.Hadamard(0)
        qml.TrotterProduct(H, time=2.4, order=2)
        return qml.state()
    
    >>> circuit()
    [-0.13259524+0.59790098j  0.        +0.j         -0.13259524-0.77932754j  0.        +0.j        ]
    
  • Approximating matrix exponentiation with random product formulas, qDrift, is now available with the new QDrift operation. (#4671)

    As shown in 1811.08017, qDrift is a Markovian process that can provide a speedup in Hamiltonian simulation. At a high level, qDrift works by randomly sampling from the Hamiltonian terms with a probability that depends on the Hamiltonian coefficients. This method for Hamiltonian simulation is now ready to use in PennyLane with the QDrift operator. Simply specify the evolution time and the number of samples drawn from the Hamiltonian, n:

    coeffs = [0.25, 0.75]
    ops = [qml.PauliX(0), qml.PauliZ(0)]
    H = qml.dot(coeffs, ops)
    
    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev)
    def circuit():
        qml.Hadamard(0)
        qml.QDrift(H, time=1.2, n = 10)
        return qml.probs()
    
    >>> circuit()
    array([0.61814334, 0.        , 0.38185666, 0.        ])
    

Building blocks for quantum phase estimation ๐Ÿงฑ

  • A new operator called CosineWindow has been added to prepare an initial state based on a cosine wave function. (#4683)

    As outlined in 2110.09590, the cosine tapering window is part of a modification to quantum phase estimation that can provide a cubic improvement to the algorithmโ€™s error rate. Using CosineWindow will prepare a state whose amplitudes follow a cosinusoidal distribution over the computational basis.

    import matplotlib.pyplot as plt
    
    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def example_circuit():
          qml.CosineWindow(wires=range(4))
          return qml.state()
    output = example_circuit()
    
    plt.style.use("pennylane.drawer.plot")
    plt.bar(range(len(output)), output)
    plt.show()
    

  • Controlled gate sequences raised to decreasing powers, a sub-block in quantum phase estimation, can now be created with the new ControlledSequence operator. (#4707)

    To use ControlledSequence, specify the controlled unitary operator and the control wires, control:

    dev = qml.device("default.qubit", wires = 4)
    
    @qml.qnode(dev)
    def circuit():
        for i in range(3):
            qml.Hadamard(wires = i)
        qml.ControlledSequence(qml.RX(0.25, wires = 3), control = [0, 1, 2])
        qml.adjoint(qml.QFT)(wires = range(3))
        return qml.probs(wires = range(3))
    
    >>> print(circuit())
    [0.92059345 0.02637178 0.00729619 0.00423258 0.00360545 0.00423258 0.00729619 0.02637178]
    

New device capabilities, integration with Catalyst, and more! โš—๏ธ

  • default.qubit now uses the new qml.devices.Device API and functionality in qml.devices.qubit. If you experience any issues with the updated default.qubit, please let us know by posting an issue. The old version of the device is still accessible by the short name default.qubit.legacy, or directly via qml.devices.DefaultQubitLegacy. (#4594) (#4436) (#4620) (#4632)

    This changeover has a number of benefits for default.qubit, including:

    • The number of wires is now optional โ€” simply having qml.device("default.qubit") is valid! If wires are not provided at instantiation, the device automatically infers the required number of wires for each circuit provided for execution.

      dev = qml.device("default.qubit")
      
      @qml.qnode(dev)
      def circuit():
          qml.PauliZ(0)
          qml.RZ(0.1, wires=1)
          qml.Hadamard(2)
          return qml.state()
      
      >>> print(qml.draw(circuit)())
      0: โ”€โ”€Zโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
      1: โ”€โ”€RZ(0.10)โ”€โ”ค  State
      2: โ”€โ”€Hโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
      
    • default.qubit is no longer silently swapped out with an interface-appropriate device when the backpropagation differentiation method is used. For example, consider:

      import jax
      
      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev, diff_method="backprop")
      def f(x):
          qml.RX(x, wires=0)
          return qml.expval(qml.PauliZ(0))
      
      f(jax.numpy.array(0.2))
      

      In previous versions of PennyLane, the device will be swapped for the JAX equivalent:

      >>> f.device
      <DefaultQubitJax device (wires=1, shots=None) at 0x7f8c8bff50a0>
      >>> f.device == dev
      False
      

      Now, default.qubit can itself dispatch to all the interfaces in a backprop-compatible way and hence does not need to be swapped:

      >>> f.device
      <default.qubit device (wires=1) at 0x7f20d043b040>
      >>> f.device == dev
      True
      
  • A QNode that has been decorated with qjit from PennyLaneโ€™s Catalyst library for just-in-time hybrid compilation is now compatible with qml.draw. (#4609)

    import catalyst
    
    @catalyst.qjit
    @qml.qnode(qml.device("lightning.qubit", wires=3))
    def circuit(x, y, z, c):
        """A quantum circuit on three wires."""
    
        @catalyst.for_loop(0, c, 1)
        def loop(i):
            qml.Hadamard(wires=i)
    
        qml.RX(x, wires=0)
        loop()
        qml.RY(y, wires=1)
        qml.RZ(z, wires=2)
        return qml.expval(qml.PauliZ(0))
    
    draw = qml.draw(circuit, decimals=None)(1.234, 2.345, 3.456, 1)
    
    >>> print(draw)
    0: โ”€โ”€RXโ”€โ”€Hโ”€โ”€โ”ค  <Z>
    1: โ”€โ”€Hโ”€โ”€โ”€RYโ”€โ”ค
    2: โ”€โ”€RZโ”€โ”€โ”€โ”€โ”€โ”ค
    

Improvements ๐Ÿ› 

More PyTrees!

  • MeasurementProcess and QuantumScript objects are now registered as JAX PyTrees. (#4607) (#4608)

    It is now possible to JIT-compile functions with arguments that are a MeasurementProcess or a QuantumScript:

    import jax
    
    tape0 = qml.tape.QuantumTape([qml.RX(1.0, 0), qml.RY(0.5, 0)], [qml.expval(qml.PauliZ(0))])
    dev = qml.device('lightning.qubit', wires=5)
    
    execute_kwargs = {"device": dev, "gradient_fn": qml.gradients.param_shift, "interface":"jax"}
    
    jitted_execute = jax.jit(qml.execute, static_argnames=execute_kwargs.keys())
    jitted_execute((tape0, ), **execute_kwargs)
    

Improving QChem and existing algorithms

  • Computationally expensive functions in integrals.py, electron_repulsion and _hermite_coulomb, have been modified to replace indexing with slicing for better compatibility with JAX. (#4685)

  • qml.qchem.import_state has been extended to import more quantum chemistry wavefunctions, from MPS, DMRG and SHCI classical calculations performed with the Block2 and Dice libraries. #4523 #4524 #4626 #4634

    Check out our how-to guide to learn more about how PennyLane integrates with your favourite quantum chemistry libraries.

  • The qchem fermionic_dipole and particle_number functions have been updated to use a FermiSentence. The deprecated features for using tuples to represent fermionic operations are removed. (#4546) (#4556)

  • The tensor-network template qml.MPS now supports changing the offset between subsequent blocks for more flexibility. (#4531)

  • Builtin types support with qml.pauli_decompose have been improved. (#4577)

  • AmplitudeEmbedding now inherits from StatePrep, allowing for it to not be decomposed when at the beginning of a circuit, thus behaving like StatePrep. (#4583)

  • qml.cut_circuit is now compatible with circuits that compute the expectation values of Hamiltonians with two or more terms. (#4642)

Next-generation device API

  • default.qubit now tracks the number of equivalent qpu executions and total shots when the device is sampling. Note that "simulations" denotes the number of simulation passes, whereas "executions" denotes how many different computational bases need to be sampled in. Additionally, the new default.qubit tracks the results of device.execute. (#4628) (#4649)

  • DefaultQubit can now accept a jax.random.PRNGKey as a seed to set the key for the JAX pseudo random number generator when using the JAX interface. This corresponds to the prng_key on default.qubit.jax in the old API. (#4596)

  • The JacobianProductCalculator abstract base class and implementations TransformJacobianProducts DeviceDerivatives, and DeviceJacobianProducts have been added to pennylane.interfaces.jacobian_products. (#4435) (#4527) (#4637)

  • DefaultQubit dispatches to a faster implementation for applying ParametrizedEvolution to a state when it is more efficient to evolve the state than the operation matrix. (#4598) (#4620)

  • Wires can be provided to the new device API. (#4538) (#4562)

  • qml.sample() in the new device API now returns a np.int64 array instead of np.bool8. (#4539)

  • The new device API now has a repr() method. (#4562)

  • DefaultQubit now works as expected with measurement processes that donโ€™t specify wires. (#4580)

  • Various improvements to measurements have been made for feature parity between default.qubit.legacy and the new DefaultQubit. This includes not trying to squeeze batched CountsMP results and implementing MutualInfoMP.map_wires. (#4574)

  • devices.qubit.simulate now accepts an interface keyword argument. If a QNode with DefaultQubit specifies an interface, the result will be computed with that interface. (#4582)

  • ShotAdaptiveOptimizer has been updated to pass shots to QNode executions instead of overriding device shots before execution. This makes it compatible with the new device API. (#4599)

  • pennylane.devices.preprocess now offers the transforms decompose, validate_observables, validate_measurements, validate_device_wires, validate_multiprocessing_workers, warn_about_trainable_observables, and no_sampling to assist in constructing devices under the new device API. (#4659)

  • Updated qml.device, devices.preprocessing and the tape_expand.set_decomposition context manager to bring DefaultQubit to feature parity with default.qubit.legacy with regards to using custom decompositions. The DefaultQubit device can now be included in a set_decomposition context or initialized with a custom_decomps dictionary, as well as a custom max_depth for decomposition. (#4675)

Other improvements

  • The StateMP measurement now accepts a wire order (e.g., a device wire order). The process_state method will re-order the given state to go from the inputted wire-order to the processโ€™s wire-order. If the processโ€™s wire-order contains extra wires, it will assume those are in the zero-state. (#4570) (#4602)

  • Methods called add_transform and insert_front_transform have been added to TransformProgram. (#4559)

  • Instances of the TransformProgram class can now be added together. (#4549)

  • Transforms can now be applied to devices following the new device API. (#4667)

  • All gradient transforms have been updated to the new transform program system. (#4595)

  • Multi-controlled operations with a single-qubit special unitary target can now automatically decompose. (#4697)

  • pennylane.defer_measurements will now exit early if the input does not contain mid circuit measurements. (#4659)

  • The density matrix aspects of StateMP have been split into their own measurement process called DensityMatrixMP. (#4558)

  • StateMeasurement.process_state now assumes that the input is flat. ProbabilityMP.process_state has been updated to reflect this assumption and avoid redundant reshaping. (#4602)

  • qml.exp returns a more informative error message when decomposition is unavailable for non-unitary operators. (#4571)

  • Added qml.math.get_deep_interface to get the interface of a scalar hidden deep in lists or tuples. (#4603)

  • Updated qml.math.ndim and qml.math.shape to work with built-in lists or tuples that contain interface-specific scalar dat (e.g., [(tf.Variable(1.1), tf.Variable(2.2))]). (#4603)

  • When decomposing a unitary matrix with one_qubit_decomposition and opting to include the GlobalPhase in the decomposition, the phase is no longer cast to dtype=complex. (#4653)

  • _qfunc_output has been removed from QuantumScript, as it is no longer necessary. There is still a _qfunc_output property on QNode instances. (#4651)

  • qml.data.load properly handles parameters that come after 'full' (#4663)

  • The qml.jordan_wigner function has been modified to optionally remove the imaginary components of the computed qubit operator, if imaginary components are smaller than a threshold. (#4639)

  • qml.data.load correctly performs a full download of the dataset after a partial download of the same dataset has already been performed. (#4681)

  • The performance of qml.data.load() has been improved when partially loading a dataset (#4674)

  • Plots generated with the pennylane.drawer.plot style of matplotlib.pyplot now have black axis labels and are generated at a default DPI of 300. (#4690)

  • Shallow copies of the QNode now also copy the execute_kwargs and transform program. When applying a transform to a QNode, the new qnode is only a shallow copy of the original and thus keeps the same device. (#4736)

  • QubitDevice and CountsMP are updated to disregard samples containing failed hardware measurements (record as np.NaN) when tallying samples, rather than counting failed measurements as ground-state measurements, and to display qml.counts coming from these hardware devices correctly. (#4739)

Breaking changes ๐Ÿ’”

  • qml.defer_measurements now raises an error if a transformed circuit measures qml.probs, qml.sample, or qml.counts without any wires or observable, or if it measures qml.state. (#4701)

  • The device test suite now converts device keyword arguments to integers or floats if possible. (#4640)

  • MeasurementProcess.eigvals() now raises an EigvalsUndefinedError if the measurement observable does not have eigenvalues. (#4544)

  • The __eq__ and __hash__ methods of Operator and MeasurementProcess no longer rely on the objectโ€™s address in memory. Using == with operators and measurement processes will now behave the same as qml.equal, and objects of the same type with the same data and hyperparameters will have the same hash. (#4536)

    In the following scenario, the second and third code blocks show the previous and current behaviour of operator and measurement process equality, determined by ==:

    op1 = qml.PauliX(0)
    op2 = qml.PauliX(0)
    op3 = op1
    

    Old behaviour:

    >>> op1 == op2
    False
    >>> op1 == op3
    True
    

    New behaviour:

    >>> op1 == op2
    True
    >>> op1 == op3
    True
    

    The __hash__ dunder method defines the hash of an object. The default hash of an object is determined by the objects memory address. However, the new hash is determined by the properties and attributes of operators and measurement processes. Consider the scenario below. The second and third code blocks show the previous and current behaviour.

    op1 = qml.PauliX(0)
    op2 = qml.PauliX(0)
    

    Old behaviour:

    >>> print({op1, op2})
    {PauliX(wires=[0]), PauliX(wires=[0])}
    

    New behaviour:

    >>> print({op1, op2})
    {PauliX(wires=[0])}
    
  • The old return type and associated functions qml.enable_return and qml.disable_return have been removed. (#4503)

  • The mode keyword argument in QNode has been removed. Please use grad_on_execution instead. (#4503)

  • The CV observables qml.X and qml.P have been removed. Please use qml.QuadX and qml.QuadP instead. (#4533)

  • The sampler_seed argument of qml.gradients.spsa_grad has been removed. Instead, the sampler_rng argument should be set, either to an integer value, which will be used to create a PRNG internally, or to a NumPy pseudo-random number generator (PRNG) created via np.random.default_rng(seed). (#4550)

  • The QuantumScript.set_parameters method and the QuantumScript.data setter have been removed. Please use QuantumScript.bind_new_parameters instead. (#4548)

  • The method tape.unwrap() and corresponding UnwrapTape and Unwrap classes have been removed. Instead of tape.unwrap(), use qml.transforms.convert_to_numpy_parameters. (#4535)

  • The RandomLayers.compute_decomposition keyword argument ratio_imprivitive has been changed to ratio_imprim to match the call signature of the operation. (#4552)

  • The private TmpPauliRot operator used for SpecialUnitary no longer decomposes to nothing when the theta value is trainable. (#4585)

  • ProbabilityMP.marginal_prob has been removed. Its contents have been moved into process_state, which effectively just called marginal_prob with np.abs(state) ** 2. (#4602)

Deprecations ๐Ÿ‘‹

  • The following decorator syntax for transforms has been deprecated and will raise a warning: (#4457)

    @transform_fn(**transform_kwargs)
    @qml.qnode(dev)
    def circuit():
        ...
    

    If you are using a transform that has supporting transform_kwargs, please call the transform directly using circuit = transform_fn(circuit, **transform_kwargs), or use functools.partial:

    @functools.partial(transform_fn, **transform_kwargs)
    @qml.qnode(dev)
    def circuit():
        ...
    
  • The prep keyword argument in QuantumScript has been deprecated and will be removed from QuantumScript. StatePrepBase operations should be placed at the beginning of the ops list instead. (#4554)

  • qml.gradients.pulse_generator has been renamed to qml.gradients.pulse_odegen to adhere to paper naming conventions. During v0.33, pulse_generator is still available but raises a warning. (#4633)

Documentation ๐Ÿ“

  • A warning section in the docstring for DefaultQubit regarding the start method used in multiprocessing has been added. This may help users circumvent issues arising in Jupyter notebooks on macOS for example. (#4622)

  • Documentation improvements to the new device API have been made. The documentation now correctly states that interface-specific parameters are only passed to the device for backpropagation derivatives. (#4542)

  • Functions for qubit-simulation to the qml.devices sub-page of the โ€œInternalโ€ section have been added. Note that these functions are unstable while device upgrades are underway. (#4555)

  • A documentation improvement to the usage example in the qml.QuantumMonteCarlo page has been made. An integral was missing the differential \(dx\). (#4593)

  • A documentation improvement for the use of the pennylane style of qml.drawer and the pennylane.drawer.plot style of matplotlib.pyplot has been made by clarifying the use of the default font. (#4690)

Bug fixes ๐Ÿ›

  • Jax jit now works when a probability measurement is broadcasted onto all wires. (#4742)

  • Fixed LocalHilbertSchmidt.compute_decomposition so that the template can be used in a QNode. (#4719)

  • Fixes transforms.transpile with arbitrary measurement processes. (#4732)

  • Providing work_wires=None to qml.GroverOperator no longer interprets None as a wire. (#4668)

  • Fixed an issue where the __copy__ method of the qml.Select() operator attempted to access un-initialized data. (#4551)

  • Fixed the skip_first option in expand_tape_state_prep. (#4564)

  • convert_to_numpy_parameters now uses qml.ops.functions.bind_new_parameters. This reinitializes the operation and makes sure everything references the new NumPy parameters. (#4540)

  • tf.function no longer breaks ProbabilityMP.process_state, which is needed by new devices. (#4470)

  • Fixed unit tests for qml.qchem.mol_data. (#4591)

  • Fixed ProbabilityMP.process_state so that it allows for proper Autograph compilation. Without this, decorating a QNode that returns an expval with tf.function would fail when computing the expectation. (#4590)

  • The torch.nn.Module properties are now accessible on a pennylane.qnn.TorchLayer. (#4611)

  • qml.math.take with Pytorch now returns tensor[..., indices] when the user requests the last axis (axis=-1). Without the fix, it would wrongly return tensor[indices]. (#4605)

  • Ensured the logging TRACE level works with gradient-free execution. (#4669)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Jack Brown, Stepan Fomichev, Joana Fraxanet, Diego Guala, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Ivana Kureฤiฤ‡ Christina Lee, Lillian M. A. Frederiksen, Vincent Michaud-Rioux, Romain Moyard, Daniel F. Nino, Lee James Oโ€™Riordan, Mudit Pandey, Matthew Silverman, Jay Soni.

orphan

Release 0.32.0ยถ

New features since last release

Encode matrices using a linear combination of unitaries โ›“๏ธ๏ธ

  • It is now possible to encode an operator A into a quantum circuit by decomposing it into a linear combination of unitaries using PREP (qml.StatePrep) and SELECT (qml.Select) routines. (#4431) (#4437) (#4444) (#4450) (#4506) (#4526)

    Consider an operator A composed of a linear combination of Pauli terms:

    >>> A = qml.PauliX(2) + 2 * qml.PauliY(2) + 3 * qml.PauliZ(2)
    

    A decomposable block-encoding circuit can be created:

    def block_encode(A, control_wires):
        probs = A.coeffs / np.sum(A.coeffs)
        state = np.pad(np.sqrt(probs, dtype=complex), (0, 1))
        unitaries = A.ops
    
        qml.StatePrep(state, wires=control_wires)
        qml.Select(unitaries, control=control_wires)
        qml.adjoint(qml.StatePrep)(state, wires=control_wires)
    
    >>> print(qml.draw(block_encode, show_matrices=False)(A, control_wires=[0, 1]))
    0: โ”€โ•ญ|ฮจโŸฉโ”€โ•ญSelectโ”€โ•ญ|ฮจโŸฉโ€ โ”€โ”ค
    1: โ”€โ•ฐ|ฮจโŸฉโ”€โ”œSelectโ”€โ•ฐ|ฮจโŸฉโ€ โ”€โ”ค
    2: โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐSelectโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
    

    This circuit can be used as a building block within a larger QNode to perform algorithms such as QSVT and Hamiltonian simulation.

  • Decomposing a Hermitian matrix into a linear combination of Pauli words via qml.pauli_decompose is now faster and differentiable. (#4395) (#4479) (#4493)

    def find_coeffs(p):
        mat = np.array([[3, p], [p, 3]])
        A = qml.pauli_decompose(mat)
        return A.coeffs
    
    >>> import jax
    >>> from jax import numpy as np
    >>> jax.jacobian(find_coeffs)(np.array(2.))
    Array([0., 1.], dtype=float32, weak_type=True)
    

Monitor PennyLane's inner workings with logging ๐Ÿ“ƒ

  • Python-native logging can now be enabled with qml.logging.enable_logging(). (#4377) (#4383)

    Consider the following code that is contained in my_code.py:

    import pennylane as qml
    qml.logging.enable_logging()  # enables logging
    
    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev)
    def f(x):
        qml.RX(x, wires=0)
        return qml.state()
    
    f(0.5)
    

    Executing my_code.py with logging enabled will detail every step in PennyLaneโ€™s pipeline that gets used to run your code.

    $ python my_code.py
    [1967-02-13 15:18:38,591][DEBUG][<PID 8881:MainProcess>] - pennylane.qnode.__init__()::"Creating QNode(func=<function f at 0x7faf2a6fbaf0>, device=<DefaultQubit device (wires=2, shots=None) at 0x7faf2a689b50>, interface=auto, diff_method=best, expansion_strategy=gradient, max_expansion=10, grad_on_execution=best, mode=None, cache=True, cachesize=10000, max_diff=1, gradient_kwargs={}"
    ...
    

    Additional logging configuration settings can be specified by modifying the contents of the logging configuration file, which can be located by running qml.logging.config_path(). Follow our logging docs page for more details!

More input states for quantum chemistry calculations โš›๏ธ

  • Input states obtained from advanced quantum chemistry calculations can be used in a circuit. (#4427) (#4433) (#4461) (#4476) (#4505)

    Quantum chemistry calculations rely on an initial state that is typically selected to be the trivial Hartree-Fock state. For molecules with a complicated electronic structure, using initial states obtained from affordable post-Hartree-Fock calculations helps to improve the efficiency of the quantum simulations. These calculations can be done with external quantum chemistry libraries such as PySCF.

    It is now possible to import a PySCF solver object in PennyLane and extract the corresponding wave function in the form of a state vector that can be directly used in a circuit. First, perform your classical quantum chemistry calculations and then use the qml.qchem.import_state function to import the solver object and return a state vector.

   >>> from pyscf import gto, scf, ci
   >>> mol = gto.M(atom=[['H', (0, 0, 0)], ['H', (0,0,0.71)]], basis='sto6g')
   >>> myhf = scf.UHF(mol).run()
   >>> myci = ci.UCISD(myhf).run()
   >>> wf_cisd = qml.qchem.import_state(myci, tol=1e-1)
   >>> print(wf_cisd)
   [ 0.        +0.j  0.        +0.j  0.        +0.j  0.1066467 +0.j
     1.        +0.j  0.        +0.j  0.        +0.j  0.        +0.j
     2.        +0.j  0.        +0.j  0.        +0.j  0.        +0.j
    -0.99429698+0.j  0.        +0.j  0.        +0.j  0.        +0.j]

The state vector can be implemented in a circuit using ``qml.StatePrep``.
   >>> dev = qml.device('default.qubit', wires=4)
   >>> @qml.qnode(dev)
   ... def circuit():
   ...     qml.StatePrep(wf_cisd, wires=range(4))
   ...     return qml.state()
   >>> print(circuit())
   [ 0.        +0.j  0.        +0.j  0.        +0.j  0.1066467 +0.j
     1.        +0.j  0.        +0.j  0.        +0.j  0.        +0.j
     2.        +0.j  0.        +0.j  0.        +0.j  0.        +0.j
    -0.99429698+0.j  0.        +0.j  0.        +0.j  0.        +0.j]

The currently supported post-Hartree-Fock methods are RCISD, UCISD, RCCSD, and UCCSD which
denote restricted (R) and unrestricted (U) configuration interaction (CI) and coupled cluster (CC)
calculations with single and double (SD) excitations.

Reuse and reset qubits after mid-circuit measurements โ™ป๏ธ

  • PennyLane now allows you to define circuits that reuse a qubit after a mid-circuit measurement has taken place. Optionally, the wire can also be reset to the \(|0\rangle\) state. (#4402) (#4432)

    Post-measurement reset can be activated by setting reset=True when calling qml.measure. In this version of PennyLane, executing circuits with qubit reuse will result in the defer_measurements transform being applied. This transform replaces each reused wire with an additional qubit. However, future releases of PennyLane will explore device-level support for qubit reuse without consuming additional qubits.

    Qubit reuse and reset is also fully differentiable:

    dev = qml.device("default.qubit", wires=4)
    
    @qml.qnode(dev)
    def circuit(p):
        qml.RX(p, wires=0)
        m = qml.measure(0, reset=True)
        qml.cond(m, qml.Hadamard)(1)
    
        qml.RX(p, wires=0)
        m = qml.measure(0)
        qml.cond(m, qml.Hadamard)(1)
        return qml.expval(qml.PauliZ(1))
    
    >>> jax.grad(circuit)(0.4)
    Array(-0.35867804, dtype=float32, weak_type=True)
    

    You can read more about mid-circuit measurements in the documentation, and stay tuned for more mid-circuit measurement features in the next few releases!

Improvements ๐Ÿ› 

A new PennyLane drawing style

  • Circuit drawings and plots can now be created following a PennyLane style. (#3950)

    The qml.draw_mpl function accepts a style='pennylane' argument to create PennyLane themed circuit diagrams:

    def circuit(x, z):
        qml.QFT(wires=(0,1,2,3))
        qml.Toffoli(wires=(0,1,2))
        qml.CSWAP(wires=(0,2,3))
        qml.RX(x, wires=0)
        qml.CRZ(z, wires=(3,0))
        return qml.expval(qml.PauliZ(0))
    
    qml.draw_mpl(circuit, style="pennylane")(1, 1)
    

    PennyLane-styled plots can also be drawn by passing "pennylane.drawer.plot" to Matplotlibโ€™s plt.style.use function:

    import matplotlib.pyplot as plt
    
    plt.style.use("pennylane.drawer.plot")
    for i in range(3):
        plt.plot(np.random.rand(10))
    

    If the font Quicksand Bold isnโ€™t available, an available default font is used instead.

Making operators immutable and PyTrees

  • Any class inheriting from Operator is now automatically registered as a pytree with JAX. This unlocks the ability to jit functions of Operator. (#4458)

    >>> op = qml.adjoint(qml.RX(1.0, wires=0))
    >>> jax.jit(qml.matrix)(op)
    Array([[0.87758255-0.j        , 0.        +0.47942555j],
         [0.        +0.47942555j, 0.87758255-0.j        ]],      dtype=complex64, weak_type=True)
    >>> jax.tree_util.tree_map(lambda x: x+1, op)
    Adjoint(RX(2.0, wires=[0]))
    
  • All Operator objects now define Operator._flatten and Operator._unflatten methods that separate trainable from untrainable components. These methods will be used in serialization and pytree registration. Custom operations may need an update to ensure compatibility with new PennyLane features. (#4483) (#4314)

  • The QuantumScript class now has a bind_new_parameters method that allows creation of new QuantumScript objects with the provided parameters. (#4345)

  • The qml.gradients module no longer mutates operators in-place for any gradient transforms. Instead, operators that need to be mutated are copied with new parameters. (#4220)

  • PennyLane no longer directly relies on Operator.__eq__. (#4398)

  • qml.equal no longer raises errors when operators or measurements of different types are compared. Instead, it returns False. (#4315)

Transforms

  • Transform programs are now integrated with the QNode. (#4404)

    def null_postprocessing(results: qml.typing.ResultBatch) -> qml.typing.Result:
        return results[0]
    
    @qml.transforms.core.transform
    def scale_shots(tape: qml.tape.QuantumTape, shot_scaling) -> (Tuple[qml.tape.QuantumTape], Callable):
        new_shots = tape.shots.total_shots * shot_scaling
        new_tape = qml.tape.QuantumScript(tape.operations, tape.measurements, shots=new_shots)
        return (new_tape, ), null_postprocessing
    
    dev = qml.devices.experimental.DefaultQubit2()
    
    @partial(scale_shots, shot_scaling=2)
    @qml.qnode(dev, interface=None)
    def circuit():
        return qml.sample(wires=0)
    
    >>> circuit(shots=1)
    array([False, False])
    
  • Transform Programs, qml.transforms.core.TransformProgram, can now be called on a batch of circuits and return a new batch of circuits and a single post processing function. (#4364)

  • TransformDispatcher now allows registration of custom QNode transforms. (#4466)

  • QNode transforms in qml.qinfo now support custom wire labels. #4331

  • qml.transforms.adjoint_metric_tensor now uses the simulation tools in qml.devices.qubit instead of private methods of qml.devices.DefaultQubit. (#4456)

  • Auxiliary wires and device wires are now treated the same way in qml.transforms.metric_tensor as in qml.gradients.hadamard_grad. All valid wire input formats for aux_wire are supported. (#4328)

Next-generation device API

  • The experimental device interface has been integrated with the QNode for JAX, JAX-JIT, TensorFlow and PyTorch. (#4323) (#4352) (#4392) (#4393)

  • The experimental DefaultQubit2 device now supports computing VJPs and JVPs using the adjoint method. (#4374)

  • New functions called adjoint_jvp and adjoint_vjp that compute the JVP and VJP of a tape using the adjoint method have been added to qml.devices.qubit.adjoint_jacobian (#4358)

  • DefaultQubit2 now accepts a max_workers argument which controls multiprocessing. A ProcessPoolExecutor executes tapes asynchronously using a pool of at most max_workers processes. If max_workers is None or not given, only the current process executes tapes. If you experience any issue, say using JAX, TensorFlow, Torch, try setting max_workers to None. (#4319) (#4425)

  • qml.devices.experimental.Device now accepts a shots keyword argument and has a shots property. This property is only used to set defaults for a workflow, and does not directly influence the number of shots used in executions or derivatives. (#4388)

  • expand_fn() for DefaultQubit2 has been updated to decompose StatePrep operations present in the middle of a circuit. (#4444)

  • If no seed is specified on initialization with DefaultQubit2, the local random number generator will be seeded from NumPyโ€™s global random number generator. (#4394)

Improvements to machine learning library interfaces

  • pennylane/interfaces has been refactored. The execute_fn passed to the machine learning framework boundaries is now responsible for converting parameters to NumPy. The gradients module can now handle TensorFlow parameters, but gradient tapes now retain the original dtype instead of converting to float64. This may cause instability with finite-difference differentiation and float32 parameters. The machine learning boundary functions are now uncoupled from their legacy counterparts. (#4415)

  • qml.interfaces.set_shots now accepts a Shots object as well as intโ€˜s and tuples of intโ€˜s. (#4388)

  • Readability improvements and stylistic changes have been made to pennylane/interfaces/jax_jit_tuple.py (#4379)

Pulses

  • A HardwareHamiltonian can now be summed with int or float objects. A sequence of HardwareHamiltonians can now be summed via the builtin sum. (#4343)

  • qml.pulse.transmon_drive has been updated in accordance with 1904.06560. In particular, the functional form has been changed from \(\Omega(t)(\cos(\omega_d t + \phi) X - \sin(\omega_d t + \phi) Y)$ to $\Omega(t) \sin(\omega_d t + \phi) Y\). (#4418) (#4465) (#4478) (#4418)

Other improvements

  • The qchem module has been upgraded to use the fermionic operators of the fermi module. #4336 #4521

  • The calculation of Sum, Prod, SProd, PauliWord, and PauliSentence sparse matrices are orders of magnitude faster. (#4475) (#4272) (#4411)

  • A function called qml.math.fidelity_statevector that computes the fidelity between two state vectors has been added. (#4322)

  • qml.ctrl(qml.PauliX) returns a CNOT, Toffoli, or MultiControlledX operation instead of Controlled(PauliX). (#4339)

  • When given a callable, qml.ctrl now does its custom pre-processing on all queued operators from the callable. (#4370)

  • The qchem functions primitive_norm and contracted_norm have been modified to be compatible with higher versions of SciPy. The private function _fac2 for computing double factorials has also been added. #4321

  • tape_expand now uses Operator.decomposition instead of Operator.expand in order to make more performant choices. (#4355)

  • CI now runs tests with TensorFlow 2.13.0 (#4472)

  • All tests in CI and pre-commit hooks now enable linting. (#4335)

  • The default label for a StatePrepBase operator is now |ฮจโŸฉ. (#4340)

  • Device.default_expand_fn() has been updated to decompose qml.StatePrep operations present in the middle of a provided circuit. (#4437)

  • QNode.construct has been updated to only apply the qml.defer_measurements transform if the device does not natively support mid-circuit measurements. (#4516)

  • The application of the qml.defer_measurements transform has been moved from QNode.construct to qml.Device.batch_transform to allow more fine-grain control over when defer_measurements should be used. (#4432)

  • The label for ParametrizedEvolution can display parameters with the requested format as set by the kwarg decimals. Array-like parameters are displayed in the same format as matrices and stored in the cache. (#4151)

Breaking changes ๐Ÿ’”

  • Applying gradient transforms to broadcasted/batched tapes has been deactivated until it is consistently supported for QNodes as well. (#4480)

  • Gradient transforms no longer implicitly cast float32 parameters to float64. Finite difference differentiation with float32 parameters may no longer give accurate results. (#4415)

  • The do_queue keyword argument in qml.operation.Operator has been removed. Instead of setting do_queue=False, use the qml.QueuingManager.stop_recording() context. (#4317)

  • Operator.expand now uses the output of Operator.decomposition instead of what it queues. (#4355)

  • The gradients module no longer needs shot information passed to it explicitly, as the shots are on the tapes. (#4448)

  • qml.StatePrep has been renamed to qml.StatePrepBase and qml.QubitStateVector has been renamed to qml.StatePrep. qml.operation.StatePrep and qml.QubitStateVector are still accessible. (#4450)

  • Support for Python 3.8 has been dropped. (#4453)

  • MeasurementValueโ€˜s signature has been updated to accept a list of MidMeasureMPโ€˜s rather than a list of their IDs. (#4446)

  • The grouping_type and grouping_method keyword arguments have been removed from qchem.molecular_hamiltonian. (#4301)

  • zyz_decomposition and xyx_decomposition have been removed. Use one_qubit_decomposition instead. (#4301)

  • LieAlgebraOptimizer has been removed. Use RiemannianGradientOptimizer instead. (#4301)

  • Operation.base_name has been removed. (#4301)

  • QuantumScript.name has been removed. (#4301)

  • qml.math.reduced_dm has been removed. Use qml.math.reduce_dm or qml.math.reduce_statevector instead. (#4301)

  • The qml.specs dictionary no longer supports direct key access to certain keys. (#4301)

    Instead, these quantities can be accessed as fields of the new Resources object saved under specs_dict["resources"]:

    • num_operations is no longer supported, use specs_dict["resources"].num_gates

    • num_used_wires is no longer supported, use specs_dict["resources"].num_wires

    • gate_types is no longer supported, use specs_dict["resources"].gate_types

    • gate_sizes is no longer supported, use specs_dict["resources"].gate_sizes

    • depth is no longer supported, use specs_dict["resources"].depth

  • 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. (#4322)

  • The private QuantumScript._prep list has been removed, and prep operations now go into the _ops list. (#4485)

Deprecations ๐Ÿ‘‹

  • qml.enable_return and qml.disable_return have been deprecated. Please avoid calling disable_return, as the old return system has been deprecated along with these switch functions. (#4316)

  • qml.qchem.jordan_wigner has been deprecated. Use qml.jordan_wigner instead. List input to define the fermionic operator has also been deprecated; the fermionic operators in the qml.fermi module should be used instead. (#4332)

  • The qml.RandomLayers.compute_decomposition keyword argument ratio_imprimitive will be changed to ratio_imprim to match the call signature of the operation. (#4314)

  • The CV observables qml.X and qml.P have been deprecated. Use qml.QuadX and qml.QuadP instead. (#4330)

  • The method tape.unwrap() and corresponding UnwrapTape and Unwrap classes have been deprecated. Use convert_to_numpy_parameters instead. (#4344)

  • The mode keyword argument in QNode has been deprecated, as it was only used in the old return system (which has also been deprecated). Please use grad_on_execution instead. (#4316)

  • The QuantumScript.set_parameters method and the QuantumScript.data setter have been deprecated. Please use QuantumScript.bind_new_parameters instead. (#4346)

  • The __eq__ and __hash__ dunder methods of Operator and MeasurementProcess will now raise warnings to reflect upcoming changes to operator and measurement process equality and hashing. (#4144) (#4454) (#4489) (#4498)

  • The sampler_seed argument of qml.gradients.spsa_grad has been deprecated, along with a bug fix of the seed-setting behaviour. Instead, the sampler_rng argument should be set, either to an integer value, which will be used to create a PRNG internally or to a NumPy pseudo-random number generator created via np.random.default_rng(seed). (4165)

Documentation ๐Ÿ“

  • The qml.pulse.transmon_interaction and qml.pulse.transmon_drive documentation has been updated. #4327

  • qml.ApproxTimeEvolution.compute_decomposition() now has a code example. (#4354)

  • The documentation for qml.devices.experimental.Device has been improved to clarify some aspects of its use. (#4391)

  • Input types and sources for operators in qml.import_operator are specified. (#4476)

Bug fixes ๐Ÿ›

  • qml.Projector is pickle-able again. (#4452)

  • _copy_and_shift_params does not cast or convert integral types, just relying on + and *โ€˜s casting rules in this case. (#4477)

  • Sparse matrix calculations of SProds containing a Tensor are now allowed. When using Tensor.sparse_matrix(), it is recommended to use the wire_order keyword argument over wires. (#4424)

  • op.adjoint has been replaced with qml.adjoint in QNSPSAOptimizer. (#4421)

  • jax.ad (deprecated) has been replaced by jax.interpreters.ad. (#4403)

  • metric_tensor stops accidentally catching errors that stem from flawed wires assignments in the original circuit, leading to recursion errors. (#4328)

  • A warning is now raised if control indicators are hidden when calling qml.draw_mpl (#4295)

  • qml.qinfo.purity now produces correct results with custom wire labels. (#4331)

  • default.qutrit now supports all qutrit operations used with qml.adjoint. (#4348)

  • The observable data of qml.GellMann now includes its index, allowing correct comparison between instances of qml.GellMann, as well as Hamiltonians and Tensors containing qml.GellMann. (#4366)

  • qml.transforms.merge_amplitude_embedding now works correctly when the AmplitudeEmbeddings have a batch dimension. (#4353)

  • The jordan_wigner function has been modified to work with Hamiltonians built with an active space. (#4372)

  • When a style option is not provided, qml.draw_mpl uses the current style set from qml.drawer.use_style instead of black_white. (#4357)

  • qml.devices.qubit.preprocess.validate_and_expand_adjoint no longer sets the trainable parameters of the expanded tape. (#4365)

  • qml.default_expand_fn now selectively expands operations or measurements allowing more operations to be executed in circuits when measuring non-qwc Hamiltonians. (#4401)

  • qml.ControlledQubitUnitary no longer reports has_decomposition as True when it does not really have a decomposition. (#4407)

  • qml.transforms.split_non_commuting now correctly works on tapes containing both expval and var measurements. (#4426)

  • Subtracting a Prod from another operator now works as expected. (#4441)

  • The sampler_seed argument of qml.gradients.spsa_grad has been changed to sampler_rng. One can either provide an integer, which will be used to create a PRNG internally. Previously, this lead to the same direction being sampled, when num_directions is greater than 1. Alternatively, one can provide a NumPy PRNG, which allows reproducibly calling spsa_grad without getting the same results every time. (4165) (4482)

  • qml.math.get_dtype_name now works with autograd array boxes. (#4494)

  • The backprop gradient of qml.math.fidelity is now correct. (#4380)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Amintor Dusko, Stepan Fomichev, Lillian M. A. Frederiksen, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Ivana Kureฤiฤ‡, Christina Lee, Vincent Michaud-Rioux, Romain Moyard, Lee James Oโ€™Riordan, Mudit Pandey, Borja Requena, Matthew Silverman, Jay Soni, David Wierichs, Frederik Wilde.

orphan

Release 0.31.0ยถ

New features since last release

Seamlessly create and combine fermionic operators ๐Ÿ”ฌ

  • Fermionic operators and arithmetic are now available. (#4191) (#4195) (#4200) (#4201) (#4209) (#4229) (#4253) (#4255) (#4262) (#4278)

    There are a couple of ways to create fermionic operators with this new feature:

    • 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โบ(0) and a(3) are respectively constructed as

      >>> qml.FermiC(0)
      aโบ(0)
      >>> qml.FermiA(3)
      a(3)
      

      These operators can be composed with (*) and linearly combined with (+ and -) 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)
      
    • via 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 also 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)
      

    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!

Workflow-level resource estimation ๐Ÿงฎ

  • PennyLaneโ€™s Tracker now monitors the resource requirements of circuits being executed by the device. (#4045) (#4110)

    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. Consider the following workflow that takes the gradient of a 50-qubit circuit:

    n_wires = 50
    dev = qml.device("null.qubit", wires=n_wires)
    
    weight_shape = qml.StronglyEntanglingLayers.shape(2, n_wires)
    weights = np.random.random(weight_shape, requires_grad=True)
    
    @qml.qnode(dev, diff_method="parameter-shift")
    def circuit(weights):
        qml.StronglyEntanglingLayers(weights, wires=range(n_wires))
        return qml.expval(qml.PauliZ(0))
    
    with qml.Tracker(dev) as tracker:
        qml.grad(circuit)(weights)
    

    The tracker can be inspected to extract resource requirements without requiring a 50-qubit circuit run:

    >>> tracker.totals
    {'executions': 451, 'batches': 2, 'batch_len': 451}
    >>> tracker.history["resources"][0]
    wires: 50
    gates: 200
    depth: 77
    shots: Shots(total=None)
    gate_types:
    {'Rot': 100, 'CNOT': 100}
    gate_sizes:
    {1: 100, 2: 100}
    
  • Custom operations can now be constructed that solely define resource requirements โ€” an explicit decomposition or matrix representation is not needed. (#4033)

    PennyLane is now able to estimate the total resource requirements of circuits that include one or more of these operations, 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 ๐Ÿค

  • ParametrizedHamiltonian now has an improved string representation. (#4176)

    >>> def f1(p, t): return p[0] * jnp.sin(p[1] * t)
    >>> def f2(p, t): return p * t
    >>> coeffs = [2., f1, f2]
    >>> observables =  [qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)]
    >>> qml.dot(coeffs, observables)
      (2.0*(PauliX(wires=[0])))
    + (f1(params_0, t)*(PauliY(wires=[0])))
    + (f2(params_1, t)*(PauliZ(wires=[0])))
    
  • The quantum information module now supports trace distance. (#4181)

    Two cases are enabled for calculating the trace distance:

    • 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
      
  • It is now possible to prepare qutrit basis states with qml.QutritBasisState. (#4185)

    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 new transform called one_qubit_decomposition has been added to provide 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 pi. (#4210) (#4246)

    >>> 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]))]
    
  • The has_unitary_generator attribute in qml.ops.qubit.attributes no longer contains operators with non-unitary generators. (#4183)

  • PennyLane Docker builds have been updated to include the latest plugins and interface versions. (#4178)

Extended support for differentiating pulses โš›๏ธ

  • The stochastic parameter-shift gradient method can now be used with hardware-compatible Hamiltonians. (#4132) (#4215)

    This new feature generalizes the stochastic parameter-shift gradient transform for pulses (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. (#4160)

  • qml.pulse.ParametrizedEvolution now uses batched compressed sparse row (BCSR) format. This allows for computing Jacobians of the unitary directly even when dense=False. (#4126)

    def U(params):
        H = jnp.polyval * qml.PauliZ(0) # time dependent Hamiltonian
        Um = qml.evolve(H, dense=False)(params, t=10.)
        return qml.matrix(Um)
    params = jnp.array([[0.5]], dtype=complex)
    jac = jax.jacobian(U, holomorphic=True)(params)
    

Broadcasting and other tweaks to Torch and Keras layers ๐Ÿฆพ

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

    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)
    

    The following features are now available:

    • Native support for parameter broadcasting. (#4131)

      >>> 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(). (#4197)

      >>> print(qml.draw(qlayer, show_matrices=False)(inputs))
      0: โ”€โ•ญAngleEmbedding(M0)โ”€โ•ญBasicEntanglerLayers(M1)โ”€โ”ค  <Z>
      1: โ”€โ•ฐAngleEmbedding(M0)โ”€โ•ฐBasicEntanglerLayers(M1)โ”€โ”ค  <Z>
      
    • Support for KerasLayer model saving and clearer instructions on TorchLayer model saving. (#4149) (#4158)

      >>> torch.save(qlayer.state_dict(), "weights.pt")  # Saving
      >>> qlayer.load_state_dict(torch.load("weights.pt"))  # Loading
      >>> qlayer.eval()
      

      Hybrid models containing KerasLayer or TorchLayer objects can also be saved and loaded.

Improvements ๐Ÿ› 

A more flexible projector

  • qml.Projector now accepts a state vector representation, which enables the creation of projectors in any basis. (#4192)

    dev = qml.device("default.qubit", wires=2)
    @qml.qnode(dev)
    def circuit(state):
        return qml.expval(qml.Projector(state, wires=[0, 1]))
    zero_state = [0, 0]
    plusplus_state = np.array([1, 1, 1, 1]) / 2
    
    >>> circuit(zero_state)
    tensor(1., requires_grad=True)
    >>> circuit(plusplus_state)
    tensor(0.25, requires_grad=True)
    

Do more with qutrits

  • Three qutrit rotation operators have been added that are analogous to RX, RY, and RZ:

    • qml.TRX: an X rotation

    • qml.TRY: a Y rotation

    • qml.TRZ: a Z rotation

    (#2845) (#2846) (#2847)

  • Qutrit devices now support parameter-shift differentiation. (#2845)

The qchem module

  • qchem.molecular_hamiltonian(), qchem.qubit_observable(), qchem.import_operator(), and qchem.dipole_moment() now return an arithmetic operator if enable_new_opmath() is active. (#4138) (#4159) (#4189) (#4204)

  • Non-cubic lattice support for all electron resource estimation has been added. (3956)

  • The qchem.molecular_hamiltonian() function has been upgraded to support custom wires for constructing differentiable Hamiltonians. The zero imaginary component of the Hamiltonian coefficients have been removed. (#4050) (#4094)

  • Jordan-Wigner transforms that cache Pauli gate objects have been accelerated. (#4046)

  • An error is now raised by qchem.molecular_hamiltonian when the dhf method is used for an open-shell system. This duplicates a similar error in qchem.Molecule but makes it clear that the pyscf backend can be used for open-shell calculations. (#4058)

  • Updated various qubit tapering methods to support operator arithmetic. (#4252)

Next-generation device API

  • The new device interface has been integrated with qml.execute for autograd, backpropagation, and no differentiation. (#3903)

  • Support for adjoint differentiation has been added to the DefaultQubit2 device. (#4037)

  • A new function called measure_with_samples that returns a sample-based measurement result given a state has been added. (#4083) (#4093) (#4162) (#4254)

  • DefaultQubit2.preprocess now returns a new ExecutionConfig object with decisions for gradient_method, use_device_gradient, and grad_on_execution. (#4102)

  • Support for sample-based measurements has been added to the DefaultQubit2 device. (#4105) (#4114) (#4133) (#4172)

  • The DefaultQubit2 device now has a seed keyword argument. (#4120)

  • Added a dense keyword to ParametrizedEvolution that allows forcing dense or sparse matrices. (#4079) (#4095) (#4285)

  • Adds the Type variables pennylane.typing.Result and pennylane.typing.ResultBatch for type hinting the result of an execution. (#4018)

  • qml.devices.ExecutionConfig no longer has a shots property, as it is now on the QuantumScript.
    It now has a use_device_gradient property. ExecutionConfig.grad_on_execution = None indicates a request for "best", instead of a string. (#4102)

  • The new device interface for Jax has been integrated with qml.execute. (#4137)

  • The new device interface is now integrated with qml.execute for Tensorflow. (#4169)

  • The experimental device DefaultQubit2 now supports qml.Snapshot. (#4193)

  • The experimental device interface is integrated with the QNode. (#4196)

  • The new device interface in integrated with qml.execute for Torch. (#4257)

Handling shots

  • QuantumScript now has a shots property, allowing shots to be tied to executions instead of devices. (#4067) (#4103) (#4106) (#4112)

  • Several Python built-in functions are now properly defined for instances of the Shots class.

    • print: printing Shots instances is now human-readable

    • str: converting Shots instances to human-readable strings

    • ==: equating two different Shots instances

    • hash: obtaining the hash values of Shots instances

    (#4081) (#4082)

  • qml.devices.ExecutionConfig no longer has a shots property, as it is now on the QuantumScript. It now has a use_device_gradient property. ExecutionConfig.grad_on_execution = None indicates a request for "best" instead of a string. (#4102)

  • QuantumScript.shots has been integrated with QNodes so that shots are placed on the QuantumScript during QNode construction. (#4110)

  • The gradients module has been updated to use the new Shots object internally (#4152)

Operators

  • qml.prod now accepts a single quantum function input for creating new Prod operators. (#4011)

  • DiagonalQubitUnitary now decomposes into RZ, IsingZZ and MultiRZ gates instead of a QubitUnitary operation with a dense matrix. (#4035)

  • All objects being queued in an AnnotatedQueue are now wrapped so that AnnotatedQueue is not dependent on the has of any operators or measurement processes. (#4087)

  • A dense keyword to ParametrizedEvolution that allows forcing dense or sparse matrices has been added. (#4079) (#4095)

  • Added a new function qml.ops.functions.bind_new_parameters that creates a copy of an operator with new parameters without mutating the original operator. (#4113) (#4256)

  • qml.CY has been moved from qml.ops.qubit.non_parametric_ops to qml.ops.op_math.controlled_ops and now inherits from qml.ops.op_math.ControlledOp. (#4116)

  • qml.CZ now inherits from the ControlledOp class and supports exponentiation to arbitrary powers with pow, which is no longer limited to integers. It also supports sparse_matrix and decomposition representations. (#4117)

  • The construction of the Pauli representation for the Sum class is now faster. (#4142)

  • qml.drawer.drawable_layers.drawable_layers and qml.CircuitGraph have been updated to not rely on Operator equality or hash to work correctly. (#4143)

Other improvements

  • A transform dispatcher and program have been added. (#4109) (#4187)

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

  • The following functions in qml.qinfo now support parameter broadcasting:

    • reduced_dm

    • purity

    • vn_entropy

    • mutual_info

    • fidelity

    • relative_entropy

    • trace_distance

    (#4234)

  • The following functions in qml.math now support parameter broadcasting:

    • purity

    • vn_entropy

    • mutual_info

    • fidelity

    • relative_entropy

    • max_entropy

    • sqrt_matrix

    (#4186)

  • pulse.ParametrizedEvolution now raises an error if the number of input parameters does not match the number of parametrized coefficients in the ParametrizedHamiltonian that generates it. An exception is made for HardwareHamiltonians which are not checked. (#4216)

  • The default value for the show_matrices keyword argument in all drawing methods is now True. This allows for quick insights into broadcasted tapes, for example. (#3920)

  • Type variables for qml.typing.Result and qml.typing.ResultBatch have been added for type hinting the result of an execution. (#4108)

  • The Jax-JIT interface now uses symbolic zeros to determine trainable parameters. (4075)

  • A new function called pauli.pauli_word_prefactor() that extracts the prefactor for a given Pauli word has been added. (#4164)

  • Variable-length argument lists of functions and methods in some docstrings is now more clear. (#4242)

  • qml.drawer.drawable_layers.drawable_layers and qml.CircuitGraph have been updated to not rely on Operator equality or hash to work correctly. (#4143)

  • Drawing mid-circuit measurements connected by classical control signals to conditional operations is now possible. (#4228)

  • The autograd interface now submits all required tapes in a single batch on the backward pass. (#4245)

Breaking changes ๐Ÿ’”

  • The default value for the show_matrices keyword argument in all drawing methods is now True. This allows for quick insights into broadcasted tapes, for example. (#3920)

  • DiagonalQubitUnitary now decomposes into RZ, IsingZZ, and MultiRZ gates rather than a QubitUnitary. (#4035)

  • Jax trainable parameters are now Tracer instead of JVPTracer. It is not always the right definition for the JIT interface, but we update them in the custom JVP using symbolic zeros. (4075)

  • The experimental Device interface qml.devices.experimental.Device now requires that the preprocess method also returns an ExecutionConfig object. This allows the device to choose what "best" means for various hyperparameters like gradient_method and grad_on_execution. (#4007) (#4102)

  • Gradient transforms with Jax no longer support argnum. Use argnums instead. (#4076)

  • qml.collections, qml.op_sum, and qml.utils.sparse_hamiltonian have been removed. (#4071)

  • The pennylane.transforms.qcut module now uses (op, id(op)) as nodes in directed multigraphs that are used within the circuit cutting workflow instead of op. This change removes the dependency of the module on the hash of operators. (#4227)

  • Operator.data now returns a tuple instead of a list. (#4222)

  • The pulse differentiation methods, pulse_generator and stoch_pulse_grad, now raise an error when they are applied to a QNode directly. Instead, use differentiation via a JAX entry point (jax.grad, jax.jacobian, โ€ฆ). (#4241)

Deprecations ๐Ÿ‘‹

  • LieAlgebraOptimizer has been renamed to RiemannianGradientOptimizer. [(#4153)(https://github.com/PennyLaneAI/pennylane/pull/4153)]

  • Operation.base_name has been deprecated. Please use Operation.name or type(op).__name__ instead.

  • QuantumScriptโ€˜s name keyword argument and property have been deprecated. This also affects QuantumTape and OperationRecorder. (#4141)

  • The qml.grouping module has been removed. Its functionality has been reorganized in the qml.pauli module.

  • The public methods of DefaultQubit are pending changes to follow the new device API, as used in DefaultQubit2. Warnings have been added to the docstrings to reflect this. (#4145)

  • qml.math.reduced_dm has been deprecated. Please use qml.math.reduce_dm or qml.math.reduce_statevector instead. (#4173)

  • 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. (#4186)

  • The do_queue keyword argument in qml.operation.Operator has been deprecated. Instead of setting do_queue=False, use the qml.QueuingManager.stop_recording() context. (#4148)

  • zyz_decomposition and xyx_decomposition are now deprecated in favour of one_qubit_decomposition. (#4230)

Documentation ๐Ÿ“

  • The documentation is updated to construct QuantumTape upon initialization instead of with queuing. (#4243)

  • The docstring for qml.ops.op_math.Pow.__new__ is now complete and it has been updated along with qml.ops.op_math.Adjoint.__new__. (#4231)

  • The docstring for qml.grad now states that it should be used with the Autograd interface only. (#4202)

  • The description of mult in the qchem.Molecule docstring now correctly states the value of mult that is supported. (#4058)

Bug Fixes ๐Ÿ›

  • Fixed adjoint jacobian results with grad_on_execution=False in the JAX-JIT interface. (4217)

  • Fixed the matrix of SProd when the coefficient is tensorflow and the target matrix is not complex128. (#4249)

  • Fixed a bug where stoch_pulse_grad would ignore prefactors of rescaled Pauli words in the generating terms of a pulse Hamiltonian. (4156)

  • Fixed a bug where the wire ordering of the wires argument to qml.density_matrix was not taken into account. (#4072)

  • A patch in interfaces/autograd.py that checks for the strawberryfields.gbs device has been removed. That device is pinned to PennyLane <= v0.29.0, so that patch is no longer necessary. (#4089)

  • qml.pauli.are_identical_pauli_words now treats all identities as equal. Identity terms on Hamiltonians with non-standard wire orders are no longer eliminated. (#4161)

  • qml.pauli_sentence() is now compatible with empty Hamiltonians qml.Hamiltonian([], []). (#4171)

  • Fixed a bug with Jax where executing multiple tapes with gradient_fn="device" would fail. (#4190)

  • A more meaningful error message is raised when broadcasting with adjoint differentiation on DefaultQubit. (#4203)

  • The has_unitary_generator attribute in qml.ops.qubit.attributes no longer contains operators with non-unitary generators. (#4183)

  • Fixed a bug where op = qml.qsvt() was incorrect up to a global phase when using convention="Wx"" and qml.matrix(op). (#4214)

  • Fixed a buggy calculation of the angle in xyx_decomposition that causes it to give an incorrect decomposition. An if conditional was intended to prevent divide by zero errors, but the division was by the sine of the argument. So, any multiple of $pi$ should trigger the conditional, but it was only checking if the argument was 0. Example: qml.Rot(2.3, 2.3, 2.3) (#4210)

  • Fixed bug that caused ShotAdaptiveOptimizer to truncate dimensions of parameter-distributed shots during optimization. (#4240)

  • Sum observables can now have trainable parameters. (#4251) (#4275)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Venkatakrishnan AnushKrishna, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Lillian M. A. Frederiksen, Emiliano Godinez Ramirez Nikhil Harle Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Romain Moyard, Tristan Nemoz, Mudit Pandey, Manul Patel, Borja Requena, Modjtaba Shokrian-Zini, Mainak Roy, Matthew Silverman, Jay Soni, Edward Thomas, David Wierichs, Frederik Wilde.

orphan

Release 0.30.0ยถ

New features since last release

Pulse programming on hardware โš›๏ธ๐Ÿ”ฌ

  • Support for loading time-dependent Hamiltonians that are compatible with quantum hardware has been added, making it possible to load a Hamiltonian that describes an ensemble of Rydberg atoms or a collection of transmon qubits. (#3749) (#3911) (#3930) (#3936) (#3966) (#3987) (#4021) (#4040)

    Rydberg atoms are the foundational unit for neutral atom quantum computing. A Rydberg-system Hamiltonian can be constructed from a drive term โ€” qml.pulse.rydberg_drive โ€” and an interaction term โ€” qml.pulse.rydberg_interaction:

    from jax import numpy as jnp
    
    atom_coordinates = [[0, 0], [0, 4], [4, 0], [4, 4]]
    wires = [0, 1, 2, 3]
    
    amplitude = lambda p, t: p * jnp.sin(jnp.pi * t)
    phase = jnp.pi / 2
    detuning = 3 * jnp.pi / 4
    
    H_d = qml.pulse.rydberg_drive(amplitude, phase, detuning, wires)
    H_i = qml.pulse.rydberg_interaction(atom_coordinates, wires)
    H = H_d + H_i
    

    The time-dependent Hamiltonian H can be used in a PennyLane pulse-level differentiable circuit:

    dev = qml.device("default.qubit.jax", wires=wires)
    
    @qml.qnode(dev, interface="jax")
    def circuit(params):
        qml.evolve(H)(params, t=[0, 10])
        return qml.expval(qml.PauliZ(0))
    
    >>> params = jnp.array([2.4])
    >>> circuit(params)
    Array(0.6316659, dtype=float32)
    >>> import jax
    >>> jax.grad(circuit)(params)
    Array([1.3116529], dtype=float32)
    

    The qml.pulse page contains additional details. Check out our release blog post for a demonstration of how to perform the execution on actual hardware!

  • A pulse-level circuit can now be differentiated using a stochastic parameter-shift method. (#3780) (#3900) (#4000) (#4004)

    The new qml.gradient.stoch_pulse_grad differentiation method unlocks stochastic-parameter-shift differentiation for pulse-level circuits. The current version of this new method is restricted to Hamiltonians composed of parametrized Pauli words, but future updates to extend to parametrized Pauli sentences can allow this method to be compatible with hardware-based systems such as an ensemble of Rydberg atoms.

    This method can be activated by setting diff_method to qml.gradient.stoch_pulse_grad:

    >>> dev = qml.device("default.qubit.jax", wires=2)
    >>> sin = lambda p, t: jax.numpy.sin(p * t)
    >>> ZZ = qml.PauliZ(0) @ qml.PauliZ(1)
    >>> H = 0.5 * qml.PauliX(0) + qml.pulse.constant * ZZ + sin * qml.PauliX(1)
    >>> @qml.qnode(dev, interface="jax", diff_method=qml.gradients.stoch_pulse_grad)
    >>> def ansatz(params):
    ...     qml.evolve(H)(params, (0.2, 1.))
    ...     return qml.expval(qml.PauliY(1))
    >>> params = [jax.numpy.array(0.4), jax.numpy.array(1.3)]
    >>> jax.grad(ansatz)(params)
    [Array(0.16921353, dtype=float32, weak_type=True),
     Array(-0.2537478, dtype=float32, weak_type=True)]
    

Quantum singular value transformation ๐Ÿ›โžก๏ธ๐Ÿฆ‹

  • PennyLane now supports the quantum singular value transformation (QSVT), which describes how a quantum circuit can be constructed to apply a polynomial transformation to the singular values of an input matrix. (#3756) (#3757) (#3758) (#3905) (#3909) (#3926) (#4023)

    Consider a matrix A along with a vector angles that describes the target polynomial transformation. The qml.qsvt function creates a corresponding circuit:

    dev = qml.device("default.qubit", wires=2)
    
    A = np.array([[0.1, 0.2], [0.3, 0.4]])
    angles = np.array([0.1, 0.2, 0.3])
    
    @qml.qnode(dev)
    def example_circuit(A):
        qml.qsvt(A, angles, wires=[0, 1])
        return qml.expval(qml.PauliZ(wires=0))
    

    This circuit is composed of qml.BlockEncode and qml.PCPhase operations.

    >>> example_circuit(A)
    tensor(0.97777078, requires_grad=True)
    >>> print(example_circuit.qtape.expand(depth=1).draw(decimals=2))
    0: โ”€โ•ญโˆ_ฯ•(0.30)โ”€โ•ญBlockEncode(M0)โ”€โ•ญโˆ_ฯ•(0.20)โ”€โ•ญBlockEncode(M0)โ€ โ”€โ•ญโˆ_ฯ•(0.10)โ”€โ”ค  <Z>
    1: โ”€โ•ฐโˆ_ฯ•(0.30)โ”€โ•ฐBlockEncode(M0)โ”€โ•ฐโˆ_ฯ•(0.20)โ”€โ•ฐBlockEncode(M0)โ€ โ”€โ•ฐโˆ_ฯ•(0.10)โ”€โ”ค
    

    The qml.qsvt function creates a circuit that is targeted at simulators due to the use of matrix-based operations. For advanced users, you can use the operation-based qml.QSVT template to perform the transformation with a custom choice of unitary and projector operations, which may be hardware compatible if a decomposition is provided.

    The QSVT is a complex but powerful transformation capable of generalizing important algorithms like amplitude amplification. Stay tuned for a demo in the coming few weeks to learn more!

Intuitive QNode returns โ†ฉ๏ธ

  • An updated QNode return system has been introduced. PennyLane QNodes now return exactly what you tell them to! ๐ŸŽ‰ (#3957) (#3969) (#3946) (#3913) (#3914) (#3934)

    This was an experimental feature introduced in version 0.25 of PennyLane that was enabled via qml.enable_return(). Now, itโ€™s the default return system. Letโ€™s see how it works.

    Consider the following circuit:

    import pennylane as qml
    
    dev = qml.device("default.qubit", wires=1)
    
    @qml.qnode(dev)
    def circuit(x):
        qml.RX(x, wires=0)
        return qml.expval(qml.PauliZ(0)), qml.probs(0)
    

    In version 0.29 and earlier of PennyLane, circuit() would return a single length-3 array:

    >>> circuit(0.5)
    tensor([0.87758256, 0.93879128, 0.06120872], requires_grad=True)
    

    In versions 0.30 and above, circuit() returns a length-2 tuple containing the expectation value and probabilities separately:

    >>> circuit(0.5)
    (tensor(0.87758256, requires_grad=True),
     tensor([0.93879128, 0.06120872], requires_grad=True))
    

    You can find more details about this change, along with help and troubleshooting tips to solve any issues. If you still have questions, comments, or concerns, we encourage you to post on the PennyLane discussion forum.

A bunch of performance tweaks ๐Ÿƒ๐Ÿ’จ

  • Single-qubit operations that have multi-qubit control can now be decomposed more efficiently using fewer CNOT gates. (#3851)

    Three decompositions from arXiv:2302.06377 are provided and compare favourably to the already-available qml.ops.ctrl_decomp_zyz:

    wires = [0, 1, 2, 3, 4, 5]
    control_wires = wires[1:]
    
    @qml.qnode(qml.device('default.qubit', wires=6))
    def circuit():
        with qml.QueuingManager.stop_recording():
            # the decomposition does not un-queue the target
            target = qml.RX(np.pi/2, wires=0)
        qml.ops.ctrl_decomp_bisect(target, (1, 2, 3, 4, 5))
        return qml.state()
    
    print(qml.draw(circuit, expansion_strategy="device")())
    
    0: โ”€โ”€Hโ”€โ•ญXโ”€โ”€U(M0)โ”€โ•ญXโ”€โ”€U(M0)โ€ โ”€โ•ญXโ”€โ”€U(M0)โ”€โ•ญXโ”€โ”€U(M0)โ€ โ”€โ”€Hโ”€โ”ค  State
    1: โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    2: โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    3: โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    4: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”œโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    5: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    
  • A new decomposition to qml.SingleExcitation has been added that halves the number of CNOTs required. (3976)

    >>> qml.SingleExcitation.compute_decomposition(1.23, wires=(0,1))
    [Adjoint(T(wires=[0])), Hadamard(wires=[0]), S(wires=[0]),
     Adjoint(T(wires=[1])), Adjoint(S(wires=[1])), Hadamard(wires=[1]),
     CNOT(wires=[1, 0]), RZ(-0.615, wires=[0]), RY(0.615, wires=[1]),
     CNOT(wires=[1, 0]), Adjoint(S(wires=[0])), Hadamard(wires=[0]),
     T(wires=[0]), Hadamard(wires=[1]), S(wires=[1]), T(wires=[1])]
    
  • The adjoint differentiation method can now be more efficient, avoiding the decomposition of operations that can be differentiated directly. Any operation that defines a generator() can be differentiated with the adjoint method. (#3874)

    For example, in version 0.29 the qml.CRY operation would be decomposed when calculating the adjoint-method gradient. Executing the code below shows that this decomposition no longer takes place in version 0.30 and qml.CRY is differentiated directly:

    import jax
    from jax import numpy as jnp
    
    def compute_decomposition(self, phi, wires):
        print("A decomposition has been performed!")
        decomp_ops = [
            qml.RY(phi / 2, wires=wires[1]),
            qml.CNOT(wires=wires),
            qml.RY(-phi / 2, wires=wires[1]),
            qml.CNOT(wires=wires),
        ]
        return decomp_ops
    
    qml.CRY.compute_decomposition = compute_decomposition
    
    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev, diff_method="adjoint")
    def circuit(phi):
        qml.Hadamard(wires=0)
        qml.CRY(phi, wires=[0, 1])
        return qml.expval(qml.PauliZ(1))
    
    phi = jnp.array(0.5)
    jax.grad(circuit)(phi)
    
  • Derivatives are computed more efficiently when using jax.jit with gradient transforms; the trainable parameters are now set correctly instead of every parameter having to be set as trainable. (#3697)

    In the circuit below, only the derivative with respect to parameter b is now calculated:

    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev, interface="jax-jit")
    def circuit(a, b):
        qml.RX(a, wires=0)
        qml.RY(b, wires=0)
        qml.CNOT(wires=[0, 1])
        return qml.expval(qml.PauliZ(0))
    
    a = jnp.array(0.4)
    b = jnp.array(0.5)
    
    jac = jax.jacobian(circuit, argnums=[1])
    jac_jit = jax.jit(jac)
    
    jac_jit(a, b)
    assert len(circuit.tape.trainable_params) == 1
    

Improvements ๐Ÿ› 

Next-generation device API

In this release and future releases, we will be making changes to our device API with the goal in mind to make developing plugins much easier for developers and unlock new device capabilities. Users shouldnโ€™t yet feel any of these changes when using PennyLane, but here is what has changed this release:

  • Several functions in devices/qubit have been added or improved:

    • sample_state: returns a series of samples based on a given state vector and a number of shots. (#3720)

    • simulate: supports measuring expectation values of large observables such as qml.Hamiltonian, qml.SparseHamiltonian, and qml.Sum. (#3759)

    • apply_operation: supports broadcasting. (#3852)

    • adjoint_jacobian: supports adjoint differentiation in the new qubit state-vector device. (#3790)

  • qml.devices.qubit.preprocess now allows circuits with non-commuting observables. (#3857)

  • qml.devices.qubit.measure now computes the expectation values of Hamiltonian and Sum in a backpropagation-compatible way. (#3862)

Pulse programming

  • Here are the functions, classes, and more that were added or improved to facilitate simulating ensembles of Rydberg atoms: (#3749) (#3911) (#3930) (#3936) (#3966) (#3987) (#3889) (#4021)

    • HardwareHamiltonian: an internal class that contains additional information about pulses and settings.

    • rydberg_interaction: a user-facing function that returns a HardwareHamiltonian containing the Hamiltonian of the interaction of all the Rydberg atoms.

    • transmon_interaction: a user-facing function for constructing the Hamiltonian that describes the circuit QED interaction Hamiltonian of superconducting transmon systems.

    • drive: a user-facing function function that returns a ParametrizedHamiltonian (HardwareHamiltonian) containing the Hamiltonian of the interaction between a driving electro-magnetic field and a group of qubits.

    • rydberg_drive: a user-facing function that returns a ParametrizedHamiltonian (HardwareHamiltonian) containing the Hamiltonian of the interaction between a driving laser field and a group of Rydberg atoms.

    • max_distance: a keyword argument added to qml.pulse.rydberg_interaction to allow for the removal of negligible contributions from atoms beyond max_distance from each other.

  • ParametrizedEvolution now takes two new Boolean keyword arguments: return_intermediate and complementary. They allow computing intermediate time evolution matrices. (#3900)

    Activating return_intermediate will return intermediate time evolution steps, for example for the matrix of the Operation, or of a quantum circuit when used in a QNode. Activating complementary will make these intermediate steps be the remaining time evolution complementary to the output for complementary=False. See the docstring for details.

  • Hardware-compatible pulse sequence gradients with qml.gradient.stoch_pulse_grad can now be calculated faster using the new keyword argument use_broadcasting. Executing a ParametrizedEvolution that returns intermediate evolutions has increased performance using the state vector ODE solver, as well. (#4000) (#4004)

Intuitive QNode returns

  • The QNode keyword argument mode has been replaced by the boolean grad_on_execution. (#3969)

  • The "default.gaussian" device and parameter-shift CV both support the new return system, but only for single measurements. (#3946)

  • Keras and Torch NN modules are now compatible with the new return type system. (#3913) (#3914)

  • DefaultQutrit now supports the new return system. (#3934)

Performance improvements

  • The efficiency of tapering(), tapering_hf() and clifford() have been improved. (3942)

  • The peak memory requirements of tapering() and tapering_hf() have been improved when used for larger observables. (3977)

  • Pauli arithmetic has been updated to convert to a Hamiltonian more efficiently. (#3939)

  • Operator has a new Boolean attribute has_generator. It returns whether or not the Operator has a generator defined. has_generator is used in qml.operation.has_gen, which improves its performance and extends differentiation support. (#3875)

  • The performance of CompositeOp has been significantly improved now that it overrides determining whether it is being used with a batch of parameters (see Operator._check_batching). Hamiltonian also now overrides this, but it does nothing since it does not support batching. (#3915)

  • The performance of a Sum operator has been significantly improved now that is_hermitian checks that all coefficients are real if the operator has a pre-computed Pauli representation. (#3915)

  • The coefficients function and the visualize submodule of the qml.fourier module now allow assigning different degrees for different parameters of the input function. (#3005)

    Previously, the arguments degree and filter_threshold to qml.fourier.coefficients were expected to be integers. Now, they can be a sequences of integers with one integer per function parameter (i.e. len(degree)==n_inputs), resulting in a returned array with shape (2*degrees[0]+1,..., 2*degrees[-1]+1). The functions in qml.fourier.visualize accordingly accept such arrays of coefficients.

Other improvements

  • A Shots class has been added to the measurements module to hold shot-related data. (#3682)

  • The custom JVP rules in PennyLane also now support non-scalar and mixed-shape tape parameters as well as multi-dimensional tape return types, like broadcasted qml.probs, for example. (#3766)

  • The qchem.jordan_wigner function has been extended to support more fermionic operator orders. (#3754) (#3751)

  • The AdaptiveOptimizer has been updated to use non-default user-defined QNode arguments. (#3765)

  • Operators now use TensorLike types dunder methods. (#3749)

  • qml.QubitStateVector.state_vector now supports broadcasting. (#3852)

  • qml.SparseHamiltonian can now be applied to any wires in a circuit rather than being restricted to all wires in the circuit. (#3888)

  • Operators can now be divided by scalars with / with the addition of the Operation.__truediv__ dunder method. (#3749)

  • Printing an instance of MutualInfoMP now displays the distribution of the wires between the two subsystems. (#3898)

  • Operator.num_wires has been changed from an abstract value to AnyWires. (#3919)

  • qml.transforms.sum_expand is not run in Device.batch_transform if the device supports Sum observables. (#3915)

  • The type of n_electrons in qml.qchem.Molecule has been set to int. (#3885)

  • Explicit errors have been added to QutritDevice if classical_shadow or shadow_expval is measured. (#3934)

  • QubitDevice now defines the private _get_diagonalizing_gates(circuit) method and uses it when executing circuits. This allows devices that inherit from QubitDevice to override and customize their definition of diagonalizing gates. (#3938)

  • retworkx has been renamed to rustworkx to accommodate the change in the package name. (#3975)

  • Exp, Sum, Prod, and SProd operator data is now a flat list instead of nested. (#3958) (#3983)

  • qml.transforms.convert_to_numpy_parameters has been added to convert a circuit with interface-specific parameters to one with only numpy parameters. This transform is designed to replace qml.tape.Unwrap. (#3899)

  • qml.operation.WiresEnum.AllWires is now -2 instead of 0 to avoid the ambiguity between op.num_wires = 0 and op.num_wires = AllWires. (#3978)

  • Execution code has been updated to use the new qml.transforms.convert_to_numpy_parameters instead of qml.tape.Unwrap. (#3989)

  • A sub-routine of expand_tape has been converted into qml.tape.tape.rotations_and_diagonal_measurements, a helper function that computes rotations and diagonal measurements for a tape with measurements with overlapping wires. (#3912)

  • Various operators and templates have been updated to ensure that their decompositions only return lists of operators. (#3243)

  • The qml.operation.enable_new_opmath toggle has been introduced to cause dunder methods to return arithmetic operators instead of a Hamiltonian or Tensor. (#4008)

    >>> type(qml.PauliX(0) @ qml.PauliZ(1))
    <class 'pennylane.operation.Tensor'>
    >>> qml.operation.enable_new_opmath()
    >>> type(qml.PauliX(0) @ qml.PauliZ(1))
    <class 'pennylane.ops.op_math.prod.Prod'>
    >>> qml.operation.disable_new_opmath()
    >>> type(qml.PauliX(0) @ qml.PauliZ(1))
    <class 'pennylane.operation.Tensor'>
    
  • A new data class called Resources has been added to store resources like the number of gates and circuit depth throughout a quantum circuit. (#3981)

  • A new function called _count_resources() has been added to count the resources required when executing a QuantumTape for a given number of shots. (#3996)

  • QuantumScript.specs has been modified to make use of the new Resources class. This also modifies the output of qml.specs(). (#4015)

  • A new class called ResourcesOperation has been added to allow users to define operations with custom resource information. (#4026)

    For example, users can define a custom operation by inheriting from this new class:

    >>> class CustomOp(qml.resource.ResourcesOperation):
    ...     def resources(self):
    ...         return qml.resource.Resources(num_wires=1, num_gates=2,
    ...                                       gate_types={"PauliX": 2})
    ...
    >>> CustomOp(wires=1)
    CustomOp(wires=[1])
    

    Then, we can track and display the resources of the workflow using qml.specs():

    >>> dev = qml.device("default.qubit", wires=[0,1])
    >>> @qml.qnode(dev)
    ... def circ():
    ...     qml.PauliZ(wires=0)
    ...     CustomOp(wires=1)
    ...     return qml.state()
    ...
    >>> print(qml.specs(circ)()['resources'])
    wires: 2
    gates: 3
    depth: 1
    shots: 0
    gate_types:
    {'PauliZ': 1, 'PauliX': 2}
    
  • MeasurementProcess.shape now accepts a Shots object as one of its arguments to reduce exposure to unnecessary execution details. (#4012)

Breaking changes ๐Ÿ’”

  • The seed_recipes argument has been removed from qml.classical_shadow and qml.shadow_expval. (#4020)

  • The tape method get_operation has an updated signature. (#3998)

  • Both JIT interfaces are no longer compatible with JAX >0.4.3 (we raise an error for those versions). (#3877)

  • An operation that implements a custom generator method, but does not always return a valid generator, also has to implement a has_generator property that reflects in which scenarios a generator will be returned. (#3875)

  • Trainable parameters for the Jax interface are the parameters that are JVPTracer, defined by setting argnums. Previously, all JAX tracers, including those used for JIT compilation, were interpreted to be trainable. (#3697)

  • The keyword argument argnums is now used for gradient transforms using Jax instead of argnum. argnum is automatically converted to argnums when using Jax and will no longer be supported in v0.31 of PennyLane. (#3697) (#3847)

  • qml.OrbitalRotation and, consequently, qml.GateFabric are now more consistent with the interleaved Jordan-Wigner ordering. Previously, they were consistent with the sequential Jordan-Wigner ordering. (#3861)

  • Some MeasurementProcess classes can now only be instantiated with arguments that they will actually use. For example, you can no longer create StateMP(qml.PauliX(0)) or PurityMP(eigvals=(-1,1), wires=Wires(0)). (#3898)

  • Exp, Sum, Prod, and SProd operator data is now a flat list, instead of nested. (#3958) (#3983)

  • qml.tape.tape.expand_tape and, consequentially, QuantumScript.expand no longer update the input tape with rotations and diagonal measurements. Note that the newly expanded tape that is returned will still have the rotations and diagonal measurements. (#3912)

  • qml.Evolution now initializes the coefficient with a factor of -1j instead of 1j. (#4024)

Deprecations ๐Ÿ‘‹

Nothing for this release!

Documentation ๐Ÿ“

  • The documentation of QubitUnitary and DiagonalQubitUnitary was clarified regarding the parameters of the operations. (#4031)

  • A typo has been corrected in the documentation for the introduction to inspecting_circuits and chemistry. (#3844)

  • Usage Details and Theory sections have been separated in the documentation for qml.qchem.taper_operation. (3977)

Bug fixes ๐Ÿ›

  • ctrl_decomp_bisect and ctrl_decomp_zyz are no longer used by default when decomposing controlled operations due to the presence of a global phase difference in the zyz decomposition of some target operators. (#4065)

  • Fixed a bug where qml.math.dot returned a numpy array instead of an autograd array, breaking autograd derivatives in certain circumstances. (#4019)

  • Operators now cast a tuple to an np.ndarray as well as list. (#4022)

  • Fixed a bug where qml.ctrl with parametric gates was incompatible with PyTorch tensors on GPUs. (#4002)

  • Fixed a bug where the broadcast expand results were stacked along the wrong axis for the new return system. (#3984)

  • A more informative error message is raised in qml.jacobian to explain potential problems with the new return types specification. (#3997)

  • Fixed a bug where calling Evolution.generator with coeff being a complex ArrayBox raised an error. (#3796)

  • MeasurementProcess.hash now uses the hash property of the observable. The property now depends on all properties that affect the behaviour of the object, such as VnEntropyMP.log_base or the distribution of wires between the two subsystems in MutualInfoMP. (#3898)

  • The enum measurements.Purity has been added so that PurityMP.return_type is defined. str and repr for PurityMP are also now defined. (#3898)

  • Sum.hash and Prod.hash have been changed slightly to work with non-numeric wire labels. sum_expand should now return correct results and not treat some products as the same operation. (#3898)

  • Fixed bug where the coefficients where not ordered correctly when summing a ParametrizedHamiltonian with other operators. (#3749) (#3902)

  • The metric tensor transform is now fully compatible with Jax and therefore users can provide multiple parameters. (#3847)

  • qml.math.ndim and qml.math.shape are now registered for built-ins and autograd to accomodate Autoray 0.6.1. #3864

  • Ensured that qml.data.load returns datasets in a stable and expected order. (#3856)

  • The qml.equal function now handles comparisons of ParametrizedEvolution operators. (#3870)

  • qml.devices.qubit.apply_operation catches the tf.errors.UnimplementedError that occurs when PauliZ or CNOT gates are applied to a large (>8 wires) tensorflow state. When that occurs, the logic falls back to the tensordot logic instead. (#3884)

  • Fixed parameter broadcasting support with qml.counts in most cases and introduced explicit errors otherwise. (#3876)

  • An error is now raised if a QNode with Jax-jit in use returns counts while having trainable parameters (#3892)

  • A correction has been added to the reference values in test_dipole_of to account for small changes (~2e-8) in the computed dipole moment values resulting from the new PySCF 2.2.0 release. (#3908)

  • SampleMP.shape is now correct when sampling only occurs on a subset of the device wires. (#3921)

  • An issue has been fixed in qchem.Molecule to allow basis sets other than the hard-coded ones to be used in the Molecule class. (#3955)

  • Fixed bug where all devices that inherit from DefaultQubit claimed to support ParametrizedEvolution. Now, only DefaultQubitJax supports the operator, as expected. (#3964)

  • Ensured that parallel AnnotatedQueues do not queue each otherโ€™s contents. (#3924)

  • Added a map_wires method to PauliWord and PauliSentence, and ensured that operators call it in their respective map_wires methods if they have a Pauli rep. (#3985)

  • Fixed a bug when a Tensor is multiplied by a Hamiltonian or vice versa. (#4036)

Contributors โœ๏ธ

This release contains contributions from (in alphabetical order):

Komi Amiko, Utkarsh Azad, Thomas Bromley, Isaac De Vlugt, Olivia Di Matteo, Lillian M. A. Frederiksen, Diego Guala, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Albert Mitjans Coma, Romain Moyard, Lee J. Oโ€™Riordan, Mudit Pandey, Matthew Silverman, Jay Soni, David Wierichs.

orphan

Release 0.29.0ยถ

New features since last release

Pulse programming ๐Ÿ”Š

  • Support for creating pulse-based circuits that describe evolution under a time-dependent Hamiltonian has now been added, as well as the ability to execute and differentiate these pulse-based circuits on simulator. (#3586) (#3617) (#3645) (#3652) (#3665) (#3673) (#3706) (#3730)

    A time-dependent Hamiltonian can be created using qml.pulse.ParametrizedHamiltonian, which holds information representing a linear combination of operators with parametrized coefficents and can be constructed as follows:

    from jax import numpy as jnp
    
    f1 = lambda p, t: p * jnp.sin(t) * (t - 1)
    f2 = lambda p, t: p[0] * jnp.cos(p[1]* t ** 2)
    
    XX = qml.PauliX(0) @ qml.PauliX(1)
    YY = qml.PauliY(0) @ qml.PauliY(1)
    ZZ = qml.PauliZ(0) @ qml.PauliZ(1)
    
    H =  2 * ZZ + f1 * XX + f2 * YY
    
    >>> H
    ParametrizedHamiltonian: terms=3
    >>> p1 = jnp.array(1.2)
    >>> p2 = jnp.array([2.3, 3.4])
    >>> H((p1, p2), t=0.5)
    (2*(PauliZ(wires=[0]) @ PauliZ(wires=[1]))) + ((-0.2876553231625218*(PauliX(wires=[0]) @ PauliX(wires=[1]))) + (1.517961235535459*(PauliY(wires=[0]) @ PauliY(wires=[1]))))
    

    The time-dependent Hamiltonian can be used within a circuit with qml.evolve:

    def pulse_circuit(params, time):
        qml.evolve(H)(params, time)
        return qml.expval(qml.PauliX(0) @ qml.PauliY(1))
    

    Pulse-based circuits can be executed and differentiated on the default.qubit.jax simulator using JAX as an interface:

    >>> dev = qml.device("default.qubit.jax", wires=2)
    >>> qnode = qml.QNode(pulse_circuit, dev, interface="jax")
    >>> params = (p1, p2)
    >>> qnode(params, time=0.5)
    Array(0.72153819, dtype=float64)
    >>> jax.grad(qnode)(params, time=0.5)
    (Array(-0.11324919, dtype=float64),
     Array([-0.64399616,  0.06326374], dtype=float64))
    

    Check out the qml.pulse documentation page for more details!

Special unitary operation ๐ŸŒž

  • A new operation qml.SpecialUnitary has been added, providing access to an arbitrary unitary gate via a parametrization in the Pauli basis. (#3650) (#3651) (#3674)

    qml.SpecialUnitary creates a unitary that exponentiates a linear combination of all possible Pauli words in lexicographical order โ€” except for the identity operator โ€” for num_wires wires, of which there are 4**num_wires - 1. As its first argument, qml.SpecialUnitary takes a list of the 4**num_wires - 1 parameters that are the coefficients of the linear combination.

    To see all possible Pauli words for num_wires wires, you can use the qml.ops.qubit.special_unitary.pauli_basis_strings function:

    >>> qml.ops.qubit.special_unitary.pauli_basis_strings(1) # 4**1-1 = 3 Pauli words
    ['X', 'Y', 'Z']
    >>> qml.ops.qubit.special_unitary.pauli_basis_strings(2) # 4**2-1 = 15 Pauli words
    ['IX', 'IY', 'IZ', 'XI', 'XX', 'XY', 'XZ', 'YI', 'YX', 'YY', 'YZ', 'ZI', 'ZX', 'ZY', 'ZZ']
    

    To use qml.SpecialUnitary, for example, on a single qubit, we may define

    >>> thetas = np.array([0.2, 0.1, -0.5])
    >>> U = qml.SpecialUnitary(thetas, 0)
    >>> qml.matrix(U)
    array([[ 0.8537127 -0.47537233j,  0.09507447+0.19014893j],
           [-0.09507447+0.19014893j,  0.8537127 +0.47537233j]])
    

    A single non-zero entry in the parameters will create a Pauli rotation:

    >>> x = 0.412
    >>> theta = x * np.array([1, 0, 0]) # The first entry belongs to the Pauli word "X"
    >>> su = qml.SpecialUnitary(theta, wires=0)
    >>> rx = qml.RX(-2 * x, 0) # RX introduces a prefactor -0.5 that has to be compensated
    >>> qml.math.allclose(qml.matrix(su), qml.matrix(rx))
    True
    

    This operation can be differentiated with hardware-compatible methods like parameter shifts and it supports parameter broadcasting/batching, but not both at the same time. Learn more by visiting the qml.SpecialUnitary documentation.

Always differentiable ๐Ÿ“ˆ

  • The Hadamard test gradient transform is now available via qml.gradients.hadamard_grad. This transform is also available as a differentiation method within QNodes. (#3625) (#3736)

    qml.gradients.hadamard_grad is a hardware-compatible transform that calculates the gradient of a quantum circuit using the Hadamard test. Note that the device requires an auxiliary wire to calculate the gradient.

    >>> dev = qml.device("default.qubit", wires=2)
    >>> @qml.qnode(dev)
    ... def circuit(params):
    ...     qml.RX(params[0], wires=0)
    ...     qml.RY(params[1], wires=0)
    ...     qml.RX(params[2], wires=0)
    ...     return qml.expval(qml.PauliZ(0))
    >>> params = np.array([0.1, 0.2, 0.3], requires_grad=True)
    >>> qml.gradients.hadamard_grad(circuit)(params)
    (tensor(-0.3875172, requires_grad=True),
     tensor(-0.18884787, requires_grad=True),
     tensor(-0.38355704, requires_grad=True))
    

    This transform can be registered directly as the quantum gradient transform to use during autodifferentiation:

    >>> dev = qml.device("default.qubit", wires=2)
    >>> @qml.qnode(dev, interface="jax", diff_method="hadamard")
    ... def circuit(params):
    ...     qml.RX(params[0], wires=0)
    ...     qml.RY(params[1], wires=0)
    ...     qml.RX(params[2], wires=0)
    ...     return qml.expval(qml.PauliZ(0))
    >>> params = jax.numpy.array([0.1, 0.2, 0.3])
    >>> jax.jacobian(circuit)(params)
    Array([-0.3875172 , -0.18884787, -0.38355705], dtype=float32)
    
  • The gradient transform qml.gradients.spsa_grad is now registered as a differentiation method for QNodes. (#3440)

    The SPSA gradient transform can now be used implicitly by marking a QNode as differentiable with SPSA. It can be selected via

    >>> dev = qml.device("default.qubit", wires=1)
    >>> @qml.qnode(dev, interface="jax", diff_method="spsa", h=0.05, num_directions=20)
    ... def circuit(x):
    ...     qml.RX(x, 0)
    ...     return qml.expval(qml.PauliZ(0))
    >>> jax.jacobian(circuit)(jax.numpy.array(0.5))
    Array(-0.4792258, dtype=float32, weak_type=True)
    

    The argument num_directions determines how many directions of simultaneous perturbation are used and therefore the number of circuit evaluations, up to a prefactor. See the SPSA gradient transform documentation for details. Note: The full SPSA optimization method is already available as qml.SPSAOptimizer.

  • The default interface is now auto. There is no need to specify the interface anymore; it is automatically determined by checking your QNode parameters. (#3677) (#3752) (#3829)

    import jax
    import jax.numpy as jnp
    
    qml.enable_return()
    a = jnp.array(0.1)
    b = jnp.array(0.2)
    
    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev)
    def circuit(a, b):
        qml.RY(a, wires=0)
        qml.RX(b, wires=1)
        qml.CNOT(wires=[0, 1])
        return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(1))
    
    >>> circuit(a, b)
    (Array(0.9950042, dtype=float32), Array(-0.19767681, dtype=float32))
    >>> jac = jax.jacobian(circuit)(a, b)
    >>> jac
    (Array(-0.09983341, dtype=float32, weak_type=True), Array(0.01983384, dtype=float32, weak_type=True))
    
  • The JAX-JIT interface now supports higher-order gradient computation with the new return types system. (#3498)

    Here is an example of using JAX-JIT to compute the Hessian of a circuit:

    import pennylane as qml
    import jax
    from jax import numpy as jnp
    
    jax.config.update("jax_enable_x64", True)
    
    qml.enable_return()
    
    dev = qml.device("default.qubit", wires=2)
    
    @jax.jit
    @qml.qnode(dev, interface="jax-jit", diff_method="parameter-shift", max_diff=2)
    def circuit(a, b):
        qml.RY(a, wires=0)
        qml.RX(b, wires=1)
        return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
    
    a, b = jnp.array(1.0), jnp.array(2.0)
    
    >>> jax.hessian(circuit, argnums=[0, 1])(a, b)
    (((Array(-0.54030231, dtype=float64, weak_type=True),
       Array(0., dtype=float64, weak_type=True)),
      (Array(-1.76002563e-17, dtype=float64, weak_type=True),
       Array(0., dtype=float64, weak_type=True))),
     ((Array(0., dtype=float64, weak_type=True),
       Array(-1.00700085e-17, dtype=float64, weak_type=True)),
      (Array(0., dtype=float64, weak_type=True),
      Array(0.41614684, dtype=float64, weak_type=True))))
    
  • The qchem workflow has been modified to support both Autograd and JAX frameworks. (#3458) (#3462) (#3495)

    The JAX interface is automatically used when the differentiable parameters are JAX objects. Here is an example for computing the Hartree-Fock energy gradients with respect to the atomic coordinates.

    import pennylane as qml
    from pennylane import numpy as np
    import jax
    
    symbols = ["H", "H"]
    geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
    
    mol = qml.qchem.Molecule(symbols, geometry)
    
    args = [jax.numpy.array(mol.coordinates)]
    
    >>> jax.grad(qml.qchem.hf_energy(mol))(*args)
    Array([[ 0.       ,  0.       ,  0.3650435],
           [ 0.       ,  0.       , -0.3650435]], dtype=float64)
    
  • The kernel matrix utility functions in qml.kernels are now autodifferentiation-compatible. In addition, they support batching, for example for quantum kernel execution with shot vectors. (#3742)

    This allows for the following:

    dev = qml.device('default.qubit', wires=2, shots=(100, 100))
    @qml.qnode(dev)
    def circuit(x1, x2):
        qml.templates.AngleEmbedding(x1, wires=dev.wires)
        qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=dev.wires)
        return qml.probs(wires=dev.wires)
    
    kernel = lambda x1, x2: circuit(x1, x2)
    

    We can then compute the kernel matrix on a set of 4 (random) feature vectors X but using two sets of 100 shots each via

    >>> X = np.random.random((4, 2))
    >>> qml.kernels.square_kernel_matrix(X, kernel)[:, 0]
    tensor([[[1.  , 0.86, 0.88, 0.92],
             [0.86, 1.  , 0.75, 0.97],
             [0.88, 0.75, 1.  , 0.91],
             [0.92, 0.97, 0.91, 1.  ]],
            [[1.  , 0.93, 0.91, 0.92],
             [0.93, 1.  , 0.8 , 1.  ],
             [0.91, 0.8 , 1.  , 0.91],
             [0.92, 1.  , 0.91, 1.  ]]], requires_grad=True)
    

    Note that we have extracted the first probability vector entry for each 100-shot evaluation.

Smartly decompose Hamiltonian evolution ๐Ÿ’ฏ

  • Hamiltonian evolution using qml.evolve or qml.exp can now be decomposed into operations. (#3691) (#3777)

    If the time-evolved Hamiltonian is equivalent to another PennyLane operation, then that operation is returned as the decomposition:

    >>> exp_op = qml.evolve(qml.PauliX(0) @ qml.PauliX(1))
    >>> exp_op.decomposition()
    [IsingXX((2+0j), wires=[0, 1])]
    

    If the Hamiltonian is a Pauli word, then the decomposition is provided as a qml.PauliRot operation:

    >>> qml.evolve(qml.PauliZ(0) @ qml.PauliX(1)).decomposition()
    [PauliRot((2+0j), ZX, wires=[0, 1])]
    

    Otherwise, the Hamiltonian is a linear combination of operators and the Suzuki-Trotter decomposition is used:

    >>> qml.evolve(qml.sum(qml.PauliX(0), qml.PauliY(0), qml.PauliZ(0)), num_steps=2).decomposition()
    [RX((1+0j), wires=[0]),
     RY((1+0j), wires=[0]),
     RZ((1+0j), wires=[0]),
     RX((1+0j), wires=[0]),
     RY((1+0j), wires=[0]),
     RZ((1+0j), wires=[0])]
    

Tools for quantum chemistry and other applications ๐Ÿ› ๏ธ

  • A new method called qml.qchem.givens_decomposition has been added, which decomposes a unitary into a sequence of Givens rotation gates with phase shifts and a diagonal phase matrix. (#3573)

    unitary = np.array([[ 0.73678+0.27511j, -0.5095 +0.10704j, -0.06847+0.32515j],
                        [-0.21271+0.34938j, -0.38853+0.36497j,  0.61467-0.41317j],
                        [ 0.41356-0.20765j, -0.00651-0.66689j,  0.32839-0.48293j]])
    
    phase_mat, ordered_rotations = qml.qchem.givens_decomposition(unitary)
    
    >>> phase_mat
    tensor([-0.20604358+0.9785369j , -0.82993272+0.55786114j,
            0.56230612-0.82692833j], requires_grad=True)
    >>> ordered_rotations
    [(tensor([[-0.65087861-0.63937521j, -0.40933651-0.j        ],
              [-0.29201359-0.28685265j,  0.91238348-0.j        ]], requires_grad=True),
      (0, 1)),
    (tensor([[ 0.47970366-0.33308926j, -0.8117487 -0.j        ],
              [ 0.66677093-0.46298215j,  0.5840069 -0.j        ]], requires_grad=True),
      (1, 2)),
    (tensor([[ 0.36147547+0.73779454j, -0.57008306-0.j        ],
              [ 0.2508207 +0.51194108j,  0.82158706-0.j        ]], requires_grad=True),
      (0, 1))]
    
  • A new template called qml.BasisRotation has been added, which performs a basis transformation defined by a set of fermionic ladder operators. (#3573)

    import pennylane as qml
    from pennylane import numpy as np
    
    V = np.array([[ 0.53672126+0.j        , -0.1126064 -2.41479668j],
                  [-0.1126064 +2.41479668j,  1.48694623+0.j        ]])
    eigen_vals, eigen_vecs = np.linalg.eigh(V)
    umat = eigen_vecs.T
    wires = range(len(umat))
    def circuit():
        qml.adjoint(qml.BasisRotation(wires=wires, unitary_matrix=umat))
        for idx, eigenval in enumerate(eigen_vals):
            qml.RZ(eigenval, wires=[idx])
        qml.BasisRotation(wires=wires, unitary_matrix=umat)
    
    >>> circ_unitary = qml.matrix(circuit)()
    >>> np.round(circ_unitary/circ_unitary[0][0], 3)
    tensor([[ 1.   -0.j   , -0.   +0.j   , -0.   +0.j   , -0.   +0.j   ],
            [-0.   +0.j   , -0.516-0.596j, -0.302-0.536j, -0.   +0.j   ],
            [-0.   +0.j   ,  0.35 +0.506j, -0.311-0.724j, -0.   +0.j   ],
            [-0.   +0.j   , -0.   +0.j   , -0.   +0.j   , -0.438+0.899j]], requires_grad=True)
    
  • A new function called qml.qchem.load_basisset has been added to extract qml.qchem basis set data from the Basis Set Exchange library. (#3363)

  • A new function called qml.math.max_entropy has been added to compute the maximum entropy of a quantum state. (#3594)

  • A new template called qml.TwoLocalSwapNetwork has been added that implements a canonical 2-complete linear (2-CCL) swap network described in arXiv:1905.05118. (#3447)

    dev = qml.device('default.qubit', wires=5)
    weights = np.random.random(size=qml.templates.TwoLocalSwapNetwork.shape(len(dev.wires)))
    acquaintances = lambda index, wires, param: (qml.CRY(param, wires=index)
                                     if np.abs(wires[0]-wires[1]) else qml.CRZ(param, wires=index))
    @qml.qnode(dev)
    def swap_network_circuit():
        qml.templates.TwoLocalSwapNetwork(dev.wires, acquaintances, weights, fermionic=False)
        return qml.state()
    
    >>> print(weights)
    tensor([0.20308242, 0.91906199, 0.67988804, 0.81290256, 0.08708985,
            0.81860084, 0.34448344, 0.05655892, 0.61781612, 0.51829044], requires_grad=True)
    >>> print(qml.draw(swap_network_circuit, expansion_strategy = 'device')())
    0: โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ”ค  State
    1: โ”€โ•ฐRY(0.20)โ”€โ•ฐSWAPโ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ•ฐRY(0.09)โ”€โ•ฐSWAPโ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ•ฐRY(0.62)โ”€โ•ฐSWAPโ”€โ”ค  State
    2: โ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ•ฐRY(0.68)โ”€โ•ฐSWAPโ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ•ฐRY(0.34)โ”€โ•ฐSWAPโ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ”ค  State
    3: โ”€โ•ฐRY(0.92)โ”€โ•ฐSWAPโ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ•ฐRY(0.82)โ”€โ•ฐSWAPโ”€โ•ญโ—โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ญSWAPโ”€โ•ฐRY(0.52)โ”€โ•ฐSWAPโ”€โ”ค  State
    4: โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐRY(0.81)โ”€โ•ฐSWAPโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฐRY(0.06)โ”€โ•ฐSWAPโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค  State
    

Improvements ๐Ÿ› 

Pulse programming

  • A new function called qml.pulse.pwc has been added as a convenience function for defining a qml.pulse.ParametrizedHamiltonian. This function can be used to create a callable coefficient by setting the timespan over which the function should be non-zero. The resulting callable can be passed an array of parameters and a time. (#3645)

    >>> timespan = (2, 4)
    >>> f = qml.pulse.pwc(timespan)
    >>> f * qml.PauliX(0)
    ParametrizedHamiltonian: terms=1
    

    The params array will be used as bin values evenly distributed over the timespan, and the parameter t will determine which of the bins is returned.

    >>> f(params=[1.2, 2.3, 3.4, 4.5], t=3.9)
    DeviceArray(4.5, dtype=float32)
    >>> f(params=[1.2, 2.3, 3.4, 4.5], t=6)  # zero outside the range (2, 4)
    DeviceArray(0., dtype=float32)
    
  • A new function calledqml.pulse.pwc_from_function has been added as a decorator for defining a qml.pulse.ParametrizedHamiltonian. This function can be used to decorate a function and create a piecewise constant approximation of it. (#3645)

    >>> @qml.pulse.pwc_from_function((2, 4), num_bins=10)
    ... def f1(p, t):
    ...     return p * t
    

    The resulting function approximates the same of p**2 * t on the interval t=(2, 4) in 10 bins, and returns zero outside the interval.

    # t=2 and t=2.1 are within the same bin
    >>> f1(3, 2), f1(3, 2.1)
    (DeviceArray(6., dtype=float32), DeviceArray(6., dtype=float32))
    # next bin
    >>> f1(3, 2.2)
    DeviceArray(6.6666665, dtype=float32)
    # outside the interval t=(2, 4)
    >>> f1(3, 5)
    DeviceArray(0., dtype=float32)
    
  • Add ParametrizedHamiltonianPytree class, which is a pytree jax object representing a parametrized Hamiltonian, where the matrix computation is delayed to improve performance. (#3779)

Operations and batching

  • The function qml.dot has been updated to compute the dot product between a vector and a list of operators. (#3586)

    >>> coeffs = np.array([1.1, 2.2])
    >>> ops = [qml.PauliX(0), qml.PauliY(0)]
    >>> qml.dot(coeffs, ops)
    (1.1*(PauliX(wires=[0]))) + (2.2*(PauliY(wires=[0])))
    >>> qml.dot(coeffs, ops, pauli=True)
    1.1 * X(0) + 2.2 * Y(0)
    
  • qml.evolve returns the evolution of an Operator or a ParametrizedHamiltonian. (#3617) (#3706)

  • qml.ControlledQubitUnitary now inherits from qml.ops.op_math.ControlledOp, which defines decomposition, expand, and sparse_matrix rather than raising an error. (#3450)

  • Parameter broadcasting support has been added for the qml.ops.op_math.Controlled class if the base operator supports broadcasting. (#3450)

  • The qml.generator function now checks if the generator is Hermitian, rather than whether it is a subclass of Observable. This allows it to return valid generators from SymbolicOp and CompositeOp classes. (#3485)

  • The qml.equal function has been extended to compare Prod and Sum operators. (#3516)

  • qml.purity has been added as a measurement process for purity (#3551)

  • In-place inversion has been removed for qutrit operations in preparation for the removal of in-place inversion. (#3566)

  • The qml.utils.sparse_hamiltonian function has been moved to thee qml.Hamiltonian.sparse_matrix method. (#3585)

  • The qml.pauli.PauliSentence.operation() method has been improved to avoid instantiating an SProd operator when the coefficient is equal to 1. (#3595)

  • Batching is now allowed in all SymbolicOp operators, which include Exp, Pow and SProd. (#3597)

  • The Sum and Prod operations now have broadcasted operands. (#3611)

  • The XYX single-qubit unitary decomposition has been implemented. (#3628)

  • All dunder methods now return NotImplemented, allowing the right dunder method (e.g. __radd__) of the other class to be called. (#3631)

  • The qml.GellMann operators now include their index when displayed. (#3641)

  • qml.ops.ctrl_decomp_zyz has been added to compute the decomposition of a controlled single-qubit operation given a single-qubit operation and the control wires. (#3681)

  • qml.pauli.is_pauli_word now supports Prod and SProd operators, and it returns False when a Hamiltonian contains more than one term. (#3692)

  • qml.pauli.pauli_word_to_string now supports Prod, SProd and Hamiltonian operators. (#3692)

  • qml.ops.op_math.Controlled can now decompose single qubit target operations more effectively using the ZYZ decomposition. (#3726)

    • The qml.qchem.Molecule class raises an error when the molecule has an odd number of electrons or when the spin multiplicity is not 1. (#3748)

  • qml.qchem.basis_rotation now accounts for spin, allowing it to perform Basis Rotation Groupings for molecular hamiltonians. (#3714) (#3774)

  • The gradient transforms work for the new return type system with non-trivial classical jacobians. (#3776)

  • The default.mixed device has received a performance improvement for multi-qubit operations. This also allows to apply channels that act on more than seven qubits, which was not possible before. (#3584)

  • qml.dot now groups coefficients together. (#3691)

    >>> qml.dot(coeffs=[2, 2, 2], ops=[qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2)])
    2*(PauliX(wires=[0]) + PauliY(wires=[1]) + PauliZ(wires=[2]))
    
  • qml.generator now supports operators with Sum and Prod generators. (#3691)

  • The Sum._sort method now takes into account the name of the operator when sorting. (#3691)

  • A new tape transform called qml.transforms.sign_expand has been added. It implements the optimal decomposition of a fast-forwardable Hamiltonian that minimizes the variance of its estimator in the Single-Qubit-Measurement from arXiv:2207.09479. (#2852)

Differentiability and interfaces

  • The qml.math module now also contains a submodule for fast Fourier transforms, qml.math.fft. (#1440)

    The submodule in particular provides differentiable versions of the following functions, available in all common interfaces for PennyLane

    Note that the output of the derivative of these functions may differ when used with complex-valued inputs, due to different conventions on complex-valued derivatives.

  • Validation has been added on gradient keyword arguments when initializing a QNode โ€” if unexpected keyword arguments are passed, a UserWarning is raised. A list of the current expected gradient function keyword arguments can be accessed via qml.gradients.SUPPORTED_GRADIENT_KWARGS. (#3526)

  • The numpy version has been constrained to <1.24. (#3563)

  • Support for two-qubit unitary decomposition with JAX-JIT has been added. (#3569)

  • qml.math.size now supports PyTorch tensors. (#3606)

  • Most quantum channels are now fully differentiable on all interfaces. (#3612)

  • qml.math.matmul now supports PyTorch and Autograd tensors. (#3613)

  • Add qml.math.detach, which detaches a tensor from its trace. This stops automatic gradient computations. (#3674)

  • Add typing.TensorLike type. (#3675)

  • qml.QuantumMonteCarlo template is now JAX-JIT compatible when passing jax.numpy arrays to the template. (#3734)

  • DefaultQubitJax now supports evolving the state vector when executing qml.pulse.ParametrizedEvolution gates. (#3743)

  • SProd.sparse_matrix now supports interface-specific variables with a single element as the scalar. (#3770)

  • Added argnum argument to metric_tensor. By passing a sequence of indices referring to trainable tape parameters, the metric tensor is only computed with respect to these parameters. This reduces the number of tapes that have to be run. (#3587)

  • The parameter-shift derivative of variances saves a redundant evaluation of the corresponding unshifted expectation value tape, if possible (#3744)

Next generation device API

  • The apply_operation single-dispatch function is added to devices/qubit that applies an operation to a state and returns a new state. (#3637)

  • The preprocess function is added to devices/qubit that validates, expands, and transforms a batch of QuantumTape objects to abstract preprocessing details away from the device. (#3708)

  • The create_initial_state function is added to devices/qubit that returns an initial state for an execution. (#3683)

  • The simulate function is added to devices/qubit that turns a single quantum tape into a measurement result. The function only supports state based measurements with either no observables or observables with diagonalizing gates. It supports simultaneous measurement of non-commuting observables. (#3700)

  • The ExecutionConfig data class has been added. (#3649)

  • The StatePrep class has been added as an interface that state-prep operators must implement. (#3654)

  • qml.QubitStateVector now implements the StatePrep interface. (#3685)

  • qml.BasisState now implements the StatePrep interface. (#3693)

  • New Abstract Base Class for devices Device is added to the devices.experimental submodule. This interface is still in experimental mode and not integrated with the rest of pennylane. (#3602)

Other improvements

  • Writing Hamiltonians to a file using the qml.data module has been improved by employing a condensed writing format. (#3592)

  • Lazy-loading in the qml.data.Dataset.read() method is more universally supported. (#3605)

  • The qchem.Molecule class raises an error when the molecule has an odd number of electrons or when the spin multiplicity is not 1. (#3748)

  • qml.draw and qml.draw_mpl have been updated to draw any quantum function, which allows for visualizing only part of a complete circuit/QNode. (#3760)

  • The string representation of a Measurement Process now includes the _eigvals property if it is set. (#3820)

Breaking changes ๐Ÿ’”

  • The argument mode in execution has been replaced by the boolean grad_on_execution in the new execution pipeline. (#3723)

  • qml.VQECost has been removed. (#3735)

  • The default interface is now auto. (#3677) (#3752) (#3829)

    The interface is determined during the QNode call instead of the initialization. It means that the gradient_fn and gradient_kwargs are only defined on the QNode at the beginning of the call. Moreover, without specifying the interface it is not possible to guarantee that the device will not be changed during the call if you are using backprop (such as default.qubit changing to default.qubit.jax) whereas before it was happening at initialization.

  • The tape method get_operation can also now return the operation index in the tape, and it can be activated by setting the return_op_index to True: get_operation(idx, return_op_index=True). It will become the default in version 0.30. (#3667)

  • Operation.inv() and the Operation.inverse setter have been removed. Please use qml.adjoint or qml.pow instead. (#3618)

    For example, instead of

    >>> qml.PauliX(0).inv()
    

    use

    >>> qml.adjoint(qml.PauliX(0))
    
  • The Operation.inverse property has been removed completely. (#3725)

  • The target wires of qml.ControlledQubitUnitary are no longer available via op.hyperparameters["u_wires"]. Instead, they can be accesses via op.base.wires or op.target_wires. (#3450)

  • The tape constructed by a QNode is no longer queued to surrounding contexts. (#3509)

  • Nested operators like Tensor, Hamiltonian, and Adjoint now remove their owned operators from the queue instead of updating their metadata to have an "owner". (#3282)

  • qml.qchem.scf, qml.RandomLayers.compute_decomposition, and qml.Wires.select_random now use local random number generators instead of global random number generators. This may lead to slightly different random numbers and an independence of the results from the global random number generation state. Please provide a seed to each individual function instead if you want controllable results. (#3624)

  • qml.transforms.measurement_grouping has been removed. Users should use qml.transforms.hamiltonian_expand instead. (#3701)

  • op.simplify() for operators which are linear combinations of Pauli words will use a builtin Pauli representation to more efficiently compute the simplification of the operator. (#3481)

  • All Operatorโ€˜s input parameters that are lists are cast into vanilla numpy arrays. (#3659)

  • QubitDevice.expval no longer permutes an observableโ€™s wire order before passing it to QubitDevice.probability. The associated downstream changes for default.qubit have been made, but this may still affect expectations for other devices that inherit from QubitDevice and override probability (or any other helper functions that take a wire order such as marginal_prob, estimate_probability or analytic_probability). (#3753)

Deprecations ๐Ÿ‘‹

  • qml.utils.sparse_hamiltonian function has been deprecated, and usage will now raise a warning. Instead, one should use the qml.Hamiltonian.sparse_matrix method. (#3585)

  • The collections module has been deprecated. (#3686) (#3687)

  • qml.op_sum has been deprecated. Users should use qml.sum instead. (#3686)

  • The use of Evolution directly has been deprecated. Users should use qml.evolve instead. This new function changes the sign of the given parameter. (#3706)

  • Use of qml.dot with a QNodeCollection has been deprecated. (#3586)

Documentation ๐Ÿ“

  • Revise note on GPU support in the circuit introduction. (#3836)

  • Make warning about vanilla version of NumPy for differentiation more prominent. (#3838)

  • The documentation for qml.operation has been improved. (#3664)

  • The code example in qml.SparseHamiltonian has been updated with the correct wire range. (#3643)

  • A hyperlink has been added in the text for a URL in the qml.qchem.mol_data docstring. (#3644)

  • A typo was corrected in the documentation for qml.math.vn_entropy. (#3740)

Bug fixes ๐Ÿ›

  • Fixed a bug where measuring qml.probs in the computational basis with non-commuting measurements returned incorrect results. Now an error is raised. (#3811)

  • Fixed a bug where measuring qml.probs in the computational basis with non-commuting measurements returned incorrect results. Now an error is raised. (#3811)

  • Fixed a bug in the drawer where nested controlled operations would output the label of the operation being controlled, rather than the control values. (#3745)

  • Fixed a bug in qml.transforms.metric_tensor where prefactors of operation generators were taken into account multiple times, leading to wrong outputs for non-standard operations. (#3579)

  • Local random number generators are now used where possible to avoid mutating the global random state. (#3624)

  • The networkx version change being broken has been fixed by selectively skipping a qcut TensorFlow-JIT test. (#3609) (#3619)

  • Fixed the wires for the Y decomposition in the ZX calculus transform. (#3598)

  • qml.pauli.PauliWord is now pickle-able. (#3588)

  • Child classes of QuantumScript now return their own type when using SomeChildClass.from_queue. (#3501)

  • A typo has been fixed in the calculation and error messages in operation.py (#3536)

  • qml.data.Dataset.write() now ensures that any lazy-loaded values are loaded before they are written to a file. (#3605)

  • Tensor._batch_size is now set to None during initialization, copying and map_wires. (#3642) (#3661)

  • Tensor.has_matrix is now set to True. (#3647)

  • Fixed typo in the example of qml.IsingZZ gate decomposition. (#3676)

  • Fixed a bug that made tapes/qnodes using qml.Snapshot incompatible with qml.drawer.tape_mpl. (#3704)

  • Tensor._pauli_rep is set to None during initialization and Tensor.data has been added to its setter. (#3722)

  • qml.math.ndim has been redirected to jnp.ndim when using it on a jax tensor. (#3730)

  • Implementations of marginal_prob (and subsequently, qml.probs) now return probabilities with the expected wire order. (#3753)

    This bug affected most probabilistic measurement processes on devices that inherit from QubitDevice when the measured wires are out of order with respect to the device wires and 3 or more wires are measured. The assumption was that marginal probabilities would be computed with the deviceโ€™s state and wire order, then re-ordered according to the measurement process wire order. Instead, the re-ordering went in the inverse direction (that is, from measurement process wire order to device wire order). This is now fixed. Note that this only occurred for 3 or more measured wires because this mapping is identical otherwise. More details and discussion of this bug can be found in the original bug report.

  • Empty iterables can no longer be returned from QNodes. (#3769)

  • The keyword arguments for qml.equal now are used when comparing the observables of a Measurement Process. The eigvals of measurements are only requested if both observables are None, saving computational effort. (#3820)

  • Only converts input to qml.Hermitian to a numpy array if the input is a list. (#3820)

Contributors โœ

This release contains contributions from (in alphabetical order):

Gian-Luca Anselmetti, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Ikko Ashimine, Utkarsh Azad, Miriam Beddig, Cristian Boghiu, Thomas Bromley, Astral Cai, Isaac De Vlugt, Olivia Di Matteo, Lillian M. A. Frederiksen, Soran Jahangiri, Korbinian Kottmann, Christina Lee, Albert Mitjans Coma, Romain Moyard, Mudit Pandey, Borja Requena, Matthew Silverman, Jay Soni, Antal Szรกva, Frederik Wilde, David Wierichs, Moritz Willmann.

orphan

Release 0.28.0ยถ

New features since last release

Custom measurement processes ๐Ÿ“

  • Custom measurements can now be facilitated with the addition of the qml.measurements module. (#3286) (#3343) (#3288) (#3312) (#3287) (#3292) (#3287) (#3326) (#3327) (#3388) (#3439) (#3466)

    Within qml.measurements are new subclasses that allow for the possibility to create custom measurements:

    • SampleMeasurement: represents a sample-based measurement

    • StateMeasurement: represents a state-based measurement

    • MeasurementTransform: represents a measurement process that requires the application of a batch transform

    Creating a custom measurement involves making a class that inherits from one of the classes above. An example is given below. Here, the measurement computes the number of samples obtained of 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)
    

    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 supported for this new measurement process:

    >>> x = qml.numpy.array(1.23, requires_grad=True)
    >>> qml.grad(circuit)(x)
    4715.000000000001
    

    For more information about these new features, see the documentation for ``qml.measurements` <https://docs.pennylane.ai/en/stable/code/qml_measurements.html>`_.

ZX Calculus ๐Ÿงฎ

  • QNodes can now be converted into ZX diagrams via the PyZX framework. (#3446)

    ZX diagrams are the medium for which we can envision a quantum circuit as a graph in the ZX-calculus language, showing properties of quantum protocols in a visually compact and logically complete fashion.

    QNodes decorated with @qml.transforms.to_zx will return a PyZX graph that represents the computation in the ZX-calculus language.

    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.

QChem databases and basis sets โš›๏ธ

  • The symbols and geometry of a compound from the PubChem database can now be accessed via qchem.mol_data(). (#3289) (#3378)

    >>> 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))
    
  • Perform quantum chemistry calculations with two new basis sets: 6-311g and CC-PVDZ. (#3279)

    >>> 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]
    

A bunch of new operators ๐Ÿ‘€

  • The controlled CZ gate and controlled Hadamard gate are now available via qml.CCZ and qml.CH, respectively. (#3408)

    >>> ccz = qml.CCZ(wires=[0, 1, 2])
    >>> qml.matrix(ccz)
    [[ 1  0  0  0  0  0  0  0]
     [ 0  1  0  0  0  0  0  0]
     [ 0  0  1  0  0  0  0  0]
     [ 0  0  0  1  0  0  0  0]
     [ 0  0  0  0  1  0  0  0]
     [ 0  0  0  0  0  1  0  0]
     [ 0  0  0  0  0  0  1  0]
     [ 0  0  0  0  0  0  0 -1]]
    >>> ch = qml.CH(wires=[0, 1])
    >>> qml.matrix(ch)
    [[ 1.          0.          0.          0.        ]
     [ 0.          1.          0.          0.        ]
     [ 0.          0.          0.70710678  0.70710678]
     [ 0.          0.          0.70710678 -0.70710678]]
    
  • 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. (#2715)

    >>> 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()
    ...
    >>>