catalyst.passes.cancel_inverses

cancel_inverses(fn=None)[source]

Specify that the -removed-chained-self-inverse MLIR compiler pass for cancelling two neighbouring self-inverse gates should be applied to the decorated QNode during qjit() compilation.

The full list of supported gates are as follows:

One-bit Gates: qml.Hadamard, qml.PauliX, qml.PauliY, qml.PauliZ

Two-bit Gates: qml.CNOT, qml.CY, qml.CZ, qml.SWAP

Three-bit Gates: - qml.Toffoli

Note

Unlike PennyLane circuit transformations, the QNode itself will not be changed or transformed by applying these decorators.

As a result, circuit inspection tools such as draw() will continue to display the circuit as written in Python.

To instead view the optimized circuit, the MLIR must be viewed after the "QuantumCompilationPass" stage via the get_compilation_stage() function.

Parameters

fn (QNode) – the QNode to apply the cancel inverses compiler pass to

Return type

QNode

Example

from catalyst.debug import get_compilation_stage
from catalyst.passes import cancel_inverses

dev = qml.device("lightning.qubit", wires=1)

@qjit(keep_intermediate=True)
@cancel_inverses
@qml.qnode(dev)
def circuit(x: float):
    qml.RX(x, wires=0)
    qml.Hadamard(wires=0)
    qml.Hadamard(wires=0)
    return qml.expval(qml.PauliZ(0))
>>> circuit(0.54)
Array(0.85770868, dtype=float64)

Note that the QNode will be unchanged in Python, and will continue to include self-inverse gates when inspected with Python (for example, with draw()).

To instead view the optimized circuit, the MLIR must be viewed after the "QuantumCompilationPass" stage:

>>> print(get_compilation_stage(circuit, stage="QuantumCompilationPass"))
module @circuit {
  func.func public @jit_circuit(%arg0: tensor<f64>) -> tensor<f64> attributes {llvm.emit_c_interface} {
    %0 = call @circuit(%arg0) : (tensor<f64>) -> tensor<f64>
    return %0 : tensor<f64>
  }
  func.func private @circuit(%arg0: tensor<f64>) -> tensor<f64> attributes {diff_method = "parameter-shift", llvm.linkage = #llvm.linkage<internal>, qnode} {
    quantum.device["catalyst/utils/../lib/librtd_lightning.dylib", "LightningSimulator", "{'shots': 0, 'mcmc': False, 'num_burnin': 0, 'kernel_name': None}"]
    %0 = quantum.alloc( 1) : !quantum.reg
    %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit
    %extracted = tensor.extract %arg0[] : tensor<f64>
    %out_qubits = quantum.custom "RX"(%extracted) %1 : !quantum.bit
    %2 = quantum.namedobs %out_qubits[ PauliZ] : !quantum.obs
    %3 = quantum.expval %2 : f64
    %from_elements = tensor.from_elements %3 : tensor<f64>
    %4 = quantum.insert %0[ 0], %out_qubits : !quantum.reg, !quantum.bit
    quantum.dealloc %4 : !quantum.reg
    quantum.device_release
    return %from_elements : tensor<f64>
  }
  func.func @setup() {
    quantum.init
    return
  }
  func.func @teardown() {
    quantum.finalize
    return
  }
}

It can be seen that both Hadamards have been cancelled, and the measurement directly follows the RX gate:

%out_qubits = quantum.custom "RX"(%extracted) %1 : !quantum.bit
%2 = quantum.namedobs %out_qubits[ PauliZ] : !quantum.obs
%3 = quantum.expval %2 : f64