Skip to content

Improve single-qubit gate count in TwoQubitControlledUDecomposer#16123

Merged
ShellyGarion merged 26 commits into
Qiskit:mainfrom
ShellyGarion:two_qubit_control_u_optimize
May 12, 2026
Merged

Improve single-qubit gate count in TwoQubitControlledUDecomposer#16123
ShellyGarion merged 26 commits into
Qiskit:mainfrom
ShellyGarion:two_qubit_control_u_optimize

Conversation

@ShellyGarion
Copy link
Copy Markdown
Member

@ShellyGarion ShellyGarion commented May 3, 2026

close #16036

This PR reduces the total number of 1-qubit unitaries needed to synthesize a general 2-qubit unitary using TwoQubitControlledUDecomposer from 24 to 8.

This is in particular useful for the peephole optimization in #13419.

Details:

The TwoQubitControlledUDecomposer consists of the following steps.

  1. We start from a general 4x4 unitary U.

  2. Then we find a cannonical gate W (Weyl gate) s.t.
    U(0,1) = c2r(0) c2l(1) W(0,1) c1r(0) c1l(1)
    this yields 4 1-qubit unitary gates.

     ┌─────┐┌───────┐┌─────┐
q_0: ┤ c2r ├┤0      ├┤ c1r ├
     ├─────┤│  Weyl │├─────┤
q_1: ┤ c2l ├┤1      ├┤ c1l ├
     └─────┘└───────┘└─────┘
  1. Then, we write the Weyl gate W as:
    W(0,1) = RXX(0,1) RYY(0,1) RZZ(0,1)
     ┌─────────┐┌─────────┐        
q_0: ┤0        ├┤0        ├─■──────
     │  Rxx(a) ││  Ryy(b) │ │ZZ(c) 
q_1: ┤1        ├┤1        ├─■──────
     └─────────┘└─────────┘        
  1. Now, we rewrite RYY(0,1) and RZZ(0,1) as:
    RYY(0,1) = sdg(0) sdg(0,1) RXX(0,1) s(0,1) s(0,1)
    RZZ(0,1) = h(0) h(0,1) h(0,1) h(0,1) h(0,1)
    yielding 8 additional 1-qubit unitary gates.
     ┌─────┐┌─────────┐┌───┐
q_0: ┤ Sdg ├┤0        ├┤ S ├
     ├─────┤│  Rxx(b) │├───┤
q_1: ┤ Sdg ├┤1        ├┤ S ├
     └─────┘└─────────┘└───┘
     ┌───┐┌─────────┐┌───┐
q_0: ┤ H ├┤0        ├┤ H ├
     ├───┤│  Rxx(c) │├───┤
q_1: ┤ H ├┤1        ├┤ H ├
     └───┘└─────────┘└───┘
  1. If Equiv is the target controlled-U gate (which is locally equivalent to RXX), then we rewrite RXX using Equiv:
    RXX(0,1) = k2r(0) k2l(1) Equiv(0,1) k1r(0) k1l(1)
    yielding 3*4 1-qubit unitary gates.
     ┌─────┐┌────────┐┌─────┐
q_0: ┤ k2r ├┤0       ├┤ k1r ├
     ├─────┤│  Equiv │├─────┤
q_1: ┤ k2l ├┤1       ├┤ k1l ├
     └─────┘└────────┘└─────┘

This gives a total of:
3*4 + 8 + 4 = 24 1-qubit unitary gates.

     ┌─────┐┌─────────┐┌───────────┐┌─────────┐┌─────┐┌─────────┐┌───────────┐»
q_0: ┤ c2r ├┤ rxx_k2r ├┤0          ├┤ rxx_k1r ├┤ Sdg ├┤ ryy_k2r ├┤0          ├»
     ├─────┤├─────────┤│  Equiv(a) │├─────────┤├─────┤├─────────┤│  Equiv(b) │»
q_1: ┤ c2l ├┤ rxx_k2l ├┤1          ├┤ rxx_k1l ├┤ Sdg ├┤ ryy_k2l ├┤1          ├»
     └─────┘└─────────┘└───────────┘└─────────┘└─────┘└─────────┘└───────────┘»
