Skip to content

Release 0.27.0

Compare
Choose a tag to compare
@Jaybsoni Jaybsoni released this 14 Nov 21:34
51667d2

New features since last release

An all-new data module 💾

  • The qml.data module is now available, allowing users to download, load, and create quantum datasets. (#3156)

    Datasets are hosted on Xanadu Cloud and can be downloaded by using qml.data.load():

    >>> H2_datasets = qml.data.load(
    ...   data_name="qchem", molname="H2", basis="STO-3G", bondlength=1.1
    ... )
    >>> H2data = H2_datasets[0]
    >>> H2data
    <Dataset = description: qchem/H2/STO-3G/1.1, attributes: ['molecule', 'hamiltonian', ...]>
    • Datasets available to be downloaded can be listed with qml.data.list_datasets().

    • To download or load only specific properties of a dataset, we can specify the desired properties in qml.data.load with the attributes keyword argument:

      >>> H2_hamiltonian = qml.data.load(
      ... data_name="qchem", molname="H2", basis="STO-3G", bondlength=1.1,
      ... attributes=["molecule", "hamiltonian"]
      ... )[0]
      >>> H2_hamiltonian.hamiltonian
      <Hamiltonian: terms=15, wires=[0, 1, 2, 3]>

      The available attributes can be found using qml.data.list_attributes():

    • To select data interactively, we can use qml.data.load_interactive():

      >>> qml.data.load_interactive()
      Please select a data name:
          1) qspin
          2) qchem
      Choice [1-2]: 1
      Please select a sysname:
          ...
      Please select a periodicity:
          ...
      Please select a lattice:
          ...
      Please select a layout:
          ...
      Please select attributes:
          ...
      Force download files? (Default is no) [y/N]: N
      Folder to download to? (Default is pwd, will download to /datasets subdirectory):
      
      Please confirm your choices:
      dataset: qspin/Ising/open/rectangular/4x4
      attributes: ['parameters', 'ground_states']
      force: False
      dest folder: datasets
      Would you like to continue? (Default is yes) [Y/n]:
      <Dataset = description: qspin/Ising/open/rectangular/4x4, attributes: ['parameters', 'ground_states']>
    • Once a dataset is loaded, its properties can be accessed as follows:

      >>> dev = qml.device("default.qubit",wires=4)
      >>> @qml.qnode(dev)
      ... def circuit():
      ...     qml.BasisState(H2data.hf_state, wires = [0, 1, 2, 3])
      ...     for op in H2data.vqe_gates:
      ...          qml.apply(op)
      ...     return qml.expval(H2data.hamiltonian)
      >>> print(circuit())
      -1.0791430411076344

    It's also possible to create custom datasets with qml.data.Dataset:

    >>> example_hamiltonian = qml.Hamiltonian(coeffs=[1,0.5], observables=[qml.PauliZ(wires=0),qml.PauliX(wires=1)])
    >>> example_energies, _ = np.linalg.eigh(qml.matrix(example_hamiltonian))
    >>> example_dataset = qml.data.Dataset(
    ... data_name = 'Example', hamiltonian=example_hamiltonian, energies=example_energies
    ... )
    >>> example_dataset.data_name
    'Example'
    >>> example_dataset.hamiltonian
      (0.5) [X1]
    + (1) [Z0]
    >>> example_dataset.energies
    array([-1.5, -0.5,  0.5,  1.5])

    Custom datasets can be saved and read with the qml.data.Dataset.write() and qml.data.Dataset.read() methods, respectively.

    >>> example_dataset.write('./path/to/dataset.dat')
    >>> read_dataset = qml.data.Dataset()
    >>> read_dataset.read('./path/to/dataset.dat')
    >>> read_dataset.data_name
    'Example'
    >>> read_dataset.hamiltonian
      (0.5) [X1]
    + (1) [Z0]
    >>> read_dataset.energies
    array([-1.5, -0.5,  0.5,  1.5])

    We will continue to work on adding more datasets and features for qml.data in future releases.

