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

pauli_list.insert(..., qubit=True) with length-1 pauli_list sets phase to wrong shape #13623

Closed
aeddins-ibm opened this issue Jan 7, 2025 · 0 comments · Fixed by #13624
Closed
Labels
bug Something isn't working

Comments

@aeddins-ibm
Copy link
Contributor

aeddins-ibm commented Jan 7, 2025

Environment

  • Qiskit version: 1.3.1
  • Python version: 3.11
  • Operating system: macos

What is happening?

I believe the phase attribute of any PauliList should always be a 1D numpy array.

For instance, this creates a PauliList where the phase attribute is a 1D array of length 1:

plist = PauliList(['XYZ'])

However, this code:

plist = plist.insert(1, PauliList(['X']), qubit=True)

makes plist.phase a 2D numpy array (shape = 1 x 1).

This causes problems when further manipulating the object. For example, trying to pass it to SparsePauliOp() fails.

How can we reproduce the issue?

import qiskit.quantum_info as qi

plist1 = qi.PauliList(['XX'])

plist2 = qi.PauliList(['X'])
plist2 = plist2.insert(1, qi.PauliList(['X']), qubit=True)

# qiskit thinks the two PauliLists are equivalent:
print(f'{(plist1 == plist2) = }') # np.True_

# but their phase attributes are different shapes:
print(f'{plist1.phase.shape = }') # (1,)
print(f'{plist2.phase.shape = }') # (1, 1)

# and plist1 can be used to construct a SparsePauliOp:
qi.SparsePauliOp(plist1)

# whereas plist2 cannot:
qi.SparsePauliOp(plist2) # raises an error

What should happen?

The phase attribute of the output of insert should be a 1D array, not a 2D array.
Perhaps the PauliList.from_symplectic() constructor (called at the end of insert) should also verify/coerce that the phase argument is 1D.

(If that's somehow actually the correct behavior, then maybe we'd instead need to fix the SparsePauliOp constructor to handle that input.)

Any suggestions?

The issue may be how these lines in PauliList.insert() treat phase on the same footing as x and z, which are 2D arrays:

if len(value) == 1:
# Pad blocks to correct size
value_x = np.vstack(size * [value.x])
value_z = np.vstack(size * [value.z])
value_phase = np.vstack(size * [value.phase])

@aeddins-ibm aeddins-ibm added the bug Something isn't working label Jan 7, 2025
aeddins-ibm added a commit to aeddins-ibm/qiskit that referenced this issue Jan 8, 2025
`len(value) == 1` is the simplest case, and is also the case where the problematic clause `len(value) == size` fails.

This commit switches the order, so we check for `len(value) == 1` first.

This ensures that when the `len(value) == size` clause runs, we know that `size != 1`, avoiding the bug in Qiskit#13623.
github-merge-queue bot pushed a commit that referenced this issue Jan 8, 2025
…_list` (#13624)

* switch order of if-clauses

`len(value) == 1` is the simplest case, and is also the case where the problematic clause `len(value) == size` fails.

This commit switches the order, so we check for `len(value) == 1` first.

This ensures that when the `len(value) == size` clause runs, we know that `size != 1`, avoiding the bug in #13623.

* add test

* Simplify `value.phase` broadcasting

Here, `phase` should be a 1D array. `np.vstack()` docs say explicitly output will be at least 2D, so we should not use that to create `phase`.

The intent of using `np.vstack()` was essentially to broadcast `phase` to properly add to `self.phase`. But, this happens automatically and correctly if we just add as-is. So this commit simplifies the code accordingly.

* Verify that `phase` is 1D in `from_symplectic()`

Verifying the input arg `phase`, since otherwise `from_symplectic()` will silently create PauliLists with malformed `phase` attributes (i.e. not 1D).

Zero-dimensional is OK (e.g. `phase` defaults to `0`) since that can broadcast per the shape of the `z` and `x` arrays.

* remove vestigial line

* lint

* release note

* Update releasenotes/notes/fix-paulilist-length1-phase-688d0e3a64ec9a9f.yaml

Co-authored-by: Jake Lishman <[email protected]>

---------

Co-authored-by: Jake Lishman <[email protected]>
mergify bot pushed a commit that referenced this issue Jan 8, 2025
…_list` (#13624)

* switch order of if-clauses

`len(value) == 1` is the simplest case, and is also the case where the problematic clause `len(value) == size` fails.

This commit switches the order, so we check for `len(value) == 1` first.

This ensures that when the `len(value) == size` clause runs, we know that `size != 1`, avoiding the bug in #13623.

* add test

* Simplify `value.phase` broadcasting

Here, `phase` should be a 1D array. `np.vstack()` docs say explicitly output will be at least 2D, so we should not use that to create `phase`.

The intent of using `np.vstack()` was essentially to broadcast `phase` to properly add to `self.phase`. But, this happens automatically and correctly if we just add as-is. So this commit simplifies the code accordingly.

* Verify that `phase` is 1D in `from_symplectic()`

Verifying the input arg `phase`, since otherwise `from_symplectic()` will silently create PauliLists with malformed `phase` attributes (i.e. not 1D).

Zero-dimensional is OK (e.g. `phase` defaults to `0`) since that can broadcast per the shape of the `z` and `x` arrays.

* remove vestigial line

* lint

* release note

* Update releasenotes/notes/fix-paulilist-length1-phase-688d0e3a64ec9a9f.yaml

Co-authored-by: Jake Lishman <[email protected]>

---------

Co-authored-by: Jake Lishman <[email protected]>
(cherry picked from commit 408741c)
github-merge-queue bot pushed a commit that referenced this issue Jan 8, 2025
…_list` (#13624) (#13637)

* switch order of if-clauses

`len(value) == 1` is the simplest case, and is also the case where the problematic clause `len(value) == size` fails.

This commit switches the order, so we check for `len(value) == 1` first.

This ensures that when the `len(value) == size` clause runs, we know that `size != 1`, avoiding the bug in #13623.

* add test

* Simplify `value.phase` broadcasting

Here, `phase` should be a 1D array. `np.vstack()` docs say explicitly output will be at least 2D, so we should not use that to create `phase`.

The intent of using `np.vstack()` was essentially to broadcast `phase` to properly add to `self.phase`. But, this happens automatically and correctly if we just add as-is. So this commit simplifies the code accordingly.

* Verify that `phase` is 1D in `from_symplectic()`

Verifying the input arg `phase`, since otherwise `from_symplectic()` will silently create PauliLists with malformed `phase` attributes (i.e. not 1D).

Zero-dimensional is OK (e.g. `phase` defaults to `0`) since that can broadcast per the shape of the `z` and `x` arrays.

* remove vestigial line

* lint

* release note

* Update releasenotes/notes/fix-paulilist-length1-phase-688d0e3a64ec9a9f.yaml

Co-authored-by: Jake Lishman <[email protected]>

---------

Co-authored-by: Jake Lishman <[email protected]>
(cherry picked from commit 408741c)

Co-authored-by: aeddins-ibm <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant