Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correctly updating global phase when removing gates that are identity up to a global phase #13785

Open
wants to merge 7 commits into from

Conversation

alexanderivrii
Copy link
Contributor

@alexanderivrii alexanderivrii commented Feb 4, 2025

Summary

This fixes a bug in the RemoveIdentityEquivalent transpiler pass (the pass was introduced in #12384), where unitary gates close to identity up to a global phase were removed from the circuit but the global phase of the circuit was not updated. This includes, for instance, single-qubit unitary gates with matrices close to [[ $e^{i\theta}$, 0], [0, $e^{i\theta}$]] for fix floating-point values of $\theta$, standard gates $R_X(2\pi)$, $R_Y(2\pi)$, $R_Z(2\pi)$ (whose matrices are close to $-I$), and some others. Now the pass removes these gates and correctly updates the global phase of the circuit. In particular, the pass now handles non-parameterized GlobalPhaseGates as well.

Fixes #13778.

Details and comments:

The pass ignores parameterized gates. We could probably relatively easily extend it to handle parameterized global phase gates.

The thing to notice in the current documentation of the pass is that the current condition for removing a gate is indeed satisfied for gates of the form $e^{i\theta} I$. And in fact the opposite is also true (thanks to @ShellyGarion for helping me work out the math): if the removal condition is satisfied, then a gate is necessarily of the form $e^{i\theta} I$.

Lol, yet another change after the #13759 was merged is to now also explicitly handle Unitary gates.

Thanks to @Cryoris and @ShellyGarion for joint debugging of the problem.

@alexanderivrii alexanderivrii added stable backport potential The bug might be minimal and/or import enough to be port to stable Changelog: Bugfix Include in the "Fixed" section of the changelog labels Feb 4, 2025
@alexanderivrii alexanderivrii added this to the 2.0.0 milestone Feb 4, 2025
@alexanderivrii alexanderivrii requested a review from a team as a code owner February 4, 2025 15:41
@qiskit-bot
Copy link
Collaborator

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

  • @Qiskit/terra-core

@mtreinish mtreinish modified the milestones: 2.0.0, 1.3.3 Feb 4, 2025
@alexanderivrii
Copy link
Contributor Author

alexanderivrii commented Feb 4, 2025

Update: there is another part of the pass dealing with RX, RY, RZ and RXXGate, RYYGate, RZZGate, RZXGate gates that I should look at to see if a similar problem is possible that is similarly fixed in b7713a1.

@coveralls
Copy link

coveralls commented Feb 4, 2025

Pull Request Test Coverage Report for Build 13242973242

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

  • 28 of 28 (100.0%) changed or added relevant lines in 1 file are covered.
  • 680 unchanged lines in 32 files lost coverage.
  • Overall coverage decreased (-0.3%) to 88.297%

Files with Coverage Reduction New Missed Lines %
qiskit/pulse/schedule.py 1 88.42%
qiskit/utils/parallel.py 1 80.7%
qiskit/pulse/instruction_schedule_map.py 1 95.9%
qiskit/circuit/add_control.py 1 97.69%
qiskit/providers/fake_provider/fake_openpulse_3q.py 1 92.86%
qiskit/transpiler/passes/scheduling/alignments/check_durations.py 1 77.78%
crates/accelerate/src/euler_one_qubit_decomposer.rs 1 91.5%
crates/accelerate/src/target_transpiler/nullable_index_map.rs 1 67.17%
crates/qasm2/src/expr.rs 1 94.23%
crates/circuit/src/operations.rs 2 88.34%
Totals Coverage Status
Change from base Build 13180493627: -0.3%
Covered Lines: 78791
Relevant Lines: 89234

💛 - Coveralls

Copy link
Contributor

@Cryoris Cryoris 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 tackling this!

I would expect this needs to cover the full spectrum of operations that are the identity up to a global phase, i.e. exp(i phi) * I 🙂 This could be done e.g. by just looking at the first entry of the matrix and extracting the phase (similar to how it is done in Operator.equiv).

@alexanderivrii
Copy link
Contributor Author

@Cryoris, indeed, you are absolutely correct, we have the same problem with all of the operations that are identity up to a global phase, I will need to check in an updated fix shortly. A related question is do you know why the global phase gates were ignored by RemoveIdentityEquivalent pass?

@alexanderivrii alexanderivrii changed the title correctly updating global phase when removing -I gates from the circuit Correctly updating global phase when removing gates that are identity up to a global phase Feb 6, 2025
@alexanderivrii
Copy link
Contributor Author

Hmm, after merging with main (and in particular with #13759), I had to do a small fix to support unitary matrices coming from unitary gates. However, this means that the pass now can't be backported as is, in the backported version the block for unitary matrices needs to be removed.

ShellyGarion
ShellyGarion previously approved these changes Feb 9, 2025
Copy link
Member

@ShellyGarion ShellyGarion left a comment

Choose a reason for hiding this comment

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

LGTM. Thanks @alexanderivrii for fixing this bug. I'm not sure to which branch it should be merged.

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

Two tiny comments but the logic LGTM!

// Skip global phase like gate
if gate.num_qubits() < 1 {
continue;
if let Some(matrix) = gate.matrix(inst.params_view()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if the Gate and UnitaryGate branches could be merged, as they are identical (right?) and both are OperationRef and provide the matrix method... maybe something like

let view = inst.op.view();

match view { 
    // existing parts...
    OperationRef::Gate | OperationRef::Unitary => {
        if let Some(matrix) = view.matrix(inst.params_view()) {
            // existing parts...
        }
    }
 }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a nice suggestion, but the compiler does not like it. The problem is that gate has different types in the two arms: in OperationRef::Gate(gate) it's a &PyGate, and in OperationRef::UnitaryGate(gate) it's a &UnitaryGate.


if global_phase_update != 0. {
dag.add_global_phase(py, &Param::Float(global_phase_update))
.unwrap();
Copy link
Contributor

Choose a reason for hiding this comment

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

The addition can only fail if the Parameter is not a float, so we can give a reason why this shouldn't fail (and if it does fail, then we know what assumption we had that's wrong 🙂)

Suggested change
.unwrap();
.unwrap("The global phase is guaranteed to be a float");

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 68e6a7f. And of course, you mean expect instead of unwrap.

Comment on lines 4 to 8
Fixed a bug in :class:`.RemoveIdentityEquivalent` transpiler pass, where the
unitary gates close to identity up to a global phase were removed from the circuit,
but the global phase of the circuit was not updated. In particular,
:class:`.RemoveIdentityEquivalent` now removes non-parameterized :class:`.GlobalPhaseGate`
gates.
Copy link
Contributor

Choose a reason for hiding this comment

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

To me the adjective "unitary" here could mean we refer to UnitaryGate objects, maybe removing it is better (since it could have been any other gate)?

Suggested change
Fixed a bug in :class:`.RemoveIdentityEquivalent` transpiler pass, where the
unitary gates close to identity up to a global phase were removed from the circuit,
but the global phase of the circuit was not updated. In particular,
:class:`.RemoveIdentityEquivalent` now removes non-parameterized :class:`.GlobalPhaseGate`
gates.
Fixed a bug in the :class:`.RemoveIdentityEquivalent` transpiler pass, where
gates close to identity up to a global phase were removed from the circuit,
but the global phase of the circuit was not updated. In particular,
:class:`.RemoveIdentityEquivalent` now removes non-parameterized :class:`.GlobalPhaseGate`
gates.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, done in 68e6a7f.

Copy link
Contributor

@Cryoris Cryoris left a comment

Choose a reason for hiding this comment

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

This looks great, thank you! 🙂

@Cryoris Cryoris added this pull request to the merge queue Feb 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Changelog: Bugfix Include in the "Fixed" section of the changelog stable backport potential The bug might be minimal and/or import enough to be port to stable
Projects
Status: To do
Development

Successfully merging this pull request may close these issues.

incorrect statevector simulation using qiskit & qiskit-aer
6 participants