Skip to content

Graph decomposition for device and stopping_condition#2472

Open
lazypanda10117 wants to merge 51 commits intomainfrom
lazypanda10117/graph-decomp-stop-condition
Open

Graph decomposition for device and stopping_condition#2472
lazypanda10117 wants to merge 51 commits intomainfrom
lazypanda10117/graph-decomp-stop-condition

Conversation

@lazypanda10117
Copy link
Copy Markdown
Member

@lazypanda10117 lazypanda10117 commented Feb 10, 2026

Context:

Supporting device-specific gateset decomposition in the program capture flow. This is enabled by extending the handle_qnode function. As stated in Ali's comment, the new handle_qnode supports the following cases:

  1. User defined stopping_condition in qml.decompose: Use PLxPR decompose with user stopping condition!
  2. No user decomposition (default): Load device capabilities --> device-specific gate set --> Use graph and decompose-lowering. Fallback to PLxPR when graph fails.
  3. User specified gate_set : Graph-based decomposition on user gate set (the current support). Fallback to PLxPR when graph fails.

[sc-110277]
[sc-110276]
[sc-107559]

@lazypanda10117 lazypanda10117 marked this pull request as ready for review February 10, 2026 22:37
@lazypanda10117 lazypanda10117 added the wip PRs that are a Work-In-Progress label Feb 10, 2026
@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.

Copy link
Copy Markdown
Member

@maliasadi maliasadi left a comment

Choose a reason for hiding this comment

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

Thanks @lazypanda10117! The main remaining TODO is to report/fix the edge cases:

  1. Counts Not Implemented with Capture <- this issue is a program capture limitation and doesn't have anything to do with decomposition

  2. Almost all xDSL / PPR parse errors are related to producing a different IR as of applying decomposition! We can fix most of them by updating the MLIR output to be compatible with the checks.

  3. HybridOp decomp assertion <-- we should be able to fix all these issues by ensureing that HybridOps are in the stopping _condition OR excluded from decomposition.

  4. Ops missing from device gateset <- Lightning/null devices support more gates than Catalyst can represent! You need to take the intersection of these ops and decompose the remaining ones in PLxPR.

    • KeyError: 'StatePrep'
    • KeyError: 'BasisState'
    • KeyError: 'QubitUnitary'
    • KeyError: 'QFT'
  5. _OperatorNode Can't Be Captured Without Wire Count <- the graph decomposition produces _OperatorNode objects that require a wire count to be captured/lowered into MLIR, but the wire count isn't available at graph construction time. cc: @astralcai

  6. In specs/pass counting/circuit drawing tests: the current pipeline adds extra decompose-lowering pass to the compiled module. We need to update the logic (and maybe tests) to avoid this.

  7. there are some other errors such as DID NOT RAISE/WARN or Cannot resolve wire for SliceOp or TracerBoolConversionError; let's get back to them after fixing the above as some of them are tied with those edge cases.

Comment thread frontend/catalyst/device/capabilities_utils.py Outdated
consts = args[shots_len : n_consts + shots_len]
non_const_args = args[shots_len + n_consts :]

closed_jaxpr = (
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.

The new handle_qnode supports the following cases:

  1. User defined stopping_condition in qml.decompose: Use PLxPR decompose with user stopping condition!
  2. No user decomposition (default): Load device capabilities --> device-specific gate set --> Use graph and decompose-lowering
  3. User specified gate_set : Graph-based decomposition on user gate set (the current support)

cc: @astralcai @isaacdevlugt if you guys have any comments or suggestions

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.

If a user applies a @qml.decompose on top of a qnode, it would be applied in addition to the decomposition to the device gate set right? That is the current behaviour at least. Whether the user has a @qml.decompose or not does not change the fact that a decomposition to the device gate set is always going to happen. Is that not the case here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

If a user applies a @qml.decompose on top of a qnode, it would be applied in addition to the decomposition to the device gate set right?

My understanding is that when a user specifies a transform, we will only use that user-specified gateset. We only do device-level decomposition when no such user-defined transform is provided.

Comment thread frontend/catalyst/from_plxpr/decompose_utils.py Outdated
Comment thread frontend/catalyst/device/decomposition.py Outdated
Comment thread frontend/catalyst/from_plxpr/from_plxpr.py Outdated

def calculate_diff_method(device, closed_jaxpr):
"""Calculate the diff method for the device."""
return _calculate_diff_method(device, closed_jaxpr)
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 this correct? 🤔

We don't have any logic to deal with "diff" ops in the decomposition system

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is used in catalyst acceptance. I am just porting over the function.

Comment thread frontend/catalyst/device/capabilities_utils.py Outdated
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 merge the util methods with the one in ./catalyst/device or add these methods to from_plxpr/decompose.py ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes we can, I just think this is cleaner since this is the subset of functions (with slight modifications) that are needed from device capabilities.

Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com>
@lazypanda10117
Copy link
Copy Markdown
Member Author

Ops missing from device gateset <- Lightning/null devices support more gates than Catalyst can represent! You need to take the intersection of these ops and decompose the remaining ones in PLxPR.

I think the issue with (4) is actually due to apply_compiler_decompose_to_plxpr. In that function, we do disable_graph and then enable_graph. However, sometimes, graph is not necessarily enabled when we initially called _apply_compiler_decompose_to_plxpr. When running parameterized tests, the environment is not reset across each parameterized run, so this makes subsequent tests run with graph enabled, causing the failures above.

@astralcai
Copy link
Copy Markdown
Contributor

_OperatorNode Can't Be Captured Without Wire Count <- the graph decomposition produces _OperatorNode objects that require a wire count to be captured/lowered into MLIR, but the wire count isn't available at graph construction time. cc: @astralcai

As discussed offline with @lazypanda10117, the issue with this type of errors comes from operators like RotXZX not supported in Catalyst. We need to do a first pass decomposition into the compiler gate set.

Comment thread frontend/catalyst/device/qjit_device.py Outdated
@lazypanda10117
Copy link
Copy Markdown
Member Author

@maliasadi Point 3,4,5,6 are mostly addressed.

ringo-but-quantum and others added 6 commits February 12, 2026 16:55
**Context:**
Fix failing test after
PennyLaneAI/pennylane#9001

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**
**Context:**
We are about to start the audit of tests. For convenience, a tiny nice
fixture to automatically apply the two values of `capture` kwarg locally
is good.

**Description of the Change:**

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**
[sc-111330]

---------

Co-authored-by: Andrija Paurevic <46359773+andrijapau@users.noreply.github.com>
**Context:**
The PPR/PPM lowering passes (`lower-qec-init-ops`,
`unroll-conditional-ppr-ppm`) are now run
as part of the main quantum compilation pipeline. When using `to-ppr`
and `ppr-to-ppm` transforms,
these passes are applied automatically during compilation; we no longer
need to stack them
  explicitly.

[[sc-109665]]

---------

Co-authored-by: Paul <79805239+paul0403@users.noreply.github.com>
Co-authored-by: Hong-Sheng Zheng <mathan0203@gmail.com>
@lazypanda10117 lazypanda10117 changed the base branch from main to lazypanda10117/stop-condition February 18, 2026 21:58
lazypanda10117 added a commit that referenced this pull request Feb 23, 2026
Context: Supporting `stopping_condition` when user supplies it in
`qml.decompose` with capture enabled. This will use the plxpr transform
to handle the decomposition with stopping condition.

This PR is a subset of
#2472. See that PR for a
more detailed discussion.

[[sc-110276]]

---------

Co-authored-by: Ali Asadi <10773383+maliasadi@users.noreply.github.com>
Base automatically changed from lazypanda10117/stop-condition to main February 23, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

wip PRs that are a Work-In-Progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants