Skip to content

Support decomposition to/from Adjoint Ops to the GraphSolver - #3001

Open
maliasadi wants to merge 8 commits into
decomp/pass-id-supportfrom
graph-solver/adjoint-op
Open

Support decomposition to/from Adjoint Ops to the GraphSolver#3001
maliasadi wants to merge 8 commits into
decomp/pass-id-supportfrom
graph-solver/adjoint-op

Conversation

@maliasadi

@maliasadi maliasadi commented Jul 8, 2026

Copy link
Copy Markdown
Member

Context:
The decomposition graph solver could not decompose Adjoint ops. This PR adds this support to the graph solver to handle them, mirroring the two decomposition families used by PL:

  1. Decompose Adjoint(Op) directly: Rules registered against the adjoint operator itself: self_adjoint and adjoint_rotation funcs in PL. and other custom rules like _adjoint_rot, etc. These were already supported by the graph. A RuleNode whose output has adjoint = true is registered and solved like any other rule; so no new logic was needed to the solver; they just need to be provided as rules. I'll take care of this in a follow-up PR to integrate the support with the graph-decomposition pass.

  2. Decompose Op, then adjoint each produced gate: From each base rule Op, the graph builder should synthesize Adjoint(Op) with the same multiplicities. I assumed that gate-order reversal doesn't affect resources, so the graph only needs the Adjoint(Op) counts; the actual reversal happens in the follow-up PR when integrating with the pass.

Both pathways coexist in the graph, and the solver picks the cheapest decomp pathway! 🎉

Benefits:

  • Supporting Adjoint(Op) decomposition and resource estimation

Possible Drawbacks:

  • For circuits with many Adjoints, the size of the graph will be doubled as we need to add mirrored rules for each Adjoint(Op) and track them all throughout multiple decomp pathways.
  • I'm using explicit rules for self-adjoint gates! This can be further optimized later after integration with the graph-decomposition pass.

Related GitHub Issues:
[sc-125400]

@maliasadi
maliasadi requested review from astralcai and kipawaa July 8, 2026 21:25
@codecov

codecov Bot commented Jul 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.99%. Comparing base (292e3f4) to head (9a5f1e4).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3001   +/-   ##
=======================================
  Coverage   96.99%   96.99%           
=======================================
  Files         165      165           
  Lines       19091    19091           
  Branches     1816     1816           
=======================================
  Hits        18518    18518           
  Misses        420      420           
  Partials      153      153           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

* - AdjointGenerated: A rule synthesized by adjointing a base decomposition rule.
*/
enum class RuleOrigin : uint8_t { Default = 0, Fixed = 1, Alternative = 2 };
enum class RuleOrigin : uint8_t { Default = 0, Fixed = 1, Alternative = 2, AdjointGenerated = 3 };

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.

Should we differentiate between adjoint genereated from default, adjoint generated from fixed and adjoint generated from alternative?

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.

No we don't need to consider those cases. Fixed/Alternative are consumed by the builder to enforced which rules exist for an op -- the solver works without knowing about the origin of these rule! AdjointGenerated is also consumed by the builder but it's needed in the solver as the solver needs to consider both pathways (in the ADR) and to propagate rules that are built by the solver using makeAdjointRule (Pathway 2 in the ADR).

{
RuleNode adj;
adj.name = base.name + "_adjoint";
adj.output = makeAdjoint(base.output);

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.

Do we have to worry about this in-place mutating base.output?

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.

No. makeAdjoint takes operators by "value"; it operates on a copy and returns it so the original op remains untouched.

@maliasadi
maliasadi force-pushed the graph-solver/adjoint-op branch from 180d5cb to 44cdf32 Compare August 3, 2026 20:05
@maliasadi

Copy link
Copy Markdown
Member Author

Rebased to include #3046 changes in the solver

@maliasadi
maliasadi changed the base branch from main to decomp/pass-id-support August 3, 2026 20:08
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