Repository: PennyLaneAI/pennylane
Pull request #7367: [Python Compiler] Integration of Python Compiler
Author: login: erick-xanadu
Created: 2025-05-01T19:55:03Z | Merged: 2025-05-16T17:30:33Z

**Context:** Initial integration with xDSL.

**Description of the Change:**  Adds the initial integration for the python compiler. The `run` function is intended to be called from Catalyst.

[sc-89797]



Diff:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 6689a77020a..e240b8d6200 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -155,6 +155,12 @@
   decomposition [Theorem 7 in Shende et al.](https://arxiv.org/abs/quant-ph/0406176)
   that contains fewer gates than the previous decomposition.
   [(#7370)](https://github.com/PennyLaneAI/pennylane/pull/7370)
+ 
+* An experimental integration for a Python compiler using [xDSL](https://xdsl.dev/index) has been introduced.
+  This is similar to [Catalyst's MLIR dialects](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/dialects.html#mlir-dialects-in-catalyst), 
+  but it is coded in Python instead of C++.
+  [(#7357)](https://github.com/PennyLaneAI/pennylane/pull/7357)
+  [(#7367)](https://github.com/PennyLaneAI/pennylane/pull/7367)
 
 * PennyLane supports `JAX` version 0.6.0.
   [(#7299)](https://github.com/PennyLaneAI/pennylane/pull/7299)
@@ -166,11 +172,6 @@
   and :class:`~.SelectPauliRot`, now takes much less computational effort and memory.
   [(#7377)](https://github.com/PennyLaneAI/pennylane/pull/7377)
 
-* An experimental quantum dialect written in [xDSL](https://xdsl.dev/index) has been introduced.
-  This is similar to [Catalyst's MLIR dialects](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/dialects.html#mlir-dialects-in-catalyst), 
-  but it is coded in Python instead of C++.
-  [(#7357)](https://github.com/PennyLaneAI/pennylane/pull/7357)
-  
 * The :func:`~.transforms.cancel_inverses` transform no longer changes the order of operations that don't have shared wires, providing a deterministic output.
   [(#7328)](https://github.com/PennyLaneAI/pennylane/pull/7328)
 
diff --git a/pennylane/compiler/python_compiler/__init__.py b/pennylane/compiler/python_compiler/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/pennylane/compiler/python_compiler/impl.py b/pennylane/compiler/python_compiler/impl.py
new file mode 100644
index 00000000000..fc293063925
--- /dev/null
+++ b/pennylane/compiler/python_compiler/impl.py
@@ -0,0 +1,74 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""This file contains the implementation of the PennyLane-xDSL integration API."""
+
+
+import io
+
+from jax._src.interpreters import mlir
+from jaxlib.mlir.dialects import stablehlo
+from jaxlib.mlir.ir import Context as jaxContext  # pylint: disable=no-name-in-module
+from jaxlib.mlir.ir import Module as jaxModule  # pylint: disable=no-name-in-module
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf
+from xdsl.dialects import stablehlo as xdslstablehlo
+from xdsl.dialects import tensor, transform
+from xdsl.parser import Parser
+from xdsl.passes import PipelinePass
+from xdsl.printer import Printer
+
+from .quantum_dialect import QuantumDialect as Quantum
+from .transforms import ApplyTransformSequence
+
+
+# pylint: disable=too-few-public-methods
+class Compiler:
+    """Compiler namespace"""
+
+    @staticmethod
+    def run(jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(
+            binary=False, print_generic_op_form=True, assume_verified=True
+        )
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(xdslstablehlo.StableHLO)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
+        ctx.load_dialect(Quantum)
+
+        xmod: builtin.ModuleOp = Parser(ctx, gentxtmod).parse_module()
+        pipeline = PipelinePass((ApplyTransformSequence(),))
+        # xmod is modified in place
+        pipeline.apply(ctx, xmod)
+
+        buffer = io.StringIO()
+        Printer(stream=buffer, print_generic_format=True).print(xmod)
+        with jaxContext() as ctx:
+            ctx.allow_unregistered_dialects = True
+            ctx.append_dialect_registry(mlir.upstream_dialects)
+            stablehlo.register_dialect(ctx)  # pylint: disable=no-member
+            newmod: jaxModule = jaxModule.parse(buffer.getvalue())
+
+        return newmod
diff --git a/pennylane/compiler/python_compiler/jax_utils/__init__.py b/pennylane/compiler/python_compiler/jax_utils/__init__.py
new file mode 100644
index 00000000000..4bb3eeab637
--- /dev/null
+++ b/pennylane/compiler/python_compiler/jax_utils/__init__.py
@@ -0,0 +1,139 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Utilities for translating JAX to xDSL"""
+
+from functools import wraps
+from typing import Any, Callable, TypeAlias
+
+import jax
+import jaxlib
+
+
+from jax.extend import mlir as jmlir  # pylint: disable=no-name-in-module
+from jaxlib.mlir.ir import Context as jContext  # pylint: disable=no-name-in-module
+from jaxlib.mlir.ir import Module as jModule  # pylint: disable=no-name-in-module
+from jaxlib.mlir.dialects import stablehlo as jstablehlo  # pylint: disable=no-name-in-module
+
+from xdsl.dialects import arith as xarith
+from xdsl.dialects import builtin as xbuiltin
+from xdsl.dialects import func as xfunc
+from xdsl.dialects import scf as xscf
+from xdsl.dialects import stablehlo as xstablehlo
+from xdsl.dialects import tensor as xtensor
+from xdsl.dialects import transform as xtransform
+
+from xdsl.parser import Parser as xParser
+from xdsl.context import Context as xContext
+
+JaxJittedFunction: TypeAlias = jaxlib.xla_extension.PjitFunction
+
+
+def _module_inline(func: JaxJittedFunction, *args, **kwargs) -> jModule:
+    """Get the module from the jax.jitted function"""
+    return func.lower(*args, **kwargs).compiler_ir()
+
+
+def module(func: JaxJittedFunction) -> Callable[Any, jModule]:
+    """
+    Decorator for _module_inline
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs) -> jModule:
+        return _module_inline(func, *args, **kwargs)
+
+    return wrapper
+
+
+def _generic_inline(func: JaxJittedFunction, *args, **kwargs) -> str:  # pragma: no cover
+    """
+    Create the generic textual representation for the jax.jit'ed function
+    """
+    lowered = func.lower(*args, **kwargs)
+    mod = lowered.compiler_ir()
+    return mod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+
+def generic(func: JaxJittedFunction) -> Callable[Any, str]:  # pragma: no cover
+    """
+    Decorator for _generic_inline.
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs) -> str:
+        return _generic_inline(func, *args, **kwargs)
+
+    return wrapper
+
+
+def parse_generic_to_xdsl_module(program: str) -> xbuiltin.ModuleOp:  # pragma: no cover
+    """Parses generic MLIR program to xDSL module"""
+    ctx = xContext(allow_unregistered=True)
+    ctx.load_dialect(xarith.Arith)
+    ctx.load_dialect(xbuiltin.Builtin)
+    ctx.load_dialect(xfunc.Func)
+    ctx.load_dialect(xscf.Scf)
+    ctx.load_dialect(xstablehlo.StableHLO)
+    ctx.load_dialect(xtensor.Tensor)
+    ctx.load_dialect(xtransform.Transform)
+    moduleOp: xbuiltin.ModuleOp = xParser(ctx, program).parse_module()
+    return moduleOp
+
+
+def parse_generic_to_jax_module(program: str) -> jModule:  # pragma: no cover
+    """Parses an MLIR program in string representation to a jax Module"""
+    with jContext() as ctx:
+        ctx.allow_unregistered_dialects = True
+        jstablehlo.register_dialect(ctx)  # pylint: disable=no-member
+        return jModule.parse(program)
+
+
+def jax_from_docstring(func: Callable) -> jModule:  # pragma: no cover
+    """Parses an MLIR program in string representation located in the docstring."""
+
+    @wraps(func)
+    def wrapper(*_, **__):
+        return parse_generic_to_jax_module(func.__doc__)
+
+    return wrapper
+
+
+def _xdsl_module_inline(
+    func: JaxJittedFunction, *args, **kwargs
+) -> xbuiltin.ModuleOp:  # pragma: no cover
+    generic_repr = _generic_inline(func, *args, **kwargs)
+    return parse_generic_to_xdsl_module(generic_repr)
+
+
+def xdsl_from_docstring(func: Callable) -> xbuiltin.ModuleOp:  # pragma: no cover
+    """Parses a docstring into an xdsl module"""
+
+    @wraps(func)
+    def wrapper(*_, **__):
+        return parse_generic_to_xdsl_module(func.__doc__)
+
+    return wrapper
+
+
+def xdsl_module(func: JaxJittedFunction) -> Callable[Any, xbuiltin.ModuleOp]:  # pragma: no cover
+    """
+    Decorator for _xdsl_module_inline
+    """
+
+    @wraps(func)
+    def wrapper(*args, **kwargs) -> xbuiltin.ModuleOp:
+        return _xdsl_module_inline(func, *args, **kwargs)
+
+    return wrapper
diff --git a/pennylane/compiler/python_compiler/transforms/__init__.py b/pennylane/compiler/python_compiler/transforms/__init__.py
new file mode 100644
index 00000000000..b1e9f53be62
--- /dev/null
+++ b/pennylane/compiler/python_compiler/transforms/__init__.py
@@ -0,0 +1,24 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""PennyLane-xDSL transformations API."""
+
+from xdsl.transforms.transform_interpreter import TransformInterpreterPass
+from .apply_transform_sequence import ApplyTransformSequence, register_pass
+
+
+__all__ = [
+    "ApplyTransformSequence",
+    "TransformInterpreterPass",
+    "register_pass",
+]
diff --git a/pennylane/compiler/python_compiler/transforms/apply_transform_sequence.py b/pennylane/compiler/python_compiler/transforms/apply_transform_sequence.py
new file mode 100644
index 00000000000..094668dd5a6
--- /dev/null
+++ b/pennylane/compiler/python_compiler/transforms/apply_transform_sequence.py
@@ -0,0 +1,71 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""This file contains the pass that applies all passes present in the program representation."""
+
+
+from dataclasses import dataclass
+
+from xdsl.context import Context
+from xdsl.dialects import builtin
+from xdsl.passes import ModulePass, PipelinePass
+
+from .transform_interpreter import TransformInterpreterPass  # pylint: disable=no-name-in-module
+
+available_passes = {}
+
+
+def register_pass(name, _callable):
+    """Registers the passes available in the dictionary"""
+    available_passes[name] = _callable  # pragma: no cover
+
+
+# pylint: disable=too-few-public-methods
+@dataclass(frozen=True)
+class ApplyTransformSequence(ModulePass):
+    """
+    Looks for nested modules. Nested modules in this context are guaranteed to correspond
+    to qnodes. These modules are already annotated with which passes are to be executed.
+    The pass ApplyTransformSequence will run passes annotated in the qnode modules.
+
+    At the end, we delete the list of passes as they have already been applied.
+    """
+
+    name = "apply-transform-sequence"
+
+    def apply(  # pylint: disable=arguments-renamed,no-self-use
+        self, ctx: Context, module: builtin.ModuleOp
+    ) -> None:
+        """Applies the transformation"""
+        nested_modules = []
+        for region in module.regions:
+            for block in region.blocks:
+                for op in block.ops:
+                    if isinstance(op, builtin.ModuleOp):
+                        nested_modules.append(op)
+
+        pipeline = PipelinePass(
+            # pylint: disable-next=unexpected-keyword-arg
+            (TransformInterpreterPass(passes=available_passes),)
+        )
+        for op in nested_modules:
+            pipeline.apply(ctx, op)
+
+        for mod in nested_modules:
+            for region in mod.regions:
+                for block in region.blocks:
+                    for op in block.ops:
+                        if isinstance(op, builtin.ModuleOp) and op.get_attr_or_prop(
+                            "transform.with_named_sequence"
+                        ):
+                            block.erase_op(op)  # pragma: no cover
diff --git a/pennylane/compiler/python_compiler/transforms/transform_interpreter/__init__.py b/pennylane/compiler/python_compiler/transforms/transform_interpreter/__init__.py
new file mode 100644
index 00000000000..9973b84d7ee
--- /dev/null
+++ b/pennylane/compiler/python_compiler/transforms/transform_interpreter/__init__.py
@@ -0,0 +1,18 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Public API"""
+
+from .transform_interpreter_catalyst import TransformInterpreterPass
+
+__all__ = ["TransformInterpreterPass"]
diff --git a/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/__init__.py b/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/__init__.py
new file mode 100644
index 00000000000..432874f0769
--- /dev/null
+++ b/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/__init__.py
@@ -0,0 +1,19 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Public API"""
+
+from .impl import TransformFunctionsExt
+
+__all__ = ["TransformFunctionsExt"]
diff --git a/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py b/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py
new file mode 100644
index 00000000000..e7f290d9164
--- /dev/null
+++ b/pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py
@@ -0,0 +1,92 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+An custom implementation of the interpreter functions used for the transform dialect.
+"""
+
+import io
+from typing import Callable
+
+from xdsl.context import Context
+from xdsl.dialects import transform
+from xdsl.interpreter import Interpreter, PythonValues, impl, register_impls
+from xdsl.interpreters.transform import TransformFunctions
+from xdsl.parser import Parser
+from xdsl.passes import ModulePass, PipelinePass
+from xdsl.printer import Printer
+from xdsl.rewriter import Rewriter
+from xdsl.utils import parse_pipeline
+
+
+# pylint: disable=too-few-public-methods
+@register_impls
+class TransformFunctionsExt(TransformFunctions):
+    """
+    Unlike the implementation available in xDSL, this implementation overrides
+    the semantics of the `transform.apply_registered_pass` operation by
+    first always attempting to apply the xDSL pass, but if it isn't found
+    then it will try to run this pass in Catalyst.
+    """
+
+    ctx: Context
+    passes: dict[str, Callable[[], type[ModulePass]]]
+
+    def __init__(
+        self, ctx: Context, passes: dict[str, Callable[[], type[ModulePass]]]
+    ):  # pragma: no cover
+
+        self.ctx = ctx  # pragma: no cover
+        self.passes = passes  # pragma: no cover
+
+    @impl(transform.ApplyRegisteredPassOp)
+    def run_apply_registered_pass_op(  # pragma: no cover
+        self,
+        _interpreter: Interpreter,
+        op: transform.ApplyRegisteredPassOp,
+        args: PythonValues,
+    ) -> PythonValues:
+        """Try to run the pass in xDSL, if it can't run on catalyst"""
+
+        pass_name = op.pass_name.data  # pragma: no cover
+        requested_by_user = PipelinePass.build_pipeline_tuples(
+            self.passes, parse_pipeline.parse_pipeline(pass_name)
+        )
+
+        try:
+            # pragma: no cover
+            schedule = tuple(
+                pass_type.from_pass_spec(spec) for pass_type, spec in requested_by_user
+            )
+            pipeline = PipelinePass(schedule)
+            pipeline.apply(self.ctx, args[0])
+            return (args[0],)
+        except:  # pylint: disable=bare-except
+            # pragma: no cover
+            # Yes, we are importing it here because we don't want to import it
+            # and have an import time dependency on catalyst.
+            # This allows us to test without having a hard requirement on Catalyst
+            from catalyst.compiler import _quantum_opt  # pylint: disable=import-outside-toplevel
+
+            buffer = io.StringIO()
+
+            Printer(stream=buffer, print_generic_format=True).print(args[0])
+            schedule = f"--{pass_name}"
+            modified = _quantum_opt(schedule, "-mlir-print-op-generic", stdin=buffer.getvalue())
+
+            module = args[0]
+            data = Parser(self.ctx, modified).parse_module()
+            rewriter = Rewriter()
+            rewriter.replace_op(module, data)
+            return (data,)
diff --git a/pennylane/compiler/python_compiler/transforms/transform_interpreter/transform_interpreter_catalyst.py b/pennylane/compiler/python_compiler/transforms/transform_interpreter/transform_interpreter_catalyst.py
new file mode 100644
index 00000000000..99910d5a15a
--- /dev/null
+++ b/pennylane/compiler/python_compiler/transforms/transform_interpreter/transform_interpreter_catalyst.py
@@ -0,0 +1,61 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Custom Transform Dialect Interpreter Pass
+
+Differs from xDSL's upstream implementation by allowing passes
+to be passed in as options.
+"""
+
+from typing import Callable
+
+from xdsl.context import Context
+from xdsl.dialects import builtin, transform
+from xdsl.interpreters import Interpreter
+from xdsl.passes import ModulePass
+from xdsl.utils.exceptions import PassFailedException
+
+from .interpreter import TransformFunctionsExt
+
+
+class TransformInterpreterPass(ModulePass):
+    """Transform dialect interpreter"""
+
+    passes: dict[str, Callable[[], type[ModulePass]]]
+    name = "transform-interpreter"
+
+    entry_point: str = "__transform_main"
+
+    def __init__(self, passes):
+        self.passes = passes
+
+    @staticmethod
+    def find_transform_entry_point(
+        root: builtin.ModuleOp, entry_point: str
+    ) -> transform.NamedSequenceOp:
+        """Find the entry point of the program"""
+        for op in root.walk():
+            if isinstance(op, transform.NamedSequenceOp) and op.sym_name.data == entry_point:
+                return op
+        raise PassFailedException(  # pragma: no cover
+            f"{root} could not find a nested named sequence with name: {entry_point}"
+        )
+
+    def apply(self, ctx: Context, op: builtin.ModuleOp) -> None:
+        """Run the interpreter with op."""
+        schedule = TransformInterpreterPass.find_transform_entry_point(op, self.entry_point)
+        interpreter = Interpreter(op)
+        interpreter.register_implementations(TransformFunctionsExt(ctx, self.passes))
+        schedule.parent_op().detach()
+        interpreter.call_op(schedule, (op,))
diff --git a/tests/pytest.ini b/tests/pytest.ini
index c68d3c55137..66ed3f7876b 100644
--- a/tests/pytest.ini
+++ b/tests/pytest.ini
@@ -29,6 +29,6 @@ filterwarnings =
     error::pennylane.exceptions.PennyLaneDeprecationWarning
     # Suppress expected AutoGraphWarnings that arise from the tests
     error:AutoGraph will not transform the function:pennylane.capture.autograph.AutoGraphWarning
-addopts = --benchmark-disable
+#addopts = --benchmark-disable
 xfail_strict=true
 rng_salt = v0.40.0
diff --git a/tests/python_compiler/test_python_compiler.py b/tests/python_compiler/test_python_compiler.py
new file mode 100644
index 00000000000..91106ddf969
--- /dev/null
+++ b/tests/python_compiler/test_python_compiler.py
@@ -0,0 +1,104 @@
+# Copyright 2025 Xanadu Quantum Technologies Inc.
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+
+#     http://www.apache.org/licenses/LICENSE-2.0
+
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Unit test module for pennylane/compiler/python_compiler/impl.py"""
+
+# pylint: disable=wrong-import-position
+import pytest
+
+pytestmark = pytest.mark.external
+
+xdsl = pytest.importorskip("xdsl")
+jax = pytest.importorskip("jax")
+jaxlib = pytest.importorskip("jaxlib")
+
+
+from pennylane.compiler.python_compiler.impl import Compiler
+from pennylane.compiler.python_compiler.jax_utils import jax_from_docstring, module
+
+
+def test_compiler():
+    """Test that we can pass a jax module into the compiler.
+
+    In this particular case, the compiler is not doing anything
+    because this module does not contain nested modules which is what
+    is expected of Catalyst.
+
+    So, it just tests that Compiler.run does not trigger an assertion
+    and returns a valid
+    """
+
+    @module
+    @jax.jit
+    def identity(x):
+        return x
+
+    input_module = identity(1)
+    retval = Compiler.run(input_module)
+    assert isinstance(retval, jaxlib.mlir.ir.Module)
+    assert str(retval) == str(input_module)
+
+
+def test_generic_catalyst_program():
+    """
+    test that actually will trigger the transform interpreter
+    """
+
+    @jax_from_docstring
+    def program():
+        """
+        "builtin.module"() <{sym_name = "circuit"}> ({
+          "func.func"() <{function_type = () -> tensor<2xcomplex<f64>>, sym_name = "jit_circuit", sym_visibility = "public"}> ({
+            %8 = "catalyst.launch_kernel"() <{callee = @module_circuit::@circuit}> : () -> tensor<2xcomplex<f64>>
+            "func.return"(%8) : (tensor<2xcomplex<f64>>) -> ()
+          }) {llvm.emit_c_interface} : () -> ()
+          "builtin.module"() <{sym_name = "module_circuit"}> ({
+            "builtin.module"() ({
+              "transform.named_sequence"() <{function_type = (!transform.op<"builtin.module">) -> (), sym_name = "__transform_main"}> ({
+              ^bb0(%arg0: !transform.op<"builtin.module">):
+                "transform.yield"() : () -> ()
+              }) : () -> ()
+            }) {transform.with_named_sequence} : () -> ()
+            "func.func"() <{function_type = () -> tensor<2xcomplex<f64>>, sym_name = "circuit", sym_visibility = "public"}> ({
+              %0 = "arith.constant"() <{value = 0 : i64}> : () -> i64
+              "quantum.device"(%0) <{kwargs = "{'shots': 0, 'mcmc': False, 'num_burnin': 0, 'kernel_name': None}", lib = "/usr/local/lib/python3.11/dist-packages/pennylane_lightning/liblightning_qubit_catalyst.so", name = "LightningSimulator"}> : (i64) -> ()
+              %1 = "quantum.alloc"() <{nqubits_attr = 1 : i64}> : () -> !quantum.reg
+              %2 = "quantum.extract"(%1) <{idx_attr = 0 : i64}> : (!quantum.reg) -> !quantum.bit
+              %3 = "quantum.custom"(%2) <{gate_name = "Hadamard", operandSegmentSizes = array<i32: 0, 1, 0, 0>, resultSegmentSizes = array<i32: 1, 0>}> : (!quantum.bit) -> !quantum.bit
+              %4 = "quantum.custom"(%3) <{gate_name = "Hadamard", operandSegmentSizes = array<i32: 0, 1, 0, 0>, resultSegmentSizes = array<i32: 1, 0>}> : (!quantum.bit) -> !quantum.bit
+              %5 = "quantum.insert"(%1, %4) <{idx_attr = 0 : i64}> : (!quantum.reg, !quantum.bit) -> !quantum.reg
+              %6 = "quantum.compbasis"(%5) <{operandSegmentSizes = array<i32: 0, 1>}> : (!quantum.reg) -> !quantum.obs
+              %7 = "quantum.state"(%6) <{operandSegmentSizes = array<i32: 1, 0, 0>}> : (!quantum.obs) -> tensor<2xcomplex<f64>>
+              "quantum.dealloc"(%5) : (!quantum.reg) -> ()
+              "quantum.device_release"() : () -> ()
+              "func.return"(%7) : (tensor<2xcomplex<f64>>) -> ()
+            }) {diff_method = "parameter-shift", llvm.linkage = #llvm.linkage<internal>, qnode} : () -> ()
+          }) : () -> ()
+          "func.func"() <{function_type = () -> (), sym_name = "setup"}> ({
+            "quantum.init"() : () -> ()
+            "func.return"() : () -> ()
+          }) : () -> ()
+          "func.func"() <{function_type = () -> (), sym_name = "teardown"}> ({
+            "quantum.finalize"() : () -> ()
+            "func.return"() : () -> ()
+          }) : () -> ()
+        }) : () -> ()
+        """
+
+    retval = Compiler.run(program())
+    assert isinstance(retval, jaxlib.mlir.ir.Module)
+
+
+if __name__ == "__main__":
+    pytest.main(["-x", __file__])


Reviews:
user: login: isaacdevlugt
body: Don't forget the changelog! 😈 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:28:19Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:30:57Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:32:08Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:33:08Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:33:55Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:36:42Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:37:22Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:37:33Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:40:52Z
user: login: mudit2812
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:42:39Z
user: login: mudit2812
body: Thanks Erick, this is pretty impressive, but I'd be lying if I said I understood all of it. Can we chat about the implementation on Monday? I'd like to wrap my head around it a bit before approving.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:46:02Z
user: login: PietropaoloFrisoni
body: I would suggest a `# pragma no cover` if testing is not an option right now : )
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-09T20:48:24Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:08:29Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:09:01Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T15:09:32Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T13:43:02Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T16:37:38Z
user: login: isaacdevlugt
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T19:14:25Z
user: login: erick-xanadu
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-14T19:50:14Z
user: login: PietropaoloFrisoni
body: Thanks a lot @erick-xanadu !
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-16T16:48:37Z

Review comments:
diff_hunk: @@ -0,0 +1,50 @@
+import io
path: pennylane/compiler/python_compiler/impl.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This file contains the implementation of the PennyLane-xDSL integration API."""

import io
```
created_at: 2025-05-09T20:30:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
path: pennylane/compiler/python_compiler/impl.py
user: login: mudit2812
body: Why not import the stableHLO dialect? Do you anticipate issues since the xDSL stableHLO dialect is incomplete?
created_at: 2025-05-09T20:32:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
+        ctx.load_dialect(Quantum)
+
+        xmod: builtin.ModuleOp = Parser(ctx, gentxtmod).parse_module()
+        pipeline = PipelinePass((ApplyTransformSequence(),))
+        # xmod is modified in place
+        pipeline.apply(ctx, xmod)
+
+        buffer = io.StringIO()
+        Printer(stream=buffer, print_generic_format=True).print(xmod)
+        with jaxContext() as ctx:
+            ctx.allow_unregistered_dialects = True
+            ctx.append_dialect_registry(mlir.upstream_dialects)
+            stablehlo.register_dialect(ctx)
+            newmod: jaxModule = jaxModule.parse(buffer.getvalue())
path: pennylane/compiler/python_compiler/impl.py
user: login: mudit2812
body: Do you mind explaining what's going on here? i'm not familiar with this API
created_at: 2025-05-09T20:33:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,8 @@
+from .apply_transform_sequence import ApplyTransformSequence, register_pass
path: pennylane/compiler/python_compiler/transforms/__init__.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""PennyLane-xDSL transformations API."""

from .apply_transform_sequence import ApplyTransformSequence, register_pass
```
created_at: 2025-05-09T20:33:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,35 @@
+from dataclasses import dataclass
path: pennylane/compiler/python_compiler/transforms/apply_transform_sequence.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This file contains the pass that applies all passes present in the program representation."""

from dataclasses import dataclass
```
created_at: 2025-05-09T20:36:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,5 @@
+from .transform_interpreter_catalyst import TransformInterpreterPass
path: pennylane/compiler/python_compiler/transforms/transform_interpreter/__init__.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .transform_interpreter_catalyst import TransformInterpreterPass
```
created_at: 2025-05-09T20:37:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,5 @@
+from .impl import TransformFunctionsExt
path: pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/__init__.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .impl import TransformFunctionsExt
```
created_at: 2025-05-09T20:37:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+import io
path: pennylane/compiler/python_compiler/transforms/transform_interpreter/interpreter/impl.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import io
```
We should also add a minimal docstring at the top of this file explaining what's in it.

Could you also give a quick rundown of what this file is doing to me? I don't really understand it.
created_at: 2025-05-09T20:40:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,43 @@
+from dataclasses import dataclass
path: pennylane/compiler/python_compiler/transforms/transform_interpreter/transform_interpreter_catalyst.py
user: login: mudit2812
body: ```suggestion
# Copyright 2025 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#     http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from dataclasses import dataclass
```
created_at: 2025-05-09T20:42:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
+        ctx.load_dialect(Quantum)
+
+        xmod: builtin.ModuleOp = Parser(ctx, gentxtmod).parse_module()
+        pipeline = PipelinePass((ApplyTransformSequence(),))
+        # xmod is modified in place
+        pipeline.apply(ctx, xmod)
path: pennylane/compiler/python_compiler/impl.py
user: login: PietropaoloFrisoni
body: This is modifying `xmod` in place, right?
created_at: 2025-05-09T20:44:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
+        ctx.load_dialect(Quantum)
+
+        xmod: builtin.ModuleOp = Parser(ctx, gentxtmod).parse_module()
+        pipeline = PipelinePass((ApplyTransformSequence(),))
+        # xmod is modified in place
+        pipeline.apply(ctx, xmod)
+
+        buffer = io.StringIO()
+        Printer(stream=buffer, print_generic_format=True).print(xmod)
+        with jaxContext() as ctx:
+            ctx.allow_unregistered_dialects = True
+            ctx.append_dialect_registry(mlir.upstream_dialects)
+            stablehlo.register_dialect(ctx)
+            newmod: jaxModule = jaxModule.parse(buffer.getvalue())
path: pennylane/compiler/python_compiler/impl.py
user: login: PietropaoloFrisoni
body: I think we are constructing `jaxModule` as an MLIR module, but I might be totally wrong
created_at: 2025-05-09T20:46:53Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
+        ctx.load_dialect(Quantum)
+
+        xmod: builtin.ModuleOp = Parser(ctx, gentxtmod).parse_module()
+        pipeline = PipelinePass((ApplyTransformSequence(),))
+        # xmod is modified in place
+        pipeline.apply(ctx, xmod)
+
+        buffer = io.StringIO()
+        Printer(stream=buffer, print_generic_format=True).print(xmod)
+        with jaxContext() as ctx:
+            ctx.allow_unregistered_dialects = True
+            ctx.append_dialect_registry(mlir.upstream_dialects)
+            stablehlo.register_dialect(ctx)
+            newmod: jaxModule = jaxModule.parse(buffer.getvalue())
path: pennylane/compiler/python_compiler/impl.py
user: login: erick-xanadu
body: Yes, we are parsing a generic MLIR textual representation back into JAX. This is not strictly necessary, but I added it to maintain the same type as the one in the input. Potentially we can skip this step if we know that from `xmod` we can get what we need in the caller of this function.
created_at: 2025-05-13T15:08:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
path: pennylane/compiler/python_compiler/impl.py
user: login: erick-xanadu
body: We can try importing it. I am not 100% sure what the behaviour will be with incomplete dialects :thinking: 
created_at: 2025-05-13T15:09:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
+        ctx.load_dialect(Quantum)
+
+        xmod: builtin.ModuleOp = Parser(ctx, gentxtmod).parse_module()
+        pipeline = PipelinePass((ApplyTransformSequence(),))
+        # xmod is modified in place
+        pipeline.apply(ctx, xmod)
path: pennylane/compiler/python_compiler/impl.py
user: login: erick-xanadu
body: Correct!
created_at: 2025-05-13T15:09:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
path: pennylane/compiler/python_compiler/impl.py
user: login: erick-xanadu
body: It looks like it is possible and it won't raise errors. :) However, operations which are not registered will still be shown as UnregisteredOperations.
created_at: 2025-05-14T13:43:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,50 @@
+import io
+
+from jaxlib.mlir.ir import Context as jaxContext, Module as jaxModule
+from jaxlib.mlir.dialects import stablehlo
+from jax._src.interpreters import mlir
+
+from xdsl.context import Context as xdslContext
+from xdsl.dialects import arith, builtin, func, scf, tensor, transform
+from xdsl.parser import Parser
+from xdsl.printer import Printer
+from xdsl.passes import PipelinePass
+
+from .quantum_dialect import QuantumDialect as Quantum
+
+from .transforms import ApplyTransformSequence
+
+class Compiler:
+
+    def run(self, jmod: jaxModule) -> jaxModule:
+        """Runs the apply-transform-sequence pass.
+
+        The apply-transform-sequence pass is a "meta-pass". In other words,
+        it is a pass that runs other passes.
+        """
+
+        gentxtmod: str = jmod.operation.get_asm(binary=False, print_generic_op_form=True, assume_verified=True)
+
+        ctx = xdslContext(allow_unregistered=True)
+        ctx.load_dialect(arith.Arith)
+        ctx.load_dialect(builtin.Builtin)
+        ctx.load_dialect(func.Func)
+        ctx.load_dialect(scf.Scf)
+        ctx.load_dialect(tensor.Tensor)
+        ctx.load_dialect(transform.Transform)
path: pennylane/compiler/python_compiler/impl.py
user: login: erick-xanadu
body: I just added it in my latest commit.
created_at: 2025-05-14T16:37:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,10 +120,11 @@
 
 <h3>Improvements 🛠</h3>
 
-* An experimental quantum dialect written in [xDSL](https://xdsl.dev/index) has been introduced.
+* Added an experimental integration for a python compiler using [xDSL](https://xdsl.dev/index) has been introduced.
   This is similar to [Catalyst's MLIR dialects](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/dialects.html#mlir-dialects-in-catalyst), 
path: doc/releases/changelog-dev.md
user: login: isaacdevlugt
body: ```suggestion
* An experimental integration for a Python compiler using [xDSL](https://xdsl.dev/index) has been introduced.
  This is similar to [Catalyst's MLIR dialects](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/dialects.html#mlir-dialects-in-catalyst), 
```
created_at: 2025-05-14T19:14:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -120,10 +120,11 @@
 
 <h3>Improvements 🛠</h3>
 
-* An experimental quantum dialect written in [xDSL](https://xdsl.dev/index) has been introduced.
+* Added an experimental integration for a python compiler using [xDSL](https://xdsl.dev/index) has been introduced.
   This is similar to [Catalyst's MLIR dialects](https://docs.pennylane.ai/projects/catalyst/en/stable/dev/dialects.html#mlir-dialects-in-catalyst), 
path: doc/releases/changelog-dev.md
user: login: erick-xanadu
body: https://github.com/PennyLaneAI/pennylane/pull/7367/commits/fc5fe01b18062bf598ea0de28ae85b46d76c6e8c Thanks isaac!
created_at: 2025-05-14T19:50:13Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-05-01T19:55:15Z
body: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.
author_association: CONTRIBUTOR
user: login: codecov[bot]
created_at: 2025-05-01T20:12:32Z
body: ## [Codecov](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7367?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
:white_check_mark: All modified and coverable lines are covered by tests.
:white_check_mark: Project coverage is 99.69%. Comparing base ([`1116746`](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/11167460d8e857450b85797371719e3f95365413?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)) to head ([`21c81ea`](https://app.codecov.io/gh/PennyLaneAI/pennylane/commit/21c81eab88702b6a58245512bac2236f84fef1ab?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)).
:warning: Report is 402 commits behind head on master.

<details><summary>Additional details and impacted files</summary>



```diff
@@           Coverage Diff            @@
##           master    #7367    +/-   ##
========================================
  Coverage   99.69%   99.69%            
========================================
  Files         529      537     +8     
  Lines       50738    50873   +135     
========================================
+ Hits        50583    50718   +135     
  Misses        155      155            
```
</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/PennyLaneAI/pennylane/pull/7367?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI).
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: erick-xanadu
created_at: 2025-05-13T15:11:03Z
body: > Can we chat about the implementation on Monday? 

Definitely, I also need your help with the linter. I am not sure how to apply it :sweat_smile: In Catalyst we use a make format command. :) 
author_association: CONTRIBUTOR
user: login: erick-xanadu
created_at: 2025-05-14T13:40:16Z
body: @mudit2812 or @PietropaoloFrisoni how can I run the linter from PennyLane, in Catalyst I use `make format`
author_association: CONTRIBUTOR
user: login: PietropaoloFrisoni
created_at: 2025-05-14T13:50:19Z
body: @erick-xanadu In PennyLane there's no Makefile, so I usually just format with `black/isort` and I check locally by running `pylint` on the relevant file(s)
author_association: CONTRIBUTOR
user: login: mudit2812
created_at: 2025-05-14T14:32:54Z
body: Hey @erick-xanadu , if you use `pre-commit`, pylint should automatically get run every time you try to commit to pennylane. You can set it up by doing:

```
pip install pre-commit
pre-commit install
```

After this, the pre-commit hook (which includes black, pylint, and isort), should get run automatically every time you commit.
author_association: CONTRIBUTOR
user: login: PietropaoloFrisoni
created_at: 2025-05-14T14:34:48Z
body: >  if you use pre-commit, pylint should automatically get run every time you try to commit to pennylane.

I wish I had known sooner :smiling_face_with_tear: 
author_association: CONTRIBUTOR
user: login: erick-xanadu
created_at: 2025-05-14T18:02:39Z
body: @PietropaoloFrisoni and/or @mudit2812 is there a way to disable codecov? I already tried no cover. :thinking: 
author_association: CONTRIBUTOR
user: login: mudit2812
created_at: 2025-05-14T18:39:55Z
body: @erick-xanadu [this](https://coverage.readthedocs.io/en/latest/excluding.html) is a good reference. Apparently using `# pragma: exclude file` should disable coverage for the whole file. `# pragma: no cover` is for single lines and closures, not files.
author_association: CONTRIBUTOR
user: login: erick-xanadu
created_at: 2025-05-14T20:12:41Z
body: @mudit2812 , I think your link says I should modify the pyproject.toml file:

```
[tool.coverage.report]
exclude_also = [
    # 1. Exclude an except clause of a specific form:
    'except ValueError:\n\s*assume\(False\)',
    # 2. Comments to turn coverage on and off:
    'no cover: start(?s:.)*?no cover: stop',
    # 3. A pragma comment that excludes an entire file:
    '\A(?s:.*# pragma: exclude file.*)\Z',
]
```
author_association: CONTRIBUTOR
user: login: erick-xanadu
created_at: 2025-05-16T13:01:44Z
body: @mudit2812 @PietropaoloFrisoni 
author_association: CONTRIBUTOR