Skip to content

Commit

Permalink
Added argsort and sort method to SparsePauliOp (Qiskit#8016)
Browse files Browse the repository at this point in the history
* first share

* Remove reverse option

* Apply Lint

* Modify wording

* add release note

* Correct the text

* Correct the text

* Delete words

* Added description of arguments

* Fix typos & update reno

Co-authored-by: Julien Gacon <[email protected]>
  • Loading branch information
kUmezawa and Cryoris authored Jul 12, 2022
1 parent 3c419a9 commit 925d8cb
Show file tree
Hide file tree
Showing 3 changed files with 252 additions and 0 deletions.
111 changes: 111 additions & 0 deletions qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,117 @@ def simplify(self, atol=None, rtol=None):
PauliList.from_symplectic(z, x), coeffs, ignore_pauli_phase=True, copy=False
)

def argsort(self, weight=False):
"""Return indices for sorting the rows of the table.
Returns the composition of permutations in the order of sorting
by coefficient and sorting by Pauli.
By using the `weight` kwarg the output can additionally be sorted
by the number of non-identity terms in the Pauli, where the set of
all Pauli's of a given weight are still ordered lexicographically.
**Example**
Here is an example of how to use SparsePauliOp argsort.
.. jupyter-execute::
import numpy as np
from qiskit.quantum_info import SparsePauliOp
# 2-qubit labels
labels = ["XX", "XX", "XX", "YI", "II", "XZ", "XY", "XI"]
# coeffs
coeffs = [2.+1.j, 2.+2.j, 3.+0.j, 3.+0.j, 4.+0.j, 5.+0.j, 6.+0.j, 7.+0.j]
# init
spo = SparsePauliOp(labels, coeffs)
print('Initial Ordering')
print(spo)
# Lexicographic Ordering
srt = spo.argsort()
print('Lexicographically sorted')
print(srt)
# Lexicographic Ordering
srt = spo.argsort(weight=False)
print('Lexicographically sorted')
print(srt)
# Weight Ordering
srt = spo.argsort(weight=True)
print('Weight sorted')
print(srt)
Args:
weight (bool): optionally sort by weight if True (Default: False).
By using the weight kwarg the output can additionally be sorted
by the number of non-identity terms in the Pauli.
Returns:
array: the indices for sorting the table.
"""
sort_coeffs_inds = np.argsort(self._coeffs, kind="stable")
pauli_list = self._pauli_list[sort_coeffs_inds]
sort_pauli_inds = pauli_list.argsort(weight=weight, phase=False)
return sort_coeffs_inds[sort_pauli_inds]

def sort(self, weight=False):
"""Sort the rows of the table.
After sorting the coefficients using numpy's argsort, sort by Pauli.
Pauli sort takes precedence.
If Pauli is the same, it will be sorted by coefficient.
By using the `weight` kwarg the output can additionally be sorted
by the number of non-identity terms in the Pauli, where the set of
all Pauli's of a given weight are still ordered lexicographically.
**Example**
Here is an example of how to use SparsePauliOp sort.
.. jupyter-execute::
import numpy as np
from qiskit.quantum_info import SparsePauliOp
# 2-qubit labels
labels = ["XX", "XX", "XX", "YI", "II", "XZ", "XY", "XI"]
# coeffs
coeffs = [2.+1.j, 2.+2.j, 3.+0.j, 3.+0.j, 4.+0.j, 5.+0.j, 6.+0.j, 7.+0.j]
# init
spo = SparsePauliOp(labels, coeffs)
print('Initial Ordering')
print(spo)
# Lexicographic Ordering
srt = spo.sort()
print('Lexicographically sorted')
print(srt)
# Lexicographic Ordering
srt = spo.sort(weight=False)
print('Lexicographically sorted')
print(srt)
# Weight Ordering
srt = spo.sort(weight=True)
print('Weight sorted')
print(srt)
Args:
weight (bool): optionally sort by weight if True (Default: False).
By using the weight kwarg the output can additionally be sorted
by the number of non-identity terms in the Pauli.
Returns:
SparsePauliOp: a sorted copy of the original table.
"""
indices = self.argsort(weight=weight)
return SparsePauliOp(self._pauli_list[indices], self._coeffs[indices])

