Skip to content

Avoid panics in Quantum Shannon Decomposition Rust code - #15635

Merged
Matthew Treinish (mtreinish) merged 20 commits into
Qiskit:mainfrom
ShellyGarion:ctrl_unitary
Mar 12, 2026
Merged

Matthew Treinish (mtreinish) merged 20 commits into
Qiskit:mainfrom
ShellyGarion:ctrl_unitary

Conversation

@ShellyGarion

Copy link
Copy Markdown
Member

Summary

close #15610 .

Since iterative Quantum Shannon Decomposition may provide imprecise matrices, and the rust code may panic when calcualting SVD or Schur decompositions, we use the Isometry decomposition in such cases.

Details and comments

@ShellyGarion Shelly Garion (ShellyGarion) added this to the 2.4.0 milestone Feb 1, 2026
@ShellyGarion Shelly Garion (ShellyGarion) added the stable backport potential Make Mergify open a backport PR to the most recent stable branch on merge. label Feb 1, 2026
@ShellyGarion
Shelly Garion (ShellyGarion) requested a review from a team as a code owner February 1, 2026 15:33
@ShellyGarion Shelly Garion (ShellyGarion) added Changelog: Fixed Add a "Fixed" entry in the GitHub Release changelog. mod: synthesis Rust This PR or issue is related to Rust code in the repository labels Feb 1, 2026
@github-project-automation github-project-automation Bot moved this to Ready in Qiskit 2.4 Feb 1, 2026
@qiskit-bot

Copy link
Copy Markdown
Collaborator

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

  • @Cryoris
  • @Qiskit/terra-core
  • @ajavadia

@coveralls

Coveralls (coveralls) commented Feb 1, 2026 •

Copy link
Copy Markdown

Pull Request Test Coverage Report for Build 21586424465

Details

  • 28 of 41 (68.29%) changed or added relevant lines in 2 files are covered.
  • 6 unchanged lines in 2 files lost coverage.
  • Overall coverage increased (+0.03%) to 87.972%

Changes Missing Coverage Covered Lines Changed/Added Lines %
qiskit/circuit/library/generalized_gates/unitary.py 10 13 76.92%
crates/synthesis/src/qsd.rs 18 28 64.29%
Files with Coverage Reduction New Missed Lines %
crates/qasm2/src/expr.rs 1 93.82%
crates/qasm2/src/lex.rs 5 92.03%
Totals Coverage Status
Change from base Build 21575821626: 0.03%
Covered Lines: 100031
Relevant Lines: 113708

💛 - Coveralls

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.

Thanks Shelly, LGTM. Added one minor comment.

@Cryoris Julien Gacon (Cryoris) 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.

I'm not convinced this is the right way to approach this, we should properly propagate errors and not just wrap the panic into a Python error -- I'm just putting request changes so we don't eagerly merge it, I can leave a more detailed comment later

Comment thread crates/synthesis/src/qsd.rs Resolved
@ShellyGarion

Copy link
Copy Markdown
Member Author

I'm not convinced this is the right way to approach this, we should properly propagate errors and not just wrap the panic into a Python error -- I'm just putting request changes so we don't eagerly merge it, I can leave a more detailed comment later

The errors were better handled by Alexander Ivrii (@alexanderivrii) in 782cfdb

