PennyLane is a cross-platform Python library for differentiable programming of quantum computers

Overview

PennyLane is a cross-platform Python library for differentiable programming of quantum computers.

Train a quantum computer the same way as a neural network.

Key Features

  • Machine learning on quantum hardware. Connect to quantum hardware using PyTorch, TensorFlow, JAX, Keras, or NumPy. Build rich and flexible hybrid quantum-classical models.

  • Device independent. Run the same quantum circuit on different quantum backends. Install plugins to access even more devices, including Strawberry Fields, Amazon Braket, IBM Q, Google Cirq, Rigetti Forest, Qulacs, Pasqal, Honeywell, and more.

  • Follow the gradient. Hardware-friendly automatic differentiation of quantum circuits.

  • Batteries included. Built-in tools for quantum machine learning, optimization, and quantum chemistry. Rapidly prototype using built-in quantum simulators with backpropagation support.

Installation

PennyLane requires Python version 3.7 and above. Installation of PennyLane, as well as all dependencies, can be done using pip:

python -m pip install pennylane

Docker support

Docker support exists for building using CPU and GPU (Nvidia CUDA 11.1+) images. See a more detailed description here.

Getting started

For an introduction to quantum machine learning, guides and resources are available on PennyLane's quantum machine learning hub:

You can also check out our documentation for quickstart guides to using PennyLane, and detailed developer guides on how to write your own PennyLane-compatible quantum device.

Tutorials and demonstrations

Take a deeper dive into quantum machine learning by exploring cutting-edge algorithms on our demonstrations page.

All demonstrations are fully executable, and can be downloaded as Jupyter notebooks and Python scripts.

If you would like to contribute your own demo, see our demo submission guide.

Contributing to PennyLane

We welcome contributions—simply fork the PennyLane repository, and then make a pull request containing your contribution. All contributors to PennyLane will be listed as authors on the releases. All users who contribute significantly to the code (new plugins, new functionality, etc.) will be listed on the PennyLane arXiv paper.

We also encourage bug reports, suggestions for new features and enhancements, and even links to cool projects or applications built on PennyLane.

See our contributions page and our developer hub for more details.

Support

If you are having issues, please let us know by posting the issue on our GitHub issue tracker.

We also have a PennyLane discussion forum—come join the community and chat with the PennyLane team.

Note that we are committed to providing a friendly, safe, and welcoming environment for all. Please read and respect the Code of Conduct.

Authors

PennyLane is the work of many contributors.

If you are doing research using PennyLane, please cite our paper:

Ville Bergholm, Josh Izaac, Maria Schuld, Christian Gogolin, M. Sohaib Alam, Shahnawaz Ahmed, Juan Miguel Arrazola, Carsten Blank, Alain Delgado, Soran Jahangiri, Keri McKiernan, Johannes Jakob Meyer, Zeyue Niu, Antal Száva, and Nathan Killoran. PennyLane: Automatic differentiation of hybrid quantum-classical computations. 2018. arXiv:1811.04968

License

PennyLane is free and open source, released under the Apache License, Version 2.0.

Comments
  • [unitaryHACK] Create a Pytorch simulator #1225

    [unitaryHACK] Create a Pytorch simulator #1225

    Context: Create a quantum simulator with PyTorch.

    Description of the Change: A new device default.qubit.torch is created. It allows all quantum operations and measurements to be performed within the PyTorch worlkflow.

    Benefits: It allows end-to-end GPU computation and integration of quantum circuits with the torch interface of Pennylane. A fully working example can be found here: https://colab.research.google.com/drive/1Xb_-l3TIOZhbDw6K9jO34oDgDqzs3o7V?usp=sharing All the gates implemented with Tensorflow have been reimplemented and seem to work.

    Remaining work:

    1. The plugin is not fully tested. When I run the line pl-device-test --device default.qubit.torch --shots None I get the following error
    /Users/slimane/Desktop/pennylane/pennylane/devices/tests/test_wires.py:65: 
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    pennylane/qnode.py:555: in __call__
        res = self.qtape.execute(device=self.device)
    pennylane/tape/tape.py:1264: in execute
        return self._execute(params, device=device)
    //anaconda3/lib/python3.7/site-packages/autograd/tracer.py:48: in f_wrapped
        return f_raw(*args, **kwargs)
    _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
    
    self = <AutogradQuantumTape: wires=[-1, -2], params=0>, params = (), device = <DefaultQubitTorch device (wires=2, shots=None) at 0x1a5fb4e278>
    
        @autograd.extend.primitive
        def _execute(self, params, device):
            # unwrap all NumPy scalar arrays to Python literals
            params = [p.item() if p.shape == tuple() else p for p in params]
            params = autograd.builtins.tuple(params)
        
            # unwrap constant parameters
            self._all_params_unwrapped = [
                p.numpy() if isinstance(p, np.tensor) else p for p in self._all_parameter_values
            ]
        
            # evaluate the tape
            self.set_parameters(self._all_params_unwrapped, trainable_only=False)
            res = self.execute_device(params, device=device)
            self.set_parameters(self._all_parameter_values, trainable_only=False)
        
            if self.is_sampled:
                return res
        
    >       if res.dtype == np.dtype("object"):
    E       TypeError: Cannot interpret 'torch.float64' as a data type
    
    pennylane/interfaces/autograd.py:171: TypeError
    

    Since it seems to be a fundamental error of incompatibility between torch.dtypeand numpy.dtype, I don't understand why I am still able to normally run the circuit.

    1. Writing the appropriate unit test test_default_qubit_torch.py

    2. Check formatting

    Possible drawbacks: More maintenance, torch operations are more different of numpy ones than tensorflow ones are. Autograd in Pytorch is very sensitive. For example this formulation of RZ gate works

    def RZ(theta, device=None):
        theta = torch.as_tensor(theta, dtype=C_DTYPE, device=device)
        p = torch.exp(-0.5j * theta)
        return p * torch.tensor([1, 0], dtype=torch.complex128) + torch.conj(p) * torch.tensor([0, 1], dtype=torch.complex128)
    

    while this one doesn't

    def RZ(theta, device=None):
        theta = torch.as_tensor(theta, dtype=C_DTYPE, device=device)
        p = torch.exp(-0.5j * theta)
        return torch.tensor([p, torch.conj(p)], dtype=torch.complex128)
    

    Related GitHub Issues: #1225

    This is a collective contribution from: @arshpreetsingh, @PCesteban, @artm88, @charmerDark, @mkasmi, @Slimane33

    WIP :construction: unitaryhack 
    opened by Slimane33 47
  • [unitaryhack] Add SPSA optimization

    [unitaryhack] Add SPSA optimization

    Context: Adding a feature as part of unitaryhack related to issue #2451

    Description of the Change: The qml.SPSAOptimizer was added with its corresponding tests based on:

    J. Spall, "An Overview of the Simultaneous Perturbation Method for Efficient Optimization." Johns Hopkins api technical digest, 19(4), 482-492, 1998

    Benefits:

    • The Simultaneous Perturbation Stochastic Approximation method (SPSA) is an iterative algorithm for optimization ideal for noisy loss functions.
    • In contrast to other methods that perform multiple operations to determine the gradient, SPSA only measures two times the loss function to obtain it.

    Possible Drawbacks:

    Related GitHub Issues: Closes https://github.com/PennyLaneAI/pennylane/issues/2451

    unitaryhack-accepted 
    opened by ixime 38
  • Add script to compare commits and additional benchmarks

    Add script to compare commits and additional benchmarks

    Context: The PR #567 needs some tests to see if it works.

    Description of the Change: This PR introduces a script to benchmark across commits. Furthermore it adds new benchmarks.

    There now exists a script benchmark_revisions.py that accepts a list of git revisions (i.e. branches, tags or commits). It downloads those commits and then runs benchmark.py with all the other parameters it got using the downloaded revision of pennylane.

    Revisions are stored to decrease the number of necessary downloads.

    Furthermore, a new benchmark bm_iqp_circuit, benchmarking mostly diagonal gates, was added and other benchmarks fixed.

    review-ready :ok_hand: performance :timer_clock: 
    opened by johannesjmeyer 35
  • added feature to decompose hamiltonians to Pauli matrices

    added feature to decompose hamiltonians to Pauli matrices

    Context: Currently, if a user has a numerical matrix, they must either use qml.Hermitian in a single QNode (causing additional classical processing/requiring full subsystem rotations), or decompose it manually into Pauli operators to be used with qml.map().

    Description of the Change: Adds a function that allows users to easily decompose their Hamiltonian into Pauli operators.

    Benefits: Allows numeric Hamiltonians to be easily used with qml.map().

    Possible Drawbacks: n/a

    Related GitHub Issues:

    Closes #527

    Provides another solution to #553

    enhancement :sparkles: WIP :construction: 
    opened by rahul1704 32
  • Add QML templates module

    Add QML templates module

    Adds a "models" (now called "template") module for providing templates for easy construction of qml models including documentation.

    This is just to get things going in this direction. Would be nice to add more such templates in response to #133. @mariaschuld , you probably have the best overview of existing architectures. Would be great if you could contribute.

    enhancement :sparkles: 
    opened by cgogolin 32
  • Modify `default.mixed` to use `qml.math` for native autodiff backpropagation support

    Modify `default.mixed` to use `qml.math` for native autodiff backpropagation support

    Feature details

    We would like to create a version of the default.mixed simulator that can perform computations native to the TensorFlow, PyTorch and Jax autodiff frameworks. This can be achieved by modifying the existing device to use the qml.math module. The qml.math module supports dispatching to the aforementioned frameworks by using autoray. It allows one to write code that would be compatible with multiple auto differentiation frameworks.

    There would be two advantages to modifying the default.mixed device to use qml.math:

    • The device would support backpropagation and so diff_method='backprop' should be supported;
    • Framework specific features would work with default.mixed:
      • TensorFlow: hybrid models using Keras could be supported using backprop
      • Jax: the jax.vmap function for parallelizing the quantum circuit computations would be supported

    Implementation

    As pointers, the following code snippets should execute well without any errors raised:

    TensorFlow

    import tensorflow as tf
    
    p = 0.01
    dev = qml.device("default.mixed", wires=1)
    
    @qml.qnode(dev, interface="tf", diff_method="backprop")
    def circuit(x):
        qml.RX(x[1], wires=0)
        qml.Rot(x[0], x[1], x[2], wires=0)
        qml.DepolarizingChannel(p, wires=0)
        return qml.expval(qml.PauliZ(0))
    
    weights = tf.Variable([0.2, 0.5, 0.1])
    
    with tf.GradientTape() as tape:
        res = circuit(weights)
    
    print(tape.gradient(res, weights))
    

    PyTorch

    dev = qml.device('default.mixed', wires=2)
    
    p = 0.01
    @qml.qnode(dev, interface='torch', diff_method='backprop')
    def circuit3(phi, theta):
        qml.RX(x[1], wires=0)
        qml.Rot(x[0], x[1], x[2], wires=0)
        qml.DepolarizingChannel(p, wires=0)
        return qml.expval(qml.PauliZ(0))
    
    phi = torch.tensor([0.5, 0.1, 0.4], requires_grad=True)
    result = circuit3(phi)
    
    result.backward()
    phi.grad
    theta.grad
    

    Jax

    from jax import numpy as jnp
    
    dev = qml.device("default.mixed", wires=1)
    
    p = 0.01
    @qml.qnode(dev, interface="jax", diff_method="backprop")
    def circuit(x):
        qml.RX(x[1], wires=0)
        qml.Rot(x[0], x[1], x[2], wires=0)
        qml.DepolarizingChannel(p, wires=0)
        return qml.expval(qml.PauliZ(0))
    
    weights = jnp.array([0.2, 0.5, 0.1])
    grad_fn = jax.grad(circuit)
    print(grad_fn(weights))
    
    import jax
    import networkx as nx
    import pennylane as qml
    
    G = nx.Graph()
    G.add_nodes_from([0,1,2,3])
    G.add_edges_from([(0,3),(1,2),(1,3)])
    
    def noisy_circuit(prob,**kwargs):
        for k in range(len(G.nodes)):
            qml.BitFlip(prob, wires=k)
        return qml.expval(qml.PauliZ(0))
    
    dev = qml.device("default.mixed", wires = len(G.nodes))
    qcircuit = qml.QNode(noisy_circuit, dev, interface = "jax")
    vcircuit = jax.vmap(qcircuit)
    
    probs = jax.numpy.array([0., 0.05, 0.1])
    vcircuit(probs)
    

    It is worth noting that this issue is highly exploratory and resolving the issue will likely entail a deeper dive beforehand.

    How important would you say this feature is?

    1: Not important. Would be nice to have.

    Additional information

    See the following discussion forum threads:

    TensorFlow & Keras with noise models: https://discuss.pennylane.ai/t/adding-noise-to-a-keras-hybrid-nn/1241 Jax vmap function: https://discuss.pennylane.ai/t/jax-with-default-mixed-device/1170

    enhancement :sparkles: intermediate issue :hatched_chick: hacktoberfest-accepted 
    opened by antalszava 30
  • The inputs and outputs of the quantum function circuit

    The inputs and outputs of the quantum function circuit

    This issue is related to how we pass/call the quantum function associated with a QNode. As far as I can see, we want to provide maximum freedom to the user, but are constrained by autograd/design decisions/python decorators.

    From @mariaschuld:

    1. The function where the user defines the quantum circuit should be defined in the same way as it is later used. This is at the moment clashing with how autograd wants things, and we don't fully know how autograd wants things because of its poor documentation,
    2. it should have two "slots" for data inputs and arguments, since the user cannot poke around in one
    3. it should be (at least very soon) able to return the expectations of multiple qubits/qmodes, otherwise things like the variational eigensolver or multi-class variational classifier are reduced to a simplicity that takes away the power of what we want to implement

    Open questions:

    1. should circuit() take batches of data? For example when the device has some parallelisation functionality for each data point in a batch
    2. how do we envision the user to handle thousands of parameters, layered parameter structures and possibly non-vectorial data in future?
    discussion :speech_balloon: interface :electric_plug: 
    opened by josh146 29
  • Help device plugin developers deal with the Operation refactor

    Help device plugin developers deal with the Operation refactor

    Feature details

    As was discussed in #2023 with @josh146 and @mariaschuld in the light of the Operation refactor a smooth transition with useful deprecation warnings should be ensured so that device developers have time to update their devices and custom Operations and support at least a few versions of PennyLane.

    Currently this has not been fully achieved.

    Assume that some third party code (e.g a device plugin) defines a "old style" custom Operation as follows:

    import pennylane as qml
    from pennylane.operation import Operation
    
    class MyOp(Operation):
        num_wires = 1
        num_params = 2
        par_domain = "R"
        grad_method = "F"
    
        @staticmethod
        def decomposition(*params, wires):
            qml.RY(params[0], wires=wires[0])
            qml.PauliZ(wires=wires[0])
            qml.RX(params[1], wires=wires[0])
    
    dev = qml.device('default.qubit', wires=1)
    
    @qml.qnode(dev)
    def qnode(*params):
        MyOp(*params, wires=0)
        return qml.state()
    
    print(qml.draw(qnode)(0.1, 0.2))
    print(qnode(0.1, 0.2))
    

    This works until v0.21.0 but with master the user gets the unspecific error message:

    TypeError: decomposition() missing 1 required keyword-only argument: 'wires'
    

    Converting the implementation to "new style" will break the code with all PL versions prior to and including v0.21.0.

    This is not nice.

    Due to the smart re-naming of functions in #2023 it is indeed possible to define the above operation in a way that is compatible with both the old and the new style:

        def decomposition(self, *params, wires=None):
            if wires is None:
                wires = self.wires
            if not params:
                params = self.parameters
            qml.RY(params[0], wires=wires[0])
            qml.PauliZ(wires=wires[0])
            qml.RX(params[1], wires=wires[0])
    

    Alternatively one can also directly implement the new compute_decomposition() and patch in a suitable preparation() if one is running on an old PL version. The following also works with both 0.21 and the current master:

    class MyOp(Operation):
        num_wires = 1
        num_params = 2
        par_domain = "R"
        grad_method = "F"
    
        if not hasattr(Operation, 'get_eigvals'): # pennylane <= v0.21
            @classmethod
            def decomposition(cls, *params, wires):
                return cls.compute_decomposition(*params, wires=wires)
    
        @staticmethod
        def compute_decomposition(*params, wires=None):
            qml.RY(params[0], wires=wires[0])
            qml.PauliZ(wires=wires[0])
            qml.RX(params[1], wires=wires[0])
    

    There are of course a few further variants...

    It would be nice to replace the above unspecific error message with a useful error that gives a hint for how the implementations can be made compatible across the 0.21/0.22 cut so that plugin developers can at all times support at least some range of PennyLane versions.

    Implementation

    No response

    How important would you say this feature is?

    3: Very important! Blocking work.

    Additional information

    No response

    enhancement :sparkles: 
    opened by cvjjm 27
  • [WIP] Add broadcast_double constructor template

    [WIP] Add broadcast_double constructor template

    Context: Part of the base (now "constructor") templates.

    Description of the Change:

    • Adds broadcast_double, which broadcasts a two-wire unitary over pairs of wires, using the even flag to determine whether to start with the 0th or 1st wire.

    Screenshot from 2020-02-18 15-02-19

    • Adds tests.

    • Adds broadcast_double to the gallery of templates.

    Screenshot from 2020-02-18 15-01-29

    review-ready :ok_hand: templates :file_folder: 
    opened by mariaschuld 25
  • [BUG] jax.jit(jax.grad()) of a circuit with shots crashes

    [BUG] jax.jit(jax.grad()) of a circuit with shots crashes

    Expected behavior

    Jitting the gradient of a QNode with a device using shots, when setting the PRNGKey leads to a crash. I would expect this to work. Below there is a snippet that easily reproduces this issue on master. Do note that if you remove the jax.jit the gradient works, but this is by accident.

    I think I know what is causing the bug, but the explanation is a bit involved, I will first give you a TLDR, then I will show you exactly where the crash happens, then I will reason on what is happening there.

    TLDR

    The problem arises because you are storing a tracer in DEfaultQubiJax._prng_key, but you are not correctly passing this prng key as an argument of the host callback in jax_jit.py:_execute. Conceptually, you should pass as an arg of the callback the prng key like you do for the parameters.

    Instead, the device and therefore the _prng_key is captured in a nested series of lambdas/functions called from the callback. Therefore when the callback is executed, he encounters a tracer object for the prng key which is not substituted with concrete values and crashes.

    Observing where the crash happens

    As I am not very familiar with the interiors of Pennylane, and as this crash happens inside of a callback, preventing proper stack traces from being printed, I had to resort to a very primitive way of debugging. I have added several print statements in the various functions of penny lane. You can install my copy of 'instrumented penny lane by running'

    pip install git+https://github.com/PhilipVinc/pennylane@pv/debug
    

    Using this copy, and running the snippet below, you will see the following messages printed:

    INSIDE THE non_diff_wrapper CALLBACK, EXECUTING PYTHON CODE. Called with args=([array(0.34564769), array(0.45750395)], [array([1.])])
     ...
     after batch vjp, ...
      INSIDE cache_execute called with tapes=[<QuantumTape: wires=[0, 1], params=2>, <QuantumTape: wires=[0, 1], params=2>, <QuantumTape: wires=[0, 1], params=2>, <QuantumTape: wires=[0, 1], params=2>] and kwargs={}
      doing some stuff in the wrapper
       this qubit device has prng_key=Traced<ShapedArray(uint32[2])>with<DynamicJaxprTrace(level=0/1)>
       executing the circuit
         inside circuit execute for self=<DefaultQubitJax device (wires=2, shots=1000) at 0x1342ecdf0>.circuit=<QuantumTape: wires=[0, 1], params=2>
         generating samples
          - Sampling basis states for a jax qubit device with self._prng_key=Traced<ShapedArray(uint32[2])>with<DynamicJaxprTrace(level=0/1)>
          - IF THE PRNG KEY ABOVE IS A TRACER, THIS WILL CRASH!
    ERROR:absl:Outside call <jax.experimental.host_callback._CallbackWrapper object at 0x134312230> threw exception Encountered an unexpected tracer. A function transformed by JAX had a side effect, allowing for a reference to an intermediate value with type uint32[2] wrapped in a DynamicJaxprTracer to escape the scope of the transformation.
    JAX transformations require that functions explicitly return their outputs, and disallow saving intermediate values to global state.
    

    The call chain at the point of the crash is the following:

    In this function we use the ._prng_key to execute some jax random functions. But as I said before, this is all being executed inside of a callback, so there should be no tracers there! Instead, as the device was captured in some lambdas, the device has a tracer as a prngKey and leads to a crash.

    Possible solution

    The solution is to pass the prng key as an argument to the callback. In a sense, you'd need to do something similar to cp_tape for the prng key of the device.

    However, this seems complicated to do because you are not passing the device itself as an argument to those functions, but it captured inside of lambdas (I think). But maybe someone who is more familiar with pennylane @antalszava might know how to do this?

    Source code

    from jax.config import config
    config.update("jax_enable_x64", True)
    
    import jax
    import jax.numpy as jnp
    import numpy as np
    
    import pennylane as qml
    
    phys_qubits = 2
    pars_q      = np.random.rand(3)
    
    def minimal_circ(params, prng_key=None):
        if prng_key is not None:
            dev = qml.device("default.qubit.jax", wires=tuple(range(phys_qubits)), shots=1000, prng_key=prng_key)
        else:
            dev = qml.device("default.qubit.jax", wires=tuple(range(phys_qubits)), shots=1000)
        @qml.qnode(dev, interface="jax",diff_method="parameter-shift")
        def _measure_operator():
            qml.RY(params[0],wires=0)
            qml.RY(params[1],wires=1)
            op = qml.Hamiltonian([1.0],[qml.PauliZ(0) @ qml.PauliZ(1)])
            return qml.expval(op)
        res = _measure_operator()
        return res
    
    grad_fun = jax.grad(minimal_circ)
    
    jax.jit(grad_fun)(pars_q, jax.random.PRNGKey(0))
    

    System information

    >>> import pennylane as qml; qml.about()
    Name: PennyLane
    Version: 0.27.0.dev0
    Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
    Home-page: https://github.com/XanaduAI/pennylane
    Author:
    Author-email:
    License: Apache License 2.0
    Location: /Users/filippovicentini/Documents/pythonenvs/dev-pennylane/python-3.10.7/lib/python3.10/site-packages
    Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, retworkx, scipy, semantic-version, toml
    Required-by: PennyLane-Lightning
    
    Platform info:           macOS-13.0-x86_64-i386-64bit
    Python version:          3.10.7
    Numpy version:           1.23.4
    Scipy version:           1.9.3
    Installed devices:
    - default.gaussian (PennyLane-0.27.0.dev0)
    - default.mixed (PennyLane-0.27.0.dev0)
    - default.qubit (PennyLane-0.27.0.dev0)
    - default.qubit.autograd (PennyLane-0.27.0.dev0)
    - default.qubit.jax (PennyLane-0.27.0.dev0)
    - default.qubit.tf (PennyLane-0.27.0.dev0)
    - default.qubit.torch (PennyLane-0.27.0.dev0)
    - default.qutrit (PennyLane-0.27.0.dev0)
    - null.qubit (PennyLane-0.27.0.dev0)
    - lightning.qubit (PennyLane-Lightning-0.26.1)
    

    Existing GitHub issues

    • [X] I have searched existing GitHub issues to make sure the issue does not already exist.
    bug :bug: 
    opened by PhilipVinc 23
  • Add noisy channel: thermal relaxation

    Add noisy channel: thermal relaxation

    Before submitting

    Please complete the following checklist when submitting a PR:

    • [x] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the test directory!

    • [x] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running make docs.

    • [x] Ensure that the test suite passes, by running make test.

    • [x] Add a new entry to the doc/releases/changelog-dev.md file, summarizing the change, and including a link back to the PR.

    • [x] The PennyLane source code conforms to PEP8 standards. We check all of our code against Pylint. To lint modified files, simply pip install pylint, and then run pylint pennylane/path/to/file.py.

    When all the above are checked, delete everything above the dashed line and fill in the pull request template.


    Context: Adding a new noisy channel which gives the thermal relaxation channel as part of the #971 issue. I got the information from the supplementary of Quantum classifier with tailored quantum kernel. I still need to figure out how to get the second case when T2 > T1. Description of the Change:

    Benefits:

    Possible Drawbacks:

    Related GitHub Issues:

    hacktoberfest-accepted 
    opened by alejomonbar 23
  • Add `argnum` argument to `metric_tensor`

    Add `argnum` argument to `metric_tensor`

    Context: The metric_tensor function is constructed by differentiating a quantum tape with respect to all trainable parameters and computing inner products between the respective quantum states. When one is interested in only a subset of all metric-tensor elements one might want to restrict the set of trainable parameters to save computational resources. The argnum argument allows the caller to do this.

    Description of the Change: The argnum argument must be either an integer or a list of integers or None. The default is None which reproduces the previous behavior, i.e. derivatives with respect to all trainable parameters are computed. When an integer or a list of integers is given only the corresponding tape parameters are taken into account. All other metric tensor entries are set to 0.

    Example:

    dev = qml.device("default.qubit", wires=3)
    
    def circuit(weights):
        qml.RX(weights[0], wires=0)
        qml.RY(weights[1], wires=0)
        qml.CNOT(wires=[0, 1])
        qml.RZ(weights[2], wires=1)
        qml.RZ(weights[3], wires=0)
    
    weights = np.array([0.1, 0.2, 0.3, 0.5], requires_grad=True)
    
    with qml.tape.QuantumTape() as tape:
        circuit(weights)
    
    tapes, proc_fn = qml.metric_tensor(tape)
    res = qml.execute(tapes, dev, None)
    print(proc_fn(res))
    
    # [[ 0.25        0.         -0.04966733 -0.04966733]
    #  [ 0.          0.24750832  0.02433865  0.02433865]
    #  [-0.04966733  0.02433865  0.01226071  0.01226071]
    #  [-0.04966733  0.02433865  0.01226071  0.01226071]]
    

    We can restrict ourselves to only the parameters 2 and 3.

    tapes, proc_fn = qml.metric_tensor(tape, argnum=(2, 3))
    res = qml.execute(tapes, dev, None)
    print(proc_fn(res))
    
    # [[0.         0.         0.         0.        ]
    #  [0.         0.         0.         0.        ]
    #  [0.         0.         0.01226071 0.01226071]
    #  [0.         0.         0.01226071 0.01226071]]
    

    Notably, the first evaluation required 6 quantum tapes, while the second one only requires one tape, as both remaining parameters are contained in one layer.

    Benefits: When only a reduced version of the metric tensor is needed, this argument allows a convenient way to reduce required quantum resources.

    Possible Drawbacks: The indices of a QNode argument might not be identical to the corresponding tape parameters. However, the argnum argument has to specify the parameters by tape indices. This might lead to confusing behavior for a user that tries to compute the metric tensor of a QNode. To mitigate this to some extend, a warning is given when an index is specified that does not refer to any trainable tape parameter.

    Related GitHub Issues: This PR addresses #1880.

    Note I am not sure the test I added is extensive enough. The argument works with all three approx methods. Since the approx=None case covers the entire code used to compute the block-diagonal and diagonal approximation, I don't think one has to test these individually.

    opened by frederikwilde 1
  • [WIP]  Move code from qml.utils.sparse_hamiltonian to Hamiltonian.sparse_matrix #3236

    [WIP] Move code from qml.utils.sparse_hamiltonian to Hamiltonian.sparse_matrix #3236

    …trix, added a deprecation warning to qml.utils.sparse_hamiltonian

    Before submitting

    Please complete the following checklist when submitting a PR:

    • [ ] All new features must include a unit test. If you've fixed a bug or added code that should be tested, add a test to the test directory!

    • [ ] All new functions and code must be clearly commented and documented. If you do make documentation changes, make sure that the docs build and render correctly by running make docs.

    • [ ] Ensure that the test suite passes, by running make test.

    • [ ] Add a new entry to the doc/releases/changelog-dev.md file, summarizing the change, and including a link back to the PR.

    • [ ] The PennyLane source code conforms to PEP8 standards. We check all of our code against Pylint. To lint modified files, simply pip install pylint, and then run pylint pennylane/path/to/file.py.

    When all the above are checked, delete everything above the dashed line and fill in the pull request template.


    Context:

    Description of the Change:

    Benefits:

    Possible Drawbacks:

    Related GitHub Issues:

    opened by 12mB7693 1
  • Add `tensordot`-based method to `default.mixed` device

    Add `tensordot`-based method to `default.mixed` device

    Context: The _apply_channel method of default.mixed devices uses qml.math.einsum. For a device with n wires, einsum does not support applying operations that act on all n wires if n>7, see #3582.

    Description of the Change: This PR adds an _apply_channel_tensordot method to the device, which does not have the above problem. In addition, preliminary benchmarks showed that the tensordot-based method is equally fast or faster for operations that act on more than a single qubit. Therefore, this PR not only switches the device to use the new tensordot-based method if an operation acts on more than 7 qubits, but already for all multi-qubit operations.

    Benefits: Support of many-qubit operations. Performance upgrade for multi-qubit operations.

    Possible Drawbacks: A bit of code duplication/complexity. If this is a major drawback, it probably is best to even remove the old einsum-based method, as its performance gain compared to the new method is small even for single-qubit operations.

    Related GitHub Issues: #3582

    review-ready :ok_hand: performance :timer_clock: 
    opened by dwierichs 3
  • [WIP] [experimental] mps simulator backend

    [WIP] [experimental] mps simulator backend

    A simple python MPS simulator

    Goal: mps simulator able to run circuit of 50 qubits with local gates, i.e.

    • Initialize MPS state
    • Apply (local) gates
    • Compute expectation values
    • Cherry on top: If time allows, differentiate the whole thing. Preferred choice is adjoint method as backpropagation probably wont work very efficiently (why is that again?).

    As a MPS backend, there are various options:

    • Use own code (e.g. Qottmann/simple-DMRG-with-MPS) or these tenpy toycodes (which the former is based on and arguably more reliable)
    • Use TeNPy (The best/default/go-to MPS library in Python, compiled matmul (cython), supports abelian symmetries)
    • Use itensor.org (julia/cpp, supports abelian and non-abelian symmetries)

    Opting for the first variant since handling charges in TeNPy can be a bit of a mess and bridging julia and python not something I already know how to do.

    The idea is to start with the simplest solutions and then build up from there if time allows. Note that this prototype is just an exploration and test and may never make it into PennyLane. Note also that it is based on the new device API, see https://github.com/PennyLaneAI/pennylane/pull/3457 . Note also that currently single qubit operation does not work (could be fixed but not a priority atm) - just add qml.Identity(1) to your ops.

    do not merge :warning: 
    opened by Qottmann 4
  • [BUG] too many subscripts in einsum with 8 qubits (default.mixed)

    [BUG] too many subscripts in einsum with 8 qubits (default.mixed)

    Expected behavior

    The circuit runs without any error.

    Actual behavior

    Execution fails with error:

    ValueError: too many subscripts in einsum
    

    Additional information

    It is mentioned here https://github.com/PennyLaneAI/pennylane/pull/807#discussion_r489727196 that the device can handle 23 qubits before encountering this error. This also forms the basis of this error:

    ValueError: This device does not currently support computations on more than 23 wires
    

    But, the execution fails with some circuits using just 8 qubits. A simple example is given below.

    Source code

    import pennylane.numpy as np
    import pennylane as qml
    
    n = 7
    dev = qml.device('default.mixed', wires=n + 1, shots=None)
    state_vec = np.random.random(2 ** n)
    state_vec = state_vec / np.linalg.norm(state_vec)
    dm = state_vec.reshape([2 ** n, 1]) @ state_vec.reshape([2 ** n, 1]).T
    
    
    @qml.qnode(dev)
    def circ():
        qml.QubitDensityMatrix(dm, wires=range(n))
        qml.MultiControlledX(wires=range(n + 1))
        return qml.probs()
    
    probs = circ()
    print(probs)
    

    Tracebacks

    No response

    System information

    Name: PennyLane
    Version: 0.28.0
    Summary: PennyLane is a Python quantum machine learning library by Xanadu Inc.
    Home-page: https://github.com/XanaduAI/pennylane
    Author: 
    Author-email: 
    License: Apache License 2.0
    Location: 
    Requires: appdirs, autograd, autoray, cachetools, networkx, numpy, pennylane-lightning, requests, retworkx, scipy, semantic-version, toml
    Required-by: PennyLane-Lightning, PennyLane-Lightning-GPU, PennyLane-qiskit, pennylane-qulacs
    Platform info:           Linux-5.15.0-56-generic-x86_64-with-glibc2.35
    Python version:          3.9.10
    Numpy version:           1.22.3
    Scipy version:           1.8.0
    Installed devices:
    - lightning.qubit (PennyLane-Lightning-0.28.0)
    - default.gaussian (PennyLane-0.28.0)
    - default.mixed (PennyLane-0.28.0)
    - default.qubit (PennyLane-0.28.0)
    - default.qubit.autograd (PennyLane-0.28.0)
    - default.qubit.jax (PennyLane-0.28.0)
    - default.qubit.tf (PennyLane-0.28.0)
    - default.qubit.torch (PennyLane-0.28.0)
    - default.qutrit (PennyLane-0.28.0)
    - null.qubit (PennyLane-0.28.0)
    - qiskit.aer (PennyLane-qiskit-0.23.0)
    - qiskit.basicaer (PennyLane-qiskit-0.23.0)
    - qiskit.ibmq (PennyLane-qiskit-0.23.0)
    - qiskit.ibmq.circuit_runner (PennyLane-qiskit-0.23.0)
    - qiskit.ibmq.sampler (PennyLane-qiskit-0.23.0)
    - lightning.gpu (PennyLane-Lightning-GPU-0.23.0)
    - qulacs.simulator (pennylane-qulacs-0.16.0)
    

    Existing GitHub issues

    • [X] I have searched existing GitHub issues to make sure the issue does not already exist.
    bug :bug: 
    opened by ankit27kh 6
Releases(v0.28.0)
  • v0.28.0(Dec 19, 2022)

    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.

    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()
      ...
      >>> circuit()
      tensor([0.        +0.j       , 0.33423773+0.9424888j, 
              1.        +0.j       , 0.        +0.j       ], requires_grad=True)
      
    • A new gate operation called qml.FermionicSWAP has been added. This implements the exchange of spin orbitals representing fermionic-modes while maintaining proper anti-symmetrization. (#3380)

      dev = qml.device('default.qubit', wires=2)
      
      @qml.qnode(dev)
      def circuit(phi):
          qml.BasisState(np.array([0, 1]), wires=[0, 1])
          qml.FermionicSWAP(phi, wires=[0, 1])
          return qml.state()
      
      >>> circuit(0.1)
      tensor([0.        +0.j        , 0.99750208+0.04991671j,
            0.00249792-0.04991671j, 0.        +0.j        ], requires_grad=True)
      
    • Create operators defined from a generator via qml.ops.op_math.Evolution. (#3375)

    qml.ops.op_math.Evolution defines the exponential of an operator $\hat{O}$ of the form $e^{ix\hat{O}}$, with a single trainable parameter, $x$. Limiting to a single trainable parameter allows the use of qml.gradients.param_shift to find the gradient with respect to the parameter $x$.

    dev = qml.device('default.qubit', wires=2)
    
    @qml.qnode(dev, diff_method=qml.gradients.param_shift)
    def circuit(phi):
        qml.ops.op_math.Evolution(qml.PauliX(0), -.5 * phi)
        return qml.expval(qml.PauliZ(0))
    
    >>> phi = np.array(1.2)
    >>> circuit(phi)
    tensor(0.36235775, requires_grad=True)
    >>> qml.grad(circuit)(phi)
    -0.9320390495504149
    
    • The qutrit Hadamard gate, qml.THadamard, is now available. (#3340)

    The operation accepts a subspace keyword argument which determines which variant of the qutrit Hadamard to use.

    >>> th = qml.THadamard(wires=0, subspace=[0, 1])
    >>> qml.matrix(th)
    array([[ 0.70710678+0.j,  0.70710678+0.j,  0.        +0.j],
          [ 0.70710678+0.j, -0.70710678+0.j,  0.        +0.j],
          [ 0.        +0.j,  0.        +0.j,  1.        +0.j]])
    

    New transforms, functions, and more 😯

    • Calculating the purity of arbitrary quantum states is now supported. (#3290)

    The purity can be calculated in an analogous fashion to, say, the Von Neumann entropy:

    • qml.math.purity can be used as an in-line function:

      >>> x = [1, 0, 0, 1] / np.sqrt(2)
      >>> qml.math.purity(x, [0, 1])
      1.0
      >>> qml.math.purity(x, [0])
      0.5
      
      >>> x = [[1 / 2, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 1 / 2]]
      >>> qml.math.purity(x, [0, 1])
      0.5
      
    • qml.qinfo.transforms.purity can transform a QNode returning a state to a function that returns the purity:

      dev = qml.device("default.mixed", wires=2)
      
      @qml.qnode(dev)
      def circuit(x):
          qml.IsingXX(x, wires=[0, 1])
          return qml.state()
      
      >>> qml.qinfo.transforms.purity(circuit, wires=[0])(np.pi / 2)
      0.5
      >>> qml.qinfo.transforms.purity(circuit, wires=[0, 1])(np.pi / 2)
      1.0
      

    As with the other methods in qml.qinfo, the purity is fully differentiable:

    >>> param = np.array(np.pi / 4, requires_grad=True)
    >>> qml.grad(qml.qinfo.transforms.purity(circuit, wires=[0]))(param)
    -0.5
    
    • A new gradient transform, qml.gradients.spsa_grad, that is based on the idea of SPSA is now available. (#3366)

    This new transform allows users to compute a single estimate of a quantum gradient using simultaneous perturbation of parameters and a stochastic approximation. A QNode that takes, say, an argument x, the approximate gradient can be computed as follows.

    >>> dev = qml.device("default.qubit", wires=2)
    >>> x = np.array(0.4, requires_grad=True)
    >>> @qml.qnode(dev)
    ... def circuit(x):
    ...     qml.RX(x, 0)
    ...     qml.RX(x, 1)
    ...     return qml.expval(qml.PauliZ(0))
    >>> grad_fn = qml.gradients.spsa_grad(circuit, h=0.1, num_directions=1)
    >>> grad_fn(x)
    array(-0.38876964)
    

    The argument num_directions determines how many directions of simultaneous perturbation are used, which is proportional to the number of circuit evaluations. See the SPSA gradient transform documentation for details. Note that the full SPSA optimizer is already available as qml.SPSAOptimizer.

    • Multiple mid-circuit measurements can now be combined arithmetically to create new conditionals. (#3159)

      dev = qml.device("default.qubit", wires=3)
      
      @qml.qnode(dev)
      def circuit():
          qml.Hadamard(wires=0)
          qml.Hadamard(wires=1)
          m0 = qml.measure(wires=0)
          m1 = qml.measure(wires=1)
          combined = 2 * m1 + m0
          qml.cond(combined == 2, qml.RX)(1.3, wires=2)
          return qml.probs(wires=2)
      
      >>> circuit()
      [0.90843735 0.09156265]  
      
    • A new method called pauli_decompose() has been added to the qml.pauli module, which takes a hermitian matrix, decomposes it in the Pauli basis, and returns it either as a qml.Hamiltonian or qml.PauliSentence instance. (#3384)

    • Operation or Hamiltonian instances can now be generated from a qml.PauliSentence or qml.PauliWord via the new operation() and hamiltonian() methods. (#3391)

      >>> pw = qml.pauli.PauliWord({0: 'X', 1: 'Y'})
      >>> print(pw.operation())
      PauliX(wires=[0]) @ PauliY(wires=[1])
      >>> print(pw.hamiltonian())
        (1) [X0 Y1]
      >>> ps = qml.pauli.PauliSentence({pw: -1.23})
      >>> print(ps.operation())
      -1.23*(PauliX(wires=[0]) @ PauliY(wires=[1]))
      >>> print(ps.hamiltonian())
        (-1.23) [X0 Y1]
      
    • A sum_expand function has been added for tapes, which splits a tape measuring a Sum expectation into mutliple tapes of summand expectations, and provides a function to recombine the results. (#3230)

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

    • The autograd and Tensorflow interfaces now support devices with shot vectors when qml.enable_return() has been called. (#3374) (#3400)

      Here is an example using Tensorflow:

      import tensorflow as tf
      qml.enable_return()
      
      dev = qml.device("default.qubit", wires=2, shots=[1000, 2000, 3000])
      
      @qml.qnode(dev, diff_method="parameter-shift", interface="tf")
      def circuit(a):
          qml.RY(a, wires=0)
          qml.RX(0.2, wires=0)
          qml.CNOT(wires=[0, 1])
          return qml.expval(qml.PauliZ(0)), qml.probs([0, 1])
      
      >>> a = tf.Variable(0.4)
      >>> with tf.GradientTape() as tape:
      ...     res = circuit(a)
      ...     res = tf.stack([tf.experimental.numpy.hstack(r) for r in res])
      ...
      >>> res
      <tf.Tensor: shape=(3, 5), dtype=float64, numpy=
      array([[0.902, 0.951, 0.   , 0.   , 0.049],
             [0.898, 0.949, 0.   , 0.   , 0.051],
             [0.892, 0.946, 0.   , 0.   , 0.054]])>
      >>> tape.jacobian(res, a)
      <tf.Tensor: shape=(3, 5), dtype=float64, numpy=
      array([[-0.345     , -0.1725    ,  0.        ,  0.        ,  0.1725    ],
             [-0.383     , -0.1915    ,  0.        ,  0.        ,  0.1915    ],
             [-0.38466667, -0.19233333,  0.        ,  0.        ,  0.19233333]])>
      
    • The PyTorch interface is now fully supported when qml.enable_return() has been called, allowing the calculation of the Jacobian and the Hessian using custom differentiation methods (e.g., parameter-shift, finite difference, or adjoint). (#3416)

      import torch
      
      qml.enable_return()
      
      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev, diff_method="parameter-shift", interface="torch")
      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.probs([0, 1])
      
      >>> a = torch.tensor(0.1, requires_grad=True)
      >>> b = torch.tensor(0.2, requires_grad=True)
      >>> torch.autograd.functional.jacobian(circuit, (a, b))
      ((tensor(-0.0998), tensor(0.)), (tensor([-0.0494, -0.0005,  0.0005,  0.0494]), tensor([-0.0991,  0.0991,  0.0002, -0.0002])))
      
    • The JAX-JIT interface now supports first-order gradient computation when qml.enable_return() has been called. (#3235) (#3445)

      import jax
      from jax import numpy as jnp
      
      jax.config.update("jax_enable_x64", True)
      
      qml.enable_return()
      
      dev = qml.device("lightning.qubit", wires=2)
      
      @jax.jit
      @qml.qnode(dev, interface="jax-jit", diff_method="parameter-shift")
      def circuit(a, b):
          qml.RY(a, wires=0)
          qml.RX(b, wires=0)
          return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
      
      a, b = jnp.array(1.0), jnp.array(2.0)
      
      >>> jax.jacobian(circuit, argnums=[0, 1])(a, b)
      ((Array(0.35017549, dtype=float64, weak_type=True),
      Array(-0.4912955, dtype=float64, weak_type=True)),
      (Array(5.55111512e-17, dtype=float64, weak_type=True),
      Array(0., dtype=float64, weak_type=True)))
      

    Improvements 🛠

    • qml.pauli.is_pauli_word now supports instances of qml.Hamiltonian. (#3389)

    • When qml.probs, qml.counts, and qml.sample are called with no arguments, they measure all wires. Calling any of the aforementioned measurements with an empty wire list (e.g., qml.sample(wires=[])) will raise an error. (#3299)

    • Made qml.gradients.finite_diff more convenient to use with custom data type observables/devices by reducing the number of magic methods that need to be defined in the custom data type to support finite_diff. (#3426)

    • The qml.ISWAP gate is now natively supported on default.mixed, improving on its efficiency. (#3284)

    • Added more input validation to qml.transforms.hamiltonian_expand such that Hamiltonian objects with no terms raise an error. (#3339)

    • Continuous integration checks are now performed for Python 3.11 and Torch v1.13. Python 3.7 is dropped. (#3276)

    • qml.Tracker now also logs results in tracker.history when tracking the execution of a circuit. (#3306)

    • The execution time of Wires.all_wires has been improved by avoiding data type changes and making use of itertools.chain. (#3302)

    • Printing an instance of qml.qchem.Molecule is now more concise and informational. (#3364)

    • The error message for qml.transforms.insert when it fails to diagonalize non-qubit-wise-commuting observables is now more detailed. (#3381)

    • Extended the qml.equal function to qml.Hamiltonian and Tensor objects. (#3390)

    • QuantumTape._process_queue has been moved to qml.queuing.process_queue to disentangle its functionality from the QuantumTape class. (#3401)

    • QPE can now accept a target operator instead of a matrix and target wires pair. (#3373)

    • The qml.ops.op_math.Controlled.map_wires method now uses base.map_wires internally instead of the private _wires property setter. (#3405)

    • A new function called qml.tape.make_qscript has been created for converting a quantum function into a quantum script. This replaces qml.transforms.make_tape. (#3429)

    • Add a _pauli_rep attribute to operators to integrate the new Pauli arithmetic classes with native PennyLane objects. (#3443)

    • Extended the functionality of qml.matrix to qutrits. (#3508)

    • The qcut.py file in pennylane/transforms/ has been reorganized into multiple files that are now in pennylane/transforms/qcut/. (#3413)

    • A warning now appears when creating a Tensor object with overlapping wires, informing that this can lead to undefined behaviour. (#3459)

    • Extended the qml.equal function to qml.ops.op_math.Controlled and qml.ops.op_math.ControlledOp objects. (#3463)

    • Nearly every instance of with QuantumTape() has been replaced with QuantumScript construction. (#3454)

    • Added validate_subspace static method to qml.Operator to check the validity of the subspace of certain qutrit operations. (#3340)

    • qml.equal now supports operators created via qml.s_prod, qml.pow, qml.exp, and qml.adjoint. (#3471)

    • Devices can now disregard observable grouping indices in Hamiltonians through the optional use_grouping attribute. (#3456)

    • Add the optional argument lazy=True to functions qml.s_prod, qml.prod and qml.op_sum to allow simplification. (#3483)

    • Updated the qml.transforms.zyz_decomposition function such that it now supports broadcast operators. This means that single-qubit qml.QubitUnitary operators, instantiated from a batch of unitaries, can now be decomposed. (#3477)

    • The performance of executing circuits under the jax.vmap transformation has been improved by being able to leverage the batch-execution capabilities of some devices. (#3452)

    • The tolerance for converting openfermion Hamiltonian complex coefficients to real ones has been modified to prevent conversion errors. (#3367)

    • OperationRecorder now inherits from AnnotatedQueue and QuantumScript instead of QuantumTape. (#3496)

    • Updated qml.transforms.split_non_commuting to support the new return types. (#3414)

    • Updated qml.transforms.mitigate_with_zne to support the new return types. (#3415)

    • Updated qml.transforms.metric_tensor, qml.transforms.adjoint_metric_tensor, qml.qinfo.classical_fisher, and qml.qinfo.quantum_fisher to support the new return types. (#3449)

    • Updated qml.transforms.batch_params and qml.transforms.batch_input to support the new return types. (#3431)

    • Updated qml.transforms.cut_circuit and qml.transforms.cut_circuit_mc to support the new return types. (#3346)

    • Limit NumPy version to <1.24. (#3346)

    Breaking changes 💔

    • Python 3.7 support is no longer maintained. PennyLane will be maintained for versions 3.8 and up. (#3276)

    • The log_base attribute has been moved from MeasurementProcess to the new VnEntropyMP and MutualInfoMP classes, which inherit from MeasurementProcess. (#3326)

    • qml.utils.decompose_hamiltonian() has been removed. Please use qml.pauli.pauli_decompose() instead. (#3384)

    • The return_type attribute of MeasurementProcess has been removed where possible. Use isinstance checks instead. (#3399)

    • Instead of having an OrderedDict attribute called _queue, AnnotatedQueue now inherits from OrderedDict and encapsulates the queue. Consequentially, this also applies to the QuantumTape class which inherits from AnnotatedQueue. (#3401)

    • The ShadowMeasurementProcess class has been renamed to ClassicalShadowMP. (#3388)

    • The qml.Operation.get_parameter_shift method has been removed. The gradients module should be used for general parameter-shift rules instead. (#3419)

    • The signature of the QubitDevice.statistics method has been changed from

      def statistics(self, observables, shot_range=None, bin_size=None, circuit=None):
      

      to

      def statistics(self, circuit: QuantumTape, shot_range=None, bin_size=None):
      

    (#3421)

    • The MeasurementProcess class is now an abstract class and return_type is now a property of the class. (#3434)

    Deprecations 👋

    Deprecations cycles are tracked at doc/developement/deprecations.rst.

    • The following methods are deprecated: (#3281)

      • qml.tape.get_active_tape: Use qml.QueuingManager.active_context() instead
      • qml.transforms.qcut.remap_tape_wires: Use qml.map_wires instead
      • qml.tape.QuantumTape.inv(): Use qml.tape.QuantumTape.adjoint() instead
      • qml.tape.stop_recording(): Use qml.QueuingManager.stop_recording() instead
      • qml.tape.QuantumTape.stop_recording(): Use qml.QueuingManager.stop_recording() instead
      • qml.QueuingContext is now qml.QueuingManager
      • QueuingManager.safe_update_info and AnnotatedQueue.safe_update_info: Use update_info instead.
    • qml.transforms.measurement_grouping has been deprecated. Use qml.transforms.hamiltonian_expand instead. (#3417)

    • The observables argument in QubitDevice.statistics is deprecated. Please use circuit instead. (#3433)

    • The seed_recipes argument in qml.classical_shadow and qml.shadow_expval is deprecated. A new argument seed has been added, which defaults to None and can contain an integer with the wanted seed. (#3388)

    • qml.transforms.make_tape has been deprecated. Please use qml.tape.make_qscript instead. (#3478)

    Documentation 📝

    • Added documentation on parameter broadcasting regarding both its usage and technical aspects. (#3356)

      The quickstart guide on circuits as well as the the documentation of QNodes and Operators now contain introductions and details on parameter broadcasting. The QNode documentation mostly contains usage details, the Operator documentation is concerned with implementation details and a guide to support broadcasting in custom operators.

    • The return type statements of gradient and Hessian transforms and a series of other functions that are a batch_transform have been corrected. (#3476)

    • Developer documentation for the queuing module has been added. (#3268)

    • More mentions of diagonalizing gates for all relevant operations have been corrected. (#3409)

      The docstrings for compute_eigvals used to say that the diagonalizing gates implemented $U$, the unitary such that $O = U \Sigma U^{\dagger}$, where $O$ is the original observable and $\Sigma$ a diagonal matrix. However, the diagonalizing gates actually implement $U^{\dagger}$, since $\langle \psi | O | \psi \rangle = \langle \psi | U \Sigma U^{\dagger} | \psi \rangle$, making $U^{\dagger} | \psi \rangle$ the actual state being measured in the $Z$-basis.

    • A warning about using dill to pickle and unpickle datasets has been added. (#3505)

    Bug fixes 🐛

    • Fixed a bug that prevented qml.gradients.param_shift from being used for broadcasted tapes. (#3528)

    • Fixed a bug where qml.transforms.hamiltonian_expand didn't preserve the type of the input results in its output. (#3339)

    • Fixed a bug that made qml.gradients.param_shift raise an error when used with unshifted terms only in a custom recipe, and when using any unshifted terms at all under the new return type system. (#3177)

    • The original tape _obs_sharing_wires attribute is updated during its expansion. (#3293)

    • An issue with drain=False in the adaptive optimizer has been fixed. Before the fix, the operator pool needed to be reconstructed inside the optimization pool when drain=False. With this fix, this reconstruction is no longer needed. (#3361)

    • If the device originally has no shots but finite shots are dynamically specified, Hamiltonian expansion now occurs. (#3369)

    • qml.matrix(op) now fails if the operator truly has no matrix (e.g., qml.Barrier) to match op.matrix(). (#3386)

    • The pad_with argument in the qml.AmplitudeEmbedding template is now compatible with all interfaces. (#3392)

    • Operator.pow now queues its constituents by default. (#3373)

    • Fixed a bug where a QNode returning qml.sample would produce incorrect results when run on a device defined with a shot vector. (#3422)

    • The qml.data module now works as expected on Windows. (#3504)

    Contributors ✍️

    This release contains contributions from (in alphabetical order):

    Guillermo Alonso, Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Thomas Bromley, Astral Cai, Albert Mitjans Coma, Ahmed Darwish, Isaac De Vlugt, Olivia Di Matteo, Amintor Dusko, Pieter Eendebak, Lillian M. A. Frederiksen, Diego Guala, Katharine Hyatt, Josh Izaac, Soran Jahangiri, Edward Jiang, Korbinian Kottmann, Christina Lee, Romain Moyard, Lee James O'Riordan, Mudit Pandey, Kevin Shen, Matthew Silverman, Jay Soni, Antal Száva, David Wierichs, Moritz Willmann, and Filippo Vicentini.

    Source code(tar.gz)
    Source code(zip)
  • v0.27.0(Nov 14, 2022)

    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.

    Source code(tar.gz)
    Source code(zip)
  • v0.26.0-postfix-1(Sep 26, 2022)

  • v0.26.0(Sep 19, 2022)

    New features since last release

    Classical shadows 👤

    • PennyLane now provides built-in support for implementing the classical-shadows measurement protocol. (#2820) (#2821) (#2871) (#2968) (#2959) (#2968)

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

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

        dev = qml.device("default.qubit", wires=2, shots=3)
        
        @qml.qnode(dev)
        def circuit():
            qml.Hadamard(wires=0)
            qml.CNOT(wires=[0, 1])
            return qml.classical_shadow(wires=[0, 1])
        
        >>> bits, recipes = circuit()
        >>> bits
        tensor([[0, 0],
                [1, 0],
                [0, 1]], dtype=uint8, requires_grad=True)
        >>> recipes
        tensor([[2, 2],
                [0, 2],
                [0, 2]], dtype=uint8, requires_grad=True)
        
      • QNodes returning qml.shadow_expval() yield the expectation value estimation using classical shadows:

        dev = qml.device("default.qubit", wires=range(2), shots=10000)
        
        @qml.qnode(dev)
        def circuit(x, H):
            qml.Hadamard(0)
            qml.CNOT((0,1))
            qml.RX(x, wires=0)
            return qml.shadow_expval(H)
        
        x = np.array(0.5, requires_grad=True) 
        H = qml.Hamiltonian(
                [1., 1.], 
                [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliX(0) @ qml.PauliX(1)]
            )  
        
        >>> circuit(x, H)
        tensor(1.8486, requires_grad=True) 
        >>> qml.grad(circuit)(x, H)
        -0.4797000000000001
        

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

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

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

    Qutrits: quantum circuits for tertiary degrees of freedom ☘️

    • An entirely new framework for quantum computing is now simulatable with the addition of qutrit functionalities. (#2699) (#2781) (#2782) (#2783) (#2784) (#2841) (#2843)

      Qutrits are like qubits, but instead live in a three-dimensional Hilbert space; they are not binary degrees of freedom, they are tertiary. The advent of qutrits allows for all sorts of interesting theoretical, practical, and algorithmic capabilities that have yet to be discovered.

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

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

      The following operations are supported on default.qutrit devices:

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

      A comprehensive example of these features is given below:

      dev = qml.device("default.qutrit", wires=1)
      
      U = np.array([
              [1, 1, 1], 
              [1, 1, 1], 
              [1, 1, 1]
          ]
      ) / np.sqrt(3) 
      
      obs = np.array([
              [1, 1, 0], 
              [1, -1, 0], 
              [0, 0, np.sqrt(2)]
          ]
      ) / np.sqrt(2)
      
      @qml.qnode(dev)
      def qutrit_state(U, obs):
          qml.TShift(0)
          qml.TClock(0)
          qml.QutritUnitary(U, wires=0)
          return qml.state()
      
      @qml.qnode(dev)
      def qutrit_expval(U, obs):
          qml.TShift(0)
          qml.TClock(0)
          qml.QutritUnitary(U, wires=0)
          return qml.expval(qml.THermitian(obs, wires=0))
      
      >>> qutrit_state(U, obs)
      tensor([-0.28867513+0.5j, -0.28867513+0.5j, -0.28867513+0.5j], requires_grad=True) 
      >>> qutrit_expval(U, obs)
      tensor(0.80473785, requires_grad=True)
      

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

    Simplifying just got... simpler 😌

    • The qml.simplify() function has several intuitive improvements with this release. (#2978) (#2982) (#2922) (#3012)

      qml.simplify can now perform the following:

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

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

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

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

      dev = qml.device("default.qubit", wires=2)
      @qml.simplify
      @qml.qnode(dev)
      def circuit():
          qml.adjoint(qml.prod(qml.RX(1, 0) ** 1, qml.RY(1, 0), qml.RZ(1, 0)))
          return qml.probs(wires=0)
      
      >>> circuit()
      >>> list(circuit.tape)
      [RZ(11.566370614359172, wires=[0]) @ RY(11.566370614359172, wires=[0]) @ RX(11.566370614359172, wires=[0]),
       probs(wires=[0])]
      

    QNSPSA optimizer 💪

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

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

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

      Use qml.QNSPSAOptimizer like you would any other optimizer:

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

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

    Operator and parameter broadcasting supplements 📈

    • Operator methods for exponentiation and raising to a power have been added. (#2799) (#3029)

      • The qml.exp function can be used to create observables or generic rotation gates:

        >>> x = 1.234
        >>> t = qml.PauliX(0) @ qml.PauliX(1) + qml.PauliY(0) @ qml.PauliY(1)
        >>> isingxy = qml.exp(t, 0.25j * x)
        >>> isingxy.matrix()
        array([[1.       +0.j        , 0.       +0.j        ,
            1.       +0.j        , 0.       +0.j        ],
           [0.       +0.j        , 0.8156179+0.j        ,
            1.       +0.57859091j, 0.       +0.j        ],
           [0.       +0.j        , 0.       +0.57859091j,
            0.8156179+0.j        , 0.       +0.j        ],
           [0.       +0.j        , 0.       +0.j        ,
            1.       +0.j        , 1.       +0.j        ]]) 
        
      • The qml.pow function raises a given operator to a power:

        >>> op = qml.pow(qml.PauliX(0), 2)
        >>> op.matrix()
        array([[1, 0], [0, 1]])
        
    • An operator called qml.PSWAP is now available. (#2667)

      The qml.PSWAP gate -- or phase-SWAP gate -- was previously available within the PennyLane-Braket plugin only. Enjoy it natively in PennyLane with v0.26.

    • Check whether or not an operator is hermitian or unitary with qml.is_hermitian and qml.is_unitary. (#2960)

      >>> op1 = qml.PauliX(wires=0)
      >>> qml.is_hermitian(op1)
      True
      >>> op2 = qml.PauliX(0) + qml.RX(np.pi/3, 0) 
      >>> qml.is_unitary(op2)
      False
      
    • Embedding templates now support parameter broadcasting. (#2810)

      Embedding templates like AmplitudeEmbedding or IQPEmbedding now support parameter broadcasting with a leading broadcasting dimension in their variational parameters. AmplitudeEmbedding, for example, would usually use a one-dimensional input vector of features. With broadcasting, we can now compute

      >>> features = np.array([
      ...     [0.5, 0.5, 0., 0., 0.5, 0., 0.5, 0.],
      ...     [1., 0., 0., 0., 0., 0., 0., 0.],
      ...     [0.5, 0.5, 0., 0., 0., 0., 0.5, 0.5],
      ... ])
      >>> op = qml.AmplitudeEmbedding(features, wires=[1, 5, 2])
      >>> op.batch_size
      3
      

      An exception is BasisEmbedding, which is not broadcastable.

    Improvements

    • The qml.math.expand_matrix() method now allows the sparse matrix representation of an operator to be extended to a larger hilbert space. (#2998)

      >>> from scipy import sparse
      >>> mat = sparse.csr_matrix([[0, 1], [1, 0]])
      >>> qml.math.expand_matrix(mat, wires=[1], wire_order=[0,1]).toarray()
      array([[0., 1., 0., 0.],
             [1., 0., 0., 0.],
             [0., 0., 0., 1.],
             [0., 0., 1., 0.]])
      
    • qml.ctrl now uses Controlled instead of ControlledOperation. The new Controlled class wraps individual Operator's instead of a tape. It provides improved representations and integration. (#2990)

    • qml.matrix can now compute the matrix of tapes and QNodes that contain multiple broadcasted operations or non-broadcasted operations after broadcasted ones. (#3025)

      A common scenario in which this becomes relevant is the decomposition of broadcasted operations: the decomposition in general will contain one or multiple broadcasted operations as well as operations with no or fixed parameters that are not broadcasted.

    • Lists of operators are now internally sorted by their respective wires while also taking into account their commutativity property.(#2995)

    • Some methods of the QuantumTape class have been simplified and reordered to improve both readability and performance. (#2963)

    • The qml.qchem.molecular_hamiltonian function is modified to support observable grouping. (#2997)

    • qml.ops.op_math.Controlled now has basic decomposition functionality. (#2938)

    • Automatic circuit cutting has been improved by making better partition imbalance derivations. Now it is more likely to generate optimal cuts for larger circuits. (#2517)

    • By default, qml.counts only returns the outcomes observed in sampling. Optionally, specifying qml.counts(all_outcomes=True) will return a dictionary containing all possible outcomes. (#2889)

      >>> dev = qml.device("default.qubit", wires=2, shots=1000)
      >>>
      >>> @qml.qnode(dev)
      >>> def circuit():
      ...     qml.Hadamard(wires=0)
      ...     qml.CNOT(wires=[0, 1])
      ...     return qml.counts(all_outcomes=True)
      >>> result = circuit()
      >>> result
      {'00': 495, '01': 0, '10': 0,  '11': 505}
      
    • Internal use of in-place inversion is eliminated in preparation for its deprecation. (#2965)

    • Controlled operators now work with qml.is_commuting. (#2994)

    • qml.prod and qml.op_sum now support the sparse_matrix() method. (#3006)

      >>> xy = qml.prod(qml.PauliX(1), qml.PauliY(1))
      >>> op = qml.op_sum(xy, qml.Identity(0))
      >>>
      >>> sparse_mat = op.sparse_matrix(wire_order=[0,1])
      >>> type(sparse_mat)
      <class 'scipy.sparse.csr.csr_matrix'>
      >>> sparse_mat.toarray()
      [[1.+1.j 0.+0.j 0.+0.j 0.+0.j]
      [0.+0.j 1.-1.j 0.+0.j 0.+0.j]
      [0.+0.j 0.+0.j 1.+1.j 0.+0.j]
      [0.+0.j 0.+0.j 0.+0.j 1.-1.j]]
      
    • Provided sparse_matrix() support for single qubit observables. (#2964)

    • qml.Barrier with only_visual=True now simplifies via op.simplify() to the identity operator or a product of identity operators.(#3016)

    • More accurate and intuitive outputs for printing some operators have been added. (#3013)

    • Results for the matrix of the sum or product of operators are stored in a more efficient manner. (#3022)

    • The computation of the (sparse) matrix for the sum or product of operators is now more efficient. (#3030)

    • When the factors of qml.prod don't share any wires, the matrix and sparse matrix are computed using a kronecker product for improved efficiency. (#3040)

    • qml.grouping.is_pauli_word now returns False for operators that don't inherit from qml.Observable instead of raising an error. (#3039)

    • Added functionality to iterate over operators created from qml.op_sum and qml.prod. (#3028)

      >>> op = qml.op_sum(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2))
      >>> len(op)
      3
      >>> op[1]
      PauliY(wires=[1])
      >>> [o.name for o in op]
      ['PauliX', 'PauliY', 'PauliZ']
      

    Deprecations

    • In-place inversion is now deprecated. This includes op.inv() and op.inverse=value. Please use qml.adjoint or qml.pow instead. Support for these methods will remain till v0.28. (#2988)

      Don't use:

      >>> v1 = qml.PauliX(0).inv()
      >>> v2 = qml.PauliX(0)
      >>> v2.inverse = True
      

      Instead use:

      >>> qml.adjoint(qml.PauliX(0))
      Adjoint(PauliX(wires=[0]))
      >>> qml.pow(qml.PauliX(0), -1)
      PauliX(wires=[0])**-1
      >>> qml.pow(qml.PauliX(0), -1, lazy=False)
      PauliX(wires=[0])
      >>> qml.PauliX(0) ** -1
      PauliX(wires=[0])**-1
      

      qml.adjoint takes the conjugate transpose of an operator, while qml.pow(op, -1) indicates matrix inversion. For unitary operators, adjoint will be more efficient than qml.pow(op, -1), even though they represent the same thing.

    • The supports_reversible_diff device capability is unused and has been removed. (#2993)

    Breaking changes

    • Measuring an operator that might not be hermitian now raises a warning instead of an error. To definitively determine whether or not an operator is hermitian, use qml.is_hermitian. (#2960)

    • The ControlledOperation class has been removed. This was a developer-only class, so the change should not be evident to any users. It is replaced by Controlled. (#2990)

    • The default execute method for the QubitDevice base class now calls self.statistics with an additional keyword argument circuit, which represents the quantum tape being executed. Any device that overrides statistics should edit the signature of the method to include the new circuit keyword argument. (#2820)

    • The expand_matrix() has been moved from pennylane.operation to pennylane.math.matrix_manipulation. (#3008)

    • qml.grouping.utils.is_commuting has been removed, and its Pauli word logic is now part of qml.is_commuting. (#3033)

    • qml.is_commuting has been moved from pennylane.transforms.commutation_dag to pennylane.ops.functions. (#2991)

    Documentation

    • Updated the Fourier transform docs to use circuit_spectrum instead of spectrum, which has been deprecated. (#3018)

    • Corrected the docstrings for diagonalizing gates for all relevant operations. The docstrings used to say that the diagonalizing gates implemented $U$, the unitary such that $O = U \Sigma U^{\dagger}$, where $O$ is the original observable and $\Sigma$ a diagonal matrix. However, the diagonalizing gates actually implement $U^{\dagger}$, since $\langle \psi | O | \psi \rangle = \langle \psi | U \Sigma U^{\dagger} | \psi \rangle$, making $U^{\dagger} | \psi \rangle$ the actual state being measured in the Z-basis. (#2981)

    Bug fixes

    • Fixed a bug with qml.ops.Exp operators when the coefficient is autograd but the diagonalizing gates don't act on all wires. (#3057)

    • Fixed a bug where the tape transform single_qubit_fusion computed wrong rotation angles for specific combinations of rotations. (#3024)

    • Jax gradients now work with a QNode when the quantum function was transformed by qml.simplify. (#3017)

    • Operators that have num_wires = AnyWires or num_wires = AnyWires now raise an error, with certain exceptions, when instantiated with wires=[]. (#2979)

    • Fixed a bug where printing qml.Hamiltonian with complex coefficients raises TypeError in some cases. (#3005)

    • Added a more descriptive error message when measuring non-commuting observables at the end of a circuit with probs, samples, counts and allcounts. (#3065)

    Contributors

    This release contains contributions from (in alphabetical order):

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

    Source code(tar.gz)
    Source code(zip)
  • v0.25.1(Aug 18, 2022)

    Bug fixes

    • Fixed Torch device discrepencies for certain parametrized operations by updating qml.math.array and qml.math.eye to preserve the Torch device used. (#2967)

    Contributors

    This release contains contributions from (in alphabetical order):

    Romain Moyard, Rashid N H M, Lee James O'Riordan, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.25.0(Aug 15, 2022)

    New features since last release

    Estimate computational resource requirements 🧠

    • Functionality for estimating molecular simulation computations has been added with qml.resource. (#2646) (#2653) (#2665) (#2694) (#2720) (#2723) (#2746) (#2796) (#2797) (#2874) (#2944) (#2644)

      The new resource module allows you to estimate the number of non-Clifford gates and logical qubits needed to implement quantum phase estimation algorithms for simulating materials and molecules. This includes support for quantum algorithms using first and second quantization with specific bases:

      • First quantization using a plane-wave basis via the FirstQuantization class:

        >>> n = 100000        # number of plane waves
        >>> eta = 156         # number of electrons
        >>> omega = 1145.166  # unit cell volume in atomic units
        >>> algo = FirstQuantization(n, eta, omega)
        >>> print(algo.gates, algo.qubits)
        1.10e+13, 4416
        
      • Second quantization with a double-factorized Hamiltonian via the DoubleFactorization class:

        symbols = ["O", "H", "H"]
        geometry = np.array(
            [
                [0.00000000, 0.00000000, 0.28377432],
                [0.00000000, 1.45278171, -1.00662237],
                [0.00000000, -1.45278171, -1.00662237],
            ],
            requires_grad=False,
        )
        
        mol = qml.qchem.Molecule(symbols, geometry, basis_name="sto-3g")
        core, one, two = qml.qchem.electron_integrals(mol)()
        
        algo = DoubleFactorization(one, two)
        
        >>> print(algo.gates, algo.qubits)
        103969925, 290
        

      The methods of the FirstQuantization and the DoubleFactorization classes, such as qubit_cost (number of logical qubits) and gate_cost (number of non-Clifford gates), can be also accessed as static methods:

      >>> qml.resource.FirstQuantization.qubit_cost(100000, 156, 169.69608, 0.01) 
      4377 
      >>> qml.resource.FirstQuantization.gate_cost(100000, 156, 169.69608, 0.01) 
      3676557345574
      

    Differentiable error mitigation ⚙️

    • Differentiable zero-noise-extrapolation (ZNE) error mitigation is now available. (#2757)

      Elevate any variational quantum algorithm to a mitigated algorithm with improved results on noisy hardware while maintaining differentiability throughout.

      In order to do so, use the qml.transforms.mitigate_with_zne transform on your QNode and provide the PennyLane proprietary qml.transforms.fold_global folding function and qml.transforms.poly_extrapolate extrapolation function. Here is an example for a noisy simulation device where we mitigate a QNode and are still able to compute the gradient:

      # Describe noise
      noise_gate = qml.DepolarizingChannel
      noise_strength = 0.1
      
      # Load devices
      dev_ideal = qml.device("default.mixed", wires=1)
      dev_noisy = qml.transforms.insert(noise_gate, noise_strength)(dev_ideal)
      
      scale_factors = [1, 2, 3]
      @mitigate_with_zne(
        scale_factors,
        qml.transforms.fold_global,
        qml.transforms.poly_extrapolate,
        extrapolate_kwargs={'order': 2}
      )
      @qml.qnode(dev_noisy)
      def qnode_mitigated(theta):
          qml.RY(theta, wires=0)
          return qml.expval(qml.PauliX(0))
      
      >>> theta = np.array(0.5, requires_grad=True)
      >>> qml.grad(qnode_mitigated)(theta)
      0.5712737447327619
      

    More native support for parameter broadcasting 📡

    • default.qubit now natively supports parameter broadcasting, providing increased performance when executing the same circuit at various parameter positions compared to manually looping over parameters, or directly using the qml.transforms.broadcast_expand transform. (#2627)

      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(x, wires=0)
          return qml.expval(qml.PauliZ(0))
      
      >>> circuit(np.array([0.1, 0.3, 0.2]))
      tensor([0.99500417, 0.95533649, 0.98006658], requires_grad=True) 
      

      Currently, not all templates have been updated to support broadcasting.

    • Parameter-shift gradients now allow for parameter broadcasting internally, which can result in a significant speedup when computing gradients of circuits with many parameters. (#2749)

      The gradient transform qml.gradients.param_shift now accepts the keyword argument broadcast. If set to True, broadcasting is used to compute the derivative:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(x, y):
          qml.RX(x, wires=0)
          qml.RY(y, wires=1)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      
      >>> x = np.array([np.pi/3, np.pi/2], requires_grad=True)
      >>> y = np.array([np.pi/6, np.pi/5], requires_grad=True)
      >>> qml.gradients.param_shift(circuit, broadcast=True)(x, y)
      (tensor([[-0.7795085,  0.       ],
               [ 0.       , -0.7795085]], requires_grad=True),
      tensor([[-0.125, 0.  ],
              [0.  , -0.125]], requires_grad=True))
      

      The following example highlights how to make use of broadcasting gradients at the QNode level. Internally, broadcasting is used to compute the parameter-shift rule when required, which may result in performance improvements.

      @qml.qnode(dev, diff_method="parameter-shift", broadcast=True)
      def circuit(x, y):
          qml.RX(x, wires=0)
          qml.RY(y, wires=1)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      
      >>> x = np.array(0.1, requires_grad=True)
      >>> y = np.array(0.4, requires_grad=True)
      >>> qml.grad(circuit)(x, y)
      (array(-0.09195267), array(-0.38747287))
      

      Here, only 2 circuits are created internally, rather than 4 with broadcast=False.

      To illustrate the speedup, for a constant-depth circuit with Pauli rotations and controlled Pauli rotations, the time required to compute qml.gradients.param_shift(circuit, broadcast=False)(params) ("No broadcasting") and qml.gradients.param_shift(circuit, broadcast=True)(params) ("Broadcasting") as a function of the number of qubits is given here.

    • Operations for quantum chemistry now support parameter broadcasting. (#2726)

      >>> op = qml.SingleExcitation(np.array([0.3, 1.2, -0.7]), wires=[0, 1])
      >>> op.matrix().shape
      (3, 4, 4)
      

    Intuitive operator arithmetic 🧮

    • New functionality for representing the sum, product, and scalar-product of operators is available. (#2475) (#2625) (#2622) (#2721)

      The following functionalities have been added to facilitate creating new operators whose matrix, terms, and eigenvalues can be accessed as per usual, while maintaining differentiability. Operators created from these new features can be used within QNodes as operations or as observables (where physically applicable).

      • Summing any number of operators via qml.op_sum results in a "summed" operator:

        >>> ops_to_sum = [qml.PauliX(0), qml.PauliY(1), qml.PauliZ(0)] 
        >>> summed_ops = qml.op_sum(*ops_to_sum)
        >>> summed_ops
        PauliX(wires=[0]) + PauliY(wires=[1]) + PauliZ(wires=[0])
        >>> qml.matrix(summed_ops)
        array([[ 1.+0.j,  0.-1.j,  1.+0.j,  0.+0.j],
               [ 0.+1.j,  1.+0.j,  0.+0.j,  1.+0.j],
               [ 1.+0.j,  0.+0.j, -1.+0.j,  0.-1.j],
               [ 0.+0.j,  1.+0.j,  0.+1.j, -1.+0.j]])
        >>> summed_ops.terms()
        ([1.0, 1.0, 1.0], (PauliX(wires=[0]), PauliY(wires=[1]), PauliZ(wires=[0])))
        
      • Multiplying any number of operators via qml.prod results in a "product" operator, where the matrix product or tensor product is used correspondingly:

        >>> theta = 1.23
        >>> prod_op = qml.prod(qml.PauliZ(0), qml.RX(theta, 1))
        >>> prod_op
        PauliZ(wires=[0]) @ RX(1.23, wires=[1]) 
        >>> qml.eigvals(prod_op)
        [-1.39373197 -0.23981492  0.23981492  1.39373197]
        
      • Taking the product of a coefficient and an operator via qml.s_prod produces a "scalar-product" operator:

        >>> sprod_op = qml.s_prod(2.0, qml.PauliX(0))
        >>> sprod_op
        2.0*(PauliX(wires=[0]))
        >>> sprod_op.matrix()
        array([[ 0., 2.],
               [ 2., 0.]])
        >>> sprod_op.terms()
        ([2.0], [PauliX(wires=[0])])
        

      Each of these new functionalities can be used within QNodes as operators or observables, where applicable, while also maintaining differentiability. For example:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(angles):
          qml.prod(qml.PauliZ(0), qml.RY(angles[0], 1))
          qml.op_sum(qml.PauliX(1), qml.RY(angles[1], 0))
      
          return qml.expval(qml.op_sum(qml.PauliX(0), qml.PauliZ(1)))
      
      >>> angles = np.array([1.23, 4.56], requires_grad=True)
      >>> circuit(angles)
      tensor(0.33423773, requires_grad=True)
      >>> qml.grad(circuit)(angles)
      array([-0.9424888,  0.       ])
      
    • All PennyLane operators can now be added, subtracted, multiplied, scaled, and raised to powers using +, -, @, *, **, respectively. (#2849) (#2825) (#2891)

      • You can now add scalars to operators, where the interpretation is that the scalar is a properly-sized identity matrix;

        >>> sum_op = 5 + qml.PauliX(0)
        >>> sum_op.matrix()
        array([[5., 1.],
               [1., 5.]])
        
      • The + and - operators can be used to combine all Pennylane operators:

        >>> sum_op = qml.RX(phi=1.23, wires=0) + qml.RZ(phi=3.14, wires=0) - qml.RY(phi=0.12, wires=0)
        >>> sum_op
        RX(1.23, wires=[0]) + RZ(3.14, wires=[0]) + -1*(RY(0.12, wires=[0]))
        >>> qml.matrix(sum_op)
        array([[-0.18063077-0.99999968j,  0.05996401-0.57695852j],
               [-0.05996401-0.57695852j, -0.18063077+0.99999968j]])
        

        Note that the behavior of + and - with observables is different; it still creates a Hamiltonian.

      • The * and @ operators can be used to scale and compose all PennyLane operators.

        >>> prod_op = 2*qml.RX(1, wires=0) @ qml.RY(2, wires=0)
        >>> prod_op
        2*(RX(1, wires=[0])) @ RY(2, wires=[0])
        >>> qml.matrix(prod_op)
        array([[ 0.94831976-0.80684536j, -1.47692053-0.51806945j],
               [ 1.47692053-0.51806945j,  0.94831976+0.80684536j]])
        
      • The ** operator can be used to raise PennyLane operators to a power.

        >>> exp_op = qml.RZ(1.0, wires=0) ** 2
        >>> exp_op
        RZ**2(1.0, wires=[0])
        >>> qml.matrix(exp_op)
        array([[0.54030231-0.84147098j, 0.        +0.j        ],
               [0.        +0.j        , 0.54030231+0.84147098j]])
        
    • A new class called Controlled is available in qml.ops.op_math to represent a controlled version of any operator. This will eventually be integrated into qml.ctrl to provide a performance increase and more feature coverage. (#2634)

    • Arithmetic operations can now be simplified using qml.simplify. (#2835) (#2854)

      >>> op = qml.adjoint(qml.adjoint(qml.RX(x, wires=0))) 
      >>> op 
      Adjoint(Adjoint(RX))(tensor([1.04719755, 1.57079633], requires_grad=True), wires=[0]) 
      >>> qml.simplify(op) 
      RX(tensor([1.04719755, 1.57079633], requires_grad=True), wires=[0]) 
      
    • A new function called qml.equal can be used to compare the equality of parametric operators. (#2651)

      >>> qml.equal(qml.RX(1.23, 0), qml.RX(1.23, 0))
      True
      >>> qml.equal(qml.RY(4.56, 0), qml.RY(7.89, 0))
      False
      

    Marvelous mixed state features 🙌

    • The default.mixed device now supports backpropagation with the "jax" interface, which can result in significant speedups. (#2754) (#2776)

      dev = qml.device("default.mixed", wires=2)
      
      @qml.qnode(dev, diff_method="backprop", interface="jax")
      def circuit(angles):
          qml.RX(angles[0], wires=0)
          qml.RY(angles[1], wires=1)
          return qml.expval(qml.PauliZ(0) + qml.PauliZ(1))
      
      >>> angles = np.array([np.pi/6, np.pi/5], requires_grad=True)
      >>> qml.grad(circuit)(angles)
      array([-0.8660254 , -0.25881905])
      

      Additionally, quantum channels now support Jax and TensorFlow tensors. This allows quantum channels to be used inside QNodes decorated by tf.function, jax.jit, or jax.vmap.

    • The default.mixed device now supports readout error. (#2786)

      A new keyword argument called readout_prob can be specified when creating a default.mixed device. Any circuits running on a default.mixed device with a finite readout_prob (upper-bounded by 1) will alter the measurements performed at the end of the circuit similarly to how a qml.BitFlip channel would affect circuit measurements:

      >>> dev = qml.device("default.mixed", wires=2, readout_prob=0.1)
      >>> @qml.qnode(dev)
      ... def circuit():
      ...     return qml.expval(qml.PauliZ(0))
      >>> circuit()
      array(0.8)
      

    Relative entropy is now available in qml.qinfo 💥

    • The quantum information module now supports computation of relative entropy. (#2772)

      We've enabled two cases for calculating the relative entropy:

      • A QNode transform via qml.qinfo.relative_entropy:

        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()
        
        >>> relative_entropy_circuit = qml.qinfo.relative_entropy(circuit, circuit, wires0=[0], wires1=[0])
        >>> x, y = np.array(0.4), np.array(0.6)
        >>> relative_entropy_circuit((x,), (y,))
        0.017750012490703237
        
      • Support in qml.math for flexible post-processing:

        >>> rho = np.array([[0.3, 0], [0, 0.7]])
        >>> sigma = np.array([[0.5, 0], [0, 0.5]])
        >>> qml.math.relative_entropy(rho, sigma)
        tensor(0.08228288, requires_grad=True)
        

    New measurements, operators, and more! ✨

    • A new measurement called qml.counts is available. (#2686) (#2839) (#2876)

      QNodes with shots != None that return qml.counts will yield a dictionary whose keys are bitstrings representing computational basis states that were measured, and whose values are the corresponding counts (i.e., how many times that computational basis state was measured):

      dev = qml.device("default.qubit", wires=2, shots=1000)
      
      @qml.qnode(dev)
      def circuit():
          qml.Hadamard(wires=0)
          qml.CNOT(wires=[0, 1])
          return qml.counts()
      
      >>> circuit()
      {'00': 495, '11': 505}
      

      qml.counts can also accept observables, where the resulting dictionary is ordered by the eigenvalues of the observable.

      dev = qml.device("default.qubit", wires=2, shots=1000)
      
      @qml.qnode(dev)
      def circuit():
          qml.Hadamard(wires=0)
          qml.CNOT(wires=[0, 1])
          return qml.counts(qml.PauliZ(0)), qml.counts(qml.PauliZ(1))
      
      >>> circuit()
      ({-1: 470, 1: 530}, {-1: 470, 1: 530})
      
    • A new experimental return type for QNodes with multiple measurements has been added. (#2814) (#2815) (#2860)

      QNodes returning a list or tuple of different measurements return an intuitive data structure via qml.enable_return(), where the individual measurements are separated into their own tensors:

      qml.enable_return()
      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(x):
          qml.Hadamard(wires=[0])
          qml.CRX(x, wires=[0, 1])
          return (qml.probs(wires=[0]), qml.vn_entropy(wires=[0]), qml.probs(wires=0), qml.expval(wires=1))
      
      >>> circuit(0.5)
      (tensor([0.5, 0.5], requires_grad=True), tensor(0.08014815, requires_grad=True), tensor([0.5, 0.5], requires_grad=True), tensor(0.93879128, requires_grad=True))
      

      In addition, QNodes that utilize this new return type support backpropagation. This new return type can be disabled thereafter via qml.disable_return().

    • An operator called qml.FlipSign is now available. (#2780)

      Mathematically, qml.FlipSign functions as follows: $\text{FlipSign}(n) \vert m \rangle = (-1)^\delta_{n,m} \vert m \rangle$, where $\vert m \rangle$ is an arbitrary qubit state and $n$ is a qubit configuration:

      basis_state = [0, 1]
      
      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit():
        for wire in list(range(2)):
              qml.Hadamard(wires = wire)
        qml.FlipSign(basis_state, wires = list(range(2)))
        return qml.state()
      
      >>> circuit()
      tensor([ 0.5+0.j  -0.5+0.j 0.5+0.j  0.5+0.j], requires_grad=True)
      
    • The simultaneous perturbation stochastic approximation (SPSA) optimizer is available via qml.SPSAOptimizer. (#2661)

      The SPSA optimizer is suitable for cost functions whose evaluation may involve noise. Use the SPSA optimizer like you would any other optimizer:

      max_iterations = 50
      opt = qml.SPSAOptimizer(maxiter=max_iterations) 
      
      for _ in range(max_iterations):
          params, cost = opt.step_and_cost(cost, params)
      

    More drawing styles 🎨

    • New PennyLane-inspired sketch and sketch_dark styles are now available for drawing circuit diagram graphics. (#2709)

    Improvements 📈

    • default.qubit now natively executes any operation that defines a matrix except for trainable Pow operations. (#2836)

    • Added expm to the qml.math module for matrix exponentiation. (#2890)

    • When adjoint differentiation is requested, circuits are now decomposed so that all trainable operations have a generator. (#2836)

    • A warning is now emitted for qml.state, qml.density_matrix, qml.vn_entropy, and qml.mutual_info when using a device with finite shots or a shot list since these measurements are always analytic. (#2918)

    • The efficiency of the Hartree-Fock workflow has been improved by removing repetitive steps. (#2850)

    • The coefficients of the non-differentiable molecular Hamiltonians generated with openfermion now have requires_grad = False by default. (#2865)

    • Upgraded performance of the compute_matrix method of broadcastable parametric operations. (#2759)

    • Jacobians are now cached with the Autograd interface when using the parameter-shift rule. (#2645)

    • The qml.state and qml.density_matrix measurements now support custom wire labels. (#2779)

    • Add trivial behaviour logic to qml.operation.expand_matrix. (#2785)

    • Added an are_pauli_words_qwc function which checks if certain Pauli words are pairwise qubit-wise commuting. This new function improves performance when measuring hamiltonians with many commuting terms. (#2789)

    • Adjoint differentiation now uses the adjoint symbolic wrapper instead of in-place inversion. (#2855)

    Breaking changes 💔

    • The deprecated qml.hf module is removed. Users with code that calls qml.hf can simply replace qml.hf with qml.qchem in most cases, or refer to the qchem documentation and demos for more information. (#2795)

    • default.qubit now uses stopping_condition to specify support for anything with a matrix. To override this behavior in inheriting devices and to support only a specific subset of operations, developers need to override stopping_condition. (#2836)

    • Custom devices inheriting from DefaultQubit or QubitDevice can break due to the introduction of parameter broadcasting. (#2627)

      A custom device should only break if all three following statements hold simultaneously:

      1. The custom device inherits from DefaultQubit, not QubitDevice.
      2. The device implements custom methods in the simulation pipeline that are incompatible with broadcasting (for example expval, apply_operation or analytic_probability).
      3. The custom device maintains the flag "supports_broadcasting": True in its capabilities dictionary or it overwrites Device.batch_transform without applying broadcast_expand (or both).

      The capabilities["supports_broadcasting"] is set to True for DefaultQubit. Typically, the easiest fix will be to change capabilities["supports_broadcasting"] flag to False for the child device and/or to include a call to broadcast_expand in CustomDevice.batch_transform, similar to how Device.batch_transform calls it.

      Separately from the above, custom devices that inherit from QubitDevice and implement a custom _gather method need to allow for the kwarg axis to be passed to this _gather method.

    • The argument argnum of the function qml.batch_input has been redefined: now it indicates the indices of the batched parameters, which need to be non-trainable, in the quantum tape. Consequently, its default value (set to 0) has been removed. (#2873)

      Before this breaking change, one could call qml.batch_input without any arguments when using batched inputs as the first argument of the quantum circuit.

      dev = qml.device("default.qubit", wires=2, shots=None)
      
      @qml.batch_input()  # argnum = 0
      @qml.qnode(dev, diff_method="parameter-shift", interface="tf")
      def circuit(inputs, weights):  # argument `inputs` is batched
          qml.RY(weights[0], wires=0)
          qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
          qml.RY(weights[1], wires=1)
          return qml.expval(qml.PauliZ(1))
      

      With this breaking change, users must set a value to argnum specifying the index of the batched inputs with respect to all quantum tape parameters. In this example the quantum tape parameters are [ weights[0], inputs, weights[1] ], thus argnum should be set to 1, specifying that inputs is batched:

      dev = qml.device("default.qubit", wires=2, shots=None)
      
      @qml.batch_input(argnum=1)
      @qml.qnode(dev, diff_method="parameter-shift", interface="tf")
      def circuit(inputs, weights):
          qml.RY(weights[0], wires=0)
          qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
          qml.RY(weights[1], wires=1)
          return qml.expval(qml.PauliZ(1))
      
    • PennyLane now depends on newer versions (>=2.7) of the semantic_version package, which provides an updated API that is incompatible which versions of the package prior to 2.7. If you run into issues relating to this package, please reinstall PennyLane. (#2744) (#2767)

    Documentation 📕

    • Added a dedicated docstring for the QubitDevice.sample method. (#2812)

    • Optimization examples of using JAXopt and Optax with the JAX interface have been added. (#2769)

    • Updated IsingXY gate docstring. (#2858)

    Bug fixes 🐞

    • Fixes qml.equal so that operators with different inverse properties are not equal. (#2947)

    • Cleans up interactions between operator arithmetic and batching by testing supported cases and adding errors when batching is not supported. (#2900)

    • Fixed a bug where the parameter-shift rule wasn't defined for qml.kUpCCGSD. (#2913)

    • Reworked the Hermiticity check in qml.Hermitian by using qml.math calls because calling .conj() on an EagerTensor from TensorFlow raised an error. (#2895)

    • Fixed a bug where the parameter-shift gradient breaks when using both custom grad_recipes that contain unshifted terms and recipes that do not contain any unshifted terms. (#2834)

    • Fixed mixed CPU-GPU data-locality issues for the Torch interface. (#2830)

    • Fixed a bug where the parameter-shift Hessian of circuits with untrainable parameters might be computed with respect to the wrong parameters or might raise an error. (#2822)

    • Fixed a bug where the custom implementation of the states_to_binary device method was not used. (#2809)

    • qml.grouping.group_observables now works when individual wire labels are iterable. (#2752)

    • The adjoint of an adjoint now has a correct expand result. (#2766)

    • Fixed the ability to return custom objects as the expectation value of a QNode with the Autograd interface. (#2808)

    • The WireCut operator now raises an error when instantiating it with an empty list. (#2826)

    • Hamiltonians with grouped observables are now allowed to be measured on devices which were transformed using qml.transform.insert(). (#2857)

    • Fixed a bug where qml.batch_input raised an error when using a batched operator that was not located at the beginning of the circuit. In addition, now qml.batch_input raises an error when using trainable batched inputs, which avoids an unwanted behaviour with duplicated parameters. (#2873)

    • Calling qml.equal with nested operators now raises a NotImplementedError. (#2877)

    • Fixed a bug where a non-sensible error message was raised when using qml.counts with shots=False. (#2928)

    • Fixed a bug where no error was raised and a wrong value was returned when using qml.counts with another non-commuting observable. (#2928)

    • Operator Arithmetic now allows Hamiltonian objects to be used and produces correct matrices. (#2957)

    Contributors

    This release contains contributions from (in alphabetical order):

    Juan Miguel Arrazola, Utkarsh Azad, Samuel Banning, Prajwal Borkar, Isaac De Vlugt, Olivia Di Matteo, Kristiyan Dilov, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Meenu Kumari, Christina Lee, Sergio Martínez-Losa, Albert Mitjans Coma, Ixchel Meza Chavez, Romain Moyard, Lee James O'Riordan, Mudit Pandey, Bogdan Reznychenko, Shuli Shu, Jay Soni, Modjtaba Shokrian-Zini, Antal Száva, David Wierichs, Moritz Willmann.

    Source code(tar.gz)
    Source code(zip)
  • v0.24.0(Jun 20, 2022)

    New features since last release

    All new quantum information quantities 📏

    • Functionality for computing quantum information quantities for QNodes has been added. (#2554) (#2569) (#2598) (#2617) (#2631) (#2640) (#2663) (#2684) (#2688) (#2695) (#2710) (#2712)

      This includes two new QNode measurements:

      • The Von Neumann entropy via qml.vn_entropy:

        >>> dev = qml.device("default.qubit", wires=2)
        >>> @qml.qnode(dev)
        ... def circuit_entropy(x):
        ...     qml.IsingXX(x, wires=[0,1])
        ...     return qml.vn_entropy(wires=[0], log_base=2)
        >>> circuit_entropy(np.pi/2)
        1.0
        
      • The mutual information via qml.mutual_info:

        >>> dev = qml.device("default.qubit", wires=2)
        >>> @qml.qnode(dev)
        ... def circuit(x):
        ...     qml.IsingXX(x, wires=[0,1])
        ...     return qml.mutual_info(wires0=[0], wires1=[1], log_base=2)
        >>> circuit(np.pi/2)
        2.0
        

      New differentiable transforms are also available in the qml.qinfo module:

      • The classical and quantum Fisher information via qml.qinfo.classical_fisher, qml.qinfo.quantum_fisher, respectively:

        dev = qml.device("default.qubit", wires=3)
        
        @qml.qnode(dev)
        def circ(params):
            qml.RY(params[0], wires=1)
            qml.CNOT(wires=(1,0))
            qml.RY(params[1], wires=1)
            qml.RZ(params[2], wires=1)
            return qml.expval(qml.PauliX(0) @ qml.PauliX(1) - 0.5 * qml.PauliZ(1))
        
        params = np.array([0.5, 1., 0.2], requires_grad=True)
        cfim = qml.qinfo.classical_fisher(circ)(params)
        qfim = qml.qinfo.quantum_fisher(circ)(params)
        

        These quantities are typically employed in variational optimization schemes to tilt the gradient in a more favourable direction --- producing what is known as the natural gradient. For example:

        >>> grad = qml.grad(circ)(params)
        >>> cfim @ grad  # natural gradient
        [ 5.94225615e-01 -2.61509542e-02 -1.18674655e-18]
        >>> qfim @ grad  # quantum natural gradient
        [ 0.59422561 -0.02615095 -0.03989212]
        
      • The fidelity between two arbitrary states via qml.qinfo.fidelity:

        dev = qml.device('default.qubit', wires=1)
        
        @qml.qnode(dev)
        def circuit_rx(x):
            qml.RX(x[0], wires=0)
            qml.RZ(x[1], wires=0)
            return qml.state()
        
        @qml.qnode(dev)
        def circuit_ry(y):
            qml.RY(y, wires=0)
            return qml.state()
        
        >>> x = np.array([0.1, 0.3], requires_grad=True)
        >>> y = np.array(0.2, requires_grad=True) 
        >>> fid_func = qml.qinfo.fidelity(circuit_rx, circuit_ry, wires0=[0], wires1=[0])
        >>> fid_func(x, y)
        0.9905158135644924
        >>> df = qml.grad(fid_func)
        >>> df(x, y)
        (array([-0.04768725, -0.29183666]), array(-0.09489803))
        
      • Reduced density matrices of arbitrary states via qml.qinfo.reduced_dm:

        dev = qml.device("default.qubit", wires=2)
        @qml.qnode(dev)
        def circuit(x):
            qml.IsingXX(x, wires=[0,1])
            return qml.state()
        
        >>> qml.qinfo.reduced_dm(circuit, wires=[0])(np.pi/2)
        [[0.5+0.j 0.+0.j]
          [0.+0.j 0.5+0.j]]
        
      • Similar transforms, qml.qinfo.vn_entropy and qml.qinfo.mutual_info exist for transforming QNodes.

      Currently, all quantum information measurements and transforms are differentiable, but only support statevector devices, with hardware support to come in a future release (with the exception of qml.qinfo.classical_fisher and qml.qinfo.quantum_fisher, which are both hardware compatible).

      For more information, check out the new qinfo module and measurements page.

    • In addition to the QNode transforms and measurements above, functions for computing and differentiating quantum information metrics with numerical statevectors and density matrices have been added to the qml.math module. This enables flexible custom post-processing.

      Added functions include:

      • qml.math.reduced_dm
      • qml.math.vn_entropy
      • qml.math.mutual_info
      • qml.math.fidelity

      For example:

      >>> x = torch.tensor([1.0, 0.0, 0.0, 1.0], requires_grad=True)
      >>> en = qml.math.vn_entropy(x / np.sqrt(2.), indices=[0])
      >>> en
      tensor(0.6931, dtype=torch.float64, grad_fn=<DivBackward0>)
      >>> en.backward()
      >>> x.grad
      tensor([-0.3069,  0.0000,  0.0000, -0.3069])
      

    Faster mixed-state training with backpropagation 📉

    • The default.mixed device now supports differentiation via backpropagation with the Autograd, TensorFlow, and PyTorch (CPU) interfaces, leading to significantly more performant optimization and training. (#2615) (#2670) (#2680)

      As a result, the default differentiation method for the device is now "backprop". To continue using the old default "parameter-shift", explicitly specify this differentiation method in the QNode:

      dev = qml.device("default.mixed", wires=2)
      
      @qml.qnode(dev, interface="autograd", diff_method="backprop")
      def circuit(x):
          qml.RY(x, wires=0)
          qml.CNOT(wires=[0, 1])
          return qml.expval(qml.PauliZ(wires=1))
      
      >>> x = np.array(0.5, requires_grad=True)
      >>> circuit(x)
      array(0.87758256)
      >>> qml.grad(circuit)(x)
      -0.479425538604203
      

    Support for quantum parameter broadcasting 📡

    • Quantum operators, functions, and tapes now support broadcasting across parameter dimensions, making it more convenient for developers to execute their PennyLane programs with multiple sets of parameters. (#2575) (#2609)

      Parameter broadcasting refers to passing tensor parameters with additional leading dimensions to quantum operators; additional dimensions will flow through the computation, and produce additional dimensions at the output.

      For example, instantiating a rotation gate with a one-dimensional array leads to a broadcasted Operation:

      >>> x = np.array([0.1, 0.2, 0.3], requires_grad=True)
      >>> op = qml.RX(x, 0)
      >>> op.batch_size
      3
      

      Its matrix correspondingly is augmented by a leading dimension of size batch_size:

      >>> np.round(qml.matrix(op), 4)
      tensor([[[0.9988+0.j    , 0.    -0.05j  ],
             [0.    -0.05j  , 0.9988+0.j    ]],
            [[0.995 +0.j    , 0.    -0.0998j],
             [0.    -0.0998j, 0.995 +0.j    ]],
            [[0.9888+0.j    , 0.    -0.1494j],
             [0.    -0.1494j, 0.9888+0.j    ]]], requires_grad=True)
      >>> qml.matrix(op).shape
      (3, 2, 2)
      

      This can be extended to quantum functions, where we may mix-and-match operations with batched parameters and those without. However, the batch_size of each batched Operator within the quantum function must be the same:

      >>> dev = qml.device('default.qubit', wires=1)
      >>> @qml.qnode(dev)
      ... def circuit_rx(x, z):
      ...     qml.RX(x, wires=0)
      ...     qml.RZ(z, wires=0)
      ...     qml.RY(0.3, wires=0)
      ...     return qml.probs(wires=0)
      >>> circuit_rx([0.1, 0.2], [0.3, 0.4])
      tensor([[0.97092256, 0.02907744],
              [0.95671515, 0.04328485]], requires_grad=True)
      

      Parameter broadcasting is supported on all devices, hardware and simulator. Note that if not natively supported by the underlying device, parameter broadcasting may result in additional quantum device evaluations.

    • A new transform, qml.transforms.broadcast_expand, has been added, which automates the process of transforming quantum functions (and tapes) to multiple quantum evaluations with no parameter broadcasting. (#2590)

      >>> dev = qml.device('default.qubit', wires=1)
      >>> @qml.transforms.broadcast_expand()
      >>> @qml.qnode(dev)
      ... def circuit_rx(x, z):
      ...     qml.RX(x, wires=0)
      ...     qml.RZ(z, wires=0)
      ...     qml.RY(0.3, wires=0)
      ...     return qml.probs(wires=0)
      >>> print(qml.draw(circuit_rx)([0.1, 0.2], [0.3, 0.4]))
      0: ──RX(0.10)──RZ(0.30)──RY(0.30)─┤  Probs
      \
      0: ──RX(0.20)──RZ(0.40)──RY(0.30)─┤  Probs
      

      Under-the-hood, this transform is used for devices that don't natively support parameter broadcasting.

    • To specify that a device natively supports broadcasted tapes, the new flag Device.capabilities()["supports_broadcasting"] should be set to True.

    • To support parameter broadcasting for new or custom operations, the following new Operator class attributes must be specified:

      • Operator.ndim_params specifies expected number of dimensions for each parameter

      Once set, Operator.batch_size and QuantumTape.batch_size will dynamically compute the parameter broadcasting axis dimension, if present.

    Improved JAX JIT support 🏎

    • JAX just-in-time (JIT) compilation now supports vector-valued QNodes, enabling new types of workflows and significant performance boosts. (#2034)

      Vector-valued QNodes include those with:

      • qml.probs;
      • qml.state;
      • qml.sample or
      • multiple qml.expval / qml.var measurements.

      Consider a QNode that returns basis-state probabilities:

      dev = qml.device('default.qubit', wires=2)
      x = jnp.array(0.543)
      y = jnp.array(-0.654)
      
      @jax.jit
      @qml.qnode(dev, diff_method="parameter-shift", interface="jax")
      def circuit(x, y):
          qml.RX(x, wires=[0])
          qml.RY(y, wires=[1])
          qml.CNOT(wires=[0, 1])
          return qml.probs(wires=[1])
      
      >>> circuit(x, y)
      DeviceArray([0.8397495 , 0.16025047], dtype=float32)
      

      Note that computing the jacobian of vector-valued QNode is not supported with JAX JIT. The output of vector-valued QNodes can, however, be used in the definition of scalar-valued cost functions whose gradients can be computed.

      For example, one can define a cost function that outputs the first element of the probability vector:

      def cost(x, y):
          return circuit(x, y)[0]
      
      >>> jax.grad(cost, argnums=[0])(x, y)
      (DeviceArray(-0.2050439, dtype=float32),)
      

    More drawing styles 🎨

    • New solarized_light and solarized_dark styles are available for drawing circuit diagram graphics. (#2662)

    New operations & transforms 🤖

    • The qml.IsingXY gate is now available (see 1912.04424). (#2649)

    • The qml.ECR (echoed cross-resonance) operation is now available (see 2105.01063). This gate is a maximally-entangling gate and is equivalent to a CNOT gate up to single-qubit pre-rotations. (#2613)

    • The adjoint transform adjoint can now accept either a single instantiated operator or a quantum function. It returns an entity of the same type / call signature as what it was given: (#2222) (#2672)

      >>> qml.adjoint(qml.PauliX(0))
      Adjoint(PauliX)(wires=[0])
      >>> qml.adjoint(qml.RX)(1.23, wires=0)
      Adjoint(RX)(1.23, wires=[0])
      

      Now, adjoint wraps operators in a symbolic operator class qml.ops.op_math.Adjoint. This class should not be constructed directly; the adjoint constructor should always be used instead. The class behaves just like any other Operator:

      >>> op = qml.adjoint(qml.S(0))
      >>> qml.matrix(op)
      array([[1.-0.j, 0.-0.j],
            [0.-0.j, 0.-1.j]])
      >>> qml.eigvals(op)
      array([1.-0.j, 0.-1.j])
      
    • A new symbolic operator class qml.ops.op_math.Pow represents an operator raised to a power. When decomposition() is called, a list of new operators equal to this one raised to the given power is given: (#2621)

      >>> op = qml.ops.op_math.Pow(qml.PauliX(0), 0.5)
      >>> op.decomposition()
      [SX(wires=[0])]
      >>> qml.matrix(op)
      array([[0.5+0.5j, 0.5-0.5j],
           [0.5-0.5j, 0.5+0.5j]])
      
    • A new transform qml.batch_partial is available which behaves similarly to functools.partial, but supports batching in the unevaluated parameters. (#2585)

      This is useful for executing a circuit with a batch dimension in some of its parameters:

      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev)
      def circuit(x, y):
         qml.RX(x, wires=0)
         qml.RY(y, wires=0)
         return qml.expval(qml.PauliZ(wires=0))
      
      >>> batched_partial_circuit = qml.batch_partial(circuit, x=np.array(np.pi / 4))
      >>> y = np.array([0.2, 0.3, 0.4])
      >>> batched_partial_circuit(y=y)
      tensor([0.69301172, 0.67552491, 0.65128847], requires_grad=True)
      
    • A new transform qml.split_non_commuting is available, which splits a quantum function or tape into multiple functions/tapes determined by groups of commuting observables: (#2587)

      dev = qml.device("default.qubit", wires=1)
      
      @qml.transforms.split_non_commuting
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(x,wires=0)
          return [qml.expval(qml.PauliX(0)), qml.expval(qml.PauliZ(0))]
      
      >>> print(qml.draw(circuit)(0.5))
      0: ──RX(0.50)─┤  <X>
      \
      0: ──RX(0.50)─┤  <Z>
      

    Improvements

    • Expectation values of multiple non-commuting observables from within a single QNode are now supported: (#2587)

      >>> dev = qml.device('default.qubit', wires=1)
      >>> @qml.qnode(dev)
      ... def circuit_rx(x, z):
      ...     qml.RX(x, wires=0)
      ...     qml.RZ(z, wires=0)
      ...     return qml.expval(qml.PauliX(0)), qml.expval(qml.PauliY(0))
      >>> circuit_rx(0.1, 0.3)
      tensor([ 0.02950279, -0.09537451], requires_grad=True)
      
    • Selecting which parts of parameter-shift Hessians are computed is now possible. (#2538)

      The argnum keyword argument for qml.gradients.param_shift_hessian is now allowed to be a two-dimensional Boolean array_like. Only the indicated entries of the Hessian will then be computed.

      A particularly useful example is the computation of the diagonal of the Hessian:

      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(x[0], wires=0)
          qml.RY(x[1], wires=0)
          qml.RX(x[2], wires=0)
          return qml.expval(qml.PauliZ(0))
      
      argnum = qml.math.eye(3, dtype=bool)
      x = np.array([0.2, -0.9, 1.1], requires_grad=True)
      
      >>> qml.gradients.param_shift_hessian(circuit, argnum=argnum)(x)
      tensor([[-0.09928388,  0.        ,  0.        ],
              [ 0.        , -0.27633945,  0.        ],
              [ 0.        ,  0.        , -0.09928388]], requires_grad=True)
      
    • Commuting Pauli operators are now measured faster. (#2425)

      The logic that checks for qubit-wise commuting (QWC) observables has been improved, resulting in a performance boost that is noticable when many commuting Pauli operators of the same type are measured.

    • It is now possible to add Observable objects to the integer 0, for example qml.PauliX(wires=[0]) + 0. (#2603)

    • Wires can now be passed as the final argument to an Operator, instead of requiring the wires to be explicitly specified with keyword wires. This functionality already existed for Observables, but now extends to all Operators: (#2432)

      >>> qml.S(0)
      S(wires=[0])
      >>> qml.CNOT((0,1))
      CNOT(wires=[0, 1])
      
    • The qml.taper function can now be used to consistently taper any additional observables such as dipole moment, particle number, and spin operators using the symmetries obtained from the Hamiltonian. (#2510)

    • Sparse Hamiltonians' representation has changed from Coordinate (COO) to Compressed Sparse Row (CSR) format. (#2561)

      The CSR representation is more performant for arithmetic operations and matrix-vector products. This change decreases the expval() calculation time for qml.SparseHamiltonian, specially for large workflows. In addition, the CSR format consumes less memory for qml.SparseHamiltonian storage.

    • IPython now displays the str representation of a Hamiltonian, rather than the repr. This displays more information about the object.(#2648)

    • The qml.qchem tests have been restructured. (#2593) (#2545)

      • OpenFermion-dependent tests are now localized and collected in tests.qchem.of_tests. The new module test_structure is created to collect the tests of the qchem.structure module in one place and remove their dependency to OpenFermion.

      • Test classes have been created to group the integrals and matrices unit tests.

    • An operations_only argument is introduced to the tape.get_parameters method. (#2543)

    • The gradients module now uses faster subroutines and uniform formats of gradient rules. (#2452)

    • Instead of checking types, objects are now processed in the QuantumTape based on a new _queue_category property. This is a temporary fix that will disappear in the future. (#2408)

    • The QNode class now contains a new method best_method_str that returns the best differentiation method for a provided device and interface, in human-readable format. (#2533)

    • Using Operation.inv() in a queuing environment no longer updates the queue's metadata, but merely updates the operation in place. (#2596)

    • A new method safe_update_info is added to qml.QueuingContext. This method is substituted for qml.QueuingContext.update_info in a variety of places. (#2612) (#2675)

    • BasisEmbedding can accept an int as argument instead of a list of bits. (#2601)

      For example, qml.BasisEmbedding(4, wires = range(4)) is now equivalent to qml.BasisEmbedding([0,1,0,0], wires = range(4)) (as 4==0b100).

    • Introduced a new is_hermitian property to Operators to determine if an operator can be used in a measurement process. (#2629)

    • Added separate requirements_dev.txt for separation of concerns for code development and just using PennyLane. (#2635)

    • The performance of building sparse Hamiltonians has been improved by accumulating the sparse representation of coefficient-operator pairs in a temporary storage and by eliminating unnecessary kron operations on identity matrices. (#2630)

    • Control values are now displayed distinctly in text and matplotlib drawings of circuits. (#2668)

    • The TorchLayer init_method argument now accepts either a torch.nn.init function or a dictionary which should specify a torch.nn.init/torch.Tensor for each different weight. (#2678)

    • The unused keyword argument do_queue for Operation.adjoint is now fully removed. (#2583)

    • Several non-decomposable Adjoint operators are added to the device test suite. (#2658)

    • The developer-facing pow method has been added to Operator with concrete implementations for many classes. (#2225)

    • The ctrl transform and ControlledOperation have been moved to the new qml.ops.op_math submodule. The developer-facing ControlledOperation class is no longer imported top-level. (#2656)

    Deprecations

    • qml.ExpvalCost has been deprecated, and usage will now raise a warning. (#2571)

      Instead, it is recommended to simply pass Hamiltonians to the qml.expval function inside QNodes:

      @qml.qnode(dev)
      def ansatz(params):
          some_qfunc(params)
          return qml.expval(Hamiltonian)
      

    Breaking changes

    • When using qml.TorchLayer, weights with negative shapes will now raise an error, while weights with size = 0 will result in creating empty Tensor objects. (#2678)

    • PennyLane no longer supports TensorFlow <=2.3. (#2683)

    • The qml.queuing.Queue class has been removed. (#2599)

    • The qml.utils.expand function is now removed; qml.operation.expand_matrix should be used instead. (#2654)

    • The module qml.gradients.param_shift_hessian has been renamed to qml.gradients.parameter_shift_hessian in order to distinguish it from the identically named function. Note that the param_shift_hessian function is unaffected by this change and can be invoked in the same manner as before via the qml.gradients module. (#2528)

    • The properties eigval and matrix from the Operator class were replaced with the methods eigval() and matrix(wire_order=None). (#2498)

    • Operator.decomposition() is now an instance method, and no longer accepts parameters. (#2498)

    • Adds tests, adds no-coverage directives, and removes inaccessible logic to improve code coverage. (#2537)

    • The base classes QubitDevice and DefaultQubit now accept data-types for a statevector. This enables a derived class (device) in a plugin to choose correct data-types: (#2448)

      >>> dev = qml.device("default.qubit", wires=4, r_dtype=np.float32, c_dtype=np.complex64)
      >>> dev.R_DTYPE
      <class 'numpy.float32'>
      >>> dev.C_DTYPE
      <class 'numpy.complex64'>
      

    Bug fixes

    • Fixed a bug where returning qml.density_matrix using the PyTorch interface would return a density matrix with wrong shape. (#2643)

    • Fixed a bug to make param_shift_hessian work with QNodes in which gates marked as trainable do not have any impact on the QNode output. (#2584)

    • QNodes can now interpret variations on the interface name, like "tensorflow" or "jax-jit", when requesting backpropagation. (#2591)

    • Fixed a bug for diff_method="adjoint" where incorrect gradients were computed for QNodes with parametrized observables (e.g., qml.Hermitian). (#2543)

    • Fixed a bug where QNGOptimizer did not work with operators whose generator was a Hamiltonian. (#2524)

    • Fixed a bug with the decomposition of qml.CommutingEvolution. (#2542)

    • Fixed a bug enabling PennyLane to work with the latest version of Autoray. (#2549)

    • Fixed a bug which caused different behaviour for Hamiltonian @ Observable and Observable @ Hamiltonian. (#2570)

    • Fixed a bug in DiagonalQubitUnitary._controlled where an invalid operation was queued instead of the controlled version of the diagonal unitary. (#2525)

    • Updated the gradients fix to only apply to the strawberryfields.gbs device, since the original logic was breaking some devices. (#2485) (#2595)

    • Fixed a bug in qml.transforms.insert where operations were not inserted after gates within a template. (#2704)

    • Hamiltonian.wires is now properly updated after in place operations. (#2738)

    Documentation

    • The centralized Xanadu Sphinx Theme is now used to style the Sphinx documentation. (#2450)

    • Added a reference to qml.utils.sparse_hamiltonian in qml.SparseHamiltonian to clarify how to construct sparse Hamiltonians in PennyLane. (2572)

    • Added a new section in the Gradients and Training page that summarizes the supported device configurations and provides justification. In addition, code examples were added for some selected configurations. (#2540)

    • Added a note for the [Depolarization Channel (https://pennylane.readthedocs.io/en/stable/code/api/pennylane.DepolarizingChannel.html) that specifies how the channel behaves for the different values of depolarization probability p. (#2669)

    • The quickstart documentation has been improved. (#2530) (#2534) (#2564 (#2565 (#2566) (#2607) (#2608)

    • The quantum chemistry quickstart documentation has been improved. (#2500)

    • Testing documentation has been improved. (#2536)

    • Documentation for the pre-commit package has been added. (#2567)

    • Documentation for draw control wires change has been updated. (#2682)

    Contributors

    This release contains contributions from (in alphabetical order):

    Guillermo Alonso-Linaje, Mikhail Andrenkov, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Samuel Banning, Avani Bhardwaj, Thomas Bromley, Albert Mitjans Coma, Isaac De Vlugt, Amintor Dusko, Trent Fridey, Christian Gogolin, Qi Hu, Katharine Hyatt, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Nathan Killoran, Korbinian Kottmann, Ankit Khandelwal, Christina Lee, Chae-Yeun Park, Mason Moreland, Romain Moyard, Maria Schuld, Jay Soni, Antal Száva, tal66, David Wierichs, Roeland Wiersema, WingCode.

    Source code(tar.gz)
    Source code(zip)
  • v0.23.1(May 9, 2022)

    Bug fixes

    • Fixed a bug enabling PennyLane to work with the latest version of Autoray. (#2548)

    Contributors

    This release contains contributions from (in alphabetical order):

    Josh Izaac.

    Source code(tar.gz)
    Source code(zip)
  • v0.23.0(Apr 25, 2022)

    New features since last release

    More powerful circuit cutting ✂️

    • Quantum circuit cutting (running N-wire circuits on devices with fewer than N wires) is now supported for QNodes of finite-shots using the new @qml.cut_circuit_mc transform. (#2313) (#2321) (#2332) (#2358) (#2382) (#2399) (#2407) (#2444)

      With these new additions, samples from the original circuit can be simulated using a Monte Carlo method, using fewer qubits at the expense of more device executions. Additionally, this transform can take an optional classical processing function as an argument and return an expectation value.

      The following 3-qubit circuit contains a WireCut operation and a sample measurement. When decorated with @qml.cut_circuit_mc, we can cut the circuit into two 2-qubit fragments:

      dev = qml.device("default.qubit", wires=2, shots=1000)
      
      @qml.cut_circuit_mc
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(0.89, wires=0)
          qml.RY(0.5, wires=1)
          qml.RX(1.3, wires=2)
      
          qml.CNOT(wires=[0, 1])
          qml.WireCut(wires=1)
          qml.CNOT(wires=[1, 2])
      
          qml.RX(x, wires=0)
          qml.RY(0.7, wires=1)
          qml.RX(2.3, wires=2)
          return qml.sample(wires=[0, 2])
      

      we can then execute the circuit as usual by calling the QNode:

      >>> x = 0.3
      >>> circuit(x)
      tensor([[1, 1],
              [0, 1],
              [0, 1],
              ...,
              [0, 1],
              [0, 1],
              [0, 1]], requires_grad=True)
      

      Furthermore, the number of shots can be temporarily altered when calling the QNode:

      >>> results = circuit(x, shots=123)
      >>> results.shape
      (123, 2)
      

      The cut_circuit_mc transform also supports returning sample-based expectation values of observables using the classical_processing_fn argument. Refer to the UsageDetails section of the transform documentation for an example.

    • The cut_circuit transform now supports automatic graph partitioning by specifying auto_cutter=True to cut arbitrary tape-converted graphs using the general purpose graph partitioning framework KaHyPar. (#2330) (#2428)

      Note that KaHyPar needs to be installed separately with the auto_cutter=True option.

      For integration with the existing low-level manual cut pipeline, refer to the documentation of the function.

      @qml.cut_circuit(auto_cutter=True)
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(x, wires=0)
          qml.RY(0.9, wires=1)
          qml.RX(0.3, wires=2)
          qml.CZ(wires=[0, 1])
          qml.RY(-0.4, wires=0)
          qml.CZ(wires=[1, 2])
          return qml.expval(qml.grouping.string_to_pauli_word("ZZZ"))
      
      >>> x = np.array(0.531, requires_grad=True)
      >>> circuit(x)
      0.47165198882111165
      >>> qml.grad(circuit)(x)
      -0.276982865449393
      

    Grand QChem unification ⚛️ 🏰

    • Quantum chemistry functionality --- previously split between an external pennylane-qchem package and internal qml.hf differentiable Hartree-Fock solver --- is now unified into a single, included, qml.qchem module. (#2164) (#2385) (#2352) (#2420) (#2454) (#2199) (#2371) (#2272) (#2230) (#2415) (#2426) (#2465)

      The qml.qchem module provides a differentiable Hartree-Fock solver and the functionality to construct a fully-differentiable molecular Hamiltonian.

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

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

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

    • Functions are added for building a differentiable dipole moment observable. Functions for computing multipole moment molecular integrals, needed for building the dipole moment observable, are also added. (#2173) (#2166)

      The dipole moment observable can be constructed using qml.qchem.dipole_moment:

      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 = [geometry]
      D = qml.qchem.dipole_moment(mol)(*args)
      
    • The efficiency of computing molecular integrals and Hamiltonian is improved. This has been done by adding optimized functions for building fermionic and qubit observables and optimizing the functions used for computing the electron repulsion integrals. (#2316)

    • The 6-31G basis set is added to the qchem basis set repo. This addition allows performing differentiable Hartree-Fock calculations with basis sets beyond the minimal sto-3g basis set for atoms with atomic number 1-10. (#2372)

      The 6-31G basis set can be used to construct a Hamiltonian as

      symbols = ["H", "H"]
      geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]])
      H, qubits = qml.qchem.molecular_hamiltonian(symbols, geometry, basis="6-31g")
      
    • External dependencies are replaced with local functions for spin and particle number observables. (#2197) (#2362)

    Pattern matching optimization 🔎 💎

    • Added an optimization transform that matches pieces of user-provided identity templates in a circuit and replaces them with an equivalent component. (#2032)

      For example, consider the following circuit where we want to replace sequence of two pennylane.S gates with a pennylane.PauliZ gate.

      def circuit():
          qml.S(wires=0)
          qml.PauliZ(wires=0)
          qml.S(wires=1)
          qml.CZ(wires=[0, 1])
          qml.S(wires=1)
          qml.S(wires=2)
          qml.CZ(wires=[1, 2])
          qml.S(wires=2)
          return qml.expval(qml.PauliX(wires=0))
      

      We specify use the following pattern that implements the identity:

      with qml.tape.QuantumTape() as pattern:
          qml.S(wires=0)
          qml.S(wires=0)
          qml.PauliZ(wires=0)
      

      To optimize the circuit with this identity pattern, we apply the qml.transforms.pattern_matching transform.

      >>> dev = qml.device('default.qubit', wires=5)
      >>> qnode = qml.QNode(circuit, dev)
      >>> optimized_qfunc = qml.transforms.pattern_matching_optimization(pattern_tapes=[pattern])(circuit)
      >>> optimized_qnode = qml.QNode(optimized_qfunc, dev)
      >>> print(qml.draw(qnode)())
      0: ──S──Z─╭C──────────┤  <X>
      1: ──S────╰Z──S─╭C────┤
      2: ──S──────────╰Z──S─┤
      >>> print(qml.draw(optimized_qnode)())
      0: ──S⁻¹─╭C────┤  <X>
      1: ──Z───╰Z─╭C─┤
      2: ──Z──────╰Z─┤
      

      For more details on using pattern matching optimization you can check the corresponding documentation and also the following paper.

    Measure the distance between two unitaries📏

    • Added the HilbertSchmidt and the LocalHilbertSchmidt templates to be used for computing distance measures between unitaries. (#2364)

      Given a unitary U, qml.HilberSchmidt can be used to measure the distance between unitaries and to define a cost function (cost_hst) used for learning a unitary V that is equivalent to U up to a global phase:

      # Represents unitary U
      with qml.tape.QuantumTape(do_queue=False) as u_tape:
          qml.Hadamard(wires=0)
      
      # Represents unitary V
      def v_function(params):
          qml.RZ(params[0], wires=1)
      
      @qml.qnode(dev)
      def hilbert_test(v_params, v_function, v_wires, u_tape):
          qml.HilbertSchmidt(v_params, v_function=v_function, v_wires=v_wires, u_tape=u_tape)
          return qml.probs(u_tape.wires + v_wires)
      
      def cost_hst(parameters, v_function, v_wires, u_tape):
          return (1 - hilbert_test(v_params=parameters, v_function=v_function, v_wires=v_wires, u_tape=u_tape)[0])
      
      >>> cost_hst(parameters=[0.1], v_function=v_function, v_wires=[1], u_tape=u_tape)
      tensor(0.999, requires_grad=True)
      

      For more information refer to the documentation of qml.HilbertSchmidt.

    More tensor network support 🕸️

    • Adds the qml.MERA template for implementing quantum circuits with the shape of a multi-scale entanglement renormalization ansatz (MERA). (#2418)

      MERA follows the style of previous tensor network templates and is similar to quantum convolutional neural networks.

        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_wires = 4
        n_block_wires = 2
        n_params_block = 2
        n_blocks = qml.MERA.get_n_blocks(range(n_wires),n_block_wires)
        template_weights = [[0.1,-0.3]]*n_blocks
      
        dev= qml.device('default.qubit',wires=range(n_wires))
        @qml.qnode(dev)
        def circuit(template_weights):
            qml.MERA(range(n_wires),n_block_wires,block, n_params_block, template_weights)
            return qml.expval(qml.PauliZ(wires=1))
      

      It may be necessary to reorder the wires to see the MERA architecture clearly:

      >>> print(qml.draw(circuit,expansion_strategy='device',wire_order=[2,0,1,3])(template_weights))
      2: ───────────────╭C──RY(0.10)──╭X──RY(-0.30)───────────────┤
      0: ─╭X──RY(-0.30)─│─────────────╰C──RY(0.10)──╭C──RY(0.10)──┤
      1: ─╰C──RY(0.10)──│─────────────╭X──RY(-0.30)─╰X──RY(-0.30)─┤  <Z>
      3: ───────────────╰X──RY(-0.30)─╰C──RY(0.10)────────────────┤
      

    New transform for transpilation ⚙️

    • Added a swap based transpiler transform. (#2118)

      The transpile function takes a quantum function and a coupling map as inputs and compiles the circuit to ensure that it can be executed on corresponding hardware. The transform can be used as a decorator in the following way:

      dev = qml.device('default.qubit', wires=4)
      
      @qml.qnode(dev)
      @qml.transforms.transpile(coupling_map=[(0, 1), (1, 2), (2, 3)])
      def circuit(param):
          qml.CNOT(wires=[0, 1])
          qml.CNOT(wires=[0, 2])
          qml.CNOT(wires=[0, 3])
          qml.PhaseShift(param, wires=0)
          return qml.probs(wires=[0, 1, 2, 3])
      
      >>> print(qml.draw(circuit)(0.3))
      0: ─╭C───────╭C──────────╭C──Rϕ(0.30)─┤ ╭Probs
      1: ─╰X─╭SWAP─╰X────╭SWAP─╰X───────────┤ ├Probs
      2: ────╰SWAP─╭SWAP─╰SWAP──────────────┤ ├Probs
      3: ──────────╰SWAP────────────────────┤ ╰Probs
      

    Improvements

    • QuantumTape objects are now iterable, allowing iteration over the contained operations and measurements. (#2342)

      with qml.tape.QuantumTape() as tape:
          qml.RX(0.432, wires=0)
          qml.RY(0.543, wires=0)
          qml.CNOT(wires=[0, 'a'])
          qml.RX(0.133, wires='a')
          qml.expval(qml.PauliZ(wires=[0]))
      

      Given a QuantumTape object the underlying quantum circuit can be iterated over using a for loop:

      >>> for op in tape:
      ...     print(op)
      RX(0.432, wires=[0])
      RY(0.543, wires=[0])
      CNOT(wires=[0, 'a'])
      RX(0.133, wires=['a'])
      expval(PauliZ(wires=[0]))
      

      Indexing into the circuit is also allowed via tape[i]:

      >>> tape[0]
      RX(0.432, wires=[0])
      

      A tape object can also be converted to a sequence (e.g., to a list) of operations and measurements:

      >>> list(tape)
      [RX(0.432, wires=[0]),
       RY(0.543, wires=[0]),
       CNOT(wires=[0, 'a']),
       RX(0.133, wires=['a']),
       expval(PauliZ(wires=[0]))]
      
    • Added the QuantumTape.shape method and QuantumTape.numeric_type attribute to allow extracting information about the shape and numeric type of the output returned by a quantum tape after execution. (#2044)

      dev = qml.device("default.qubit", wires=2)
      a = np.array([0.1, 0.2, 0.3])
      
      def func(a):
          qml.RY(a[0], wires=0)
          qml.RX(a[1], wires=0)
          qml.RY(a[2], wires=0)
      
      with qml.tape.QuantumTape() as tape:
          func(a)
          qml.state()
      
      >>> tape.shape(dev)
      (1, 4)
      >>> tape.numeric_type
      complex
      
    • Defined a MeasurementProcess.shape method and a MeasurementProcess.numeric_type attribute to allow extracting information about the shape and numeric type of results obtained when evaluating QNodes using the specific measurement process. (#2044)

    • The parameter-shift Hessian can now be computed for arbitrary operations that support the general parameter-shift rule for gradients, using qml.gradients.param_shift_hessian (#2319)

      Multiple ways to obtain the gradient recipe are supported, in the following order of preference:

      • A custom grad_recipe. It is iterated to obtain the shift rule for the second-order derivatives in the diagonal entries of the Hessian.

      • Custom parameter_frequencies. The second-order shift rule can directly be computed using them.

      • An operation's generator. Its eigenvalues will be used to obtain parameter_frequencies, if they are not given explicitly for an operation.

    • The strategy for expanding a circuit can now be specified with the qml.specs transform, for example to calculate the specifications of the circuit that will actually be executed by the device (expansion_strategy="device"). (#2395)

    • The default.qubit and default.mixed devices now skip over identity operators instead of performing matrix multiplication with the identity. (#2356) (#2365)

    • The function qml.eigvals is modified to use the efficient scipy.sparse.linalg.eigsh method for obtaining the eigenvalues of a SparseHamiltonian. This scipy method is called to compute :math:k eigenvalues of a sparse :math:N \times N matrix if k is smaller than :math:N-1. If a larger :math:k is requested, the dense matrix representation of the Hamiltonian is constructed and the regular qml.math.linalg.eigvalsh is applied. (#2333)

    • The function qml.ctrl was given the optional argument control_values=None. If overridden, control_values takes an integer or a list of integers corresponding to the binary value that each control value should take. The same change is reflected in ControlledOperation. Control values of 0 are implemented by qml.PauliX applied before and after the controlled operation (#2288)

    • Operators now have a has_matrix property denoting whether or not the operator defines a matrix. (#2331) (#2476)

    • Circuit cutting now performs expansion to search for wire cuts in contained operations or tapes. (#2340)

    • The qml.draw and qml.draw_mpl transforms are now located in the drawer module. They can still be accessed via the top-level qml namespace. (#2396)

    • Raise a warning where caching produces identical shot noise on execution results with finite shots. (#2478)

    Deprecations

    • The ObservableReturnTypes Sample, Variance, Expectation, Probability, State, and MidMeasure have been moved to measurements from operation. (#2329) (#2481)

    Breaking changes

    • The caching ability of devices has been removed. Using the caching on the QNode level is the recommended alternative going forward. (#2443)

      One way for replicating the removed QubitDevice caching behaviour is by creating a cache object (e.g., a dictionary) and passing it to the QNode:

      n_wires = 4
      wires = range(n_wires)
      
      dev = qml.device('default.qubit', wires=n_wires)
      
      cache = {}
      
      @qml.qnode(dev, diff_method='parameter-shift', cache=cache)
      def expval_circuit(params):
          qml.templates.BasicEntanglerLayers(params, wires=wires, rotation=qml.RX)
          return qml.expval(qml.PauliZ(0) @ qml.PauliY(1) @ qml.PauliX(2) @ qml.PauliZ(3))
      
      shape = qml.templates.BasicEntanglerLayers.shape(5, n_wires)
      params = np.random.random(shape)
      
      >>> expval_circuit(params)
      tensor(0.20598436, requires_grad=True)
      >>> dev.num_executions
      1
      >>> expval_circuit(params)
      tensor(0.20598436, requires_grad=True)
      >>> dev.num_executions
      1
      
    • The qml.finite_diff function has been removed. Please use qml.gradients.finite_diff to compute the gradient of tapes of QNodes. Otherwise, manual implementation is required. (#2464)

    • The get_unitary_matrix transform has been removed, please use qml.matrix instead. (#2457)

    • The update_stepsize method has been removed from GradientDescentOptimizer and its child optimizers. The stepsize property can be interacted with directly instead. (#2370)

    • Most optimizers no longer flatten and unflatten arguments during computation. Due to this change, user provided gradient functions must return the same shape as qml.grad. (#2381)

    • The old circuit text drawing infrastructure has been removed. (#2310)

      • RepresentationResolver was replaced by the Operator.label method.
      • qml.drawer.CircuitDrawer was replaced by qml.drawer.tape_text.
      • qml.drawer.CHARSETS was removed because unicode is assumed to be accessible.
      • Grid and qml.drawer.drawable_grid were removed because the custom data class was replaced by list of sets of operators or measurements.
      • qml.transforms.draw_old was replaced by qml.draw.
      • qml.CircuitGraph.greedy_layers was deleted, as it was no longer needed by the circuit drawer and did not seem to have uses outside of that situation.
      • qml.CircuitGraph.draw was deleted, as we draw tapes instead.
      • The tape method qml.tape.QuantumTape.draw now simply calls qml.drawer.tape_text.
      • In the new pathway, the charset keyword was deleted, the max_length keyword defaults to 100, and the decimals and show_matrices keywords were added.
    • The deprecated QNode, available via qml.qnode_old.QNode, has been removed. Please transition to using the standard qml.QNode. (#2336) (#2337) (#2338)

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

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

      • The deprecated tape subclasses QubitParamShiftTape, JacobianTape, CVParamShiftTape, and ReversibleTape have been removed.

    • The deprecated tape execution method tape.execute(device) has been removed. Please use qml.execute([tape], device) instead. (#2339)

    Bug fixes

    • Fixed a bug in the qml.PauliRot operation, where computing the generator was not taking into account the operation wires. (#2466)

    • Fixed a bug where non-trainable arguments were shifted in the NesterovMomentumOptimizer if a trainable argument was after it in the argument list. (#2466)

    • Fixed a bug with @jax.jit for grad when diff_method="adjoint" and mode="backward". (#2460)

    • Fixed a bug where qml.DiagonalQubitUnitary did not support @jax.jit and @tf.function. (#2445)

    • Fixed a bug in the qml.PauliRot operation, where computing the generator was not taking into account the operation wires. (#2442)

    • Fixed a bug with the padding capability of AmplitudeEmbedding where the inputs are on the GPU. (#2431)

    • Fixed a bug by adding a comprehensible error message for calling qml.probs without passing wires or an observable. (#2438)

    • The behaviour of qml.about() was modified to avoid warnings being emitted due to legacy behaviour of pip. (#2422)

    • Fixed a bug where observables were not considered when determining the use of the jax-jit interface. (#2427) (#2474)

    • Fixed a bug where computing statistics for a relatively few number of shots (e.g., shots=10), an error arose due to indexing into an array using a DeviceArray. (#2427)

    • PennyLane Lightning version in Docker container is pulled from latest wheel-builds. (#2416)

    • Optimizers only consider a variable trainable if they have requires_grad = True. (#2381)

    • Fixed a bug with qml.expval, qml.var, qml.state and qml.probs (when qml.probs is the only measurement) where the dtype specified on the device did not match the dtype of the QNode output. (#2367)

    • Fixed a bug where the output shapes from batch transforms are inconsistent with the QNode output shape. (#2215)

    • Fixed a bug caused by the squeezing in qml.gradients.param_shift_hessian. (#2215)

    • Fixed a bug in which the expval/var of a Tensor(Observable) would depend on the order in which the observable is defined: (#2276)

      >>> @qml.qnode(dev)
      ... def circ(op):
      ...   qml.RX(0.12, wires=0)
      ...   qml.RX(1.34, wires=1)
      ...   qml.RX(3.67, wires=2)
      ...   return qml.expval(op)
      >>> op1 = qml.Identity(wires=0) @ qml.Identity(wires=1) @ qml.PauliZ(wires=2)
      >>> op2 = qml.PauliZ(wires=2) @ qml.Identity(wires=0) @ qml.Identity(wires=1)
      >>> print(circ(op1), circ(op2))
      -0.8636111153905662 -0.8636111153905662
      
    • Fixed a bug where qml.hf.transform_hf() would fail due to missing wires in the qubit operator that is prepared for tapering the HF state. (#2441)

    • Fixed a bug with custom device defined jacobians not being returned properly. (#2485)

    Documentation

    • The sections on adding operator and observable support in the "How to add a plugin" section of the plugins page have been updated. (#2389)

    • The missing arXiv reference in the LieAlgebra optimizer has been fixed. (#2325)

    Contributors

    This release contains contributions from (in alphabetical order):

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

    Source code(tar.gz)
    Source code(zip)
  • v0.22.2(Apr 1, 2022)

    Bug fixes

    • Most compilation transforms, and relevant subroutines, have been updated to support just-in-time compilation with jax.jit. This fix was intended to be included in v0.22.0, but due to a bug was incomplete. (#2397)

    Documentation

    • The documentation run has been updated to require jinja2==3.0.3 due to an issue that arises with jinja2 v3.1.0 and sphinx v3.5.3. (#2378)

    Contributors

    This release contains contributions from (in alphabetical order):

    Olivia Di Matteo, Christina Lee, Romain Moyard, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.22.1(Mar 16, 2022)

    Bug fixes

    • Fixes cases with qml.measure where unexpected operations were added to the circuit. (#2328)

    Contributors

    This release contains contributions from (in alphabetical order):

    Guillermo Alonso-Linaje, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.22.0(Mar 15, 2022)

    New features since last release

    Quantum circuit cutting ✂️

    • You can now run N-wire circuits on devices with fewer than N wires, by strategically placing WireCut operations that allow their circuit to be partitioned into smaller fragments, at a cost of needing to perform a greater number of device executions. Circuit cutting is enabled by decorating a QNode with the @qml.cut_circuit transform. (#2107) (#2124) (#2153) (#2165) (#2158) (#2169) (#2192) (#2216) (#2168) (#2223) (#2231) (#2234) (#2244) (#2251) (#2265) (#2254) (#2260) (#2257) (#2279)

      The example below shows how a three-wire circuit can be run on a two-wire device:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.cut_circuit
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(x, wires=0)
          qml.RY(0.9, wires=1)
          qml.RX(0.3, wires=2)
      
          qml.CZ(wires=[0, 1])
          qml.RY(-0.4, wires=0)
      
          qml.WireCut(wires=1)
      
          qml.CZ(wires=[1, 2])
      
          return qml.expval(qml.grouping.string_to_pauli_word("ZZZ"))
      

      Instead of executing the circuit directly, it will be partitioned into smaller fragments according to the WireCut locations, and each fragment executed multiple times. Combining the results of the fragment executions will recover the expected output of the original uncut circuit.

      >>> x = np.array(0.531, requires_grad=True)
      >>> circuit(0.531)
      0.47165198882111165
      

      Circuit cutting support is also differentiable:

      >>> qml.grad(circuit)(x)
      -0.276982865449393
      

      For more details on circuit cutting, check out the qml.cut_circuit documentation page or Peng et. al.

    Conditional operations: quantum teleportation unlocked 🔓🌀

    • Support for mid-circuit measurements and conditional operations has been added, to enable use cases like quantum teleportation, quantum error correction and quantum error mitigation. (#2211) (#2236) (#2275)

      Two new functions have been added to support this capability:

      • qml.measure() places mid-circuit measurements in the middle of a quantum function.

      • qml.cond() allows operations and quantum functions to be conditioned on the result of a previous measurement.

      For example, the code below shows how to teleport a qubit from wire 0 to wire 2:

      dev = qml.device("default.qubit", wires=3)
      input_state = np.array([1, -1], requires_grad=False) / np.sqrt(2)
      
      @qml.qnode(dev)
      def teleport(state):
          # Prepare input state
          qml.QubitStateVector(state, wires=0)
      
          # Prepare Bell state
          qml.Hadamard(wires=1)
          qml.CNOT(wires=[1, 2])
      
          # Apply gates
          qml.CNOT(wires=[0, 1])
          qml.Hadamard(wires=0)
      
          # Measure first two wires
          m1 = qml.measure(0)
          m2 = qml.measure(1)
      
          # Condition final wire on results
          qml.cond(m2 == 1, qml.PauliX)(wires=2)
          qml.cond(m1 == 1, qml.PauliZ)(wires=2)
      
          # Return state on final wire
          return qml.density_matrix(wires=2)
      

      We can double-check that the qubit has been teleported by computing the overlap between the input state and the resulting state on wire 2:

      >>> output_state = teleport(input_state)
      >>> output_state
      tensor([[ 0.5+0.j, -0.5+0.j],
              [-0.5+0.j,  0.5+0.j]], requires_grad=True)
      >>> input_state.conj() @ output_state @ input_state
      tensor(1.+0.j, requires_grad=True)
      

      For a full description of new capabilities, refer to the Mid-circuit measurements and conditional operations section in the documentation.

    • Train mid-circuit measurements by deferring them, via the new @qml.defer_measurements transform. (#2211) (#2236) (#2275)

      If a device doesn't natively support mid-circuit measurements, the @qml.defer_measurements transform can be applied to the QNode to transform the QNode into one with terminal measurements and controlled operations:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      @qml.defer_measurements
      def circuit(x):
          qml.Hadamard(wires=0)
      
          m = qml.measure(0)
      
          def op_if_true():
              return qml.RX(x**2, wires=1)
      
          def op_if_false():
              return qml.RY(x, wires=1)
      
          qml.cond(m==1, op_if_true, op_if_false)()
      
          return qml.expval(qml.PauliZ(1))
      
      >>> x = np.array(0.7, requires_grad=True)
      >>> print(qml.draw(circuit, expansion_strategy="device")(x))
      0: ──H─╭C─────────X─╭C─────────X─┤
      1: ────╰RX(0.49)────╰RY(0.70)────┤  <Z>
      >>> circuit(x)
      tensor(0.82358752, requires_grad=True)
      

      Deferring mid-circuit measurements also enables differentiation:

      >>> qml.grad(circuit)(x)
      -0.651546965338656
      

    Debug with mid-circuit quantum snapshots 📷

    • A new operation qml.Snapshot has been added to assist in debugging quantum functions. (#2233) (#2289) (#2291) (#2315)

      qml.Snapshot saves the internal state of devices at arbitrary points of execution.

      Currently supported devices include:

      • default.qubit: each snapshot saves the quantum state vector
      • default.mixed: each snapshot saves the density matrix
      • default.gaussian: each snapshot saves the covariance matrix and vector of means

      During normal execution, the snapshots are ignored:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev, interface=None)
      def circuit():
          qml.Snapshot()
          qml.Hadamard(wires=0)
          qml.Snapshot("very_important_state")
          qml.CNOT(wires=[0, 1])
          qml.Snapshot()
          return qml.expval(qml.PauliX(0))
      

      However, when using the qml.snapshots transform, intermediate device states will be stored and returned alongside the results.

      >>> qml.snapshots(circuit)()
      {0: array([1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j]),
       'very_important_state': array([0.70710678+0.j, 0.        +0.j, 0.70710678+0.j, 0.        +0.j]),
       2: array([0.70710678+0.j, 0.        +0.j, 0.        +0.j, 0.70710678+0.j]),
       'execution_results': array(0.)}
      

    Batch embedding and state preparation data 📦

    • Added the @qml.batch_input transform to enable batching non-trainable gate parameters. In addition, the qml.qnn.KerasLayer class has been updated to natively support batched training data. (#2069)

      As with other transforms, @qml.batch_input can be used to decorate QNodes:

      dev = qml.device("default.qubit", wires=2, shots=None)
      
      @qml.batch_input(argnum=0)
      @qml.qnode(dev, diff_method="parameter-shift", interface="tf")
      def circuit(inputs, weights):
          # add a batch dimension to the embedding data
          qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
          qml.RY(weights[0], wires=0)
          qml.RY(weights[1], wires=1)
          return qml.expval(qml.PauliZ(1))
      

      Batched input parameters can then be passed during QNode evaluation:

      >>> x = tf.random.uniform((10, 2), 0, 1)
      >>> w = tf.random.uniform((2,), 0, 1)
      >>> circuit(x, w)
      <tf.Tensor: shape=(10,), dtype=float64, numpy=
      array([0.46230079, 0.73971315, 0.95666004, 0.5355225 , 0.66180948,
              0.44519553, 0.93874261, 0.9483197 , 0.78737918, 0.90866411])>
      

    Even more mighty quantum transforms 🐛➡🦋

    • New functions and transforms of operators have been added:

      • qml.matrix() for computing the matrix representation of one or more unitary operators. (#2241)

      • qml.eigvals() for computing the eigenvalues of one or more operators. (#2248)

      • qml.generator() for computing the generator of a single-parameter unitary operation. (#2256)

      All operator transforms can be used on instantiated operators,

      >>> op = qml.RX(0.54, wires=0)
      >>> qml.matrix(op)
      [[0.9637709+0.j         0.       -0.26673144j]
      [0.       -0.26673144j 0.9637709+0.j        ]]
      

      Operator transforms can also be used in a functional form:

      >>> x = torch.tensor(0.6, requires_grad=True)
      >>> matrix_fn = qml.matrix(qml.RX)
      >>> matrix_fn(x, wires=[0])
      tensor([[0.9553+0.0000j, 0.0000-0.2955j],
              [0.0000-0.2955j, 0.9553+0.0000j]], grad_fn=<AddBackward0>)
      

      In its functional form, it is fully differentiable with respect to gate arguments:

      >>> loss = torch.real(torch.trace(matrix_fn(x, wires=0)))
      >>> loss.backward()
      >>> x.grad
      tensor(-0.2955)
      

      Some operator transform can also act on multiple operations, by passing quantum functions or tapes:

      >>> def circuit(theta):
      ...     qml.RX(theta, wires=1)
      ...     qml.PauliZ(wires=0)
      >>> qml.matrix(circuit)(np.pi / 4)
      array([[ 0.92387953+0.j,  0.+0.j ,  0.-0.38268343j,  0.+0.j],
      [ 0.+0.j,  -0.92387953+0.j,  0.+0.j,  0. +0.38268343j],
      [ 0. -0.38268343j,  0.+0.j,  0.92387953+0.j,  0.+0.j],
      [ 0.+0.j,  0.+0.38268343j,  0.+0.j,  -0.92387953+0.j]])
      
    • A new transform has been added to construct the pairwise-commutation directed acyclic graph (DAG) representation of a quantum circuit. (#1712)

      In the DAG, each node represents a quantum operation, and edges represent non-commutation between two operations.

      This transform takes into account that not all operations can be moved next to each other by pairwise commutation:

      >>> def circuit(x, y, z):
      ...     qml.RX(x, wires=0)
      ...     qml.RX(y, wires=0)
      ...     qml.CNOT(wires=[1, 2])
      ...     qml.RY(y, wires=1)
      ...     qml.Hadamard(wires=2)
      ...     qml.CRZ(z, wires=[2, 0])
      ...     qml.RY(-y, wires=1)
      ...     return qml.expval(qml.PauliZ(0))
      >>> dag_fn = qml.commutation_dag(circuit)
      >>> dag = dag_fn(np.pi / 4, np.pi / 3, np.pi / 2)
      

      Nodes in the commutation DAG can be accessed via the get_nodes() method, returning a list of the form (ID, CommutationDAGNode):

      >>> nodes = dag.get_nodes()
      >>> nodes
      NodeDataView({0: <pennylane.transforms.commutation_dag.CommutationDAGNode object at 0x7f461c4bb580>, ...}, data='node')
      

      Specific nodes in the commutation DAG can be accessed via the get_node() method:

      >>> second_node = dag.get_node(2)
      >>> second_node
      <pennylane.transforms.commutation_dag.CommutationDAGNode object at 0x136f8c4c0>
      >>> second_node.op
      CNOT(wires=[1, 2])
      >>> second_node.successors
      [3, 4, 5, 6]
      >>> second_node.predecessors
      []
      

    Improvements

    • The text-based drawer accessed via qml.draw() has been optimized and improved. (#2128) (#2198)

      The new drawer has:

      • a decimals keyword for controlling parameter rounding
      • a show_matrices keyword for controlling display of matrices
      • a different algorithm for determining positions
      • deprecation of the charset keyword
      • additional minor cosmetic changes
      @qml.qnode(qml.device('lightning.qubit', wires=2))
      def circuit(a, w):
          qml.Hadamard(0)
          qml.CRX(a, wires=[0, 1])
          qml.Rot(*w, wires=[1])
          qml.CRX(-a, wires=[0, 1])
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      
      >>> print(qml.draw(circuit, decimals=2)(a=2.3, w=[1.2, 3.2, 0.7]))
      0: ──H─╭C─────────────────────────────╭C─────────┤ ╭<Z@Z>
      1: ────╰RX(2.30)──Rot(1.20,3.20,0.70)─╰RX(-2.30)─┤ ╰<Z@Z>
      
    • The frequencies of gate parameters are now accessible as an operation property and can be used for circuit analysis, optimization via the RotosolveOptimizer and differentiation with the parameter-shift rule (including the general shift rule). (#2180) (#2182) (#2227)

      >>> op = qml.CRot(0.4, 0.1, 0.3, wires=[0, 1])
      >>> op.parameter_frequencies
      [(0.5, 1.0), (0.5, 1.0), (0.5, 1.0)]
      

      When using qml.gradients.param_shift, either a custom grad_recipe or the parameter frequencies are used to obtain the shift rule for the operation, in that order of preference.

      See Vidal and Theis (2018) and Wierichs et al. (2021) for theoretical background information on the general parameter-shift rule.

    • No two-term parameter-shift rule is assumed anymore by default. (#2227)

      Previously, operations marked for analytic differentiation that did not provide a generator, parameter_frequencies or a custom grad_recipe were assumed to satisfy the two-term shift rule. This now has to be made explicit for custom operations by adding any of the above attributes.

    • Most compilation transforms, and relevant subroutines, have been updated to support just-in-time compilation with jax.jit. (#1894)

    • The qml.draw_mpl transform supports a expansion_strategy keyword argument. (#2271)

    • The qml.gradients module has been streamlined and special-purpose functions moved closer to their use cases, while preserving existing behaviour. (#2200)

    • Added a new partition_pauli_group function to the grouping module for efficiently measuring the N-qubit Pauli group with 3 ** N qubit-wise commuting terms. (#2185)

    • The Operator class has undergone a major refactor with the following changes:

      • Matrices: the static method Operator.compute_matrices() defines the matrix representation of the operator, and the function qml.matrix(op) computes this for a given instance. (#1996)

      • Eigvals: the static method Operator.compute_eigvals() defines the matrix representation of the operator, and the function qml.eigvals(op) computes this for a given instance. (#2048)

      • Decompositions: the static method Operator.compute_decomposition() defines the matrix representation of the operator, and the method op.decomposition() computes this for a given instance. (#2024) (#2053)

      • Sparse matrices: the static method Operator.compute_sparse_matrix() defines the sparse matrix representation of the operator, and the method op.sparse_matrix() computes this for a given instance. (#2050)

      • Linear combinations of operators: The static method compute_terms(), used for representing the linear combination of coefficients and operators representing the operator, has been added. The method op.terms() computes this for a given instance. Currently, only the Hamiltonian class overwrites compute_terms() to store coefficients and operators. The Hamiltonian.terms property hence becomes a proper method called by Hamiltonian.terms(). (#2036)

      • Diagonalization: The diagonalizing_gates() representation has been moved to the highest-level Operator class and is therefore available to all subclasses. A condition qml.operation.defines_diagonalizing_gates has been added, which can be used in tape contexts without queueing. In addition, a static compute_diagonalizing_gates method has been added, which is called by default in diagonalizing_gates(). (#1985) (#1993)

      • Error handling has been improved for Operator representations. Custom errors subclassing OperatorPropertyUndefined are raised if a representation has not been defined. This replaces the NotImplementedError and allows finer control for developers. (#2064) (#2287)

      • A Operator.hyperparameters attribute, used for storing operation parameters that are never trainable, has been added to the operator class. (#2017)

      • The string_for_inverse attribute is removed. (#2021)

      • The expand() method was moved from the Operation class to the main Operator class. (#2053) (#2239)

    Deprecations

    • There are several important changes when creating custom operations: (#2214) (#2227) (#2030) (#2061)

      • The Operator.matrix method has been deprecated and Operator.compute_matrix should be defined instead. Operator matrices should be accessed using qml.matrix(op). If you were previously defining the class method Operator._matrix(), this is a a breaking change --- please update your operation to instead overwrite Operator.compute_matrix.

      • The Operator.decomposition method has been deprecated and Operator.compute_decomposition should be defined instead. Operator decompositions should be accessed using Operator.decomposition().

      • The Operator.eigvals method has been deprecated and Operator.compute_eigvals should be defined instead. Operator eigenvalues should be accessed using qml.eigvals(op).

      • The Operator.generator property is now a method, and should return an operator instance representing the generator. Note that unlike the other representations above, this is a breaking change. Operator generators should be accessed using qml.generator(op).

      • The Operation.get_parameter_shift method has been deprecated and will be removed in a future release.

        Instead, the functionalities for general parameter-shift rules in the qml.gradients module should be used, together with the operation attributes parameter_frequencies or grad_recipe.

    • Executing tapes using tape.execute(dev) is deprecated. Please use the qml.execute([tape], dev) function instead. (#2306)

    • The subclasses of the quantum tape, including JacobianTape, QubitParamShiftTape, CVParamShiftTape, and ReversibleTape are deprecated. Instead of calling JacobianTape.jacobian() and JacobianTape.hessian(), please use a standard QuantumTape, and apply gradient transforms using the qml.gradients module. (#2306)

    • qml.transforms.get_unitary_matrix() has been deprecated and will be removed in a future release. For extracting matrices of operations and quantum functions, please use qml.matrix(). (#2248)

    • The qml.finite_diff() function has been deprecated and will be removed in an upcoming release. Instead, qml.gradients.finite_diff() can be used to compute purely quantum gradients (that is, gradients of tapes or QNode). (#2212)

    • The MultiControlledX operation now accepts a single wires keyword argument for both control_wires and wires. The single wires keyword should be all the control wires followed by a single target wire. (#2121) (#2278)

    Breaking changes

    • The representation of an operator as a matrix has been overhauled. (#1996)

      The "canonical matrix", which is independent of wires, is now defined in the static method compute_matrix() instead of _matrix. By default, this method is assumed to take all parameters and non-trainable hyperparameters that define the operation.

      >>> qml.RX.compute_matrix(0.5)
      [[0.96891242+0.j         0.        -0.24740396j]
       [0.        -0.24740396j 0.96891242+0.j        ]]
      

      If no canonical matrix is specified for a gate, compute_matrix() raises a MatrixUndefinedError.

    • The generator property has been updated to an instance method, Operator.generator(). It now returns an instantiated operation, representing the generator of the instantiated operator. (#2030) (#2061)

      Various operators have been updated to specify the generator as either an Observable, Tensor, Hamiltonian, SparseHamiltonian, or Hermitian operator.

      In addition, qml.generator(operation) has been added to aid in retrieving generator representations of operators.

    • The argument wires in heisenberg_obs, heisenberg_expand and heisenberg_tr was renamed to wire_order to be consistent with other matrix representations. (#2051)

    • The property kraus_matrices has been changed to a method, and _kraus_matrices renamed to compute_kraus_matrices, which is now a static method. (#2055)

    • The pennylane.measure module has been renamed to pennylane.measurements. (#2236)

    Bug fixes

    • The basis property of qml.SWAP was set to "X", which is incorrect; it is now set to None. (#2287)

    • The qml.RandomLayers template now decomposes when the weights are a list of lists. (#2266)

    • The qml.QubitUnitary operation now supports just-in-time compilation using JAX. (#2249)

    • Fixes a bug in the JAX interface where DeviceArray objects were not being converted to NumPy arrays before executing an external device. (#2255)

    • The qml.ctrl transform now works correctly with gradient transforms such as the parameter-shift rule. (#2238)

    • Fixes a bug in which passing required arguments into operations as keyword arguments would throw an error because the documented call signature didn't match the function definition. (#1976)

    • The operation OrbitalRotation previously was wrongfully registered to satisfy the four-term parameter shift rule. The correct eight-term rule will now be used when using the parameter-shift rule. (#2180)

    • Fixes a bug where qml.gradients.param_shift_hessian would produce an error whenever all elements of the Hessian are known in advance to be 0. (#2299)

    Documentation

    • The developer guide on adding templates and the architecture overview were rewritten to reflect the past and planned changes of the operator refactor. (#2066)

    • Links to the Strawberry Fields documentation for information on the CV model. (#2259)

    • Fixes the documentation example for qml.QFT. (#2232)

    • Fixes the documentation example for using qml.sample with jax.jit. (#2196)

    • The qml.numpy subpackage is now included in the PennyLane API documentation. (#2179)

    • Improves the documentation of RotosolveOptimizer regarding the usage of the passed substep_optimizer and its keyword arguments. (#2160)

    • Ensures that signatures of @qml.qfunc_transform decorated functions display correctly in the docs. (#2286)

    • Docstring examples now display using the updated text-based circuit drawer. (#2252)

    • Add docstring to OrbitalRotation.grad_recipe. (#2193)

    Contributors

    This release contains contributions from (in alphabetical order):

    Catalina Albornoz, Jack Y. Araz, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Olivia Di Matteo, Christian Gogolin, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Angus Lowe, Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Lee James O'Riordan, Chae-Yeun Park, Maria Schuld, Jay Soni, Antal Száva, David Wierichs.

    Source code(tar.gz)
    Source code(zip)
  • v0.21.0(Feb 8, 2022)

    New features since last release

    Reduce qubit requirements of simulating Hamiltonians ⚛️

    • Functions for tapering qubits based on molecular symmetries have been added, following results from Setia et al. (#1966) (#1974) (#2041) (#2042)

      With this functionality, a molecular Hamiltonian and the corresponding Hartree-Fock (HF) state can be transformed to a new Hamiltonian and HF state that acts on a reduced number of qubits, respectively.

      # molecular geometry
      symbols = ["He", "H"]
      geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.4588684632]])
      mol = qml.hf.Molecule(symbols, geometry, charge=1)
      
      # generate the qubit Hamiltonian
      H = qml.hf.generate_hamiltonian(mol)(geometry)
      
      # determine Hamiltonian symmetries
      generators, paulix_ops = qml.hf.generate_symmetries(H, len(H.wires))
      opt_sector = qml.hf.optimal_sector(H, generators, mol.n_electrons)
      
      # taper the Hamiltonian
      H_tapered = qml.hf.transform_hamiltonian(H, generators, paulix_ops, opt_sector)
      

      We can compare the number of qubits required by the original Hamiltonian and the tapered Hamiltonian:

      >>> len(H.wires)
      4
      >>> len(H_tapered.wires)
      2
      

      For quantum chemistry algorithms, the Hartree-Fock state can also be tapered:

      n_elec = mol.n_electrons
      n_qubits = mol.n_orbitals * 2
      
      hf_tapered = qml.hf.transform_hf(
          generators, paulix_ops, opt_sector, n_elec, n_qubits
      )
      
      >>> hf_tapered
      tensor([1, 1], requires_grad=True)
      

    New tensor network templates 🪢

    • Quantum circuits with the shape of a matrix product state tensor network can now be easily implemented using the new qml.MPS template, based on the work arXiv:1803.11537. (#1871)

      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_wires = 4
      n_block_wires = 2
      n_params_block = 2
      template_weights = np.array([[0.1, -0.3], [0.4, 0.2], [-0.15, 0.5]], requires_grad=True)
      
      dev = qml.device("default.qubit", wires=range(n_wires))
      
      @qml.qnode(dev)
      def circuit(weights):
          qml.MPS(range(n_wires), n_block_wires, block, n_params_block, weights)
          return qml.expval(qml.PauliZ(wires=n_wires - 1))
      

      The resulting circuit is:

      >>> print(qml.draw(circuit, expansion_strategy="device")(template_weights))
      0: ──╭C──RY(0.1)───────────────────────────────┤
      1: ──╰X──RY(-0.3)──╭C──RY(0.4)─────────────────┤
      2: ────────────────╰X──RY(0.2)──╭C──RY(-0.15)──┤
      3: ─────────────────────────────╰X──RY(0.5)────┤ ⟨Z⟩
      
    • Added a template for tree tensor networks, qml.TTN. (#2043)

      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_wires = 4
      n_block_wires = 2
      n_params_block = 2
      n_blocks = qml.MPS.get_n_blocks(range(n_wires), n_block_wires)
      template_weights = [[0.1, -0.3]] * n_blocks
      
      dev = qml.device("default.qubit", wires=range(n_wires))
      
      @qml.qnode(dev)
      def circuit(template_weights):
          qml.TTN(range(n_wires), n_block_wires, block, n_params_block, template_weights)
          return qml.expval(qml.PauliZ(wires=n_wires - 1))
      

      The resulting circuit is:

      >>> print(qml.draw(circuit, expansion_strategy="device")(template_weights))
      0: ──╭C──RY(0.1)─────────────────┤
      1: ──╰X──RY(-0.3)──╭C──RY(0.1)───┤
      2: ──╭C──RY(0.1)───│─────────────┤
      3: ──╰X──RY(-0.3)──╰X──RY(-0.3)──┤ ⟨Z⟩
      

    Generalized RotosolveOptmizer 📉

    • The RotosolveOptimizer has been generalized to arbitrary frequency spectra in the cost function. Also note the changes in behaviour listed under Breaking changes. (#2081)

      Previously, the RotosolveOptimizer only supported variational circuits using special gates such as single-qubit Pauli rotations. Now, circuits with arbitrary gates are supported natively without decomposition, as long as the frequencies of the gate parameters are known. This new generalization extends the Rotosolve optimization method to a larger class of circuits, and can reduce the cost of the optimization compared to decomposing all gates to single-qubit rotations.

      Consider the QNode

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def qnode(x, Y):
          qml.RX(2.5 * x, wires=0)
          qml.CNOT(wires=[0, 1])
          qml.RZ(0.3 * Y[0], wires=0)
          qml.CRY(1.1 * Y[1], wires=[1, 0])
          return qml.expval(qml.PauliX(0) @ qml.PauliZ(1))
      
      x = np.array(0.8, requires_grad=True)
      Y = np.array([-0.2, 1.5], requires_grad=True)
      

      Its frequency spectra can be easily obtained via qml.fourier.qnode_spectrum:

      >>> spectra = qml.fourier.qnode_spectrum(qnode)(x, Y)
      >>> spectra
      {'x': {(): [-2.5, 0.0, 2.5]},
       'Y': {(0,): [-0.3, 0.0, 0.3], (1,): [-1.1, -0.55, 0.0, 0.55, 1.1]}}
      

      We may then initialize the RotosolveOptimizer and minimize the QNode cost function by providing this information about the frequency spectra. We also compare the cost at each step to the initial cost.

      >>> cost_init = qnode(x, Y)
      >>> opt = qml.RotosolveOptimizer()
      >>> for _ in range(2):
      ...     x, Y = opt.step(qnode, x, Y, spectra=spectra)
      ...     print(f"New cost: {np.round(qnode(x, Y), 3)}; Initial cost: {np.round(cost_init, 3)}")
      New cost: 0.0; Initial cost: 0.706
      New cost: -1.0; Initial cost: 0.706
      

      The optimization with RotosolveOptimizer is performed in substeps. The minimal cost of these substeps can be retrieved by setting full_output=True.

      >>> x = np.array(0.8, requires_grad=True)
      >>> Y = np.array([-0.2, 1.5], requires_grad=True)
      >>> opt = qml.RotosolveOptimizer()
      >>> for _ in range(2):
      ...     (x, Y), history = opt.step(qnode, x, Y, spectra=spectra, full_output=True)
      ...     print(f"New cost: {np.round(qnode(x, Y), 3)} reached via substeps {np.round(history, 3)}")
      New cost: 0.0 reached via substeps [-0.  0.  0.]
      New cost: -1.0 reached via substeps [-1. -1. -1.]
      

      However, note that these intermediate minimal values are evaluations of the reconstructions that Rotosolve creates and uses internally for the optimization, and not of the original objective function. For noisy cost functions, these intermediate evaluations may differ significantly from evaluations of the original cost function.

    Improved JAX support 💻

    • The JAX interface now supports evaluating vector-valued QNodes. (#2110)

      Vector-valued QNodes include those with:

      • qml.probs;
      • qml.state;
      • qml.sample or
      • multiple qml.expval / qml.var measurements.

      Consider a QNode that returns basis-state probabilities:

      dev = qml.device('default.qubit', wires=2)
      x = jnp.array(0.543)
      y = jnp.array(-0.654)
      
      @qml.qnode(dev, diff_method="parameter-shift", interface="jax")
      def circuit(x, y):
          qml.RX(x, wires=[0])
          qml.RY(y, wires=[1])
          qml.CNOT(wires=[0, 1])
          return qml.probs(wires=[1])
      

      The QNode can be evaluated and its jacobian can be computed:

      >>> circuit(x, y)
      DeviceArray([0.8397495 , 0.16025047], dtype=float32)
      >>> jax.jacobian(circuit, argnums=[0, 1])(x, y)
      (DeviceArray([-0.2050439,  0.2050439], dtype=float32, weak_type=True),
       DeviceArray([ 0.26043, -0.26043], dtype=float32, weak_type=True))
      

      Note that jax.jit is not yet supported for vector-valued QNodes.

    Speedier quantum natural gradient ⚡

    • A new function for computing the metric tensor on simulators, qml.adjoint_metric_tensor, has been added, that uses classically efficient methods to massively improve performance. (#1992)

      This method, detailed in Jones (2020), computes the metric tensor using four copies of the state vector and a number of operations that scales quadratically in the number of trainable parameters.

      Note that as it makes use of state cloning, it is inherently classical and can only be used with statevector simulators and shots=None.

      It is particularly useful for larger circuits for which backpropagation requires inconvenient or even unfeasible amounts of storage, but is slower. Furthermore, the adjoint method is only available for analytic computation, not for measurements simulation with shots!=None.

      dev = qml.device("default.qubit", wires=3)
      
      @qml.qnode(dev)
      def circuit(x, y):
          qml.Rot(*x[0], wires=0)
          qml.Rot(*x[1], wires=1)
          qml.Rot(*x[2], wires=2)
          qml.CNOT(wires=[0, 1])
          qml.CNOT(wires=[1, 2])
          qml.CNOT(wires=[2, 0])
          qml.RY(y[0], wires=0)
          qml.RY(y[1], wires=1)
          qml.RY(y[0], wires=2)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliY(1))
      
      x = np.array([[0.2, 0.4, -0.1], [-2.1, 0.5, -0.2], [0.1, 0.7, -0.6]], requires_grad=False)
      y = np.array([1.3, 0.2], requires_grad=True)
      
      >>> qml.adjoint_metric_tensor(circuit)(x, y)
      tensor([[ 0.25495723, -0.07086695],
              [-0.07086695,  0.24945606]], requires_grad=True)
      

      Computational cost

      The adjoint method uses :math:2P^2+4P+1 gates and state cloning operations if the circuit is composed only of trainable gates, where :math:P is the number of trainable operations. If non-trainable gates are included, each of them is applied about :math:n^2-n times, where :math:n is the number of trainable operations that follow after the respective non-trainable operation in the circuit. This means that non-trainable gates later in the circuit are executed less often, making the adjoint method a bit cheaper if such gates appear later. The adjoint method requires memory for 4 independent state vectors, which corresponds roughly to storing a state vector of a system with 2 additional qubits.

    Compute the Hessian on hardware ⬆️

    • A new gradient transform qml.gradients.param_shift_hessian has been added to directly compute the Hessian (2nd order partial derivative matrix) of QNodes on hardware. (#1884)

      The function generates parameter-shifted tapes which allow the Hessian to be computed analytically on hardware and software devices. Compared to using an auto-differentiation framework to compute the Hessian via parameter shifts, this function will use fewer device invocations and can be used to inspect the parameter-shifted "Hessian tapes" directly. The function remains fully differentiable on all supported PennyLane interfaces.

      Additionally, the parameter-shift Hessian comes with a new batch transform decorator @qml.gradients.hessian_transform, which can be used to create custom Hessian functions.

      The following code demonstrates how to use the parameter-shift Hessian:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(x):
          qml.RX(x[0], wires=0)
          qml.RY(x[1], wires=0)
          return qml.expval(qml.PauliZ(0))
      
      x = np.array([0.1, 0.2], requires_grad=True)
      
      hessian = qml.gradients.param_shift_hessian(circuit)(x)
      
      >>> hessian
      tensor([[-0.97517033,  0.01983384],
              [ 0.01983384, -0.97517033]], requires_grad=True)
      

    Improvements

    • The qml.transforms.insert transform now supports adding operation after or before certain specific gates. (#1980)

    • Added a modified version of the simplify function to the hf module. (#2103)

      This function combines redundant terms in a Hamiltonian and eliminates terms with a coefficient smaller than a cutoff value. The new function makes construction of molecular Hamiltonians more efficient. For LiH, as an example, the time to construct the Hamiltonian is reduced roughly by a factor of 20.

    • The QAOA module now accepts both NetworkX and RetworkX graphs as function inputs. (#1791)

    • The CircuitGraph, used to represent circuits via directed acyclic graphs, now uses RetworkX for its internal representation. This results in significant speedup for algorithms that rely on a directed acyclic graph representation. (#1791)

    • For subclasses of Operator where the number of parameters is known before instantiation, the num_params is reverted back to being a static property. This allows to programmatically know the number of parameters before an operator is instantiated without changing the user interface. A test was added to ensure that different ways of defining num_params work as expected. (#2101) (#2135)

    • A WireCut operator has been added for manual wire cut placement when constructing a QNode. (#2093)

    • The new function qml.drawer.tape_text produces a string drawing of a tape. This function differs in implementation and minor stylistic details from the old string circuit drawing infrastructure. (#1885)

    • The RotosolveOptimizer now raises an error if no trainable arguments are detected, instead of silently skipping update steps for all arguments. (#2109)

    • The function qml.math.safe_squeeze is introduced and gradient_transform allows for QNode argument axes of size 1. (#2080)

      qml.math.safe_squeeze wraps qml.math.squeeze, with slight modifications:

      • When provided the axis keyword argument, axes that do not have size 1 will be ignored, instead of raising an error.

      • The keyword argument exclude_axis allows to explicitly exclude axes from the squeezing.

    • The adjoint transform now raises and error whenever the object it is applied to is not callable. (#2060)

      An example is a list of operations to which one might apply qml.adjoint:

      dev = qml.device("default.qubit", wires=2)
      @qml.qnode(dev)
      def circuit_wrong(params):
          # Note the difference:                  v                         v
          qml.adjoint(qml.templates.AngleEmbedding(params, wires=dev.wires))
          return qml.state()
      
      @qml.qnode(dev)
      def circuit_correct(params):
          # Note the difference:                  v                         v
          qml.adjoint(qml.templates.AngleEmbedding)(params, wires=dev.wires)
          return qml.state()
      
      params = list(range(1, 3))
      

      Evaluating circuit_wrong(params) now raises a ValueError and if we apply qml.adjoint correctly, we get

      >>> circuit_correct(params)
      [ 0.47415988+0.j          0.         0.73846026j  0.         0.25903472j
       -0.40342268+0.j        ]
      
    • A precision argument has been added to the tape's to_openqasm function to control the precision of parameters. (#2071)

    • Interferometer now has a shape method. (#1946)

    • The Barrier and Identity operations now support the adjoint method. (#2062) (#2063)

    • qml.BasisStatePreparation now supports the batch_params decorator. (#2091)

    • Added a new multi_dispatch decorator that helps ease the definition of new functions inside PennyLane. The decorator is used throughout the math module, demonstrating use cases. (#2082) (#2096)

      We can decorate a function, indicating the arguments that are tensors handled by the interface:

      >>> @qml.math.multi_dispatch(argnum=[0, 1])
      ... def some_function(tensor1, tensor2, option, like):
      ...     # the interface string is stored in ``like``.
      ...     ...
      

      Previously, this was done using the private utility function _multi_dispatch.

      >>> def some_function(tensor1, tensor2, option):
      ...     interface = qml.math._multi_dispatch([tensor1, tensor2])
      ...     ...
      
    • The IsingZZ gate was added to the diagonal_in_z_basis attribute. For this an explicit _eigvals method was added. (#2113)

    • The IsingXX, IsingYY and IsingZZ gates were added to the composable_rotations attribute. (#2113)

    Breaking changes

    • QNode arguments will no longer be considered trainable by default when using the Autograd interface. In order to obtain derivatives with respect to a parameter, it should be instantiated via PennyLane's NumPy wrapper using the requires_grad=True attribute. The previous behaviour was deprecated in version v0.19.0 of PennyLane. (#2116) (#2125) (#2139) (#2148) (#2156)

      from pennylane import numpy as np
      
      @qml.qnode(qml.device("default.qubit", wires=2))
      def circuit(x):
        ...
      
      x = np.array([0.1, 0.2], requires_grad=True)
      qml.grad(circuit)(x)
      

      For the qml.grad and qml.jacobian functions, trainability can alternatively be indicated via the argnum keyword:

      import numpy as np
      
      @qml.qnode(qml.device("default.qubit", wires=2))
      def circuit(hyperparam, param):
        ...
      
      x = np.array([0.1, 0.2])
      qml.grad(circuit, argnum=1)(0.5, x)
      
    • qml.jacobian now follows a different convention regarding its output shape. (#2059)

      Previously, qml.jacobian would attempt to stack the Jacobian for multiple QNode arguments, which succeeded whenever the arguments have the same shape. In this case, the stacked Jacobian would also be transposed, leading to the output shape (*reverse_QNode_args_shape, *reverse_output_shape, num_QNode_args)

      If no stacking and transposing occurs, the output shape instead is a tuple where each entry corresponds to one QNode argument and has the shape (*output_shape, *QNode_arg_shape).

      This breaking change alters the behaviour in the first case and removes the attempt to stack and transpose, so that the output always has the shape of the second type.

      Note that the behaviour is unchanged --- that is, the Jacobian tuple is unpacked into a single Jacobian --- if argnum=None and there is only one QNode argument with respect to which the differentiation takes place, or if an integer is provided as argnum.

      A workaround that allowed qml.jacobian to differentiate multiple QNode arguments will no longer support higher-order derivatives. In such cases, combining multiple arguments into a single array is recommended.

    • qml.metric_tensor, qml.adjoint_metric_tensor and qml.transforms.classical_jacobian now follow a different convention regarding their output shape when being used with the Autograd interface (#2059)

      See the previous entry for details. This breaking change immediately follows from the change in qml.jacobian whenever hybrid=True is used in the above methods.

    • The behaviour of RotosolveOptimizer has been changed regarding its keyword arguments. (#2081)

      The keyword arguments optimizer and optimizer_kwargs for the RotosolveOptimizer have been renamed to substep_optimizer and substep_kwargs, respectively. Furthermore they have been moved from step and step_and_cost to the initialization __init__.

      The keyword argument num_freqs has been renamed to nums_frequency and is expected to take a different shape now: Previously, it was expected to be an int or a list of entries, with each entry in turn being either an int or a list of int entries. Now the expected structure is a nested dictionary, matching the formatting expected by qml.fourier.reconstruct This also matches the expected formatting of the new keyword arguments spectra and shifts.

      For more details, see the RotosolveOptimizer documentation.

    Deprecations

    • Deprecates the caching ability provided by QubitDevice. (#2154)

      Going forward, the preferred way is to use the caching abilities of the QNode:

      dev = qml.device("default.qubit", wires=2)
      
      cache = {}
      
      @qml.qnode(dev, diff_method='parameter-shift', cache=cache)
      def circuit():
          qml.RY(0.345, wires=0)
          return qml.expval(qml.PauliZ(0))
      
      >>> for _ in range(10):
      ...    circuit()
      >>> dev.num_executions
      1
      

    Bug fixes

    • Fixes a bug where an incorrect number of executions are recorded by a QNode using a custom cache with diff_method="backprop". (#2171)

    • Fixes a bug where the default.qubit.jax device can't be used with diff_method=None and jitting. (#2136)

    • Fixes a bug where the Torch interface was not properly unwrapping Torch tensors to NumPy arrays before executing gradient tapes on devices. (#2117)

    • Fixes a bug for the TensorFlow interface where the dtype of input tensors was not cast. (#2120)

    • Fixes a bug where batch transformed QNodes would fail to apply batch transforms provided by the underlying device. (#2111)

    • An error is now raised during QNode creation if backpropagation is requested on a device with finite shots specified. (#2114)

    • Pytest now ignores any DeprecationWarning raised within autograd's numpy_wrapper module. Other assorted minor test warnings are fixed. (#2007)

    • Fixes a bug where the QNode was not correctly diagonalizing qubit-wise commuting observables. (#2097)

    • Fixes a bug in gradient_transform where the hybrid differentiation of circuits with a single parametrized gate failed and QNode argument axes of size 1 where removed from the output gradient. (#2080)

    • The available diff_method options for QNodes has been corrected in both the error messages and the documentation. (#2078)

    • Fixes a bug in DefaultQubit where the second derivative of QNodes at positions corresponding to vanishing state vector amplitudes is wrong. (#2057)

    • Fixes a bug where PennyLane didn't require v0.20.0 of PennyLane-Lightning, but raised an error with versions of Lightning earlier than v0.20.0 due to the new batch execution pipeline. (#2033)

    • Fixes a bug in classical_jacobian when used with Torch, where the Jacobian of the preprocessing was also computed for non-trainable parameters. (#2020)

    • Fixes a bug in queueing of the two_qubit_decomposition method that originally led to circuits with >3 two-qubit unitaries failing when passed through the unitary_to_rot optimization transform. (#2015)

    • Fixes a bug which allows using jax.jit to be compatible with circuits which return qml.probs when the default.qubit.jax is provided with a custom shot vector. (#2028)

    • Updated the adjoint() method for non-parametric qubit operations to solve a bug where repeated adjoint() calls don't return the correct operator. (#2133)

    • Fixed a bug in insert() which prevented operations that inherited from multiple classes to be inserted. (#2172)

    Documentation

    • Fixes an error in the signs of equations in the DoubleExcitation page. (#2072)

    • Extends the interfaces description page to explicitly mention device compatibility. (#2031)

    Contributors

    This release contains contributions from (in alphabetical order):

    Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Sam Banning, Thomas Bromley, Esther Cruz, Christian Gogolin, Nathan Killoran, Christina Lee, Olivia Di Matteo, Diego Guala, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Edward Jiang, Ankit Khandelwal, Korbinian Kottmann, Romain Moyard, Lee James O'Riordan, Maria Schuld, Jay Soni, Antal Száva, David Wierichs, Shaoming Zhang.

    Source code(tar.gz)
    Source code(zip)
  • v0.20.0(Dec 14, 2021)

    New features since last release

    Shiny new circuit drawer!🎨🖌️

    • PennyLane now supports drawing a QNode with matplotlib! (#1803) (#1811) (#1931) (#1954)

      dev = qml.device("default.qubit", wires=4)
      
      @qml.qnode(dev)
      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))
      fig, ax = qml.draw_mpl(circuit)(1.2345, 1.2345)
      fig.show()
      

    New and improved quantum-aware optimizers

    • Added qml.LieAlgebraOptimizer, a new quantum-aware Lie Algebra optimizer that allows one to perform gradient descent on the special unitary group. (#1911)

      dev = qml.device("default.qubit", wires=2)
      H = -1.0 * qml.PauliX(0) - qml.PauliZ(1) - qml.PauliY(0) @ qml.PauliX(1)
      
      @qml.qnode(dev)
      def circuit():
          qml.RX(0.1, wires=[0])
          qml.RY(0.5, wires=[1])
          qml.CNOT(wires=[0,1])
          qml.RY(0.6, wires=[0])
          return qml.expval(H)
      opt = qml.LieAlgebraOptimizer(circuit=circuit, stepsize=0.1)
      

      Note that, unlike other optimizers, the LieAlgebraOptimizer accepts a QNode with no parameters, and instead grows the circuit by pending operations during the optimization:

      >>> circuit()
      tensor(-1.3351865, requires_grad=True)
      >>> circuit1, cost = opt.step_and_cost()
      >>> circuit1()
      tensor(-1.99378872, requires_grad=True)
      

      For more details, see the LieAlgebraOptimizer documentation.

    • The qml.metric_tensor transform can now be used to compute the full tensor, beyond the block diagonal approximation. (#1725)

      This is performed using Hadamard tests, and requires an additional wire on the device to execute the circuits produced by the transform, as compared to the number of wires required by the original circuit. The transform defaults to computing the full tensor, which can be controlled by the approx keyword argument.

      As an example, consider the QNode

      dev = qml.device("default.qubit", wires=3)
      
      @qml.qnode(dev)
      def circuit(weights):
          qml.RX(weights[0], wires=0)
          qml.RY(weights[1], wires=0)
          qml.CNOT(wires=[0, 1])
          qml.RZ(weights[2], wires=1)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      
      weights = np.array([0.2, 1.2, -0.9], requires_grad=True)
      

      Then we can compute the (block) diagonal metric tensor as before, now using the approx="block-diag" keyword:

      >>> qml.metric_tensor(circuit, approx="block-diag")(weights)
      [[0.25       0.         0.        ]
       [0.         0.24013262 0.        ]
       [0.         0.         0.21846983]]
      

      Instead, we now can also compute the full metric tensor, using Hadamard tests on the additional wire of the device:

      >>> qml.metric_tensor(circuit)(weights)
      [[ 0.25        0.         -0.23300977]
       [ 0.          0.24013262  0.01763859]
       [-0.23300977  0.01763859  0.21846983]]
      

      See the metric tensor documentation for more information and usage details.

    Faster performance with optimized quantum workflows

    • The QNode has been re-written to support batch execution across the board, custom gradients, better decomposition strategies, and higher-order derivatives. (#1807) (#1969)

      • Internally, if multiple circuits are generated for simultaneous execution, they will be packaged into a single job for execution on the device. This can lead to significant performance improvement when executing the QNode on remote quantum hardware or simulator devices with parallelization capabilities.

      • Custom gradient transforms can be specified as the differentiation method:

        @qml.gradients.gradient_transform
        def my_gradient_transform(tape):
            ...
            return tapes, processing_fn
        
        @qml.qnode(dev, diff_method=my_gradient_transform)
        def circuit():
        

      For breaking changes related to the use of the new QNode, refer to the Breaking Changes section.

      Note that the old QNode remains accessible at @qml.qnode_old.qnode, however this will be removed in the next release.

    • Custom decompositions can now be applied to operations at the device level. (#1900)

      For example, suppose we would like to implement the following QNode:

      def circuit(weights):
          qml.BasicEntanglerLayers(weights, wires=[0, 1, 2])
          return qml.expval(qml.PauliZ(0))
      
      original_dev = qml.device("default.qubit", wires=3)
      original_qnode = qml.QNode(circuit, original_dev)
      
      >>> weights = np.array([[0.4, 0.5, 0.6]])
      >>> print(qml.draw(original_qnode, expansion_strategy="device")(weights))
       0: ──RX(0.4)──╭C──────╭X──┤ ⟨Z⟩
       1: ──RX(0.5)──╰X──╭C──│───┤
       2: ──RX(0.6)──────╰X──╰C──┤
      

      Now, let's swap out the decomposition of the CNOT gate into CZ and Hadamard, and furthermore the decomposition of Hadamard into RZ and RY rather than the decomposition already available in PennyLane. We define the two decompositions like so, and pass them to a device:

      def custom_cnot(wires):
          return [
              qml.Hadamard(wires=wires[1]),
              qml.CZ(wires=[wires[0], wires[1]]),
              qml.Hadamard(wires=wires[1])
          ]
      
      def custom_hadamard(wires):
          return [
              qml.RZ(np.pi, wires=wires),
              qml.RY(np.pi / 2, wires=wires)
          ]
      
      # Can pass the operation itself, or a string
      custom_decomps = {qml.CNOT : custom_cnot, "Hadamard" : custom_hadamard}
      
      decomp_dev = qml.device("default.qubit", wires=3, custom_decomps=custom_decomps)
      decomp_qnode = qml.QNode(circuit, decomp_dev)
      

      Now when we draw or run a QNode on this device, the gates will be expanded according to our specifications:

      >>> print(qml.draw(decomp_qnode, expansion_strategy="device")(weights))
       0: ──RX(0.4)──────────────────────╭C──RZ(3.14)──RY(1.57)──────────────────────────╭Z──RZ(3.14)──RY(1.57)──┤ ⟨Z⟩
       1: ──RX(0.5)──RZ(3.14)──RY(1.57)──╰Z──RZ(3.14)──RY(1.57)──╭C──────────────────────│───────────────────────┤
       2: ──RX(0.6)──RZ(3.14)──RY(1.57)──────────────────────────╰Z──RZ(3.14)──RY(1.57)──╰C──────────────────────┤
      

      A separate context manager, set_decomposition, has also been implemented to enable application of custom decompositions on devices that have already been created.

      >>> with qml.transforms.set_decomposition(custom_decomps, original_dev):
      ...     print(qml.draw(original_qnode, expansion_strategy="device")(weights))
       0: ──RX(0.4)──────────────────────╭C──RZ(3.14)──RY(1.57)──────────────────────────╭Z──RZ(3.14)──RY(1.57)──┤ ⟨Z⟩
       1: ──RX(0.5)──RZ(3.14)──RY(1.57)──╰Z──RZ(3.14)──RY(1.57)──╭C──────────────────────│───────────────────────┤
       2: ──RX(0.6)──RZ(3.14)──RY(1.57)──────────────────────────╰Z──RZ(3.14)──RY(1.57)──╰C──────────────────────┤
      
    • Given an operator of the form :math:U=e^{iHt}, where :math:H has commuting terms and known eigenvalues, qml.gradients.generate_shift_rule computes the generalized parameter shift rules for determining the gradient of the expectation value :math:f(t) = \langle 0|U(t)^\dagger \hat{O} U(t)|0\rangle on hardware. (#1788) (#1932)

      Given $H = \sum_i a_i h_i$, where the eigenvalues of :math:H are known and all :math:h_i commute, we can compute the frequencies (the unique positive differences of any two eigenvalues) using qml.gradients.eigvals_to_frequencies.

      qml.gradients.generate_shift_rule can then be used to compute the parameter shift rules to compute :math:f'(t) using 2R shifted cost function evaluations. This becomes cheaper than the standard application of the chain rule and two-term shift rule when R is less than the number of Pauli words in the generator.

      For example, consider the case where :math:H has eigenspectrum (-1, 0, 1):

      >>> frequencies = qml.gradients.eigvals_to_frequencies((-1, 0, 1))
      >>> frequencies
      (1, 2)
      >>> coeffs, shifts = qml.gradients.generate_shift_rule(frequencies)
      >>> coeffs
      array([ 0.85355339, -0.85355339, -0.14644661,  0.14644661])
      >>> shifts
      array([ 0.78539816, -0.78539816,  2.35619449, -2.35619449])
      

      As we can see, generate_shift_rule returns four coefficients :math:c_i and shifts :math:s_i corresponding to a four term parameter shift rule. The gradient can then be reconstructed via:

      .. math:: \frac{\partial}{\partial\phi}f = \sum_{i} c_i f(\phi + s_i),

      where :math:f(\phi) = \langle 0|U(\phi)^\dagger \hat{O} U(\phi)|0\rangle for some observable :math:\hat{O} and the unitary :math:U(\phi)=e^{iH\phi}.

    Support for TensorFlow AutoGraph mode with quantum hardware

    • It is now possible to use TensorFlow's AutoGraph mode with QNodes on all devices and with arbitrary differentiation methods. Previously, AutoGraph mode only support diff_method="backprop". This will result in significantly more performant model execution, at the cost of a more expensive initial compilation. (#1866)

      Use AutoGraph to convert your QNodes or cost functions into TensorFlow graphs by decorating them with @tf.function:

      dev = qml.device("lightning.qubit", wires=2)
      
      @qml.qnode(dev, diff_method="adjoint", interface="tf", max_diff=1)
      def circuit(x):
          qml.RX(x[0], wires=0)
          qml.RY(x[1], wires=1)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliZ(0))
      
      @tf.function
      def cost(x):
          return tf.reduce_sum(circuit(x))
      
      x = tf.Variable([0.5, 0.7], dtype=tf.float64)
      
      with tf.GradientTape() as tape:
          loss = cost(x)
      
      grad = tape.gradient(loss, x)
      

      The initial execution may take slightly longer than when executing the circuit in eager mode; this is because TensorFlow is tracing the function to create the graph. Subsequent executions will be much more performant.

      Note that using AutoGraph with backprop-enabled devices, such as default.qubit, will yield the best performance.

      For more details, please see the TensorFlow AutoGraph documentation.

    Characterize your quantum models with classical QNode reconstruction

    • The qml.fourier.reconstruct function is added. It can be used to reconstruct QNodes outputting expectation values along a specified parameter dimension, with a minimal number of calls to the original QNode. The returned reconstruction is exact and purely classical, and can be evaluated without any quantum executions. (#1864)

      The reconstruction technique differs for functions with equidistant frequencies that are reconstructed using the function value at equidistant sampling points, and for functions with arbitrary frequencies reconstructed using arbitrary sampling points.

      As an example, consider the following QNode:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(x, Y, f=1.0):
          qml.RX(f * x, wires=0)
          qml.RY(Y[0], wires=0)
          qml.RY(Y[1], wires=1)
          qml.CNOT(wires=[0, 1])
          qml.RY(3 * Y[1], wires=1)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      

      It has three variational parameters overall: A scalar input x and an array-valued input Y with two entries. Additionally, we can tune the dependence on x with the frequency f. We then can reconstruct the QNode output function with respect to x via

      >>> x = 0.3
      >>> Y = np.array([0.1, -0.9])
      >>> rec = qml.fourier.reconstruct(circuit, ids="x", nums_frequency={"x": {0: 1}})(x, Y)
      >>> rec
      {'x': {0: <function pennylane.fourier.reconstruct._reconstruct_equ.<locals>._reconstruction(x)>}}
      

      As we can see, we get a nested dictionary in the format of the input nums_frequency with functions as values. These functions are simple float-to-float callables:

      >>> univariate = rec["x"][0]
      >>> univariate(x)
      -0.880208251507
      

      For more details on usage, reconstruction cost and differentiability support, please see the fourier.reconstruct docstring.

    State-of-the-art operations and templates

    • A circuit template for time evolution under a commuting Hamiltonian utilizing generalized parameter shift rules for cost function gradients is available as qml.CommutingEvolution. (#1788)

      If the template is handed a frequency spectrum during its instantiation, then generate_shift_rule is internally called to obtain the general parameter shift rules with respect to CommutingEvolution's :math:t parameter, otherwise the shift rule for a decomposition of CommutingEvolution will be used.

      The template can be initialized within a qnode as:

      import pennylane as qml
      
      n_wires = 2
      dev = qml.device('default.qubit', wires=n_wires)
      
      coeffs = [1, -1]
      obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)]
      hamiltonian = qml.Hamiltonian(coeffs, obs)
      frequencies = (2,4)
      
      @qml.qnode(dev)
      def circuit(time):
          qml.PauliX(0)
          qml.CommutingEvolution(hamiltonian, time, frequencies)
          return qml.expval(qml.PauliZ(0))
      

      Note that there is no internal validation that 1) the input qml.Hamiltonian is fully commuting and 2) the eigenvalue frequency spectrum is correct, since these checks become prohibitively expensive for large Hamiltonians.

    • The qml.Barrier() operator has been added. With it we can separate blocks in compilation or use it as a visual tool. (#1844)

    • Added the identity observable to be an operator. Now we can explicitly call the identity operation on our quantum circuits for both qubit and CV devices. (#1829)

    • Added the qml.QubitDensityMatrix initialization gate for mixed state simulation. (#1850)

    • A thermal relaxation channel is added to the Noisy channels. The channel description can be found on the supplementary information of Quantum classifier with tailored quantum kernels. (#1766)

    Manipulate QNodes to your ❤️s content with new transforms

    • The merge_amplitude_embedding transformation has been created to automatically merge all gates of this type into one. (#1933)

      from pennylane.transforms import merge_amplitude_embedding
      
      dev = qml.device("default.qubit", wires = 3)
      
      @qml.qnode(dev)
      @merge_amplitude_embedding
      def qfunc():
          qml.AmplitudeEmbedding([0,1,0,0], wires = [0,1])
          qml.AmplitudeEmbedding([0,1], wires = 2)
          return qml.expval(qml.PauliZ(wires = 0))
      
      >>> print(qml.draw(qnode)())
       0: ──╭AmplitudeEmbedding(M0)──┤ ⟨Z⟩
       1: ──├AmplitudeEmbedding(M0)──┤
       2: ──╰AmplitudeEmbedding(M0)──┤
       M0 =
       [0.+0.j 0.+0.j 0.+0.j 1.+0.j 0.+0.j 0.+0.j 0.+0.j 0.+0.j]
      
    • The undo_swaps transformation has been created to automatically remove all swaps of a circuit. (#1960)

      dev = qml.device('default.qubit', wires=3)
      
      @qml.qnode(dev)
      @qml.transforms.undo_swaps
      def qfunc():
          qml.Hadamard(wires=0)
          qml.PauliX(wires=1)
          qml.SWAP(wires=[0,1])
          qml.SWAP(wires=[0,2])
          qml.PauliY(wires=0)
          return qml.expval(qml.PauliZ(0))
      
      
      >>> print(qml.draw(qfunc)())
       0: ──Y──┤ ⟨Z⟩
       1: ──H──┤
       2: ──X──┤
      

    Improvements

    • Added functions for computing the values of atomic and molecular orbitals at a given position. (#1867)

      The functions atomic_orbital and molecular_orbital can be used, as shown in the following codeblock, to evaluate the orbitals. By generating values of the orbitals at different positions, one can plot the spatial shape of a desired orbital.

      symbols  = ['H', 'H']
      geometry = np.array([[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], requires_grad = False)
      mol = hf.Molecule(symbols, geometry)
      hf.generate_scf(mol)()
      
      ao = mol.atomic_orbital(0)
      mo = mol.molecular_orbital(1)
      
      >>> print(ao(0.0, 0.0, 0.0))
      >>> print(mo(0.0, 0.0, 0.0))
      0.6282468778183719
      0.018251285973461928
      
    • Added support for Python 3.10. (#1964)

    • The execution of QNodes that have

      • multiple return types;
      • a return type other than Variance and Expectation

      now raises a descriptive error message when using the JAX interface. (#2011)

    • The PennyLane qchem package is now lazily imported; it will only be imported the first time it is accessed. (#1962)

    • qml.math.scatter_element_add now supports adding multiple values at multiple indices with a single function call, in all interfaces (#1864)

      For example, we may set five values of a three-dimensional tensor in the following way:

      >>> X = tf.zeros((3, 2, 9), dtype=tf.float64)
      >>> indices = [(0, 0, 1, 2, 2), (0, 0, 0, 0, 1), (1, 3, 8, 6, 7)]
      >>> values = [1 * i for i in range(1,6)]
      >>> qml.math.scatter_element_add(X, indices, values)
      <tf.Tensor: shape=(3, 2, 9), dtype=float64, numpy=
      array([[[0., 1., 0., 2., 0., 0., 0., 0., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0.]],
      
             [[0., 0., 0., 0., 0., 0., 0., 0., 3.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0.]],
      
             [[0., 0., 0., 0., 0., 0., 4., 0., 0.],
              [0., 0., 0., 0., 0., 0., 0., 5., 0.]]])>
      
    • All instances of str.format have been replace with f-strings. (#1970)

    • Tests do not loop over automatically imported and instantiated operations any more, which was opaque and created unnecessarily many tests. (#1895)

    • A decompose() method has been added to the Operator class such that we can obtain (and queue) decompositions directly from instances of operations. (#1873)

      >>> op = qml.PhaseShift(0.3, wires=0)
      >>> op.decompose()
      [RZ(0.3, wires=[0])]
      
    • qml.circuit_drawer.tape_mpl produces a matplotlib figure and axes given a tape. (#1787)

    • The AngleEmbedding, BasicEntanglerLayers and MottonenStatePreparation templates now support the batch_params decorator. (#1812) (#1883) (#1893)

    • Added a new qml.PauliError channel that allows the application of an arbitrary number of Pauli operators on an arbitrary number of wires. (#1781)

    • CircuitDrawer now supports a max_length argument to help prevent text overflows when printing circuits to the CLI. (#1892)

    • Identity operation is now part of both the ops.qubit and ops.cv modules. (#1956)

    Breaking changes

    • The QNode has been re-written to support batch execution across the board, custom gradients, better decomposition strategies, and higher-order derivatives. (#1807) (#1969)

      • Arbitrary :math:n-th order derivatives are supported on hardware using gradient transforms such as the parameter-shift rule. To specify that an :math:n-th order derivative of a QNode will be computed, the max_diff argument should be set. By default, this is set to 1 (first-order derivatives only). Increasing this value allows for higher order derivatives to be extracted, at the cost of additional (classical) computational overhead during the backwards pass.

      • When decomposing the circuit, the default decomposition strategy expansion_strategy="gradient" will prioritize decompositions that result in the smallest number of parametrized operations required to satisfy the differentiation method. While this may lead to a slight increase in classical processing, it significantly reduces the number of circuit evaluations needed to compute gradients of complicated unitaries.

        To return to the old behaviour, expansion_strategy="device" can be specified.

      Note that the old QNode remains accessible at @qml.qnode_old.qnode, however this will be removed in the next release.

    • Certain features deprecated in v0.19.0 have been removed: (#1963) (#1981)

      • The qml.template decorator (use a QuantumTape as a context manager to record operations and its operations attribute to return them, see the linked page for examples);
      • The default.tensor and default.tensor.tf experimental devices;
      • The qml.fourier.spectrum function (use the qml.fourier.circuit_spectrum or qml.fourier.qnode_spectrum functions instead);
      • The diag_approx keyword argument of qml.metric_tensor and qml.QNGOptimizer (pass approx='diag' instead).
    • The default behaviour of the qml.metric_tensor transform has been modified. By default, the full metric tensor is computed, leading to higher cost than the previous default of computing the block diagonal only. At the same time, the Hadamard tests for the full metric tensor require an additional wire on the device, so that

      >>> qml.metric_tensor(some_qnode)(weights)
      

      will revert back to the block diagonal restriction and raise a warning if the used device does not have an additional wire. (#1725)

    • The circuit_drawer module has been renamed drawer. (#1949)

    • The par_domain attribute in the operator class has been removed. (#1907)

    • The mutable keyword argument has been removed from the QNode, due to underlying bugs that result in incorrect results being returned from immutable QNodes. This functionality will return in an upcoming release. (#1807)

    • The reversible QNode differentiation method has been removed; the adjoint differentiation method is preferred instead (diff_method='adjoint'). (#1807)

    • QuantumTape.trainable_params now is a list instead of a set. This means that tape.trainable_params will return a list unlike before, but setting the trainable_params with a set works exactly as before. (#1904)

    • The num_params attribute in the operator class is now dynamic. This makes it easier to define operator subclasses with a flexible number of parameters. (#1898) (#1909)

    • The static method decomposition(), formerly in the Operation class, has been moved to the base Operator class. (#1873)

    • DiagonalOperation is not a separate subclass any more. (#1889)

      Instead, devices can check for the diagonal property using attributes:

      from pennylane.ops.qubit.attributes import diagonal_in_z_basis
      
      if op in diagonal_in_z_basis:
          # do something
      

      Custom operations can be added to this attribute at runtime via diagonal_in_z_basis.add("MyCustomOp").

    Bug fixes

    • Fixes a bug with qml.probs when using default.qubit.jax. (#1998)

    • Fixes a bug where output tensors of a QNode would always be put on the default GPU with default.qubit.torch. (#1982)

    • Device test suite doesn't use empty circuits so that it can also test the IonQ plugin, and it checks if operations are supported in more places. (#1979)

    • Fixes a bug where the metric tensor was computed incorrectly when using gates with gate.inverse=True. (#1987)

    • Corrects the documentation of qml.transforms.classical_jacobian for the Autograd interface (and improves test coverage). (#1978)

    • Fixes a bug where differentiating a QNode with qml.state using the JAX interface raised an error. (#1906)

    • Fixes a bug with the adjoint of qml.QFT. (#1955)

    • Fixes a bug where the ApproxTimeEvolution template was not correctly computing the operation wires from the input Hamiltonian. This did not affect computation with the ApproxTimeEvolution template, but did cause circuit drawing to fail. (#1952)

    • Fixes a bug where the classical preprocessing Jacobian computed by qml.transforms.classical_jacobian with JAX returned a reduced submatrix of the Jacobian. (#1948)

    • Fixes a bug where the operations are not accessed in the correct order in qml.fourier.qnode_spectrum, leading to wrong outputs. (#1935)

    • Fixes several Pylint errors. (#1951)

    • Fixes a bug where the device test suite wasn't testing certain operations. (#1943)

    • Fixes a bug where batch transforms would mutate a QNodes execution options. (#1934)

    • qml.draw now supports arbitrary templates with matrix parameters. (#1917)

    • QuantumTape.trainable_params now is a list instead of a set, making it more stable in very rare edge cases. (#1904)

    • ExpvalCost now returns corrects results shape when optimize=True with shots batch. (#1897)

    • qml.circuit_drawer.MPLDrawer was slightly modified to work with matplotlib version 3.5. (#1899)

    • qml.CSWAP and qml.CRot now define control_wires, and qml.SWAP returns the default empty wires object. (#1830)

    • The requires_grad attribute of qml.numpy.tensor objects is now preserved when pickling/unpickling the object. (#1856)

    • Device tests no longer throw warnings about the requires_grad attribute of variational parameters. (#1913)

    • AdamOptimizer and AdagradOptimizer had small fixes to their optimization step updates. (#1929)

    • Fixes a bug where differentiating a QNode with multiple array arguments via qml.gradients.param_shift throws an error. (#1989)

    • AmplitudeEmbedding template no longer produces a ComplexWarning when the features parameter is batched and provided as a 2D array. (#1990)

    • qml.circuit_drawer.CircuitDrawer no longer produces an error when attempting to draw tapes inside of circuits (e.g. from decomposition of an operation or manual placement). (#1994)

    • Fixes a bug where using SciPy sparse matrices with the new QNode could lead to a warning being raised about prioritizing the TensorFlow and PyTorch interfaces. (#2001)

    • Fixed a bug where the QueueContext was not empty when first importing PennyLane. (#1957)

    • Fixed circuit drawing problem with Interferometer and CVNeuralNet. (#1953)

    Documentation

    • Added examples in documentation for some operations. (#1902)

    • Improves the Developer's Guide Testing document. (#1896)

    • Added documentation examples for AngleEmbedding, BasisEmbedding, StronglyEntanglingLayers, SqueezingEmbedding, DisplacementEmbedding, MottonenStatePreparation and Interferometer. (#1910) (#1908) (#1912) (#1920) (#1936) (#1937)

    Contributors

    This release contains contributions from (in alphabetical order):

    Catalina Albornoz, Guillermo Alonso-Linaje, Juan Miguel Arrazola, Ali Asadi, Utkarsh Azad, Samuel Banning, Benjamin Cordier, Alain Delgado, Olivia Di Matteo, Anthony Hayes, David Ittah, Josh Izaac, Soran Jahangiri, Jalani Kanem, Ankit Khandelwal, Nathan Killoran, Shumpei Kobayashi, Robert Lang, Christina Lee, Cedric Lin, Alejandro Montanez, Romain Moyard, Lee James O'Riordan, Chae-Yeun Park, Isidor Schoch, Maria Schuld, Jay Soni, Antal Száva, Rodrigo Vargas, David Wierichs, Roeland Wiersema, Moritz Willmann

    Source code(tar.gz)
    Source code(zip)
  • v0.19.1(Nov 25, 2021)

    Bug fixes

    • Fixes several bugs when using parametric operations with the default.qubit.torch device on GPU. The device takes the torch_device argument once again to allow running non-parametric QNodes on the GPU. (#1927)

    • Fixes a bug where using JAX's jit function on certain QNodes that contain the qml.QubitStateVector operation raised an error with earlier JAX versions (e.g., jax==0.2.10 and jaxlib==0.1.64). (#1924)

    Contributors

    This release contains contributions from (in alphabetical order):

    Josh Izaac, Christina Lee, Romain Moyard, Lee James O'Riordan, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.19.0(Nov 9, 2021)

    New features since last release

    Differentiable Hartree-Fock solver

    • A differentiable Hartree-Fock (HF) solver has been added. It can be used to construct molecular Hamiltonians that can be differentiated with respect to nuclear coordinates and basis-set parameters. (#1610)

      The HF solver computes the integrals over basis functions, constructs the relevant matrices, and performs self-consistent-field iterations to obtain a set of optimized molecular orbital coefficients. These coefficients and the computed integrals over basis functions are used to construct the one- and two-body electron integrals in the molecular orbital basis which can be used to generate a differentiable second-quantized Hamiltonian in the fermionic and qubit basis.

      The following code shows the construction of the Hamiltonian for the hydrogen molecule where the geometry of the molecule is differentiable.

      symbols = ["H", "H"]
      geometry = np.array([[0.0000000000, 0.0000000000, -0.6943528941],
                           [0.0000000000, 0.0000000000,  0.6943528941]], requires_grad=True)
      
      mol = qml.hf.Molecule(symbols, geometry)
      args_mol = [geometry]
      
      hamiltonian = qml.hf.generate_hamiltonian(mol)(*args_mol)
      
      >>> hamiltonian.coeffs
      tensor([-0.09041082+0.j,  0.17220382+0.j,  0.17220382+0.j,
               0.16893367+0.j,  0.04523101+0.j, -0.04523101+0.j,
              -0.04523101+0.j,  0.04523101+0.j, -0.22581352+0.j,
               0.12092003+0.j, -0.22581352+0.j,  0.16615103+0.j,
               0.16615103+0.j,  0.12092003+0.j,  0.17464937+0.j], requires_grad=True)
      

      The generated Hamiltonian can be used in a circuit where the atomic coordinates and circuit parameters are optimized simultaneously.

      symbols = ["H", "H"]
      geometry = np.array([[0.0000000000, 0.0000000000, 0.0],
                           [0.0000000000, 0.0000000000, 2.0]], requires_grad=True)
      
      mol = qml.hf.Molecule(symbols, geometry)
      
      dev = qml.device("default.qubit", wires=4)
      params = [np.array([0.0], requires_grad=True)]
      
      def generate_circuit(mol):
          @qml.qnode(dev)
          def circuit(*args):
              qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3])
              qml.DoubleExcitation(*args[0][0], wires=[0, 1, 2, 3])
              return qml.expval(qml.hf.generate_hamiltonian(mol)(*args[1:]))
          return circuit
      
      for n in range(25):
      
          mol = qml.hf.Molecule(symbols, geometry)
          args = [params, geometry] # initial values of the differentiable parameters
      
          g_params = qml.grad(generate_circuit(mol), argnum = 0)(*args)
          params = params - 0.5 * g_params[0]
      
          forces = qml.grad(generate_circuit(mol), argnum = 1)(*args)
          geometry = geometry - 0.5 * forces
      
          print(f'Step: {n}, Energy: {generate_circuit(mol)(*args)}, Maximum Force: {forces.max()}')
      

      In addition, the new Hartree-Fock solver can further be used to optimize the basis set parameters. For details, please refer to the differentiable Hartree-Fock solver documentation.

    Integration with Mitiq

    • Error mitigation using the zero-noise extrapolation method is now available through the transforms.mitigate_with_zne transform. This transform can integrate with the Mitiq package for unitary folding and extrapolation functionality. (#1813)

      Consider the following noisy device:

      noise_strength = 0.05
      dev = qml.device("default.mixed", wires=2)
      dev = qml.transforms.insert(qml.AmplitudeDamping, noise_strength)(dev)
      

      We can mitigate the effects of this noise for circuits run on this device by using the added transform:

      from mitiq.zne.scaling import fold_global
      from mitiq.zne.inference import RichardsonFactory
      
      n_wires = 2
      n_layers = 2
      
      shapes = qml.SimplifiedTwoDesign.shape(n_wires, n_layers)
      np.random.seed(0)
      w1, w2 = [np.random.random(s) for s in shapes]
      
      @qml.transforms.mitigate_with_zne([1, 2, 3], fold_global, RichardsonFactory.extrapolate)
      @qml.beta.qnode(dev)
      def circuit(w1, w2):
          qml.SimplifiedTwoDesign(w1, w2, wires=range(2))
          return qml.expval(qml.PauliZ(0))
      

      Now, when we execute circuit, errors will be automatically mitigated:

      >>> circuit(w1, w2)
      0.19113067083636542
      

    Powerful new transforms

    • The unitary matrix corresponding to a quantum circuit can now be generated using the new get_unitary_matrix() transform. (#1609) (#1786)

      This transform is fully differentiable across all supported PennyLane autodiff frameworks.

      def circuit(theta):
          qml.RX(theta, wires=1)
          qml.PauliZ(wires=0)
          qml.CNOT(wires=[0, 1])
      
      >>> theta = torch.tensor(0.3, requires_grad=True)
      >>> matrix = qml.transforms.get_unitary_matrix(circuit)(theta)
      >>> print(matrix)
      tensor([[ 0.9888+0.0000j,  0.0000+0.0000j,  0.0000-0.1494j,  0.0000+0.0000j],
            [ 0.0000+0.0000j,  0.0000+0.1494j,  0.0000+0.0000j, -0.9888+0.0000j],
            [ 0.0000-0.1494j,  0.0000+0.0000j,  0.9888+0.0000j,  0.0000+0.0000j],
            [ 0.0000+0.0000j, -0.9888+0.0000j,  0.0000+0.0000j,  0.0000+0.1494j]],
           grad_fn=<MmBackward>)
      >>> loss = torch.real(torch.trace(matrix))
      >>> loss.backward()
      >>> theta.grad
      tensor(-0.1494)
      
    • Arbitrary two-qubit unitaries can now be decomposed into elementary gates. This functionality has been incorporated into the qml.transforms.unitary_to_rot transform, and is available separately as qml.transforms.two_qubit_decomposition. (#1552)

      As an example, consider the following randomly-generated matrix and circuit that uses it:

      U = np.array([
          [-0.03053706-0.03662692j,  0.01313778+0.38162226j, 0.4101526 -0.81893687j, -0.03864617+0.10743148j],
          [-0.17171136-0.24851809j,  0.06046239+0.1929145j, -0.04813084-0.01748555j, -0.29544883-0.88202604j],
          [ 0.39634931-0.78959795j, -0.25521689-0.17045233j, -0.1391033 -0.09670952j, -0.25043606+0.18393466j],
          [ 0.29599198-0.19573188j,  0.55605806+0.64025769j, 0.06140516+0.35499559j,  0.02674726+0.1563311j ]
      ])
      
      dev = qml.device('default.qubit', wires=2)
      
      @qml.qnode(dev)
      @qml.transforms.unitary_to_rot
      def circuit(x, y):
          qml.QubitUnitary(U, wires=[0, 1])
          return qml.expval(qml.PauliZ(wires=0))
      

      If we run the circuit, we can see the new decomposition:

      >>> circuit(0.3, 0.4)
      tensor(-0.81295986, requires_grad=True)
      >>> print(qml.draw(circuit)(0.3, 0.4))
      0: ──Rot(2.78, 0.242, -2.28)──╭X──RZ(0.176)───╭C─────────────╭X──Rot(-3.87, 0.321, -2.09)──┤ ⟨Z⟩
      1: ──Rot(4.64, 2.69, -1.56)───╰C──RY(-0.883)──╰X──RY(-1.47)──╰C──Rot(1.68, 0.337, 0.587)───┤
      
    • A new transform, @qml.batch_params, has been added, that makes QNodes handle a batch dimension in trainable parameters. (#1710) (#1761)

      This transform will create multiple circuits, one per batch dimension. As a result, it is both simulator and hardware compatible.

      @qml.batch_params
      @qml.beta.qnode(dev)
      def circuit(x, weights):
          qml.RX(x, wires=0)
          qml.RY(0.2, wires=1)
          qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1, 2])
          return qml.expval(qml.Hadamard(0))
      

      The qml.batch_params decorator allows us to pass arguments x and weights that have a batch dimension. For example,

      >>> batch_size = 3
      >>> x = np.linspace(0.1, 0.5, batch_size)
      >>> weights = np.random.random((batch_size, 10, 3, 3))
      

      If we evaluate the QNode with these inputs, we will get an output of shape (batch_size,):

      >>> circuit(x, weights)
      tensor([0.08569816, 0.12619101, 0.21122004], requires_grad=True)
      
    • The insert transform has now been added, providing a way to insert single-qubit operations into a quantum circuit. The transform can apply to quantum functions, tapes, and devices. (#1795)

      The following QNode can be transformed to add noise to the circuit:

      dev = qml.device("default.mixed", wires=2)
      
      @qml.qnode(dev)
      @qml.transforms.insert(qml.AmplitudeDamping, 0.2, position="end")
      def f(w, x, y, z):
          qml.RX(w, wires=0)
          qml.RY(x, wires=1)
          qml.CNOT(wires=[0, 1])
          qml.RY(y, wires=0)
          qml.RX(z, wires=1)
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      

      Executions of this circuit will differ from the noise-free value:

      >>> f(0.9, 0.4, 0.5, 0.6)
      tensor(0.754847, requires_grad=True)
      >>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6))
       0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩
       1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩
      
    • Common tape expansion functions are now available in qml.transforms, alongside a new create_expand_fn function for easily creating expansion functions from stopping criteria. (#1734) (#1760)

      create_expand_fn takes the default depth to which the expansion function should expand a tape, a stopping criterion, an optional device, and a docstring to be set for the created function. The stopping criterion must take a queuable object and return a boolean.

      For example, to create an expansion function that decomposes all trainable, multi-parameter operations:

      >>> stop_at = ~(qml.operation.has_multipar & qml.operation.is_trainable)
      >>> expand_fn = qml.transforms.create_expand_fn(depth=5, stop_at=stop_at)
      

      The created expansion function can be used within a custom transform. Devices can also be provided, producing expansion functions that decompose tapes to support the native gate set of the device.

    Batch execution of circuits

    • A new, experimental QNode has been added, that adds support for batch execution of circuits, custom quantum gradient support, and arbitrary order derivatives. This QNode is available via qml.beta.QNode, and @qml.beta.qnode. (#1642) (#1646) (#1651) (#1804)

      It differs from the standard QNode in several ways:

      • Custom gradient transforms can be specified as the differentiation method:

        @qml.gradients.gradient_transform
        def my_gradient_transform(tape):
            ...
            return tapes, processing_fn
        
        @qml.beta.qnode(dev, diff_method=my_gradient_transform)
        def circuit():
        
      • Arbitrary :math:n-th order derivatives are supported on hardware using gradient transforms such as the parameter-shift rule. To specify that an :math:n-th order derivative of a QNode will be computed, the max_diff argument should be set. By default, this is set to 1 (first-order derivatives only).

      • Internally, if multiple circuits are generated for execution simultaneously, they will be packaged into a single job for execution on the device. This can lead to significant performance improvement when executing the QNode on remote quantum hardware.

      • When decomposing the circuit, the default decomposition strategy will prioritize decompositions that result in the smallest number of parametrized operations required to satisfy the differentiation method. Additional decompositions required to satisfy the native gate set of the quantum device will be performed later, by the device at execution time. While this may lead to a slight increase in classical processing, it significantly reduces the number of circuit evaluations needed to compute gradients of complex unitaries.

      In an upcoming release, this QNode will replace the existing one. If you come across any bugs while using this QNode, please let us know via a bug report on our GitHub bug tracker.

      Currently, this beta QNode does not support the following features:

      • Non-mutability via the mutable keyword argument
      • The reversible QNode differentiation method
      • The ability to specify a dtype when using PyTorch and TensorFlow.

      It is also not tested with the qml.qnn module.

    New operations and templates

    • Added a new operation OrbitalRotation, which implements the spin-adapted spatial orbital rotation gate. (#1665)

      An example circuit that uses OrbitalRotation operation is:

      dev = qml.device('default.qubit', wires=4)
      
      @qml.qnode(dev)
      def circuit(phi):
          qml.BasisState(np.array([1, 1, 0, 0]), wires=[0, 1, 2, 3])
          qml.OrbitalRotation(phi, wires=[0, 1, 2, 3])
          return qml.state()
      

      If we run this circuit, we will get the following output

      >>> circuit(0.1)
      array([ 0.        +0.j,  0.        +0.j,  0.        +0.j,
              0.00249792+0.j,  0.        +0.j,  0.        +0.j,
              -0.04991671+0.j,  0.        +0.j,  0.        +0.j,
              -0.04991671+0.j,  0.        +0.j,  0.        +0.j,
              0.99750208+0.j,  0.        +0.j,  0.        +0.j,
              0.        +0.j])
      
    • Added a new template GateFabric, which implements a local, expressive, quantum-number-preserving ansatz proposed by Anselmetti et al. in arXiv:2104.05692. (#1687)

      An example of a circuit using GateFabric template is:

      coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614])
      H, qubits = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates)
      ref_state = qml.qchem.hf_state(electrons=2, orbitals=qubits)
      
      dev = qml.device('default.qubit', wires=qubits)
      
      @qml.qnode(dev)
      def ansatz(weights):
          qml.templates.GateFabric(weights, wires=[0,1,2,3],
                                      init_state=ref_state, include_pi=True)
          return qml.expval(H)
      

      For more details, see the GateFabric documentation.

    • Added a new template kUpCCGSD, which implements a unitary coupled cluster ansatz with generalized singles and pair doubles excitation operators, proposed by Joonho Lee et al. in arXiv:1810.02327. (#1743)

      An example of a circuit using kUpCCGSD template is:

      coordinates = np.array([0.0, 0.0, -0.6614, 0.0, 0.0, 0.6614])
      H, qubits = qml.qchem.molecular_hamiltonian(["H", "H"], coordinates)
      ref_state = qml.qchem.hf_state(electrons=2, orbitals=qubits)
      
      dev = qml.device('default.qubit', wires=qubits)
      
      @qml.qnode(dev)
      def ansatz(weights):
          qml.templates.kUpCCGSD(weights, wires=[0,1,2,3], k=0, delta_sz=0,
                                      init_state=ref_state)
          return qml.expval(H)
      

    Improved utilities for quantum compilation and characterization

    • The new qml.fourier.qnode_spectrum function extends the former qml.fourier.spectrum function and takes classical processing of QNode arguments into account. The frequencies are computed per (requested) QNode argument instead of per gate id. The gate ids are ignored. (#1681) (#1720)

      Consider the following example, which uses non-trainable inputs x, y and z as well as trainable parameters w as arguments to the QNode.

      import pennylane as qml
      import numpy as np
      
      n_qubits = 3
      dev = qml.device("default.qubit", wires=n_qubits)
      
      @qml.qnode(dev)
      def circuit(x, y, z, w):
          for i in range(n_qubits):
              qml.RX(0.5*x[i], wires=i)
              qml.Rot(w[0,i,0], w[0,i,1], w[0,i,2], wires=i)
              qml.RY(2.3*y[i], wires=i)
              qml.Rot(w[1,i,0], w[1,i,1], w[1,i,2], wires=i)
              qml.RX(z, wires=i)
          return qml.expval(qml.PauliZ(wires=0))
      
      x = np.array([1., 2., 3.])
      y = np.array([0.1, 0.3, 0.5])
      z = -1.8
      w = np.random.random((2, n_qubits, 3))
      

      This circuit looks as follows:

      >>> print(qml.draw(circuit)(x, y, z, w))
      0: ──RX(0.5)──Rot(0.598, 0.949, 0.346)───RY(0.23)──Rot(0.693, 0.0738, 0.246)──RX(-1.8)──┤ ⟨Z⟩
      1: ──RX(1)────Rot(0.0711, 0.701, 0.445)──RY(0.69)──Rot(0.32, 0.0482, 0.437)───RX(-1.8)──┤
      2: ──RX(1.5)──Rot(0.401, 0.0795, 0.731)──RY(1.15)──Rot(0.756, 0.38, 0.38)─────RX(-1.8)──┤
      

      Applying the qml.fourier.qnode_spectrum function to the circuit for the non-trainable parameters, we obtain:

      >>> spec = qml.fourier.qnode_spectrum(circuit, encoding_args={"x", "y", "z"})(x, y, z, w)
      >>> for inp, freqs in spec.items():
      ...     print(f"{inp}: {freqs}")
      "x": {(0,): [-0.5, 0.0, 0.5], (1,): [-0.5, 0.0, 0.5], (2,): [-0.5, 0.0, 0.5]}
      "y": {(0,): [-2.3, 0.0, 2.3], (1,): [-2.3, 0.0, 2.3], (2,): [-2.3, 0.0, 2.3]}
      "z": {(): [-3.0, -2.0, -1.0, 0.0, 1.0, 2.0, 3.0]}
      

      We can see that all three parameters in the QNode arguments x and y contribute the spectrum of a Pauli rotation [-1.0, 0.0, 1.0], rescaled with the prefactor of the respective parameter in the circuit. The three RX rotations using the parameter z accumulate, yielding a more complex frequency spectrum.

      For details on how to control for which parameters the spectrum is computed, a comparison to qml.fourier.circuit_spectrum, and other usage details, please see the fourier.qnode_spectrum docstring.

    • Two new methods were added to the Device API, allowing PennyLane devices increased control over circuit decompositions. (#1651)

      • Device.expand_fn(tape) -> tape: expands a tape such that it is supported by the device. By default, performs the standard device-specific gate set decomposition done in the default QNode. Devices may overwrite this method in order to define their own decomposition logic.

        Note that the numerical result after applying this method should remain unchanged; PennyLane will assume that the expanded tape returns exactly the same value as the original tape when executed.

      • Device.batch_transform(tape) -> (tapes, processing_fn): preprocesses the tape in the case where the device needs to generate multiple circuits to execute from the input circuit. The requirement of a post-processing function makes this distinct to the expand_fn method above.

        By default, this method applies the transform

        .. math:: \left\langle \sum_i c_i h_i\right\rangle → \sum_i c_i \left\langle h_i \right\rangle

        if expval(H) is present on devices that do not natively support Hamiltonians with non-commuting terms.

    • A new class has been added to store operator attributes, such as self_inverses, and composable_rotation, as a list of operation names. (#1763)

      A number of such attributes, for the purpose of compilation transforms, can be found in ops/qubit/attributes.py, but the class can also be used to create your own. For example, we can create a new Attribute, pauli_ops, like so:

      >>> from pennylane.ops.qubit.attributes import Attribute
      >>> pauli_ops = Attribute(["PauliX", "PauliY", "PauliZ"])
      

      We can check either a string or an Operation for inclusion in this set:

      >>> qml.PauliX(0) in pauli_ops
      True
      >>> "Hadamard" in pauli_ops
      False
      

      We can also dynamically add operators to the sets at runtime. This is useful for adding custom operations to the attributes such as composable_rotations and self_inverses that are used in compilation transforms. For example, suppose you have created a new Operation, MyGate, which you know to be its own inverse. Adding it to the set, like so

      >>> from pennylane.ops.qubit.attributes import self_inverses
      >>> self_inverses.add("MyGate")
      

      will enable the gate to be considered by the cancel_inverses compilation transform if two such gates are adjacent in a circuit.

    Improvements

    • The qml.metric_tensor transform has been improved with regards to both function and performance. (#1638) (#1721)

      • If the underlying device supports batch execution of circuits, the quantum circuits required to compute the metric tensor elements will be automatically submitted as a batched job. This can lead to significant performance improvements for devices with a non-trivial job submission overhead.

      • Previously, the transform would only return the metric tensor with respect to gate arguments, and ignore any classical processing inside the QNode, even very trivial classical processing such as parameter permutation. The metric tensor now takes into account classical processing, and returns the metric tensor with respect to QNode arguments, not simply gate arguments:

        >>> @qml.qnode(dev)
        ... def circuit(x):
        ...     qml.Hadamard(wires=1)
        ...     qml.RX(x[0], wires=0)
        ...     qml.CNOT(wires=[0, 1])
        ...     qml.RY(x[1] ** 2, wires=1)
        ...     qml.RY(x[1], wires=0)
        ...     return qml.expval(qml.PauliZ(0))
        >>> x = np.array([0.1, 0.2], requires_grad=True)
        >>> qml.metric_tensor(circuit)(x)
        array([[0.25      , 0.        ],
               [0.        , 0.28750832]])
        

        To revert to the previous behaviour of returning the metric tensor with respect to gate arguments, qml.metric_tensor(qnode, hybrid=False) can be passed.

        >>> qml.metric_tensor(circuit, hybrid=False)(x)
        array([[0.25      , 0.        , 0.        ],
               [0.        , 0.25      , 0.        ],
               [0.        , 0.        , 0.24750832]])
        
      • The metric tensor transform now works with a larger set of operations. In particular, all operations that have a single variational parameter and define a generator are now supported. In addition to a reduction in decomposition overhead, the change also results in fewer circuit evaluations.

    • The expansion rule in the qml.metric_tensor transform has been changed. (#1721)

      If hybrid=False, the changed expansion rule might lead to a changed output.

    • The ApproxTimeEvolution template can now be used with Hamiltonians that have trainable coefficients. (#1789)

      The resulting QNodes can be differentiated with respect to both the time parameter and the Hamiltonian coefficients.

      dev = qml.device('default.qubit', wires=2)
      obs = [qml.PauliX(0) @ qml.PauliY(1), qml.PauliY(0) @ qml.PauliX(1)]
      
      @qml.qnode(dev)
      def circuit(coeffs, t):
          H = qml.Hamiltonian(coeffs, obs)
          qml.templates.ApproxTimeEvolution(H, t, 2)
          return qml.expval(qml.PauliZ(0))
      
      >>> t = np.array(0.54, requires_grad=True)
      >>> coeffs = np.array([-0.6, 2.0], requires_grad=True)
      >>> qml.grad(circuit)(coeffs, t)
      (array([-1.07813375, -1.07813375]), array(-2.79516158))
      

      All differentiation methods, including backpropagation and the parameter-shift rule, are supported.

    • Quantum function transforms and batch transforms can now be applied to devices. Once applied to a device, any quantum function executed on the modified device will be transformed prior to execution. (#1809) (#1810)

      dev = qml.device("default.mixed", wires=1)
      dev = qml.transforms.merge_rotations()(dev)
      
      @qml.beta.qnode(dev)
      def f(w, x, y, z):
          qml.RX(w, wires=0)
          qml.RX(x, wires=0)
          qml.RX(y, wires=0)
          qml.RX(z, wires=0)
          return qml.expval(qml.PauliZ(0))
      
      >>> print(f(0.9, 0.4, 0.5, 0.6))
       -0.7373937155412453
      >>> print(qml.draw(f, expansion_strategy="device")(0.9, 0.4, 0.5, 0.6))
       0: ──RX(2.4)──┤ ⟨Z⟩
      
    • It is now possible to draw QNodes that have been transformed by a 'batch transform'; that is, a transform that maps a single QNode into multiple circuits under the hood. Examples of batch transforms include @qml.metric_tensor and @qml.gradients. (#1762)

      For example, consider the parameter-shift rule, which generates two circuits per parameter; one circuit that has the parameter shifted forward, and another that has the parameter shifted backwards:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.gradients.param_shift
      @qml.beta.qnode(dev)
      def circuit(x):
          qml.RX(x, wires=0)
          qml.CNOT(wires=[0, 1])
          return qml.expval(qml.PauliZ(wires=0))
      
      >>> print(qml.draw(circuit)(0.6))
       0: ──RX(2.17)──╭C──┤ ⟨Z⟩
       1: ────────────╰X──┤
      
       0: ──RX(-0.971)──╭C──┤ ⟨Z⟩
       1: ──────────────╰X──┤
      
    • Support for differentiable execution of batches of circuits has been extended to the JAX interface for scalar functions, via the beta pennylane.interfaces.batch module. (#1634) (#1685)

      For example using the execute function from the pennylane.interfaces.batch module:

      from pennylane.interfaces.batch import execute
      
      def cost_fn(x):
          with qml.tape.JacobianTape() as tape1:
              qml.RX(x[0], wires=[0])
              qml.RY(x[1], wires=[1])
              qml.CNOT(wires=[0, 1])
              qml.var(qml.PauliZ(0) @ qml.PauliX(1))
      
          with qml.tape.JacobianTape() as tape2:
              qml.RX(x[0], wires=0)
              qml.RY(x[0], wires=1)
              qml.CNOT(wires=[0, 1])
              qml.probs(wires=1)
      
          result = execute(
            [tape1, tape2], dev,
            gradient_fn=qml.gradients.param_shift,
            interface="autograd"
          )
          return (result[0] + result[1][0, 0])[0]
      
      res = jax.grad(cost_fn)(params)
      
    • All qubit operations have been re-written to use the qml.math framework for internal classical processing and the generation of their matrix representations. As a result these representations are now fully differentiable, and the framework-specific device classes no longer need to maintain framework-specific versions of these matrices. (#1749) (#1802)

    • The use of expval(H), where H is a cost Hamiltonian generated by the qaoa module, has been sped up. This was achieved by making PennyLane decompose a circuit with an expval(H) measurement into subcircuits if the Hamiltonian.grouping_indices attribute is set, and setting this attribute in the relevant qaoa module functions. (#1718)

    • Operations can now have gradient recipes that depend on the state of the operation. (#1674)

      For example, this allows for gradient recipes that are parameter dependent:

      class RX(qml.RX):
      
          @property
          def grad_recipe(self):
              # The gradient is given by [f(2x) - f(0)] / (2 sin(x)), by subsituting
              # shift = x into the two term parameter-shift rule.
              x = self.data[0]
              c = 0.5 / np.sin(x)
              return ([[c, 0.0, 2 * x], [-c, 0.0, 0.0]],)
      
    • Shots can now be passed as a runtime argument to transforms that execute circuits in batches, similarly to QNodes. (#1707)

      An example of such a transform are the gradient transforms in the qml.gradients module. As a result, we can now call gradient transforms (such as qml.gradients.param_shift) and set the number of shots at runtime.

      >>> dev = qml.device("default.qubit", wires=1, shots=1000)
      >>> @qml.beta.qnode(dev)
      ... def circuit(x):
      ...     qml.RX(x, wires=0)
      ...     return qml.expval(qml.PauliZ(0))
      >>> grad_fn = qml.gradients.param_shift(circuit)
      >>> param = np.array(0.564, requires_grad=True)
      >>> grad_fn(param, shots=[(1, 10)]).T
      array([[-1., -1., -1., -1., -1.,  0., -1.,  0., -1.,  0.]])
      >>> param2 = np.array(0.1233, requires_grad=True)
      >>> grad_fn(param2, shots=None)
      array([[-0.12298782]])
      
    • Templates are now top level imported and can be used directly e.g. qml.QFT(wires=0). (#1779)

    • qml.probs now accepts an attribute op that allows to rotate the computational basis and get the probabilities in the rotated basis. (#1692)

    • Refactored the expand_fn functionality in the Device class to avoid any edge cases leading to failures with plugins. (#1838)

    • Updated the qml.QNGOptimizer.step_and_cost method to avoid the use of deprecated functionality. (#1834)

    • Added a custom torch.to_numpy implementation to pennylane/math/single_dispatch.py to ensure compabilitity with PyTorch 1.10. (#1824) (#1825)

    • The default for an Operation's control_wires attribute is now an empty Wires object instead of the attribute raising a NonImplementedError. (#1821)

    • qml.circuit_drawer.MPLDrawer will now automatically rotate and resize text to fit inside the rectangle created by the box_gate method. (#1764)

    • Operators now have a label method to determine how they are drawn. This will eventually override the RepresentationResolver class. (#1678)

    • The operation label method now supports string variables. (#1815)

    • A new utility class qml.BooleanFn is introduced. It wraps a function that takes a single argument and returns a Boolean. (#1734)

      After wrapping, qml.BooleanFn can be called like the wrapped function, and multiple instances can be manipulated and combined with the bitwise operators &, | and ~.

    • There is a new utility function qml.math.is_independent that checks whether a callable is independent of its arguments. (#1700)

      This function is experimental and might behave differently than expected.

      Note that the test relies on both numerical and analytical checks, except when using the PyTorch interface which only performs a numerical check. It is known that there are edge cases on which this test will yield wrong results, in particular non-smooth functions may be problematic. For details, please refer to the is_indpendent docstring.

    • The qml.beta.QNode now supports the qml.qnn module. (#1748)

    • @qml.beta.QNode now supports the qml.specs transform. (#1739)

    • qml.circuit_drawer.drawable_layers and qml.circuit_drawer.drawable_grid process a list of operations to layer positions for drawing. (#1639)

    • qml.transforms.batch_transform now accepts expand_fns that take additional arguments and keyword arguments. In fact, expand_fn and transform_fn now must have the same signature. (#1721)

    • The qml.batch_transform decorator is now ignored during Sphinx builds, allowing the correct signature to display in the built documentation. (#1733)

    • The tests for qubit operations are split into multiple files. (#1661)

    • The transform for the Jacobian of the classical preprocessing within a QNode, qml.transforms.classical_jacobian, now takes a keyword argument argnum to specify the QNode argument indices with respect to which the Jacobian is computed. (#1645)

      An example for the usage of argnum is

      @qml.qnode(dev)
      def circuit(x, y, z):
          qml.RX(qml.math.sin(x), wires=0)
          qml.CNOT(wires=[0, 1])
          qml.RY(y ** 2, wires=1)
          qml.RZ(1 / z, wires=1)
          return qml.expval(qml.PauliZ(0))
      
      jac_fn = qml.transforms.classical_jacobian(circuit, argnum=[1, 2])
      

      The Jacobian can then be computed at specified parameters.

      >>> x, y, z = np.array([0.1, -2.5, 0.71])
      >>> jac_fn(x, y, z)
      (array([-0., -5., -0.]), array([-0.        , -0.        , -1.98373339]))
      

      The returned arrays are the derivatives of the three parametrized gates in the circuit with respect to y and z respectively.

      There also are explicit tests for classical_jacobian now, which previously was tested implicitly via its use in the metric_tensor transform.

      For more usage details, please see the classical Jacobian docstring.

    • A new utility function qml.math.is_abstract(tensor) has been added. This function returns True if the tensor is abstract; that is, it has no value or shape. This can occur if within a function that has been just-in-time compiled. (#1845)

    • qml.circuit_drawer.CircuitDrawer can accept a string for the charset keyword, instead of a CharSet object. (#1640)

    • qml.math.sort will now return only the sorted torch tensor and not the corresponding indices, making sort consistent across interfaces. (#1691)

    • Specific QNode execution options are now re-used by batch transforms to execute transformed QNodes. (#1708)

    • To standardize across all optimizers, qml.optimize.AdamOptimizer now also uses accumulation (in form of collections.namedtuple) to keep track of running quantities. Before it used three variables fm, sm and t. (#1757)

    Breaking changes

    • The operator attributes has_unitary_generator, is_composable_rotation, is_self_inverse, is_symmetric_over_all_wires, and is_symmetric_over_control_wires have been removed as attributes from the base class. They have been replaced by the sets that store the names of operations with similar properties in ops/qubit/attributes.py. (#1763)

    • The qml.inv function has been removed, qml.adjoint should be used instead. (#1778)

    • The input signature of an expand_fn used in a batch_transform now must have the same signature as the provided transform_fn, and vice versa. (#1721)

    • The default.qubit.torch device automatically determines if computations should be run on a CPU or a GPU and doesn't take a torch_device argument anymore. (#1705)

    • The utility function qml.math.requires_grad now returns True when using Autograd if and only if the requires_grad=True attribute is set on the NumPy array. Previously, this function would return True for all NumPy arrays and Python floats, unless requires_grad=False was explicitly set. (#1638)

    • The operation qml.Interferometer has been renamed qml.InterferometerUnitary in order to distinguish it from the template qml.templates.Interferometer. (#1714)

    • The qml.transforms.invisible decorator has been replaced with qml.tape.stop_recording, which may act as a context manager as well as a decorator to ensure that contained logic is non-recordable or non-queueable within a QNode or quantum tape context. (#1754)

    • Templates SingleExcitationUnitary and DoubleExcitationUnitary have been renamed to FermionicSingleExcitation and FermionicDoubleExcitation, respectively. (#1822)

    Deprecations

    • Allowing cost functions to be differentiated using qml.grad or qml.jacobian without explicitly marking parameters as trainable is being deprecated, and will be removed in an upcoming release. Please specify the requires_grad attribute for every argument, or specify argnum when using qml.grad or qml.jacobian. (#1773)

      The following raises a warning in v0.19.0 and will raise an error in an upcoming release:

       import pennylane as qml
      
      dev = qml.device('default.qubit', wires=1)
      
      @qml.qnode(dev)   def test(x):       qml.RY(x, wires=[0])
          return qml.expval(qml.PauliZ(0))
      
      par = 0.3
      qml.grad(test)(par)
      

      Preferred approaches include specifying the requires_grad attribute:

      import pennylane as qml
      from pennylane import numpy as np
      
      dev = qml.device('default.qubit', wires=1)
      
      @qml.qnode(dev)
      def test(x):
          qml.RY(x, wires=[0])
          return qml.expval(qml.PauliZ(0))
      
      par = np.array(0.3, requires_grad=True)
      qml.grad(test)(par)
      

      Or specifying the argnum argument when using qml.grad or qml.jacobian:

      import pennylane as qml
      
      dev = qml.device('default.qubit', wires=1)
      
      @qml.qnode(dev)
      def test(x):
          qml.RY(x, wires=[0])
          return qml.expval(qml.PauliZ(0))
      
      par = 0.3
      qml.grad(test, argnum=0)(par)
      
    • The default.tensor device from the beta folder is no longer maintained and has been deprecated. It will be removed in future releases. (#1851)

    • The qml.metric_tensor and qml.QNGOptimizer keyword argument diag_approx is deprecated. Approximations can be controlled with the more fine-grained approx keyword argument, with approx="block-diag" (the default) reproducing the old behaviour. (#1721) (#1834)

    • The template decorator is now deprecated with a warning message and will be removed in release v0.20.0. It has been removed from different PennyLane functions. (#1794) (#1808)

    • The qml.fourier.spectrum function has been renamed to qml.fourier.circuit_spectrum, in order to clearly separate the new qnode_spectrum function from this one. qml.fourier.spectrum is now an alias for circuit_spectrum but is flagged for deprecation and will be removed soon. (#1681)

    • The init module, which contains functions to generate random parameter tensors for templates, is flagged for deprecation and will be removed in the next release cycle. Instead, the templates' shape method can be used to get the desired shape of the tensor, which can then be generated manually. (#1689)

      To generate the parameter tensors, the np.random.normal and np.random.uniform functions can be used (just like in the init module). Considering the default arguments of these functions as of NumPy v1.21, some non-default options were used by the init module:

      • All functions generating normally distributed parameters used np.random.normal by passing scale=0.1;

      • Most functions generating uniformly distributed parameters (except for certain CVQNN initializers) used np.random.uniform by passing high=2*math.pi;

      • The cvqnn_layers_r_uniform, cvqnn_layers_a_uniform, cvqnn_layers_kappa_uniform functions used np.random.uniform by passing high=0.1.

    • The QNode.draw method has been deprecated, and will be removed in an upcoming release. Please use the qml.draw transform instead. (#1746)

    • The QNode.metric_tensor method has been deprecated, and will be removed in an upcoming release. Please use the qml.metric_tensor transform instead. (#1638)

    • The pad parameter of the qml.AmplitudeEmbedding template has been removed. It has instead been renamed to the pad_with parameter. (#1805)

    Bug fixes

    • Fixes a bug where qml.math.dot failed to work with @tf.function autograph mode. (#1842)

    • Fixes a bug where in rare instances the parameters of a tape are returned unsorted by Tape.get_parameters. (#1836)

    • Fixes a bug with the arrow width in the measure of qml.circuit_drawer.MPLDrawer. (#1823)

    • The helper functions qml.math.block_diag and qml.math.scatter_element_add now are entirely differentiable when using Autograd. Previously only indexed entries of the block diagonal could be differentiated, while the derivative w.r.t to the second argument of qml.math.scatter_element_add dispatched to NumPy instead of Autograd. (#1816) (#1818)

    • Fixes a bug such that the original shot vector information of a device is preserved, so that outside the context manager the device remains unchanged. (#1792)

    • Modifies qml.math.take to be compatible with a breaking change released in JAX 0.2.24 and ensure that PennyLane supports this JAX version. (#1769)

    • Fixes a bug where the GPU cannot be used with qml.qnn.TorchLayer. (#1705)

    • Fix a bug where the devices cache the same result for different observables return types. (#1719)

    • Fixed a bug of the default circuit drawer where having more measurements compared to the number of measurements on any wire raised a KeyError. (#1702)

    • Fix a bug where it was not possible to use jax.jit on a QNode when using QubitStateVector. (#1683)

    • The device suite tests can now execute successfully if no shots configuration variable is given. (#1641)

    • Fixes a bug where the qml.gradients.param_shift transform would raise an error while attempting to compute the variance of a QNode with ragged output. (#1646)

    • Fixes a bug in default.mixed, to ensure that returned probabilities are always non-negative. (#1680)

    • Fixes a bug where gradient transforms would fail to apply to QNodes containing classical processing. (#1699)

    • Fixes a bug where the parameter-shift method was not correctly using the fallback gradient function when all circuit parameters required the fallback. (#1782)

    Documentation

    • Adds a link to https://pennylane.ai/qml/demonstrations.html in the navbar. (#1624)

    • Corrects the docstring of ExpvalCost by adding wires to the signature of the ansatz argument. (#1715)

    • Updated docstring examples using the qchem.molecular_hamiltonian function. (#1724)

    • Updates the 'Gradients and training' quickstart guide to provide information on gradient transforms. (#1751)

    • All instances of qnode.draw() have been updated to instead use the transform qml.draw(qnode). (#1750)

    • Add the jax interface in QNode Documentation. (#1755)

    • Reorganized all the templates related to quantum chemistry under a common header Quantum Chemistry templates. (#1822)

    Contributors

    This release contains contributions from (in alphabetical order):

    Catalina Albornoz, Juan Miguel Arrazola, Utkarsh Azad, Akash Narayanan B, Sam Banning, Thomas Bromley, Jack Ceroni, Alain Delgado, Olivia Di Matteo, Andrew Gardhouse, Anthony Hayes, Theodor Isacsson, David Ittah, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Guillermo Alonso-Linaje, Romain Moyard, Lee James O'Riordan, Carrie-Anne Rubidge, Maria Schuld, Rishabh Singh, Jay Soni, Ingrid Strandberg, Antal Száva, Teresa Tamayo-Mendoza, Rodrigo Vargas, Cody Wang, David Wierichs, Moritz Willmann.

    Source code(tar.gz)
    Source code(zip)
  • v0.18.0-post2(Nov 1, 2021)

  • v0.18.0-post1(Sep 25, 2021)

  • v0.18.0(Sep 21, 2021)

    New features since last release

    PennyLane now comes packaged with lightning.qubit

    • The C++-based lightning.qubit device is now included with installations of PennyLane. (#1663)

      The lightning.qubit device is a fast state-vector simulator equipped with the efficient adjoint method for differentiating quantum circuits, check out the plugin release notes for more details! The device can be accessed in the following way:

      import pennylane as qml
      
      wires = 3
      layers = 2
      dev = qml.device("lightning.qubit", wires=wires)
      
      @qml.qnode(dev, diff_method="adjoint")
      def circuit(weights):
          qml.templates.StronglyEntanglingLayers(weights, wires=range(wires))
          return qml.expval(qml.PauliZ(0))
      
      weights = qml.init.strong_ent_layers_normal(layers, wires, seed=1967)
      

      Evaluating circuits and their gradients on the device can be achieved using the standard approach:

      >>> print(f"Circuit evaluated: {circuit(weights)}")
      Circuit evaluated: 0.9801286266677633
      >>> print(f"Circuit gradient:\n{qml.grad(circuit)(weights)}")
      Circuit gradient:
      [[[-9.35301749e-17 -1.63051504e-01 -4.14810501e-04]
        [-7.88816484e-17 -1.50136528e-04 -1.77922957e-04]
        [-5.20670796e-17 -3.92874550e-02  8.14523075e-05]]
      
       [[-1.14472273e-04  3.85963953e-02 -9.39190132e-18]
        [-5.76791765e-05 -9.78478343e-02  0.00000000e+00]
        [ 0.00000000e+00  0.00000000e+00  0.00000000e+00]]]
      

      The adjoint method operates after a forward pass by iteratively applying inverse gates to scan backwards through the circuit. The method is already available in PennyLane's default.qubit device, but the version provided by lightning.qubit integrates with the C++ backend and is more performant, as shown in the plot below:

    Support for native backpropagation using PyTorch

    • The built-in PennyLane simulator default.qubit now supports backpropogation with PyTorch. (#1360) (#1598)

      As a result, default.qubit can now use end-to-end classical backpropagation as a means to compute gradients. End-to-end backpropagation can be faster than the parameter-shift rule for computing quantum gradients when the number of parameters to be optimized is large. This is now the default differentiation method when using default.qubit with PyTorch.

      Using this method, the created QNode is a 'white-box' that is tightly integrated with your PyTorch computation, including TorchScript and GPU support.

      x = torch.tensor(0.43316321, dtype=torch.float64, requires_grad=True)
      y = torch.tensor(0.2162158, dtype=torch.float64, requires_grad=True)
      z = torch.tensor(0.75110998, dtype=torch.float64, requires_grad=True)
      
      p = torch.tensor([x, y, z], requires_grad=True)
      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev, interface="torch", diff_method="backprop")
      def circuit(x):
          qml.Rot(x[0], x[1], x[2], wires=0)
          return qml.expval(qml.PauliZ(0))
      
      res = circuit(p)
      res.backward()
      
      >>> res = circuit(p)
      >>> res.backward()
      >>> print(p.grad)
      tensor([-9.1798e-17, -2.1454e-01, -1.0511e-16], dtype=torch.float64)
      

    Improved quantum optimization methods

    • The RotosolveOptimizer now can tackle general parametrized circuits, and is no longer restricted to single-qubit Pauli rotations. (#1489)

      This includes:

      • layers of gates controlled by the same parameter,
      • controlled variants of parametrized gates, and
      • Hamiltonian time evolution.

      Note that the eigenvalue spectrum of the gate generator needs to be known to use RotosolveOptimizer for a general gate, and it is required to produce equidistant frequencies. For details see Vidal and Theis, 2018 and Wierichs, Izaac, Wang, Lin 2021.

      Consider a circuit with a mixture of Pauli rotation gates, controlled Pauli rotations, and single-parameter layers of Pauli rotations:

      dev = qml.device('default.qubit', wires=3, shots=None)
      
      @qml.qnode(dev)
      def cost_function(rot_param, layer_par, crot_param):
          for i, par in enumerate(rot_param):
              qml.RX(par, wires=i)
          for w in dev.wires:
              qml.RX(layer_par, wires=w)
          for i, par in enumerate(crot_param):
              qml.CRY(par, wires=[i, (i+1) % 3])
      
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1) @ qml.PauliZ(2))
      

      This cost function has one frequency for each of the first RX rotation angles, three frequencies for the layer of RX gates that depend on layer_par, and two frequencies for each of the CRY gate parameters. Rotosolve can then be used to minimize the cost_function:

      # Initial parameters
      init_param = [
          np.array([0.3, 0.2, 0.67], requires_grad=True),
          np.array(1.1, requires_grad=True),
          np.array([-0.2, 0.1, -2.5], requires_grad=True),
      ]
      # Numbers of frequencies per parameter
      num_freqs = [[1, 1, 1], 3, [2, 2, 2]]
      
      opt = qml.RotosolveOptimizer()
      param = init_param.copy()
      

      In addition, the optimization technique for the Rotosolve substeps can be chosen via the optimizer and optimizer_kwargs keyword arguments and the minimized cost of the intermediate univariate reconstructions can be read out via full_output, including the cost after the full Rotosolve step:

      for step in range(3):
          param, cost, sub_cost = opt.step_and_cost(
              cost_function,
              *param,
              num_freqs=num_freqs,
              full_output=True,
              optimizer="brute",
          )
          print(f"Cost before step: {cost}")
          print(f"Minimization substeps: {np.round(sub_cost, 6)}")
      
      Cost before step: 0.042008210392535605
      Minimization substeps: [-0.230905 -0.863336 -0.980072 -0.980072 -1.       -1.       -1.      ]
      Cost before step: -0.999999999068121
      Minimization substeps: [-1. -1. -1. -1. -1. -1. -1.]
      Cost before step: -1.0
      Minimization substeps: [-1. -1. -1. -1. -1. -1. -1.]
      

      For usage details please consider the docstring of the optimizer.

    Faster, trainable, Hamiltonian simulations

    • Hamiltonians are now trainable with respect to their coefficients. (#1483)

      from pennylane import numpy as np
      
      dev = qml.device("default.qubit", wires=2)
      @qml.qnode(dev)
      def circuit(coeffs, param):
          qml.RX(param, wires=0)
          qml.RY(param, wires=0)
          return qml.expval(
              qml.Hamiltonian(coeffs, [qml.PauliX(0), qml.PauliZ(0)], simplify=True)
          )
      
      coeffs = np.array([-0.05, 0.17])
      param = np.array(1.7)
      grad_fn = qml.grad(circuit)
      
      >>> grad_fn(coeffs, param)
      (array([-0.12777055,  0.0166009 ]), array(0.0917819))
      

      Furthermore, a gradient recipe for Hamiltonian coefficients has been added. This makes it possible to compute parameter-shift gradients of these coefficients on devices that natively support Hamiltonians. (#1551)

    • Hamiltonians are now natively supported on the default.qubit device if shots=None. This makes VQE workflows a lot faster in some cases. (#1551) (#1596)

    • The Hamiltonian can now store grouping information, which can be accessed by a device to speed up computations of the expectation value of a Hamiltonian. (#1515)

      obs = [qml.PauliX(0), qml.PauliX(1), qml.PauliZ(0)]
      coeffs = np.array([1., 2., 3.])
      H = qml.Hamiltonian(coeffs, obs, grouping_type='qwc')
      

      Initialization with a grouping_type other than None stores the indices required to make groups of commuting observables and their coefficients.

      >>> H.grouping_indices
      [[0, 1], [2]]
      

    Create multi-circuit quantum transforms and custom gradient rules

    • Custom gradient transforms can now be created using the new @qml.gradients.gradient_transform decorator on a batch-tape transform. (#1589)

      Quantum gradient transforms are a specific case of qml.batch_transform.

      Supported gradient transforms must be of the following form:

      @qml.gradients.gradient_transform
      def my_custom_gradient(tape, argnum=None, **kwargs):
          ...
          return gradient_tapes, processing_fn
      

      Various built-in quantum gradient transforms are provided within the qml.gradients module, including qml.gradients.param_shift. Once defined, quantum gradient transforms can be applied directly to QNodes:

      >>> @qml.qnode(dev)
      ... def circuit(x):
      ...     qml.RX(x, wires=0)
      ...     qml.CNOT(wires=[0, 1])
      ...     return qml.expval(qml.PauliZ(0))
      >>> circuit(0.3)
      tensor(0.95533649, requires_grad=True)
      >>> qml.gradients.param_shift(circuit)(0.5)
      array([[-0.47942554]])
      

      Quantum gradient transforms are fully differentiable, allowing higher order derivatives to be accessed:

      >>> qml.grad(qml.gradients.param_shift(circuit))(0.5)
      tensor(-0.87758256, requires_grad=True)
      

      Refer to the page of quantum gradient transforms for more details.

    • The ability to define batch transforms has been added via the new @qml.batch_transform decorator. (#1493)

      A batch transform is a transform that takes a single tape or QNode as input, and executes multiple tapes or QNodes independently. The results may then be post-processed before being returned.

      For example, consider the following batch transform:

      @qml.batch_transform
      def my_transform(tape, a, b):
          """Generates two tapes, one with all RX replaced with RY,
          and the other with all RX replaced with RZ."""
          tape1 = qml.tape.JacobianTape()
          tape2 = qml.tape.JacobianTape()
      
          # loop through all operations on the input tape
          for op in tape.operations + tape.measurements:
              if op.name == "RX":
                  with tape1:
                      qml.RY(a * qml.math.abs(op.parameters[0]), wires=op.wires)
                  with tape2:
                      qml.RZ(b * qml.math.abs(op.parameters[0]), wires=op.wires)
              else:
                  for t in [tape1, tape2]:
                      with t:
                          qml.apply(op)
      
          def processing_fn(results):
              return qml.math.sum(qml.math.stack(results))
      
          return [tape1, tape2], processing_fn
      

      We can transform a QNode directly using decorator syntax:

      >>> @my_transform(0.65, 2.5)
      ... @qml.qnode(dev)
      ... def circuit(x):
      ...     qml.Hadamard(wires=0)
      ...     qml.RX(x, wires=0)
      ...     return qml.expval(qml.PauliX(0))
      >>> print(circuit(-0.5))
      1.2629730888100839
      

      Batch tape transforms are fully differentiable:

      >>> gradient = qml.grad(circuit)(-0.5)
      >>> print(gradient)
      2.5800122591960153
      

      Batch transforms can also be applied to existing QNodes,

      >>> new_qnode = my_transform(existing_qnode, *transform_weights)
      >>> new_qnode(weights)
      

      or to tapes (in which case, the processed tapes and classical post-processing functions are returned):

      >>> tapes, fn = my_transform(tape, 0.65, 2.5)
      >>> from pennylane.interfaces.batch import execute
      >>> dev = qml.device("default.qubit", wires=1)
      >>> res = execute(tapes, dev, interface="autograd", gradient_fn=qml.gradients.param_shift)
      1.2629730888100839
      
    • Vector-Jacobian product transforms have been added to the qml.gradients package. (#1494)

      The new transforms include:

      • qml.gradients.vjp
      • qml.gradients.batch_vjp
    • Support for differentiable execution of batches of circuits has been added, via the beta pennylane.interfaces.batch module. (#1501) (#1508) (#1542) (#1549) (#1608) (#1618) (#1637)

      For now, this is a low-level feature, and will be integrated into the QNode in a future release. For example:

      from pennylane.interfaces.batch import execute
      
      def cost_fn(x):
          with qml.tape.JacobianTape() as tape1:
              qml.RX(x[0], wires=[0])
              qml.RY(x[1], wires=[1])
              qml.CNOT(wires=[0, 1])
              qml.var(qml.PauliZ(0) @ qml.PauliX(1))
      
          with qml.tape.JacobianTape() as tape2:
              qml.RX(x[0], wires=0)
              qml.RY(x[0], wires=1)
              qml.CNOT(wires=[0, 1])
              qml.probs(wires=1)
      
          result = execute(
              [tape1, tape2], dev,
              gradient_fn=qml.gradients.param_shift,
              interface="autograd"
          )
          return result[0] + result[1][0, 0]
      
      res = qml.grad(cost_fn)(params)
      

    Improvements

    • A new operation qml.SISWAP has been added, the square-root of the qml.ISWAP operation. (#1563)

    • The frobenius_inner_product function has been moved to the qml.math module, and is now differentiable using all autodiff frameworks. (#1388)

    • A warning is raised to inform the user that specifying a list of shots is only supported for QubitDevice based devices. (#1659)

    • The qml.circuit_drawer.MPLDrawer class provides manual circuit drawing functionality using Matplotlib. While not yet integrated with automatic circuit drawing, this class provides customization and control. (#1484)

      from pennylane.circuit_drawer import MPLDrawer
      
      drawer = MPLDrawer(n_wires=3, n_layers=3)
      
      drawer.label([r"$|\Psi\rangle$", r"$|\theta\rangle$", "aux"])
      
      drawer.box_gate(layer=0, wires=[0, 1, 2], text="Entangling Layers", text_options={'rotation': 'vertical'})
      drawer.box_gate(layer=1, wires=[0, 1], text="U(θ)")
      
      drawer.CNOT(layer=2, wires=[1, 2])
      drawer.measure(layer=3, wires=2)
      
      drawer.fig.suptitle('My Circuit', fontsize='xx-large')
      
    • The slowest tests, more than 1.5 seconds, now have the pytest mark slow, and can be selected or deselected during local execution of tests. (#1633)

    • The device test suite has been expanded to cover more qubit operations and observables. (#1510)

    • The MultiControlledX class now inherits from Operation instead of ControlledQubitUnitary which makes the MultiControlledX gate a non-parameterized gate. (#1557)

    • The utils.sparse_hamiltonian function can now deal with non-integer wire labels, and it throws an error for the edge case of observables that are created from multi-qubit operations. (#1550)

    • Added the matrix attribute to qml.templates.subroutines.GroverOperator (#1553)

    • The tape.to_openqasm() method now has a measure_all argument that specifies whether the serialized OpenQASM script includes computational basis measurements on all of the qubits or just those specified by the tape. (#1559)

    • An error is now raised when no arguments are passed to an observable, to inform that wires have not been supplied. (#1547)

    • The group_observables transform is now differentiable. (#1483)

      For example:

      import jax
      from jax import numpy as jnp
      
      coeffs = jnp.array([1., 2., 3.])
      obs = [PauliX(wires=0), PauliX(wires=1), PauliZ(wires=1)]
      
      def group(coeffs, select=None):
        _, grouped_coeffs = qml.grouping.group_observables(obs, coeffs)
        # in this example, grouped_coeffs is a list of two jax tensors
        # [DeviceArray([1., 2.], dtype=float32), DeviceArray([3.], dtype=float32)]
        return grouped_coeffs[select]
      
      jac_fn = jax.jacobian(group)
      
      >>> jac_fn(coeffs, select=0)
      [[1. 0. 0.]
      [0. 1. 0.]]
      
      >>> jac_fn(coeffs, select=1)
      [[0., 0., 1.]]
      
    • The tape does not verify any more that all Observables have owners in the annotated queue. (#1505)

      This allows manipulation of Observables inside a tape context. An example is expval(Tensor(qml.PauliX(0), qml.Identity(1)).prune()) which makes the expval an owner of the pruned tensor and its constituent observables, but leaves the original tensor in the queue without an owner.

    • The qml.ResetError is now supported for default.mixed device. (#1541)

    • QNode.diff_method will now reflect which method was selected from diff_method="best". (#1568)

    • QNodes now support diff_method=None. This works the same as interface=None. Such QNodes accept floats, ints, lists and NumPy arrays and return NumPy output but can not be differentiated. (#1585)

    • QNodes now include validation to warn users if a supplied keyword argument is not one of the recognized arguments. (#1591)

    Breaking changes

    • The QFT operation has been moved, and is now accessible via pennylane.templates.QFT. (#1548)

    • Specifying shots=None with qml.sample was previously deprecated. From this release onwards, setting shots=None when sampling will raise an error also for default.qubit.jax. (#1629)

    • An error is raised during QNode creation when a user requests backpropagation on a device with finite-shots. (#1588)

    • The class qml.Interferometer is deprecated and will be renamed qml.InterferometerUnitary after one release cycle. (#1546)

    • All optimizers except for Rotosolve and Rotoselect now have a public attribute stepsize. Temporary backward compatibility has been added to support the use of _stepsize for one release cycle. update_stepsize method is deprecated. (#1625)

    Bug fixes

    • Fixed a bug with shot vectors and Device base class. (#1666)

    • Fixed a bug where @jax.jit would fail on a QNode that used qml.QubitStateVector. (#1649)

    • Fixed a bug related to an edge case of single-qubit zyz_decomposition when only off-diagonal elements are present. (#1643)

    • MottonenStatepreparation can now be run with a single wire label not in a list. (#1620)

    • Fixed the circuit representation of CY gates to align with CNOT and CZ gates when calling the circuit drawer. (#1504)

    • Dask and CVXPY dependent tests are skipped if those packages are not installed. (#1617)

    • The qml.layer template now works with tensorflow variables. (#1615)

    • Remove QFT from possible operations in default.qubit and default.mixed. (#1600)

    • Fixed a bug when computing expectations of Hamiltonians using TensorFlow. (#1586)

    • Fixed a bug when computing the specs of a circuit with a Hamiltonian observable. (#1533)

    Documentation

    • The qml.Identity operation is placed under the sections Qubit observables and CV observables. (#1576)

    • Updated the documentation of qml.grouping, qml.kernels and qml.qaoa modules to present the list of functions first followed by the technical details of the module. (#1581)

    • Recategorized Qubit operations into new and existing categories so that code for each operation is easier to locate. (#1566)

    Contributors

    This release contains contributions from (in alphabetical order):

    Vishnu Ajith, Akash Narayanan B, Thomas Bromley, Olivia Di Matteo, Sahaj Dhamija, Tanya Garg, Anthony Hayes, Theodor Isacsson, Josh Izaac, Prateek Jain, Ankit Khandelwal, Nathan Killoran, Christina Lee, Ian McLean, Johannes Jakob Meyer, Romain Moyard, Lee James O'Riordan, Esteban Payares, Pratul Saini, Maria Schuld, Arshpreet Singh, Jay Soni, Ingrid Strandberg, Antal Száva, Slimane Thabet, David Wierichs, Vincent Wong.

    Source code(tar.gz)
    Source code(zip)
  • v0.17.0(Aug 17, 2021)

    New features since the last release

    Circuit optimization

    • PennyLane can now perform quantum circuit optimization using the top-level transform qml.compile. The compile transform allows you to chain together sequences of tape and quantum function transforms into custom circuit optimization pipelines. (#1475)

      For example, take the following decorated quantum function:

      dev = qml.device('default.qubit', wires=[0, 1, 2])
      
      @qml.qnode(dev)
      @qml.compile()
      def qfunc(x, y, z):
          qml.Hadamard(wires=0)
          qml.Hadamard(wires=1)
          qml.Hadamard(wires=2)
          qml.RZ(z, wires=2)
          qml.CNOT(wires=[2, 1])
          qml.RX(z, wires=0)
          qml.CNOT(wires=[1, 0])
          qml.RX(x, wires=0)
          qml.CNOT(wires=[1, 0])
          qml.RZ(-z, wires=2)
          qml.RX(y, wires=2)
          qml.PauliY(wires=2)
          qml.CZ(wires=[1, 2])
          return qml.expval(qml.PauliZ(wires=0))
      

      The default behaviour of qml.compile is to apply a sequence of three transforms: commute_controlled, cancel_inverses, and then merge_rotations.

      >>> print(qml.draw(qfunc)(0.2, 0.3, 0.4))
       0: ──H───RX(0.6)──────────────────┤ ⟨Z⟩
       1: ──H──╭X────────────────────╭C──┤
       2: ──H──╰C────────RX(0.3)──Y──╰Z──┤
      

      The qml.compile transform is flexible and accepts a custom pipeline of tape and quantum function transforms (you can even write your own!). For example, if we wanted to only push single-qubit gates through controlled gates and cancel adjacent inverses, we could do:

      from pennylane.transforms import commute_controlled, cancel_inverses
      pipeline = [commute_controlled, cancel_inverses]
      
      @qml.qnode(dev)
      @qml.compile(pipeline=pipeline)
      def qfunc(x, y, z):
          qml.Hadamard(wires=0)
          qml.Hadamard(wires=1)
          qml.Hadamard(wires=2)
          qml.RZ(z, wires=2)
          qml.CNOT(wires=[2, 1])
          qml.RX(z, wires=0)
          qml.CNOT(wires=[1, 0])
          qml.RX(x, wires=0)
          qml.CNOT(wires=[1, 0])
          qml.RZ(-z, wires=2)
          qml.RX(y, wires=2)
          qml.PauliY(wires=2)
          qml.CZ(wires=[1, 2])
          return qml.expval(qml.PauliZ(wires=0))
      
      >>> print(qml.draw(qfunc)(0.2, 0.3, 0.4))
       0: ──H───RX(0.4)──RX(0.2)────────────────────────────┤ ⟨Z⟩
       1: ──H──╭X───────────────────────────────────────╭C──┤
       2: ──H──╰C────────RZ(0.4)──RZ(-0.4)──RX(0.3)──Y──╰Z──┤
      

      The following compilation transforms have been added and are also available to use, either independently, or within a qml.compile pipeline:

      • commute_controlled: push commuting single-qubit gates through controlled operations. (#1464)

      • cancel_inverses: removes adjacent pairs of operations that cancel out. (#1455)

      • merge_rotations: combines adjacent rotation gates of the same type into a single gate, including controlled rotations. (#1455)

      • single_qubit_fusion: acts on all sequences of single-qubit operations in a quantum function, and converts each sequence to a single Rot gate. (#1458)

      For more details on qml.compile and the available compilation transforms, see the compilation documentation.

    QNodes are even more powerful

    • Computational basis samples directly from the underlying device can now be returned directly from QNodes via qml.sample(). (#1441)
    dev = qml.device("default.qubit", wires=3, shots=5)
    
    @qml.qnode(dev)
    def circuit_1():
        qml.Hadamard(wires=0)
        qml.Hadamard(wires=1)
        return qml.sample()
    
    @qml.qnode(dev)
    def circuit_2():
        qml.Hadamard(wires=0)
        qml.Hadamard(wires=1)
        return qml.sample(wires=[0,2])    # no observable provided and wires specified
    
    >>> print(circuit_1())
    [[1, 0, 0],
     [1, 1, 0],
     [1, 0, 0],
     [0, 0, 0],
     [0, 1, 0]]
    
    >>> print(circuit_2())
    [[1, 0],
     [1, 0],
     [1, 0],
     [0, 0],
     [0, 0]]
    
    >>> print(qml.draw(circuit_2)())
     0: ──H──╭┤ Sample[basis]
     1: ──H──│┤
     2: ─────╰┤ Sample[basis]
    
    • The new qml.apply function can be used to add operations that might have already been instantiated elsewhere to the QNode and other queuing contexts: (#1433)

      op = qml.RX(0.4, wires=0)
      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(x):
          qml.RY(x, wires=0)
          qml.apply(op)
          return qml.expval(qml.PauliZ(0))
      
      >>> print(qml.draw(circuit)(0.6))
      0: ──RY(0.6)──RX(0.4)──┤ ⟨Z⟩
      

      Previously instantiated measurements can also be applied to QNodes.

    Device Resource Tracker

    • The new Device Tracker capabilities allows for flexible and versatile tracking of executions, even inside parameter-shift gradients. This functionality will improve the ease of monitoring large batches and remote jobs. (#1355)

      dev = qml.device('default.qubit', wires=1, shots=100)
      
      @qml.qnode(dev, diff_method="parameter-shift")
      def circuit(x):
          qml.RX(x, wires=0)
          return qml.expval(qml.PauliZ(0))
      
      x = np.array(0.1)
      
      with qml.Tracker(circuit.device) as tracker:
          qml.grad(circuit)(x)
      
      >>> tracker.totals
      {'executions': 3, 'shots': 300, 'batches': 1, 'batch_len': 2}
      >>> tracker.history
      {'executions': [1, 1, 1],
       'shots': [100, 100, 100],
       'batches': [1],
       'batch_len': [2]}
      >>> tracker.latest
      {'batches': 1, 'batch_len': 2}
      

      Users can also provide a custom function to the callback keyword that gets called each time the information is updated. This functionality allows users to monitor remote jobs or large parameter-shift batches.

      >>> def shots_info(totals, history, latest):
      ...     print("Total shots: ", totals['shots'])
      >>> with qml.Tracker(circuit.device, callback=shots_info) as tracker:
      ...     qml.grad(circuit)(0.1)
      Total shots:  100
      Total shots:  200
      Total shots:  300
      Total shots:  300
      

    Containerization support

    • Docker support for building PennyLane with support for all interfaces (TensorFlow, Torch, and Jax), as well as device plugins and QChem, for GPUs and CPUs, has been added. (#1391)

      The build process using Docker and make requires that the repository source code is cloned or downloaded from GitHub. Visit the the detailed description for an extended list of options.

    Improved Hamiltonian simulations

    • Added a sparse Hamiltonian observable and the functionality to support computing its expectation value with default.qubit. (#1398)

      For example, the following QNode returns the expectation value of a sparse Hamiltonian:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev, diff_method="parameter-shift")
      def circuit(param, H):
          qml.PauliX(0)
          qml.SingleExcitation(param, wires=[0, 1])
          return qml.expval(qml.SparseHamiltonian(H, [0, 1]))
      

      We can execute this QNode, passing in a sparse identity matrix:

      >>> print(circuit([0.5], scipy.sparse.eye(4).tocoo()))
      0.9999999999999999
      

      The expectation value of the sparse Hamiltonian is computed directly, which leads to executions that are faster by orders of magnitude. Note that "parameter-shift" is the only differentiation method that is currently supported when the observable is a sparse Hamiltonian.

    • VQE problems can now be intuitively set up by passing the Hamiltonian as an observable. (#1474)

      dev = qml.device("default.qubit", wires=2)
      H = qml.Hamiltonian([1., 2., 3.],  [qml.PauliZ(0), qml.PauliY(0), qml.PauliZ(1)])
      w = qml.init.strong_ent_layers_uniform(1, 2, seed=1967)
      
      @qml.qnode(dev)
      def circuit(w):
          qml.templates.StronglyEntanglingLayers(w, wires=range(2))
          return qml.expval(H)
      
      >>> print(circuit(w))
      -1.5133943637878295
      >>> print(qml.grad(circuit)(w))
      [[[-8.32667268e-17  1.39122955e+00 -9.12462052e-02]
      [ 1.02348685e-16 -7.77143238e-01 -1.74708049e-01]]]
      

      Note that other measurement types like var(H) or sample(H), as well as multiple expectations like expval(H1), expval(H2) are not supported.

    • Added functionality to compute the sparse matrix representation of a qml.Hamiltonian object. (#1394)

    New gradients module

    • A new gradients module qml.gradients has been added, which provides differentiable quantum gradient transforms. (#1476) (#1479) (#1486)

      Available quantum gradient transforms include:

      • qml.gradients.finite_diff
      • qml.gradients.param_shift
      • qml.gradients.param_shift_cv

      For example,

      >>> params = np.array([0.3,0.4,0.5], requires_grad=True)
      >>> with qml.tape.JacobianTape() as tape:
      ...     qml.RX(params[0], wires=0)
      ...     qml.RY(params[1], wires=0)
      ...     qml.RX(params[2], wires=0)
      ...     qml.expval(qml.PauliZ(0))
      ...     qml.var(qml.PauliZ(0))
      >>> tape.trainable_params = {0, 1, 2}
      >>> gradient_tapes, fn = qml.gradients.finite_diff(tape)
      >>> res = dev.batch_execute(gradient_tapes)
      >>> fn(res)
      array([[-0.69688381, -0.32648317, -0.68120105],
             [ 0.8788057 ,  0.41171179,  0.85902895]])
      

    Even more new operations and templates

    • Grover Diffusion Operator template added. (#1442)

      For example, if we have an oracle that marks the "all ones" state with a negative sign:

      n_wires = 3
      wires = list(range(n_wires))
      
      def oracle():
          qml.Hadamard(wires[-1])
          qml.Toffoli(wires=wires)
          qml.Hadamard(wires[-1])
      

      We can perform Grover's Search Algorithm:

      dev = qml.device('default.qubit', wires=wires)
      
      @qml.qnode(dev)
      def GroverSearch(num_iterations=1):
          for wire in wires:
              qml.Hadamard(wire)
      
          for _ in range(num_iterations):
              oracle()
              qml.templates.GroverOperator(wires=wires)
      
          return qml.probs(wires)
      

      We can see this circuit yields the marked state with high probability:

      >>> GroverSearch(num_iterations=1)
      tensor([0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125, 0.03125,
              0.78125], requires_grad=True)
      >>> GroverSearch(num_iterations=2)
      tensor([0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125, 0.0078125,
          0.0078125, 0.9453125], requires_grad=True)
      
    • A decomposition has been added to QubitUnitary that makes the single-qubit case fully differentiable in all interfaces. Furthermore, a quantum function transform, unitary_to_rot(), has been added to decompose all single-qubit instances of QubitUnitary in a quantum circuit. (#1427)

      Instances of QubitUnitary may now be decomposed directly to Rot operations, or RZ operations if the input matrix is diagonal. For example, let

      >>> U = np.array([
          [-0.28829348-0.78829734j,  0.30364367+0.45085995j],
          [ 0.53396245-0.10177564j,  0.76279558-0.35024096j]
      ])
      

      Then, we can compute the decomposition as:

      >>> qml.QubitUnitary.decomposition(U, wires=0)
      [Rot(-0.24209530281458358, 1.1493817777199102, 1.733058145303424, wires=[0])]
      

      We can also apply the transform directly to a quantum function, and compute the gradients of parameters used to construct the unitary matrices.

      def qfunc_with_qubit_unitary(angles):
          z, x = angles[0], angles[1]
      
          Z_mat = np.array([[np.exp(-1j * z / 2), 0.0], [0.0, np.exp(1j * z / 2)]])
      
          c = np.cos(x / 2)
          s = np.sin(x / 2) * 1j
          X_mat = np.array([[c, -s], [-s, c]])
      
          qml.Hadamard(wires="a")
          qml.QubitUnitary(Z_mat, wires="a")
          qml.QubitUnitary(X_mat, wires="b")
          qml.CNOT(wires=["b", "a"])
          return qml.expval(qml.PauliX(wires="a"))
      
      >>> dev = qml.device("default.qubit", wires=["a", "b"])
      >>> transformed_qfunc = qml.transforms.unitary_to_rot(qfunc_with_qubit_unitary)
      >>> transformed_qnode = qml.QNode(transformed_qfunc, dev)
      >>> input = np.array([0.3, 0.4], requires_grad=True)
      >>> transformed_qnode(input)
      tensor(0.95533649, requires_grad=True)
      >>> qml.grad(transformed_qnode)(input)
      array([-0.29552021,  0.        ])
      
    • Ising YY gate functionality added. (#1358)

    Improvements

    • The step and step_and_cost methods of QNGOptimizer now accept a custom grad_fn keyword argument to use for gradient computations. (#1487)

    • The precision used by default.qubit.jax now matches the float precision indicated by

      from jax.config import config
      config.read('jax_enable_x64')
      

      where True means float64/complex128 and False means float32/complex64. (#1485)

    • The ./pennylane/ops/qubit.py file is broken up into a folder of six separate files. (#1467)

    • Changed to using commas as the separator of wires in the string representation of qml.Hamiltonian objects for multi-qubit terms. (#1465)

    • Changed to using np.object_ instead of np.object as per the NumPy deprecations starting version 1.20. (#1466)

    • Change the order of the covariance matrix and the vector of means internally in default.gaussian. (#1331)

    • Added the id attribute to templates. (#1438)

    • The qml.math module, for framework-agnostic tensor manipulation, has two new functions available: (#1490)

      • qml.math.get_trainable_indices(sequence_of_tensors): returns the indices corresponding to trainable tensors in the input sequence.

      • qml.math.unwrap(sequence_of_tensors): unwraps a sequence of tensor-like objects to NumPy arrays.

      In addition, the behaviour of qml.math.requires_grad has been improved in order to correctly determine trainability during Autograd and JAX backwards passes.

    • A new tape method, tape.unwrap() is added. This method is a context manager; inside the context, the tape's parameters are unwrapped to NumPy arrays and floats, and the trainable parameter indices are set. (#1491)

      These changes are temporary, and reverted on exiting the context.

      >>> with tf.GradientTape():
      ...     with qml.tape.QuantumTape() as tape:
      ...         qml.RX(tf.Variable(0.1), wires=0)
      ...         qml.RY(tf.constant(0.2), wires=0)
      ...         qml.RZ(tf.Variable(0.3), wires=0)
      ...     with tape.unwrap():
      ...         print("Trainable params:", tape.trainable_params)
      ...         print("Unwrapped params:", tape.get_parameters())
      Trainable params: {0, 2}
      Unwrapped params: [0.1, 0.3]
      >>> print("Original parameters:", tape.get_parameters())
      Original parameters: [<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.1>,
        <tf.Variable 'Variable:0' shape=() dtype=float32, numpy=0.3>]
      

      In addition, qml.tape.Unwrap is a context manager that unwraps multiple tapes:

      >>> with qml.tape.Unwrap(tape1, tape2):
      

    Breaking changes

    • Removed the deprecated tape methods get_resources and get_depth as they are superseded by the specs tape attribute. (#1522)

    • Specifying shots=None with qml.sample was previously deprecated. From this release onwards, setting shots=None when sampling will raise an error. (#1522)

    • The existing pennylane.collections.apply function is no longer accessible via qml.apply, and needs to be imported directly from the collections package. (#1358)

    Bug fixes

    • Fixes a bug in qml.adjoint and qml.ctrl where the adjoint of operations outside of a QNode or a QuantumTape could not be obtained. (#1532)

    • Fixes a bug in GradientDescentOptimizer and NesterovMomentumOptimizer where a cost function with one trainable parameter and non-trainable parameters raised an error. (#1495)

    • Fixed an example in the documentation's introduction to numpy gradients, where the wires were a non-differentiable argument to the QNode. (#1499)

    • Fixed a bug where the adjoint of qml.QFT when using the qml.adjoint function was not correctly computed. (#1451)

    • Fixed the differentiability of the operationIsingYY for Autograd, Jax and Tensorflow. (#1425)

    • Fixed a bug in the torch interface that prevented gradients from being computed on a GPU. (#1426)

    • Quantum function transforms now preserve the format of the measurement results, so that a single measurement returns a single value rather than an array with a single element. (#1434)

    • Fixed a bug in the parameter-shift Hessian implementation, which resulted in the incorrect Hessian being returned for a cost function that performed post-processing on a vector-valued QNode. (#1436)

    • Fixed a bug in the initialization of QubitUnitary where the size of the matrix was not checked against the number of wires. (#1439)

    Documentation

    • Improved Contribution Guide and Pull Requests Guide. (#1461)

    • Examples have been added to clarify use of the continuous-variable FockStateVector operation in the multi-mode case. (#1472)

    Contributors

    This release contains contributions from (in alphabetical order):

    Juan Miguel Arrazola, Olivia Di Matteo, Anthony Hayes, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Arshpreet Singh Khangura, Leonhard Kunczik, Christina Lee, Romain Moyard, Lee James O'Riordan, Ashish Panigrahi, Nahum Sá, Maria Schuld, Jay Soni, Antal Száva, David Wierichs.

    Source code(tar.gz)
    Source code(zip)
  • v0.16.0(Jun 22, 2021)

    New features since last release

    First class support for quantum kernels

    • The new qml.kernels module provides basic functionalities for working with quantum kernels as well as post-processing methods to mitigate sampling errors and device noise: (#1102)

      
      num_wires = 6
      wires = range(num_wires)
      dev = qml.device('default.qubit', wires=wires)
      
      @qml.qnode(dev)
      def kernel_circuit(x1, x2):
          qml.templates.AngleEmbedding(x1, wires=wires)
          qml.adjoint(qml.templates.AngleEmbedding)(x2, wires=wires)
          return qml.probs(wires)
      
      kernel = lambda x1, x2: kernel_circuit(x1, x2)[0]
      X_train = np.random.random((10, 6))
      X_test = np.random.random((5, 6))
      
      # Create symmetric square kernel matrix (for training)
      K = qml.kernels.square_kernel_matrix(X_train, kernel)
      
      # Compute kernel between test and training data.
      K_test = qml.kernels.kernel_matrix(X_train, X_test, kernel)
      K1 = qml.kernels.mitigate_depolarizing_noise(K, num_wires, method='single')
      

    Extract the fourier representation of quantum circuits

    • PennyLane now has a fourier module, which hosts a growing library of methods that help with investigating the Fourier representation of functions implemented by quantum circuits. The Fourier representation can be used to examine and characterize the expressivity of the quantum circuit. (#1160) (#1378)

      For example, one can plot distributions over Fourier series coefficients like this one:

    Seamless support for working with the Pauli group

    • Added functionality for constructing and manipulating the Pauli group (#1181).

      The function qml.grouping.pauli_group provides a generator to easily loop over the group, or construct and store it in its entirety. For example, we can construct the single-qubit Pauli group like so:

      >>> from pennylane.grouping import pauli_group
      >>> pauli_group_1_qubit = list(pauli_group(1))
      >>> pauli_group_1_qubit
      [Identity(wires=[0]), PauliZ(wires=[0]), PauliX(wires=[0]), PauliY(wires=[0])]
      

      We can multiply together its members at the level of Pauli words using the pauli_mult and pauli_multi_with_phase functions. This can be done on arbitrarily-labeled wires as well, by defining a wire map.

      >>> from pennylane.grouping import pauli_group, pauli_mult
      >>> wire_map = {'a' : 0, 'b' : 1, 'c' : 2}
      >>> pg = list(pauli_group(3, wire_map=wire_map))
      >>> pg[3]
      PauliZ(wires=['b']) @ PauliZ(wires=['c'])
      >>> pg[55]
      PauliY(wires=['a']) @ PauliY(wires=['b']) @ PauliZ(wires=['c'])
      >>> pauli_mult(pg[3], pg[55], wire_map=wire_map)
      PauliY(wires=['a']) @ PauliX(wires=['b'])
      

      Functions for conversion of Pauli observables to strings (and back) are included.

      >>> from pennylane.grouping import pauli_word_to_string, string_to_pauli_word
      >>> pauli_word_to_string(pg[55], wire_map=wire_map)
      'YYZ'
      >>> string_to_pauli_word('ZXY', wire_map=wire_map)
      PauliZ(wires=['a']) @ PauliX(wires=['b']) @ PauliY(wires=['c'])
      

      Calculation of the matrix representation for arbitrary Paulis and wire maps is now also supported.

      >>> from pennylane.grouping import pauli_word_to_matrix
      >>> wire_map = {'a' : 0, 'b' : 1}
      >>> pauli_word = qml.PauliZ('b')  # corresponds to Pauli 'IZ'
      >>> pauli_word_to_matrix(pauli_word, wire_map=wire_map)
      array([[ 1.,  0.,  0.,  0.],
             [ 0., -1.,  0., -0.],
             [ 0.,  0.,  1.,  0.],
             [ 0., -0.,  0., -1.]])
      

    New transforms

    • The qml.specs QNode transform creates a function that returns specifications or details about the QNode, including depth, number of gates, and number of gradient executions required. (#1245)

      For example:

      dev = qml.device('default.qubit', wires=4)
      
      @qml.qnode(dev, diff_method='parameter-shift')
      def circuit(x, y):
          qml.RX(x[0], wires=0)
          qml.Toffoli(wires=(0, 1, 2))
          qml.CRY(x[1], wires=(0, 1))
          qml.Rot(x[2], x[3], y, wires=0)
          return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliX(1))
      

      We can now use the qml.specs transform to generate a function that returns details and resource information:

      >>> x = np.array([0.05, 0.1, 0.2, 0.3], requires_grad=True)
      >>> y = np.array(0.4, requires_grad=False)
      >>> specs_func = qml.specs(circuit)
      >>> specs_func(x, y)
      {'gate_sizes': defaultdict(int, {1: 2, 3: 1, 2: 1}),
       'gate_types': defaultdict(int, {'RX': 1, 'Toffoli': 1, 'CRY': 1, 'Rot': 1}),
       'num_operations': 4,
       'num_observables': 2,
       'num_diagonalizing_gates': 1,
       'num_used_wires': 3,
       'depth': 4,
       'num_trainable_params': 4,
       'num_parameter_shift_executions': 11,
       'num_device_wires': 4,
       'device_name': 'default.qubit',
       'diff_method': 'parameter-shift'}
      

      The tape methods get_resources and get_depth are superseded by specs and will be deprecated after one release cycle.

    • Adds a decorator @qml.qfunc_transform to easily create a transformation that modifies the behaviour of a quantum function. (#1315)

      For example, consider the following transform, which scales the parameter of all RX gates by :math:x \rightarrow \sin(a) \sqrt{x}, and the parameters of all RY gates by :math:y \rightarrow \cos(a * b) y:

      @qml.qfunc_transform
      def my_transform(tape, a, b):
          for op in tape.operations + tape.measurements:
              if op.name == "RX":
                  x = op.parameters[0]
                  qml.RX(qml.math.sin(a) * qml.math.sqrt(x), wires=op.wires)
              elif op.name == "RY":
                  y = op.parameters[0]
                  qml.RX(qml.math.cos(a * b) * y, wires=op.wires)
              else:
                  op.queue()
      

      We can now apply this transform to any quantum function:

      dev = qml.device("default.qubit", wires=2)
      
      def ansatz(x):
          qml.Hadamard(wires=0)
          qml.RX(x[0], wires=0)
          qml.RY(x[1], wires=1)
          qml.CNOT(wires=[0, 1])
      
      @qml.qnode(dev)
      def circuit(params, transform_weights):
          qml.RX(0.1, wires=0)
      
          # apply the transform to the ansatz
          my_transform(*transform_weights)(ansatz)(params)
      
          return qml.expval(qml.PauliZ(1))
      

      We can print this QNode to show that the qfunc transform is taking place:

      >>> x = np.array([0.5, 0.3], requires_grad=True)
      >>> transform_weights = np.array([0.1, 0.6], requires_grad=True)
      >>> print(qml.draw(circuit)(x, transform_weights))
       0: ──RX(0.1)────H──RX(0.0706)──╭C──┤
       1: ──RX(0.299)─────────────────╰X──┤ ⟨Z⟩
      

      Evaluating the QNode, as well as the derivative, with respect to the gate parameter and the transform weights:

      >>> circuit(x, transform_weights)
      tensor(0.00672829, requires_grad=True)
      >>> qml.grad(circuit)(x, transform_weights)
      (array([ 0.00671711, -0.00207359]), array([6.69695008e-02, 3.73694364e-06]))
      
    • Adds a hamiltonian_expand tape transform. This takes a tape ending in qml.expval(H), where H is a Hamiltonian, and maps it to a collection of tapes which can be executed and passed into a post-processing function yielding the expectation value. (#1142)

      Example use:

      H = qml.PauliZ(0) + 3 * qml.PauliZ(0) @ qml.PauliX(1)
      
      with qml.tape.QuantumTape() as tape:
          qml.Hadamard(wires=1)
          qml.expval(H)
      
      tapes, fn = qml.transforms.hamiltonian_expand(tape)
      

      We can now evaluate the transformed tapes, and apply the post-processing function:

      >>> dev = qml.device("default.qubit", wires=3)
      >>> res = dev.batch_execute(tapes)
      >>> fn(res)
      3.999999999999999
      
    • The quantum_monte_carlo transform has been added, allowing an input circuit to be transformed into the full quantum Monte Carlo algorithm. (#1316)

      Suppose we want to measure the expectation value of the sine squared function according to a standard normal distribution. We can calculate the expectation value analytically as 0.432332, but we can also estimate using the quantum Monte Carlo algorithm. The first step is to discretize the problem:

      from scipy.stats import norm
      
      m = 5
      M = 2 ** m
      
      xmax = np.pi  # bound to region [-pi, pi]
      xs = np.linspace(-xmax, xmax, M)
      
      probs = np.array([norm().pdf(x) for x in xs])
      probs /= np.sum(probs)
      
      func = lambda i: np.sin(xs[i]) ** 2
      r_rotations = np.array([2 * np.arcsin(np.sqrt(func(i))) for i in range(M)])
      

      The quantum_monte_carlo transform can then be used:

      from pennylane.templates.state_preparations.mottonen import (
          _uniform_rotation_dagger as r_unitary,
      )
      
      n = 6
      N = 2 ** n
      
      a_wires = range(m)
      wires = range(m + 1)
      target_wire = m
      estimation_wires = range(m + 1, n + m + 1)
      
      dev = qml.device("default.qubit", wires=(n + m + 1))
      
      def fn():
          qml.templates.MottonenStatePreparation(np.sqrt(probs), wires=a_wires)
          r_unitary(qml.RY, r_rotations, control_wires=a_wires[::-1], target_wire=target_wire)
      
      @qml.qnode(dev)
      def qmc():
          qml.quantum_monte_carlo(fn, wires, target_wire, estimation_wires)()
          return qml.probs(estimation_wires)
      
      phase_estimated = np.argmax(qmc()[:int(N / 2)]) / N
      

      The estimated value can be retrieved using:

      >>> (1 - np.cos(np.pi * phase_estimated)) / 2
      0.42663476277231915
      

      The resources required to perform the quantum Monte Carlo algorithm can also be inspected using the specs transform.

    Extended QAOA module

    • Functionality to support solving the maximum-weighted cycle problem has been added to the qaoa module. (#1207) (#1209) (#1251) (#1213) (#1220) (#1214) (#1283) (#1297) (#1396) (#1403)

      The max_weight_cycle function returns the appropriate cost and mixer Hamiltonians:

      >>> a = np.random.random((3, 3))
      >>> np.fill_diagonal(a, 0)
      >>> g = nx.DiGraph(a)  # create a random directed graph
      >>> cost, mixer, mapping = qml.qaoa.max_weight_cycle(g)
      >>> print(cost)
        (-0.9775906842165344) [Z2]
      + (-0.9027248603361988) [Z3]
      + (-0.8722207409852838) [Z0]
      + (-0.6426184210832898) [Z5]
      + (-0.2832594164291379) [Z1]
      + (-0.0778133996933755) [Z4]
      >>> print(mapping)
      {0: (0, 1), 1: (0, 2), 2: (1, 0), 3: (1, 2), 4: (2, 0), 5: (2, 1)}
      

      Additional functionality can be found in the qml.qaoa.cycle module.

    Extended operations and templates

    • Added functionality to compute the sparse matrix representation of a qml.Hamiltonian object. (#1394)

      coeffs = [1, -0.45]
      obs = [qml.PauliZ(0) @ qml.PauliZ(1), qml.PauliY(0) @ qml.PauliZ(1)]
      H = qml.Hamiltonian(coeffs, obs)
      H_sparse = qml.utils.sparse_hamiltonian(H)
      

      The resulting matrix is a sparse matrix in scipy coordinate list (COO) format:

      >>> H_sparse
      <4x4 sparse matrix of type '<class 'numpy.complex128'>'
          with 8 stored elements in COOrdinate format>
      

      The sparse matrix can be converted to an array as:

      >>> H_sparse.toarray()
      array([[ 1.+0.j  ,  0.+0.j  ,  0.+0.45j,  0.+0.j  ],
             [ 0.+0.j  , -1.+0.j  ,  0.+0.j  ,  0.-0.45j],
             [ 0.-0.45j,  0.+0.j  , -1.+0.j  ,  0.+0.j  ],
             [ 0.+0.j  ,  0.+0.45j,  0.+0.j  ,  1.+0.j  ]])
      
    • Adds the new template AllSinglesDoubles to prepare quantum states of molecules using the SingleExcitation and DoubleExcitation operations. The new template reduces significantly the number of operations and the depth of the quantum circuit with respect to the traditional UCCSD unitary. (#1383)

      For example, consider the case of two particles and four qubits. First, we define the Hartree-Fock initial state and generate all possible single and double excitations.

      import pennylane as qml
      from pennylane import numpy as np
      
      electrons = 2
      qubits = 4
      
      hf_state = qml.qchem.hf_state(electrons, qubits)
      singles, doubles = qml.qchem.excitations(electrons, qubits)
      

      Now we can use the template AllSinglesDoubles to define the quantum circuit,

      from pennylane.templates import AllSinglesDoubles
      
      wires = range(qubits)
      
      dev = qml.device('default.qubit', wires=wires)
      
      @qml.qnode(dev)
      def circuit(weights, hf_state, singles, doubles):
          AllSinglesDoubles(weights, wires, hf_state, singles, doubles)
          return qml.expval(qml.PauliZ(0))
      
      params = np.random.normal(0, np.pi, len(singles) + len(doubles))
      

      and execute it:

      >>> circuit(params, hf_state, singles=singles, doubles=doubles)
      tensor(-0.73772194, requires_grad=True)
      
    • Adds QubitCarry and QubitSum operations for basic arithmetic. (#1169)

      The following example adds two 1-bit numbers, returning a 2-bit answer:

      dev = qml.device('default.qubit', wires = 4)
      a = 0
      b = 1
      
      @qml.qnode(dev)
      def circuit():
          qml.BasisState(np.array([a, b]), wires=[1, 2])
          qml.QubitCarry(wires=[0, 1, 2, 3])
          qml.CNOT(wires=[1, 2])
          qml.QubitSum(wires=[0, 1, 2])
          return qml.probs(wires=[3, 2])
      
      probs = circuit()
      bitstrings = tuple(itertools.product([0, 1], repeat = 2))
      indx = np.argwhere(probs == 1).flatten()[0]
      output = bitstrings[indx]
      
      >>> print(output)
      (0, 1)
      
    • Added the qml.Projector observable, which is available on all devices inheriting from the QubitDevice class. (#1356) (#1368)

      Using qml.Projector, we can define the basis state projectors to use when computing expectation values. Let us take for example a circuit that prepares Bell states:

      dev = qml.device("default.qubit", wires=2)
      
      @qml.qnode(dev)
      def circuit(basis_state):
          qml.Hadamard(wires=[0])
          qml.CNOT(wires=[0, 1])
          return qml.expval(qml.Projector(basis_state, wires=[0, 1]))
      

      We can then specify the |00> basis state to construct the |00><00| projector and compute the expectation value:

      >>> basis_state = [0, 0]
      >>> circuit(basis_state)
      tensor(0.5, requires_grad=True)
      

      As expected, we get similar results when specifying the |11> basis state:

      >>> basis_state = [1, 1]
      >>> circuit(basis_state)
      tensor(0.5, requires_grad=True)
      
    • The following new operations have been added:

      • The IsingXX gate qml.IsingXX (#1194)
      • The IsingZZ gate qml.IsingZZ (#1199)
      • The ISWAP gate qml.ISWAP (#1298)
      • The reset error noise channel qml.ResetError (#1321)

    Improvements

    • The argnum keyword argument can now be specified for a QNode to define a subset of trainable parameters used to estimate the Jacobian. (#1371)

      For example, consider two trainable parameters and a quantum function:

      dev = qml.device("default.qubit", wires=2)
      
      x = np.array(0.543, requires_grad=True)
      y = np.array(-0.654, requires_grad=True)
      
      def circuit(x,y):
          qml.RX(x, wires=[0])
          qml.RY(y, wires=[1])
          qml.CNOT(wires=[0, 1])
          return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
      

      When computing the gradient of the QNode, we can specify the trainable parameters to consider by passing the argnum keyword argument:

      >>> qnode1 = qml.QNode(circuit, dev, diff_method="parameter-shift", argnum=[0,1])
      >>> print(qml.grad(qnode1)(x,y))
      (array(0.31434679), array(0.67949903))
      

      Specifying a proper subset of the trainable parameters will estimate the Jacobian:

      >>> qnode2 = qml.QNode(circuit, dev, diff_method="parameter-shift", argnum=[0])
      >>> print(qml.grad(qnode2)(x,y))
      (array(0.31434679), array(0.))
      
    • Allows creating differentiable observables that return custom objects such that the observable is supported by devices. (1291)

      As an example, first we define NewObservable class:

      from pennylane.devices import DefaultQubit
      
      class NewObservable(qml.operation.Observable):
          """NewObservable"""
      
          num_wires = qml.operation.AnyWires
          num_params = 0
          par_domain = None
      
          def diagonalizing_gates(self):
              """Diagonalizing gates"""
              return []
      

      Once we have this new observable class, we define a SpecialObject class that can be used to encode data in an observable and a new device that supports our new observable and returns a SpecialObject as the expectation value (the code is shortened for brevity, the extended example can be found as a test in the previously referenced pull request):

      class SpecialObject:
      
          def __init__(self, val):
              self.val = val
      
          def __mul__(self, other):
              new = SpecialObject(self.val)
              new *= other
              return new
      
          ...
      
      class DeviceSupportingNewObservable(DefaultQubit):
          name = "Device supporting NewObservable"
          short_name = "default.qubit.newobservable"
          observables = DefaultQubit.observables.union({"NewObservable"})
      
          def expval(self, observable, **kwargs):
              if self.shots is None and isinstance(observable, NewObservable):
                  val = super().expval(qml.PauliZ(wires=0), **kwargs)
                  return SpecialObject(val)
      
              return super().expval(observable, **kwargs)
      

      At this point, we can create a device that will support the differentiation of a NewObservable object:

      dev = DeviceSupportingNewObservable(wires=1, shots=None)
      
      @qml.qnode(dev, diff_method="parameter-shift")
      def qnode(x):
          qml.RY(x, wires=0)
          return qml.expval(NewObservable(wires=0))
      

      We can then compute the jacobian of this object:

      >>> result = qml.jacobian(qnode)(0.2)
      >>> print(result)
      <__main__.SpecialObject object at 0x7fd2c54721f0>
      >>> print(result.item().val)
      -0.19866933079506116
      
    • PennyLane NumPy now includes the random module's Generator objects, the recommended way of random number generation. This allows for random number generation using a local, rather than global seed. (#1267)

      from pennylane import numpy as np
      
      rng = np.random.default_rng()
      random_mat1 = rng.random((3,2))
      random_mat2 = rng.standard_normal(3, requires_grad=False)
      
    • The performance of adjoint jacobian differentiation was significantly improved as the method now reuses the state computed on the forward pass. This can be turned off to save memory with the Torch and TensorFlow interfaces by passing adjoint_cache=False during QNode creation. (#1341)

    • The Operator (and by inheritance, the Operation and Observable class and their children) now have an id attribute, which can mark an operator in a circuit, for example to identify it on the tape by a tape transform. (#1377)

    • The benchmark module was deleted, since it was outdated and is superseded by the new separate benchmark repository. (#1343)

    • Decompositions in terms of elementary gates has been added for:

      • qml.CSWAP (#1306)

      • qml.SWAP (#1329)

      • qml.SingleExcitation (#1303)

      • qml.SingleExcitationPlus and qml.SingleExcitationMinus (#1278)

      • qml.DoubleExcitation (#1303)

      • qml.Toffoli (#1320)

      • qml.MultiControlledX. (#1287)

        When controlling on three or more wires, an ancilla register of worker wires is required to support the decomposition.

        ctrl_wires = [f"c{i}" for i in range(5)]
        work_wires = [f"w{i}" for i in range(3)]
        target_wires = ["t0"]
        all_wires = ctrl_wires + work_wires + target_wires
        
        dev = qml.device("default.qubit", wires=all_wires)
        
        with qml.tape.QuantumTape() as tape:
            qml.MultiControlledX(control_wires=ctrl_wires, wires=target_wires, work_wires=work_wires)
        
        >>> tape = tape.expand(depth=1)
        >>> print(tape.draw(wire_order=qml.wires.Wires(all_wires)))
        
         c0: ──────────────╭C──────────────────────╭C──────────┤
         c1: ──────────────├C──────────────────────├C──────────┤
         c2: ──────────╭C──│───╭C──────────────╭C──│───╭C──────┤
         c3: ──────╭C──│───│───│───╭C──────╭C──│───│───│───╭C──┤
         c4: ──╭C──│───│───│───│───│───╭C──│───│───│───│───│───┤
         w0: ──│───│───├C──╰X──├C──│───│───│───├C──╰X──├C──│───┤
         w1: ──│───├C──╰X──────╰X──├C──│───├C──╰X──────╰X──├C──┤
         w2: ──├C──╰X──────────────╰X──├C──╰X──────────────╰X──┤
         t0: ──╰X──────────────────────╰X──────────────────────┤
        
    • Added qml.CPhase as an alias for the existing qml.ControlledPhaseShift operation. (#1319).

    • The Device class now uses caching when mapping wires. (#1270)

    • The Wires class now uses caching for computing its hash. (#1270)

    • Added custom gate application for Toffoli in default.qubit. (#1249)

    • Added validation for noise channel parameters. Invalid noise parameters now raise a ValueError. (#1357)

    • The device test suite now provides test cases for checking gates by comparing expectation values. (#1212)

    • PennyLane's test suite is now code-formatted using black -l 100. (#1222)

    • PennyLane's qchem package and tests are now code-formatted using black -l 100. (#1311)

    Breaking changes

    • The qml.inv() function is now deprecated with a warning to use the more general qml.adjoint(). (#1325)

    • Removes support for Python 3.6 and adds support for Python 3.9. (#1228)

    • The tape methods get_resources and get_depth are superseded by specs and will be deprecated after one release cycle. (#1245)

    • Using the qml.sample() measurement on devices with shots=None continue to raise a warning with this functionality being fully deprecated and raising an error after one release cycle. (#1079) (#1196)

    Bug fixes

    • QNodes now display readable information when in interactive environments or when printed. (#1359).

    • Fixes a bug with qml.math.cast where the MottonenStatePreparation operation expected a float type instead of double. (#1400)

    • Fixes a bug where a copy of qml.ControlledQubitUnitary was non-functional as it did not have all the necessary information. (#1411)

    • Warns when adjoint or reversible differentiation specified or called on a device with finite shots. (#1406)

    • Fixes the differentiability of the operations IsingXX and IsingZZ for Autograd, Jax and Tensorflow. (#1390)

    • Fixes a bug where multiple identical Hamiltonian terms will produce a different result with optimize=True using ExpvalCost. (#1405)

    • Fixes bug where shots=None was not reset when changing shots temporarily in a QNode call like circuit(0.1, shots=3). (#1392)

    • Fixes floating point errors with diff_method="finite-diff" and order=1 when parameters are float32. (#1381)

    • Fixes a bug where qml.ctrl would fail to transform gates that had no control defined and no decomposition defined. (#1376)

    • Copying the JacobianTape now correctly also copies the jacobian_options attribute. This fixes a bug allowing the JAX interface to support adjoint differentiation. (#1349)

    • Fixes drawing QNodes that contain multiple measurements on a single wire. (#1353)

    • Fixes drawing QNodes with no operations. (#1354)

    • Fixes incorrect wires in the decomposition of the ControlledPhaseShift operation. (#1338)

    • Fixed tests for the Permute operation that used a QNode and hence expanded tapes twice instead of once due to QNode tape expansion and an explicit tape expansion call. (#1318).

    • Prevent Hamiltonians that share wires from being multiplied together. (#1273)

    • Fixed a bug where the custom range sequences could not be passed to the StronglyEntanglingLayers template. (#1332)

    • Fixed a bug where qml.sum() and qml.dot() do not support the JAX interface. (#1380)

    Documentation

    • Math present in the QubitParamShiftTape class docstring now renders correctly. (#1402)

    • Fix typo in the documentation of qml.StronglyEntanglingLayers. (#1367)

    • Fixed typo in TensorFlow interface documentation (#1312)

    • Fixed typos in the mathematical expressions in documentation of qml.DoubleExcitation. (#1278)

    • Remove unsupported None option from the qml.QNode docstrings. (#1271)

    • Updated the docstring of qml.PolyXP to reference the new location of internal usage. (#1262)

    • Removes occurrences of the deprecated device argument analytic from the documentation. (#1261)

    • Updated PyTorch and TensorFlow interface introductions. (#1333)

    • Updates the quantum chemistry quickstart to reflect recent changes to the qchem module. (#1227)

    Contributors

    This release contains contributions from (in alphabetical order):

    Marius Aglitoiu, Vishnu Ajith, Juan Miguel Arrazola, Thomas Bromley, Jack Ceroni, Alaric Cheng, Miruna Daian, Olivia Di Matteo, Tanya Garg, Christian Gogolin, Alain Delgado Gran, Diego Guala, Anthony Hayes, Ryan Hill, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Pavan Jayasinha, Nathan Killoran, Christina Lee, Ryan Levy, Alberto Maldonado, Johannes Jakob Meyer, Romain Moyard, Ashish Panigrahi, Nahum Sá, Maria Schuld, Brian Shi, Antal Száva, David Wierichs, Vincent Wong.

    Source code(tar.gz)
    Source code(zip)
  • v0.15.1(May 3, 2021)

    Bug fixes

    • Fixes two bugs in the parameter-shift Hessian. (#1260)

      • Fixes a bug where having an unused parameter in the Autograd interface would result in an indexing error during backpropagation.

      • The parameter-shift Hessian only supports the two-term parameter-shift rule currently, so raises an error if asked to differentiate any unsupported gates (such as the controlled rotation gates).

    • A bug which resulted in qml.adjoint() and qml.inv() failing to work with templates has been fixed. (#1243)

    • Deprecation warning instances in PennyLane have been changed to UserWarning, to account for recent changes to how Python warnings are filtered in PEP565. (#1211)

    • The version requirement for PySCF has been modified to allow for pyscf>=1.7.2. (#1254)

    Documentation

    • Updated the order of the parameters to the GaussianState operation to match the way that the PennyLane-SF plugin uses them. (#1255)

    Contributors

    This release contains contributions from (in alphabetical order):

    Josh Izaac, Maria Schuld, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.15.0(Apr 20, 2021)

    New features since last release

    Better and more flexible shot control

    • Adds a new optimizer qml.ShotAdaptiveOptimizer, a gradient-descent optimizer where the shot rate is adaptively calculated using the variances of the parameter-shift gradient. (#1139)

      By keeping a running average of the parameter-shift gradient and the variance of the parameter-shift gradient, this optimizer frugally distributes a shot budget across the partial derivatives of each parameter.

      In addition, if computing the expectation value of a Hamiltonian, weighted random sampling can be used to further distribute the shot budget across the local terms from which the Hamiltonian is constructed.

      This optimizer is based on both the iCANS1 and Rosalin shot-adaptive optimizers.

      Once constructed, the cost function can be passed directly to the optimizer's step method. The attribute opt.total_shots_used can be used to track the number of shots per iteration.

      >>> coeffs = [2, 4, -1, 5, 2]
      >>> obs = [
      ...   qml.PauliX(1),
      ...   qml.PauliZ(1),
      ...   qml.PauliX(0) @ qml.PauliX(1),
      ...   qml.PauliY(0) @ qml.PauliY(1),
      ...   qml.PauliZ(0) @ qml.PauliZ(1)
      ... ]
      >>> H = qml.Hamiltonian(coeffs, obs)
      >>> dev = qml.device("default.qubit", wires=2, shots=100)
      >>> cost = qml.ExpvalCost(qml.templates.StronglyEntanglingLayers, H, dev)
      >>> params = qml.init.strong_ent_layers_uniform(n_layers=2, n_wires=2)
      >>> opt = qml.ShotAdaptiveOptimizer(min_shots=10)
      >>> for i in range(5):
      ...    params = opt.step(cost, params)
      ...    print(f"Step {i}: cost = {cost(params):.2f}, shots_used = {opt.total_shots_used}")
      Step 0: cost = -5.68, shots_used = 240
      Step 1: cost = -2.98, shots_used = 336
      Step 2: cost = -4.97, shots_used = 624
      Step 3: cost = -5.53, shots_used = 1054
      Step 4: cost = -6.50, shots_used = 1798
      
    • Batches of shots can now be specified as a list, allowing measurement statistics to be course-grained with a single QNode evaluation. (#1103)

      >>> shots_list = [5, 10, 1000]
      >>> dev = qml.device("default.qubit", wires=2, shots=shots_list)
      

      When QNodes are executed on this device, a single execution of 1015 shots will be submitted. However, three sets of measurement statistics will be returned; using the first 5 shots, second set of 10 shots, and final 1000 shots, separately.

      For example, executing a circuit with two outputs will lead to a result of shape (3, 2):

      >>> @qml.qnode(dev)
      ... def circuit(x):
      ...     qml.RX(x, wires=0)
      ...     qml.CNOT(wires=[0, 1])
      ...     return qml.expval(qml.PauliZ(0) @ qml.PauliX(1)), qml.expval(qml.PauliZ(0))
      >>> circuit(0.5)
      [[0.33333333 1.        ]
       [0.2        1.        ]
       [0.012      0.868     ]]
      

      This output remains fully differentiable.

    • The number of shots can now be specified on a per-call basis when evaluating a QNode. (#1075).

      For this, the qnode should be called with an additional shots keyword argument:

      >>> dev = qml.device('default.qubit', wires=1, shots=10) # default is 10
      >>> @qml.qnode(dev)
      ... def circuit(a):
      ...     qml.RX(a, wires=0)
      ...     return qml.sample(qml.PauliZ(wires=0))
      >>> circuit(0.8)
      [ 1  1  1 -1 -1  1  1  1  1  1]
      >>> circuit(0.8, shots=3)
      [ 1  1  1]
      >>> circuit(0.8)
      [ 1  1  1 -1 -1  1  1  1  1  1]
      

    New differentiable quantum transforms

    A new module is available, qml.transforms, which contains differentiable quantum transforms. These are functions that act on QNodes, quantum functions, devices, and tapes, transforming them while remaining fully differentiable.

    • A new adjoint transform has been added. (#1111) (#1135)

      This new method allows users to apply the adjoint of an arbitrary sequence of operations.

      def subroutine(wire):
          qml.RX(0.123, wires=wire)
          qml.RY(0.456, wires=wire)
      
      dev = qml.device('default.qubit', wires=1)
      @qml.qnode(dev)
      def circuit():
          subroutine(0)
          qml.adjoint(subroutine)(0)
          return qml.expval(qml.PauliZ(0))
      

      This creates the following circuit:

      >>> print(qml.draw(circuit)())
      0: --RX(0.123)--RY(0.456)--RY(-0.456)--RX(-0.123)--| <Z>
      

      Directly applying to a gate also works as expected.

      qml.adjoint(qml.RX)(0.123, wires=0) # applies RX(-0.123)
      
    • A new transform qml.ctrl is now available that adds control wires to subroutines. (#1157)

      def my_ansatz(params):
         qml.RX(params[0], wires=0)
         qml.RZ(params[1], wires=1)
      
      # Create a new operation that applies `my_ansatz`
      # controlled by the "2" wire.
      my_ansatz2 = qml.ctrl(my_ansatz, control=2)
      
      @qml.qnode(dev)
      def circuit(params):
          my_ansatz2(params)
          return qml.state()
      

      This is equivalent to:

      @qml.qnode(...)
      def circuit(params):
          qml.CRX(params[0], wires=[2, 0])
          qml.CRZ(params[1], wires=[2, 1])
          return qml.state()
      
    • The qml.transforms.classical_jacobian transform has been added. (#1186)

      This transform returns a function to extract the Jacobian matrix of the classical part of a QNode, allowing the classical dependence between the QNode arguments and the quantum gate arguments to be extracted.

      For example, given the following QNode:

      >>> @qml.qnode(dev)
      ... def circuit(weights):
      ...     qml.RX(weights[0], wires=0)
      ...     qml.RY(weights[0], wires=1)
      ...     qml.RZ(weights[2] ** 2, wires=1)
      ...     return qml.expval(qml.PauliZ(0))
      

      We can use this transform to extract the relationship :math:f: \mathbb{R}^n \rightarrow\mathbb{R}^m between the input QNode arguments :math:w and the gate arguments :math:g, for a given value of the QNode arguments:

      >>> cjac_fn = qml.transforms.classical_jacobian(circuit)
      >>> weights = np.array([1., 1., 1.], requires_grad=True)
      >>> cjac = cjac_fn(weights)
      >>> print(cjac)
      [[1. 0. 0.]
       [1. 0. 0.]
       [0. 0. 2.]]
      

      The returned Jacobian has rows corresponding to gate arguments, and columns corresponding to QNode arguments; that is, :math:J_{ij} = \frac{\partial}{\partial g_i} f(w_j).

    More operations and templates

    • Added the SingleExcitation two-qubit operation, which is useful for quantum chemistry applications. (#1121)

      It can be used to perform an SO(2) rotation in the subspace spanned by the states :math:|01\rangle and :math:|10\rangle. For example, the following circuit performs the transformation :math:|10\rangle \rightarrow \cos(\phi/2)|10\rangle - \sin(\phi/2)|01\rangle:

      dev = qml.device('default.qubit', wires=2)
      
      @qml.qnode(dev)
      def circuit(phi):
          qml.PauliX(wires=0)
          qml.SingleExcitation(phi, wires=[0, 1])
      

      The SingleExcitation operation supports analytic gradients on hardware using only four expectation value calculations, following results from Kottmann et al.

    • Added the DoubleExcitation four-qubit operation, which is useful for quantum chemistry applications. (#1123)

      It can be used to perform an SO(2) rotation in the subspace spanned by the states :math:|1100\rangle and :math:|0011\rangle. For example, the following circuit performs the transformation :math:|1100\rangle\rightarrow \cos(\phi/2)|1100\rangle - \sin(\phi/2)|0011\rangle:

      dev = qml.device('default.qubit', wires=2)
      
      @qml.qnode(dev)
      def circuit(phi):
          qml.PauliX(wires=0)
          qml.PauliX(wires=1)
          qml.DoubleExcitation(phi, wires=[0, 1, 2, 3])
      

      The DoubleExcitation operation supports analytic gradients on hardware using only four expectation value calculations, following results from Kottmann et al..

    • Added the QuantumMonteCarlo template for performing quantum Monte Carlo estimation of an expectation value on simulator. (#1130)

      The following example shows how the expectation value of sine squared over a standard normal distribution can be approximated:

      from scipy.stats import norm
      
      m = 5
      M = 2 ** m
      n = 10
      N = 2 ** n
      target_wires = range(m + 1)
      estimation_wires = range(m + 1, n + m + 1)
      
      xmax = np.pi  # bound to region [-pi, pi]
      xs = np.linspace(-xmax, xmax, M)
      
      probs = np.array([norm().pdf(x) for x in xs])
      probs /= np.sum(probs)
      
      func = lambda i: np.sin(xs[i]) ** 2
      
      dev = qml.device("default.qubit", wires=(n + m + 1))
      
      @qml.qnode(dev)
      def circuit():
          qml.templates.QuantumMonteCarlo(
              probs,
              func,
              target_wires=target_wires,
              estimation_wires=estimation_wires,
          )
          return qml.probs(estimation_wires)
      
      phase_estimated = np.argmax(circuit()[:int(N / 2)]) / N
      expectation_estimated = (1 - np.cos(np.pi * phase_estimated)) / 2
      
    • Added the QuantumPhaseEstimation template for performing quantum phase estimation for an input unitary matrix. (#1095)

      Consider the matrix corresponding to a rotation from an RX gate:

      >>> phase = 5
      >>> target_wires = [0]
      >>> unitary = qml.RX(phase, wires=0).matrix
      

      The phase parameter can be estimated using QuantumPhaseEstimation. For example, using five phase-estimation qubits:

      n_estimation_wires = 5
      estimation_wires = range(1, n_estimation_wires + 1)
      
      dev = qml.device("default.qubit", wires=n_estimation_wires + 1)
      
      @qml.qnode(dev)
      def circuit():
          # Start in the |+> eigenstate of the unitary
          qml.Hadamard(wires=target_wires)
      
          QuantumPhaseEstimation(
              unitary,
              target_wires=target_wires,
              estimation_wires=estimation_wires,
          )
      
          return qml.probs(estimation_wires)
      
      phase_estimated = np.argmax(circuit()) / 2 ** n_estimation_wires
      
      # Need to rescale phase due to convention of RX gate
      phase_estimated = 4 * np.pi * (1 - phase)
      
    • Added the ControlledPhaseShift gate as well as the QFT operation for applying quantum Fourier transforms. (#1064)

      @qml.qnode(dev)
      def circuit_qft(basis_state):
          qml.BasisState(basis_state, wires=range(3))
          qml.QFT(wires=range(3))
          return qml.state()
      
    • Added the ControlledQubitUnitary operation. This enables implementation of multi-qubit gates with a variable number of control qubits. It is also possible to specify a different state for the control qubits using the control_values argument (also known as a mixed-polarity multi-controlled operation). (#1069) (#1104)

      For example, we can create a multi-controlled T gate using:

      T = qml.T._matrix()
      qml.ControlledQubitUnitary(T, control_wires=[0, 1, 3], wires=2, control_values="110")
      

      Here, the T gate will be applied to wire 2 if control wires 0 and 1 are in state 1, and control wire 3 is in state 0. If no value is passed to control_values, the gate will be applied if all control wires are in the 1 state.

    • Added MultiControlledX for multi-controlled NOT gates. This is a special case of ControlledQubitUnitary that applies a Pauli X gate conditioned on the state of an arbitrary number of control qubits. (#1104)

    Support for higher-order derivatives on hardware

    • Computing second derivatives and Hessians of QNodes is now supported with the parameter-shift differentiation method, on all machine learning interfaces. (#1130) (#1129) (#1110)

      Hessians are computed using the parameter-shift rule, and can be evaluated on both hardware and simulator devices.

      dev = qml.device('default.qubit', wires=1)
      
      @qml.qnode(dev, diff_method="parameter-shift")
      def circuit(p):
          qml.RY(p[0], wires=0)
          qml.RX(p[1], wires=0)
          return qml.expval(qml.PauliZ(0))
      
      x = np.array([1.0, 2.0], requires_grad=True)
      
      >>> hessian_fn = qml.jacobian(qml.grad(circuit))
      >>> hessian_fn(x)
      [[0.2248451 0.7651474]
       [0.7651474 0.2248451]]
      
    • Added the function finite_diff() to compute finite-difference approximations to the gradient and the second-order derivatives of arbitrary callable functions. (#1090)

      This is useful to compute the derivative of parametrized pennylane.Hamiltonian observables with respect to their parameters.

      For example, in quantum chemistry simulations it can be used to evaluate the derivatives of the electronic Hamiltonian with respect to the nuclear coordinates:

      >>> def H(x):
      ...    return qml.qchem.molecular_hamiltonian(['H', 'H'], x)[0]
      >>> x = np.array([0., 0., -0.66140414, 0., 0., 0.66140414])
      >>> grad_fn = qml.finite_diff(H, N=1)
      >>> grad = grad_fn(x)
      >>> deriv2_fn = qml.finite_diff(H, N=2, idx=[0, 1])
      >>> deriv2_fn(x)
      
    • The JAX interface now supports all devices, including hardware devices, via the parameter-shift differentiation method. (#1076)

      For example, using the JAX interface with Cirq:

      dev = qml.device('cirq.simulator', wires=1)
      @qml.qnode(dev, interface="jax", diff_method="parameter-shift")
      def circuit(x):
          qml.RX(x[1], wires=0)
          qml.Rot(x[0], x[1], x[2], wires=0)
          return qml.expval(qml.PauliZ(0))
      weights = jnp.array([0.2, 0.5, 0.1])
      print(circuit(weights))
      

      Currently, when used with the parameter-shift differentiation method, only a single returned expectation value or variance is supported. Multiple expectations/variances, as well as probability and state returns, are not currently allowed.

    Improvements

    • The MottonenStatePreparation template has improved performance on states with only real amplitudes by reducing the number of redundant CNOT gates at the end of a circuit.

      dev = qml.device("default.qubit", wires=2)
      
      inputstate = [np.sqrt(0.2), np.sqrt(0.3), np.sqrt(0.4), np.sqrt(0.1)]
      
      @qml.qnode(dev)
      def circuit():
          mottonen.MottonenStatePreparation(inputstate,wires=[0, 1])
          return qml.expval(qml.PauliZ(0))
      

      Previously returned:

      >>> print(qml.draw(circuit)())
      0: ──RY(1.57)──╭C─────────────╭C──╭C──╭C──┤ ⟨Z⟩ 
      1: ──RY(1.35)──╰X──RY(0.422)──╰X──╰X──╰X──┤   
      

      In this release, it now returns:

      >>> print(qml.draw(circuit)())
      0: ──RY(1.57)──╭C─────────────╭C──┤ ⟨Z⟩ 
      1: ──RY(1.35)──╰X──RY(0.422)──╰X──┤   
      
    • The templates are now classes inheriting from Operation, and define the ansatz in their expand() method. This change does not affect the user interface. (#1138) (#1156) (#1163) (#1192)

      For convenience, some templates have a new method that returns the expected shape of the trainable parameter tensor, which can be used to create random tensors.

      shape = qml.templates.BasicEntanglerLayers.shape(n_layers=2, n_wires=4)
      weights = np.random.random(shape)
      qml.templates.BasicEntanglerLayers(weights, wires=range(4))
      
    • QubitUnitary now validates to ensure the input matrix is two dimensional. (#1128)

    • Most layers in Pytorch or Keras accept arbitrary dimension inputs, where each dimension barring the last (in the case where the actual weight function of the layer operates on one-dimensional vectors) is broadcast over. This is now also supported by KerasLayer and TorchLayer. (#1062).

      Example use:

      dev = qml.device("default.qubit", wires=4)
      x = tf.ones((5, 4, 4))
      
      @qml.qnode(dev)
      def layer(weights, inputs):
          qml.templates.AngleEmbedding(inputs, wires=range(4))
          qml.templates.StronglyEntanglingLayers(weights, wires=range(4))
          return [qml.expval(qml.PauliZ(i)) for i in range(4)]
      
      qlayer = qml.qnn.KerasLayer(layer, {"weights": (4, 4, 3)}, output_dim=4)
      out = qlayer(x)
      

      The output tensor has the following shape:

      >>> out.shape
      (5, 4, 4)
      
    • If only one argument to the function qml.grad has the requires_grad attribute set to True, then the returned gradient will be a NumPy array, rather than a tuple of length 1. (#1067) (#1081)

    • An improvement has been made to how QubitDevice generates and post-processess samples, allowing QNode measurement statistics to work on devices with more than 32 qubits. (#1088)

    • Due to the addition of density_matrix() as a return type from a QNode, tuples are now supported by the output_dim parameter in qnn.KerasLayer. (#1070)

    • Two new utility methods are provided for working with quantum tapes. (#1175)

      • qml.tape.get_active_tape() gets the currently recording tape.

      • tape.stop_recording() is a context manager that temporarily stops the currently recording tape from recording additional tapes or quantum operations.

      For example:

      >>> with qml.tape.QuantumTape():
      ...     qml.RX(0, wires=0)
      ...     current_tape = qml.tape.get_active_tape()
      ...     with current_tape.stop_recording():
      ...         qml.RY(1.0, wires=1)
      ...     qml.RZ(2, wires=1)
      >>> current_tape.operations
      [RX(0, wires=[0]), RZ(2, wires=[1])]
      
    • When printing qml.Hamiltonian objects, the terms are sorted by number of wires followed by coefficients. (#981)

    • Adds qml.math.conj to the PennyLane math module. (#1143)

      This new method will do elementwise conjugation to the given tensor-like object, correctly dispatching to the required tensor-manipulation framework to preserve differentiability.

      >>> a = np.array([1.0 + 2.0j])
      >>> qml.math.conj(a)
      array([1.0 - 2.0j])
      
    • The four-term parameter-shift rule, as used by the controlled rotation operations, has been updated to use coefficients that minimize the variance as per https://arxiv.org/abs/2104.05695. (#1206)

    • A new transform qml.transforms.invisible has been added, to make it easier to transform QNodes. (#1175)

    Breaking changes

    • Devices do not have an analytic argument or attribute anymore. Instead, shots is the source of truth for whether a simulator estimates return values from a finite number of shots, or whether it returns analytic results (shots=None). (#1079) (#1196)

      dev_analytic = qml.device('default.qubit', wires=1, shots=None)
      dev_finite_shots = qml.device('default.qubit', wires=1, shots=1000)
      
      def circuit():
          qml.Hadamard(wires=0)
          return qml.expval(qml.PauliZ(wires=0))
      
      circuit_analytic = qml.QNode(circuit, dev_analytic)
      circuit_finite_shots = qml.QNode(circuit, dev_finite_shots)
      

      Devices with shots=None return deterministic, exact results:

      >>> circuit_analytic()
      0.0
      >>> circuit_analytic()
      0.0
      

      Devices with shots > 0 return stochastic results estimated from samples in each run:

      >>> circuit_finite_shots()
      -0.062
      >>> circuit_finite_shots()
      0.034
      

      The qml.sample() measurement can only be used on devices on which the number of shots is set explicitly.

    • If creating a QNode from a quantum function with an argument named shots, a DeprecationWarning is raised, warning the user that this is a reserved argument to change the number of shots on a per-call basis. (#1075)

    • For devices inheriting from QubitDevice, the methods expval, var, sample accept two new keyword arguments --- shot_range and bin_size. (#1103)

      These new arguments allow for the statistics to be performed on only a subset of device samples. This finer level of control is accessible from the main UI by instantiating a device with a batch of shots.

      For example, consider the following device:

      >>> dev = qml.device("my_device", shots=[5, (10, 3), 100])
      

      This device will execute QNodes using 135 shots, however measurement statistics will be course grained across these 135 shots:

      • All measurement statistics will first be computed using the first 5 shots --- that is, shots_range=[0, 5], bin_size=5.

      • Next, the tuple (10, 3) indicates 10 shots, repeated 3 times. This will use shot_range=[5, 35], performing the expectation value in bins of size 10 (bin_size=10).

      • Finally, we repeat the measurement statistics for the final 100 shots, shot_range=[35, 135], bin_size=100.

    • The old PennyLane core has been removed, including the following modules: (#1100)

      • pennylane.variables
      • pennylane.qnodes

      As part of this change, the location of the new core within the Python module has been moved:

      • Moves pennylane.tape.interfacespennylane.interfaces
      • Merges pennylane.CircuitGraph and pennylane.TapeCircuitGraphpennylane.CircuitGraph
      • Merges pennylane.OperationRecorder and pennylane.TapeOperationRecorder
      • pennylane.tape.operation_recorder
      • Merges pennylane.measure and pennylane.tape.measurepennylane.measure
      • Merges pennylane.operation and pennylane.tape.operationpennylane.operation
      • Merges pennylane._queuing and pennylane.tape.queuingpennylane.queuing

      This has no affect on import location.

      In addition,

      • All tape-mode functions have been removed (qml.enable_tape(), qml.tape_mode_active()),
      • All tape fixtures have been deleted,
      • Tests specifically for non-tape mode have been deleted.
    • The device test suite no longer accepts the analytic keyword. (#1216)

    Bug fixes

    • Fixes a bug where using the circuit drawer with a ControlledQubitUnitary operation raised an error. (#1174)

    • Fixes a bug and a test where the QuantumTape.is_sampled attribute was not being updated. (#1126)

    • Fixes a bug where BasisEmbedding would not accept inputs whose bits are all ones or all zeros. (#1114)

    • The ExpvalCost class raises an error if instantiated with non-expectation measurement statistics. (#1106)

    • Fixes a bug where decompositions would reset the differentiation method of a QNode. (#1117)

    • Fixes a bug where the second-order CV parameter-shift rule would error if attempting to compute the gradient of a QNode with more than one second-order observable. (#1197)

    • Fixes a bug where repeated Torch interface applications after expansion caused an error. (#1223)

    • Sampling works correctly with batches of shots specified as a list. (#1232)

    Documentation

    • Updated the diagram used in the Architectural overview page of the Development guide such that it doesn't mention Variables. (#1235)

    • Typos addressed in templates documentation. (#1094)

    • Upgraded the documentation to use Sphinx 3.5.3 and the new m2r2 package. (#1186)

    • Added flaky as dependency for running tests in the documentation. (#1113)

    Contributors

    This release contains contributions from (in alphabetical order):

    Shahnawaz Ahmed, Juan Miguel Arrazola, Thomas Bromley, Olivia Di Matteo, Alain Delgado Gran, Kyle Godbey, Diego Guala, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Christina Lee, Daniel Polatajko, Chase Roberts, Sankalp Sanand, Pritish Sehzpaul, Maria Schuld, Antal Száva, David Wierichs.

    Source code(tar.gz)
    Source code(zip)
  • v0.14.1(Feb 12, 2021)

    Bug fixes

    • Fixes a bug where inverse operations could not be differentiated using backpropagation on default.qubit. (#1072)

    • The QNode has a new keyword argument, max_expansion, that determines the maximum number of times the internal circuit should be expanded when executed on a device. In addition, the default number of max expansions has been increased from 2 to 10, allowing devices that require more than two operator decompositions to be supported. (#1074)

    • Fixes a bug where Hamiltonian objects created with non-list arguments raised an error for arithmetic operations. (#1082)

    • Fixes a bug where Hamiltonian objects with no coefficients or operations would return a faulty result when used with ExpvalCost. (#1082)

    • Fixes a testing bug where tests that required JAX would fail if JAX was not installed. The tests will now instead be skipped if JAX cannot be imported. (#1066)

    Documentation

    • Updates mentions of generate_hamiltonian to molecular_hamiltonian in the docstrings of the ExpvalCost and Hamiltonian classes. (#1077)

    Contributors

    This release contains contributions from (in alphabetical order):

    Thomas Bromley, Josh Izaac, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.14.0(Feb 2, 2021)

    New features since last release

    Perform quantum machine learning with JAX

    • QNodes created with default.qubit now support a JAX interface, allowing JAX to be used to create, differentiate, and optimize hybrid quantum-classical models. (#947)

      This is supported internally via a new default.qubit.jax device. This device runs end to end in JAX, meaning that it supports all of the awesome JAX transformations (jax.vmap, jax.jit, jax.hessian, etc).

      Here is an example of how to use the new JAX interface:

      dev = qml.device("default.qubit", wires=1)
      @qml.qnode(dev, interface="jax", diff_method="backprop")
      def circuit(x):
          qml.RX(x[1], wires=0)
          qml.Rot(x[0], x[1], x[2], wires=0)
          return qml.expval(qml.PauliZ(0))
      
      weights = jnp.array([0.2, 0.5, 0.1])
      grad_fn = jax.grad(circuit)
      print(grad_fn(weights))
      

      Currently, only diff_method="backprop" is supported, with plans to support more in the future.

    New, faster, quantum gradient methods

    • A new differentiation method has been added for use with simulators. The "adjoint" method operates after a forward pass by iteratively applying inverse gates to scan backwards through the circuit. (#1032)

      This method is similar to the reversible method, but has a lower time overhead and a similar memory overhead. It follows the approach provided by Jones and Gacon. This method is only compatible with certain statevector-based devices such as default.qubit.

      Example use:

      import pennylane as qml
      
      wires = 1
      device = qml.device("default.qubit", wires=wires)
      
      @qml.qnode(device, diff_method="adjoint")
      def f(params):
          qml.RX(0.1, wires=0)
          qml.Rot(*params, wires=0)
          qml.RX(-0.3, wires=0)
          return qml.expval(qml.PauliZ(0))
      
      params = [0.1, 0.2, 0.3]
      qml.grad(f)(params)
      
    • The default logic for choosing the 'best' differentiation method has been altered to improve performance. (#1008)

      • If the quantum device provides its own gradient, this is now the preferred differentiation method.

      • If the quantum device natively supports classical backpropagation, this is now preferred over the parameter-shift rule.

        This will lead to marked speed improvement during optimization when using default.qubit, with a sight penalty on the forward-pass evaluation.

      More details are available below in the 'Improvements' section for plugin developers.

    • PennyLane now supports analytical quantum gradients for noisy channels, in addition to its existing support for unitary operations. The noisy channels BitFlip, PhaseFlip, and DepolarizingChannel all support analytic gradients out of the box. (#968)

    • A method has been added for calculating the Hessian of quantum circuits using the second-order parameter shift formula. (#961)

      The following example shows the calculation of the Hessian:

      n_wires = 5
      weights = [2.73943676, 0.16289932, 3.4536312, 2.73521126, 2.6412488]
      
      dev = qml.device("default.qubit", wires=n_wires)
      
      with qml.tape.QubitParamShiftTape() as tape:
          for i in range(n_wires):
              qml.RX(weights[i], wires=i)
      
          qml.CNOT(wires=[0, 1])
          qml.CNOT(wires=[2, 1])
          qml.CNOT(wires=[3, 1])
          qml.CNOT(wires=[4, 3])
      
          qml.expval(qml.PauliZ(1))
      
      print(tape.hessian(dev))
      

      The Hessian is not yet supported via classical machine learning interfaces, but will be added in a future release.

    More operations and templates

    • Two new error channels, BitFlip and PhaseFlip have been added. (#954)

      They can be used in the same manner as existing error channels:

      dev = qml.device("default.mixed", wires=2)
      
      @qml.qnode(dev)
      def circuit():
          qml.RX(0.3, wires=0)
          qml.RY(0.5, wires=1)
          qml.BitFlip(0.01, wires=0)
          qml.PhaseFlip(0.01, wires=1)
          return qml.expval(qml.PauliZ(0))
      
    • Apply permutations to wires using the Permute subroutine. (#952)

      import pennylane as qml
      dev = qml.device('default.qubit', wires=5)
      
      @qml.qnode(dev)
      def apply_perm():
          # Send contents of wire 4 to wire 0, of wire 2 to wire 1, etc.
          qml.templates.Permute([4, 2, 0, 1, 3], wires=dev.wires)
          return qml.expval(qml.PauliZ(0))
      

    QNode transformations

    • The qml.metric_tensor function transforms a QNode to produce the Fubini-Study metric tensor with full autodifferentiation support---even on hardware. (#1014)

      Consider the following QNode:

      dev = qml.device("default.qubit", wires=3)
      
      @qml.qnode(dev, interface="autograd")
      def circuit(weights):
          # layer 1
          qml.RX(weights[0, 0], wires=0)
          qml.RX(weights[0, 1], wires=1)
      
          qml.CNOT(wires=[0, 1])
          qml.CNOT(wires=[1, 2])
      
          # layer 2
          qml.RZ(weights[1, 0], wires=0)
          qml.RZ(weights[1, 1], wires=2)
      
          qml.CNOT(wires=[0, 1])
          qml.CNOT(wires=[1, 2])
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1)), qml.expval(qml.PauliY(2))
      

      We can use the metric_tensor function to generate a new function, that returns the metric tensor of this QNode:

      >>> met_fn = qml.metric_tensor(circuit)
      >>> weights = np.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]], requires_grad=True)
      >>> met_fn(weights)
      tensor([[0.25  , 0.    , 0.    , 0.    ],
              [0.    , 0.25  , 0.    , 0.    ],
              [0.    , 0.    , 0.0025, 0.0024],
              [0.    , 0.    , 0.0024, 0.0123]], requires_grad=True)
      

      The returned metric tensor is also fully differentiable, in all interfaces. For example, differentiating the (3, 2) element:

      >>> grad_fn = qml.grad(lambda x: met_fn(x)[3, 2])
      >>> grad_fn(weights)
      array([[ 0.04867729, -0.00049502,  0.        ],
             [ 0.        ,  0.        ,  0.        ]])
      

      Differentiation is also supported using Torch, Jax, and TensorFlow.

    • Adds the new function qml.math.cov_matrix(). This function accepts a list of commuting observables, and the probability distribution in the shared observable eigenbasis after the application of an ansatz. It uses these to construct the covariance matrix in a framework independent manner, such that the output covariance matrix is autodifferentiable. (#1012)

      For example, consider the following ansatz and observable list:

      obs_list = [qml.PauliX(0) @ qml.PauliZ(1), qml.PauliY(2)]
      ansatz = qml.templates.StronglyEntanglingLayers
      

      We can construct a QNode to output the probability distribution in the shared eigenbasis of the observables:

      dev = qml.device("default.qubit", wires=3)
      
      @qml.qnode(dev, interface="autograd")
      def circuit(weights):
          ansatz(weights, wires=[0, 1, 2])
          # rotate into the basis of the observables
          for o in obs_list:
              o.diagonalizing_gates()
          return qml.probs(wires=[0, 1, 2])
      

      We can now compute the covariance matrix:

      >>> weights = qml.init.strong_ent_layers_normal(n_layers=2, n_wires=3)
      >>> cov = qml.math.cov_matrix(circuit(weights), obs_list)
      >>> cov
      array([[0.98707611, 0.03665537],
             [0.03665537, 0.99998377]])
      

      Autodifferentiation is fully supported using all interfaces:

      >>> cost_fn = lambda weights: qml.math.cov_matrix(circuit(weights), obs_list)[0, 1]
      >>> qml.grad(cost_fn)(weights)[0]
      array([[[ 4.94240914e-17, -2.33786398e-01, -1.54193959e-01],
              [-3.05414996e-17,  8.40072236e-04,  5.57884080e-04],
              [ 3.01859411e-17,  8.60411436e-03,  6.15745204e-04]],
      
             [[ 6.80309533e-04, -1.23162742e-03,  1.08729813e-03],
              [-1.53863193e-01, -1.38700657e-02, -1.36243323e-01],
              [-1.54665054e-01, -1.89018172e-02, -1.56415558e-01]]])
      
    • A new qml.draw function is available, allowing QNodes to be easily drawn without execution by providing example input. (#962)

      @qml.qnode(dev)
      def circuit(a, w):
          qml.Hadamard(0)
          qml.CRX(a, wires=[0, 1])
          qml.Rot(*w, wires=[1])
          qml.CRX(-a, wires=[0, 1])
          return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      

      The QNode circuit structure may depend on the input arguments; this is taken into account by passing example QNode arguments to the qml.draw() drawing function:

      >>> drawer = qml.draw(circuit)
      >>> result = drawer(a=2.3, w=[1.2, 3.2, 0.7])
      >>> print(result)
      0: ──H──╭C────────────────────────────╭C─────────╭┤ ⟨Z ⊗ Z⟩
      1: ─────╰RX(2.3)──Rot(1.2, 3.2, 0.7)──╰RX(-2.3)──╰┤ ⟨Z ⊗ Z⟩
      

    A faster, leaner, and more flexible core

    • The new core of PennyLane, rewritten from the ground up and developed over the last few release cycles, has achieved feature parity and has been made the new default in PennyLane v0.14. The old core has been marked as deprecated, and will be removed in an upcoming release. (#1046) (#1040) (#1034) (#1035) (#1027) (#1026) (#1021) (#1054) (#1049)

      While high-level PennyLane code and tutorials remain unchanged, the new core provides several advantages and improvements:

      • Faster and more optimized: The new core provides various performance optimizations, reducing pre- and post-processing overhead, and reduces the number of quantum evaluations in certain cases.

      • Support for in-QNode classical processing: this allows for differentiable classical processing within the QNode.

        dev = qml.device("default.qubit", wires=1)
        
        @qml.qnode(dev, interface="tf")
        def circuit(p):
            qml.RX(tf.sin(p[0])**2 + p[1], wires=0)
            return qml.expval(qml.PauliZ(0))
        

        The classical processing functions used within the QNode must match the QNode interface. Here, we use TensorFlow:

        >>> params = tf.Variable([0.5, 0.1], dtype=tf.float64)
        >>> with tf.GradientTape() as tape:
        ...     res = circuit(params)
        >>> grad = tape.gradient(res, params)
        >>> print(res)
        tf.Tensor(0.9460913127754935, shape=(), dtype=float64)
        >>> print(grad)
        tf.Tensor([-0.27255248 -0.32390003], shape=(2,), dtype=float64)
        

        As a result of this change, quantum decompositions that require classical processing are fully supported and end-to-end differentiable in tape mode.

      • No more Variable wrapping: QNode arguments no longer become Variable objects within the QNode.

        dev = qml.device("default.qubit", wires=1)
        
        @qml.qnode(dev)
        def circuit(x):
            print("Parameter value:", x)
            qml.RX(x, wires=0)
            return qml.expval(qml.PauliZ(0))
        

        Internal QNode parameters can be easily inspected, printed, and manipulated:

        >>> circuit(0.5)
        Parameter value: 0.5
        tensor(0.87758256, requires_grad=True)
        
      • Less restrictive QNode signatures: There is no longer any restriction on the QNode signature; the QNode can be defined and called following the same rules as standard Python functions.

        For example, the following QNode uses positional, named, and variable keyword arguments:

        x = torch.tensor(0.1, requires_grad=True)
        y = torch.tensor([0.2, 0.3], requires_grad=True)
        z = torch.tensor(0.4, requires_grad=True)
        
        @qml.qnode(dev, interface="torch")
        def circuit(p1, p2=y, **kwargs):
            qml.RX(p1, wires=0)
            qml.RY(p2[0] * p2[1], wires=0)
            qml.RX(kwargs["p3"], wires=0)
            return qml.var(qml.PauliZ(0))
        

        When we call the QNode, we may pass the arguments by name even if defined positionally; any argument not provided will use the default value.

        >>> res = circuit(p1=x, p3=z)
        >>> print(res)
        tensor(0.2327, dtype=torch.float64, grad_fn=<SelectBackward>)
        >>> res.backward()
        >>> print(x.grad, y.grad, z.grad)
        tensor(0.8396) tensor([0.0289, 0.0193]) tensor(0.8387)
        

        This extends to the qnn module, where KerasLayer and TorchLayer modules can be created from QNodes with unrestricted signatures.

      • Smarter measurements: QNodes can now measure wires more than once, as long as all observables are commuting:

        @qml.qnode(dev)
        def circuit(x):
            qml.RX(x, wires=0)
            return [
                qml.expval(qml.PauliZ(0)),
                qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
            ]
        

        Further, the qml.ExpvalCost() function allows for optimizing measurements to reduce the number of quantum evaluations required.

      With the new PennyLane core, there are a few small breaking changes, detailed below in the 'Breaking Changes' section.

    Improvements

    • The built-in PennyLane optimizers allow more flexible cost functions. The cost function passed to most optimizers may accept any combination of trainable arguments, non-trainable arguments, and keyword arguments. (#959) (#1053)

      The full changes apply to:

      • AdagradOptimizer
      • AdamOptimizer
      • GradientDescentOptimizer
      • MomentumOptimizer
      • NesterovMomentumOptimizer
      • RMSPropOptimizer
      • RotosolveOptimizer

      The requires_grad=False property must mark any non-trainable constant argument. The RotoselectOptimizer allows passing only keyword arguments.

      Example use:

      def cost(x, y, data, scale=1.0):
          return scale * (x[0]-data)**2 + scale * (y-data)**2
      
      x = np.array([1.], requires_grad=True)
      y = np.array([1.0])
      data = np.array([2.], requires_grad=False)
      
      opt = qml.GradientDescentOptimizer()
      
      # the optimizer step and step_and_cost methods can
      # now update multiple parameters at once
      x_new, y_new, data = opt.step(cost, x, y, data, scale=0.5)
      (x_new, y_new, data), value = opt.step_and_cost(cost, x, y, data, scale=0.5)
      
      # list and tuple unpacking is also supported
      params = (x, y, data)
      params = opt.step(cost, *params)
      
    • The circuit drawer has been updated to support the inclusion of unused or inactive wires, by passing the show_all_wires argument. (#1033)

      dev = qml.device('default.qubit', wires=[-1, "a", "q2", 0])
      
      @qml.qnode(dev)
      def circuit():
          qml.Hadamard(wires=-1)
          qml.CNOT(wires=[-1, "q2"])
          return qml.expval(qml.PauliX(wires="q2"))
      
      >>> print(qml.draw(circuit, show_all_wires=True)())
      >>>
       -1: ──H──╭C──┤
        a: ─────│───┤
       q2: ─────╰X──┤ ⟨X⟩
        0: ─────────┤
      
    • The logic for choosing the 'best' differentiation method has been altered to improve performance. (#1008)

      • If the device provides its own gradient, this is now the preferred differentiation method.

      • If a device provides additional interface-specific versions that natively support classical backpropagation, this is now preferred over the parameter-shift rule.

        Devices define additional interface-specific devices via their capabilities() dictionary. For example, default.qubit supports supplementary devices for TensorFlow, Autograd, and JAX:

        {
          "passthru_devices": {
              "tf": "default.qubit.tf",
              "autograd": "default.qubit.autograd",
              "jax": "default.qubit.jax",
          },
        }
        

      As a result of this change, if the QNode diff_method is not explicitly provided, it is possible that the QNode will run on a supplementary device of the device that was specifically provided:

      dev = qml.device("default.qubit", wires=2)
      qml.QNode(dev) # will default to backprop on default.qubit.autograd
      qml.QNode(dev, interface="tf") # will default to backprop on default.qubit.tf
      qml.QNode(dev, interface="jax") # will default to backprop on default.qubit.jax
      
    • The default.qubit device has been updated so that internally it applies operations in a more functional style, i.e., by accepting an input state and returning an evolved state. (#1025)

    • A new test series, pennylane/devices/tests/test_compare_default_qubit.py, has been added, allowing to test if a chosen device gives the same result as default.qubit. (#897)

      Three tests are added:

      • test_hermitian_expectation,
      • test_pauliz_expectation_analytic, and
      • test_random_circuit.
    • Adds the following agnostic tensor manipulation functions to the qml.math module: abs, angle, arcsin, concatenate, dot, squeeze, sqrt, sum, take, where. These functions are required to fully support end-to-end differentiable Mottonen and Amplitude embedding. (#922) (#1011)

    • The qml.math module now supports JAX. (#985)

    • Several improvements have been made to the Wires class to reduce overhead and simplify the logic of how wire labels are interpreted: (#1019) (#1010) (#1005) (#983) (#967)

      • If the input wires to a wires class instantiation Wires(wires) can be iterated over, its elements are interpreted as wire labels. Otherwise, wires is interpreted as a single wire label. The only exception to this are strings, which are always interpreted as a single wire label, so users can address wires with labels such as "ancilla".

      • Any type can now be a wire label as long as it is hashable. The hash is used to establish the uniqueness of two labels.

      • Indexing wires objects now returns a label, instead of a new Wires object. For example:

        >>> w = Wires([0, 1, 2])
        >>> w[1]
        >>> 1
        
      • The check for uniqueness of wires moved from Wires instantiation to the qml.wires._process function in order to reduce overhead from repeated creation of Wires instances.

      • Calls to the Wires class are substantially reduced, for example by avoiding to call Wires on Wires instances on Operation instantiation, and by using labels instead of Wires objects inside the default qubit device.

    • Adds the PauliRot generator to the qml.operation module. This generator is required to construct the metric tensor. (#963)

    • The templates are modified to make use of the new qml.math module, for framework-agnostic tensor manipulation. This allows the template library to be differentiable in backpropagation mode (diff_method="backprop"). (#873)

    • The circuit drawer now allows for the wire order to be (optionally) modified: (#992)

      >>> dev = qml.device('default.qubit', wires=["a", -1, "q2"])
      >>> @qml.qnode(dev)
      ... def circuit():
      ...     qml.Hadamard(wires=-1)
      ...     qml.CNOT(wires=["a", "q2"])
      ...     qml.RX(0.2, wires="a")
      ...     return qml.expval(qml.PauliX(wires="q2"))
      

      Printing with default wire order of the device:

      >>> print(circuit.draw())
        a: ─────╭C──RX(0.2)──┤
       -1: ──H──│────────────┤
       q2: ─────╰X───────────┤ ⟨X⟩
      

      Changing the wire order:

      >>> print(circuit.draw(wire_order=["q2", "a", -1]))
       q2: ──╭X───────────┤ ⟨X⟩
        a: ──╰C──RX(0.2)──┤
       -1: ───H───────────┤
      

    Breaking changes

    • QNodes using the new PennyLane core will no longer accept ragged arrays as inputs.

    • When using the new PennyLane core and the Autograd interface, non-differentiable data passed as a QNode argument or a gate must have the requires_grad property set to False:

      @qml.qnode(dev)
      def circuit(weights, data):
          basis_state = np.array([1, 0, 1, 1], requires_grad=False)
          qml.BasisState(basis_state, wires=[0, 1, 2, 3])
          qml.templates.AmplitudeEmbedding(data, wires=[0, 1, 2, 3])
          qml.templates.BasicEntanglerLayers(weights, wires=[0, 1, 2, 3])
          return qml.probs(wires=0)
      
      data = np.array(data, requires_grad=False)
      weights = np.array(weights, requires_grad=True)
      circuit(weights, data)
      

    Bug fixes

    • Fixes an issue where if the constituent observables of a tensor product do not exist in the queue, an error is raised. With this fix, they are first queued before annotation occurs. (#1038)

    • Fixes an issue with tape expansions where information about sampling (specifically the is_sampled tape attribute) was not preserved. (#1027)

    • Tape expansion was not properly taking into devices that supported inverse operations, causing inverse operations to be unnecessarily decomposed. The QNode tape expansion logic, as well as the Operation.expand() method, has been modified to fix this. (#956)

    • Fixes an issue where the Autograd interface was not unwrapping non-differentiable PennyLane tensors, which can cause issues on some devices. (#941)

    • qml.vqe.Hamiltonian prints any observable with any number of strings. (#987)

    • Fixes a bug where parameter-shift differentiation would fail if the QNode contained a single probability output. (#1007)

    • Fixes an issue when using trainable parameters that are lists/arrays with tape.vjp. (#1042)

    • The TensorN observable is updated to support being copied without any parameters or wires passed. (#1047)

    • Fixed deprecation warning when importing Sequence from collections instead of collections.abc in vqe/vqe.py. (#1051)

    Contributors

    This release contains contributions from (in alphabetical order):

    Juan Miguel Arrazola, Thomas Bromley, Olivia Di Matteo, Theodor Isacsson, Josh Izaac, Christina Lee, Alejandro Montanez, Steven Oud, Chase Roberts, Sankalp Sanand, Maria Schuld, Antal Száva, David Wierichs, Jiahao Yao.

    Source code(tar.gz)
    Source code(zip)
  • v0.13.0-post2(Dec 22, 2020)

  • v0.13.0-post1(Dec 8, 2020)

  • v0.13.0(Nov 27, 2020)

    New features since last release

    Automatically optimize the number of measurements

    • QNodes in tape mode now support returning observables on the same wire whenever the observables are qubit-wise commuting Pauli words. Qubit-wise commuting observables can be evaluated with a single device run as they are diagonal in the same basis, via a shared set of single-qubit rotations. (#882)

      The following example shows a single QNode returning the expectation values of the qubit-wise commuting Pauli words XX and XI:

      qml.enable_tape()
      
      @qml.qnode(dev)
      def f(x):
          qml.Hadamard(wires=0)
          qml.Hadamard(wires=1)
          qml.CRot(0.1, 0.2, 0.3, wires=[1, 0])
          qml.RZ(x, wires=1)
          return qml.expval(qml.PauliX(0) @ qml.PauliX(1)), qml.expval(qml.PauliX(0))
      
      >>> f(0.4)
      tensor([0.89431013, 0.9510565 ], requires_grad=True)
      
    • The ExpvalCost class (previously VQECost) now provides observable optimization using the optimize argument, resulting in potentially fewer device executions. (#902)

      This is achieved by separating the observables composing the Hamiltonian into qubit-wise commuting groups and evaluating those groups on a single QNode using functionality from the qml.grouping module:

      qml.enable_tape()
      commuting_obs = [qml.PauliX(0), qml.PauliX(0) @ qml.PauliZ(1)]
      H = qml.vqe.Hamiltonian([1, 1], commuting_obs)
      
      dev = qml.device("default.qubit", wires=2)
      ansatz = qml.templates.StronglyEntanglingLayers
      
      cost_opt = qml.ExpvalCost(ansatz, H, dev, optimize=True)
      cost_no_opt = qml.ExpvalCost(ansatz, H, dev, optimize=False)
      
      params = qml.init.strong_ent_layers_uniform(3, 2)
      

      Grouping these commuting observables leads to fewer device executions:

      >>> cost_opt(params)
      >>> ex_opt = dev.num_executions
      >>> cost_no_opt(params)
      >>> ex_no_opt = dev.num_executions - ex_opt
      >>> print("Number of executions:", ex_no_opt)
      Number of executions: 2
      >>> print("Number of executions (optimized):", ex_opt)
      Number of executions (optimized): 1
      

    New quantum gradient features

    • Compute the analytic gradient of quantum circuits in parallel on supported devices. (#840)

      This release introduces support for batch execution of circuits, via a new device API method Device.batch_execute(). Devices that implement this new API support submitting a batch of circuits for parallel evaluation simultaneously, which can significantly reduce the computation time.

      Furthermore, if using tape mode and a compatible device, gradient computations will automatically make use of the new batch API---providing a speedup during optimization.

    • Gradient recipes are now much more powerful, allowing for operations to define their gradient via an arbitrary linear combination of circuit evaluations. (#909) (#915)

      With this change, gradient recipes can now be of the form \frac{\partial}{\partial\phi_k}f(\phi_k) = \sum_{i} c_i f(a_i \phi_k + s_i ), and are no longer restricted to two-term shifts with identical (but opposite in sign) shift values.

      As a result, PennyLane now supports native analytic quantum gradients for the controlled rotation operations CRX, CRY, CRZ, and CRot. This allows for parameter-shift analytic gradients on hardware, without decomposition.

      Note that this is a breaking change for developers; please see the Breaking Changes section for more details.

    • The qnn.KerasLayer class now supports differentiating the QNode through classical backpropagation in tape mode. (#869)

      qml.enable_tape()
      
      dev = qml.device("default.qubit.tf", wires=2)
      
      @qml.qnode(dev, interface="tf", diff_method="backprop")
      def f(inputs, weights):
          qml.templates.AngleEmbedding(inputs, wires=range(2))
          qml.templates.StronglyEntanglingLayers(weights, wires=range(2))
          return [qml.expval(qml.PauliZ(i)) for i in range(2)]
      
      weight_shapes = {"weights": (3, 2, 3)}
      
      qlayer = qml.qnn.KerasLayer(f, weight_shapes, output_dim=2)
      
      inputs = tf.constant(np.random.random((4, 2)), dtype=tf.float32)
      
      with tf.GradientTape() as tape:
          out = qlayer(inputs)
      
      tape.jacobian(out, qlayer.trainable_weights)
      

    New operations, templates, and measurements

    • Adds the qml.density_matrix QNode return with partial trace capabilities. (#878)

      The density matrix over the provided wires is returned, with all other subsystems traced out. qml.density_matrix currently works for both the default.qubit and default.mixed devices.

      qml.enable_tape()
      dev = qml.device("default.qubit", wires=2)
      
      def circuit(x):
          qml.PauliY(wires=0)
          qml.Hadamard(wires=1)
          return qml.density_matrix(wires=[1])  # wire 0 is traced out
      
    • Adds the square-root X gate SX. (#871)

      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev)
      def circuit():
          qml.SX(wires=[0])
          return qml.expval(qml.PauliZ(wires=[0]))
      
    • Two new hardware-efficient particle-conserving templates have been implemented to perform VQE-based quantum chemistry simulations. The new templates apply several layers of the particle-conserving entanglers proposed in Figs. 2a and 2b of Barkoutsos et al., arXiv:1805.04340 (#875) (#876)

    Estimate and track resources

    • The QuantumTape class now contains basic resource estimation functionality. The method tape.get_resources() returns a dictionary with a list of the constituent operations and the number of times they appear in the circuit. Similarly, tape.get_depth() computes the circuit depth. (#862)

      >>> with qml.tape.QuantumTape() as tape:
      ...    qml.Hadamard(wires=0)
      ...    qml.RZ(0.26, wires=1)
      ...    qml.CNOT(wires=[1, 0])
      ...    qml.Rot(1.8, -2.7, 0.2, wires=0)
      ...    qml.Hadamard(wires=1)
      ...    qml.CNOT(wires=[0, 1])
      ...    qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
      >>> tape.get_resources()
      {'Hadamard': 2, 'RZ': 1, 'CNOT': 2, 'Rot': 1}
      >>> tape.get_depth()
      4
      
    • The number of device executions over a QNode's lifetime can now be returned using num_executions. (#853)

      >>> dev = qml.device("default.qubit", wires=2)
      >>> @qml.qnode(dev)
      ... def circuit(x, y):
      ...    qml.RX(x, wires=[0])
      ...    qml.RY(y, wires=[1])
      ...    qml.CNOT(wires=[0, 1])
      ...    return qml.expval(qml.PauliZ(0) @ qml.PauliX(1))
      >>> for _ in range(10):
      ...    circuit(0.432, 0.12)
      >>> print(dev.num_executions)
      10
      

    Improvements

    • Support for tape mode has improved across PennyLane. The following features now work in tape mode:

    • A new function, qml.refresh_devices(), has been added, allowing PennyLane to rescan installed PennyLane plugins and refresh the device list. In addition, the qml.device loader will attempt to refresh devices if the required plugin device cannot be found. This will result in an improved experience if installing PennyLane and plugins within a running Python session (for example, on Google Colab), and avoid the need to restart the kernel/runtime. (#907)

    • When using grad_fn = qml.grad(cost) to compute the gradient of a cost function with the Autograd interface, the value of the intermediate forward pass is now available via the grad_fn.forward property (#914):

      def cost_fn(x, y):
          return 2 * np.sin(x[0]) * np.exp(-x[1]) + x[0] ** 3 + np.cos(y)
      
      params = np.array([0.1, 0.5], requires_grad=True)
      data = np.array(0.65, requires_grad=False)
      grad_fn = qml.grad(cost_fn)
      
      grad_fn(params, data)  # perform backprop and evaluate the gradient
      grad_fn.forward  # the cost function value
      
    • Gradient-based optimizers now have a step_and_cost method that returns both the next step as well as the objective (cost) function output. (#916)

      >>> opt = qml.GradientDescentOptimizer()
      >>> params, cost = opt.step_and_cost(cost_fn, params)
      
    • PennyLane provides a new experimental module qml.proc which provides framework-agnostic processing functions for array and tensor manipulations. (#886)

      Given the input tensor-like object, the call is dispatched to the corresponding array manipulation framework, allowing for end-to-end differentiation to be preserved.

      >>> x = torch.tensor([1., 2.])
      >>> qml.proc.ones_like(x)
      tensor([1, 1])
      >>> y = tf.Variable([[0], [5]])
      >>> qml.proc.ones_like(y, dtype=np.complex128)
      <tf.Tensor: shape=(2, 1), dtype=complex128, numpy=
      array([[1.+0.j],
             [1.+0.j]])>
      

      Note that these functions are experimental, and only a subset of common functionality is supported. Furthermore, the names and behaviour of these functions may differ from similar functions in common frameworks; please refer to the function docstrings for more details.

    • The gradient methods in tape mode now fully separate the quantum and classical processing. Rather than returning the evaluated gradients directly, they now return a tuple containing the required quantum and classical processing steps. (#840)

      def gradient_method(idx, param, **options):
          # generate the quantum tapes that must be computed
          # to determine the quantum gradient
          tapes = quantum_gradient_tapes(self)
      
          def processing_fn(results):
              # perform classical processing on the evaluated tapes
              # returning the evaluated quantum gradient
              return classical_processing(results)
      
          return tapes, processing_fn
      

      The JacobianTape.jacobian() method has been similarly modified to accumulate all gradient quantum tapes and classical processing functions, evaluate all quantum tapes simultaneously, and then apply the post-processing functions to the evaluated tape results.

    • The MultiRZ gate now has a defined generator, allowing it to be used in quantum natural gradient optimization. (#912)

    • The CRot gate now has a decomposition method, which breaks the gate down into rotations and CNOT gates. This allows CRot to be used on devices that do not natively support it. (#908)

    • The classical processing in the MottonenStatePreparation template has been largely rewritten to use dense matrices and tensor manipulations wherever possible. This is in preparation to support differentiation through the template in the future. (#864)

    • Device-based caching has replaced QNode caching. Caching is now accessed by passing a cache argument to the device. (#851)

      The cache argument should be an integer specifying the size of the cache. For example, a cache of size 10 is created using:

      >>> dev = qml.device("default.qubit", wires=2, cache=10)
      
    • The Operation, Tensor, and MeasurementProcess classes now have the __copy__ special method defined. (#840)

      This allows us to ensure that, when a shallow copy is performed of an operation, the mutable list storing the operation parameters is also shallow copied. Both the old operation and the copied operation will continue to share the same parameter data,

      >>> import copy
      >>> op = qml.RX(0.2, wires=0)
      >>> op2 = copy.copy(op)
      >>> op.data[0] is op2.data[0]
      True
      

      however the list container is not a reference:

      >>> op.data is op2.data
      False
      

      This allows the parameters of the copied operation to be modified, without mutating the parameters of the original operation.

    • The QuantumTape.copy method has been tweaked so that (#840):

      • Optionally, the tape's operations are shallow copied in addition to the tape by passing the copy_operations=True boolean flag. This allows the copied tape's parameters to be mutated without affecting the original tape's parameters. (Note: the two tapes will share parameter data until one of the tapes has their parameter list modified.)

      • Copied tapes can be cast to another QuantumTape subclass by passing the tape_cls keyword argument.

    Breaking changes

    • Updated how parameter-shift gradient recipes are defined for operations, allowing for gradient recipes that are specified as an arbitrary number of terms. (#909)

      Previously, Operation.grad_recipe was restricted to two-term parameter-shift formulas. With this change, the gradient recipe now contains elements of the form [c_i, a_i, s_i], resulting in a gradient recipe of \frac{\partial}{\partial\phi_k}f(\phi_k) = \sum_{i} c_i f(a_i \phi_k + s_i ).

      As this is a breaking change, all custom operations with defined gradient recipes must be updated to continue working with PennyLane 0.13. Note though that if grad_recipe = None, the default gradient recipe remains unchanged, and corresponds to the two terms [c_0, a_0, s_0]=[1/2, 1, \pi/2] and [c_1, a_1, s_1]=[-1/2, 1, -\pi/2] for every parameter.

    • The VQECost class has been renamed to ExpvalCost to reflect its general applicability beyond VQE. Use of VQECost is still possible but will result in a deprecation warning. (#913)

    Bug fixes

    • The default.qubit.tf device is updated to handle TensorFlow objects (e.g., tf.Variable) as gate parameters correctly when using the MultiRZ and CRot operations. (#921)

    • PennyLane tensor objects are now unwrapped in BaseQNode when passed as a keyword argument to the quantum function. (#903) (#893)

    • The new tape mode now prevents multiple observables from being evaluated on the same wire if the observables are not qubit-wise commuting Pauli words. (#882)

    • Fixes a bug in default.qubit whereby inverses of common gates were not being applied via efficient gate-specific methods, instead falling back to matrix-vector multiplication. The following gates were affected: PauliX, PauliY, PauliZ, Hadamard, SWAP, S, T, CNOT, CZ. (#872)

    • The PauliRot operation now gracefully handles single-qubit Paulis, and all-identity Paulis (#860).

    • Fixes a bug whereby binary Python operators were not properly propagating the requires_grad attribute to the output tensor. (#889)

    • Fixes a bug which prevents TorchLayer from doing backward when CUDA is enabled. (#899)

    • Fixes a bug where multi-threaded execution of QNodeCollection sometimes fails because of simultaneous queuing. This is fixed by adding thread locking during queuing. (#910)

    • Fixes a bug in QuantumTape.set_parameters(). The previous implementation assumed that the self.trainable_parms set would always be iterated over in increasing integer order. However, this is not guaranteed behaviour, and can lead to the incorrect tape parameters being set if this is not the case. (#923)

    • Fixes broken error message if a QNode is instantiated with an unknown exception. (#930)

    Contributors

    This release contains contributions from (in alphabetical order):

    Juan Miguel Arrazola, Thomas Bromley, Christina Lee, Alain Delgado Gran, Olivia Di Matteo, Anthony Hayes, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Shumpei Kobayashi, Romain Moyard, Zeyue Niu, Maria Schuld, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.12.0(Oct 20, 2020)

    New features since last release

    New and improved simulators

    • PennyLane now supports a new device, default.mixed, designed for simulating mixed-state quantum computations. This enables native support for implementing noisy channels in a circuit, which generally map pure states to mixed states. (#794) (#807) (#819)

      The device can be initialized as

      >>> dev = qml.device("default.mixed", wires=1)
      

      This allows the construction of QNodes that include non-unitary operations, such as noisy channels:

      >>> @qml.qnode(dev)
      ... def circuit(params):
      ...     qml.RX(params[0], wires=0)
      ...     qml.RY(params[1], wires=0)
      ...     qml.AmplitudeDamping(0.5, wires=0)
      ...     return qml.expval(qml.PauliZ(0))
      >>> print(circuit([0.54, 0.12]))
      0.9257702929524184
      >>> print(circuit([0, np.pi]))
      0.0
      

    New tools for optimizing measurements

    • The new grouping module provides functionality for grouping simultaneously measurable Pauli word observables. (#761) (#850) (#852)

      • The optimize_measurements function will take as input a list of Pauli word observables and their corresponding coefficients (if any), and will return the partitioned Pauli terms diagonalized in the measurement basis and the corresponding diagonalizing circuits.

        from pennylane.grouping import optimize_measurements
        h, nr_qubits = qml.qchem.molecular_hamiltonian("h2", "h2.xyz")
        rotations, grouped_ops, grouped_coeffs = optimize_measurements(h.ops, h.coeffs, grouping="qwc")
        

        The diagonalizing circuits of rotations correspond to the diagonalized Pauli word groupings of grouped_ops.

      • Pauli word partitioning utilities are performed by the PauliGroupingStrategy class. An input list of Pauli words can be partitioned into mutually commuting, qubit-wise-commuting, or anticommuting groupings.

        For example, partitioning Pauli words into anticommutative groupings by the Recursive Largest First (RLF) graph colouring heuristic:

        from pennylane import PauliX, PauliY, PauliZ, Identity
        from pennylane.grouping import group_observables
        pauli_words = [
            Identity('a') @ Identity('b'),
            Identity('a') @ PauliX('b'),
            Identity('a') @ PauliY('b'),
            PauliZ('a') @ PauliX('b'),
            PauliZ('a') @ PauliY('b'),
            PauliZ('a') @ PauliZ('b')
        ]
        groupings = group_observables(pauli_words, grouping_type='anticommuting', method='rlf')
        
      • Various utility functions are included for obtaining and manipulating Pauli words in the binary symplectic vector space representation.

        For instance, two Pauli words may be converted to their binary vector representation:

        >>> from pennylane.grouping import pauli_to_binary
        >>> from pennylane.wires import Wires
        >>> wire_map = {Wires('a'): 0, Wires('b'): 1}
        >>> pauli_vec_1 = pauli_to_binary(qml.PauliX('a') @ qml.PauliY('b'))
        >>> pauli_vec_2 = pauli_to_binary(qml.PauliZ('a') @ qml.PauliZ('b'))
        >>> pauli_vec_1
        [1. 1. 0. 1.]
        >>> pauli_vec_2
        [0. 0. 1. 1.]
        

        Their product up to a phase may be computed by taking the sum of their binary vector representations, and returned in the operator representation.

        >>> from pennylane.grouping import binary_to_pauli
        >>> binary_to_pauli((pauli_vec_1 + pauli_vec_2) % 2, wire_map)
        Tensor product ['PauliY', 'PauliX']: 0 params, wires ['a', 'b']
        

        For more details on the grouping module, see the grouping module documentation

    Returning the quantum state from simulators

    • The quantum state of a QNode can now be returned using the qml.state() return function. (#818)

      import pennylane as qml
      
      dev = qml.device("default.qubit", wires=3)
      qml.enable_tape()
      
      @qml.qnode(dev)
      def qfunc(x, y):
          qml.RZ(x, wires=0)
          qml.CNOT(wires=[0, 1])
          qml.RY(y, wires=1)
          qml.CNOT(wires=[0, 2])
          return qml.state()
      
      >>> qfunc(0.56, 0.1)
      array([0.95985437-0.27601028j, 0.        +0.j        ,
             0.04803275-0.01381203j, 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ,
             0.        +0.j        , 0.        +0.j        ])
      

      Differentiating the state is currently available when using the classical backpropagation differentiation method (diff_method="backprop") with a compatible device, and when using the new tape mode.

    New operations and channels

    • PennyLane now includes standard channels such as the Amplitude-damping, Phase-damping, and Depolarizing channels, as well as the ability to make custom qubit channels. (#760) (#766) (#778)

    • The controlled-Y operation is now available via qml.CY. For devices that do not natively support the controlled-Y operation, it will be decomposed into qml.RY, qml.CNOT, and qml.S operations. (#806)

    Preview the next-generation PennyLane QNode

    • The new PennyLane tape module provides a re-formulated QNode class, rewritten from the ground-up, that uses a new QuantumTape object to represent the QNode's quantum circuit. Tape mode provides several advantages over the standard PennyLane QNode. (#785) (#792) (#796) (#800) (#803) (#804) (#805) (#808) (#810) (#811) (#815) (#820) (#823) (#824) (#829)

      • Support for in-QNode classical processing: Tape mode allows for differentiable classical processing within the QNode.

      • No more Variable wrapping: In tape mode, QNode arguments no longer become Variable objects within the QNode.

      • Less restrictive QNode signatures: There is no longer any restriction on the QNode signature; the QNode can be defined and called following the same rules as standard Python functions.

      • Unifying all QNodes: The tape-mode QNode merges all QNodes (including the JacobianQNode and the PassthruQNode) into a single unified QNode, with identical behaviour regardless of the differentiation type.

      • Optimizations: Tape mode provides various performance optimizations, reducing pre- and post-processing overhead, and reduces the number of quantum evaluations in certain cases.

      Note that tape mode is experimental, and does not currently have feature-parity with the existing QNode. Feedback and bug reports are encouraged and will help improve the new tape mode.

      Tape mode can be enabled globally via the qml.enable_tape function, without changing your PennyLane code:

      qml.enable_tape()
      dev = qml.device("default.qubit", wires=1)
      
      @qml.qnode(dev, interface="tf")
      def circuit(p):
          print("Parameter value:", p)
          qml.RX(tf.sin(p[0])**2 + p[1], wires=0)
          return qml.expval(qml.PauliZ(0))
      

      For more details, please see the tape mode documentation.

    Improvements

    • QNode caching has been introduced, allowing the QNode to keep track of the results of previous device executions and reuse those results in subsequent calls. Note that QNode caching is only supported in the new and experimental tape-mode. (#817)

      Caching is available by passing a caching argument to the QNode:

      dev = qml.device("default.qubit", wires=2)
      qml.enable_tape()
      
      @qml.qnode(dev, caching=10)  # cache up to 10 evaluations
      def qfunc(x):
          qml.RX(x, wires=0)
          qml.RX(0.3, wires=1)
          qml.CNOT(wires=[0, 1])
          return qml.expval(qml.PauliZ(1))
      
      qfunc(0.1)  # first evaluation executes on the device
      qfunc(0.1)  # second evaluation accesses the cached result
      
    • Sped up the application of certain gates in default.qubit by using array/tensor manipulation tricks. The following gates are affected: PauliX, PauliY, PauliZ, Hadamard, SWAP, S, T, CNOT, CZ. (#772)

    • The computation of marginal probabilities has been made more efficient for devices with a large number of wires, achieving in some cases a 5x speedup. (#799)

    • Adds arithmetic operations (addition, tensor product, subtraction, and scalar multiplication) between Hamiltonian, Tensor, and Observable objects, and inline arithmetic operations between Hamiltonians and other observables. (#765)

      Hamiltonians can now easily be defined as sums of observables:

      >>> H = 3 * qml.PauliZ(0) - (qml.PauliX(0) @ qml.PauliX(1)) + qml.Hamiltonian([4], [qml.PauliZ(0)])
      >>> print(H)
      (7.0) [Z0] + (-1.0) [X0 X1]
      
    • Adds compare() method to Observable and Hamiltonian classes, which allows for comparison between observable quantities. (#765)

      >>> H = qml.Hamiltonian([1], [qml.PauliZ(0)])
      >>> obs = qml.PauliZ(0) @ qml.Identity(1)
      >>> print(H.compare(obs))
      True
      
      >>> H = qml.Hamiltonian([2], [qml.PauliZ(0)])
      >>> obs = qml.PauliZ(1) @ qml.Identity(0)
      >>> print(H.compare(obs))
      False
      
    • Adds simplify() method to the Hamiltonian class. (#765)

      >>> H = qml.Hamiltonian([1, 2], [qml.PauliZ(0), qml.PauliZ(0) @ qml.Identity(1)])
      >>> H.simplify()
      >>> print(H)
      (3.0) [Z0]
      
    • Added a new bit-flip mixer to the qml.qaoa module. (#774)

    • Summation of two Wires objects is now supported and will return a Wires object containing the set of all wires defined by the terms in the summation. (#812)

    Breaking changes

    • The PennyLane NumPy module now returns scalar (zero-dimensional) arrays where Python scalars were previously returned. (#820) (#833)

      For example, this affects array element indexing, and summation:

      >>> x = np.array([1, 2, 3], requires_grad=False)
      >>> x[0]
      tensor(1, requires_grad=False)
      >>> np.sum(x)
      tensor(6, requires_grad=True)
      

      This may require small updates to user code. A convenience method, np.tensor.unwrap(), has been added to help ease the transition. This converts PennyLane NumPy tensors to standard NumPy arrays and Python scalars:

      >>> x = np.array(1.543, requires_grad=False)
      >>> x.unwrap()
      1.543
      

      Note, however, that information regarding array differentiability will be lost.

    • The device capabilities dictionary has been redesigned, for clarity and robustness. In particular, the capabilities dictionary is now inherited from the parent class, various keys have more expressive names, and all keys are now defined in the base device class. For more details, please refer to the developer documentation. (#781)

    PennyLane-QChem

    • The functions one_particle and two_particle have been implemented to extend PennyLane-QChem capabilities to construct observables of many-body quantum systems. These functions can be used in conjunction with the observable function to construct electronic structure hamiltonians involving one- and two-particle operators. (#809)

    • The function observable in the obs module has been generalized to build many-body observables combining one- and two-particle operators (e.g., Hamiltonians) (#791)

    • Fix calculation of the contribution of core orbitals to two-particle operators in the function two_particle. (#825)

    Bug fixes

    • Changed to use lists for storing variable values inside BaseQNode allowing complex matrices to be passed to QubitUnitary. (#773)

    • Fixed a bug within default.qubit, resulting in greater efficiency when applying a state vector to all wires on the device. (#849)

    Documentation

    • Equations have been added to the qml.sample and qml.probs docstrings to clarify the mathematical foundation of the performed measurements. (#843)

    Contributors

    This release contains contributions from (in alphabetical order):

    Aroosa Ijaz, Juan Miguel Arrazola, Thomas Bromley, Jack Ceroni, Alain Delgado Gran, Josh Izaac, Soran Jahangiri, Nathan Killoran, Robert Lang, Cedric Lin, Olivia Di Matteo, Nicolás Quesada, Maria Schuld, Antal Száva.

    Source code(tar.gz)
    Source code(zip)
  • v0.11.0(Aug 18, 2020)

    New features since last release

    New and improved simulators

    • Added a new device, default.qubit.autograd, a pure-state qubit simulator written using Autograd. This device supports classical backpropagation (diff_method="backprop"); this can be faster than the parameter-shift rule for computing quantum gradients when the number of parameters to be optimized is large. (#721)

      >>> dev = qml.device("default.qubit.autograd", wires=1)
      >>> @qml.qnode(dev, diff_method="backprop")
      ... def circuit(x):
      ...     qml.RX(x[1], wires=0)
      ...     qml.Rot(x[0], x[1], x[2], wires=0)
      ...     return qml.expval(qml.PauliZ(0))
      >>> weights = np.array([0.2, 0.5, 0.1])
      >>> grad_fn = qml.grad(circuit)
      >>> print(grad_fn(weights))
      array([-2.25267173e-01, -1.00864546e+00,  6.93889390e-18])
      

      See the device documentation for more details.

    • A new experimental C++ state-vector simulator device is now available, lightning.qubit. It uses the C++ Eigen library to perform fast linear algebra calculations for simulating quantum state-vector evolution.

      lightning.qubit is currently in beta; it can be installed via pip:

      $ pip install pennylane-lightning
      

      Once installed, it can be used as a PennyLane device:

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

      For more details, please see the lightning qubit documentation.

    New algorithms and templates

    • Added built-in QAOA functionality via the new qml.qaoa module. (#712) (#718) (#741) (#720)

      This includes the following features:

      • New qml.qaoa.x_mixer and qml.qaoa.xy_mixer functions for defining Pauli-X and XY mixer Hamiltonians.

      • MaxCut: The qml.qaoa.maxcut function allows easy construction of the cost Hamiltonian and recommended mixer Hamiltonian for solving the MaxCut problem for a supplied graph.

      • Layers: qml.qaoa.cost_layer and qml.qaoa.mixer_layer take cost and mixer Hamiltonians, respectively, and apply the corresponding QAOA cost and mixer layers to the quantum circuit

      For example, using PennyLane to construct and solve a MaxCut problem with QAOA:

      wires = range(3)
      graph = Graph([(0, 1), (1, 2), (2, 0)])
      cost_h, mixer_h = qaoa.maxcut(graph)
      
      def qaoa_layer(gamma, alpha):
          qaoa.cost_layer(gamma, cost_h)
          qaoa.mixer_layer(alpha, mixer_h)
      
      def antatz(params, **kwargs):
      
          for w in wires:
              qml.Hadamard(wires=w)
      
          # repeat the QAOA layer two times
          qml.layer(qaoa_layer, 2, params[0], params[1])
      
      dev = qml.device('default.qubit', wires=len(wires))
      cost_function = qml.VQECost(ansatz, cost_h, dev)
      
    • Added an ApproxTimeEvolution template to the PennyLane templates module, which can be used to implement Trotterized time-evolution under a Hamiltonian. (#710)

    • Added a qml.layer template-constructing function, which takes a unitary, and repeatedly applies it on a set of wires to a given depth. (#723)

      def subroutine():
          qml.Hadamard(wires=[0])
          qml.CNOT(wires=[0, 1])
          qml.PauliX(wires=[1])
      
      dev = qml.device('default.qubit', wires=3)
      
      @qml.qnode(dev)
      def circuit():
          qml.layer(subroutine, 3)
          return [qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))]
      

      This creates the following circuit:

      >>> circuit()
      >>> print(circuit.draw())
      0: ──H──╭C──X──H──╭C──X──H──╭C──X──┤ ⟨Z⟩
      1: ─────╰X────────╰X────────╰X─────┤ ⟨Z⟩
      
    • Added the qml.utils.decompose_hamiltonian function. This function can be used to decompose a Hamiltonian into a linear combination of Pauli operators. (#671)

      >>> A = np.array(
      ... [[-2, -2+1j, -2, -2],
      ... [-2-1j,  0,  0, -1],
      ... [-2,  0, -2, -1],
      ... [-2, -1, -1,  0]])
      >>> coeffs, obs_list = decompose_hamiltonian(A)
      

    New device features

    • It is now possible to specify custom wire labels, such as ['anc1', 'anc2', 0, 1, 3], where the labels can be strings or numbers. (#666)

      Custom wire labels are defined by passing a list to the wires argument when creating the device:

      >>> dev = qml.device("default.qubit", wires=['anc1', 'anc2', 0, 1, 3])
      

      Quantum operations should then be invoked with these custom wire labels:

      >>> @qml.qnode(dev)
      >>> def circuit():
      ...    qml.Hadamard(wires='anc2')
      ...    qml.CNOT(wires=['anc1', 3])
      ...    ...
      

      The existing behaviour, in which the number of wires is specified on device initialization, continues to work as usual. This gives a default behaviour where wires are labelled by consecutive integers.

      >>> dev = qml.device("default.qubit", wires=5)
      
    • An integrated device test suite has been added, which can be used to run basic integration tests on core or external devices. (#695) (#724) (#733)

      The test can be invoked against a particular device by calling the pl-device-test command line program:

      $ pl-device-test --device=default.qubit --shots=1234 --analytic=False
      

      If the tests are run on external devices, the device and its dependencies must be installed locally. For more details, please see the plugin test documentation.

    Improvements

    • Added support for TensorFlow 2.3 and PyTorch 1.6. (#725)

    • Returning probabilities is now supported from photonic QNodes. As with qubit QNodes, photonic QNodes returning probabilities are end-to-end differentiable. (#699)

      >>> dev = qml.device("strawberryfields.fock", wires=2, cutoff_dim=5)
      >>> @qml.qnode(dev)
      ... def circuit(a):
      ...     qml.Displacement(a, 0, wires=0)
      ...     return qml.probs(wires=0)
      >>> print(circuit(0.5))
      [7.78800783e-01 1.94700196e-01 2.43375245e-02 2.02812704e-03 1.26757940e-04]
      

    Breaking changes

    • The pennylane.plugins and pennylane.beta.plugins folders have been renamed to pennylane.devices and pennylane.beta.devices, to reflect their content better. (#726)

    Bug fixes

    • The PennyLane interface conversion functions can now convert QNodes with pre-existing interfaces. (#707)

    Documentation

    • The interfaces section of the documentation has been renamed to 'Interfaces and training', and updated with the latest variable handling details. (#753)

    Contributors

    This release contains contributions from (in alphabetical order):

    Juan Miguel Arazzola, Thomas Bromley, Jack Ceroni, Alain Delgado Gran, Shadab Hussain, Theodor Isacsson, Josh Izaac, Nathan Killoran, Maria Schuld, Antal Száva, Nicola Vitucci.

    Source code(tar.gz)
    Source code(zip)
Owner
PennyLaneAI
PennyLane is a cross-platform Python library for differentiable programming of quantum computers. Train a quantum computer the same way as a neural network.
PennyLaneAI
A Python Automated Machine Learning tool that optimizes machine learning pipelines using genetic programming.

Master status: Development status: Package information: TPOT stands for Tree-based Pipeline Optimization Tool. Consider TPOT your Data Science Assista

Epistasis Lab at UPenn 8.9k Jan 9, 2023
Probabilistic programming framework that facilitates objective model selection for time-varying parameter models.

Time series analysis today is an important cornerstone of quantitative science in many disciplines, including natural and life sciences as well as eco

Christoph Mark 129 Dec 24, 2022
A simple example of ML classification, cross validation, and visualization of feature importances

Simple-Classifier This is a basic example of how to use several different libraries for classification and ensembling, mostly with sklearn. Example as

Rob 2 Aug 25, 2022
a distributed deep learning platform

Apache SINGA Distributed deep learning system http://singa.apache.org Quick Start Installation Examples Issues JIRA tickets Code Analysis: Mailing Lis

The Apache Software Foundation 2.7k Jan 5, 2023
AutoOED: Automated Optimal Experiment Design Platform

AutoOED is an optimal experiment design platform powered with automated machine learning to accelerate the discovery of optimal solutions. Our platform solves multi-objective optimization problems and automatically guides the design of experiment to be evaluated.

Yunsheng Tian 107 Jan 3, 2023
Apache Liminal is an end-to-end platform for data engineers & scientists, allowing them to build, train and deploy machine learning models in a robust and agile way

Apache Liminals goal is to operationalise the machine learning process, allowing data scientists to quickly transition from a successful experiment to an automated pipeline of model training, validation, deployment and inference in production. Liminal provides a Domain Specific Language to build ML workflows on top of Apache Airflow.

The Apache Software Foundation 121 Dec 28, 2022
Model factory is a ML training platform to help engineers to build ML models at scale

Model Factory Machine learning today is powering many businesses today, e.g., search engine, e-commerce, news or feed recommendation. Training high qu

null 16 Sep 23, 2022
Graphsignal is a machine learning model monitoring platform.

Graphsignal is a machine learning model monitoring platform. It helps ML engineers, MLOps teams and data scientists to quickly address issues with data and models as well as proactively analyze model performance and availability.

Graphsignal 143 Dec 5, 2022
This repository contains full machine learning pipeline of the Zillow Houses competition on Kaggle platform.

Zillow-Houses This repository contains full machine learning pipeline of the Zillow Houses competition on Kaggle platform. Pipeline is consists of 10

null 2 Jan 9, 2022
MLReef is an open source ML-Ops platform that helps you collaborate, reproduce and share your Machine Learning work with thousands of other users.

The collaboration platform for Machine Learning MLReef is an open source ML-Ops platform that helps you collaborate, reproduce and share your Machine

MLReef 1.4k Dec 27, 2022
A flexible CTF contest platform for coming PKU GeekGame events

Project Guiding Star: the Backend A flexible CTF contest platform for coming PKU GeekGame events Still in early development Highlights Not configurabl

PKU GeekGame 14 Dec 15, 2022
An open source framework that provides a simple, universal API for building distributed applications. Ray is packaged with RLlib, a scalable reinforcement learning library, and Tune, a scalable hyperparameter tuning library.

Ray provides a simple, universal API for building distributed applications. Ray is packaged with the following libraries for accelerating machine lear

null 23.3k Dec 31, 2022
Python library which makes it possible to dynamically mask/anonymize data using JSON string or python dict rules in a PySpark environment.

pyspark-anonymizer Python library which makes it possible to dynamically mask/anonymize data using JSON string or python dict rules in a PySpark envir

null 6 Jun 30, 2022
A library of extension and helper modules for Python's data analysis and machine learning libraries.

Mlxtend (machine learning extensions) is a Python library of useful tools for the day-to-day data science tasks. Sebastian Raschka 2014-2021 Links Doc

Sebastian Raschka 4.2k Dec 29, 2022
Open source time series library for Python

PyFlux PyFlux is an open source time series library for Python. The library has a good array of modern time series models, as well as a flexible array

Ross Taylor 2k Jan 2, 2023
MLBox is a powerful Automated Machine Learning python library.

MLBox is a powerful Automated Machine Learning python library. It provides the following features: Fast reading and distributed data preprocessing/cle

Axel 1.4k Jan 6, 2023
Scalable, Portable and Distributed Gradient Boosting (GBDT, GBRT or GBM) Library, for Python, R, Java, Scala, C++ and more. Runs on single machine, Hadoop, Spark, Dask, Flink and DataFlow

eXtreme Gradient Boosting Community | Documentation | Resources | Contributors | Release Notes XGBoost is an optimized distributed gradient boosting l

Distributed (Deep) Machine Learning Community 23.6k Jan 3, 2023
A fast, scalable, high performance Gradient Boosting on Decision Trees library, used for ranking, classification, regression and other machine learning tasks for Python, R, Java, C++. Supports computation on CPU and GPU.

Website | Documentation | Tutorials | Installation | Release Notes CatBoost is a machine learning method based on gradient boosting over decision tree

CatBoost 6.9k Jan 5, 2023
A statistical library designed to fill the void in Python's time series analysis capabilities, including the equivalent of R's auto.arima function.

pmdarima Pmdarima (originally pyramid-arima, for the anagram of 'py' + 'arima') is a statistical library designed to fill the void in Python's time se

alkaline-ml 1.3k Dec 22, 2022