Skip to content

Release 0.34.0

Compare
Choose a tag to compare
@lillian542 lillian542 released this 08 Jan 20:06
1a315b9

New features since last release

Statistics and drawing for mid-circuit measurements 🎨

  • It is now possible to return statistics of composite mid-circuit measurements. (#4888)

    Mid-circuit measurement results can be composed using basic arithmetic operations and then statistics can be calculated by putting the result within a PennyLane measurement like qml.expval(). For example:

    import pennylane as qml
    
    dev = qml.device("default.qubit")
    
    @qml.qnode(dev) 
    def circuit(phi, theta): 
        qml.RX(phi, wires=0) 
        m0 = qml.measure(wires=0) 
        qml.RY(theta, wires=1) 
        m1 = qml.measure(wires=1) 
        return qml.expval(~m0 + m1)
    
    print(circuit(1.23, 4.56)) 
    1.2430187928114291 
    

    Another option, for ease-of-use when using qml.sample(), qml.probs(), or qml.counts(), is to provide a simple list of mid-circuit measurement results:

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev) 
    def circuit(phi, theta): 
        qml.RX(phi, wires=0) 
        m0 = qml.measure(wires=0) 
        qml.RY(theta, wires=1) 
        m1 = qml.measure(wires=1) 
        return qml.sample(op=[m0, m1])
    
    print(circuit(1.23, 4.56, shots=5)) 
    [[0 1] 
     [0 1] 
     [0 0] 
     [1 0] 
     [0 1]] 
    

    Composite mid-circuit measurement statistics are supported on default.qubit and default.mixed. To learn more about which measurements and arithmetic operators are supported, refer to the measurements page and the documentation for qml.measure.

  • Mid-circuit measurements can now be visualized with the text-based qml.draw() and the graphical qml.draw_mpl() methods. (#4775) (#4803) (#4832) (#4901) (#4850) (#4917) (#4930) (#4957)

    Drawing of mid-circuit measurement capabilities including qubit reuse and reset, postselection, conditioning, and collecting statistics is now supported. Here is an all-encompassing example:

    def circuit(): 
        m0 = qml.measure(0, reset=True) 
        m1 = qml.measure(1, postselect=1) 
        qml.cond(m0 - m1 == 0, qml.S)(0) 
        m2 = qml.measure(1) 
        qml.cond(m0 + m1 == 2, qml.T)(0) 
        qml.cond(m2, qml.PauliX)(1) 

    The text-based drawer outputs:

    >>> print(qml.draw(circuit)()) 
    0: ──┤↗│  │0⟩────────S───────T────┤ 
    1: ───║────────┤↗₁├──║──┤↗├──║──X─┤ 
          ╚═════════║════╬═══║═══╣  ║ 
                    ╚════╩═══║═══╝  ║ 
                             ╚══════╝ 

    The graphical drawer outputs:

    >>> print(qml.draw_mpl(circuit)()) 

Catalyst is seamlessly integrated with PennyLane ⚗️

  • Catalyst, our next-generation compilation framework, is now accessible within PennyLane, allowing you to more easily benefit from hybrid just-in-time (JIT) compilation.

    To access these features, simply install pennylane-catalyst:

    pip install pennylane-catalyst 
    

    The qml.compiler module provides support for hybrid quantum-classical compilation. (#4692) (#4979)

    Through the use of the qml.qjit decorator, entire workflows can be JIT compiled — including both quantum and classical processing — down to a machine binary on first-function execution. Subsequent calls to the compiled function will execute the previously-compiled binary, resulting in significant performance improvements.

    import pennylane as qml
    
    dev = qml.device("lightning.qubit", wires=2)
    
    @qml.qjit 
    @qml.qnode(dev) 
    def circuit(theta): 
        qml.Hadamard(wires=0) 
        qml.RX(theta, wires=1) 
        qml.CNOT(wires=[0,1]) 
        return qml.expval(qml.PauliZ(wires=1)) 
    >>> circuit(0.5) # the first call, compilation occurs here array(0.) 
    >>> circuit(0.5) # the precompiled quantum function is called 
    array(0.) 

    Currently, PennyLane supports the Catalyst hybrid compiler with the qml.qjit decorator. A significant benefit of Catalyst is the ability to preserve complex control flow around quantum operations — such as if statements and for loops, and including measurement feedback — during compilation, while continuing to support end-to-end autodifferentiation.

  • The following functions can now be used with the qml.qjit decorator: qml.grad, qml.jacobian, qml.vjp, qml.jvp, and qml.adjoint. (#4709) (#4724) (#4725) (#4726)

    When qml.grad or qml.jacobian are used with @qml.qjit, they are patched to catalyst.grad and catalyst.jacobian, respectively.

    dev = qml.device("lightning.qubit", wires=1)
    
    @qml.qjit 
    def workflow(x):
    
        @qml.qnode(dev) 
        def circuit(x): 
            qml.RX(np.pi * x[0], wires=0) 
            qml.RY(x[1], wires=0) 
            return qml.probs()
    
        g = qml.jacobian(circuit)
    
        return g(x) 
    >>> workflow(np.array([2.0, 1.0])) 
    array([[ 3.48786850e-16, -4.20735492e-01], 
           [-8.71967125e-17, 4.20735492e-01]]) 
  • JIT-compatible functionality for control flow has been added via qml.for_loop, qml.while_loop, and qml.cond. (#4698)

    qml.for_loop and qml.while_loop can be deployed as decorators on functions that are the body of the loop. The arguments to both follow typical conventions:

    @qml.for_loop(lower_bound, upper_bound, step) 
    
    @qml.while_loop(cond_function) 
    

    Here is a concrete example with qml.for_loop:

    dev = qml.device("lightning.qubit", wires=1)
    
    @qml.qjit 
    @qml.qnode(dev) 
    def circuit(n: int, x: float):
    
        @qml.for_loop(0, n, 1) 
        def loop_rx(i, x): 
            # perform some work and update (some of) the arguments 
            qml.RX(x, wires=0)
    
            # update the value of x for the next iteration 
            return jnp.sin(x)
    
        # apply the for loop 
        final_x = loop_rx(x)
    
        return qml.expval(qml.PauliZ(0)), final_x 
    >>> circuit(7, 1.6) 
    (array(0.97926626), array(0.55395718)) 

Decompose circuits into the Clifford+T gateset 🧩

  • The new qml.clifford_t_decomposition() transform provides an approximate breakdown of an input circuit into the Clifford+T gateset. Behind the scenes, this decomposition is enacted via the sk_decomposition() function using the Solovay-Kitaev algorithm. (#4801) (#4802)

    The Solovay-Kitaev algorithm approximately decomposes a quantum circuit into the Clifford+T gateset. To account for this, a desired total circuit decomposition error, epsilon, must be specified when using qml.clifford_t_decomposition:

    dev = qml.device("default.qubit")
    
    @qml.qnode(dev) 
    def circuit(): 
        qml.RX(1.1, 0) 
        return qml.state()
    
    circuit = qml.clifford_t_decomposition(circuit, epsilon=0.1) 
    >>> print(qml.draw(circuit)()) 
    0:   ──T†──H──T†──H──T──H──T──H──T──H──T──H──T†──H──T†──T†──H──T†──H──T──H──T──H──T──H──T──H──T†──H
    
     ───T†──H──T──H──GlobalPhase(0.39)─┤ 

    The resource requirements of this circuit can also be evaluated:

    >>> with qml.Tracker(dev) as tracker: 
    ...     circuit() 
    >>> resources_lst = tracker.history["resources"] 
    >>> resources_lst[0] 
    wires: 1 
    gates: 34 
    depth: 34 
    shots: Shots(total=None) 
    gate_types: {'Adjoint(T)': 8, 'Hadamard': 16, 'T': 9, 'GlobalPhase': 1} 
    gate_sizes:   {1: 33, 0: 1} 

Use an iterative approach for quantum phase estimation 🔄

  • Iterative Quantum Phase Estimation is now available with qml.iterative_qpe. (#4804)

    The subroutine can be used similarly to mid-circuit measurements:

    import pennylane as qml
    
    dev = qml.device("default.qubit", shots=5)
    
    @qml.qnode(dev) 
    def circuit():
    
          # Initial state 
          qml.PauliX(wires=[0])
    
          # Iterative QPE 
          measurements = qml.iterative_qpe(qml.RZ(2., wires=[0]), ancilla=[1], iters=3)
    
          return [qml.sample(op=meas) for meas in measurements] 
    >>> print(circuit()) 
    [array([0, 0, 0, 0, 0]), array([1, 0, 0, 0, 0]), array([0, 1, 1, 1, 1])] 

    The $i$-th element in the list refers to the 5 samples generated by the $i$-th measurement of the algorithm.

Improvements 🛠

Community contributions 🥳

  • The += operand can now be used with a PauliSentence, which has also provides a performance boost. (#4662)

  • The Approximate Quantum Fourier Transform (AQFT) is now available with qml.AQFT. (#4715)

  • qml.draw and qml.draw_mpl now render operator IDs. (#4749)

    The ID can be specified as a keyword argument when instantiating an operator:

    >>> def circuit(): 
    ...    qml.RX(0.123, id="data", wires=0) 
    >>> print(qml.draw(circuit)()) 
    0: ──RX(0.12,"data")─┤ 
  • Non-parametric operators such as Barrier, Snapshot, and Wirecut have been grouped together and moved to pennylane/ops/meta.py. Additionally, the relevant tests have been organized and placed in a new file, tests/ops/test_meta.py. (#4789)

  • The TRX, TRY, and TRZ operators are now differentiable via backpropagation on default.qutrit. (#4790)

  • The function qml.equal now supports ControlledSequence operators. (#4829)

  • XZX decomposition has been added to the list of supported single-qubit unitary decompositions. (#4862)

  • == and != operands can now be used with TransformProgram and TransformContainers instances. (#4858)

  • A qutrit_mixed module has been added to qml.devices to store helper functions for a future qutrit mixed-state device. A function called create_initial_state has been added to this module that creates device-compatible initial states. (#4861)

  • The function qml.Snapshot now supports arbitrary state-based measurements (i.e., measurements of type StateMeasurement). (#4876)

  • qml.equal now supports the comparison of QuantumScript and BasisRotation objects. (#4902) (#4919)

  • The function qml.draw_mpl now accept a keyword argument fig to specify the output figure window. (#4956)

Better support for batching

  • qml.AmplitudeEmbedding now supports batching when used with Tensorflow. (#4818)

  • default.qubit can now evolve already batched states with qml.pulse.ParametrizedEvolution. (#4863)

  • qml.ArbitraryUnitary now supports batching. (#4745)

  • Operator and tape batch sizes are evaluated lazily, helping run expensive computations less frequently and an issue with Tensorflow pre-computing batch sizes. (#4911)

Performance improvements and benchmarking

  • Autograd, PyTorch, and JAX can now use vector-Jacobian products (VJPs) provided by the device from the new device API. If a device provides a VJP, this can be selected by providing device_vjp=True to a QNode or qml.execute. (#4935) (#4557) (#4654) (#4878) (#4841)

    >>> dev = qml.device('default.qubit') 
    >>> @qml.qnode(dev, diff_method="adjoint", device_vjp=True) 
    >>> def circuit(x): 
    ...    qml.RX(x, wires=0) 
    ...    return qml.expval(qml.PauliZ(0)) 
    >>> with dev.tracker: 
    ...    g = qml.grad(circuit)(qml.numpy.array(0.1)) 
    >>> dev.tracker.totals 
    {'batches': 1, 'simulations': 1, 'executions': 1, 'vjp_batches': 1, 'vjps': 1} 
    >>> g 
    -0.09983341664682815 
  • qml.expval with large Hamiltonian objects is now faster and has a significantly lower memory footprint (and constant with respect to the number of Hamiltonian terms) when the Hamiltonian is a PauliSentence. This is due to the introduction of a specialized dot method in the PauliSentence class which performs PauliSentence-state products. (#4839)

  • default.qubit no longer uses a dense matrix for MultiControlledX for more than 8 operation wires. (#4673)

  • Some relevant Pytests have been updated to enable its use as a suite of benchmarks. (#4703)

  • default.qubit now applies GroverOperator faster by not using its matrix representation but a custom rule for apply_operation. Also, the matrix representation of GroverOperator now runs faster. (#4666)

  • A new pipeline to run benchmarks and plot graphs comparing with a fixed reference has been added. This pipeline will run on a schedule and can be activated on a PR with the label ci:run_benchmarks. (#4741)

  • default.qubit now supports adjoint differentiation for arbitrary diagonal state-based measurements. (#4865)

  • The benchmarks pipeline has been expanded to export all benchmark data to a single JSON file and a CSV file with runtimes. This includes all references and local benchmarks. (#4873)

Final phase of updates to transforms

  • qml.quantum_monte_carlo and qml.simplify now use the new transform system. (#4708) (#4949)

  • The formal requirement that type hinting be provided when using the qml.transform decorator has been removed. Type hinting can still be used, but is now optional. Please use a type checker such as mypy if you wish to ensure types are being passed correctly. (#4942)

Other improvements

  • PennyLane now supports Python 3.12. (#4985)

  • SampleMeasurement now has an optional method process_counts for computing the measurement results from a counts dictionary. (#4941)

  • A new function called ops.functions.assert_valid has been added for checking if an Operator class is defined correctly. (#4764)

  • Shots objects can now be multiplied by scalar values. (#4913)

  • GlobalPhase now decomposes to nothing in case devices do not support global phases. (#4855)

  • Custom operations can now provide their matrix directly through the Operator.matrix() method without needing to update the has_matrix property. has_matrix will now automatically be True if Operator.matrix is overridden, even if Operator.compute_matrix is not. (#4844)

  • The logic for re-arranging states before returning them has been improved. (#4817)

  • When multiplying SparseHamiltonians by a scalar value, the result now stays as a SparseHamiltonian. (#4828)

  • trainable_params can now be set upon initialization of a QuantumScript instead of having to set the parameter after initialization. (#4877)

  • default.qubit now calculates the expectation value of Hermitian operators in a differentiable manner. (#4866)

  • The rot decomposition now has support for returning a global phase. (#4869)

  • The "pennylane_sketch" MPL-drawer style has been added. This is the same as the "pennylane" style, but with sketch-style lines. (#4880)

  • Operators now define a pauli_rep property, an instance of PauliSentence, defaulting to None if the operator has not defined it (or has no definition in the Pauli basis). (#4915)

  • qml.ShotAdaptiveOptimizer can now use a multinomial distribution for spreading shots across the terms of a Hamiltonian measured in a QNode. Note that this is equivalent to what can be done with qml.ExpvalCost, but this is the preferred method because ExpvalCost is deprecated. (#4896)

  • Decomposition of qml.PhaseShift now uses qml.GlobalPhase for retaining the global phase information. (#4657) (#4947)

  • qml.equal for Controlled operators no longer returns False when equivalent but differently-ordered sets of control wires and control values are compared. (#4944)

  • All PennyLane Operator subclasses are automatically tested by ops.functions.assert_valid to ensure that they follow PennyLane Operator standards. (#4922)

  • Probability measurements can now be calculated from a counts dictionary with the addition of a process_counts method in the ProbabilityMP class. (#4952)

  • ClassicalShadow.entropy now uses the algorithm outlined in 1106.5458 to project the approximate density matrix (with potentially negative eigenvalues) onto the closest valid density matrix. (#4959)

  • The ControlledSequence.compute_decomposition default now decomposes the Pow operators, improving compatibility with machine learning interfaces. (#4995)

Breaking changes 💔

  • The functions qml.transforms.one_qubit_decomposition, qml.transforms.two_qubit_decomposition, and qml.transforms.sk_decomposition were moved to qml.ops.one_qubit_decomposition, qml.ops.two_qubit_decomposition, and qml.ops.sk_decomposition, respectively. (#4906)

  • The function qml.transforms.classical_jacobian has been moved to the gradients module and is now accessible as qml.gradients.classical_jacobian. (#4900)

  • The transforms submodule qml.transforms.qcut is now its own module: qml.qcut. (#4819)

  • The decomposition of GroverOperator now has an additional global phase operation. (#4666)

  • qml.cond and the Conditional operation have been moved from the transforms folder to the ops/op_math folder. qml.transforms.Conditional will now be available as qml.ops.Conditional. (#4860)

  • The prep keyword argument has been removed from QuantumScript and QuantumTape. StatePrepBase operations should be placed at the beginning of the ops list instead. (#4756)

  • qml.gradients.pulse_generator is now named qml.gradients.pulse_odegen to adhere to paper naming conventions. (#4769)

  • Specifying control_values passed to qml.ctrl as a string is no longer supported. (#4816)

  • The rot decomposition will now normalize its rotation angles to the range [0, 4pi] for consistency (#4869)

  • QuantumScript.graph is now built using tape.measurements instead of tape.observables because it depended on the now-deprecated Observable.return_type property. (#4762)

  • The "pennylane" MPL-drawer style now draws straight lines instead of sketch-style lines. (#4880)

  • The default value for the term_sampling argument of ShotAdaptiveOptimizer is now None instead of "weighted_random_sampling". (#4896)

Deprecations 👋

  • single_tape_transform, batch_transform, qfunc_transform, and op_transform are deprecated. Use the new qml.transform function instead. (#4774)

  • Observable.return_type is deprecated. Instead, you should inspect the type of the surrounding measurement process. (#4762) (#4798)

  • All deprecations now raise a qml.PennyLaneDeprecationWarning instead of a UserWarning. (#4814)

  • QuantumScript.is_sampled and QuantumScript.all_sampled are deprecated. Users should now validate these properties manually. (#4773)

  • With an algorithmic improvement to ClassicalShadow.entropy, the keyword atol becomes obsolete and will be removed in v0.35. (#4959)

Documentation 📝

  • Documentation for unitaries and operations' decompositions has been moved from qml.transforms to qml.ops.ops_math. (#4906)

  • Documentation for qml.metric_tensor and qml.adjoint_metric_tensor and qml.transforms.classical_jacobian is now accessible via the gradients API page qml.gradients in the documentation. (#4900)

  • Documentation for qml.specs has been moved to the resource module. (#4904)

  • Documentation for QCut has been moved to its own API page: qml.qcut. (#4819)

  • The documentation page for qml.measurements now links top-level accessible functions (e.g., qml.expval) to their top-level pages rather than their module-level pages (e.g., qml.measurements.expval). (#4750)

  • Information for the documentation of qml.matrix about wire ordering has been added for using qml.matrix on a QNode which uses a device with device.wires=None. (#4874)

Bug fixes 🐛

  • TransformDispatcher now stops queuing when performing the transform when applying it to a qfunc. Only the output of the transform will be queued. (#4983)

  • qml.map_wires now works properly with qml.cond and qml.measure. (#4884)

  • Pow operators are now picklable. (#4966)

  • Finite differences and SPSA can now be used with tensorflow-autograph on setups that were seeing a bus error. (#4961)

  • qml.cond no longer incorrectly queues operators used arguments. (#4948)

  • Attribute objects now return False instead of raising a TypeError when checking if an object is inside the set. (#4933)

  • Fixed a bug where the parameter-shift rule of qml.ctrl(op) was wrong if op had a generator that has two or more eigenvalues and is stored as a SparseHamiltonian. (#4899)

  • Fixed a bug where trainable parameters in the post-processing of finite-differences were incorrect for JAX when applying the transform directly on a QNode. (#4879)

  • qml.grad and qml.jacobian now explicitly raise errors if trainable parameters are integers. (#4836)

  • JAX-JIT now works with shot vectors. (#4772)

  • JAX can now differentiate a batch of circuits where one tape does not have trainable parameters. (#4837)

  • The decomposition of GroverOperator now has the same global phase as its matrix. (#4666)

  • The tape.to_openqasm method no longer mistakenly includes interface information in the parameter string when converting tapes using non-NumPy interfaces. (#4849)

  • qml.defer_measurements now correctly transforms circuits when terminal measurements include wires used in mid-circuit measurements. (#4787)

  • Fixed a bug where the adjoint differentiation method would fail if an operation that has a parameter with grad_method=None is present. (#4820)

  • MottonenStatePreparation and BasisStatePreparation now raise an error when decomposing a broadcasted state vector. (#4767)

  • Gradient transforms now work with overridden shot vectors and default.qubit. (#4795)

  • Any ScalarSymbolicOp, like Evolution, now states that it has a matrix if the target is a Hamiltonian. (#4768)

  • In default.qubit, initial states are now initialized with the simulator's wire order, not the circuit's wire order. (#4781)

  • qml.compile will now always decompose to expand_depth, even if a target basis set is not specified. (#4800)

  • qml.transforms.transpile can now handle measurements that are broadcasted onto all wires. (#4793)

  • Parametrized circuits whose operators do not act on all wires return PennyLane tensors instead of NumPy arrays, as expected. (#4811) (#4817)

  • qml.transforms.merge_amplitude_embedding no longer depends on queuing, allowing it to work as expected with QNodes. (#4831)

  • qml.pow(op) and qml.QubitUnitary.pow() now also work with Tensorflow data raised to an integer power. (#4827)

  • The text drawer has been fixed to correctly label qml.qinfo measurements, as well as qml.classical_shadow qml.shadow_expval. (#4803)

  • Removed an implicit assumption that an empty PauliSentence gets treated as identity under multiplication. (#4887)

  • Using a CNOT or PauliZ operation with large batched states and the Tensorflow interface no longer raises an unexpected error. (#4889)

  • qml.map_wires no longer fails when mapping nested quantum tapes. (#4901)

  • Conversion of circuits to openqasm now decomposes to a depth of 10, allowing support for operators requiring more than 2 iterations of decomposition, such as the ApproxTimeEvolution gate. (#4951)

  • MPLDrawer does not add the bonus space for classical wires when no classical wires are present. (#4987)

  • Projector now works with parameter-broadcasting. (#4993) * The jax-jit interface can now be used with float32 mode. (#4990)

  • Keras models with a qnn.KerasLayer no longer fail to save and load weights properly when they are named "weights". (#5008)

Contributors ✍️

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Ali Asadi, Utkarsh Azad, Gabriel Bottrill, Thomas Bromley, Astral Cai, Minh Chau, Isaac De Vlugt, Amintor Dusko, Pieter Eendebak, Lillian Frederiksen, Pietropaolo Frisoni, Josh Izaac, Juan Giraldo, Emiliano Godinez Ramirez, Ankit Khandelwal, Korbinian Kottmann, Christina Lee, Vincent Michaud-Rioux, Anurav Modak, Romain Moyard, Mudit Pandey, Matthew Silverman, Jay Soni, David Wierichs, Justin Woodring.