catalyst.passes.ppr_to_ppm

ppr_to_ppm(qnode=None, *, decompose_method='pauli-corrected', avoid_y_measure=False)[source]

A quantum compilation pass that decomposes Pauli product rotations (PPRs), \(P(\theta) = \exp(-iP\theta)\), into Pauli product measurements (PPMs).

This pass is used to decompose both non-Clifford and Clifford PPRs into PPMs. The non-Clifford PPRs (\(\theta = \tfrac{\pi}{8}\)) are decomposed first, then Clifford PPRs (\(\theta = \tfrac{\pi}{4}\)) are decomposed.

For more information on PPRs and PPMs, check out the Compilation Hub.

Note

The circuits that generated from this pass are currently not executable on any backend. This pass is only for analysis with the null.qubit device and potential future execution when a suitable backend is available.

Parameters:
  • qnode (QNode) – QNode to apply the pass to.

  • decompose_method (str, optional) – The method to use for decomposing non-Clifford PPRs. Options are "pauli-corrected", "auto-corrected", and "clifford-corrected". Defaults to "pauli-corrected". "pauli-corrected" uses a reactive measurement for correction that is based on Figure 13 in arXiv:2211.15465. "auto-corrected" uses an additional measurement for correction that is based on Figure 7 in A Game of Surface Codes, and "clifford-corrected" uses a Clifford rotation for correction that is based on Figure 17(b) in A Game of Surface Codes.

  • 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). This is currently only supported when using the "clifford-corrected" and "pauli-corrected" decomposition method. Defaults to False.

Returns:

QNode

Example

The ppr_to_ppm compilation pass can be applied as a dectorator on a QNode:

import pennylane as qml
from functools import partial
import jax.numpy as jnp

qml.capture.enable()

@qml.qjit(target="mlir")
@partial(ppr_to_ppm, decompose_method="auto-corrected")
@qml.qnode(qml.device("null.qubit", wires=2))
def circuit():
    # equivalent to a Hadamard gate
    qml.PauliRot(jnp.pi / 2, pauli_word="Z", wires=0)
    qml.PauliRot(jnp.pi / 2, pauli_word="X", wires=0)
    qml.PauliRot(jnp.pi / 2, pauli_word="Z", wires=0)

    # equivalent to a CNOT gate
    qml.PauliRot(jnp.pi / 2, pauli_word="ZX", wires=[0, 1])
    qml.PauliRot(-jnp.pi / 2, pauli_word="Z", wires=[0])
    qml.PauliRot(-jnp.pi / 2, pauli_word="X", wires=[1])

    # equivalent to a T gate
    qml.PauliRot(jnp.pi / 4, pauli_word="Z", wires=0)

    return

For clear and inspectable results, use target="mlir" in the qjit decorator, ensure that PennyLane’s program capture is enabled, pennylane.capture.enable(), and call ppr_to_ppm from the PennyLane frontend (qml.transforms.ppr_to_ppm) instead of with catalyst.passes.ppr_to_ppm.

>>> print(qml.specs(circuit, level="all")()['resources'])
{
    'No transforms': ...,
    'Before MLIR Passes (MLIR-0)': ...,
    'ppr-to-ppm (MLIR-1)': Resources(
        num_wires=8,
        num_gates=21,
        gate_types=defaultdict(<class 'int'>, {'PPM-w2': 6, 'PPM-w1': 7, 'PPR-pi/2-w1': 6, 'PPM-w3': 1, 'PPR-pi/2-w2': 1}),
        gate_sizes=defaultdict(<class 'int'>, {2: 7, 1: 13, 3: 1}),
        depth=None,
        shots=Shots(total_shots=None, shot_vector=())
    )
}

In the above output, PPM-weight denotes the type of PPM present in the circuit, where weight is the PPM weight. PPR-theta-weight denotes the type of PPR present in the circuit, where theta is the PPR angle (\(\theta\)) and weight is the PPR weight. Note that \(\theta = \tfrac{\pi}{2}\) PPRs correspond to Pauli operators: \(P(\tfrac{\pi}{2}) = \exp(-iP\tfrac{\pi}{2}) = P\). Pauli operators can be commuted to the end of the circuit and absorbed into terminal measurements.