catalyst.passes.ppm_compilation

ppm_compilation(qnode=None, *, decompose_method='auto-corrected', avoid_y_measure=False, max_pauli_size=0)[source]

Specify that the MLIR compiler pass for transforming clifford+T gates into Pauli Product Measurements (PPM) will be applied.

This pass combines multiple sub-passes:

The avoid_y_measure and decompose_method arguments are passed to the ppr_to_ppm() pass. The max_pauli_size argument is passed to the commute_ppr() and merge_ppr_ppm() passes.

Parameters:
  • qnode (QNode, optional) – QNode to apply the pass to. If None, returns a decorator.

  • decompose_method (str, optional) – The method to use for decomposing non-Clifford PPRs. Options are "auto-corrected" and "clifford-corrected". Defaults to "auto-corrected". "auto-corrected" uses an additional measurement for correction. "clifford-corrected" uses a Clifford rotation for correction.

  • avoid_y_measure (bool) – Rather than performing a Pauli-Y measurement for Clifford rotations (sometimes more costly), a \(Y\) state (\(Y\vert 0 \rangle\)) is used instead (requires \(Y\) state preparation). Defaults to False.

  • max_pauli_size (int) – The maximum size of the Pauli strings after commuting or merging. Defaults to 0 (no limit).

Returns:

Returns decorated QNode if qnode is provided, otherwise returns a decorator.

Return type:

QNode or callable

Example

If a merging resulted in a PPM acting on more than max_pauli_size qubits (here, max_pauli_size = 2), that merging would be skipped. However, when decomposed into PPMs, at least one qubit will be applied, so the final PPMs will act on at least one additional qubit.

import pennylane as qml
from catalyst import qjit, measure
from catalyst.passes import ppm_compilation

pipeline = [("pipe", ["enforce-runtime-invariants-pipeline"])]
method = "clifford-corrected"

@qjit(pipelines=pipeline, target="mlir")
@ppm_compilation(decompose_method=method, avoid_y_measure=True, max_pauli_size=2)
@qml.qnode(qml.device("null.qubit", wires=2))
def circuit():
    qml.CNOT([0, 1])
    qml.CNOT([1, 0])
    qml.adjoint(qml.T)(0)
    qml.T(1)
    return measure(0), measure(1)

print(circuit.mlir_opt)

Example MLIR Representation:

. . .
%m, %out:3 = qec.ppm ["Z", "Z", "Z"] %1, %2, %4 : !quantum.bit, !quantum.bit, !quantum.bit
%m_0, %out_1:2 = qec.ppm ["Z", "Y"] %3, %out#2 : !quantum.bit, !quantum.bit
%m_2, %out_3 = qec.ppm ["X"] %out_1#1 : !quantum.bit
%m_4, %out_5 = qec.select.ppm(%m, ["X"], ["Z"]) %out_1#0 : !quantum.bit
%5 = arith.xori %m_0, %m_2 : i1
%6:2 = qec.ppr ["Z", "Z"](2) %out#0, %out#1 cond(%5) : !quantum.bit, !quantum.bit
quantum.dealloc_qb %out_5 : !quantum.bit
quantum.dealloc_qb %out_3 : !quantum.bit
%7 = quantum.alloc_qb : !quantum.bit
%8 = qec.fabricate  magic_conj : !quantum.bit
%m_6, %out_7:2 = qec.ppm ["Z", "Z"] %6#1, %8 : !quantum.bit, !quantum.bit
%m_8, %out_9:2 = qec.ppm ["Z", "Y"] %7, %out_7#1 : !quantum.bit, !quantum.bit
%m_10, %out_11 = qec.ppm ["X"] %out_9#1 : !quantum.bit
%m_12, %out_13 = qec.select.ppm(%m_6, ["X"], ["Z"]) %out_9#0 : !quantum.bit
%9 = arith.xori %m_8, %m_10 : i1
%10 = qec.ppr ["Z"](2) %out_7#0 cond(%9) : !quantum.bit
quantum.dealloc_qb %out_13 : !quantum.bit
quantum.dealloc_qb %out_11 : !quantum.bit
%m_14, %out_15:2 = qec.ppm ["Z", "Z"] %6#0, %10 : !quantum.bit, !quantum.bit
%from_elements = tensor.from_elements %m_14 : tensor<i1>
%m_16, %out_17 = qec.ppm ["Z"] %out_15#1 : !quantum.bit
. . .