Skip to content

Commit

Permalink
Performance improvement of SparsePauliOp.to_matrix (#9620)
Browse files Browse the repository at this point in the history
* optimize SparsePauliOp.to_matrix

* optimize dense

* proper vectorize

* update copyright year

* small opt

* update a comment
  • Loading branch information
t-imamichi authored Mar 23, 2023
1 parent 262cf08 commit 84a0fdd
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 6 deletions.
25 changes: 20 additions & 5 deletions qiskit/quantum_info/operators/symplectic/base_pauli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020
# (C) Copyright IBM 2017, 2023
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand All @@ -26,6 +26,10 @@
from qiskit.quantum_info.operators.mixins import AdjointMixin, MultiplyMixin


# utility for _to_matrix
_PARITY = np.array([-1 if bin(i).count("1") % 2 else 1 for i in range(256)], dtype=complex)


class BasePauli(BaseOperator, AdjointMixin, MultiplyMixin):
r"""Symplectic representation of a list of N-qubit Paulis.
Expand Down Expand Up @@ -392,7 +396,7 @@ def _from_array(z, x, phase=0):

@staticmethod
def _to_matrix(z, x, phase=0, group_phase=False, sparse=False):
"""Return the matrix matrix from symplectic representation.
"""Return the matrix from symplectic representation.
The Pauli is defined as :math:`P = (-i)^{phase + z.x} * Z^z.x^x`
where ``array = [x, z]``.
Expand Down Expand Up @@ -430,7 +434,19 @@ def _to_matrix(z, x, phase=0, group_phase=False, sparse=False):
coeff = (-1j) ** phase
else:
coeff = 1
data = np.array([coeff * (-1) ** (bin(i).count("1") % 2) for i in z_indices & indptr])

# Compute parities of `z_indices & indptr`, i.e.,
# np.array([(-1) ** bin(i).count("1") for i in z_indices & indptr])
vec_u64 = z_indices & indptr
mat_u8 = np.zeros((vec_u64.size, 8), dtype=np.uint8)
for i in range(8):
mat_u8[:, i] = vec_u64 & 255
vec_u64 >>= 8
if np.all(vec_u64 == 0):
break
parity = _PARITY[np.bitwise_xor.reduce(mat_u8, axis=1)]

data = coeff * parity
if sparse:
# Return sparse matrix
from scipy.sparse import csr_matrix
Expand All @@ -439,8 +455,7 @@ def _to_matrix(z, x, phase=0, group_phase=False, sparse=False):

# Build dense matrix using csr format
mat = np.zeros((dim, dim), dtype=complex)
for i in range(dim):
mat[i][indices[indptr[i] : indptr[i + 1]]] = data[indptr[i] : indptr[i + 1]]
mat[range(dim), indices[:dim]] = data[:dim]
return mat

@staticmethod
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2017, 2020.
# (C) Copyright IBM 2017, 2023.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
Expand Down Expand Up @@ -236,6 +236,20 @@ def test_to_matrix(self):
for coeff, label in zip(coeffs, labels):
target += coeff * pauli_mat(label)
np.testing.assert_array_equal(spp_op.to_matrix(), target)
np.testing.assert_array_equal(spp_op.to_matrix(sparse=True).toarray(), target)

def test_to_matrix_large(self):
"""Test to_matrix method with a large number of qubits."""
reps = 5
labels = ["XI" * reps, "YZ" * reps, "YY" * reps, "ZZ" * reps]
coeffs = [-3, 4.4j, 0.2 - 0.1j, 66.12]
spp_op = SparsePauliOp(labels, coeffs)
size = 1 << 2 * reps
target = np.zeros((size, size), dtype=complex)
for coeff, label in zip(coeffs, labels):
target += coeff * pauli_mat(label)
np.testing.assert_array_equal(spp_op.to_matrix(), target)
np.testing.assert_array_equal(spp_op.to_matrix(sparse=True).toarray(), target)

def test_to_matrix_parameters(self):
"""Test to_matrix method for parameterized SparsePauliOp."""
Expand Down

0 comments on commit 84a0fdd

Please sign in to comment.