github PennyLaneAI/pennylane v0.45.0
Release 0.45.0

2 hours ago

New features since last release

Sum of Slaters State Preparation 🆘

  • A new state preparation method called SumOfSlatersPrep is now available. This state preparation routine introduced in Fomichev et al., PRX Quantum 5, 040339 (see the associated demo) is a state-of-the-art technique for algorithms that require a high-quality initial state, like in ground-state energy estimation algorithms of chemical systems. (#8964) (#8997) (#9228) (#9323)

    Consider this sparse state on three qubits, specified by normalized coefficients and statevector indices pointing to the populated computational basis states:

    import pennylane as qp
    import numpy as np
    
    coefficients = np.array([1, -1j, 1j, 1]) / np.sqrt(4)
    indices = (0, 1, 2, 4)
    num_wires = 3
    wires = list(range(num_wires))

    The SumOfSlatersPrep operation requires the coefficients, indices, and wires as input. It efficiently prepares sparse states using auxiliary wires, QROM\ s, reversible bit encodings, and a dense-state preparation on a smaller subspace. Its implementation can be inspected by using PennyLane's graph-based decomposition algorithm (enable_graph) with the following gate set and five auxiliary wires (work_wires).

    qp.decomposition.enable_graph()
    
    gate_set = {"QROM", "TemporaryAND", "Adjoint(TemporaryAND)", "StatePrep", "CNOT", "X"}
    
    num_work_wires = 5
    
    @qp.decompose(gate_set=gate_set, num_work_wires=num_work_wires)
    @qp.qnode(qp.device("lightning.qubit", wires=num_work_wires+num_wires))
    def circuit():
        qp.SumOfSlatersPrep(coefficients, wires, indices)
        return qp.state()
    >>> print(qp.draw(circuit, show_matrices=False, max_length=180)())
                0: ────────────────╭QROM(M0)──X─╭●────────────●╮────╭●────────────●╮──X─╭●───────────────●╮─────────────┤  State
                1: ────────────────├QROM(M0)──X─├●────────────●┤──X─├●────────────●┤──X─├●───────────────●┤──X──────────┤  State
                2: ────────────────├QROM(M0)────│──╭●─────●╮───│──X─│──╭●─────●╮───│────│──╭●────────●╮───│──X──────────┤  State
    <DynamicWire>: ─╭Allocate─╭|Ψ⟩─├QROM(M0)────│──│───────│───│────│──│──╭X───│───│────│──│──╭X──────│───│─╭Deallocate─┤  State
    <DynamicWire>: ─├Allocate─╰|Ψ⟩─├QROM(M0)────│──│──╭X───│───│────│──│──│────│───│────│──│──│──╭X───│───│─├Deallocate─┤  State
    <DynamicWire>: ─├Allocate──────╰QROM(M0)────│──│──│────│───│────│──│──│────│───│────│──│──│──│────│───│─├Deallocate─┤  State
    <DynamicWire>: ─├Allocate───────────────────╰⊕─├●─│───●┤──⊕╯────╰⊕─├●─│───●┤──⊕╯────╰⊕─├●─│──│───●┤──⊕╯─├Deallocate─┤  State
    <DynamicWire>: ─╰Allocate──────────────────────╰⊕─╰●──⊕╯───────────╰⊕─╰●──⊕╯───────────╰⊕─╰●─╰●──⊕╯─────╰Deallocate─┤  State

    The smaller dense state preparation is represented by |Ψ⟩, which is on two wires, and the <DynamicWire> labels represent dynamically allocated wires (with allocate) used in its decomposition.

Workflow Inspection 🔍

  • When using specs with qjit and level="all", you can now display the returned CircuitSpecs as a table, allowing you to easily see how circuit resources evolve with each stage of compilation. In addition, specs now supports setting level="user" for workflows compiled with qjit, returning circuit specifications after all user-specified transforms have been applied. (#9088) (#9307) (#9426)

    @qp.qjit
    @qp.transforms.merge_rotations
    @qp.transforms.cancel_inverses
    @qp.qnode(qp.device("lightning.qubit", wires=2))
    def circuit():
        qp.RX(1.23,0)
        qp.RX(1.23,0)
        qp.X(0)
        qp.H(0)
        qp.H(0)
        return qp.probs()
    >>> print(qp.specs(circuit, level="all")())
    Device: lightning.qubit
    Device wires: 2
    Shots: Shots(total=None)
    Levels:
    - 0: Before MLIR Passes
    - 1: cancel-inverses
    - 2: merge-rotations
    <BLANKLINE>
    ↓Metric     Level→ |  0 |  1 |  2
    ---------------------------------
    Wire allocations   |  2 |  2 |  2
    Total gates        |  5 |  3 |  2
    Gate counts:       |
    - Hadamard         |  2 |  0 |  0
    - PauliX           |  1 |  1 |  1
    - RX               |  2 |  2 |  1
    Measurements:      |
    - probs(all wires) |  1 |  1 |  1

    We can also observe the specifications after all user-applied transforms using level="user":

    >>> print(qp.specs(circuit, level="user")())
    Device: lightning.qubit
    Device wires: 2
    Shots: Shots(total=None)
    Level: merge-rotations
    
    Wire allocations: 2
    Total gates: 2
    Gate counts:
    - PauliX: 1
    - RX: 1
    Measurements:
    - probs(all wires): 1
    Depth: Not computed
  • When inspecting a circuit via the level argument in specs or draw, markers placed in a CompilePipeline (with marker) are now accessible exclusively via their label, making it much easier to track levels of compilation without having to track shifting integer level values. In addition, markers can now be added directly to a CompilePipeline with the add_marker method, and printing a CompilePipeline now legibly distinguishes transforms and markers. (#8990) (#9007) (#9076) (#9102)

    pipeline = qp.CompilePipeline()
    pipeline.add_marker("no-transforms")
    pipeline += qp.transforms.cancel_inverses
    
    @qp.marker("after-cancel-inverses")
    @pipeline
    @qp.qnode(qp.device("default.qubit"))
    def circuit():
      qp.X(0)
      qp.H(0)
      qp.H(0)
      return qp.probs()

    The new string representation of CompilePipeline allows you to inspect the transforms and markers:

    >>> print(circuit.compile_pipeline)
    CompilePipeline(
       ├─▶ no-transforms
      [1] cancel_inverses()
       └─▶ after-cancel-inverses
    )

    As usual, marker labels can be used as an argument to level in specs and draw, showing the cumulative result of compilation up to the provided marker:

    >>> print(qp.draw(circuit, level="no-transforms")()) # or level=0
    0: ──X──H──H─┤  Probs
    >>> print(qp.draw(circuit, level="after-cancel-inverses")()) # or level=1
    0: ──X─┤  Probs
  • specs now includes PPR and PPM weights in its output, allowing for better categorization of PPMs and PPRs in workflows compiled with qjit. (#8983)

    @qp.qjit
    @qp.transforms.to_ppr
    @qp.qnode(qp.device("null.qubit", wires=2))
    def circuit():
        qp.H(0)
        qp.CNOT([0, 1])
        m = qp.measure(0)
        qp.T(0)
        return qp.expval(qp.Z(0))
    >>> print(qp.specs(circuit, level="user")())
    Device: null.qubit
    Device wires: 2
    Shots: Shots(total=None)
    Level: to-ppr
    <BLANKLINE>
    Wire allocations: 2
    Total gates: 11
    Gate counts:
    - GlobalPhase: 3
    - PPM-w1: 1
    - PPR-pi/4-w1: 5
    - PPR-pi/4-w2: 1
    - PPR-pi/8-w1: 1
    Measurements:
    - expval(PauliZ): 1
    Depth: Not computed
    
  • specs has been upgraded with significantly faster processing of large workflows with many gates and/or measurements for qjit compiled workflows in pass-by-pass mode. This is achieved using Catalyst's ResourceAnalysis pass behind the scenes, improving upon the former implementation. For more details, check out the Catalyst v0.15 release notes. (#9279)

  • specs now returns measurement information for qjit workloads when using level="device". (#8988)

  • When using pass-by-pass specs with Catalyst, the output will no longer display a "Before Tape Transforms" level if no tape transforms have been applied. In particular, for scenarios where no tape transforms are present, the "Before MLIR passes" level becomes level 0. In scenarios with at least one tape transform, level 0 corresponds to "Before Tape Transforms" and "Before MLIR passes" is the level after all tape transforms but before the first MLIR pass. (#9091) (#9166)

QSVT Angle Solver 📐

  • A new angle solver has been added to find QSVT phase angles faster for large-degree polynomials. This can be accessed by setting angle_solver="iterative-optax" in qsvt and poly_to_angles, where the benefits are seen when when repeatedly evaluating the same-degree polynomial with different coefficients. Note that this requires optax to be installed. (#8685) (#9435)

    poly = np.array([0, 1.0, 0, -1/2, 0, 1/3])
    qsvt_angles = qp.poly_to_angles(poly, "QSVT", angle_solver="iterative-optax")
    >>> print(qsvt_angles)
    [-4.74724627  1.51868559  0.57952342  0.57952342  1.51868559 -0.03485729]

Decomposition Inspection and Pre-defined Gate Sets 📠

New tools dedicated to accessible inspectability of PennyLane's graph-based decomposition system (enabled with enable_graph) are now available! With this release, you can query the solutions of the graph-based system to understand how PennyLane decomposed a circuit, why specific rules where chosen over others, and more.

  • It is now possible to assign custom names to decomposition rules using the name argument in qp.register_resources, making it easier to identify specific decomposition rules. (#9257)

    import pennylane as qp
    
    qp.decomposition.enable_graph()
    
    @qp.register_resources({qp.CNOT: 1, qp.H: 2}, name='my_cz_rule')
    def cz_to_h_cnot(wires):
        qp.H(wires[1])
        qp.CNOT(wires)
        qp.H(wires[1])
    ``` ```pycon
    >>> cz_to_h_cnot.name
    'my_cz_rule'
  • A new function called inspect_decomps allows for the visualization and inspection of all possible decomposition paths the graph system can take for a concrete operator instance. (#9322) (#9359) (#9427)

    For each decomposition rule applicable to the operator instance, the output includes its name, circuit diagram, gate count, and wire allocation (if any):

    >>> qp.inspect_decomps(qp.CRX(0.5, wires=[0, 1]))
    Decomposition 0 (name: _crx_to_rx_cz)
    0: ───────────╭●────────────╭●─┤
    1: ──RX(0.25)─╰Z──RX(-0.25)─╰Z─┤
    Gate Count: {RX: 2, CZ: 2}
    
    Decomposition 1 (name: _crx_to_rz_ry)
    0: ─────────────────────╭●────────────╭●────────────┤
    1: ──RZ(1.57)──RY(0.25)─╰X──RY(-0.25)─╰X──RZ(-1.57)─┤
    Gate Count: {RZ: 2, RY: 2, CNOT: 2}
    
    Decomposition 2 (name: _crx_to_h_crz)
    0: ────╭●───────────┤
    1: ──H─╰RZ(0.50)──H─┤
    Gate Count: {Hadamard: 2, CRZ: 1}
    
    Decomposition 3 (name: _crx_to_ppr)
    0: ───────────╭RZX(-0.25)─┤
    1: ──RX(0.25)─╰RZX(-0.25)─┤
    Gate Count: {PauliRot(pauli_word=ZX): 1, PauliRot(pauli_word=X): 1}

    By default, inspect_decomps displays all available decomposition rules for an operator. Alternatively, a single decomposition rule can be inspected by passing its name:

    >>> qp.inspect_decomps(qp.CRX(0.5, wires=[0, 1]), "_crx_to_h_crz")
    Decomposition 0 (name: _crx_to_h_crz)
    0: ────╭●───────────┤
    1: ──H─╰RZ(0.50)──H─┤
    Gate Count: {Hadamard: 2, CRZ: 1}
  • A new function called decomp_inspector is available for verifying how the decomposition graph chooses decomposition rules for each operator instance in a circuit. (#9359) (#9436)

    The decomp_inspector acts as a transform that can be applied on a QNode as a decorator. It returns an object that allows for interactively querying a given operator to identify which decomposition rules were considered and which one was chosen.

    Consider the following example where we want to efficiently decompose a MultiRZ into single-qubit rotations and CNOTs:

    qp.decomposition.enable_graph()
    
    gate_sets = {"CNOT", "RX", "RY", "RZ", "Identity", "GlobalPhase", "MidMeasureMP"}
    
    @qp.decomp_inspector(gate_set=gate_sets, num_work_wires=2)
    @qp.qnode(qp.device("default.qubit"))
    def circuit():
        qp.ctrl(qp.MultiRZ(0.5, [0, 1]), control=[3, 4, 5])
        return qp.probs()
    
    inspector = circuit()

    We can then call the inspector's inspect_decomps method and provide the MultiRZ instance of interest to see which decomposition rules were considered.

    >>> inspector.inspect_decomps(qp.ctrl(qp.MultiRZ(0.5, [0, 1]), control=[3, 4, 5]), num_work_wires=2)
    CHOSEN: Decomposition 0 (name: flip_zero_ctrl_values(_ctrl_single_work_wire))
    <DynamicWire>: ──Allocate─╭X─╭●─────────────╭X──Deallocate─┤
                3: ───────────├●─│──────────────├●─────────────┤
                4: ───────────├●─│──────────────├●─────────────┤
                5: ───────────╰●─│──────────────╰●─────────────┤
                0: ──────────────├MultiRZ(0.50)────────────────┤
                1: ──────────────╰MultiRZ(0.50)────────────────┤
    First-Level Expansion Gates: {MultiControlledX(num_control_wires=3, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 2, Controlled(MultiRZ(num_wires=2), num_control_wires=1, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 1}
    Wire Allocations: {'zero': 1}
    Full Expansion Gates: {RZ: 58, CNOT: 34, GlobalPhase: 64, RY: 18, RX: 8, MidMeasure: 2}
    Weighted Cost: 120.0
    
    Decomposition 1 (name: to_controlled_qubit_unitary)
    Not applicable (provided operator instance does not meet all conditions for this rule).
    
    Decomposition 2 (name: controlled(_multi_rz_decomposition))
    0: ─╭X─╭RZ(0.50)─╭X─┤
    1: ─├●─│─────────├●─┤
    3: ─├●─├●────────├●─┤
    4: ─├●─├●────────├●─┤
    5: ─╰●─╰●────────╰●─┤
    First-Level Expansion Gates: {Controlled(RZ, num_control_wires=3, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 1, MultiControlledX(num_control_wires=4, num_work_wires=0, num_zero_control_values=0, work_wire_type=borrowed): 2}
    Full Expansion Gates: {GlobalPhase: 76, RX: 16, MidMeasure: 4, RY: 24, RZ: 80, CNOT: 72}
    Weighted Cost: 196.0

    For each decomposition rule applicable to the controlled MultiRZ operator instance, the inspector provides a summary of its weighted cost, wire allocations, and the "Full Expansion" (the final gate counts produced after decomposing all the way down to the target gate set).

    Similar to the qp.decompose transform, the decomp_inspector provides the ability to inject new decomposition rules via the keyword arguments fixed_decomps and alt_decomps. For more details on the inspection capabilities please consult the documentation for decomp_inspector.

  • The list_decomps function now returns an object that is easier to interact with, including better legibility when printing the entire set of available decomposition rules and when printing individual ones. Additionally, the object returned supports accessing a specific rule by index or by name. (#9260)

    >>> collection = qp.list_decomps(qp.CRX)
    >>> print(collection)
    Available Decomposition Rules:
    0: _crx_to_rx_cz
    1: _crx_to_rz_ry
    2: _crx_to_h_crz
    3: _crx_to_ppr
    >>> collection[0]
    DecompositionRule(name=_crx_to_rx_cz)
    >>> collection['_crx_to_ppr']
    DecompositionRule(name=_crx_to_ppr)
    >>> print(qp.draw(collection[0])(0.5, wires=[0, 1]))
    0: ───────────╭●────────────╭●─┤
    1: ──RX(0.25)─╰Z──RX(-0.25)─╰Z─┤
  • A new gate_sets module contains pre-defined gate sets that can be plugged into the gate_set argument of the decompose transform. These pre-defined gate sets can be easily accessed and integrated into decompositions workflows. Key gate sets include: (#8915) (#9045) (#9259) (#9417)

    • qp.gate_sets.CLIFFORD_T which contains the Clifford+T gate set and qp.gate_sets.CLIFFORD_T_PLUS_RZ with an additional RZ gate. - qp.gate_sets.ROTATIONS_PLUS_CNOT which contains single-qubit rotations and CNOT. - qp.gate_sets.IDENTITY which contains the Identity and the GlobalPhase gates.

    Here is an example using the ROTATIONS_PLUS_CNOT gate set to decompose a controlled MultiRZ gate:

    qp.decomposition.enable_graph()
    
    @qp.decompose(gate_set=qp.gate_sets.ROTATIONS_PLUS_CNOT, num_work_wires=2)
    @qp.qnode(qp.device("default.qubit"))
    def circuit():
        qp.ctrl(qp.MultiRZ(0.5, [0, 1]), control=[3, 4, 5])
        return qp.expval(qp.Z(0))
    >>> print(qp.specs(circuit, level="device")().resources.gate_counts)
    {'RZ': 54, 'RY': 14, 'GlobalPhase': 52, 'CNOT': 36, 'CRZ': 4, 'CRY': 4, 'C(GlobalPhase)': 4, 'Toffoli': 2}

Resource Estimation Templates 📏

  • New lightweight representations of the HybridQRAM, SelectOnlyQRAM, BasisEmbedding, and BasisState templates have been added for fast and efficient resource estimation. These are available in the estimator module as: qp.estimator.HybridQRAM, qp.estimator.SelectOnlyQRAM, qp.estimator.BasisEmbedding, and qp.estimator.BasisState. (#8828) (#8826) (#9415) (#9449)

    import pennylane.estimator as qre
    
    data = [[0, 1, 0], [1, 1, 1], [1, 1, 0], [0, 0, 0], [0, 1, 0], [1, 1, 1], [1, 1, 0], [0, 0, 0]]
    bitstring_size = 3
    
    k = 2
    num_control_wires = 3
    num_work_wires = 1 + 1 + 3 * (1 << (num_control_wires - k) - 1)
    
    reg = qp.registers(
        {
            "control": num_control_wires,
            "target": bitstring_size,
            "work": num_work_wires
        }
    )
    
    dev = qp.device("null.qubit")
    @qp.qnode(dev)
    def hybrid_qram():
        # prepare an address, e.g., |010> (index 2)
        qp.BasisEmbedding(2, wires=reg["control"])
    
        qp.HybridQRAM(
            data,
            control_wires=reg["control"],
            target_wires=reg["target"],
            work_wires=reg["work"],
            k=k
        )
        return qp.probs(wires=reg["target"])
    >>> qre.estimate(hybrid_qram)()
    --- Resources: ---
    Total wires: 12
      algorithmic wires: 11
      allocated wires: 1
        zero state: 1
        any state: 0
    Total gates : 2.797E+3
      'Toffoli': 142,
      'T': 2.112E+3,
      'CNOT': 262,
      'X': 65,
      'Hadamard': 216

Improvements 🛠

Decompositions 🍏

  • qp.transforms.decompose is now conveniently accessible from the top level as qp.decompose. (#9011)

  • It is now possible to locally add decomposition rules to an operator via a qp.decomposition.local_decomps context manager. These rules will only be available within the context. (#8955) (#8998)

    @qp.register_resources({qp.CNOT: 1, qp.H: 2})
    def custom_decomp(wires):
        qp.H(wires[1])
        qp.CNOT(wires)
        qp.H(wires[1])
    >>> with qp.decomposition.local_decomps():
    >>>    qp.add_decomps(qp.CZ, custom_decomp)
    >>>    print(qp.list_decomps(qp.CZ))
    Available Decomposition Rules:
    0: _cz_to_cps
    1: _cz_to_cnot
    2: _cz_to_ppr
    3: my_cz_rule
    >>> print(qp.list_decomps(qp.CZ))
    Available Decomposition Rules:
    0: _cz_to_cps
    1: _cz_to_cnot
    2: _cz_to_ppr
  • The following gate decompositions have been optimized for resource efficiency:

    • The QROM decomposition now has a more efficient allocation of work wires. (#9131)

    • CSWAP is now decomposed more efficiently, using change_op_basis with two CNOT gates and a single Toffoli gate. (#8887)

  • Several new decomposition rules have been added that can be accessed with enable_graph:

    • MultiControlledX has a new decomposition into a pair of TemporaryAND gates and one CNOT. It takes two control wires and at least one zeroed work wire that has been passed explicitly. (#9291)

    • TemporaryAND can now be decomposed into the equivalent (although slightly more expensive) Toffoli gate. Note that this decomposition only is valid if TemporaryAND is used as intended - on zeroed input target qubits or zeroed output target qubits for Adjoint(TemporaryAND). (#9303) (#9424)

    • A new decomposition of Evolution into qp.PauliRot has been added which is compatible with the new graph-based decomposition system. Similarly, a decomposition of qp.RZ into qp.PhaseShift, including a global phase, has been added. (#9001) (#9049)

  • The graph-based decompositions system, enabled via enable_graph, now additionally supports the custom adjoint method of qutrit operators such as QutritUnitary, ControlledQutritUnitary, and TRZ. (#9056)

  • The custom adjoint method of qutrit operators are implemented as decomposition rules compatible with the new graph-based decomposition system. (#9056)

  • Now, when the new graph-based decomposition system is enabled, the decompose transform no longer tries to find a decomposition for an operator that meets a provided stopping_condition, even if it is not in the defined gate_set. (#9036)

  • A strict keyword argument was added to the decompose transform that, when set to False, allows the decomposition graph to treat operators without a decomposition as part of the gate set. This prevents the decomposition graph from erroring out by keeping these operators in the circuit. (#9025)

  • The inspectibility of general symbolic decomposition rules is improved. The string representation of a decomposition rule is by default its source code. Now for symbolic decomposition rules that wrap a base decomposition rule, the source code for the base decomposition rule is also displayed when printing this rule. (#9305)

  • Allowed the passing of num_work_wires, alt_decomps and fixed_decomps to the device preprocessing function decompose, which are then passed through to the graph-based decomposition system. (#9094)

  • The decomposition of BasisState is now compatible with qjit and jax.jit for static wires and/or states. Additionally, the parametric decomposition for traced states without qjit was updated to use powers of X rather than RX. (#9069) (#9124) (#9339)

  • The decompositions of TemporaryAND, MultiRZ, and DiagonalQubitUnitary are now compatible with Catalyst. (#9157)

  • The sk_decomposition now accepts "Adjoint(T)" and "Adjoint(S)" in the basis_set as a now-preferred alternative to the old "T*" and "S*" convention for gate adjoints. (#9231)

  • Some decomposition rules for MultiControlledX (e.g., _mcx_two_borrowed_workers and _mcx_one_borrowed_worker) became identical for instances of this operator on less than 6 wires. To prevent this, stricter conditions have been applied on these decompositions. (#9324)

  • Some wire reusage was removed in Select that was not consistent with the approach to work wires elsewhere in PennyLane, and that was not taken into account in the resource functions for the graph-based decomposition system (leading to decompositions not being resolved correctly). Also simplified the resource calculation of one decomposition of Select. (#9222)

  • The decomposition of QSVT has been updated to be consistent with or without the graph-based decomposition system enabled. (#8994)

  • When the new graph-based decomposition system is enabled, the decompose transform no longer raises duplicate warnings about operators that cannot be decomposed. (#9025)

  • A new DecompositionWarning is now raised instead of a general UserWarning if the decomposition graph is unable to find a solution for an operator. (#9001)

  • With the new graph-based decomposition system enabled, internal use of the decompose transform no longer raises warnings when the graph is unable to find a decomposition for an operator in the following scenarios: (#9001)

    • circuit execution in the null.qubit device. - compilation with qp.compile. - gradient transforms within the expand_transform of hadamard_grad and param_shift.

    In these cases the operators will be treated as supported.

Disentangling Transforms 🧶

  • The disentangle_cnot and disentangle_swap are now callable from PennyLane, not just from Catalyst's passes module. These compilation passes simplify rendundant CNOT and SWAP gates. (#9133)

    .. note::

    The disentangle_cnot and disentangle_swap are compilation passes that are only compatible with qjit workflows.

    Both compilation passes are designed to recognize patterns that include CNOT and SWAP gates that are redundant. In the case of disentangle_cnot, CNOT gates are replaced when the control wire is preceded by an X gate (and the control wire is guaranteed to be in the $\vert 1 \rangle$ state). This is illustrated in the example below, where no CNOT gates remain after the compilation pass is applied.

    import pennylane as qp
    
    dev = qp.device("lightning.qubit", wires=2)
    
    @qp.qjit(capture=True)
    @qp.transforms.disentangle_cnot
    @qp.qnode(dev)
    def circuit():
        qp.X(0)
        qp.CNOT([0, 1])
        return qp.state()
    >>> print(qp.specs(circuit, level=1)().resources.gate_counts)
    {'PauliX': 2}

Drawing ✏️

  • The draw_graph function is now accessible from PennyLane, not just from Catalyst. This function allows for compact graphical inspection of qjit-compiled circuits, preserving structured control flow. (#9020)

    .. note::

    The draw_graph function is only compatible with qjit workflows.

    Like with draw, draw_mpl, and specs, draw_graph can be given a level to inspect how compilation passes affect the circuit.

    import pennylane as qp
    
    @qp.qjit(capture=True, autograph=True)
    @qp.transforms.merge_rotations
    @qp.transforms.cancel_inverses
    @qp.qnode(qp.device("null.qubit", wires=3))
    def circuit(x):
        for i in range(10):
    
            w = i % 3
            qp.H(w)
            qp.H(w)
            qp.CNOT((i % 3, (i + 1) % 3))
    
            qp.RX(0.1, wires=w)
            qp.RX(0.2, wires=w)
    
        if x > 1.2:
            qp.Toffoli((0, 1, 2))
        else:
            qp.RZ(0.1, wires=2)
            qp.RZ(0.1, wires=2)
    
        return qp.expval(qp.X(0))

    After all optimizations are applied (level=2), we can still see the structure of the circuit.

    >>> fig, ax = qp.draw_graph(circuit, level=2)(0.1)
    >>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")

    Graphical representation of circuit

  • A new function called label has been added, which allows for attaching custom labels to operator instances for circuit drawing. (#9078)

    @qp.qnode(qp.device("default.qubit"))
    def circuit():
        qp.drawer.label(qp.H(0), "my-h")
        qp.CNOT([0, 1])
        return qp.probs()
    >>> print(qp.draw(circuit)())
    0: ──H("my-h")─╭●─┤  Probs
    1: ────────────╰X─┤  Probs

    In addition to this change, the mark was added to mark an operator as an input-encoding gate for circuit_spectrum, and qnode_spectrum.

Program Capture 📥

  • With program capture enabled and using for_loop and while_loop, constant closure variables with dynamic shapes can be used as such multiple times, no longer leading to leaked-tracer errors. (#9275) (#9335)

  • A more informative error is raised if something that is not a measurement process is returned from a QNode when program capture is turned on. (#9072)

  • qp.cond converts non-boolean predicates to boolean immediately during capture time instead of from_plxpr doing this, allowing for easier maintenance and organization of from_plxpr. (#9336)

  • qp.for_loop with negative step sizes is now handled immediately during capture time instead of handling this within from_plxpr, allowing for easier maintenance and organization of from_plxpr. (#9299)

  • With program capture, dynamic-shaped arrays returned from qp.for_loop and qp.while_loop can now be combined with other dynamic-shaped arrays returned from qp.for_loop and qp.while_loop. (#9245)

  • qp.vjp and qp.jvp are now compatible with program capture. (#8736) (#8788) (#9019)

  • A qp.capture.subroutine has been added for jitting quantum subroutines with program capture. (#8912)

  • qp.counts of mid-circuit measurement results is now compatible with program capture. (#9022)

  • Program capture support for StatePrep and BasisState has been enhanced to accept state arguments of list or tuple types. (#9338)

Catalyst Compatibility 🤝

  • BasisEmbedding is now captured as BasisState for compatibility with Catalyst and program capture. (#9183)

  • The dynamic_one_shot and split_to_single_terms transforms are now compatible with qp.qjit. (#9129)

  • BBQRAM, HybridQRAM, SelectOnlyQRAM and QROM now accept their classical data as a 2-dimensional array data type, which increases compatibility with Catalyst. (#8791)

  • There is now one single source of truth for documentation of Catalyst passes while still maintaining accessibility from both PennyLane and Catalyst. This includes the following transforms: to_ppr, commute_ppr, merge_ppr_ppm, ppr_to_ppm, reduce_t_depth, decompose_arbitrary_ppr, ppm_compilation, and parity_synth. (#9020) (#9395) (#9444)

    The source code for these passes in PennyLane has been removed as part of this change. However, all transforms listed above can still be accessed from the pennylane.transforms module as before (if Catalyst is installed: pip install pennylane-catalyst).

  • qp.math.givens_decomposition and qp.BasisRotation are now compatible with qjit when capture is disabled. (#9155)

  • qp.value_and_grad is now available to simultaneously calculate the results and gradients in Catalyst. (#8814)

  • Catalyst version information has been added to about. (#9050)

Other improvements

  • Support has been added to assert_valid for decompositions that include mid-circuit measurements alongside better verification for the length of compared iterables. (#9378)

  • A convenience function called ceil_log2 has been added, which computes the ceiling of the base-2 logarithm of its input and casts the result to an int. It is equivalent to int(np.ceil(np.log2(n))). (#8972) (#9069)

  • A new function called binary_decimals has been added to enable easy translation of rotation angles to the binary representation of their decimals. This is important for discretization steps, for example via phase gradient decompositions. (#9117)

  • The binary_finite_reduced_row_echelon function was moved to a new file and now includes further linear algebraic functionalities over $\mathbb{Z}_2$. (#8982)

    • binary_is_independent computes whether a vector is linearly independent of a basis of binary vectors over $\mathbb{Z}_2$. - binary_matrix_rank computes the rank over $\mathbb{Z}_2$ of a binary matrix. - binary_solve_linear_system solves a linear system of the form $A\cdot x=b$ with binary matrix $A$ and binary coefficient vector $b$ over $\mathbb{Z}_2$. - binary_select_basis selects linearly independent columns out of a collection of binary column vectors. The result forms a basis for the columnspace of the input. The columns that are not selected are returned as well.
  • A PauliSentence.prune and FermiSentence.prune method has been added, which removes terms with coefficients below a provided threshold. (#9278)

  • Replaced the O(n²) incremental @= operator chaining in qp.pauli.string_to_pauli_word and qp.pauli.binary_to_pauli with a single qp.prod(*tuple_of_ops) call, collecting operators via generator expressions. These operators are now much faster for large Pauli strings. (#9271)

  • Operations using PauliSentence are now much faster due to additional memorization in PauliWord.__hash__ (#9261)

  • ZX-related transforms are now compatible with pyzx v0.10.0. (#9179)

  • The to_zx transform is now compatible with the new graph-based decomposition system. (#8994)

  • A new function called qp.decomposition.reconstruct has been added, which reconstructs the original operator instance from (*op.data, op.wires, **op.resource_params). This enables qjit-compatible symbolic decomposition rules that do not need to take an instance of the base operator as input. (#9188)

  • The output of the qp.while_loop condition is now automatically converted to a bool. (#9184)

  • A function for setting up transform inputs, including setting default values and basic validation, can now be provided to qp.transform via setup_inputs. (#8732)

  • The unitary_to_rot transform now recursively decomposes QubitUnitary operations. This fixed a bug where two-qubit unitaries would decompose incorrectly to two single-qubit unitaries rather than their rotation decomposition. (#9144)

  • Operations involving FermiWord objects are now significantly faster due to various performance enhancements made to the class. (#9283)

  • MottonenStatePreparation now supports parameter broadcasting in its decomposition. (#9148)

  • Circuits containing GlobalPhase are now trainable without removing the GlobalPhase. (#8950)

  • Quantum functions defining quantum operations can now be passed to the compute_op, target_op and uncompute_op arguments of change_op_basis. (#9163)

  • The qp.estimator.Resources class now has a better string representation in Jupyter Notebooks. (#8880)

  • matrix can now also be applied to a sequence of operators. (#8861)

  • A qp.workflow.get_compile_pipeline(qnode, level)(*args, **kwargs) function has been added to extract the CompilePipeline of a given QNode at a specific level. (#8979) (#9425)

  • No unnecessary classical registers will be created now when using qp.to_openqasm with measure_all=False. (#9033)

  • Both "subroutines" and "custom_gates" are now always initialized in the QASM interpreter, resulting in more robust behaviour with PennyLane's QASM interpreter. (#9201)

  • Applying qp.ctrl on Snapshot no longer produces a Controlled(Snapshot). Instead, it now returns the original Snapshot. (#9001)

  • The default.qubit device now supports parameter-broadcasted GlobalPhase operations. (#9148)

  • Global phases are now supported in from_qasm3 so that QASM including the gphase instruction can be interpreted. (#9247)

Labs: a place for unified and rapid prototyping of research software 🧪

  • Added a new labs.estimator_beta for experimental development of resource estimation tools. Added various classes and functions to labs.estimator_beta to support advanced qubit management for resource estimation. Removed existing resource estimation functionality from the labs.resource_estimation module. (#8996) (#8868)

    • Allocate, allows users to allocate qubits in a resource decomposition. - Deallocate, allows users to deallocate qubits in a resource decomposition. - MarkClean, allows users to mark the state of qubits as the zero state in a circuit. - MarkQubits, allows users to mark the state of qubits in a circuit. - estimate_wires_from_circuit, estimates the number of additional qubits required from a circuit. - estimate_wires_from_resources, estimates the number of additional qubits required from a Resources object.
  • A new labs.estimator_beta.estimate() function has been created, which extends the functionality of qp.estimator.estimate() to utilize the advanced qubit management features for resource estimation. (#9139)

  • Created a new LabsQROM resource operator in labs and added multiple alternate decompositions in labs for MultiControlledX that utilize the new qubit management features. (#9258)

    • mcx_many_clean_aux_resource_decomp, uses multiple clean qubits to decompose. - mcx_one_clean_aux_resource_decomp, uses only one clean qubit to decompose. - mcx_one_dirty_aux_resource_decomp, uses only one dirty qubit to decompose.
  • Added alternate decompositions for CH and Hadamard operations in labs.estimator_beta to get optimal numbers. (#9178)

  • Added comparator decompositions for RegisterEquality and OutOfPlaceIntegerComparator in labs.estimator_beta (#9220)

  • Added alternate controlled decompositions for PauliRot and SelectPauliRot operations in labs.estimator_beta to get optimal numbers. (#9186)

  • Added resource templates for state preparation operators, which include LabsMottonenStatePreparation, LabsCosineWindow, and LabsSumOfSlatersPrep. (#9202)

  • Added various alternate resource decomposition functions for operators which make use of the phase gradient trick to accurately track auxiliary qubits using the new qubit management features. (#9391)

  • Added custom phase gradient decomposition rules for RZ and SelectPauliRot.

    • make_rz_to_phase_gradient_decomp for RZ - make_selectpaulirot_to_phase_gradient_decomp for SelectPauliRot

    Their outputs can be passed as fixed_decomps in qp.decompose and are necessary for efficient discretization strategies in application algorithms. (#9115)

  • The integration test for computing perturbation error of a compressed double-factorized (CDF) Hamiltonian in labs.trotter_error is upgraded to use a more realistic molecular geometry and a more reliable reference error. (#8790)

Breaking changes 💔

  • The num_x_wires and num_work_wires arguments were added to the resource_keys and resource_params of SemiAdder. (#9293)

    With this breaking change, please note the following:

    • Decomposition rules for SemiAdder now require those arguments. - When registering a resource function (qp.register_resources) to a decomposition rule of an operator that contains SemiAdder, the resource representation of SemiAdder must also receive these new arguments. - These changes are relevant only with enable_graph.
  • All Operator classes are now queued by default, unless they implement a custom queue method that changes this behaviour. (#8131) (#9029) (#9423)

    This change also affects operators commonly used for operator math:

    • Hermitian - SProd - Sum - SparseHamiltonian - Projector - BasisStateProjector

    Note, however, that all Operator classes that are used to construct new operators are de-queued, so the following example does not illustrate the changed behaviour (i.e., creating B removes A from the queue in the example below):

    import pennylane as qp
    import numpy as np
    coeff = np.array([0.2, 0.1])
    
    @qp.qnode(qp.device("lightning.qubit", wires=3))
    def expval(x: float):
        qp.RX(x, 1)
        A = qp.Hamiltonian(coeff, [qp.Y(1), qp.X(0)])
        B = A @ qp.Z(2)
        return qp.expval(B)
    >>> print(qp.draw(expval)(0.4))
    0: ───────────┤ ╭<𝓗(0.20,0.10)>
    1: ──RX(0.40)─┤ ├<𝓗(0.20,0.10)>
    2: ───────────┤ ╰<𝓗(0.20,0.10)>

    However, if we convert an operator A to numerical data, from which a new operator B is constructed, the chain of operator dependencies is broken and de-queuing will not work as previously expected:

    coeff = np.array([0.2, 0.1])
    
    @qp.qnode(qp.device("lightning.qubit", wires=3))
    def expval(x: float):
        qp.RX(x, 1)
        A = qp.Hamiltonian(coeff, [qp.Y(1), qp.X(0)])
        numerical_data = A.matrix()
        B = qp.Hermitian(numerical_data, wires=[2, 0])
        return qp.expval(B)
    >>> print(qp.draw(expval, show_matrices=False)(0.4))
    0: ───────────╭𝓗(0.20,0.10)─┤ ╭<𝓗(M0)>
    1: ──RX(0.40)─╰𝓗(0.20,0.10)─┤ │
    2: ─────────────────────────┤ ╰<𝓗(M0)>

    As we can see, the Hamiltonian instance assigned to A remained in the queue. In cases where such a conversion to numerical data is unavoidable, perform the conversion outside of the quantum circuit.

  • Support for NumPy 1.x has been dropped following its end-of-life. NumPy 2.0 or higher is now required. (#8914) (#8954) (#9017) (#9167)

  • compute_qfunc_decomposition and has_qfunc_decomposition have been removed from Operator and all subclasses that implemented them. The graph decomposition system (enable_graph) should be used when capture is enabled. (#8922)

  • The pennylane.devices.preprocess.mid_circuit_measurements transform has been removed. Instead, the device should determine which MCM method to use, and explicitly include dynamic_one_shot or defer_measurements in its preprocess transforms if necessary. See DefaultQubit.setup_execution_config and DefaultQubit.preprocess_transforms for an example. (#8926)

  • The custom_decomps keyword argument to qp.device has been removed. Instead, with the graph decomposition system (enable_graph), new decomposition rules can be defined as quantum functions with registered resources. See pennylane.decomposition for more details. (#8928)

    As an example, consider the case of running the following circuit, where we wish to convert CNOT gates into Hadamard and CZ gates.

    def circuit():
        qp.CNOT(wires=[0, 1])
        return qp.expval(qp.X(1))

    Instead of defining the CNOT decomposition as follows with custom_decomps,

    def custom_cnot(wires):
        return [
            qp.Hadamard(wires=wires[1]),
            qp.CZ(wires=[wires[0], wires[1]]),
            qp.Hadamard(wires=wires[1])
        ]
    
    dev = qp.device('default.qubit', wires=2, custom_decomps={"CNOT" : custom_cnot})
    qnode = qp.QNode(circuit, dev)

    the same result would now be obtained using:

    @qp.decomposition.register_resources({qp.H: 2, qp.CZ: 1})
    def _custom_cnot_decomposition(wires, **_):
        qp.Hadamard(wires=wires[1])
        qp.CZ(wires=[wires[0], wires[1]])
        qp.Hadamard(wires=wires[1])
    
    qp.decomposition.add_decomps(qp.CNOT, _custom_cnot_decomposition)
    
    qp.decomposition.enable_graph()
    
    @qp.transforms.decompose(gate_set={qp.CZ, qp.H})
    def circuit():
        qp.CNOT(wires=[0, 1])
        return qp.expval(qp.X(1))
    
    dev = qp.device('default.qubit', wires=2)
    qnode = qp.QNode(circuit, dev)
    >>> print(qp.draw(qnode, level="device")())
    0: ────╭●────┤
    1: ──H─╰Z──H─┤  <X>
  • The pennylane.operation.Operator.is_hermitian property has been removed and replaced with pennylane.operation.Operator.is_verified_hermitian, as it better reflects the functionality of this property. Alternatively, consider using the pennylane.is_hermitian function instead as it provides a more reliable check for hermiticity. Please be aware that it comes with a higher computational cost. (#8919)

  • Passing a function to the gate_set argument in pennylane.decompose has been removed. The gate_set argument expects a static iterable of operator type and/or operator names, and the function should be passed to the stopping_condition argument instead. (#8919)

  • argnum has been renamed argnums in qp.grad, qp.jacobian, qp.jvp, and qp.vjp to better match Catalyst and JAX. (#8919)

  • Access to the following functions and classes from the pennylane.resources module has been removed. Instead, these functions must be imported from the pennylane.estimator module. (#8919)

    • qp.estimator.estimate_shots in favor of qp.resources.estimate_shots - qp.estimator.estimate_error in favor of qp.resources.estimate_error - qp.estimator.FirstQuantization in favor of qp.resources.FirstQuantization - qp.estimator.DoubleFactorization in favor of qp.resources.DoubleFactorization

Deprecations 👋

  • The pennylane.workflow.get_transform_program function has been deprecated and will be removed in v0.46. Instead, please use the improved pennylane.workflow.get_compile_pipeline to retrieve the compilation pipeline of a QNode. (#9077)

  • The id keyword argument to several classes has been renamed or removed entirely, and those changes will be official in v0.46. (#8951) (#9051)

    • The id keyword argument to MeasureNode and PrepareNode has been renamed to node_uid. - The id keyword argument to MidMeasure has been renamed to meas_uid. - The id keyword argument to MeasurementProcess will be removed. - The id keyword argument to Operator has been deprecated and will be removed.

    The id argument previously served two purposes: (1) adding custom labels to operator instances which were rendered in circuit drawings and (2) tagging encoding gates for Fourier spectrum analysis.

    These are now handled by dedicated functions:

    .. warning::

    Neither of these functions are supported in a qjit-compiled circuit, as the original behaviour was never supported.

    • Use label to attach a custom label to an operator instance for circuit drawing:

      # Legacy method (deprecated):
      qp.RX(0.5, wires=0, id="my-rx")
      
      # New method:
      qp.drawer.label(qp.RX(0.5, wires=0), "my-rx")
    • Use mark to mark an operator as an input-encoding gate for circuit_spectrum, and qnode_spectrum:

      # Legacy method (deprecated):
      qp.RX(0.5, wires=0, id="x0")
      
      # New method:
      qp.fourier.mark(qp.RX(0.5, wires=0), "x0")
  • Setting _queue_category=None in an operator class in order to deactivate its instances being queued has been deprecated. Implement a custom queue method for the respective class instead. Operator classes that used to have _queue_category=None have been updated to _queue_category="_ops", so that they are queued now. (#8131)

  • The BoundTransform.transform property has been deprecated. Use BoundTransform.tape_transform instead. (#8985)

  • expand and related functions (expand_tape, expand_tape_state_prep, and create_expand_trainable_multipar) have been deprecated and will be removed in v0.46. Instead, please use the qp.transforms.decompose transform for decomposing circuits. (#8943) (#9438)

  • Providing a value of None to aux_wire of qp.gradients.hadamard_grad with mode="reversed" or mode="standard" has been deprecated and will no longer be supported in v0.46. An aux_wire will no longer be automatically assigned. (#8905)

  • The qp.transforms.create_expand_fn function has been deprecated and will be removed in v0.46. Instead, please use the qp.transforms.decompose function for decomposing circuits. (#8941) (#8977) (#9006)

  • The transform_program property of QNodes has been renamed to compile_pipeline. The deprecated access through transform_program will be removed in PennyLane v0.46. (#8906) (#8945)

Internal changes ⚙️

  • Read and write permissions have been added to all GitHub Actions workflows. (#9377)

  • The doctest group has been added in the pyproject.toml to easily maintain dependencies of the documentation tests workflow. (#9237)

  • Documentation tests have been added for various modules such as the decomposition, measurements and devices modules. (#9004) (#9206) (#8653) (#9062) (#9236)

  • A new qp.templates.Subroutine class has been added as a layer of abstraction for quantum functions. These objects can now return classical values or mid circuit measurements, and are compatible with Program Capture Catalyst. Any Operator with a single definition in terms of its implementation, a more complicated call signature, and that exists at a higher, algorithmic layer of abstraction could switch to using this class instead of Operator/ Operation. (#8929) (#9080) (#9096) (#9070) (#9097) (#9138) (#9119) (#9151) (#9172) (#9180) (#9177) (#9191) (#9176)

    from pennylane.templates import Subroutine
    
    @Subroutine
    def MyTemplate(x, y, wires):
        qp.RX(x, wires[0])
        qp.RY(y, wires[0])
    
    @qp.qnode(qp.device('default.qubit'))
    def c():
        MyTemplate(0.1, 0.2, 0)
        return qp.state()
    >>> print(qp.draw(c)())
    0: ──MyTemplate(0.10,0.20)─┤  State
    
    • qp.BasisRotation` was initially ported to be a Subroutine, but this change was reverted. (#9026) (#9252)
  • Decomposition rules have been re-written for qjit compatibility, so that they can be lowered to Catalyst/MLIR. Rules for the following SymbolicOps have been re-written:

  • qp.pytrees.PyTreeStructure is now frozen and hashable. PyTreeStructure.children should now be a tuple instead of a list. (#9080)

  • Pass-by-pass specs now uses BoundTransform.tape_transform rather than the deprecated BoundTransform.transform. Additionally, several internal comments have been updated to bring specs in line with the new CompilePipeline class. (#9012)

  • When using specs with Catalyst and with multiple levels, with the split-non-commuting MLIR pass applied, the returned CircuitSpecs object will include a list of SpecsResources objects for the associated level. (#9120)

  • Largely unused PLxPR code was recently removed in Lightning. Consequently, tests from PennyLane that are no longer relevant have been removed. (#9345)

  • Patched jax._src.pjit._infer_params_internal for dynamic shapes to correctly handle the concatenation of closure variables and arguments before return. (#9250)

  • Docker files and workflow have been removed given that the PennyLane Docker image is no longer being built and managed in the repo. (#9273)

  • The requirements file has been removed from the docs folder. (#9242)

  • A transform's setup_inputs is no longer called twice when applied on a QNode. (#9189)

  • The pyzx dependency has been upper bounded to pyzx<0.10 to ensure compatibility. (#9175)

  • The internal PassPipelineWrapper in Catalyst was removed in favour of PennyLane's new unified transforms API. Consequently, usage of PassPipelineWrapper has been removed in PennyLane. (#9123)

  • Updated the diastatic-malt dependency to version v2.15.3. (#9154)

  • A workflow was created to sync the main branch to master and later on deleted, after master branch was deleted. (#9127) (#9316)

  • Removed pytest-benchmark from the pyproject.toml dev dependency group. Benchmarking is no longer internally performed in our test suite. (#7900)

  • References to the master branch have been changed to the new default branch main. (#9128)

  • Update nightly RC builds to not be a schedule triggered in Pennylane anymore. Instead, it will be triggered in the order Lightning —> Catalyst —> Pennylane. (#9092)

  • Duplicate transforms found in both ftqc/catalyst_pass_aliases.py and transforms/decompositions/pauli_based_computation.py have been removed. (#9090)

  • PennyLane has been updated to use a uv lockfile for package dependency tracking. Added UV_SYSTEM_PYTHON to the repository's nightly sync workflows. Removed stable dependency folder and files. (#8755) (#9110) (#9218)

  • sybil has been added to the dev dependency group in pyproject.toml. (#9060)

  • Seeded a test tests/measurements/test_classical_shadow.py::TestClassicalShadow::test_return_distribution to fix stochastic failures by adding a seed parameter to the circuit helper functions and the test method. (#8981)

  • Standardized the tolerances of several stochastic tests to use a 3-sigma rule based on theoretical variance and number of shots, reducing spurious failures. This includes TestHamiltonianSamples::test_multi_wires, TestSampling::test_complex_hamiltonian, and TestBroadcastingPRNG::test_nonsample_measure. Bumped rng_salt to v0.45.0. (#8959) (#8958) (#8938) (#8908) (#8963)

  • The test helper get_device has been updated to correctly seed Lightning devices. (#8942)

  • Updated internal dependencies: autoray (to 0.8.4) and tach (to 0.32.2). (#8911) (#8962) (#9030)

  • The torch dependency has been relaxed from ==2.9.0 to ~=2.9.0 to allow for compatible patch updates. (#8911)

  • Internal calls to the decompose transform have been updated to provide a target_gates argument so that they are compatible with the new graph-based decomposition system. (#8939)

  • A qp.decomposition.toggle_graph_ctx context manager has been added to temporarily enable or disable graph-based decompositions in a thread-safe way. The fixtures "enable_graph_decomposition", "disable_graph_decomposition", and "enable_and_disable_graph_decomp" have been updated to use this method so that they are thread-safe. (#8966)

  • Specialized gate kernels have been added for RX, RY, RZ, and Hadamard in the default.qubit device. These bypass generic einsum/tensordot dispatches and use direct contractions for NumPy states, with correct fallbacks for autodiff interfaces (Autograd, Torch, JAX). (#9075)

Documentation 📝

  • The README has been updated to better introduce PennyLane. (#9370)

  • The qml alias as in import pennylane as qml has been updated to qp in our source code and documentation. (#9310) (#9317) (#9320) (#9315) (#9312) (#9314) (#9319) (#9313) (#9326) (#9333) (#9331) (#9329) (#9280) (#9327) (#9330) (#9325) (#9358) (#9281) (#9360) (#9376) (#9375) (#9384) (#9397)

  • Docstrings for several optimization transforms have been improved by showing the drawing of the circuit after the transform has been applied as opposed to just the numeric simulation result, making it clearer that they can be applied to both QNodes and quantum functions, and clarifying discrepancies when using with qjit where applicable. The improved transform docstrings include cancel_inverses, commute_controlled, merge_amplitude_embedding, merge_rotations, pattern_matching_optimization, remove_barrier, single_qubit_fusion, undo_swaps, combine_global_phases, compile, decompose, transpile, and unitary_to_rot. (#9381) (#9441)

  • A new AI policy document is now applied across the PennyLaneAI organization for all AI contributions. (#9079)

  • Wide-spread changes have been made to our documentation to recommend using program capture with qjit only, and enabling it via qjit(capture=True) instead of the global toggle (qp.capture.enable()). (#9059)

  • TensorFlow related documentation has been updated to clarify that maintenance support has been dropped since PennyLane v0.44. (#9362)

  • The pennylane.transforms module has been reorganized to allow for easier indexing through available transforms in PennyLane. (#9130)

  • The docstrings for cancel_inverses, merge_rotations, and combine_global_phases now has a "Usage with qjit" section to outline what the transform does when used with Catalyst. (#9134) (#9386)

  • Docstring examples in the Pauli-based computation module have been updated to reflect the QEC-to-PBC dialect rename in Catalyst. References to qec.fabricate and qec.prepare are now pbc.fabricate and pbc.prepare. (#9071)

  • Two warning notes have been removed from the gridsynth docstring as we now issue a warning when users provide epsilon smaller than 1e-6, and simulation of PPRs is now possible. (#9221)

  • The documentation of the QASM interpreter class has been updated to include Raises error sections for its methods. (#9244)

  • A typo causing a rendering issue in the docstring for QNode has been fixed. (#8652)

  • A typo in the docstring for ControlledOp was fixed and the Controlled docstring recommends using ctrl instead. (#7154)

  • A note has been added to the documentation of estimate to clarify that an error will be raised if a ResourceOperator is encountered that does not have a resource decomposition defined and it is not in the provided gate_set. (#9230)

  • The documentation of the shadow_expval measurement has been refined to clarify the role of the seed keyword argument and include instructions for achieving reproducible results with this argument. (#9216)

  • The definition of the pipeline argument for compile was clarified in its documentation. (#9159)

  • A typo in the type of a parameter in the docstring of BasicEntanglerLayers has been fixed. (#9046)

  • Infrastructure has been put in place for features that should be accessible from both PennyLane and Catalyst to have a single source of truth for documentation, which will provide a better overall experience when consulting our documentation. (#9020)

    The process for Catalyst frontend features to be automatically accessible from PennyLane, while ensuring that such features' documentation is properly sourced from Catalyst and hosted on PennyLane's documentation, has been outlined in the documentation development guide under the section titled "Making Catalyst functionality callable from PennyLane". Related work in Catalyst can be found in (#2409).

  • Though the documentation for this function is now solely in the Catalyst repository, a correction was made in the output of the code example for decompose_arbitrary_ppr while the documentation still resided in the PennyLane repository. (#9116)

  • Fixed the docstring of FermiSentence that incorrectly claims that it is immutable. (#9278)

  • Fixed a typo in the documentation for qre.SelectPauli. (#9373)

  • Broken documentation links to external demos and tutorials have been fixed by replacing hardcoded URLs with proper :doc: cross references. (#9356)

  • The description of NumPy array slicing used to get the subspace of a density matrix is now more clear in the docs of _phase_shift. (#9246) (#9432)

Bug fixes 🐛

  • SProd.is_verified_hermitian can now be calculated with abstract (jax tracer) coefficients. (#9446)

  • Fixed a bug where specs would fail in multi-threaded or multi-processed settings. (#9420)

  • Fixed a bug where ParametrizedHamiltonian, HardwareHamiltonian, and ParametrizedEvolution did not follow PennyLane's queuing convention. They have been updated to de-queue their input operators and queue themselves like other PennyLane gate objects (that derive from Operator). (#9423)

  • Fixed a bug where the Pytree structure of the following operators were inconsistent with the structure of their data: Pow, QPE, GQSP, estimator.qpe_resources.FirstQuantization, and estimator.qpe_resources.DoubleFactorization. (#9378)

  • Fixed a bug where Reflection did not queue all operators of its decomposition. (#9378)

  • Fixed a bug where Hermitian did not queue its decomposition. (#9378)

  • Fixed a bug in the decomposition of Adjoint(TemporaryAND) where control values were ignored. (#9303)

  • Fixed a bug where debug_state, debug_probs, and debug_expval all mutated the circuit they participated in, leading to incorrect results. (#9344)

  • MultiControlledX is now compatible with qjit. An issue with jax.jit tracing for controlled single-qubit unitary decompositions in pennylane.ops.op_math.decompositions.controlled_decompositions has been fixed by ensuring consistent return types across conditional branches and casting wires to JAX-friendly types during tracing. (#9306)

  • Fixed a warning of casting complex values to reals within qp.math.givens_decomposition. (#9155)

  • Fixed a bug with program capture when a transform is applied to a QNode with a dynamic number of shots and return qp.sample. (#9342)

  • Fixed wire overlap validation in QROM and Select to support JAX-traced wires, enabling qp.QROM to be used with qjit when wires are passed as dynamic arguments. (#9282)

  • Fixed an issue with Catalyst and qp.for_loop and qp.while_loop, where it was defaulting to allow_array_resizing=True instead of allow_array_resizing=False. (#9251)

  • Fixed a bug where the work_wire_type argument of ctrl was silently dropped when the call was delegated to the active compiler (qjit). The parameter is now forwarded to the compiler's ctrl implementation. (#9328)

  • Workflows with program capture that involve dynamic device wires will now raise a NotImplementedError rather than providing incorrect results. (#9248)

  • Fixed a bug in estimator where the ResourcesUndefinedError was being returned as a class type rather than an instance, preventing the intended diagnostic message from being displayed. (#9229)

  • Fixed a bug where the data file transforms/sign_expand/sign_expand_data.json was not included in the source distribution, causing errors when using qp.transforms.sign_expand in a production environment. (#9210)

  • Fixed a bug where qp.math.givens_decomposition modified the input in place when using qjit. (#9155)

  • Fixed a bug where the hashable parameters of a CompressedResourceOp in the graph-based decomposition system were sensitive to the insertion order of keyword arguments/hyperparameters. (#9137)

  • Jacobian-level caching is now unconditionally enabled for the autograd interface, preventing redundant derivative tape executions during the backward pass. (#9081)

  • Fixed a bug where qp.transforms.transpile would fail when qp.GlobalPhase gates were present in a circuit. (#9041)

  • Fixed a bug where LinearCombination did not correctly de-queue the constituents of an operator product via the dunder method __matmul__. (#9029)

  • Fixed :attr:~.ops.Controlled.map_wires and equal with Controlled instances to handle the work_wire_type correctly within map_wires. Also fixed Controlled.map_wires to preserve work_wires. (#9010)

  • The tolerance used in determining whether the norm of the probabilities is sufficiently close to 1 in default.qubit has been increased to 1e-6. (#9014)

  • A bug was fixed that prevents decompositions in the graph-based system from using nested operator products while reporting their resources accurately. (#8773)

  • decomp_int_to_powers_of_two now adheres to standard 64-bit C integer bounds, fixing runtime warnings associated with integer overflow. (#8993)

  • CompilePipeline no longer automatically pushes final transforms to the end of the pipeline as it's being built. (#8995)

  • The error message was improved for when the number of inputs to a qp.for_loop-decorated function is not one greater than the number of outputs. (#8984)

  • Fixed a bug that qp.QubitDensityMatrix was applied in default.mixed device using qp.math.partial_trace incorrectly. This would cause wrong results as described in this issue. (#8933)

  • Fixed an issue when binding a transform when the first positional arg is a Sequence, but not a Sequence of tapes. (#8920)

  • Fixed a bug with qp.estimator.templates.QSVT which allows users to instantiate the class without providing wires. This is now consistent with the standard in the estimator module. (#8949)

  • Fixed a bug where decomposition raises an error for Pow operators when the exponent is batched. (#8969)

  • Fixed a bug where the DecomposeInterpreter cannot be applied on a QNode with the new graph-based decomposition system enabled. (#8965)

  • Fixed a bug where qp.equal raises an error for SProd with abstract scalar parameters and Exp with abstract coefficients. (#8965)

  • Fixed various issues found with decomposition rules for QubitUnitary, BasisRotation, StronglyEntanglingLayers. (#8965)

  • The decompose transform no longer warns about being unable to decompose Barrier and Snapshot. (#9001)

  • When the new graph-based decomposition system is enabled, Exp no longer decomposes to nothing when the exponent is the identity. Instead, a PauliRot is always produced, which in this case decomposes to a GlobalPhase. (#9001)

  • Fixed a bug where the graph-based decomposition system was unable to find a decomposition for a ControlledQubitUnitary with more than two target wires. (#9036)

  • Fixed a discontinuity in the gradient of the single-qubit unitary decompositions. (#9036)

  • Fixed a MemoryError in default.clifford when preparing a BasisState with a large number of wires. (#9018)

  • Fixed a bug where a controlled ChangeOpBasis was sometimes decomposed inefficiently when the graph-based system is enabled. (#9161)

  • Fixed a bug where the decomposition graph was unable to find trivial decompositions of qp.X(0) ** 1 and qp.X(0) ** 0. (#9152)

  • Fixed various small bugs within pennylane.estimator, including: (#9194)

    • The resource decompositions for estimator.QubitUnitary and estimator.OutMultiplier so that they match the results from literature - Incorrect wire mapping when converting QuantumPhaseEstimation to estimator.QPE - Added support for mapping Barrier and SnapShot to estimator.Identity
  • Fixed a bug in the C(SemiAdder) decomposition where incorrect results were produced for a specific wire configuration. (#9270)

  • Fixed a bug where the DecompositionGraph would underestimate the minimum number of work wires required to solve for a particular operator. This occurred when rules with lower wire requirements existed but were unreachable within the provided gate set. (#9298)

  • Fixed a bug that prevented instances of controlled composite operators such as Controlled(2.0 * qp.X(0)) from being unpickled. (#9366)

Contributors ✍️

This release contains contributions from (in alphabetical order):

Guillermo Alonso, Ali Asadi, Astral Cai, Yushao Chen, Isaac De Vlugt, Diksha Dhawan, Olivia Di Matteo, Marcus Edwards, Lillian Frederiksen, Diego Guala, Sengthai Heng, Jacob Kitchen, Korbinian Kottmann, Christina Lee, Joseph Lee, Anton Naim Ibrahim, Oumarou Oumarou, Mudit Pandey, Andrija Paurevic, Alex Preciado, David D.W. Ren, Gabriela Sanchez Diaz, Omkar Sarkar, Jay Soni, Nate Stemen, David Wierichs, Fuyuan Xia, Jake Zaia, Hong-Sheng Zheng.

Don't miss a new pennylane release

NewReleases is sending notifications on new releases.