-
Experimental integration of the PennyLane capture module is available. It currently only supports quantum gates, without control flow. (#1109)
To trigger the PennyLane pipeline for capturing the program as a JaxPR, one needs to simply set
experimental_capture=True
in the qjit decorator.import pennylane as qml from catalyst import qjit dev = qml.device("lightning.qubit", wires=1) @qjit(experimental_capture=True) @qml.qnode(dev) def circuit(): qml.Hadamard(0) qml.CNOT([0, 1]) return qml.expval(qml.Z(0))
-
Shot-vector support for Catalyst: Introduces support for shot-vectors in Catalyst, currently available for
qml.sample
measurements in thelightning.qubit
device. Shot-vectors now allow elements of the form((20, 5),)
, which is equivalent to(20,)*5
or(20, 20, 20, 20, 20)
. Furthermore, multipleqml.sample
calls can now be returned from the same program, and can be structured using Python containers. For example, a program can return a dictionary likereturn {"first": qml.sample(), "second": qml.sample()}
. (#1051)For example,
import pennylane as qml from catalyst import qjit dev = qml.device("lightning.qubit", wires=1, shots=((5, 2), 7)) @qjit @qml.qnode(dev) def circuit(): qml.Hadamard(0) return qml.sample()
>>> circuit() (Array([[0], [1], [0], [1], [1]], dtype=int64), Array([[0], [1], [1], [0], [1]], dtype=int64), Array([[1], [0], [1], [1], [0], [1],[0]], dtype=int64))
-
A new function
catalyst.passes.pipeline
allows the quantum circuit transformation pass pipeline for QNodes within a qjit-compiled workflow to be configured. (#1131)my_passes = { "cancel_inverses": {}, "my_circuit_transformation_pass": {"my-option" : "my-option-value"}, } dev = qml.device("lightning.qubit", wires=2) @pipeline(my_passes) @qnode(dev) def circuit(x): qml.RX(x, wires=0) return qml.expval(qml.PauliZ(0)) @qjit def fn(x): return jnp.sin(circuit(x ** 2))
pipeline
can also be used to specify different pass pipelines for different parts of the same qjit-compiled workflow:my_pipeline = { "cancel_inverses": {}, "my_circuit_transformation_pass": {"my-option" : "my-option-value"}, } my_other_pipeline = {"cancel_inverses": {}} @qjit def fn(x): circuit_pipeline = pipeline(my_pipeline)(circuit) circuit_other = pipeline(my_other_pipeline)(circuit) return jnp.abs(circuit_pipeline(x) - circuit_other(x))
For a list of available passes, please see the catalyst.passes module documentation.
The pass pipeline order and options can be configured globally for a qjit-compiled function, by using the
circuit_transform_pipeline
argument of the :func:~.qjit
decorator.my_passes = { "cancel_inverses": {}, "my_circuit_transformation_pass": {"my-option" : "my-option-value"}, } @qjit(circuit_transform_pipeline=my_passes) def fn(x): return jnp.sin(circuit(x ** 2))
Global and local (via
@pipeline
) configurations can coexist, however local pass pipelines will always take precedence over global pass pipelines.Available MLIR passes are now documented and available within the catalyst.passes module documentation.
-
A peephole merge rotations pass is now available in MLIR. It can be added to
catalyst.passes.pipeline
, or the Python functioncatalyst.passes.merge_rotations
can be directly called on aQNode
. (#1162) (#1206)Using the pipeline, one can run:
from catalys.passes import pipeline my_passes = { "merge_rotations": {} } @qjit(circuit_transform_pipeline=my_passes) @qml.qnode(qml.device("lightning.qubit", wires=1)) def g(x: float): qml.RX(x, wires=0) qml.RX(x, wires=0) qml.Hadamard(wires=0) return qml.expval(qml.PauliZ(0))
Using the python function, one can run:
from catalys.passes import merge_rotations @qjit @merge_rotations @qml.qnode(qml.device("lightning.qubit", wires=1)) def g(x: float): qml.RX(x, wires=0) qml.RX(x, wires=0) qml.Hadamard(wires=0) return qml.expval(qml.PauliZ(0))
-
Catalyst Autograph now supports updating a single index or a slice of JAX arrays using Python's array assignment operator syntax. (#769) (#1143)
Using operator assignment syntax in favor of
at...op
expressions is now possible for the following operations:x[i] += y
in favor ofx.at[i].add(y)
x[i] -= y
in favor ofx.at[i].add(-y)
x[i] *= y
in favor ofx.at[i].multiply(y)
x[i] /= y
in favor ofx.at[i].divide(y)
x[i] **= y
in favor ofx.at[i].power(y)
@qjit(autograph=True) def f(x): first_dim = x.shape[0] result = jnp.copy(x) for i in range(first_dim): result[i] *= 2 # This is now supported return result
>>> f(jnp.array([1, 2, 3])) Array([2, 4, 6], dtype=int64)
-
Implement a Catalyst runtime plugin that mocks out all functions in the QuantumDevice interface. (#1179)
-
Scalar tensors are eliminated from control flow operations in the program, and are replaced with bare scalars instead. This improves compilation time and memory usage at runtime by avoiding heap allocations and reducing the amount of instructions. (#1075)
A new MLIR pass
detensorize-scf
is added that works in conjuction with the existinglinalg-detensorize
pass to detensorize input programs. The IR generated by JAX wraps all values in the program in tensors, including scalars, leading to unnecessary memory allocations for programs compiled to CPU via MLIR to LLVM pipeline. -
Bufferization of
gradient.ForwardOp
andgradient.ReverseOp
now requires 3 steps:gradient-preprocessing
,gradient-bufferize
, andgradient-postprocessing
.gradient-bufferize
has a new rewrite forgradient.ReturnOp
. (#1139) -
The decorator
self_inverses
now supports all Hermitian Gates. (#1136)The full list of supported gates are as follows:
One-bit Gates:
Two-bit Gates:
Three-bit Gates: Toffoli
-
Support is expanded for backend devices that exculsively return samples in the measurement basis. Pre- and post-processing now allows
qjit
to be used on these devices withqml.expval
,qml.var
andqml.probs
measurements in addiiton toqml.sample
, using themeasurements_from_samples
transform. (#1106) -
Importing Catalyst will now pollute less of JAX's global variables by using
LoweringParameters
. (#1152) -
Compiling
qnode
s to asynchronous functions will no longer print to stderr in case of an error. (#645) -
Cached primitive lowerings is used instead of a custom cache structure. (#1159)
-
Calling gradients twice (with same GradParams) will now only lower to a single MLIR function. (#1172)
-
Samples on lightning.qubit/kokkos can now be seeded with
qjit(seed=...)
. (#1164) -
The compiler pass
-remove-chained-self-inverse
can now also cancel adjoints of arbitrary unitary operations (in addition to the named Hermitian gates). (#1186) (#1211)
-
Remove
static_size
field fromAbstractQreg
class. (#1113)This reverts a previous breaking change.
-
Nesting qnodes now raises an error. (#1176)
This is unlikely to affect users since only under certain conditions did nesting qnodes worked successfully.
-
Removes
debug.compile_from_mlir
. (#1181)Please use
debug.replace_ir
.
-
Fix a bug preventing the target of
qml.adjoint
andqml.ctrl
calls from being transformed by AutoGraph. (#1212) -
Resolve a bug where
mitigate_with_zne
does not work properly with shots and devices supporting only Counts and Samples (e.g. Qrack). (transform:measurements_from_sample
). (#1165) -
Resolve a bug in the
vmap
function when passing shapeless values to the target. (#1150) -
Fix error message displayed when using
qml.cond
on callables with arguments. (#1151) -
Fixes taking gradient of nested accelerate callbacks. (#1156)
-
Registers the func dialect as a requirement for running the scatter lowering pass. (#1216)
-
Remove deprecated pennylane code across the frontend. (#1168)
-
Update Enzyme to version
v0.0.149
. (#1142) -
Remove the
MemMemCpyOptPass
in llvm O2 (applied for Enzyme), this reduces bugs when running gradient like functions. (#1063) -
Functions with multiple tapes are now split with a new mlir pass
--split-multiple-tapes
, with one tape per function. The reset routine that makes a maeasurement between tapes and inserts a X gate if measured one is no longer used. (#1017) (#1130) -
Prefer creating new
qml.devices.ExecutionConfig
objects over using the globalqml.devices.DefaultExecutionConfig
. Doing so helps avoid unexpected bugs and test failures in case theDefaultExecutionConfig
object becomes modified from its original state. (#1137) -
Remove the old
QJITDevice
API. (#1138) -
The device capability loading mechanism has been moved into the
QJITDevice
constructor. (#1141) -
Several functions related to device capabilities have been refactored. (#1149)
In particular, the signatures of
get_device_capability
,catalyst_decompose
,catalyst_acceptance
, andQJITDevice.__init__
have changed, and thepennylane_operation_set
function has been removed entirely. -
Catalyst now generates nested modules denoting quantum programs. (#1144)
Similar to MLIR's
gpu.launch_kernel
function, Catalyst, now supports acall_function_in_module
. This allows Catalyst to call functions in modules and have modules denote a quantum kernel. This will allow for device specific optimizations and compilation pipelines.At the moment, no one is using this. This is just the necessary scaffolding to supporting device specific transformations. As such, the module will be inlined to preserve current semantics. However, in the future, we will explore lowering this nested module into other IRs/binary formats and lowering
call_function_in_module
to something that can dispatch calls to another runtime / VM.
This release contains contributions from (in alphabetical order):
Amintor Dusko, Joey Carter, Spencer Comin, Lillian M.A. Frederiksen, Sengthai Heng, David Ittah, Mehrdad Malekmohammadi, Vincent Michaud-Rioux, Romain Moyard, Erick Ochoa Lopez, Daniel Strano, Raul Torres, Paul Haochen Wang.