Release 0.14.0 (current release)

New features since last release

  • Programs compiled with qjit can now be visualized with draw_graph(), allowing for sequentially analyzing impacts of compilation passes on structured and dynamic programs. (#2213) (#2214) (#2218) (#2229) (#2231) (#2234) (#2243) (#2246) (#2260) (#2285) (#2287) (#2298) (#2290) (#2340) (#2357) (#2309) (#2363) (#2380)

    Consider the following circuit:

    import pennylane as qml
    import catalyst
    
    @qml.qjit(autograph=True)
    @catalyst.passes.cancel_inverses
    @catalyst.passes.merge_rotations
    @qml.qnode(qml.device("null.qubit", wires=3))
    def circuit(x, y):
        qml.X(0)
        qml.Y(1)
        qml.H(x)
        qml.GlobalPhase(1.0)
    
        for i in range(3):
            qml.S(0)
            qml.RX(0.1, wires=1)
            qml.RX(0.2, wires=1)
    
            if i == 3:
                qml.T(0)
            else:
                qml.H(0)
                qml.H(0)
    
        qml.H(x)
    
        return qml.expval(qml.Z(y))
    

    The circuit structure (for loop and conditional branches) along with the dynamicism (variables x and y) can be succinctly represented with draw_graph().

    >>> x, y = 1, 0
    >>> fig, ax = catalyst.draw_graph(circuit)(x, y)
    >>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
    
    Graphical representation of circuit with dynamicism and structure

    The output of draw_graph() is a matplotlib.figure.Figure, allowing for natural manipulations like increasing resolution, size, etc.

    By default, all compilation passes specified will be applied and visualized. However, draw_graph() can be used with the level argument to inspect compilation pass impacts, where the level value denotes the cumulative set of applied compilation transforms (in the order they appear) to be applied and visualized. With level=1, drawing the above circuit will apply the merge_rotation transform only:

    >>> fig, ax = catalyst.draw_graph(circuit, level=1)(x, y)
    >>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
    
    Graphical representation of circuit with dynamicism and structure

    The draw_graph() function visualizes a qjit-compiled QNode in a similar manner as view-op-graph does in MLIR, which leverages Graphviz to show data-flow in the compiled IR. As such, use of draw_graph() requires installation of Graphviz and the pydot software package. Please consult the links provided for installation instructions. Additionally, it is recommended to use draw_graph() with PennyLane’s program capture enabled (see qml.capture.enable).

  • The Ross-Sellinger Gridsynth algorithm for discretizing RZ and PhaseShift gates has been added to Catalyst with gridsynth(), allowing for Clifford+T workloads to benefit more from just-in-time compilation with qjit. (#2140) (#2166) (#2292)

    The gridsynth() compilation pass discretizes RZ and PhaseShift gates to either the Clifford+T basis or to the Pauli-product-rotation (PPR) basis, complimenting existing transforms like pennylane.transforms.clifford_t_decomposition() and Pauli-based-computation compilation passes. This pass is also callable from the PennyLane frontend via pennylane.transforms.gridsynth().

  • A new statevector simulator called lightning.amdgpu has been added for optimized performance on AMD GPUs, and is compatible with Catalyst. (#2283)

    The lightning.amdgpu device is a specific instantiation of the lightning.kokkos backend, supporting the same features and operations as lightning.kokkos, with pre-compiled wheels for lightning.amdgpu available on PyPI for easy installation to use on MI300 series AMD GPUs.

    This device can be used within qjit‘d workflows exactly as other devices compatible with Catalyst:

    @qml.qjit
    @qml.qnode(qml.device('lightning.amdgpu', wires=2))
    def circuit():
      qml.Hadamard(0)
      return qml.state()
    
    >>> circuit()
    [0.70710678+0.j 0.        +0.j 0.70710678+0.j 0.        +0.j]
    

    See the Lightning-AMDGPU documentation for more details and installation instructions.

  • A new control-flow operation has been added called catalyst.switch(), which is a qjit-compatible index-switch style control flow decorator. Switches allow for more efficient, non-recursive lowering of distinct cases and can simplify control flow among multiple branches. (#2171)

    from catalyst import qjit, switch
    
    @qjit
    @qml.qnode(qml.device("lightning.qubit", wires=1))
    def my_circuit(i, theta):
        @switch(i) # initialize a switch on variable i
        def my_switch(angle): # this is the default branch (required)
            qml.RX(angle, wires=0)
    
        @my_switch.branch(1) # create a branch with case i = 1
        def my_branch(angle):
            qml.RY(angle, wires=0)
    
        @my_switch.branch(4) # create a branch with case i = 4
        def my_branch_4(angle):
            qml.H(0)
    
        my_switch(theta) # must invoke the switch
        return qml.probs()
    
  • Catalyst can now compile circuits that are directly expressed in terms of Pauli product rotation (PPR) and Pauli product measurement (PPM) operations: PauliRot and pauli_measure(), respectively. This is only supported with PennyLane program capture enabled (pennylane.capture.enable()). This support enables research and development spurred from A Game of Surface Codes (arXiv1808.02892). (#2145) (#2233) (#2284) (#2296) (#2336) (#2360)

    PauliRot and pauli_measure() can be manipulated with Catalyst’s existing passes for PPR-PPM compilation only when PennyLane program capture is enabled. This includes pennylane.transforms.to_ppr(), pennylane.transforms.commute_ppr(), pennylane.transforms.merge_ppr_ppm(), pennylane.transforms.ppr_to_ppm(), pennylane.transforms.reduce_t_depth(), pennylane.transforms.decompose_arbitrary_ppr() and pennylane.transforms.ppm_compilation(). Note that these transforms must be called from the PennyLane frontend, not from catalyst.passes.

    import pennylane as qml
    import jax.numpy as jnp
    import catalyst
    
    qml.capture.enable()
    
    pipelines=[('pip', ["quantum-compilation-stage"])]
    
    @qml.qjit(pipelines=pipelines, target="mlir")
    @qml.transforms.ppm_compilation
    @qml.qnode(qml.device("null.qubit", wires=4))
    def circuit():
        # equivalent to a Hadamard gate
        qml.PauliRot(jnp.pi / 2, pauli_word="Z", wires=0)
        qml.PauliRot(jnp.pi / 2, pauli_word="X", wires=0)
        qml.PauliRot(jnp.pi / 2, pauli_word="Z", wires=0)
    
        # equivalent to a CNOT gate
        qml.PauliRot(jnp.pi / 2, pauli_word="ZX", wires=[0, 1])
        qml.PauliRot(-jnp.pi / 2, pauli_word="Z", wires=[0])
        qml.PauliRot(-jnp.pi / 2, pauli_word="X", wires=[1])
    
        # equivalent to a T gate
        qml.PauliRot(jnp.pi / 4, pauli_word="Z", wires=0)
    
        ppm = qml.pauli_measure(pauli_word="ZXY", wires=[1, 2, 0])
    
        return
    
    >>> print(circuit.mlir_opt)
    ...
    %3 = qec.fabricate  magic : !quantum.bit
    %mres, %out_qubits:2 = qec.ppm ["X", "Z"] %1, %3 : i1, !quantum.bit, !quantum.bit
    %mres_0, %out_qubits_1 = qec.select.ppm(%mres, ["Y"], ["X"]) %out_qubits#1 : i1, !quantum.bit
    %4 = qec.ppr ["X"](2) %out_qubits#0 cond(%mres_0) : !quantum.bit
    quantum.dealloc_qb %out_qubits_1 : !quantum.bit
    %5 = quantum.extract %0[ 2] : !quantum.reg -> !quantum.bit
    %mres_2, %out_qubits_3:3 = qec.ppm ["Z", "Y", "X"] %4, %2, %5 : i1, !quantum.bit, !quantum.bit, !quantum.bit
    ...
    
  • A new transform called decompose_arbitrary_ppr() pass has been added, which decomposes abitrary-angle Pauli-product rotations (PPRs) as outlined in Figure 13(d) from arXiv:2211.15465. (#2304) (#2354)

    An arbitrary-angle PPR is defined as a PPR whose angle of rotation is not \(\tfrac{\pi}{2}\), \(\tfrac{\pi}{4}\), or \(\tfrac{\pi}{8}\). The decompose_arbitrary_ppr() compilation pass will decompose an arbitrary-angle PPR into a collection of non-arbitrary PPRs, Pauli-product measurements (PPMs), and a single-qubit arbitrary PPR in the Z basis.

    For compatibility with pennylane.specs(), it is recommended to use this transform with PennyLane program capture enabled and by calling it from the PennyLane frontend (pennylane.transforms.decompose_arbitrary_ppr()), not from catalyst.passes.

    import pennylane as qml
    
    qml.capture.enable()
    
    @qml.qjit(target="mlir")
    @qml.transforms.decompose_arbitrary_ppr
    @qml.transforms.to_ppr
    @qml.qnode(qml.device("null.qubit", wires=3))
    def circuit():
        qml.PauliRot(0.1, pauli_word="XY", wires=[0, 1])
        return
    
    >>> print(qml.specs(circuit, level=3)())
    Device: null.qubit
    Device wires: 3
    Shots: Shots(total=None)
    Level: 3
    
    Resource specifications:
      Total wire allocations: 4
      Total gates: 6
      Circuit depth: Not computed
    
      Gate types:
        qec.prepare: 1
        PPM: 2
        PPR-pi/2: 2
        PPR-Phi: 1
    
      Measurements:
        No measurements.
    

Improvements 🛠

  • An informative error is now raised if a transform is applied inside of a qjit‘d QNode when PennyLane’s program capture is enabled. (#2256)

    @qml.qjit
    @qml.qnode(qml.device('lightning.qubit', wires=1))
    @qml.transforms.cancel_inverses
    def c():
        qml.X(0)
        qml.X(0)
        return qml.probs()
    
    >>> c()
    NotImplementedError: transforms cannot currently be applied inside a QNode.
    
  • qml.PCPhase can now be qjit-compiled and executed with PennyLane’s program capture enabled. (#2226)

  • The new graph-based decomposition framework (enabled with pennylane.decomposition.enable_graph()) has Autograph feature parity with PennyLane when PennyLane’s program capture is enabled. When compiling with qml.qjit(autograph=True), the decomposition rules returned by the graph-based framework are now correctly compiled using Autograph. This ensures compatibility and deeper optimization for dynamically generated decomposition rules. (#2161)

  • The decomposition of qml.MultiRZ operations with an arbitrary number of wires is now supported at the MLIR level with graph-based decompositions enabled and PennyLane’s program capture enabled. (#2160)

  • Catalyst can now use the new pass_name property of pennylane transform objects. Passes can now be created using qml.transform(pass_name=pass_name) instead of PassPipelineWrapper. This allows for better integration of Catalyst transforms with the PennyLane frontend. (#2149

  • Compilation passes registered in PennyLane via @qml.transform can now take in optional keyword arguments when used with qjit() and when PennyLane’s program capture is enabled. (#2154)

  • Pytree inputs can now be used when PennyLane’s program capture is enabled. (#2165)

  • The ppr-to-mbqc pass now supports lowering qec.ppr.arbitrary operations (Pauli Product Rotations with arbitrary angles) to MBQC-style gate sequences. The lowering follows the same pattern as fixed-angle PPR operations: conjugation gates to map Paulis to the Z-basis, a CNOT ladder to accumulate parity, an RZ gate with angle (where θ is the PPR angle), and reverse operations to restore the original basis. (#2373)

  • qml.grad and qml.jacobian can now be used with qjit when PennyLane’s program capture is enabled. (#2078)

  • A new "changed" option has been added to the keep_intermediate parameter of qjit(). This option saves intermediate IR files after each pass, but only when the IR is actually modified by the pass. Additionally, intermediate IR files are now organized into subdirectories for each compilation stage when using keep_intermediate="changed" or keep_intermediate="pass". These changes culminate in better IR file management. (#2186)

  • Resource tracking with pennylane.specs() now includes qml.StatePrep operations and accounts for dynamic wire allocation (pennylane.allocate()). (#2230) (#2203)

  • When saving the IR that each compilation pass generates, the apply-transform-sequence pass is now counted as a single pass instead of potentially many passes. (#1978)

  • A new option called use_nameloc has been added to qjit() that embeds variable names from Python into the compiler IR, which can make it easier to read when debugging programs. (#2054)

  • Dynamically allocated wires (pennylane.allocate()) can now be passed into control flow blocks and subroutines. (#2130) (#2268)

  • The --adjoint-lowering pass can now handle Pauli-product rotation (PPR) operations. (#2227)

  • Catalyst now supports Pauli product rotations (PPR) with arbitrary or dynamic angles in the QEC dialect. This will allow pennylane.PauliRot with arbitrary or dynamic angles (angles not known at compile time) to be lowered to the QEC dialect. This is implemented as a new qec.ppr.arbitrary operation, which takes a Pauli-product and an arbitrary or dynamic angle as input. (#2232) (#2233)

    For example:

    %const = arith.constant 0.124 : f64
    %1:2 = qec.ppr.arbitrary ["X", "Z"](%const) %q1, %q2 : !quantum.bit, !quantum.bit
    %2:2 = qec.ppr.arbitrary ["X", "Z"](%const) %1#0, %1#1 cond(%c0) : !quantum.bit, !quantum.bit
    
  • Catalyst now features a unified compilation framework, which will enable users and developers to design and implement compilation passes in Python in addition to C++, acting on the same Catalyst IR. The Python interface relies on the xDSL library <https://xdsl.dev/> to represent and manipulate programs (analogous to the MLIR library in C++). As a result, transformations can be quickly prototyped, easily debugged, and dynamically integrated into Catalyst without changes to the compiled Catalyst package. (#2199)

    This new module is available under the catalyst.python_interface namespace, and will feature more user-friendly functionality for writing qjit-compatible compilation passes in upcoming releases.

    This functionality was originally developed as part of the PennyLane package, and has been migrated here. For earlier development notes to the feature, please refer to the PennyLane release notes.

    Here is a list of what’s included with this change:

    • Added the PauliRotOp, PCPhaseOp, and PPRotationArbitraryOp operations to the xDSL quantum dialect. (#2307) (#8621)

    • An xDSL Universe containing all custom xDSL dialects and passes has been registered as an entry point, allowing usage of PennyLane’s dialects and passes with xDSL’s command-line tools. (#2208)

    • A new catalyst.python_interface.inspection.mlir_specs function has been added to facilitate PennyLane’s new pass-by-pass pennylane.specs() feature with qjit. This function returns information gathered by parsing the xDSL-generated MLIR from a given QJIT object, such as gate counts, measurements, or qubit allocations. (#2238) (#2303) (#2315)

    • Added an experimental outline_state_evolution_pass xDSL pass to catalyst.python_interface.transforms, which moves all quantum gate operations to a private callable. (#8367)

    • A new experimental split_non_commuting_pass compiler pass has been added to catalyst.python_interface.transforms. This pass splits quantum functions that measure observables on the same wires into multiple function executions, where each execution measures observables on different wires (using the "wires" grouping strategy). The original function is replaced with calls to these generated functions, and the results are combined appropriately. (#8531)

    • Users can now apply xDSL passes without the need to pass the pass_plugins argument to the qjit decorator. (#8572) (#8573) (#2169) (#2183)

    • The catalyst.python_interface.transforms.convert_to_mbqc_formalism_pass() now supports IndexSwitchOp in the IR and ignores regions that have no body. (#8632)

    • The convert_to_mbqc_formalism compilation pass now outlines the operations to represent a gate in the MBQC formalism into subroutines in order to reduce the IR size for large programs. (#8619)

    • The catalyst.python_interface.Compiler.run() method now accepts a string as input, which is parsed and transformed with xDSL. (#8587)

    • An is_xdsl_pass function has been added to the catalyst.python_interface.pass_api module. This function checks if a pass name corresponds to an xDSL implemented pass. (#8572)

    • A new catalyst.python_interface.utils submodule has been added, containing general-purpose utilities for working with xDSL. This includes a function that extracts the concrete value of scalar, constant SSA values. (#8514)

    • The catalyst.python_interface.visualization module has been renamed to catalyst.python_interface.inspection, and various utility functions within this module have been streamlined. (#2237)

    • The experimental xDSL measurements_from_samples_pass() pass has been updated to support shots defined by an arith.constant operation. (#8460)

    • Removed the catalyst.python_interface.dialects.transform module in favor of using the xdsl.dialects.transform module directly. (#2261)

    • Added a “Unified Compiler Cookbook” RST file, along with tutorials, to catalyst.python_interface.doc, which provides a quickstart guide for getting started with xDSL and its integration with PennyLane and Catalyst. (#8571)

    • xDSL passes are now automatically detected when using the qjit decorator. This removes the need to pass the pass_plugins argument to the qjit decorator. (#2169) (#2183)

    • The mlir_opt property now correctly handles xDSL passes by automatically detecting when the Python compiler is being used and routing through it appropriately. (#2190)

    • A new experimental parity_synth_pass compiler pass has been added to catalyst.python_interface.transforms. This pass groups CNOT and RZ operators into phase polynomials and re-synthesizes them into CNOT and RZ operators again. (#2294)

    • The catalyst.python_interface.pass_api.PassDispatcher now has a more lightweight implementation. (#2324)

    • The global xDSL pass registry is now explicitly refreshed before compiling workflows decorated with catalyst.qjit(). (#2322)

Breaking changes 💔

  • The standard Catalyst pipelines have been restructured, such that default and user QNode passes are run together in the first pipeline. For this purpose, the old quantum-compilation-pipeline and enforce-runtime-invariants-pipeline have been merged into a single quantum-compilation-pipeline, while a new gradient-lowering-pipeline has been split out from the old quantum-compilation-pipeline. (#2186)

  • The pipeline and "passes" postfixes in the compilation stage names have been changed to stage for clarity. (#2230)

  • The JAX version used by Catalyst has been updated to 0.7.0. (#2131)

  • (Compiler integrators only) The versions of LLVM/Enzyme/stablehlo used by Catalyst have been updated. (#2122) (#2174) (#2175) (#2181)

  • The remove-chained-self-inverse pass has been renamed to cancel-inverses to better conform with the name of the corresponding transform in PennyLane. (#2201)

  • The to-ppr pass now automatically runs canonicalization patterns after converting quantum operations to Pauli Product Rotation (PPR) form. This removes identity Pauli rotations (e.g., ["I", "I", "I"]) automatically, simplifying the resulting IR. (#2367)

Deprecations 👋

No deprecations have been made in this release.

Bug fixes 🐛

  • Fixed a bug in the catalyst.passes.merge_ppr_ppm() that was causing an iteration out-of-bound error. (#2359)

  • Updated the type support for callbacks allowing for the use of unsigned integers. (#2330)

  • Fixed a bug in the gradient.value_and_grad verifier that incorrectly validated gradient result types by matching from the tail of callee arguments, ignoring diffArgIndices. This caused false verification errors when differentiating a subset of arguments with different types. (#2349)

  • Fixed a bug in the catalyst.python_interface.pass_api.TranformInterpreterPass pass that prevented pass options from being used correctly. (#2289)

  • The experimental xDSL diagonalize_measurements() pass has been updated to fix a bug that included the wrong SSA value for final qubit insertion and deallocation at the end of the circuit. A clear error is now also raised when there are observables with overlapping wires. (#8383)

  • Fixed a bug in the constructor of the xDSL Quantum dialect’s QubitUnitaryOp that prevented an instance from being constructed. (#8456)

  • Fixed a bug where the qec.ppr op attribute rotation_kind was not correctly constrained to be one of ±1, ±2, ±4, or ±8. Also, for the identity Pauli product, the rotation_kind was correctly set to 1, instead of 0. (#2344)

  • Running the Catalyst compiler from the command line no longer misses the detensorize-function-boundary and symbol-dce passes. (#2266)

  • Fixed an issue where a heap-to-stack allocation conversion pass was causing SIGSEGV issues during program execution at runtime. (#2172)

  • Fixed an issue with capturing unutilized abstracted adjoint and controlled rules by the graph in the new decomposition framework. (#2160)

  • Fixed the translation of PennyLane control flow (qml.for_loop) to Catalyst control flow for edge cases where the consts were being reordered. (#2128) (#2133)

  • Fixed the translation of QubitUnitary and GlobalPhase operations to Catalyst when they are modified by adjoint or ctrl. (##2158)

  • Fixed an issue with the translation of a workflow with different transforms applied to different QNodes, which was causing transforms to act beyond the code they are intended to be applied to. (#2167)

  • Fixed canonicalization of redundant quantum.insert and quantum.extract pairs. When extracting a qubit immediately after inserting it at the same index, the operations can be cancelled out while properly updating remaining uses of the register. (#2162)

    For an example:

    // Before canonicalization
    %1 = quantum.insert %0[%idx], %qubit1 : !quantum.reg, !quantum.bit
    %2 = quantum.extract %1[%idx] : !quantum.reg -> !quantum.bit
    ...
    %3 = quantum.insert %1[%i0], %qubit2 : !quantum.reg, !quantum.bit
    %4 = quantum.extract %1[%i1] : !quantum.reg -> !quantum.bit
    // ... use %1
    // ... use %4
    
    // After canonicalization
    // %2 directly uses %qubit1
    // %3 and %4 updated to use %0 instead of %1
    %3 = quantum.insert %0[%i0], %qubit2 : !quantum.reg, !quantum.bit
    %4 = quantum.extract %0[%i1] : !quantum.reg -> !quantum.bit
    // ... use %qubit1
    // ... use %4
    
  • Fixed an issue with commute_ppr() and merge_ppr_ppm() where they were incorrectly moving operations. This also improves the compilation time by reducing the sort function by explicitly passing the operations that need to be sorted. (#2200)

  • Fixed a bug that was causing compilation passes to not apply when using mcm_method="one-shot". (#2198)

  • Fixed a bug where qml.StatePrep and qml.BasisState might be pushed after other gates, overwriting their effects. (#2239)

  • Fixed a bug where quantum.num_qubits operations were not properly removed during classical processing of gradient transforms. This fix enables automatic qubit management (i.e., creating a device and not providing the wires argument) to be used with gradients. (#2262)

  • Fixed a but with commute_ppr() that was incorrectly modifying operands of PPRs that live in different blocks of MLIR. (#2267)

  • The --inline-nested-module pass no longer renames external function declarations. This pass inlines the QNode MLIR modules into the global QJIT MLIR module. If a QNode module contains function declarations to external APIs, the names of these declarations must stay unchanged. This change enables quantum compilation passes to generate calls to external APIs. (#2244)

  • Fixed a bug where Catalyst was incorrectly raising an error about a missing shots parameter on devices that support analytical execution. (#2281)

  • Fixed a bug where qml.vjp and qml.jvp were not working with Autograph. (#2345)

  • Fixed incorrect detection of tracer wires in the frontend. Previously, NumPy integers would be detected as dynamic wires leading to unnecessary instructions in the program IR. (#2361)

Internal changes ⚙️

  • The jaxpr transform pl_map_wires has been removed along with its test. (#2220)

  • DecompRuleInterpreter now solves the graph and adds decompositions rules in the cleanup method instead of during the first call to interpret_measurement. (#2312)

  • Updated references to TransformProgram with the new pennylane.CompilePipeline class. (#2314)

  • xDSL and xDSL-JAX are now dependencies of Catalyst. (#2282)

  • Python 3.14 is now officially supported. Added the forward capability with Python 3.14. (#2271)

  • The RTIO dialect is added to bypass the compilation flow from OpenAPL to ARTIQ’s LLVM IR. It is introduced to bridge the gap between the ion dialect and ARTIQ’s LLVM IR. The design philosophy of the RTIO dialect is primarily event-based. Every operation is asynchronous; sync behaviour occurs only via rtio.sync or wait operand in event operation. And we now support the compiling from the ion dialect to the RTIO dilalect. (#2185) (#2204)

  • Integration tests for qml.specs have been updated to match the new output format introduced in PennyLane v0.44. (#2255)

  • Resource tracking now writes out at device destruction time instead of qubit deallocation time. The written resources will be the total amount of resources collected throughout the lifetime of the execution. For executions that split work between multiple functions (e.g., with the split-non-commuting pass), this ensures that resource tracking outputs the total resources used for all splits. (#2219)

  • Replaced the deprecated shape_dtype_to_ir_type function with the RankedTensorType.get method. (#2159)

  • Updates to PennyLane’s use of a single transform primitive with a transform kwarg. (#2177)

  • The pytest tests are now run with strict=True by default. (#2180)

  • Refactored Catalyst’s pass registering so that it’s no longer necessary to manually add new passes at registerAllCatalystPasses. (#1984)

  • Split from_plxpr.py into two files. (#2142)

  • Re-worked DataView to avoid an axis of size 0 possibly triggering a segfault via an underflow error, as discovered in this comment. (#1621)

  • Decoupled the ION dialect from the quantum dialect to support the new RTIO compilation flow. The ion dialect now uses its own !ion.qubit type instead of depending on !quantum.bit. Conversion between qubits of quantum and ion dialects is handled via unrealized conversion casts. (#2163)

    For an example, quantum qubits are converted to ion qubits as follows:

    %qreg = quantum.alloc(1) : !quantum.reg
    %q0 = quantum.extract %qreg[0] : !quantum.reg -> !quantum.bit
    
    // Convert quantum.bit to ion.qubit
    %ion_qubit_0 = builtin.unrealized_conversion_cast %q0 : !quantum.bit to !ion.qubit
    
    // Use in ion dialect operations
    %pp = ion.parallelprotocol(%ion_qubit_0) : !ion.qubit {
      ^bb0(%arg1: !ion.qubit):
        // ... ion operations ...
    }
    
  • Added support for ppr-to-ppm as an individual MLIR pass and Python binding for the qec dialect. (#2189)

  • Added a canonicalization pattern for qec.ppr to remove any PPRs consisting only of identities. (#2192)

  • Renamed the annotate-function pass to annotate-invalid-gradient-functions and moved it to the gradient dialect and the lower-gradients compilation stage. (#2241)

  • Added support for PPRs and arbitrary-angle PPRs to the merge_rotations() pass. This pass now merges PPRs with equivalent angles, and cancels PPRs with opposite angles, or angles that sum to identity when the angles are known. The pass also supports conditions on PPRs, merging when conditions are identical and not merging otherwise. (#2224) (#2245) (#2254) (#2258) (#2311)

  • Refactored QEC tablegen files to separate QEC operations into a new QECOp.td file (#2253

  • Removed the getRotationKind and setRotationKind methods from the QEC interface QECOpInterface to simplify the interface. (#2250)

  • A new PauliFrame dialect has been added. This dialect includes a set of abstractions and operations for interacting with an external Pauli frame tracking library. (#2188)

  • A new to-pauli-frame compilation pass has been added, which applies the Pauli frame tracking protocols to a Clifford+T program. (#2269)

  • Adding the measurement type into the MLIR assembly format for qec.ppm and qec.select.ppm (#2347)

  • Remove duplicate code for canonicalization and verification of Pauli Product Rotation operations. (#2313)

Documentation 📝

  • A note was made in the Sharp Bits page for the behaviour of qml.transforms.decompose when graph-based decompositions are enabled with pennylane.decomposition.enable_graph(). It clarifies that non-deterministic graph solutions may lead to non-executable programs if intermediate gates are not executable by Catalyst. (#2377)

  • Clarifications were made in the Sharp Bits page for the behaviour of qml.allocate when used with Catalyst. In particular, returning any terminal measurement besides qml.probs when qml.allocate is used within a qjit‘d workflow is not supported. (#2317) (#2358)

  • A typo in the code example for ppr_to_ppm() has been corrected. (#2136)

  • Fixed a rendering issue in catalyst.qjit and catalyst.CompileOptions docstrings. (#2156)

  • Updated the MLIR Plugins documentation stating that plugins require adding passes via --pass-pipeline. (#2168)

  • Typos in the docstrings for PPRotationArbitraryOp and PPRRotationOp have been corrected. (#2297)

  • The --save-ir-after-each command line option documentation has been updated to explain the changed value. (#2355)

Contributors ✍️

This release contains contributions from (in alphabetical order):

Ali Asadi, Joey Carter, Yushao Chen, Isaac De Vlugt, Sengthai Heng, David Ittah, Jeffrey Kam, Christina Lee, Joseph Lee, Mehrdad Malekmohammadi, River McCubbin, Lee J. O’Riordan, Mudit Pandey, Andrija Paurevic, Roberto Turrado, Paul Haochen Wang, David Wierichs, Jake Zaia, Hongsheng Zheng.