Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/openfermion/linalg/sparse_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,8 @@ def qubit_operator_sparse(qubit_operator, n_qubits=None):
column_list = [[]]

# Loop through the terms.
for qubit_term in qubit_operator.terms:
for qubit_term, coefficient in _iter_simplified_qubit_operator_terms(qubit_operator):
tensor_factor = 0
coefficient = qubit_operator.terms[qubit_term]
sparse_operators = [coefficient]
for pauli_operator in qubit_term:
# Grow space for missing identity operators.
Expand Down Expand Up @@ -194,6 +193,13 @@ def qubit_operator_sparse(qubit_operator, n_qubits=None):
return sparse_operator


def _iter_simplified_qubit_operator_terms(qubit_operator):
"""Yield terms after applying QubitOperator's Pauli simplification."""
for qubit_term, coefficient in qubit_operator.terms.items():
coefficient, qubit_term = qubit_operator._simplify(qubit_term, coefficient)
yield qubit_term, coefficient
Comment on lines +196 to +200

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Calling _simplify on every term of the QubitOperator introduces a significant performance overhead (due to sorting, dictionary lookups, and tuple allocations), especially for large operators with tens of thousands of terms. Since the vast majority of QubitOperator instances are already simplified, we can optimize this by first checking if the term is already simplified (i.e., indices are strictly increasing and there are no identity 'I' operators). If it is already simplified, we can yield it directly and bypass _simplify entirely.

def _iter_simplified_qubit_operator_terms(qubit_operator):
    """Yield terms after applying QubitOperator's Pauli simplification."""
    for qubit_term, coefficient in qubit_operator.terms.items():
        is_simplified = True
        prev_index = -1
        for index, action in qubit_term:
            if action == 'I' or index <= prev_index:
                is_simplified = False
                break
            prev_index = index

        if is_simplified:
            yield qubit_term, coefficient
        else:
            coefficient, qubit_term = qubit_operator._simplify(qubit_term, coefficient)
            yield qubit_term, coefficient



def get_linear_qubit_operator_diagonal(qubit_operator, n_qubits=None):
"""Return a linear operator's diagonal elements.

Expand Down Expand Up @@ -221,7 +227,7 @@ def get_linear_qubit_operator_diagonal(qubit_operator, n_qubits=None):
ones_diagonal = numpy.ones(n_hilbert)
linear_operator_diagonal = zeros_diagonal
# Loop through the terms.
for qubit_term in qubit_operator.terms:
for qubit_term, coefficient in _iter_simplified_qubit_operator_terms(qubit_operator):
is_zero = False
tensor_factor = 0
vecs = [ones_diagonal]
Expand All @@ -243,7 +249,7 @@ def get_linear_qubit_operator_diagonal(qubit_operator, n_qubits=None):
vecs = [v for vp in vec_pairs for v in (vp[0], -vp[1])]
tensor_factor = pauli_operator[0] + 1
if not is_zero:
linear_operator_diagonal += qubit_operator.terms[qubit_term] * numpy.concatenate(vecs)
linear_operator_diagonal += coefficient * numpy.concatenate(vecs)
return linear_operator_diagonal


Expand Down
41 changes: 40 additions & 1 deletion src/openfermion/linalg/sparse_tools_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@
MajoranaOperator,
)
from openfermion.ops.representations import DiagonalCoulombHamiltonian
from openfermion.transforms.opconversions import jordan_wigner, get_fermion_operator, normal_ordered
from openfermion.transforms.opconversions import (
jordan_wigner,
get_fermion_operator,
normal_ordered,
symmetry_conserving_bravyi_kitaev,
)
from openfermion.transforms.repconversions import fourier_transform, get_interaction_operator

from openfermion.testing.testing_utils import random_hermitian_matrix
Expand Down Expand Up @@ -137,6 +142,40 @@ def test_qubit_jw_fermion_integration(self):
fermion_spectrum = sparse_eigenspectrum(fermion_sparse)
self.assertAlmostEqual(0.0, numpy.amax(numpy.absolute(fermion_spectrum - qubit_spectrum)))

def test_qubit_operator_sparse_simplifies_repeated_qubit_factors(self):
qubit_operator = QubitOperator()
qubit_operator.terms[((0, 'X'), (1, 'Y'), (1, 'X'))] = 1.0
expected_operator = -1j * QubitOperator('X0 Z1')

sparse_operator = qubit_operator_sparse(qubit_operator, n_qubits=2)
expected_sparse_operator = qubit_operator_sparse(expected_operator, n_qubits=2)

self.assertTrue(
numpy.allclose(sparse_operator.toarray(), expected_sparse_operator.toarray())
)

def test_get_sparse_operator_with_unsimplified_symmetry_conserving_bk(self):
fermion_operator = FermionOperator('0^ 1^')
qubit_operator = symmetry_conserving_bravyi_kitaev(fermion_operator, 4, 2)
expected_operator = QubitOperator()
for qubit_term, coefficient in qubit_operator.terms.items():
expected_operator += QubitOperator(qubit_term, coefficient)

sparse_operator = get_sparse_operator(qubit_operator)
expected_sparse_operator = get_sparse_operator(expected_operator)

self.assertTrue(
numpy.allclose(sparse_operator.toarray(), expected_sparse_operator.toarray())
)

def test_get_linear_qubit_operator_diagonal_simplifies_repeated_qubit_factors(self):
qubit_operator = QubitOperator()
qubit_operator.terms[((0, 'Z'), (1, 'Z'), (1, 'Z'))] = 2.0

diagonal = get_linear_qubit_operator_diagonal(qubit_operator, n_qubits=2)

self.assertTrue(numpy.allclose(diagonal, 2.0 * numpy.array([1, 1, -1, -1])))


class JordanWignerSparseTest(unittest.TestCase):
def test_jw_sparse_0create(self):
Expand Down
Loading