Comment thread crates/synthesis/src/qsd.rs Outdated
fn from(error: QSDError) -> Self {
match error {
QSDError::TwoQubitDecompositionFailed => {
PyRuntimeError::new_err("two-qubit decomposition failed")

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 there not a qiskit error we should be using for these. While a Python runtime error does fit, it's kind of generic for something specific to Qiskit.

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.

Changed in e36fe93 to return QiskitError instead.

1.0,
"U",
None,
)?;

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 there error information from the 2q decomposer we're discarding this way? Granted the only way to keep this error information and also maintain Rust native errors is to update the 2q decomposer code. If you don't want to do this for this PR (which would be a bit much for backport) you should be able to keep the PyResult on this function and use ? or .into() on the qsd_inner returning QSDError and it should work fine.

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.

Since we are planning to backport this, I have left the 2q decomposer code unchanged, for now. In e36fe93 we are now wrapping different QSDError types around Python/CircuitData errors, so error information should get propagated.

Comment thread crates/synthesis/src/qsd.rs Outdated

impl From<CircuitDataError> for QSDError {
fn from(_error: CircuitDataError) -> Self {
QSDError::CircuitConstructionFailed

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.

Doesn't this discard the inner error message? Why not either wrap the CircuitDataError or reuse the error message?

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.

Improved in e36fe93.

one_qubit_decomposer: &EulerBasisSet,
depth: usize,
) -> Result<CircuitData, CircuitDataError> {
) -> Result<CircuitData, QSDError> {

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.

Does this function ever return an error other than a CircuitDataError? If it doesn't why do we need to change the error type here to something broader that is never used?

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 qsd_inner function may return either a circuit data error or schur decomposition error.

Comment thread crates/synthesis/src/qsd.rs Outdated
angles: &mut [f64],
vw_type_all: bool,
) -> Result<CircuitData, CircuitDataError> {
) -> Result<CircuitData, QSDError> {

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.

Same question here why are you changing the error type here if the current one is more specific?

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.

Reverted to return CircuitDataError, see fc592c8.

Comment thread crates/synthesis/src/qsd.rs Outdated
new: CircuitData,
qubit_map: &[Qubit],
) -> Result<(), CircuitDataError> {
fn append(circ: &mut CircuitData, new: CircuitData, qubit_map: &[Qubit]) -> Result<(), QSDError> {

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.

An here too

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.

Also reverted here too.

Comment on lines +212 to +216
# pylint: disable=cyclic-import
from qiskit.quantum_info.operators import Operator

if not matrix_equal(Operator(cmat_def).to_matrix(), cmat, atol=1e-7):
self.definition = Isometry(cmat, 0, 0).definition

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.

Does this need to be under the try block? Runtime error is fairly generic and it could be masking a real error

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.

e36fe93 changes the error to be QiskitError. This still requires a try-catch block, but we check for qiskit error instead.

Alternatively, we could change the rust qs_decomposition function to return None when unsuccessful to synthesize a unitary for whichever reason, would this be a cleaner approach?

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.

My concern is how large the try block is here. The intent here is to only catch on errors coming from qs_decomposition right? If Operator(), matrix_equal or Isometry raise we don't want those to be handled like qs_decomposition() and should just raise. Right now the try block is covering too much code and could mask real bugs. This also applies to the same code in the method above.

@ShellyGarion Shelly Garion (ShellyGarion) Mar 12, 2026 •

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.

but since qs_decomposition isn't numerically stable, our solution is only to fall back to Isometry, right?

the Operator() calculation check was added in a previous PR: #13688, to prevent some tests from failing due to inaccurate numerical outcome.

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.

addressed in dcc5327

@Cryoris
Julien Gacon (Cryoris) dismissed their stale review March 5, 2026 16:38

It's no longer a generic wrap that warrants a "Request changes" blocker

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.

Thanks for the updates. I think this looks fine now. I just pushed up a small fix to the release note to make it a bit more clear what's being fixed.

@mtreinish Matthew Treinish (mtreinish) changed the title handle panic errors in Quantum Shannon Decomposition rust code Avoid panics in Quantum Shannon Decomposition Rust code Mar 12, 2026
@mtreinish
Matthew Treinish (mtreinish) added this pull request to the merge queue Mar 12, 2026
Merged via the queue into Qiskit:main with commit 02fef9b Mar 12, 2026
25 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in Qiskit 2.4 Mar 12, 2026
mergify Bot pushed a commit that referenced this pull request Mar 12, 2026
* catch rust panic errors in qs_decomposition

* add try/catch in qs_decompsition, and fall to Isometry if needed

* add test, based on issue 15610

* formatting

* add release notes

* add Isometry to the test

* Improving error handling for QSD synthesis

* Fix misprint

* enhance failed matrix test

* Changing RuntimeError to QiskitError

* comment

* not discarding info for CircuitDataError

* tightening try-except blocks

* further fix to avoid infinite recursion

* Update release note

---------

Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
(cherry picked from commit 02fef9b)

# Conflicts:
#	crates/synthesis/src/qsd.rs
#	qiskit/circuit/library/generalized_gates/unitary.py
Jake Lishman (jakelishman) pushed a commit that referenced this pull request Mar 12, 2026
* catch rust panic errors in qs_decomposition

* add try/catch in qs_decompsition, and fall to Isometry if needed

* add test, based on issue 15610

* formatting

* add release notes

* add Isometry to the test

* Improving error handling for QSD synthesis

* Fix misprint

* enhance failed matrix test

* Changing RuntimeError to QiskitError

* comment

* not discarding info for CircuitDataError

* tightening try-except blocks

* further fix to avoid infinite recursion

* Update release note

---------

Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
(cherry picked from commit 02fef9b)
github-merge-queue Bot pushed a commit that referenced this pull request Mar 12, 2026
) (#15802)

* Avoid panics in Quantum Shannon Decomposition Rust code (#15635)

* catch rust panic errors in qs_decomposition

* add try/catch in qs_decompsition, and fall to Isometry if needed

* add test, based on issue 15610

* formatting

* add release notes

* add Isometry to the test

* Improving error handling for QSD synthesis

* Fix misprint

* enhance failed matrix test

* Changing RuntimeError to QiskitError

* comment

* not discarding info for CircuitDataError

* tightening try-except blocks

* further fix to avoid infinite recursion

* Update release note

---------

Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
(cherry picked from commit 02fef9b)

* Reinstate stronger pylint suppressions

---------

Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com>
Co-authored-by: Alexander Ivrii <alexi@il.ibm.com>
Co-authored-by: Matthew Treinish <mtreinish@kortar.org>
Co-authored-by: Jake Lishman <jake.lishman@ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Changelog: Fixed Add a "Fixed" entry in the GitHub Release changelog. mod: synthesis Rust This PR or issue is related to Rust code in the repository stable backport potential Make Mergify open a backport PR to the most recent stable branch on merge.

Projects

No open projects
Status: Done

Development

Successfully merging this pull request may close these issues.

qiskit.circuit.Gate.control() broken in Qiskit 2.3.0

7 participants