Skip to content

Generalize python-decompositions#2983

Draft
kipawaa wants to merge 6 commits into
graph-decomp-rule-improvementsfrom
decomp-interface
Draft

Generalize python-decompositions#2983
kipawaa wants to merge 6 commits into
graph-decomp-rule-improvementsfrom
decomp-interface

Conversation

@kipawaa

@kipawaa kipawaa commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Context:
The initial implementation of python-decompositions was specialized to quantum.paulirot. This was done partly for simplicity, and partly due to a lack of consistency in op interfaces, which would have required bespoke solutions to parse dynamic data, static data etc for each quantum operation.

Description of the Change:
Introduces a generic DecomposableOpInterface interface for interacting with ops in the context of decomposition. This provides consistent methods for accessing dynamic data, static data, op graph IDs and more.

Benefits:
Enables consistent interfaces across all op types relevant to decomposition, reduces type-checking boilerplate and code duplication. This allows any op implementing the interface to use python-decompositions, and further simplifies the general graph infrastructure.

Possible Drawbacks:

Related GitHub Issues:

[sc-122013]

@kipawaa kipawaa changed the base branch from main to graph-decomp-rule-improvements June 30, 2026 15:54
@kipawaa kipawaa force-pushed the decomp-interface branch from 5a65616 to 3812a39 Compare June 30, 2026 18:05
@kipawaa kipawaa force-pushed the decomp-interface branch from 3812a39 to d46c6af Compare June 30, 2026 18:08

@dime10 dime10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is great thanks! Moving in a good direction :)

"Return the name of the operation.", "std::string", "getOpName"
>,
InterfaceMethod<
"Return the number of wires the operation acts on.", "size_t", "getNumWires"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I guess this won't generalize to multiple wire args 🤔

@kipawaa kipawaa Jul 2, 2026

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.

Can you explain this? I think that even when an op has multiple wire args, from a decomposition perspective we only need to know the total number of wires to lower the rule/properly assess resources.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think that's true. If there was an operator that had multiple wire args that could be varied independently, the decomp rule would likely be specific the number of wires for each arg, not the total number (i.e. Op(Wires[3], Wires[2]) and Op(Wires[2], Wires[3]) are most likely different operators for decomposition purposes, or at least we cannot assert that they would always be the same).
The best approach is to simply treat the wires as part of the signature (which they are). The full spec for an operator ID for decomp purposes should look something like this (dyn arg sig, wire sig, static data OR uid). But this is why I think it would be good to align with the Python decomp folks on the exact form of the ID.

"mlir::DictionaryAttr", "getStaticData"
>,
InterfaceMethod<
"Return the operations ID for use with the graph.", "std::string", "getDecompId"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What is the ID? Is it the same as UID or some other concept?

@dime10 dime10 Jun 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh I see, for paulirot it's currently "paulirot" + getPauliWord(). Yeah I think that's the format that we should standardize on, but as a concept makes sense 👍

Essentially we want a standard recipe to build a unique identifier for every op that enters the graph as a unique node.

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.

Exactly, it's effectively a generalization of UID that includes static data, since the graph needs to distinguish all instantiations of an operator. I think that this is only relevant to the compiler graph, so standardization should be as simple as requiring the use of the interface when using/checking the decomp IDs!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exactly, it's effectively a generalization of UID that includes static data, since the graph needs to distinguish all instantiations of an operator.

Yeah I think this is a good idea, one could just hash this tuple (dyn arg sig, wire sig, static data OR uid) to generate a single distinguishable operator ID to attach to the graph nodes.

@github-actions

Copy link
Copy Markdown
Contributor

Hello. You may have forgotten to update the changelog!
Please edit doc/releases/changelog-dev.md on your branch 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.

@kipawaa kipawaa changed the title Add DecomposableGate interface Generalize python-decompositions Jun 30, 2026

@maliasadi maliasadi left a comment

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.

Happy to see this PR come through 🙌

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.

Could we return a more structured format (to be used/parsed later at MLIR) rather than a pretty-printed IR string? Can we use Bytecode? it is versioned and far more robust than textual round-tripping here. It also avoids the name-prefix conventions in the string-version of rules.

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.

We probably need to raise an error to guarantee the biggest constraint with the system which is if any rule for an op is present, all its rules need to be present. in other words, no support for user defined rules via add_decomps with python decomps.

def circuit():
paulirot_subroutine(theta, wires)
for subroutine in subroutines:
subroutine(*[0.5 for _ in dynamic_shape], wires=wires)

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.

Pls add a TODO for 0.5. It's probably safe for all present ops/rules, but let's bring this up in the Op2 sync meeting to double-check with the rest of the team.

];
}

def DecomposableGate : OpInterface<"DecomposableGate", [QuantumGate]> {

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.

Is it also meant to eventually replace the special-casing in getRuleNodes or basically name-handling (gphase -> GlobalPhase, which already has a "TODO: replace this with DecomposableGate interface")? If so, sketching that end state now would clarify whether the current method set is sufficient for use cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants