Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
f0ac7a2
add pass option and functionality
kipawaa Jun 17, 2026
9101dd8
add test
kipawaa Jun 17, 2026
a9e06ab
headers
kipawaa Jun 23, 2026
06b413a
header delimiter
kipawaa Jun 23, 2026
325a814
custom inline in dl
kipawaa Jun 24, 2026
5a7f39b
update tests
kipawaa Jun 24, 2026
0391ff5
update graph-decomposition
kipawaa Jun 24, 2026
7a5887b
Merge branch 'main' into dl-driver-restructure
kipawaa Jun 25, 2026
689ada3
cleanup
kipawaa Jun 25, 2026
3648480
update pass
kipawaa Jun 25, 2026
9060f22
update test
kipawaa Jun 25, 2026
8e55f6a
fix paulirot rule collisions, centralize resources in addRuleNode
kipawaa Jun 25, 2026
291727b
changelog
kipawaa Jun 26, 2026
be42e17
Merge branch 'dl-driver-restructure' into graph-decomp-rule-improvements
kipawaa Jun 26, 2026
30de0ca
changelog
kipawaa Jun 26, 2026
3831ddd
Merge branch 'main' into dl-driver-restructure
kipawaa Jun 30, 2026
5f12f98
Merge branch 'dl-driver-restructure' into graph-decomp-rule-improvements
kipawaa Jun 30, 2026
73e6de0
define decomp interface
kipawaa Jun 29, 2026
eb637de
add interface to paulirot
kipawaa Jun 29, 2026
99d4a7a
update interface
kipawaa Jun 30, 2026
6b12ac0
generalize python rule lowering
kipawaa Jun 30, 2026
d46c6af
generalize frontend
kipawaa Jun 30, 2026
bca7801
support multiple rules
kipawaa Jul 2, 2026
4023f9e
Update doc/releases/changelog-dev.md
kipawaa Jul 7, 2026
24237a0
Apply suggestion from @maliasadi
kipawaa Jul 7, 2026
73afab2
review changes
kipawaa Jul 7, 2026
7c41490
Merge branch 'main' into graph-decomp-rule-improvements
kipawaa Jul 8, 2026
4edaf7c
fix merge mistake
kipawaa Jul 8, 2026
35cfc99
move interface methods to cpp file
kipawaa Jul 8, 2026
c4d2091
support multiple wire args
kipawaa Jul 8, 2026
2a2fd76
improve interface implementation
kipawaa Jul 10, 2026
2e3518d
add interface tests
kipawaa Jul 10, 2026
30c2af3
Merge branch 'main' into graph-decomp-rule-improvements
kipawaa Jul 10, 2026
a5e03da
requested changes
kipawaa Jul 10, 2026
634b85f
merge graph-decomp-rule-improvements
kipawaa Jul 10, 2026
d9f3df6
fix floatattr stringify
kipawaa Jul 10, 2026
e3e6736
improve interface note in graph-decomposition
kipawaa Jul 10, 2026
0437262
improve frontend TODOs
kipawaa Jul 10, 2026
8ef0e38
use interface for paulirot lowering
kipawaa Jul 10, 2026
133e8fa
remove debug prints
kipawaa Jul 10, 2026
7676c35
update pytests
kipawaa Jul 10, 2026
7b70b1f
merge main
kipawaa Jul 10, 2026
674f944
update DL to support new paulirot ID
kipawaa Jul 10, 2026
d22d836
add getExtraData to interface
kipawaa Jul 10, 2026
105b7b8
merge main
kipawaa Jul 10, 2026
00d0207
fix casing in QPD test
kipawaa Jul 10, 2026
9b33de7
add dtype for wires
kipawaa Jul 10, 2026
5570514
Update mlir/include/Quantum/IR/QuantumInterfaces.td
kipawaa Jul 13, 2026
fde3c2e
use getOperatorName
kipawaa Jul 13, 2026
3e295d9
fix unittests
kipawaa Jul 13, 2026
9be1f4f
Implement decomposable gate interface methods for other gate ops
paul0403 Jul 14, 2026
77ccac8
requested changes
kipawaa Jul 14, 2026
58346be
pcphase
paul0403 Jul 14, 2026
18b7971
use op_name for list_decomps
kipawaa Jul 14, 2026
1d0079c
merge main
kipawaa Jul 14, 2026
c94e974
customop
paul0403 Jul 14, 2026
7e7cd1a
move tests to frontend and use `catalyst --tool=opt` instead of `quan…
paul0403 Jul 14, 2026
4577db9
gphase op
paul0403 Jul 14, 2026
644ac39
changelog
kipawaa Jul 14, 2026
a4d67e6
frontend lit test find test_rules.mlirbc
paul0403 Jul 14, 2026
cef8c94
Merge remote-tracking branch 'origin/decomp-interface' into paul0403/…
paul0403 Jul 14, 2026
3b41980
try except on bad rules
paul0403 Jul 14, 2026
093badc
Merge remote-tracking branch 'origin/main' into paul0403/other_decomp…
paul0403 Jul 15, 2026
d8462be
unitary op
paul0403 Jul 15, 2026
ec21744
compile test_rules.mlirbc in CI
paul0403 Jul 15, 2026
ba092c3
use cmake
paul0403 Jul 15, 2026
30758e5
call frontend lit test camke in `make lit-coverage`
paul0403 Jul 15, 2026
e521133
just use quantum-opt
paul0403 Jul 15, 2026
22f8992
use catalyst cli
paul0403 Jul 15, 2026
2059a42
Merge remote-tracking branch 'origin/main' into paul0403/other_decomp…
paul0403 Jul 16, 2026
519c5e9
pylint
paul0403 Jul 16, 2026
553e504
print as a tensor
paul0403 Jul 16, 2026
66dfb16
add support for ids
kipawaa Jul 17, 2026
1f539f3
add tests
kipawaa Jul 17, 2026
cafd58c
changelog
kipawaa Jul 17, 2026
bb70c72
generic decomp rule lowering
kipawaa Jul 14, 2026
db395d8
combine tests for rule lowering
kipawaa Jul 14, 2026
4816e32
improve interface type handling
kipawaa Jul 14, 2026
ebf3c5c
update test for improve type handling
kipawaa Jul 14, 2026
65d3b12
support multiple rules
kipawaa Jul 14, 2026
6a73292
codefactor
kipawaa Jul 14, 2026
e5e206a
platform-dependent suffix for fallback pathway
kipawaa Jul 15, 2026
d595ea9
cleanup
kipawaa Jul 15, 2026
dd4121e
integrate with precompilation
kipawaa Jul 15, 2026
122b0a4
update tests
kipawaa Jul 15, 2026
3eca934
support complex types
kipawaa Jul 15, 2026
f682367
try/except on resources
kipawaa Jul 15, 2026
14cfd6d
improve id handling
kipawaa Jul 16, 2026
e36c323
Merge branch 'decomp/solver-id-support' into generic-decomp-rule-lowe…
kipawaa Jul 17, 2026
e3f37a1
Merge branch 'main' into decomp/solver-id-support
kipawaa Jul 17, 2026
c3ae29c
Merge branch 'decomp/solver-id-support' into generic-decomp-rule-lowe…
kipawaa Jul 17, 2026
eaf0883
Merge branch 'paul0403/other_decomp_interface_ops' into generic-decom…
kipawaa Jul 17, 2026
614b4b6
Merge remote-tracking branch 'origin/main' into generic-decomp-rule-l…
paul0403 Jul 21, 2026
54db9c9
Merge remote-tracking branch 'origin/main' into generic-decomp-rule-l…
paul0403 Jul 21, 2026
db627c2
git whoops
paul0403 Jul 21, 2026
79300d0
git whoops again
paul0403 Jul 21, 2026
c295f78
codefactor
paul0403 Jul 21, 2026
af0fecf
Merge branch 'decomp/gateset-by-name' into generic-decomp-rule-lowering
kipawaa Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 147 additions & 32 deletions frontend/catalyst/device/python_decompositions.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,6 @@

"""
This module provides infrastructure for compile-time lowering of decomposition rules via python.

Python decomposition wrappers should adhere to the following specifications:
The wrapper:
- Is named `{op name}_decomposition_wrapper`.
- Has a signature identical to the named parameters of the associated PL operator; dynamic
arguments may be unused, but should still be included for compatibility.
- Is able to AOT lower the decomposition rule to MLIR without invoking the compiler, e.g.
using `target="mlir"`, AOT compilation and `QJIT.mlir_module`.
See existing examples for further information.
- Returns a string representation of an MLIR module, containing a FuncOp which represents
the instantiated decomposition rule.

The FuncOp decomposition rule in the returned string:
- Is named `{op name}_decomp_rule`.
- Is an MLIR representation of the PennyLane decomposition rule associated with the
specified operator.
- Is instantiated with the static data provided, and all other data remains dynamic.
- Is compatible with the `decompose-lowering` pass, i.e. can be mapped to the MLIR operation
it decomposes and inlined.
- Is self-contained, and does not contain any device initialization, setup/teardown etc.
"""

# pylint: disable=protected-access,bare-except
Expand All @@ -42,25 +22,134 @@

import jax.numpy as jnp
import pennylane as qp
from jax._src.lib.mlir import ir
from jaxlib.mlir.dialects.builtin import ModuleOp

from catalyst.jax_extras.lowering import get_mlir_attribute_from_pyval

_MLIR_DTYPES = {
"i1": jnp.bool_,
"i8": jnp.int8,
"i16": jnp.int16,
"i32": jnp.int32,
"i64": jnp.int64,
"f16": jnp.float16,
"f32": jnp.float32,
"f64": jnp.float64,
"complex<f64>": jnp.complex64,
"complex<f128>": jnp.complex128,
}


def get_dummy_values_for_container(container):
"""Given a container of python types, replace the types with corresponding dummy values."""
dummy_args = []
for dtype in container:
if isinstance(dtype, str):
if dtype in _MLIR_DTYPES:
count = 1
dtype = _MLIR_DTYPES[dtype]
elif dtype.startswith("tensor"):
# tensor<{number}x{type}>
dtype = dtype.removeprefix("tensor<")
dtype = dtype.remove_suffice(">")
count, dtype = dtype.split("x")
else:
raise ValueError(f"Unknown dtype {dtype}.")
else:
count = 1
dtype = jnp.dtype(dtype)

dummy_args.append(jnp.zeros((count,), dtype=dtype))

return tuple(dummy_args)


def get_graph_op_id(op: qp.decomposition.CompressedResourceOp | qp.core.Operator2):
"""
Return the graph operator id for the operator2 instance `op`.

The FuncOp decomposition rules in the returned string satisfy the following requirements:
- Are named `{rule name}_{op graph ID}`.
- Are MLIR representations of the PennyLane decomposition rules associated with the
specified operator.
- Are instantiated with the static data provided, and all other data remains dynamic.
- Are self-contained, and do not contain any device initialization, setup/teardown etc.
- Are compatible with the `decompose-lowering` and `graph-decomposition` passes, meaning
the following:
- Their `target_gate` attribute is set to the provided graph operator ID
- They have a resources attribute containing an operations attribute which maps graph
operator IDs to counts of their occurrences in the rule.
- Their arguments are mappable to the operator they decompose via `decompose-lowering`.

Note that this function should not be updated without updating the corresponding method on the
DecomposableGate interface in mlir/lib/quantum/IR/QuantumInterfaces.cpp.
"""
if isinstance(op, qp.decomposition.CompressedResourceOp):
# NOTE: handling this the old-fashioned way, remove once Operator2 migration is complete
op_type = op.op_type
else:
op_type = op

if issubclass(op_type, qp.core.operator.Operator):
name = op_type.__name__
num_params = op_type.num_params
num_wires = str(op_type.num_wires) if op_type.num_wires else "0"
return name + "[" + ",".join(["f64"] * num_params) + "][" + num_wires + "]{}"
elif isinstance(op_type, qp.core.operator.Operator2):
# TODO: use real getters here
name = op_type.__name__
dynamic_shape = op_type.getDynamicShape()
wire_lens = op_type.getWireLens()
static_data = op_type.getStaticData()
extra_data = op_type.uid
return (
name
+ ("[" + dynamic_shape + "]")
+ ("[" + wire_lens + "]")
+ ("{" + static_data + "}")
+ ("[" + extra_data + "]")
)
else:
raise ValueError(
"Only AbstractOperator and CompressedResourceOp types are supported for generating a "
f"graph ID, got {op} of type {type(op)}"
)


def python_decomposition_wrapper(op_name, op_id, dynamic_shape, wire_lens, static_data) -> str:
"""Generic decomposition wrapper."""
def python_decomposition(op_name, op_id, dynamic_shape, wire_lens, static_data) -> ModuleOp:
"""Python decomposition rule lowering."""
# TODO update docstring
device = qp.device("null.qubit", wires=sum(wire_lens))
wires = tuple(jnp.array(range(length), dtype=int) for length in wire_lens)

decomp_rules = list(qp.decomposition.list_decomps(op_name))

# map rules to resource resources, in a more generic format

name_to_resources = {}
for rule in decomp_rules:
# TODO: not all PL ops have been migrated to the operator 2 format expected by mlir graph
# decomp This means some rules will fail the python callback compilation. When migration is
# complete, remove the try-except.
try:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This inner try-except around the op id generation means we can remove the original try-except around the qjit right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more-so around the compute_resources, but yes! This way we can skip incompatible rules without skipping the op entirely

name_to_resources[rule.name] = {
get_graph_op_id(op): count
for op, count in rule.compute_resources(**static_data).gate_counts.items()
}
except: # pylint: disable=bare-except
decomp_rules.remove(rule)

def rule_to_subroutine(rule):
def decomp_rule(*params, wires):
rule._impl(*params, *wires, **static_data)

# TODO remove this once we have unified lowering, we should be able to set target_gate and
# stop relying on function names
decomp_rule.__name__ = op_id + "_" + rule.name
# keep the frontend name for readability, append target op_id for symbol uniqueness
decomp_rule.__name__ = rule._impl.__name__ + "_" + op_id

return qp.capture.subroutine(decomp_rule)

# let this fail with the standard error message if the op is not found
subroutines = [rule_to_subroutine(rule) for rule in qp.decomposition.list_decomps(op_name)]
subroutines = [rule_to_subroutine(rule) for rule in decomp_rules]

# TODO: not all PL ops have been migrated to the operator 2 format expected by mlir graph decomp
# This means some rules will fail the python callback compilation.
Expand All @@ -74,12 +163,33 @@ def decomp_rule(*params, wires):
@qp.qnode(device=device)
def circuit():
for subroutine in subroutines:
# TODO: I know this is dynamic, but we should probably have a better way of handling
# this than hard-coded dummy values. Revisit this when unifying the decomp-rule
# lowering pipeline
subroutine(*[0.5 for _ in dynamic_shape], wires=wires)
subroutine(*get_dummy_values_for_container(dynamic_shape), wires=wires)

return str(circuit.mlir_module)
module = circuit.mlir_module

def update_funcop_attributes(op):
"""Update the decomposition rule attributes if op is a decomposition rule.

For use with module.walk

This function updates the following attributes:
- Adds the `target_gate` attribute.
- Adds the `resources` attribute.
"""
if op.name == "func.func":
rule_name = ir.StringAttr(op.attributes["sym_name"]).value.removesuffix("_" + op_id)
if rule_name in name_to_resources:
op.attributes["resources"] = get_mlir_attribute_from_pyval(
{"operations": name_to_resources[rule_name]}
)
op.attributes["target_gate"] = ir.StringAttr.get(op_id)

return ir.WalkResult.ADVANCE

with module.context:
module.operation.walk(update_funcop_attributes)

return module
except:
warnings.warn(
f"Python decomposition rule compilation failed for operator "
Expand All @@ -88,3 +198,8 @@ def circuit():
UserWarning,
)
return "builtin.module{}"


def python_decomposition_wrapper(op_name, op_id, dynamic_shape, wire_lens, static_data) -> str:
"""Generic decomposition wrapper."""
return str(python_decomposition(op_name, op_id, dynamic_shape, wire_lens, static_data))
Loading
Loading