catalyst.passes.disentangle_swap

disentangle_swap(qnode)[source]

Specify that the -disentangle-SWAP MLIR compiler pass for simplifying SWAP gates should be applied to the decorated QNode during qjit() compilation.

Parameters:

fn (QNode) – the QNode to apply the disentangle SWAP compiler pass to

Return type:

QNode

Example

import pennylane as qml
from pennylane import numpy as np
from catalyst import qjit
from catalyst.debug import get_compilation_stage
from catalyst.passes import disentangle_swap

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

@qjit(keep_intermediate=True)
@disentangle_swap
@qml.qnode(dev)
def circuit():
    # first qubit in |1>
    qml.X(0)
    # second qubit in non-basis
    qml.RX(np.pi/4,1)
    qml.SWAP([0,1])
    return qml.state()
>>> circuit()
[0.+0.j  0.92387953+0.j  0.+0.j  0.-0.38268343j]

Note that the QNode will be unchanged in Python, and will continue to include keep SWAP gates 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() -> tensor<4xcomplex<f64>> attributes {llvm.emit_c_interface} {
        %0 = call @circuit_0() : () -> tensor<4xcomplex<f64>>
        return %0 : tensor<4xcomplex<f64>>
    }
    func.func public @circuit_0() -> tensor<4xcomplex<f64>> attributes {diff_method = "parameter-shift", llvm.linkage = #llvm.linkage<internal>, qnode} {
        %c0_i64 = arith.constant 0 : i64
        %cst = arith.constant 0.78539816339744828 : f64
        quantum.device["catalyst/utils/../lib/librtd_lightning.dylib", "LightningSimulator", "{'shots': 0, 'mcmc': False, 'num_burnin': 0, 'kernel_name': None}"]
        %0 = quantum.alloc( 2) : !quantum.reg
        %1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit
        %out_qubits = quantum.custom "PauliX"() %1 : !quantum.bit5
        %2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit
        %out_qubits_0 = quantum.custom "RX"(%cst) %2 : !quantum.bit
        %out_qubits_1 = quantum.custom "PauliX"() %out_qubits_0 : !quantum.bit
        %out_qubits_2:2 = quantum.custom "CNOT"() %out_qubits_1, %out_qubits : !quantum.bit, !quantum.bit
        %out_qubits_3:2 = quantum.custom "CNOT"() %out_qubits_2#1, %out_qubits_2#0 : !quantum.bit, !quantum.bit
        %3 = quantum.insert %0[ 0], %out_qubits_3#0 : !quantum.reg, !quantum.bit
        %4 = quantum.insert %3[ 1], %out_qubits_3#1 : !quantum.reg, !quantum.bit
        %5 = quantum.compbasis qreg %4 : !quantum.obs
        %6 = quantum.state %5 : tensor<4xcomplex<f64>>
        quantum.dealloc %4 : !quantum.reg
        quantum.device_release
        return %6 : tensor<4xcomplex<f64>>
    }
    func.func @setup() {
        quantum.init
        return
    }
    func.func @teardown() {
        quantum.finalize
        return
    }
}

It can be seen that the SWAP(0,1) has been replaced with the folliowing

%0 = quantum.alloc( 2) : !quantum.reg
%1 = quantum.extract %0[ 0] : !quantum.reg -> !quantum.bit
%out_qubits = quantum.custom "PauliX"() %1 : !quantum.bit5
%2 = quantum.extract %0[ 1] : !quantum.reg -> !quantum.bit
%out_qubits_0 = quantum.custom "RX"(%cst) %2 : !quantum.bit
%out_qubits_1 = quantum.custom "PauliX"() %out_qubits_0 : !quantum.bit
%out_qubits_2:2 = quantum.custom "CNOT"() %out_qubits_1, %out_qubits : !quantum.bit, !quantum.bit
%out_qubits_3:2 = quantum.custom "CNOT"() %out_qubits_2#1, %out_qubits_2#0 : !quantum.bit, !quantum.bit