Skip to content

Releases: PennyLaneAI/catalyst

Catalyst v0.3.2

08 Nov 17:21
eac6d11
Compare
Choose a tag to compare

New features

  • The experimental AutoGraph feature now supports Python while loops, allowing native Python loops to be captured and compiled with Catalyst. (#318)

    dev = qml.device("lightning.qubit", wires=4)
    
    @qjit(autograph=True)
    @qml.qnode(dev)
    def circuit(n: int, x: float):
        i = 0
    
        while i < n:
            qml.RX(x, wires=i)
            i += 1
    
        return qml.expval(qml.PauliZ(0))
    >>> circuit(4, 0.32)
    array(0.94923542)

    This feature extends the existing AutoGraph support for Python for loops and if statements introduced in v0.3. Note that TensorFlow must be installed for AutoGraph support.

    For more details, please see the AutoGraph guide.

  • In addition to loops and conditional branches, AutoGraph now supports native Python and, or and not operators in Boolean expressions. (#325)

    dev = qml.device("lightning.qubit", wires=1)
    
    @qjit(autograph=True)
    @qml.qnode(dev)
    def circuit(x: float):
    
        if x >= 0 and x < jnp.pi:
            qml.RX(x, wires=0)
    
        return qml.probs()
    >>> circuit(0.43)
    array([0.95448287, 0.04551713])
    >>> circuit(4.54)
    array([1., 0.])

    Note that logical Boolean operators will only be captured by AutoGraph if all operands are dynamic variables (that is, a value known only at runtime, such as a measurement result or function argument). For other use cases, it is recommended to use the jax.numpy.logical_* set of functions where appropriate.

  • Debug compiled programs and print dynamic values at runtime with debug.print (#279) (#356)

    You can now print arbitrary values from your running program, whether they are arrays, constants, strings, or abitrary Python objects. Note that while non-array Python objects will be printed at runtime, their string representation is captured at compile time, and thus will always be the same regardless of program inputs. The output for arrays optionally includes a descriptor for how the data is stored in memory ("memref").

    @qjit
    def func(x: float):
        debug.print(x, memref=True)
        debug.print("exit")
    >>> func(jnp.array(0.43))
    MemRef: base@ = 0x5629ff2b6680 rank = 0 offset = 0 sizes = [] strides = [] data =
    0.43
    exit
  • Catalyst now officially supports macOS X86_64 devices, with macOS binary wheels available for both AARCH64 and X86_64. (#347) (#313)

  • It is now possible to dynamically load third-party Catalyst compatible devices directly into a pre-installed Catalyst runtime on Linux. (#327)

    To take advantage of this, third-party devices must implement the Catalyst::Runtime::QuantumDevice interface, in addition to defining the following method:

    extern "C" Catalyst::Runtime::QuantumDevice*
    getCustomDevice() { return new CustomDevice(); }

    This support can also be integrated into existing PennyLane Python devices that inherit from the QuantumDevice class, by defining the get_c_interface static method.

    For more details, see the custom devices documentation.

Improvements

  • Return values of conditional functions no longer need to be of exactly the same type. Type promotion is automatically applied to branch return values if their types don't match. (#333)

    @qjit
    def func(i: int, f: float):
    
        @cond(i < 3)
        def cond_fn():
            return i
    
        @cond_fn.otherwise
        def otherwise():
            return f
    
        return cond_fn()
    >>> func(1, 4.0)
    array(1.0)

    Automatic type promotion across conditional branches also works with AutoGraph:

    @qjit(autograph=True)
    def func(i: int, f: float):
    
        if i < 3:
            i = i
        else:
            i = f
    
        return i
    >>> func(1, 4.0)
    array(1.0)
  • AutoGraph now supports converting functions even when they are invoked through functional wrappers such as adjoint, ctrl, grad, jacobian, etc. (#336)

    For example, the following should now succeed:

    def inner(n):
      for i in range(n):
        qml.T(i)
    
    @qjit(autograph=True)
    @qml.qnode(dev)
    def f(n: int):
        adjoint(inner)(n)
        return qml.state()
  • To prepare for Catalyst's frontend being integrated with PennyLane, the appropriate plugin entry point interface has been added to Catalyst. (#331)

    For any compiler packages seeking to be registered in PennyLane, the entry_points metadata under the the group name pennylane.compilers must be added, with the following try points:

    • context: Path to the compilation evaluation context manager. This context manager should have the method context.is_tracing(), which returns True if called within a program that is being traced or captured.

    • ops: Path to the compiler operations module. This operations module may contain compiler specific versions of PennyLane operations. Within a JIT context, PennyLane operations may dispatch to these.

    • qjit: Path to the JIT compiler decorator provided by the compiler. This decorator should have the signature qjit(fn, *args, **kwargs), where fn is the function to be compiled.

  • The compiler driver diagnostic output has been improved, and now includes failing IR as well as the names of failing passes. (#349)

  • The scatter operation in the Catalyst dialect now uses an SCF for loop to avoid ballooning the compiled code. (#307)

  • The CopyGlobalMemRefPass pass of our MLIR processing pipeline now supports dynamically shaped arrays. (#348)

  • The Catalyst utility dialect is now included in the Catalyst MLIR C-API. (#345)

  • Fix an issue with the AutoGraph conversion system that would prevent the fallback to Python from working correctly in certain instances. (#352)

    The following type of code is now supported:

    @qjit(autograph=True)
    def f():
      l = jnp.array([1, 2])
      for _ in range(2):
          l = jnp.kron(l, l)
      return l

Breaking changes

  • The axis ordering for catalyst.jacobian is updated to match jax.jacobian. Assuming we have parameters of shape [a,b] and results of shape [c,d], the returned Jacobian will now have shape [c, d, a, b] instead of [a, b, c, d]. (#283)

Bug fixes

  • An upstream change in the PennyLane-Lightning project was addressed to prevent compilation issues in the StateVectorLQubitDynamic class in the runtime. The issue was introduced in #499. (#322)

  • The requirements.txt file to build Catalyst from source has been updated with a minimum pip version, >=22.3. Previous versions of pip are unable to perform editable installs when the system-wide site-packages are read-only, even when the --user flag is provided. (#311)

  • The frontend has been updated to make it compatible with PennyLane MeasurementProcess objects now being PyTrees in PennyLane version 0.33. (#315)

Contributors

This release contains contributions from (in alphabetical order):

Ali Asadi,
David Ittah,
Sergei Mironov,
Romain Moyard,
Erick Ochoa Lopez.

Catalyst v0.3.1-post1

20 Oct 16:01
a1c35c5
Compare
Choose a tag to compare

This post-release updates the docs to include the AutoGraph guide.

Catalyst v0.3.1

17 Oct 15:55
Compare
Choose a tag to compare

New features

  • The experimental AutoGraph feature, now supports Python for loops, allowing native Python loops to be captured and compiled with Catalyst. (#258)

    dev = qml.device("lightning.qubit", wires=n)
    
    @qjit(autograph=True)
    @qml.qnode(dev)
    def f(n):
        for i in range(n):
            qml.Hadamard(wires=i)
    
        return qml.expval(qml.PauliZ(0))

    This feature extends the existing AutoGraph support for Python if statements introduced in v0.3. Note that TensorFlow must be installed for AutoGraph support.

  • The quantum control operation can now be used in conjunction with Catalyst control flow, such as loops and conditionals, via the new catalyst.ctrl function. (#282)

    Similar in behaviour to the qml.ctrl control modifier from PennyLane, catalyst.ctrl can additionally wrap around quantum functions which contain control flow, such as the Catalyst cond, for_loop, and while_loop primitives.

    @qjit
    @qml.qnode(qml.device("lightning.qubit", wires=4))
    def circuit(x):
    
        @for_loop(0, 3, 1)
        def repeat_rx(i):
            qml.RX(x / 2, wires=i)
    
        catalyst.ctrl(repeat_rx, control=3)()
    
        return qml.expval(qml.PauliZ(0))
    >>> circuit(0.2)
    array(1.)
  • Catalyst now supports JAX's array.at[index] notation for array element assignment and updating. (#273)

    @qjit
    def add_multiply(l: jax.core.ShapedArray((3,), dtype=float), idx: int):
        res = l.at[idx].multiply(3)
        res2 = l.at[idx].add(2)
        return res + res2
    
    res = add_multiply(jnp.array([0, 1, 2]), 2)
    >>> res
    [0, 2, 10]

    For more details on available methods, see the JAX documentation.

Improvements

  • A new compiler driver has been implemented in C++. This improves compile-time performance by avoiding round-tripping, which is when the entire program being compiled is dumped to a textual form and re-parsed by another tool.

    This is also a requirement for providing custom metadata at the LLVM level, which is necessary for better integration with tools like Enzyme. Finally, this makes it more natural to improve error messages originating from C++ when compared to the prior subprocess-based approach. (#216)

  • Support the braket.devices.Devices enum class and s3_destination_folder device options for AWS Braket remote devices. (#278)

  • Improvements have been made to the build process, including avoiding unnecessary processes such as removing opt and downloading the wheel. (#298)

  • Remove a linker warning about duplicate rpaths when Catalyst wheels are installed on macOS. (#314)

Bug fixes

  • Fix incompatibilities with GCC on Linux introduced in v0.3.0 when compiling user programs. Due to these, Catalyst v0.3.0 only works when clang is installed in the user environment.

    • Resolve an issue with an empty linker flag, causing ld to error. (#276)

    • Resolve an issue with undefined symbols provided the Catalyst runtime. (#316)

  • Remove undocumented package dependency on the zlib/zstd compression library. (#308)

  • Fix filesystem issue when compiling multiple functions with the same name and keep_intermediate=True. (#306)

  • Add support for applying the adjoint operation to QubitUnitary gates. QubitUnitary was not able to be adjointed when the variable holding the unitary matrix might change. This can happen, for instance, inside of a for loop. To solve this issue, the unitary matrix gets stored in the array list via push and pops. The unitary matrix is later reconstructed from the array list and QubitUnitary can be executed in the adjointed context. (#304) (#310)

Contributors

This release contains contributions from (in alphabetical order):

Ali Asadi,
David Ittah,
Erick Ochoa Lopez,
Jacob Mai Peng,
Sergei Mironov,
Romain Moyard.

Catalyst v0.3.0

01 Sep 17:40
4dc143d
Compare
Choose a tag to compare

New features

  • Catalyst now officially supports macOS ARM devices, such as Apple M1/M2 machines, with macOS binary wheels available on PyPI. For more details on the changes involved to support macOS, please see the improvements section. (#229) (#232) (#233) (#234)

  • Write Catalyst-compatible programs with native Python conditional statements. (#235)

    AutoGraph is a new, experimental, feature that automatically converts Python conditional statements like if, else, and elif, into their equivalent functional forms provided by Catalyst (such as catalyst.cond).

    This feature is currently opt-in, and requires setting the autograph=True flag in the qjit decorator:

    dev = qml.device("lightning.qubit", wires=1)
    
    @qjit(autograph=True)
    @qml.qnode(dev)
    def f(x):
        if x < 0.5:
            qml.RY(jnp.sin(x), wires=0)
        else:
            qml.RX(jnp.cos(x), wires=0)
    
        return qml.expval(qml.PauliZ(0))

    The implementation is based on the AutoGraph module from TensorFlow, and requires a working TensorFlow installation be available. In addition, Python loops (for and while) are not yet supported, and do not work in AutoGraph mode.

    Note that there are some caveats when using this feature especially around the ues of global variables or object mutation inside of methods. A functional style is always recommended when using qjit or AutoGraph.

  • The quantum adjoint operation can now be used in conjunction with Catalyst control flow, such as loops and conditionals. For this purpose a new instruction, catalyst.adjoint, has been added. (#220)

    catalyst.adjoint can wrap around quantum functions which contain the Catalyst cond, for_loop, and while_loop primitives. Previously, the usage of qml.adjoint on functions with these primitives would result in decomposition errors. Note that a future release of Catalyst will
    merge the behaviour of catalyst.adjoint into qml.adjoint for convenience.

    dev = qml.device("lightning.qubit", wires=3)
    
    @qjit
    @qml.qnode(dev)
    def circuit(x):
    
        @for_loop(0, 3, 1)
        def repeat_rx(i):
            qml.RX(x / 2, wires=i)
    
        adjoint(repeat_rx)()
    
        return qml.expval(qml.PauliZ(0))
    >>> circuit(0.2)
    array(0.99500417)

    Additionally, the ability to natively represent the adjoint construct in Catalyst's program representation (IR) was added.

  • QJIT-compiled programs now support (nested) container types as inputs and outputs of compiled functions. This includes lists and dictionaries, as well as any data structure implementing the PyTree protocol. (#215) (#221)

    For example, a program that accepts and returns a mix of dictionaries, lists, and tuples:

    @qjit
    def workflow(params1, params2):
        res1 = params1["a"][0][0] + params2[1]
        return {"y1": jnp.sin(res1), "y2": jnp.cos(res1)}
    >>> params1 = {"a": [[0.1], 0.2]}
    >>> params2 = (0.6, 0.8)
    >>> workflow(params1, params2)
    array(0.78332691)
  • Compile-time backpropagation of arbitrary hybrid programs is now supported, via integration with Enzyme AD. (#158) (#193) (#224) (#225) (#239) (#244)

    This allows catalyst.grad to differentiate hybrid functions that contain both classical pre-processing (inside & outside of QNodes), QNodes, as well as classical post-processing (outside of QNodes) via a combination of backpropagation and quantum gradient methods.

    The new default for the differentiation method attribute in catalyst.grad has been changed to "auto", which performs Enzyme-based reverse mode AD on classical code, in conjunction with the quantum diff_method specified on each QNode:

    dev = qml.device("lightning.qubit", wires=1)
    
    @qml.qnode(dev, diff_method="parameter-shift")
    def circuit(theta):
        qml.RX(jnp.exp(theta ** 2) / jnp.cos(theta / 4), wires=0)
        return qml.expval(qml.PauliZ(wires=0))
    >>> grad = qjit(catalyst.grad(circuit, method="auto"))
    >>> grad(jnp.pi)
    array(0.05938718)

    The reworked differentiation pipeline means you can now compute exact derivatives of programs with both classical pre- and post-processing, as shown below:

    @qml.qnode(qml.device("lightning.qubit", wires=1), diff_method="adjoint")
    def circuit(theta):
        qml.RX(jnp.exp(theta ** 2) / jnp.cos(theta / 4), wires=0)
        return qml.expval(qml.PauliZ(wires=0))
    
    def loss(theta):
        return jnp.pi / jnp.tanh(circuit(theta))
    
    @qjit
    def grad_loss(theta):
        return catalyst.grad(loss)(theta)
    >>> grad_loss(1.0)
    array(-1.90958669)

    You can also use multiple QNodes with different differentiation methods:

    @qml.qnode(qml.device("lightning.qubit", wires=1), diff_method="parameter-shift")
    def circuit_A(params):
        qml.RX(jnp.exp(params[0] ** 2) / jnp.cos(params[1] / 4), wires=0)
        return qml.probs()
    
    @qml.qnode(qml.device("lightning.qubit", wires=1), diff_method="adjoint")
    def circuit_B(params):
        qml.RX(jnp.exp(params[1] ** 2) / jnp.cos(params[0] / 4), wires=0)
        return qml.expval(qml.PauliZ(wires=0))
    
    def loss(params):
        return jnp.prod(circuit_A(params)) + circuit_B(params)
    
    @qjit
    def grad_loss(theta):
        return catalyst.grad(loss)(theta)
    >>> grad_loss(jnp.array([1.0, 2.0]))
    array([ 0.57367285, 44.4911605 ])

    And you can differentiate purely classical functions as well:

    def square(x: float):
        return x ** 2
    
    @qjit
    def dsquare(x: float):
        return catalyst.grad(square)(x)
    >>> dsquare(2.3)
    array(4.6)

    Note that the current implementation of reverse mode AD is restricted to 1st order derivatives, but you can still use catalyst.grad(method="fd") is still available to perform a finite differences approximation of any differentiable function.

  • Add support for the new PennyLane arithmetic operators. (#250)

    PennyLane is in the process of replacing Hamiltonian and Tensor observables with a set of general arithmetic operators. These consist of Prod, Sum and SProd.

    By default, using dunder methods (eg. +, -, @, *) to combine operators with scalars or other operators will create Hamiltonian and Tensor objects. However, these two methods will be deprecated in coming releases of PennyLane.

    To enable the new arithmetic operators, one can use Prod, Sum, and Sprod directly or activate them by calling enable_new_opmath at the beginning of your PennyLane program.

    dev = qml.device("lightning.qubit", wires=2)
    
    @qjit
    @qml.qnode(dev)
    def circuit(x: float, y: float):
        qml.RX(x, wires=0)
        qml.RX(y, wires=1)
        qml.CNOT(wires=[0, 1])
        return qml.expval(0.2 * qml.PauliX(wires=0) - 0.4 * qml.PauliY(wires=1))
    >>> qml.operation.enable_new_opmath()
    >>> qml.operation.active_new_opmath()
    True
    >>> circuit(np.pi / 4, np.pi / 2)
    array(0.28284271)

Improvements

  • Better support for Hamiltonian observables:

    • Allow Hamiltonian observables with integer coefficients. (#248)

      For example, compiling the following circuit wasn't previously allowed, but is now supported in Catalyst:

      dev = qml.device("lightning.qubit", wires=2)
      
      @qjit
      @qml.qnode(dev)
      def circuit(x: float, y: float):
          qml.RX(x, wires=0)
          qml.RY(y, wires=1)
      
          coeffs = [1, 2]
          obs = [qml.PauliZ(0), qml.PauliZ(1)]
          return qml.expval(qml.Hamiltonian(coeffs, obs))
    • Allow nested Hamiltonian observables. (#255)

      @qjit
      @qml.qnode(qml.device("lightning.qubit", wires=3))
      def circuit(x, y, coeffs1, coeffs2):
          qml.RX(x, wires=0)
          qml.RX(y, wires=1)
          qml.RY(x + y, wires=2)
      
          obs = [
              qml.PauliX(0) @ qml.PauliZ(1),
              qml.Hamiltonian(coeffs1, [qml.PauliZ(0) @ qml.Hadamard(2)]),
          ]
      
          return qml.var(qml.Hamiltonian(coeffs2, obs))
  • Various performance improvements:

    • The execution and compile time of programs has been reduced, by generating more efficient code and avoiding unnecessary optimizations. Specifically, a scalarization procedure was added to the MLIR pass pipeline, and LLVM IR compilation is now invoked with optimization level 0. (#217)

    • The execution time of compile...

Read more

Catalyst v0.2.1

14 Jul 09:23
b9193e4
Compare
Choose a tag to compare

Bug fixes

  • Add missing OpenQASM backend in binary distribution, which relies on the latest version of the AWS Braket plugin for PennyLane to resolve dependency issues between the plugin, Catalyst, and PennyLane. The Lightning-Kokkos backend with Serial and OpenMP modes is also added to the binary distribution. #198

Improvements

  • When using OpenQASM-based devices the string representation of the circuit is printed on exception. #199

  • Use pybind11::module interface library instead of pybind11::embed in the runtime for OpenQasm backend to avoid linking to the python library at compile time. #200

Contributors

This release contains contributions from (in alphabetical order):

Ali Asadi, David Ittah.

Catalyst v0.2.0

11 Jul 10:11
0602bdc
Compare
Choose a tag to compare

New features

  • Catalyst programs can now be used inside of a larger JAX workflow which uses JIT compilation, automatic differentiation, and other JAX transforms. #96 #123 #167 #192

    For example, call a Catalyst qjit-compiled function from within a JAX jit-compiled function:

    dev = qml.device("lightning.qubit", wires=1)
    
    @qjit
    @qml.qnode(dev)
    def circuit(x):
        qml.RX(jnp.pi * x[0], wires=0)
        qml.RY(x[1] ** 2, wires=0)
        qml.RX(x[1] * x[2], wires=0)
        return qml.probs(wires=0)
    
    @jax.jit
    def cost_fn(weights):
        x = jnp.sin(weights)
        return jnp.sum(jnp.cos(circuit(x)) ** 2)
    >>> cost_fn(jnp.array([0.1, 0.2, 0.3]))
    Array(1.32269195, dtype=float64)

    Catalyst-compiled functions can now also be automatically differentiated via JAX, both in forward and reverse mode to first-order,

    >>> jax.grad(cost_fn)(jnp.array([0.1, 0.2, 0.3]))
    Array([0.49249037, 0.05197949, 0.02991883], dtype=float64)

    as well as vectorized using jax.vmap:

    >>> jax.vmap(cost_fn)(jnp.array([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]))
    Array([1.32269195, 1.53905377], dtype=float64)

    In particular, this allows for a reduction in boilerplate when using JAX-compatible optimizers such as jaxopt:

    >>> opt = jaxopt.GradientDescent(cost_fn)
    >>> params = jnp.array([0.1, 0.2, 0.3])
    >>> (final_params, _) = jax.jit(opt.run)(params)
    >>> final_params
    Array([-0.00320799,  0.03475223,  0.29362844], dtype=float64)

    Note that, in general, best performance will be seen when the Catalyst @qjit decorator is used to JIT the entire hybrid workflow. However, there may be cases where you may want to delegate only the quantum part of your workflow to Catalyst, and let JAX handle classical components (for example, due to missing a feature or compatibility issue in Catalyst).

  • Support for Amazon Braket devices provided via the PennyLane-Braket plugin. #118 #139 #179 #180

    This enables quantum subprograms within a JIT-compiled Catalyst workflow to execute on Braket simulator and hardware devices, including remote cloud-based simulators such as SV1.

    def circuit(x, y):
        qml.RX(y * x, wires=0)
        qml.RX(x * 2, wires=1)
        return qml.expval(qml.PauliY(0) @ qml.PauliZ(1))
    
    @qjit
    def workflow(x: float, y: float):
        device = qml.device("braket.local.qubit", backend="braket_sv", wires=2)
        g = qml.qnode(device)(circuit)
        h = catalyst.grad(g)
        return h(x, y)
    
    workflow(1.0, 2.0)

    For a list of available devices, please see the PennyLane-Braket documentation.

    Internally, the quantum instructions are generating OpenQASM3 kernels at runtime; these are then executed on both local (braket.local.qubit) and remote (braket.aws.qubit) devices backed by Amazon Braket Python SDK, with measurement results then propagated back to the frontend.

    Note that at initial release, not all Catalyst features are supported with Braket. In particular, dynamic circuit features, such as mid-circuit measurements, will not work with Braket devices.

  • Catalyst conditional functions defined via @catalyst.cond now support an arbitrary number of 'else if' chains. #104

    dev = qml.device("lightning.qubit", wires=1)
    
    @qjit
    @qml.qnode(dev)
    def circuit(x):
    
        @catalyst.cond(x > 2.7)
        def cond_fn():
            qml.RX(x, wires=0)
    
        @cond_fn.else_if(x > 1.4)
        def cond_elif():
            qml.RY(x, wires=0)
    
        @cond_fn.otherwise
        def cond_else():
            qml.RX(x ** 2, wires=0)
    
        cond_fn()
    
        return qml.probs(wires=0)
  • Iterating in reverse is now supported with constant negative step sizes via catalyst.for_loop. #129

    dev = qml.device("lightning.qubit", wires=1)
    
    @qjit
    @qml.qnode(dev)
    def circuit(n):
    
        @catalyst.for_loop(n, 0, -1)
        def loop_fn(_):
            qml.PauliX(0)
    
        loop_fn()
        return measure(0)
  • Additional gradient transforms for computing the vector-Jacobian product (VJP) and Jacobian-vector product (JVP) are now available in Catalyst. #98

    Use catalyst.vjp to compute the forward-pass value and VJP:

    @qjit
    def vjp(params, cotangent):
        def f(x):
            y = [jnp.sin(x[0]), x[1] ** 2, x[0] * x[1]]
            return jnp.stack(y)
    
        return catalyst.vjp(f, [params], [cotangent])
    >>> x = jnp.array([0.1, 0.2])
    >>> dy = jnp.array([-0.5, 0.1, 0.3])
    >>> vjp(x, dy)
    [array([0.09983342, 0.04      , 0.02      ]),
     array([-0.43750208,  0.07000001])]

    Use catalyst.jvp to compute the forward-pass value and JVP:

    @qjit
    def jvp(params, tangent):
        def f(x):
            y = [jnp.sin(x[0]), x[1] ** 2, x[0] * x[1]]
            return jnp.stack(y)
    
        return catalyst.jvp(f, [params], [tangent])
    >>> x = jnp.array([0.1, 0.2])
    >>> tangent = jnp.array([0.3, 0.6])
    >>> jvp(x, tangent)
    [array([0.09983342, 0.04      , 0.02      ]),
     array([0.29850125, 0.24000006, 0.12      ])]
  • Support for multiple backend devices within a single qjit-compiled function is now available. #89

    For example, if you compile the Catalyst runtime with lightning.kokkos support (via the compilation flag ENABLE_LIGHTNING_KOKKOS=ON), you can use lightning.qubit and lightning.kokkos within a singular workflow:

    dev1 = qml.device("lightning.qubit", wires=1)
    dev2 = qml.device("lightning.kokkos", wires=1)
    
    @qml.qnode(dev1)
    def circuit1(x):
        qml.RX(jnp.pi * x[0], wires=0)
        qml.RY(x[1] ** 2, wires=0)
        qml.RX(x[1] * x[2], wires=0)
        return qml.var(qml.PauliZ(0))
    
    @qml.qnode(dev2)
    def circuit2(x):
    
        @catalyst.cond(x > 2.7)
        def cond_fn():
            qml.RX(x, wires=0)
    
        @cond_fn.otherwise
        def cond_else():
            qml.RX(x ** 2, wires=0)
    
        cond_fn()
    
        return qml.probs(wires=0)
    
    @qjit
    def cost(x):
        return circuit2(circuit1(x))
    >>> x = jnp.array([0.54, 0.31])
    >>> cost(x)
    array([0.80842369, 0.19157631])
  • Support for returning the variance of Hamiltonians, Hermitian matrices, and Tensors via qml.var has been added. #124

    dev = qml.device("lightning.qubit", wires=2)
    
    @qjit
    @qml.qnode(dev)
    def circuit(x):
        qml.RX(jnp.pi * x[0], wires=0)
        qml.RY(x[1] ** 2, wires=1)
        qml.CNOT(wires=[0, 1])
        qml.RX(x[1] * x[2], wires=0)
        return qml.var(qml.PauliZ(0) @ qml.PauliX(1))
    >>> x = jnp.array([0.54, 0.31])
    >>> circuit(x)
    array(0.98851544)

Breaking changes

  • The catalyst.grad function now supports using the differentiation method defined on the QNode (via the diff_method argument) rather than applying a global differentiation method. #163

    As part of this change, the method argument now accepts the following options:

    • method="defer": Quantum components of the hybrid function are differentiated according to the corresponding QNode diff_method, while the classical computation is differentiated using traditional auto-diff.

      With this strategy, Catalyst only currently supports QNodes with diff_method="param-shift" and diff_method="adjoint"`.

    • method="fd": First-order finite-differences for the entire hybrid function. The diff_method argument for each QNode is ignored.

    This is an intermediate step towards differentiating functions that internally call multiple QNodes, and towards supporting differentiation of classical postprocessing.

Improvements

  • Catalyst has been upgraded to work with JAX v0.4.13. #143 #185

  • Add a Backprop operation for using autodifferentiation (AD) at the LLVM level with Enzyme AD. The Backprop operations has a bufferization pattern and a lowering to LLVM. #107 #116

  • Error handling has been improved. The runtime now throws more descriptive and unified expressions for runtime errors and assertions. #92

  • In preparation for easier debugging, the compiler has been refactored to allow easy prototyping of new compilation pipelines. #38

    In the future, this will allow the ability to generate MLIR or LLVM-IR by loading input from a string or file, rather than generating it from Python.

    As part of this refactor, the following changes were made:

    • Passes are now classes. This allow developers/users looking to change flags to inherit from these passes and change the flags.

    • Passes are now passed as arguments to the compiler. Custom passes can just be passed to the compiler as an argum...

Read more

Catalyst v0.1.2

04 Apr 18:21
Compare
Choose a tag to compare

New features

  • Add an option to print verbose messages explaining the compilation process
    #68

  • Allow catalyst.grad to be used on any traceable function (within a qjit context).
    This means the operation is no longer resticted to acting on qml.qnodes only.
    #75

Improvements

  • Work in progress on a Lightning-Kokkos backend:

    Bring feature parity to the Lightning-Kokkos backend simulator.
    #55

    Add support for variance measurements for all observables.
    #70

  • Build the runtime against qir-stdlib v0.1.0.
    #58

  • Replace input-checking assertions with exceptions.
    #67

  • Perform function inlining to improve optimizations and memory management within the compiler.
    #72

Breaking changes

Bug fixes

  • Several fixes to address memory leaks in the compiled program:

    Fix memory leaks from data that flows back into the Python environment.
    #54

    Fix memory leaks resulting from partial bufferization at the MLIR level. This fix makes the
    necessary changes to reintroduce the -buffer-deallocation pass into the MLIR pass pipeline.
    The pass guarantees that all allocations contained within a function (that is allocations that are
    not returned from a function) are also deallocated.
    #61

    Lift heap allocations for quantum op results from the runtime into the MLIR compiler core. This
    allows all memref buffers to be memory managed in MLIR using the
    MLIR bufferization infrastructure.
    #63

    Eliminate all memory leaks by tracking memory allocations at runtime. The memory allocations
    which are still alive when the compiled function terminates, will be freed in the
    finalization / teardown function.
    #78

  • Fix returning complex scalars from the compiled function.
    #77

Contributors

This release contains contributions from (in alphabetical order):

Ali Asadi,
David Ittah,
Erick Ochoa Lopez,
Sergei Mironov.

Catalyst v0.1.1

07 Mar 04:31
4c3f3b9
Compare
Choose a tag to compare

New features

  • Adds support for interpreting control flow operations.
    #31

Improvements

  • Adds fallback compiler drivers to increase reliability during linking phase. Also adds support for a
    CATALYST_CC environment variable for manual specification of the compiler driver used for linking.
    #30

Breaking changes

Bug fixes

  • Fixes to codecov, build-lightning, and GH actions.
    #34

  • Fixes the Catalyst image path in the readme to properly render on PyPI.

Contributors

This release contains contributions from (in alphabetical order):

Ali Asadi,
Erick Ochoa Lopez.

Catalyst v0.1.0

01 Mar 17:20
Compare
Choose a tag to compare

Initial public release.

Contributors

This release contains contributions from (in alphabetical order):

Ali Asadi,
Sam Banning,
David Ittah,
Josh Izaac,
Erick Ochoa Lopez,
Sergei Mironov,
Isidor Schoch.