github PennyLaneAI/pennylane v0.29.0
Release 0.29.0

latest releases: v0.38.1, v0.38.0-rc0, v0.38.0...
19 months ago

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 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.

Don't miss a new pennylane release

NewReleases is sending notifications on new releases.