«     ┌─────────┐┌───┐┌───┐┌─────────┐┌───────────┐┌─────────┐┌───┐┌─────┐
«q_0: ┤ ryy_k1r ├┤ S ├┤ H ├┤ rzz_k2r ├┤0          ├┤ rzz_k1r ├┤ H ├┤ c1r ├
«     ├─────────┤├───┤├───┤├─────────┤│  Equiv(c) │├─────────┤├───┤├─────┤
«q_1: ┤ ryy_k1l ├┤ S ├┤ H ├┤ rzz_k2l ├┤1          ├┤ rzz_k1l ├┤ H ├┤ c1l ├
«     └─────────┘└───┘└───┘└─────────┘└───────────┘└─────────┘└───┘└─────┘

In this PR we multiply the 1-qubit unitary matrices betwen the 2-qubit gates, to get at most 8 1-qubit unitary gates (in the general case where 3 2-qubit gates are needed).

AI/LLM disclosure

  • I didn't use LLM tooling, or only used it privately.
  • I used the following tool to help write this PR description:
  • I used the following tool to generate or modify code:

@ShellyGarion ShellyGarion added this to the 2.5.0 milestone May 3, 2026
@ShellyGarion ShellyGarion requested a review from mtreinish May 3, 2026 08:09
@ShellyGarion ShellyGarion added synthesis Rust This PR or issue is related to Rust code in the repository labels May 3, 2026
@github-project-automation github-project-automation Bot moved this to Ready in Qiskit 2.5 May 3, 2026
@coveralls
Copy link
Copy Markdown

coveralls commented May 3, 2026

Coverage Report for CI Build 25690415793

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage increased (+0.05%) to 87.621%

Details

  • Coverage increased (+0.05%) from the base build.
  • Patch coverage: 6 uncovered changes across 1 file (190 of 196 lines covered, 96.94%).
  • 764 coverage regressions across 18 files.

Uncovered Changes

File Changed Covered %
crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs 196 190 96.94%

Coverage Regressions

764 previously-covered lines in 18 files lost coverage.

Top 10 Files by Coverage Loss Lines Losing Coverage Coverage
crates/circuit/src/dag_circuit.rs 398 84.69%
crates/circuit/src/circuit_drawer.rs 73 95.26%
qiskit/circuit/quantumcircuit.py 71 94.46%
crates/circuit/src/circuit_data.rs 52 87.17%
crates/circuit/src/parameter/parameter_expression.rs 51 91.04%
crates/circuit/src/register_data.rs 42 69.74%
crates/transpiler/src/passes/basis_translator/compose_transforms.rs 16 84.67%
crates/transpiler/src/commutation_checker.rs 14 88.79%
crates/transpiler/src/passes/commutation_cancellation.rs 10 91.38%
crates/circuit/src/interner.rs 9 97.02%

Coverage Stats

Coverage Status
Relevant Lines: 122141
Covered Lines: 107021
Line Coverage: 87.62%
Coverage Strength: 963275.63 hits per line

💛 - Coveralls

@ShellyGarion ShellyGarion marked this pull request as ready for review May 6, 2026 10:03
@ShellyGarion ShellyGarion requested a review from a team as a code owner May 6, 2026 10:03
@qiskit-bot
Copy link
Copy Markdown
Collaborator

One or more of the following people are relevant to this code:

  • @Qiskit/terra-core
  • @levbishop

@ShellyGarion ShellyGarion changed the title [WIP] Improve single-qubit gate count in TwoQubitControlledUDecomposer Improve single-qubit gate count in TwoQubitControlledUDecomposer May 6, 2026
@ShellyGarion ShellyGarion added the Changelog: Added Add an "Added" entry in the GitHub Release changelog. label May 6, 2026
Copy link
Copy Markdown
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

Overall this looks good, thanks for fixing this. I just had a few comments inline.

Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
@mtreinish mtreinish self-assigned this May 6, 2026
@ShellyGarion ShellyGarion requested a review from mtreinish May 7, 2026 13:22
Copy link
Copy Markdown
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

Thanks for the updates, I just had a few more small comments inline.

Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
Comment thread crates/synthesis/src/two_qubit_decompose/controlled_u_decomposer.rs Outdated
@ShellyGarion ShellyGarion requested a review from mtreinish May 8, 2026 05:22
mtreinish added 2 commits May 11, 2026 12:32
This commit updates the result type usec internally between the
to_rxx_gate() function and it's caller to be a struct instead of a
4-tuple. This allows named access and makes it easier to reason about
about what each of the fields are and give them context.
Copy link
Copy Markdown
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

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