Adaptive optimization 🏃🏋️🏊

  • Optimizing quantum circuits can now be done adaptively with qml.AdaptiveOptimizer. (#3192)

    The qml.AdaptiveOptimizer takes an initial circuit and a collection of operators as input and adds a selected gate to the circuit at each optimization step. The process of growing the circuit can be repeated until the circuit gradients converge to zero within a given threshold. The adaptive optimizer can be used to implement algorithms such as ADAPT-VQE as shown in the following example.

    Firstly, we define some preliminary variables needed for VQE:

    symbols = ["H", "H", "H"]
    geometry = np.array([[0.01076341, 0.04449877, 0.0],
                        [0.98729513, 1.63059094, 0.0],
                        [1.87262415, -0.00815842, 0.0]], requires_grad=False)
    H, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, charge = 1)

    The collection of gates to grow the circuit is built to contain all single and double excitations:

    n_electrons = 2
    singles, doubles = qml.qchem.excitations(n_electrons, qubits)
    singles_excitations = [qml.SingleExcitation(0.0, x) for x in singles]
    doubles_excitations = [qml.DoubleExcitation(0.0, x) for x in doubles]
    operator_pool = doubles_excitations + singles_excitations

    Next, an initial circuit that prepares a Hartree-Fock state and returns the expectation value of the Hamiltonian is defined:

    hf_state = qml.qchem.hf_state(n_electrons, qubits)
    dev = qml.device("default.qubit", wires=qubits)
    @qml.qnode(dev)
    def circuit():
        qml.BasisState(hf_state, wires=range(qubits))
        return qml.expval(H)

    Finally, the optimizer is instantiated and then the circuit is created and optimized adaptively:

    opt = qml.optimize.AdaptiveOptimizer()
    for i in range(len(operator_pool)):
        circuit, energy, gradient = opt.step_and_cost(circuit, operator_pool, drain_pool=True)
        print('Energy:', energy)
        print(qml.draw(circuit)())
        print('Largest Gradient:', gradient)
        print()
        if gradient < 1e-3:
            break
    Energy: -1.246549938420637
    0: ─╭BasisState(M0)─╭G²(0.20)─┤ ╭<𝓗>
    1: ─├BasisState(M0)─├G²(0.20)─┤ ├<𝓗>
    2: ─├BasisState(M0)─│─────────┤ ├<𝓗>
    3: ─├BasisState(M0)─│─────────┤ ├<𝓗>
    4: ─├BasisState(M0)─├G²(0.20)─┤ ├<𝓗>
    5: ─╰BasisState(M0)─╰G²(0.20)─┤ ╰<𝓗>
    Largest Gradient: 0.14399872776755085
    
    Energy: -1.2613740231529604
    0: ─╭BasisState(M0)─╭G²(0.20)─╭G²(0.19)─┤ ╭<𝓗>
    1: ─├BasisState(M0)─├G²(0.20)─├G²(0.19)─┤ ├<𝓗>
    2: ─├BasisState(M0)─│─────────├G²(0.19)─┤ ├<𝓗>
    3: ─├BasisState(M0)─│─────────╰G²(0.19)─┤ ├<𝓗>
    4: ─├BasisState(M0)─├G²(0.20)───────────┤ ├<𝓗>
    5: ─╰BasisState(M0)─╰G²(0.20)───────────┤ ╰<𝓗>
    Largest Gradient: 0.1349349562423238
    
    Energy: -1.2743971719780331
    0: ─╭BasisState(M0)─╭G²(0.20)─╭G²(0.19)──────────┤ ╭<𝓗>
    1: ─├BasisState(M0)─├G²(0.20)─├G²(0.19)─╭G(0.00)─┤ ├<𝓗>
    2: ─├BasisState(M0)─│─────────├G²(0.19)─│────────┤ ├<𝓗>
    3: ─├BasisState(M0)─│─────────╰G²(0.19)─╰G(0.00)─┤ ├<𝓗>
    4: ─├BasisState(M0)─├G²(0.20)────────────────────┤ ├<𝓗>
    5: ─╰BasisState(M0)─╰G²(0.20)────────────────────┤ ╰<𝓗>
    Largest Gradient: 0.00040841755397108586

    For a detailed breakdown of its implementation, check out the Adaptive circuits for quantum chemistry demo.

