Release 0.11.0¶
New features since last release
A novel optimization technique is implemented in Catalyst that performs quantum peephole optimizations across loop boundaries. The technique has been added to the existing optimizations
cancel_inversesandmerge_rotationsto increase their effectiveness in structured programs. (#1476)A frequently occurring pattern is operations at the beginning and end of a loop that cancel each other out. With loop boundary analysis, the
cancel_inversesoptimization can eliminate these redundant operations and thus reduce quantum circuit depth.For example,
dev = qml.device("lightning.qubit", wires=2) @qml.qjit @catalyst.passes.cancel_inverses @qml.qnode(dev) def circuit(): for i in range(3): qml.Hadamard(0) qml.CNOT([0, 1]) qml.Hadamard(0) return qml.expval(qml.Z(0))
Here, the Hadamard gate pairs which are consecutive across two iterations are eliminated, leaving behind only two unpaired Hadamard gates, from the first and last iteration, without unrolling the for loop. For more details on loop-boundary optimization, see the PennyLane Compilation entry.
A new intermediate representation and compilation framework has been added to Catalyst to describe and manipulate programs in the Pauli product measurement (PPM) representation. As part of this framework, three new passes are now available to convert Clifford + T gates to Pauli product measurements as described in arXiv:1808.02892. (#1499) (#1551) (#1563) (#1564) (#1577)
Note that programs in the PPM representation cannot yet be executed on available backends. The passes currently exist for analysis, but PPM programs may become executable in the future when a suitable backend is available.
The following new compilation passes can be accessed from the
passesmodule or inpipeline():catalyst.passes.to_ppr: Clifford + T gates are converted into Pauli product rotations (PPRs) (\(\exp{iP \theta}\), where \(P\) is a tensor product of Pauli operators):Hgate → 3 rotations with \(P_1 = Z, P_2 = X, P_3 = Z\) and \(\theta = \tfrac{\pi}{4}\)Sgate → 1 rotation with \(P = Z\) and \(\theta = \tfrac{\pi}{4}\)Tgate → 1 rotation with \(P = Z\) and \(\theta = \tfrac{\pi}{8}\)CNOTgate → 3 rotations with \(P_1 = (Z \otimes X), P_2 = (-Z \otimes \mathbb{1}), P_3 = (-\mathbb{1} \otimes X)\) and \(\theta = \tfrac{\pi}{4}\)
catalyst.passes.commute_ppr: Commute Clifford PPR operations (PPRs with \(\theta = \tfrac{\pi}{4}\)) to the end of the circuit, past non-Clifford PPRs (PPRs with \(\theta = \tfrac{\pi}{8}\))catalyst.passes.ppr_to_ppm: Absorb Clifford PPRs into terminal Pauli product measurements (PPMs).
For more information on PPMs, please refer to our PPM documentation page.
Catalyst now supports qubit number-invariant compilation. That is, programs can be compiled without specifying the number of qubits to allocate ahead of time. Instead, the device can be supplied with a dynamic program variable as the number of wires. (#1549) (#1553) (#1565) (#1574)
For example, the following toy workflow is now supported, where the number of qubits,
n, is provided as an argument to a qjit’d function:import catalyst import pennylane as qml @catalyst.qjit(autograph=True) def f(n): device = qml.device("lightning.qubit", wires=n, shots=10) @qml.qnode(device) def circuit(): for i in range(n): qml.RX(1.5, wires=i) return qml.counts() return circuit()
>>> f(3) (Array([0, 1, 2, 3, 4, 5, 6, 7], dtype=int64), Array([0, 0, 3, 2, 3, 1, 1, 0], dtype=int64)) >>> f(4) (Array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], dtype=int64), Array([0, 0, 1, 1, 2, 0, 0, 0, 0, 0, 1, 1, 2, 1, 0, 1], dtype=int64))
Catalyst better integrates with PennyLane program capture, supporting PennyLane-native control flow operations and providing more efficient transform handling when both Catalyst and PennyLane support a transform. (#1468) (#1509) (#1521) (#1544) (#1561) (#1567) (#1578)
Using PennyLane’s program capture mechanism involves setting
experimental_capture=Truein the qjit decorator. With this present, the following control flow functions in PennyLane are now usable with qjit:Support for
qml.cond:import pennylane as qml from catalyst import qjit dev = qml.device("lightning.qubit", wires=1) @qjit(experimental_capture=True) @qml.qnode(dev) def circuit(x: float): def ansatz_true(): qml.RX(x, wires=0) qml.Hadamard(wires=0) def ansatz_false(): qml.RY(x, wires=0) qml.cond(x > 1.4, ansatz_true, ansatz_false)() return qml.expval(qml.Z(0))
>>> circuit(0.1) Array(0.99500417, dtype=float64)
Support for
qml.for_loop:dev = qml.device("lightning.qubit", wires=2) @qjit(experimental_capture=True) @qml.qnode(dev) def circuit(x: float): @qml.for_loop(10) def loop(i): qml.H(wires=1) qml.RX(x, wires=0) qml.CNOT(wires=[0, 1]) loop() return qml.expval(qml.Z(0))
>>> circuit(0.1) Array(0.97986841, dtype=float64)
Support for
qml.while_loop:@qjit(experimental_capture=True) @qml.qnode(dev) def circuit(x: float): f = lambda c: c < 5 @qml.while_loop(f) def loop(c): qml.H(wires=1) qml.RX(x, wires=0) qml.CNOT(wires=[0, 1]) return c + 1 loop(0) return qml.expval(qml.Z(0))
>>> circuit(0.1) Array(0.97526892, dtype=float64)
Additionally, Catalyst can now apply its own compilation passes when equivalent transforms are provided by PennyLane (e.g.,
cancel_inversesandmerge_rotations). In cases where Catalyst does not have its own analogous implementation of a transform available in PennyLane, the transform will be expanded according to rules provided by PennyLane.For example, consider this workflow that contains two PennyLane transforms:
cancel_inversesandsingle_qubit_fusion. Catalyst has its own implementation ofcancel_inversesin thepassesmodule, and will smartly invoke its implementation intead. Conversely, Catalyst does not have its own implementation ofsingle_qubit_fusion, and will therefore resort to PennyLane’s implementation of the transform.dev = qml.device("lightning.qubit", wires=1) @qjit(experimental_capture=True) def func(r1, r2): @qml.transforms.cancel_inverses @qml.transforms.single_qubit_fusion @qml.qnode(dev) def circuit(r1, r2): qml.Rot(*r1, wires=0) qml.Rot(*r2, wires=0) qml.RZ(r1[0], wires=0) qml.RZ(r2[0], wires=0) qml.Hadamard(wires=0) qml.Hadamard(wires=0) return qml.expval(qml.PauliZ(0)) return circuit(r1, r2)
>>> r1 = jnp.array([0.1, 0.2, 0.3]) >>> r2 = jnp.array([0.4, 0.5, 0.6]) >>> func(r1, r2) Array(0.7872403, dtype=float64)
Improvements 🛠
Several changes have been made to reduce compile time:
Catalyst now decomposes non-differentiable gates when differentiating through workflows. Additionally, with
diff_method=parameter-shift, circuits are now verified to be fully compatible with Catalyst’s parameter-shift implementation before compilation. (#1562) (#1568) (#1569) (#1604)Gates that are constant, such as when all parameters are Python or NumPy data types, are not decomposed when this is allowable. For the adjoint differentiation method, this is allowable for the
StatePrep,BasisState, andQubitUnitaryoperations. For the parameter-shift method, this is allowable for all operations.An
mlir_optproperty has been added toqjitto access the optimized MLIR representation of a compiled function. This is the representation of the program after running everything in the MLIR stage of the entire pipeline. (#1579) (#1637)from catalyst import qjit @qjit def f(x): return x**2
>>> f(2) Array(4, dtype=int64) >>> print(f.mlir_opt) module @f { llvm.func @__catalyst__rt__finalize() llvm.func @__catalyst__rt__initialize(!llvm.ptr) llvm.func @_mlir_memref_to_llvm_alloc(i64) -> !llvm.ptr llvm.func @jit_f(%arg0: !llvm.ptr, %arg1: !llvm.ptr, %arg2: i64) -> !llvm.struct<(ptr, ptr, i64)> attributes {llvm.copy_memref, llvm.emit_c_interface} ... llvm.func @teardown() { llvm.call @__catalyst__rt__finalize() : () -> () llvm.return } }
The error messages that indicate invalid
scale_factorsincatalyst.mitigate_with_znehave been improved to be formatted properly. (#1603)
Bug fixes 🐛
Fixed the
argnumsparameter ofgradandvalue_and_gradbeing ignored. (#1478)All dialects are loaded preemptively. This allows third-party plugins to load their dialects. (#1584)
Fixed an issue where Catalyst could give incorrect results for circuits containing
qml.StatePrep. (#1491)Fixed an issue where using autograph in conjunction with catalyst passes caused a crash. (#1541)
Fixed an issue where using autograph in conjunction with catalyst pipeline caused a crash. (#1576)
Fixed an issue where using chained catalyst passes decorators caused a crash. (#1576)
Specialized handling for
pipelines was added. (#1599)Fixed an issue where using autograph with control/adjoint functions used on operator objects caused a crash. (#1605)
Fixed an issue where using pytrees inside a loop with autograph caused falling back to Python. (#1601)
For example, the following example will now be captured and executed properly with Autograph enabled:
from catalyst import qjit
def updateList(x):
return [x[0]+1, x[1]+2]
@qjit(autograph=True)
def fn(x):
for i in range(4):
x = updateList(x)
return x
>>> fn([1, 2])
[Array(5, dtype=int64), Array(10, dtype=int64)]
Closure variables are now supported with
gradandvalue_and_grad. (#1613)
Internal changes ⚙️
Pattern rewriting in the
quantum-to-ionlowering pass has been changed to use MLIR’s dialect conversion infrastructure. (#1442)Updated the call signature for the plxpr
qnode_primprimitive. (#1538)Update deprecated access to
QNode.execute_kwargs["mcm_config"]. Insteadpostselect_modeandmcm_methodshould be accessed instead. (#1452)from_plxprnow uses theqml.capture.PlxprInterpreterclass for reduced code duplication. (#1398)Improved the error message for invalid measurement in
adjoin()orctrl()region. (#1425)Replaced
ValueRangewithResultRangeandValuewithOpResultto better align with the semantics of**QubitResult()functions likegetNonCtrlQubitResults(). This change ensures clearer intent and usage. Also, thematchAndRewritefunction has improved by usingreplaceAllUsesWithinstead of aforloop. (#1426)Several changes for experimental support of trapped-ion OQD devices have been made, including:
The
get_c_interfacemethod has been added to the OQD device, which enables retrieval of the C++ implementation of the device from Python. This allowsqjitto accept an instance of the device and connect to its runtime. (#1420)The ion dialect has been improved to reduce redundant code generated, a string attribute
labelhas been added to Level, and the levels of a transition have changed fromLevelAttrtostring. (#1471)The region of a
ParallelProtocolOpis now always terminated with aion::YieldOpwith explicitly yielded SSA values. This ensures the op is well-formed, and improves readability. (#1475)Added a new pass called
convert-ion-to-llvmwhich lowers the Ion dialect to llvm dialect. This pass introduces oqd device specific stubs that will be implemented in oqd runtime including:@ __catalyst__oqd__pulse,@ __catalyst__oqd__ParallelProtocol. (#1466)The OQD device can now generate OpenAPL JSON specs during runtime. The oqd stubs
@ __catalyst__oqd__pulse, and@ __catalyst__oqd__ParallelProtocol, which are called in the llvm dialect after the aforementioned lowering ((#1466)), are defined to produce JSON specs that OpenAPL expects. (#1516)The OQD device has been moved from
frontend/catalyst/third_party/oqdtoruntime/lib/backend/oqd. An overall switch,ENABLE_OQD, is added to control the OQD build system from a single entry point. The switch isOFFby default, and OQD can be built from source viamake all ENABLE_OQD=ON, ormake runtime ENABLE_OQD=ON. (#1508)Ion dialect now supports phonon modes using
ion.modesoperation. (#1517)Rotation angles are normalized to avoid negative duration for pulses during ion dialect lowering. (#1517)
Catalyst now generates OpenAPL programs for Pennylane circuits of up to two qubits using the OQD device. (#1517)
The end-to-end compilation pipeline for OQD devices is available as an API function. (#1545)
The source code has been updated to comply with changes requested by black v25.1.0 (#1490)
Reverted
StaticCustomOpin favour of adding helper functionsisStatic(),getStaticParams()to theCustomOpwhich preserves the same functionality. More specifically, this reverts [#1387] and [#1396], modifies [#1489]. (#1558) (#1555)Updated the C++ standard in mlir layer from 17 to 20. (#1229)
Documentation 📝
Added more details to JAX integration documentation regarding the use of
.atwith multiple indices. (#1595)
Contributors ✍️
This release contains contributions from (in alphabetical order):
Joey Carter, Yushao Chen, Isaac De Vlugt, Zach Goldthorpe, Sengthai Heng, David Ittah, Rohan Nolan Lasrado, Christina Lee, Mehrdad Malekmohammadi, Erick Ochoa Lopez, Andrija Paurevic, Raul Torres, Paul Haochen Wang.