@@ -669,3 +669,115 @@ def circuit():
669669 )
670670
671671 return PassPipelineWrapper (qnode , passes )
672+
673+
674+ def ppm_compilation (
675+ qnode = None , * , decompose_method = "auto-corrected" , avoid_y_measure = False , max_pauli_size = 0
676+ ):
677+ R"""
678+ Specify that the MLIR compiler pass for transforming
679+ clifford+T gates into Pauli Product Measurements (PPM) will be applied.
680+
681+ This pass combines multiple sub-passes:
682+
683+ - :func:`~.passes.to_ppr` : Converts gates into Pauli Product Rotations (PPRs)
684+ - :func:`~.passes.commute_ppr` : Commutes PPRs past non-Clifford PPRs
685+ - :func:`~.passes.merge_ppr_ppm` : Merges PPRs into Pauli Product Measurements (PPMs)
686+ - :func:`~.passes.ppr_to_ppm` : Decomposes PPRs into PPMs
687+
688+ The ``avoid_y_measure`` and ``decompose_method`` arguments are passed
689+ to the :func:`~.passes.ppr_to_ppm` pass.
690+ The ``max_pauli_size`` argument is passed to the :func:`~.passes.commute_ppr`
691+ and :func:`~.passes.merge_ppr_ppm` passes.
692+
693+ Args:
694+ qnode (QNode, optional): QNode to apply the pass to. If None, returns a decorator.
695+ decompose_method (str, optional): The method to use for decomposing non-Clifford PPRs.
696+ Options are ``"auto-corrected"`` and ``"clifford-corrected"``. Defaults to
697+ ``"auto-corrected"``.
698+ ``"auto-corrected"`` uses an additional measurement for correction.
699+ ``"clifford-corrected"`` uses a Clifford rotation for correction.
700+ avoid_y_measure (bool): Rather than performing a Pauli-Y measurement for Clifford rotations
701+ (sometimes more costly), a :math:`Y` state (:math:`Y\vert 0 \rangle`) is used instead
702+ (requires :math:`Y` state preparation). Defaults to ``False``.
703+ max_pauli_size (int): The maximum size of the Pauli strings after commuting or merging.
704+ Defaults to 0 (no limit).
705+
706+ Returns:
707+ ~.QNode or callable: Returns decorated QNode if qnode is provided,
708+ otherwise returns a decorator.
709+
710+ **Example**
711+
712+ If a merging resulted in a PPM acting on more than
713+ ``max_pauli_size`` qubits (here, ``max_pauli_size = 2``), that merging would be skipped.
714+ However, when decomposed into PPMs, at least one qubit will be applied, so the final
715+ PPMs will act on at least one additional qubit.
716+
717+ .. code-block:: python
718+
719+ import pennylane as qml
720+ from catalyst import qjit, measure
721+ from catalyst.passes import ppm_compilation
722+
723+ pipeline = [("pipe", ["enforce-runtime-invariants-pipeline"])]
724+ method = "clifford-corrected"
725+
726+ @qjit(pipelines=pipeline, target="mlir")
727+ @ppm_compilation(decompose_method=method, avoid_y_measure=True, max_pauli_size=2)
728+ @qml.qnode(qml.device("null.qubit", wires=2))
729+ def circuit():
730+ qml.CNOT([0, 1])
731+ qml.CNOT([1, 0])
732+ qml.adjoint(qml.T)(0)
733+ qml.T(1)
734+ return measure(0), measure(1)
735+
736+ print(circuit.mlir_opt)
737+
738+ Example MLIR Representation:
739+
740+ .. code-block:: mlir
741+
742+ . . .
743+ %m, %out:3 = qec.ppm ["Z", "Z", "Z"] %1, %2, %4 : !quantum.bit, !quantum.bit, !quantum.bit
744+ %m_0, %out_1:2 = qec.ppm ["Z", "Y"] %3, %out#2 : !quantum.bit, !quantum.bit
745+ %m_2, %out_3 = qec.ppm ["X"] %out_1#1 : !quantum.bit
746+ %m_4, %out_5 = qec.select.ppm(%m, ["X"], ["Z"]) %out_1#0 : !quantum.bit
747+ %5 = arith.xori %m_0, %m_2 : i1
748+ %6:2 = qec.ppr ["Z", "Z"](2) %out#0, %out#1 cond(%5) : !quantum.bit, !quantum.bit
749+ quantum.dealloc_qb %out_5 : !quantum.bit
750+ quantum.dealloc_qb %out_3 : !quantum.bit
751+ %7 = quantum.alloc_qb : !quantum.bit
752+ %8 = qec.fabricate magic_conj : !quantum.bit
753+ %m_6, %out_7:2 = qec.ppm ["Z", "Z"] %6#1, %8 : !quantum.bit, !quantum.bit
754+ %m_8, %out_9:2 = qec.ppm ["Z", "Y"] %7, %out_7#1 : !quantum.bit, !quantum.bit
755+ %m_10, %out_11 = qec.ppm ["X"] %out_9#1 : !quantum.bit
756+ %m_12, %out_13 = qec.select.ppm(%m_6, ["X"], ["Z"]) %out_9#0 : !quantum.bit
757+ %9 = arith.xori %m_8, %m_10 : i1
758+ %10 = qec.ppr ["Z"](2) %out_7#0 cond(%9) : !quantum.bit
759+ quantum.dealloc_qb %out_13 : !quantum.bit
760+ quantum.dealloc_qb %out_11 : !quantum.bit
761+ %m_14, %out_15:2 = qec.ppm ["Z", "Z"] %6#0, %10 : !quantum.bit, !quantum.bit
762+ %from_elements = tensor.from_elements %m_14 : tensor<i1>
763+ %m_16, %out_17 = qec.ppm ["Z"] %out_15#1 : !quantum.bit
764+ . . .
765+
766+ """
767+ passes = {
768+ "ppm_compilation" : {
769+ "decompose-method" : decompose_method ,
770+ "avoid-y-measure" : avoid_y_measure ,
771+ "max-pauli-size" : max_pauli_size ,
772+ }
773+ }
774+
775+ if qnode is None :
776+ return functools .partial (
777+ ppm_compilation ,
778+ decompose_method = decompose_method ,
779+ avoid_y_measure = avoid_y_measure ,
780+ max_pauli_size = max_pauli_size ,
781+ )
782+
783+ return PassPipelineWrapper (qnode , passes )
0 commit comments