Automatic interface detection 🧩

  • QNodes now accept an auto interface argument which automatically detects the machine learning library to use. (#3132)

    from pennylane import numpy as np
    import torch
    import tensorflow as tf
    from jax import numpy as jnp
    
    dev = qml.device("default.qubit", wires=2)
    @qml.qnode(dev, interface="auto")
    def circuit(weight):
        qml.RX(weight[0], wires=0)
        qml.RY(weight[1], wires=1)
        return qml.expval(qml.PauliZ(0))
    
    interface_tensors = [[0, 1], np.array([0, 1]), torch.Tensor([0, 1]), tf.Variable([0, 1], dtype=float), jnp.array([0, 1])]
    for tensor in interface_tensors:
        res = circuit(weight=tensor)
        print(f"Result value: {res:.2f}; Result type: {type(res)}")
    Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'>
    Result value: 1.00; Result type: <class 'pennylane.numpy.tensor.tensor'>
    Result value: 1.00; Result type: <class 'torch.Tensor'>
    Result value: 1.00; Result type: <class 'tensorflow.python.framework.ops.EagerTensor'>
    Result value: 1.00; Result type: <class 'jaxlib.xla_extension.DeviceArray'>

Upgraded JAX-JIT gradient support 🏎

  • JAX-JIT support for computing the gradient of QNodes that return a single vector of probabilities or multiple expectation values is now available. (#3244) (#3261)

    import jax
    from jax import numpy as jnp
    from jax.config import config
    config.update("jax_enable_x64", True)
    
    dev = qml.device("lightning.qubit", wires=2)
    
    @jax.jit
    @qml.qnode(dev, diff_method="parameter-shift", interface="jax")
    def circuit(x, y):
        qml.RY(x, wires=0)
        qml.RY(y, wires=1)
        qml.CNOT(wires=[0, 1])
        return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
    
    x = jnp.array(1.0)
    y = jnp.array(2.0)
    >>> jax.jacobian(circuit, argnums=[0, 1])(x, y)
    (DeviceArray([-0.84147098,  0.35017549], dtype=float64, weak_type=True),
     DeviceArray([ 4.47445479e-18, -4.91295496e-01], dtype=float64, weak_type=True))

    Note that this change depends on jax.pure_callback, which requires jax>=0.3.17.

Construct Pauli words and sentences 🔤

  • We've reorganized and grouped everything in PennyLane responsible for manipulating Pauli operators into a pauli module. The grouping module has been deprecated as a result, and logic was moved from pennylane/grouping to pennylane/pauli/grouping. (#3179)

  • qml.pauli.PauliWord and qml.pauli.PauliSentence can be used to represent tensor products and linear combinations of Pauli operators, respectively. These provide a more performant method to compute sums and products of Pauli operators. (#3195)

    • qml.pauli.PauliWord represents tensor products of Pauli operators. We can efficiently multiply and extract the matrix of these operators using this representation.

      >>> pw1 = qml.pauli.PauliWord({0:"X", 1:"Z"})
      >>> pw2 = qml.pauli.PauliWord({0:"Y", 1:"Z"})
      >>> pw1, pw2
      (X(0) @ Z(1), Y(0) @ Z(1))
      >>> pw1 * pw2
      (Z(0), 1j)
      >>> pw1.to_mat(wire_order=[0,1])
      array([[ 0,  0,  1,  0],
            [ 0,  0,  0, -1],
            [ 1,  0,  0,  0],
            [ 0, -1,  0,  0]])
    • qml.pauli.PauliSentence represents linear combinations of Pauli words. We can efficiently add, multiply and extract the matrix of these operators in this representation.

      >>> ps1 = qml.pauli.PauliSentence({pw1: 1.2, pw2: 0.5j})
      >>> ps2 = qml.pauli.PauliSentence({pw1: -1.2})
      >>> ps1
      1.2 * X(0) @ Z(1)
      + 0.5j * Y(0) @ Z(1)
      >>> ps1 + ps2
      0.0 * X(0) @ Z(1)
      + 0.5j * Y(0) @ Z(1)
      >>> ps1 * ps2
      -1.44 * I
      + (-0.6+0j) * Z(0)
      >>> (ps1 + ps2).to_mat(wire_order=[0,1])
      array([[ 0. +0.j,  0. +0.j,  0.5+0.j,  0. +0.j],
            [ 0. +0.j,  0. +0.j,  0. +0.j, -0.5+0.j],
            [-0.5+0.j,  0. +0.j,  0. +0.j,  0. +0.j],
            [ 0. +0.j,  0.5+0.j,  0. +0.j,  0. +0.j]])

(Experimental) More support for multi-measurement and gradient output types 🧪

  • qml.enable_return() now supports QNodes returning multiple measurements, including shots vectors, and gradient output types. (#2886) (#3052) (#3041) (#3090) (#3069) (#3137) (#3127) (#3099) (#3098) (#3095) (#3091) (#3176) (#3170) (#3194) (#3267) (#3234) (#3232) (#3223) (#3222) (#3315)

    In v0.25, we introduced qml.enable_return(), which separates measurements into their own tensors. The motivation of this change is the deprecation of ragged ndarray creation in NumPy.

    With this release, we're continuing to elevate this feature by adding support for:

    • Execution (qml.execute)

    • Jacobian vector product (JVP) computation

    • Gradient transforms (qml.gradients.param_shift, qml.gradients.finite_diff, qml.gradients.hessian_transform, qml.gradients.param_shift_hessian).

    • Interfaces (Autograd, TensorFlow, and JAX, although without JIT)

    With this added support, the JAX interface can handle multiple shots (shots vectors), measurements, and gradient output types with qml.enable_return():

    import jax
    
    qml.enable_return()
    dev = qml.device("default.qubit", wires=2, shots=(1, 10000))
    
    params = jax.numpy.array([0.1, 0.2])
    
    @qml.qnode(dev, interface="jax", diff_method="parameter-shift", max_diff=2)
    def circuit(x):
        qml.RX(x[0], wires=[0])
        qml.RY(x[1], wires=[1])
        qml.CNOT(wires=[0, 1])
        return qml.var(qml.PauliZ(0) @ qml.PauliX(1)), qml.probs(wires=[0])
    >>> jax.hessian(circuit)(params)
    ((DeviceArray([[ 0.,  0.],
                  [ 2., -3.]], dtype=float32),
    DeviceArray([[[-0.5,  0. ],
                  [ 0. ,  0. ]],
                [[ 0.5,  0. ],
                  [ 0. ,  0. ]]], dtype=float32)),
    (DeviceArray([[ 0.07677898,  0.0563341 ],
                  [ 0.07238522, -1.830669  ]], dtype=float32),
    DeviceArray([[[-4.9707499e-01,  2.9999996e-04],
                  [-6.2500127e-04,  1.2500001e-04]],
                  [[ 4.9707499e-01, -2.9999996e-04],
                  [ 6.2500127e-04, -1.2500001e-04]]], dtype=float32)))

    For more details, please refer to the documentation.

New basis rotation and tapering features in qml.qchem 🤓

  • Grouped coefficients, observables, and basis rotation transformation matrices needed to construct a qubit Hamiltonian in the rotated basis of molecular orbitals are now calculable via qml.qchem.basis_rotation(). (#3011)

    >>> symbols  = ['H', 'H']
    >>> geometry = np.array([[0.0, 0.0, 0.0], [1.398397361, 0.0, 0.0]], requires_grad = False)
    >>> mol = qml.qchem.Molecule(symbols, geometry)
    >>> core, one, two = qml.qchem.electron_integrals(mol)()
    >>> coeffs, ops, unitaries = qml.qchem.basis_rotation(one, two, tol_factor=1.0e-5)
    >>> unitaries
    [tensor([[-1.00000000e+00, -5.46483514e-13],
           [ 5.46483514e-13, -1.00000000e+00]], requires_grad=True),
    tensor([[-1.00000000e+00,  3.17585063e-14],
            [-3.17585063e-14, -1.00000000e+00]], requires_grad=True),
    tensor([[-0.70710678, -0.70710678],
            [-0.70710678,  0.70710678]], requires_grad=True),
    tensor([[ 2.58789009e-11,  1.00000000e+00],
            [-1.00000000e+00,  2.58789009e-11]], requires_grad=True)]
  • Any gate operation can now be tapered according to :math:\mathbb{Z}_2 symmetries of the Hamiltonian via qml.qchem.taper_operation. (#3002) (#3121)

    >>> symbols = ['He', 'H']
    >>> geometry =  np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4589]])
    >>> mol = qml.qchem.Molecule(symbols, geometry, charge=1)
    >>> H, n_qubits = qml.qchem.molecular_hamiltonian(symbols, geometry)
    >>> generators = qml.qchem.symmetry_generators(H)
    >>> paulixops = qml.qchem.paulix_ops(generators, n_qubits)
    >>> paulix_sector = qml.qchem.optimal_sector(H, generators, mol.n_electrons)
    >>> tap_op = qml.qchem.taper_operation(qml.SingleExcitation, generators, paulixops,
    ...                paulix_sector, wire_order=H.wires, op_wires=[0, 2])
    >>> tap_op(3.14159)
    [Exp(1.5707949999999993j PauliY)]

    Moreover, the obtained tapered operation can be used directly within a QNode.

    >>> dev = qml.device('default.qubit', wires=[0, 1])
    >>> @qml.qnode(dev)
    ... def circuit(params):
    ...     tap_op(params[0])
    ...     return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
    >>> drawer = qml.draw(circuit, show_all_wires=True)
    >>> print(drawer(params=[3.14159]))
    0: ──Exp(0.00+1.57j Y)─┤ ╭<Z@Z>
    1: ────────────────────┤ ╰<Z@Z>
  • Functionality has been added to estimate the number of measurements required to compute an expectation value with a target error and estimate the error in computing an expectation value with a given number of measurements. (#3000)

New functions, operations, and observables 🤩

  • Wires of operators or entire QNodes can now be mapped to other wires via qml.map_wires(). (#3143) (#3145)

    The qml.map_wires() function requires a dictionary representing a wire map. Use it with

    • arbitrary operators:

      >>> op = qml.RX(0.54, wires=0) + qml.PauliX(1) + (qml.PauliZ(2) @ qml.RY(1.23, wires=3))
      >>> op
      (RX(0.54, wires=[0]) + PauliX(wires=[1])) + (PauliZ(wires=[2]) @ RY(1.23, wires=[3]))
      >>> wire_map = {0: 10, 1: 11, 2: 12, 3: 13}
      >>> qml.map_wires(op, wire_map)
      (RX(0.54, wires=[10]) + PauliX(wires=[11])) + (PauliZ(wires=[12]) @ RY(1.23, wires=[13]))

      A map_wires method has also been added to operators, which returns a copy
      of the operator with its wires changed according to the given wire map.

    • entire QNodes:

      dev = qml.device("default.qubit", wires=["A", "B", "C", "D"])
      wire_map = {0: "A", 1: "B", 2: "C", 3: "D"}
      
      @qml.qnode(dev)
      def circuit():
          qml.RX(0.54, wires=0)
          qml.PauliX(1)
          qml.PauliZ(2)
          qml.RY(1.23, wires=3)
          return qml.probs(wires=0)
      >>> mapped_circuit = qml.map_wires(circuit, wire_map)
      >>> mapped_circuit()
      tensor([0.92885434, 0.07114566], requires_grad=True)
      >>> print(qml.draw(mapped_circuit)())
      A: ──RX(0.54)─┤  Probs
      B: ──X────────┤
      C: ──Z────────┤
      D: ──RY(1.23)─┤
  • The qml.IntegerComparator arithmetic operation is now available. (#3113)

    Given a basis state :math:\vert n \rangle, where :math:n is a positive integer, and a fixed positive integer :math:L, qml.IntegerComparator flips a target qubit if :math:n \geq L. Alternatively, the flipping condition can be :math:n < L as demonstrated below:

    dev = qml.device("default.qubit", wires=2)
    
    @qml.qnode(dev)
    def circuit():
        qml.BasisState(np.array([0, 1]), wires=range(2))
        qml.broadcast(qml.Hadamard, wires=range(2), pattern='single')
        qml.IntegerComparator(2, geq=False, wires=[0, 1])
        return qml.state()
    >>> circuit()
    [-0.5+0.j  0.5+0.j -0.5+0.j  0.5+0.j]
  • The qml.GellMann qutrit observable, the ternary generalization of the Pauli observables, is now available. (#3035)

    When using qml.GellMann, the index keyword argument determines which of the 8 Gell-Mann matrices is used.

    dev = qml.device("default.qutrit", wires=2)
    
    @qml.qnode(dev)
    def circuit():
        qml.TClock(wires=0)
        qml.TShift(wires=1)
        qml.TAdd(wires=[0, 1])
        return qml.expval(qml.GellMann(wires=0, index=8) + qml.GellMann(wires=1, index=3))
    >>> circuit()
    -0.42264973081037416
  • Controlled qutrit operations can now be performed with qml.ControlledQutritUnitary. (#2844)

    The control wires and values that define the operation are defined analogously to the qubit operation.

    dev = qml.device("default.qutrit", wires=3)
    
    @qml.qnode(dev)
    def circuit(U):
        qml.TShift(wires=0)
        qml.TAdd(wires=[0, 1])
        qml.ControlledQutritUnitary(U, control_wires=[0, 1], control_values='12', wires=2)
        return qml.state()
    >>> U = np.array([[1, 1, 0], [1, -1, 0], [0, 0, np.sqrt(2)]]) / np.sqrt(2)
    >>> circuit(U)
    tensor([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, 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, 0.+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)

Improvements

  • PennyLane now supports Python 3.11! (#3297)

  • qml.sample and qml.counts work more efficiently and track if computational basis samples are being generated when they are called without specifying an observable. (#3207)

  • The parameters of a basis set containing a different number of Gaussian functions are now easier to differentiate. (#3213)

  • Printing a qml.MultiControlledX operator now shows the control_values keyword argument. (#3113)

  • qml.simplify and transforms like qml.matrix, batch_transform, hamiltonian_expand, and split_non_commuting now work with QuantumScript as well as QuantumTape. (#3209)

  • A redundant flipping of the initial state in the UCCSD and kUpCCGSD templates has been removed. (#3148)

  • qml.adjoint now supports batching if the base operation supports batching. (#3168)

  • qml.OrbitalRotation is now decomposed into two qml.SingleExcitation operations for faster execution and more efficient parameter-shift gradient calculations on devices that natively support qml.SingleExcitation. (#3171)

  • The Exp class decomposes into a PauliRot class if the coefficient is imaginary and the base operator is a Pauli Word. (#3249)

  • Added the operator attributes has_decomposition and has_adjoint that indicate whether a corresponding decomposition or adjoint method is available. (#2986)

  • Structural improvements are made to QueuingManager, formerly QueuingContext, and AnnotatedQueue. (#2794) (#3061) (#3085)

    • QueuingContext is renamed to QueuingManager.
    • QueuingManager should now be the global communication point for putting queuable objects into the active queue.
    • QueuingManager is no longer an abstract base class.
    • AnnotatedQueue and its children no longer inherit from QueuingManager.
    • QueuingManager is no longer a context manager.
    • Recording queues should start and stop recording via the QueuingManager.add_active_queue and QueuingContext.remove_active_queue class methods instead of directly manipulating the _active_contexts property.
    • AnnotatedQueue and its children no longer provide global information about actively recording queues. This information is now only available through QueuingManager.
    • AnnotatedQueue and its children no longer have the private _append, _remove, _update_info, _safe_update_info, and _get_info methods. The public analogues should be used instead.
    • QueuingManager.safe_update_info and AnnotatedQueue.safe_update_info are deprecated. Their functionality is moved to update_info.
  • qml.Identity now accepts multiple wires. (#3049)

    >>> id_op = qml.Identity([0, 1])
    >>> id_op.matrix()
    array([[1., 0., 0., 0.],
        [0., 1., 0., 0.],
        [0., 0., 1., 0.],
        [0., 0., 0., 1.]])
    >>> id_op.sparse_matrix()
    <4x4 sparse matrix of type '<class 'numpy.float64'>'
        with 4 stored elements in Compressed Sparse Row format>
    >>> id_op.eigvals()
    array([1., 1., 1., 1.])
  • Added unitary_check keyword argument to the constructor of the QubitUnitary class which indicates whether the user wants to check for unitarity of the input matrix or not. Its default value is false. (#3063)

  • Modified the representation of WireCut by using qml.draw_mpl. (#3067)

  • Improved the performance of qml.math.expand_matrix function for dense and sparse matrices. (#3060) (#3064)

  • Added support for sums and products of operator classes with scalar tensors of any interface (NumPy, JAX, Tensorflow, PyTorch...). (#3149)

    >>> s_prod = torch.tensor(4) * qml.RX(1.23, 0)
    >>> s_prod
    4*(RX(1.23, wires=[0]))
    >>> s_prod.scalar
    tensor(4)
  • Added overlapping_ops property to the Composite class to improve the performance of the eigvals, diagonalizing_gates and Prod.matrix methods. (#3084)

  • Added the map_wires method to the operators, which returns a copy of the operator with its wires changed according to the given wire map. (#3143)

    >>> op = qml.Toffoli([0, 1, 2])
    >>> wire_map = {0: 2, 2: 0}
    >>> op.map_wires(wire_map=wire_map)
    Toffoli(wires=[2, 1, 0])
  • Calling compute_matrix and compute_sparse_matrix of simple non-parametric operations is now faster and more memory-efficient with the addition of caching. (#3134)

  • Added details to the output of Exp.label(). (#3126)

  • qml.math.unwrap no longer creates ragged arrays. Lists remain lists. (#3163)

  • New null.qubit device. The null.qubitperforms no operations or memory allocations. (#2589)

  • default.qubit favours decomposition and avoids matrix construction for QFT and GroverOperator at larger qubit numbers. (#3193)

  • qml.ControlledQubitUnitary now has a control_values property. (#3206)

  • Added a new qml.tape.QuantumScript class that contains all the non-queuing behavior of QuantumTape. Now, QuantumTape inherits from QuantumScript as well as AnnotatedQueue. (#3097)

  • Extended the qml.equal function to MeasurementProcesses (#3189)

  • qml.drawer.draw.draw_mpl now accepts a style kwarg to select a style for plotting, rather than calling qml.drawer.use_style(style) before plotting. Setting a style for draw_mpl does not change the global configuration for matplotlib plotting. If no style is passed, the function defaults to plotting with the black_white style. (#3247)

Breaking changes

  • QuantumTape._par_info is now a list of dictionaries, instead of a dictionary whose keys are integers starting from zero. (#3185)

  • QueuingContext has been renamed to QueuingManager. (#3061)

  • Deprecation patches for the return types enum's location and qml.utils.expand are removed. (#3092)

  • _multi_dispatch functionality has been moved inside the get_interface function. This function can now be called with one or multiple tensors as arguments. (#3136)

    >>> torch_scalar = torch.tensor(1)
    >>> torch_tensor = torch.Tensor([2, 3, 4])
    >>> numpy_tensor = np.array([5, 6, 7])
    >>> qml.math.get_interface(torch_scalar)
    'torch'
    >>> qml.math.get_interface(numpy_tensor)
    'numpy'

    _multi_dispatch previously had only one argument which contained a list of the tensors to be dispatched:

    >>> qml.math._multi_dispatch([torch_scalar, torch_tensor, numpy_tensor])
    'torch'

    To differentiate whether the user wants to get the interface of a single tensor or multiple tensors, get_interface now accepts a different argument per tensor to be dispatched:

    >>> qml.math.get_interface(*[torch_scalar, torch_tensor, numpy_tensor])
    'torch'
    >>> qml.math.get_interface(torch_scalar, torch_tensor, numpy_tensor)
    'torch'
  • Operator.compute_terms is removed. On a specific instance of an operator, op.terms() can be used instead. There is no longer a static method for this. (#3215)

Deprecations

  • QueuingManager.safe_update_info and AnnotatedQueue.safe_update_info are deprecated. Instead, update_info no longer raises errors if the object isn't in the queue. (#3085)

  • qml.tape.stop_recording and QuantumTape.stop_recording have been moved to qml.QueuingManager.stop_recording. The old functions will still be available until v0.29. (#3068)

  • qml.tape.get_active_tape has been deprecated. Use qml.QueuingManager.active_context() instead. (#3068)

  • Operator.compute_terms has been removed. On a specific instance of an operator, use op.terms() instead. There is no longer a static method for this. (#3215)

  • qml.tape.QuantumTape.inv() has been deprecated. Use qml.tape.QuantumTape.adjoint instead. (#3237)

  • qml.transforms.qcut.remap_tape_wires has been deprecated. Use qml.map_wires instead. (#3186)

  • The grouping module qml.grouping has been deprecated. Use qml.pauli or qml.pauli.grouping instead. The module will still be available until v0.28. (#3262)

Documentation

  • The code block in the usage details of the UCCSD template has been updated. (#3140)

  • Added a "Deprecations" page to the developer documentation. (#3093)

  • The example of the qml.FlipSign template has been updated. (#3219)

Bug fixes

  • qml.SparseHamiltonian now validates the size of the input matrix. (#3278)

  • Users no longer see unintuitive errors when inputing sequences to qml.Hermitian. (#3181)

  • The evaluation of QNodes that return either vn_entropy or mutual_info raises an informative error message when using devices that define a vector of shots. (#3180)

  • Fixed a bug that made qml.AmplitudeEmbedding incompatible with JITting. (#3166)

  • Fixed the qml.transforms.transpile transform to work correctly for all two-qubit operations. (#3104)

  • Fixed a bug with the control values of a controlled version of a ControlledQubitUnitary. (#3119)

  • Fixed a bug where qml.math.fidelity(non_trainable_state, trainable_state) failed unexpectedly. (#3160)

  • Fixed a bug where qml.QueuingManager.stop_recording did not clean up if yielded code raises an exception. (#3182)

  • Returning qml.sample() or qml.counts() with other measurements of non-commuting observables now raises a QuantumFunctionError (e.g., return qml.expval(PauliX(wires=0)), qml.sample() now raises an error). (#2924)

  • Fixed a bug where op.eigvals() would return an incorrect result if the operator was a non-hermitian composite operator. (#3204)

  • Fixed a bug where qml.BasisStatePreparation and qml.BasisEmbedding were not jit-compilable with JAX. (#3239)

  • Fixed a bug where qml.MottonenStatePreparation was not jit-compilable with JAX. (#3260)

  • Fixed a bug where qml.expval(qml.Hamiltonian()) would not raise an error if the Hamiltonian involved some wires that are not present on the device. (#3266)

  • Fixed a bug where qml.tape.QuantumTape.shape() did not account for the batch dimension of the tape (#3269)

Contributors

This release contains contributions from (in alphabetical order):

Kamal Mohamed Ali, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Utkarsh Azad, Thomas Bromley, Albert Mitjans Coma, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Lillian M. A. Frederiksen, Diego Guala, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee J. O'Riordan, Mudit Pandey, Matthew Silverman, Jay Soni, Antal Száva, David Wierichs.