Skip to content

Releases: PennyLaneAI/pennylane

Release 0.14.0

02 Feb 08:13
Compare
Choose a tag to compare

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)](https://github.com/Pe...
Read more

Release 0.13.0-post2

22 Dec 16:10
10f3a5a
Compare
Choose a tag to compare

A minor post-release to update the main page of the PennyLane documentation.

Release 0.13.0-post1

08 Dec 23:55
Compare
Choose a tag to compare

A minor post-release to update the main page of the PennyLane documentation.

Release 0.13.0

27 Nov 18:18
Compare
Choose a tag to compare

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.com...
Read more

Release 0.12.0

20 Oct 07:40
Compare
Choose a tag to compare

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

Read more

Release 0.11.0

18 Aug 05:09
5cb0994
Compare
Choose a tag to compare

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.

Release 0.10.0

22 Jun 15:26
Compare
Choose a tag to compare

New features since last release

New and improved simulators

  • Added a new device, default.qubit.tf, a pure-state qubit simulator written using TensorFlow. As a result, it supports classical backpropagation as a means to compute the Jacobian. This can be faster than the parameter-shift rule for computing quantum gradients when the number of parameters to be optimized is large.

    default.qubit.tf is designed to be used with end-to-end classical backpropagation (diff_method="backprop") with the TensorFlow interface. This is the default method of differentiation when creating a QNode with this device.

    Using this method, the created QNode is a 'white-box' that is tightly integrated with your TensorFlow computation, including AutoGraph support:

    >>> dev = qml.device("default.qubit.tf", wires=1)
    >>> @tf.function
    ... @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)
    ...     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))
    tf.Tensor([-2.2526717e-01 -1.0086454e+00  1.3877788e-17], shape=(3,), dtype=float32)

    See the default.qubit.tf documentation for more details.

  • The default.tensor plugin has been significantly upgraded. It now allows two different tensor network representations to be used: "exact" and "mps". The former uses a exact factorized representation of quantum states, while the latter uses a matrix product state representation. (#572) (#599)

New machine learning functionality and integrations

  • PennyLane QNodes can now be converted into Torch layers, allowing for creation of quantum and hybrid models using the torch.nn API. (#588)

    A PennyLane QNode can be converted into a torch.nn layer using the qml.qnn.TorchLayer class:

    >>> @qml.qnode(dev)
    ... def qnode(inputs, weights_0, weight_1):
    ...    # define the circuit
    ...    # ...
    
    >>> weight_shapes = {"weights_0": 3, "weight_1": 1}
    >>> qlayer = qml.qnn.TorchLayer(qnode, weight_shapes)

    A hybrid model can then be easily constructed:

    >>> model = torch.nn.Sequential(qlayer, torch.nn.Linear(2, 2))
  • Added a new "reversible" differentiation method which can be used in simulators, but not hardware.

    The reversible approach is similar to backpropagation, but trades off extra computation for enhanced memory efficiency. Where backpropagation caches the state tensors at each step during a simulated evolution, the reversible method only caches the final pre-measurement state.

    Compared to the parameter-shift method, the reversible method can be faster or slower, depending on the density and location of parametrized gates in a circuit (circuits with higher density of parametrized gates near the end of the circuit will see a benefit). (#670)

    >>> dev = qml.device("default.qubit", wires=2)
    ... @qml.qnode(dev, diff_method="reversible")
    ... def circuit(x):
    ...     qml.RX(x, wires=0)
    ...     qml.RX(x, wires=0)
    ...     qml.CNOT(wires=[0,1])
    ...     return qml.expval(qml.PauliZ(0))
    >>> qml.grad(circuit)(0.5)
    (array(-0.47942554),)

New templates and cost functions

  • Added the new templates UCCSD, SingleExcitationUnitary, andDoubleExcitationUnitary, which together implement the Unitary Coupled-Cluster Singles and Doubles (UCCSD) ansatz to perform VQE-based quantum chemistry simulations using PennyLane-QChem. (#622) (#638) (#654) (#659) (#622)

  • Added module pennylane.qnn.cost with class SquaredErrorLoss. The module contains classes to calculate losses and cost functions on circuits with trainable parameters. (#642)

Improvements

  • A significant improvement with respect to how QNodes and interfaces mark quantum function arguments as differentiable when using Autograd, designed to improve performance and make QNodes more intuitive. (#648) (#650)

    In particular, the following changes have been made:

    • A new ndarray subclass pennylane.numpy.tensor, which extends NumPy arrays with the keyword argument and attribute requires_grad. Tensors which have requires_grad=False are treated as non-differentiable by the Autograd interface.

    • A new subpackage pennylane.numpy, which wraps autograd.numpy such that NumPy functions accept the requires_grad keyword argument, and allows Autograd to differentiate pennylane.numpy.tensor objects.

    • The argnum argument to qml.grad is now optional; if not provided, arguments explicitly marked as requires_grad=False are excluded for the list of differentiable arguments. The ability to pass argnum has been retained for backwards compatibility, and if present the old behaviour persists.

  • The QNode Torch interface now inspects QNode positional arguments. If any argument does not have the attribute requires_grad=True, it is automatically excluded from quantum gradient computations. (#652) (#660)

  • The QNode TF interface now inspects QNode positional arguments. If any argument is not being watched by a tf.GradientTape(), it is automatically excluded from quantum gradient computations. (#655) (#660)

  • QNodes have two new public methods: QNode.set_trainable_args() and QNode.get_trainable_args(). These are designed to be called by interfaces, to specify to the QNode which of its input arguments are differentiable. Arguments which are non-differentiable will not be converted to PennyLane Variable objects within the QNode. (#660)

  • Added decomposition method to PauliX, PauliY, PauliZ, S, T, Hadamard, and PhaseShift gates, which decomposes each of these gates into rotation gates. (#668)

  • The CircuitGraph class now supports serializing contained circuit operations and measurement basis rotations to an OpenQASM2.0 script via the new CircuitGraph.to_openqasm() method. (#623)

Breaking changes

  • Removes support for Python 3.5. (#639)

Documentation

  • Various small typos were fixed.

Contributors

This release contains contributions from (in alphabetical order):

Thomas Bromley, Jack Ceroni, Alain Delgado Gran, Theodor Isacsson, Josh Izaac, Nathan Killoran, Maria Schuld, Antal Száva, Nicola Vitucci.

Release 0.9.0

15 May 01:50
51d3bd4
Compare
Choose a tag to compare

New features since last release

New machine learning integrations

  • PennyLane QNodes can now be converted into Keras layers, allowing for creation of quantum and hybrid models using the Keras API. (#529)

    A PennyLane QNode can be converted into a Keras layer using the KerasLayer class:

    from pennylane.qnn import KerasLayer
    
    @qml.qnode(dev)
    def circuit(inputs, weights_0, weight_1):
       # define the circuit
       # ...
    
    weight_shapes = {"weights_0": 3, "weight_1": 1}
    qlayer = qml.qnn.KerasLayer(circuit, weight_shapes, output_dim=2)

    A hybrid model can then be easily constructed:

    model = tf.keras.models.Sequential([qlayer, tf.keras.layers.Dense(2)])
  • Added a new type of QNode, qml.qnodes.PassthruQNode. For simulators which are coded in an external library which supports automatic differentiation, PennyLane will treat a PassthruQNode as a "white box", and rely on the external library to directly provide gradients via backpropagation. This can be more efficient than the using parameter-shift rule for a large number of parameters. (#488)

    Currently this behaviour is supported by PennyLane's default.tensor.tf device backend, compatible with the 'tf' interface using TensorFlow 2:

    dev = qml.device('default.tensor.tf', wires=2)
    
    @qml.qnode(dev, diff_method="backprop")
    def circuit(params):
        qml.RX(params[0], wires=0)
        qml.RX(params[1], wires=1)
        qml.CNOT(wires=[0, 1])
        return qml.expval(qml.PauliZ(0))
    
    qnode = PassthruQNode(circuit, dev)
    params = tf.Variable([0.3, 0.1])
    
    with tf.GradientTape() as tape:
        tape.watch(params)
        res = qnode(params)
    
    grad = tape.gradient(res, params)

New optimizers

  • Added the qml.RotosolveOptimizer, a gradient-free optimizer that minimizes the quantum function by updating each parameter, one-by-one, via a closed-form expression while keeping other parameters fixed.
    (#636) (#539)

  • Added the qml.RotoselectOptimizer, which uses Rotosolve to minimizes a quantum function with respect to both the
    rotation operations applied and the rotation parameters. (#636) (#539)

    For example, given a quantum function f that accepts parameters x and a list of corresponding rotation operations generators, the Rotoselect optimizer will, at each step, update both the parameter values and the list of rotation gates to minimize the loss:

    >>> opt = qml.optimize.RotoselectOptimizer()
    >>> x = [0.3, 0.7]
    >>> generators = [qml.RX, qml.RY]
    >>> for _ in range(100):
    ...     x, generators = opt.step(f, x, generators)

New operations

  • Added the PauliRot gate, which performs an arbitrary Pauli rotation on multiple qubits, and the MultiRZ gate,
    which performs a rotation generated by a tensor product of Pauli Z operators. (#559)

    dev = qml.device('default.qubit', wires=4)
    
    @qml.qnode(dev)
    def circuit(angle):
        qml.PauliRot(angle, "IXYZ", wires=[0, 1, 2, 3])
        return [qml.expval(qml.PauliZ(wire)) for wire in [0, 1, 2, 3]]
    >>> circuit(0.4)
    [1.         0.92106099 0.92106099 1.        ]
    >>> print(circuit.draw())
     0: ──╭RI(0.4)──┤ ⟨Z⟩
     1: ──├RX(0.4)──┤ ⟨Z⟩
     2: ──├RY(0.4)──┤ ⟨Z⟩
     3: ──╰RZ(0.4)──┤ ⟨Z⟩

    If the PauliRot gate is not supported on the target device, it will be decomposed into Hadamard, RX and MultiRZ gates. Note that identity gates in the Pauli word result in untouched wires:

    >>> print(circuit.draw())
     0: ───────────────────────────────────┤ ⟨Z⟩
     1: ──H──────────╭RZ(0.4)──H───────────┤ ⟨Z⟩
     2: ──RX(1.571)──├RZ(0.4)──RX(-1.571)──┤ ⟨Z⟩
     3: ─────────────╰RZ(0.4)──────────────┤ ⟨Z⟩

    If the MultiRZ gate is not supported, it will be decomposed into
    CNOT and RZ gates:

    >>> print(circuit.draw())
     0: ──────────────────────────────────────────────────┤ ⟨Z⟩
     1: ──H──────────────╭X──RZ(0.4)──╭X──────H───────────┤ ⟨Z⟩
     2: ──RX(1.571)──╭X──╰C───────────╰C──╭X──RX(-1.571)──┤ ⟨Z⟩
     3: ─────────────╰C───────────────────╰C──────────────┤ ⟨Z⟩
  • PennyLane now provides DiagonalQubitUnitary for diagonal gates, that are e.g., encountered in IQP circuits. These kinds of gates can be evaluated much faster on a simulator device. (#567)

    The gate can be used, for example, to efficiently simulate oracles:

    dev = qml.device('default.qubit', wires=3)
    
    # Function as a bitstring
    f = np.array([1, 0, 0, 1, 1, 0, 1, 0])
    
    @qml.qnode(dev)
    def circuit(weights1, weights2):
        qml.templates.StronglyEntanglingLayers(weights1, wires=[0, 1, 2])
    
        # Implements the function as a phase-kickback oracle
        qml.DiagonalQubitUnitary((-1)**f, wires=[0, 1, 2])
    
        qml.templates.StronglyEntanglingLayers(weights2, wires=[0, 1, 2])
        return [qml.expval(qml.PauliZ(w)) for w in range(3)]
  • Added the TensorN CVObservable that can represent the tensor product of the NumberOperator on photonic backends. (#608)

New templates

  • Added the ArbitraryUnitary and ArbitraryStatePreparation templates, which use PauliRot gates to perform an arbitrary unitary and prepare an arbitrary basis state with the minimal number of parameters. (#590)

    dev = qml.device('default.qubit', wires=3)
    
    @qml.qnode(dev)
    def circuit(weights1, weights2):
          qml.templates.ArbitraryStatePreparation(weights1, wires=[0, 1, 2])
          qml.templates.ArbitraryUnitary(weights2, wires=[0, 1, 2])
          return qml.probs(wires=[0, 1, 2])
  • Added the IQPEmbedding template, which encodes inputs into the diagonal gates of an IQP circuit. (#605)

  • Added the SimplifiedTwoDesign template, which implements the circuit design of Cerezo et al. (2020). (#556)

  • Added the BasicEntanglerLayers template, which is a simple layer architecture of rotations and CNOT nearest-neighbour entanglers. (#555)

  • PennyLane now offers a broadcasting function to easily construct templates: qml.broadcast() takes single quantum operations or other templates and applies them to wires in a specific pattern. (#515) (#522) (#526) (#603)

    For example, we can use broadcast to repeat a custom template across multiple wires:

    from pennylane.templates import template
    
    @template
    def mytemplate(pars, wires):
        qml.Hadamard(wires=wires)
        qml.RY(pars, wires=wires)
    
    dev = qml.device('default.qubit', wires=3)
    
    @qml.qnode(dev)
    def circuit(pars):
        qml.broadcast(mytemplate, pattern="single", wires=[0,1,2], parameters=pars)
        return qml.expval(qml.PauliZ(0))
    >>> circuit([1, 1, 0.1])
    -0.841470984807896
    >>> print(circuit.draw())
     0: ──H──RY(1.0)──┤ ⟨Z⟩
     1: ──H──RY(1.0)──┤
     2: ──H──RY(0.1)──┤

    For other available patterns, see the broadcast function documentation.

Breaking changes

  • The QAOAEmbedding now uses the new MultiRZ gate as a ZZ entangler, which changes the convention. While previously, the ZZ gate in the embedding was implemented as

    CNOT(wires=[wires[0], wires[1]])
    RZ(2 * parameter, wires=wires[0])
    CNOT(wires=[wires[0], wires[1]])

    the MultiRZ corresponds to

    CNOT(wires=[wires[1], wires[0]])
    RZ(parameter, wires=wires[0])
    CNOT(wires=[wires[1], wires[0]])

    which differs in the factor of 2, and fixes a bug in the wires that the CNOT was applied to. (#609)

  • Probability methods are handled by QubitDevice and device method requirements are modified to simplify plugin development. (#573)

  • The internal variables All and Any to mark an Operation as acting on all or any wires have been renamed to AllWires and AnyWires. (#614)

Improvements

  • Improvements to the speed/performance of the default.qubit device. (#567) (#559)

  • Added the "backprop" and "device" differentiation methods to the qnode decorator. (#552)

    • "backprop": Use classical backpropagation. Default on simulator devices that are classically end-to-end differentiable.
      The returned QNode can only be used with the same machine learning framework (e.g., default.tensor.tf simul...
Read more

Release 0.8.1

28 Feb 12:32
Compare
Choose a tag to compare

Improvements

  • Beginning of support for Python 3.8, with the test suite now being run in a Python 3.8 environment. (#501)

Documentation

  • Present templates as a gallery of thumbnails showing the basic circuit architecture. (#499)

Bug fixes

  • Fixed a bug where multiplying a QNode parameter by 0 caused a divide by zero error when calculating the parameter shift formula. (#512)

  • Fixed a bug where the shape of differentiable QNode arguments was being cached on the first construction, leading to indexing errors if the QNode was re-evaluated if the argument changed shape. (#505)

Contributors

This release contains contributions from (in alphabetical order):

Ville Bergholm, Josh Izaac, Maria Schuld, Antal Száva.

Release 0.8.0

07 Feb 21:54
Compare
Choose a tag to compare

See the release notes on the PennyLane website for code examples and more details.

New features since last release

  • Added a quantum chemistry package, pennylane.qchem, which supports integration with OpenFermion, Psi4, PySCF, and OpenBabel. Functions and classes for creating and solving VQE
    problems have also been addded. (#453) (#467)

    Features include:

    • Generate the qubit Hamiltonians directly starting with the atomic structure of the molecule.
    • Calculate the mean-field (Hartree-Fock) electronic structure of molecules.
    • Allow to define an active space based on the number of active electrons and active orbitals.
    • Perform the fermionic-to-qubit transformation of the electronic Hamiltonian by
      using different functions implemented in OpenFermion.
    • Convert OpenFermion's QubitOperator to a Pennylane Hamiltonian class.
    • Perform a Variational Quantum Eigensolver (VQE) computation with this Hamiltonian in PennyLane.
    • qml.Hamiltonian: a lightweight class for representing qubit Hamiltonians
    • qml.VQECost: a class for quickly constructing a differentiable cost function
      given a circuit ansatz, Hamiltonian, and one or more devices

    Check out the quantum chemistry quickstart, as well the quantum chemistry and VQE tutorials.

  • Added a circuit drawing feature that provides a text-based representation of a QNode instance. It can be invoked via qnode.draw(). The user can specify to display variable names instead of variable values and choose either an ASCII or Unicode charset. (#446)

  • Added QAOAEmbedding and its parameter initialization as a new trainable template.
    (#442)

  • Added the qml.probs() measurement function, allowing QNodes to differentiate variational circuit probabilities on simulators and hardware. QNodes that return probabilities fully support autodifferentiation. (#432)

  • Added the convenience load functions qml.from_pyquil, qml.from_quil and qml.from_quil_file that convert pyQuil objects and Quil code to PennyLane templates. This feature requires version 0.8 or above of the PennyLane-Forest plugin. (#459)

  • Added a qml.inv method that inverts templates and sequences of Operations. Added a @qml.template decorator that makes templates return the queued Operations. (#462)

  • Added the QNodeCollection container class, that allows independent QNodes to be stored and evaluated simultaneously. Experimental support for asynchronous evaluation of contained QNodes is provided with the parallel=True keyword argument. (#466)

  • Added a high level qml.map function, that maps a quantum circuit template over a list of observables or devices, returning a QNodeCollection. (#466)

  • Added high level qml.sum, qml.dot, qml.apply functions that act on QNode collections. qml.sum and qml.dot take the sum of a QNode collection, and a dot product of tensors/arrays/QNode collections, respectively.(#466)

Breaking changes

  • Deprecated the old-style QNode such that only the new-style QNode and its syntax can be used, moved all related files from the pennylane/beta folder to pennylane. (#440)

Improvements

  • Added the Tensor.prune() method and the Tensor.non_identity_obs property for extracting non-identity instances from the observables making up a Tensor instance. (#498)

  • Renamed the expt.tensornet and expt.tensornet.tf devices to default.tensor and default.tensor.tf. (#495)

  • Added a serialization method to the CircuitGraph class that is used to create a unique hash for each quantum circuit graph. (#470)

  • Added the Observable.eigvals method to return the eigenvalues of observables. (#449)

  • Added the Observable.diagonalizing_gates method to return the gates that diagonalize an observable in the computational basis. (#454)

  • Added the Operator.matrix method to return the matrix representation of an operator in the computational basis. (#454)

  • Added a QubitDevice class which implements common functionalities of plugin devices such that plugin devices can rely on these implementations. The new QubitDevice includes a new execute method, which allows for more convenient plugin design. As a result, the way samples are generated on qubit based devices has been unified. (#461) (#452) (#473)

  • Improved documentation of AmplitudeEmbedding and BasisEmbedding templates. (#441) (#439)

  • Codeblocks in the documentation now have a 'copy' button for easily copying examples. (#437)

Documentation

  • Update the developers plugin guide to use QubitDevice. (#483)

Bug fixes

  • Fixed a bug in CVQNode._pd_analytic, where non-descendant observables were not Heisenberg-transformed before evaluating the partial derivatives when using the order-2 parameter-shift method, resulting in an erroneous Jacobian for some circuits. (#433)

Contributors

This release contains contributions from (in alphabetical order):

Juan Miguel Arrazola, Ville Bergholm, Alain Delgado Gran, Olivia Di Matteo, Theodor Isacsson, Josh Izaac, Soran Jahangiri, Nathan Killoran, Johannes Jakob Meyer, Zeyue Niu, Maria Schuld, Antal Száva.