From cea93a051671b4c56945e916a70d22c2f5d488d8 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi <31178928+t-imamichi@users.noreply.github.com> Date: Tue, 14 May 2024 20:09:01 +0400 Subject: [PATCH] Fix a corner case of `SparsePauliOp.apply_layout` (#12375) * fix a corner case of `SparsePauliOp.apply_layout` * Add zero-qubit tests of Pauli.apply_layout * use combine and apply isort * Update releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml --------- Co-authored-by: Matthew Treinish --- .../operators/symplectic/sparse_pauli_op.py | 2 + ...op-apply-layout-zero-43b9e70f0d1536a6.yaml | 10 +++++ .../operators/symplectic/test_pauli.py | 40 +++++++++++-------- .../symplectic/test_sparse_pauli_op.py | 31 ++++++++++---- 4 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml diff --git a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py index cf51579bef..440a0319c3 100644 --- a/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py +++ b/qiskit/quantum_info/operators/symplectic/sparse_pauli_op.py @@ -1165,6 +1165,8 @@ class SparsePauliOp(LinearOp): raise QiskitError("Provided layout contains indices outside the number of qubits.") if len(set(layout)) != len(layout): raise QiskitError("Provided layout contains duplicate indices.") + if self.num_qubits == 0: + return type(self)(["I" * n_qubits] * self.size, self.coeffs) new_op = type(self)("I" * n_qubits) return new_op.compose(self, qargs=layout) diff --git a/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml new file mode 100644 index 0000000000..117230aee5 --- /dev/null +++ b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml @@ -0,0 +1,10 @@ +fixes: + - | + Fixed :meth:`.SparsePauliOp.apply_layout` to work correctly with zero-qubit operators. + For example, if you previously created a 0 qubit and applied a layout like:: + + op = SparsePauliOp("") + op.apply_layout(None, 3) + + this would have previously raised an error. Now this will correctly return an operator of the form: + ``SparsePauliOp(['III'], coeffs=[1.+0.j])`` diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index 89324e8212..35acd46a4d 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -14,42 +14,41 @@ """Tests for Pauli operator class.""" +import itertools as it import re import unittest -import itertools as it from functools import lru_cache +from test import QiskitTestCase, combine + import numpy as np -from ddt import ddt, data, unpack +from ddt import data, ddt, unpack from qiskit import QuantumCircuit from qiskit.circuit import Qubit -from qiskit.exceptions import QiskitError from qiskit.circuit.library import ( + CXGate, + CYGate, + CZGate, + ECRGate, + EfficientSU2, + HGate, IGate, + SdgGate, + SGate, + SwapGate, XGate, YGate, ZGate, - HGate, - SGate, - SdgGate, - CXGate, - CZGate, - CYGate, - SwapGate, - ECRGate, - EfficientSU2, ) from qiskit.circuit.library.generalized_gates import PauliGate from qiskit.compiler.transpiler import transpile -from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.exceptions import QiskitError from qiskit.primitives import BackendEstimator +from qiskit.providers.fake_provider import GenericBackendV2 +from qiskit.quantum_info.operators import Operator, Pauli, SparsePauliOp from qiskit.quantum_info.random import random_clifford, random_pauli -from qiskit.quantum_info.operators import Pauli, Operator, SparsePauliOp from qiskit.utils import optionals -from test import QiskitTestCase # pylint: disable=wrong-import-order - - LABEL_REGEX = re.compile(r"(?P[+-]?1?[ij]?)(?P[IXYZ]*)") PHASE_MAP = {"": 0, "-i": 1, "-": 2, "i": 3} @@ -618,6 +617,13 @@ class TestPauli(QiskitTestCase): with self.assertRaises(QiskitError): op.apply_layout(layout=[0, 0], num_qubits=3) + @combine(phase=["", "-i", "-", "i"], layout=[None, []]) + def test_apply_layout_zero_qubit(self, phase, layout): + """Test apply_layout with a zero-qubit operator""" + op = Pauli(phase) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(Pauli(phase + "IIIII"), res) + if __name__ == "__main__": unittest.main() diff --git a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py index 1149ef1f34..e7a9b89b73 100644 --- a/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py +++ b/test/python/quantum_info/operators/symplectic/test_sparse_pauli_op.py @@ -14,23 +14,22 @@ import itertools as it import unittest +from test import QiskitTestCase, combine + import numpy as np -import scipy.sparse import rustworkx as rx +import scipy.sparse from ddt import ddt - from qiskit import QiskitError -from qiskit.circuit import ParameterExpression, Parameter, ParameterVector -from qiskit.circuit.parametertable import ParameterView -from qiskit.quantum_info.operators import Operator, Pauli, PauliList, SparsePauliOp +from qiskit.circuit import Parameter, ParameterExpression, ParameterVector from qiskit.circuit.library import EfficientSU2 +from qiskit.circuit.parametertable import ParameterView +from qiskit.compiler.transpiler import transpile from qiskit.primitives import BackendEstimator from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit.compiler.transpiler import transpile +from qiskit.quantum_info.operators import Operator, Pauli, PauliList, SparsePauliOp from qiskit.utils import optionals -from test import QiskitTestCase # pylint: disable=wrong-import-order -from test import combine # pylint: disable=wrong-import-order def pauli_mat(label): @@ -1191,6 +1190,22 @@ class TestSparsePauliOpMethods(QiskitTestCase): with self.assertRaises(QiskitError): op.apply_layout(layout=[0, 0], num_qubits=3) + @combine(layout=[None, []]) + def test_apply_layout_zero_qubit(self, layout): + """Test apply_layout with a zero-qubit operator""" + with self.subTest("default"): + op = SparsePauliOp("") + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII"), res) + with self.subTest("coeff"): + op = SparsePauliOp("", 2) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII", 2), res) + with self.subTest("multiple ops"): + op = SparsePauliOp.from_list([("", 1), ("", 2)]) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(SparsePauliOp.from_list([("IIIII", 1), ("IIIII", 2)]), res) + if __name__ == "__main__": unittest.main()