def chop(self, tol=1e-14):
"""Set real and imaginary parts of the coefficients to 0 if ``< tol`` in magnitude.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
features:
- |
Added a new method :meth:`.SparsePauliOp.argsort`, which
returns the composition of permutations in the order of sorting
by coefficient and sorting by Pauli. By using the `weight` kwarg
the output can additionally be sorted
by the number of non-identity terms in the Pauli, where the set of
all Pauli's of a given weight are still ordered lexicographically.
- |
Added a new method :meth:`.SparsePauliOp.sort`.
After sorting the coefficients using numpy's argsort, sort by Pauli.
Pauli sort takes precedence. If Pauli is the same, it will be sorted
by coefficient. By using the `weight` kwarg the output can additionally
be sorted by the number of non-identity terms in the Pauli, where the
set ofall Pauli's of a given weight are still ordered lexicographically.
126 changes: 126 additions & 0 deletions test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,132 @@ def test_simplify_zero(self, num_qubits):
np.testing.assert_array_equal(zero_op.paulis.phase, np.zeros(zero_op.size))
np.testing.assert_array_equal(simplified_op.paulis.phase, np.zeros(simplified_op.size))

def test_sort(self):
"""Test sort method."""
with self.assertRaises(QiskitError):
target = SparsePauliOp([], [])

with self.subTest(msg="1 qubit real number"):
target = SparsePauliOp(
["I", "I", "I", "I"], [-3.0 + 0.0j, 1.0 + 0.0j, 2.0 + 0.0j, 4.0 + 0.0j]
)
value = SparsePauliOp(["I", "I", "I", "I"], [1, 2, -3, 4]).sort()
self.assertEqual(target, value)

with self.subTest(msg="1 qubit complex"):
target = SparsePauliOp(
["I", "I", "I", "I"], [-1.0 + 0.0j, 0.0 - 1.0j, 0.0 + 1.0j, 1.0 + 0.0j]
)
value = SparsePauliOp(
["I", "I", "I", "I"], [1.0 + 0.0j, 0.0 + 1.0j, 0.0 - 1.0j, -1.0 + 0.0j]
).sort()
self.assertEqual(target, value)

with self.subTest(msg="1 qubit Pauli I, X, Y, Z"):
target = SparsePauliOp(
["I", "X", "Y", "Z"], [-1.0 + 2.0j, 1.0 + 0.0j, 2.0 + 0.0j, 3.0 - 4.0j]
)
value = SparsePauliOp(
["Y", "X", "Z", "I"], [2.0 + 0.0j, 1.0 + 0.0j, 3.0 - 4.0j, -1.0 + 2.0j]
).sort()
self.assertEqual(target, value)

with self.subTest(msg="1 qubit weight order"):
target = SparsePauliOp(
["I", "X", "Y", "Z"], [-1.0 + 2.0j, 1.0 + 0.0j, 2.0 + 0.0j, 3.0 - 4.0j]
)
value = SparsePauliOp(
["Y", "X", "Z", "I"], [2.0 + 0.0j, 1.0 + 0.0j, 3.0 - 4.0j, -1.0 + 2.0j]
).sort(weight=True)
self.assertEqual(target, value)

with self.subTest(msg="1 qubit multi Pauli"):
target = SparsePauliOp(
["I", "I", "I", "I", "X", "X", "Y", "Z"],
[
-1.0 + 2.0j,
1.0 + 0.0j,
2.0 + 0.0j,
3.0 - 4.0j,
-1.0 + 4.0j,
-1.0 + 5.0j,
-1.0 + 3.0j,
-1.0 + 2.0j,
],
)
value = SparsePauliOp(
["I", "I", "I", "I", "X", "Z", "Y", "X"],
[
2.0 + 0.0j,
1.0 + 0.0j,
3.0 - 4.0j,
-1.0 + 2.0j,
-1.0 + 5.0j,
-1.0 + 2.0j,
-1.0 + 3.0j,
-1.0 + 4.0j,
],
).sort()
self.assertEqual(target, value)

with self.subTest(msg="2 qubit standard order"):
target = SparsePauliOp(
["II", "XI", "XX", "XX", "XX", "XY", "XZ", "YI"],
[
4.0 + 0.0j,
7.0 + 0.0j,
2.0 + 1.0j,
2.0 + 2.0j,
3.0 + 0.0j,
6.0 + 0.0j,
5.0 + 0.0j,
3.0 + 0.0j,
],
)
value = SparsePauliOp(
["XX", "XX", "XX", "YI", "II", "XZ", "XY", "XI"],
[
2.0 + 1.0j,
2.0 + 2.0j,
3.0 + 0.0j,
3.0 + 0.0j,
4.0 + 0.0j,
5.0 + 0.0j,
6.0 + 0.0j,
7.0 + 0.0j,
],
).sort()
self.assertEqual(target, value)

with self.subTest(msg="2 qubit weight order"):
target = SparsePauliOp(
["II", "XI", "YI", "XX", "XX", "XX", "XY", "XZ"],
[
4.0 + 0.0j,
7.0 + 0.0j,
3.0 + 0.0j,
2.0 + 1.0j,
2.0 + 2.0j,
3.0 + 0.0j,
6.0 + 0.0j,
5.0 + 0.0j,
],
)
value = SparsePauliOp(
["XX", "XX", "XX", "YI", "II", "XZ", "XY", "XI"],
[
2.0 + 1.0j,
2.0 + 2.0j,
3.0 + 0.0j,
3.0 + 0.0j,
4.0 + 0.0j,
5.0 + 0.0j,
6.0 + 0.0j,
7.0 + 0.0j,
],
).sort(weight=True)
self.assertEqual(target, value)

def test_chop(self):
"""Test chop, which individually truncates real and imaginary parts of the coeffs."""
eps = 1e-10
Expand Down

0 comments on commit 925d8cb

Please sign in to comment.