Skip to content

Conversation

@dmeliksetian
Copy link

Summary

This PR fixes a visualization artifact in plot_state_qsphere where symmetric entangled states (e.g., Bell states) were being arbitrarily rotated due to floating-point noise.

The Issue:
The function uses scipy.linalg.eigh internally to decompose the input state. For symmetric states (e.g., |00> - |11>), the solver introduces microscopic noise ($\approx 10^{-16}$) that often makes the last element slightly larger than the first.
The previous logic (loc = np.absolute(state).argmax()) strictly trusted this noise, causing the visualization to select the "wrong" basis state as the global phase anchor (Phase 0).

The Fix:
Added a rounding step (np.round(..., decimals=13)) before calculating argmax. This filters out solver noise and ensures that effectively equal amplitudes are treated as a tie, allowing argmax to default to the canonical first index.

Details and comments

  • Location: qiskit/visualization/state_visualization.py (Line ~951)
  • Verification: Verified locally with a standard Bell state $|\Psi^-\rangle$.
    • Before: Anchor often defaulted to $|11\rangle$ (rendering $-|00\rangle + |11\rangle$).
    • After: Anchor correctly defaults to $|00\rangle$ (rendering $|00\rangle - |11\rangle$).
  • AI Disclosure: AI tool used: Google Gemini (Assisted in isolating the scipy solver noise and drafting the fix).

Fixes #15493

@dmeliksetian dmeliksetian requested a review from a team as a code owner January 2, 2026 16:44
@qiskit-bot qiskit-bot added the Community PR PRs from contributors that are not 'members' of the Qiskit repo label Jan 2, 2026
@qiskit-bot
Copy link
Collaborator

Thank you for opening a new pull request.

Before your PR can be merged it will first need to pass continuous integration tests and be reviewed. Sometimes the review process can be slow, so please be patient.

While you're waiting, please feel free to review other open PRs. While only a subset of people are authorized to approve pull requests for merging, everyone is encouraged to review open pull requests. Doing reviews helps reduce the burden on the core team and helps make the project's code better for everyone.

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

@CLAassistant
Copy link

CLAassistant commented Jan 2, 2026

CLA assistant check
All committers have signed the CLA.

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
1 out of 2 committers have signed the CLA.

✅ dmeliksetian
❌ dsmeliks
You have signed the CLA already but the status is still pending? Let us recheck it.

@mergify
Copy link
Contributor

mergify bot commented Jan 2, 2026

⚠️ The sha of the head commit of this PR conflicts with #15473. Mergify cannot evaluate rules on this PR. ⚠️

loc = np.absolute(state).argmax()
# Rounding to 13 decimals ignores machine epsilon noise (~1e-16)
# from the solver, ensuring 'argmax' finds the true analytical winner.
loc = np.round(np.absolute(state), decimals=13).argmax()
Copy link
Collaborator

Choose a reason for hiding this comment

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

In the cases with similar eigenvalues, doesn't the order returned by scipy.linalg.eigh also vary? If it does, then rounding afterwards doesn't guarantee a fixed order 🤔

Copy link
Author

Choose a reason for hiding this comment

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

we are looking at the magnitude of the elements of a vector, the vector elements are naturally ordered and argmax() returns the index of the first element when there are more than one element equal to the maximum value. So when the magnitudes are equal it will return the lowest position. In terms of adjusting the global phase, this is exactly what we want, if everything else is equal.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah right, these are not the eigenvalues but the vector itself. All good then!

@coveralls
Copy link

coveralls commented Jan 5, 2026

Pull Request Test Coverage Report for Build 20754331594

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 1 of 1 (100.0%) changed or added relevant line in 1 file are covered.
  • 33 unchanged lines in 5 files lost coverage.
  • Overall coverage increased (+0.009%) to 88.319%

Files with Coverage Reduction New Missed Lines %
crates/circuit/src/parameter/parameter_expression.rs 1 82.3%
qiskit/circuit/library/pauli_product_measurement.py 2 95.35%
crates/qasm2/src/lex.rs 4 91.52%
crates/circuit/src/parameter/symbol_expr.rs 8 72.98%
qiskit/circuit/library/n_local/evolved_operator_ansatz.py 18 89.3%
Totals Coverage Status
Change from base Build 20366546417: 0.009%
Covered Lines: 96732
Relevant Lines: 109526

💛 - Coveralls

@Cryoris
Copy link
Collaborator

Cryoris commented Jan 7, 2026

It would be great to add a test for this, given that there's currently only a single qsphere test which is displaying the $|1\rangle$ state, not a bell state. This would be similar to

def test_plot_state_qsphere(self):

just using a bell state instead. Generating the reference image can be a bit tricky though, due to slight mismatches in plot package versions. You could still try generating the reference locally, or otherwise download the generated image from the CI run. Let me know if you need help with this.

@dmeliksetian
Copy link
Author

It would be great to add a test for this, given that there's currently only a single qsphere test which is displaying the | 1 ⟩ state, not a bell state. This would be similar to

def test_plot_state_qsphere(self):

just using a bell state instead. Generating the reference image can be a bit tricky though, due to slight mismatches in plot package versions. You could still try generating the reference locally, or otherwise download the generated image from the CI run. Let me know if you need help with this.

Done, I locally tested test_plot_entangled_state_qsphere and it passed the test

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

Labels

Community PR PRs from contributors that are not 'members' of the Qiskit repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

plot_state_qsphere Phase Anchor is sensitive to scipy.linalg.eigh noise, causing arbitrary rotation of symmetric states

5 participants