Overall this LGTM now and is basically ready to merge. Thanks for doing this. I did pushed up a small couple of commits to cleanup some of the code (using a struct instead of a tuple so we get named access on the return from to_rxx_gate() and also renaming a couple of variables. I won't automerge it so you can review that you're ok with my changes before we merge. If you're ok with them feel free to enqueue for merging.

I also had a comment on the tests which we would be good to fix now, but also it isn't critical since it's really more a test of the euler one qubit decomposer.

Comment on lines +1466 to +1470
self.assertLessEqual(circ.count_ops().get("u", 0), 8)
self.assertLessEqual(circ.count_ops().get("sx", 0), 14)
self.assertLessEqual(circ.count_ops().get("rx", 0), 14)
self.assertLessEqual(circ.count_ops().get("ry", 0), 8)
self.assertLessEqual(circ.count_ops().get("rz", 0), 22)
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.

I think we can tighten this up by adding a condition on the basis. Like right now none of the tests really check the 1q gate count for the U3, U321, or RR basis. We should maybe validate the gate count for a given basis matches the specified euler basis or make the check more generic and say we expect < n single qubit gates where n is determined by the euler basis.

Copy link
Copy Markdown
Member Author

@ShellyGarion ShellyGarion May 12, 2026

Choose a reason for hiding this comment

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

Thanks for the fixes you've pushed - the code looks nicer now!

Since this test is quite complicated as it includes many combinations of 2-qubit gates and euler bases, this is just a sanity check (since we already have seperate tests for the euler decomposers).

The main check is that the maximal number of u gates is 8.
Similarly for ry gates as they appear only in ZYZ.
rx gates can appear only in ZXZ (at most 8 times), or XYX / XZX, so their number is at most 16.
The rz gate count is not tight (it's at most 8 in XZX, 16 at ZYZ and ZXZ and more in ZSX and ZSXX), but since they don't have an error, I don't care much about their counts for now.

@ShellyGarion ShellyGarion added this pull request to the merge queue May 12, 2026
Merged via the queue into Qiskit:main with commit 02f052d May 12, 2026
26 checks passed
@ShellyGarion ShellyGarion deleted the two_qubit_control_u_optimize branch May 12, 2026 05:41
@github-project-automation github-project-automation Bot moved this from Ready to Done in Qiskit 2.5 May 12, 2026
mtreinish added a commit to mtreinish/qiskit-core that referenced this pull request May 27, 2026
This commit adds an option to the TwoQubitPeepholeOptimization transpiler
pass added in Qiskit#13419 to allow configuring the heuristic priority when
instantiating the pass. When the pass is making a decision on which
potential synthesis outcome out of multiple is the best or when to
replace a block with the best synthesis outcome there are three metrics
we look at, the estimated fidelity, the number of 2q gates, and the total
number of gates. The order of this comparison is inherently flexible and
prioritizes different aspects of the synthesis. This exposes a new
option to the pass to enable users to specify which aspect they want the
pass to prioritize.

Previously the pass was hard coded to prioritize 2q gate count, but as
was discussed in the PR review and the commit history of the PR branch
this isn't necessarily the ideal choice. Intuiatively assuming relatively
accurate error rates in the target the estimated fidelity should be the
first priority. This was changed to prioritize two qubit gate count
during development as a debugging step and left in place while we worked
on issues with the TwoQubitControlledUDecomposer's 1q component handling
(see Qiskit#16123). Now that the TwoQubitControlledUDecomposer issue has been
resolved we can explore switching the default, this new argument will
make it much easier to do side by side comparisons of the different
options. The intent is to enable updating the usage in the preset pass
managers (added in Qiskit#16136) without having to modify the pass's rust
code.

This does not include a release not as the the new pass has not been
included in a release yet so there is no addition to a public API in
this PR.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Added Add an "Added" entry in the GitHub Release changelog. Rust This PR or issue is related to Rust code in the repository synthesis

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Improve single-qubit gate count in TwoQubitControlledUDecomposer

4 participants