From 49e96faf3680c3a9aa7b244d8dd2fa5cedb52878 Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Thu, 9 May 2024 12:09:43 +0900 Subject: [PATCH 1/4] fix a corner case of `SparsePauliOp.apply_layout` --- .../operators/symplectic/sparse_pauli_op.py | 2 ++ ...op-apply-layout-zero-43b9e70f0d1536a6.yaml | 3 +++ .../symplectic/test_sparse_pauli_op.py | 19 +++++++++++++++++++ 3 files changed, 24 insertions(+) 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 cf51579bef8..440a0319c33 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 @@ def apply_layout( 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 00000000000..e2f0285e3b2 --- /dev/null +++ b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml @@ -0,0 +1,3 @@ +fixes: + - | + Fixed :meth:`~.SparsePauliOp.apply_layout` to work correctly with zero-qubit operators. 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 1149ef1f346..358e58fe304 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 @@ -1191,6 +1191,25 @@ def test_apply_layout_duplicate_indices(self): with self.assertRaises(QiskitError): op.apply_layout(layout=[0, 0], num_qubits=3) + def test_apply_layout_zero_qubit(self): + """Test apply_layout with a zero-qubit operator""" + with self.subTest("default"): + op = SparsePauliOp("") + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII"), res) + with self.subTest("coeff"): + op = SparsePauliOp("", 2) + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII", 2), res) + with self.subTest("layout"): + op = SparsePauliOp("") + res = op.apply_layout(layout=[], num_qubits=5) + self.assertEqual(SparsePauliOp("IIIII"), res) + with self.subTest("multiple ops"): + op = SparsePauliOp.from_list([("", 1), ("", 2)]) + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(SparsePauliOp.from_list([("IIIII", 1), ("IIIII", 2)]), res) + if __name__ == "__main__": unittest.main() From 5cdaf81be63df3a039009a278c9cf5d8c7fc9c3c Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Mon, 13 May 2024 16:23:36 +0900 Subject: [PATCH 2/4] Add zero-qubit tests of Pauli.apply_layout --- ...op-apply-layout-zero-43b9e70f0d1536a6.yaml | 2 +- .../operators/symplectic/test_pauli.py | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) 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 index e2f0285e3b2..551fc245bb3 100644 --- a/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml +++ b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml @@ -1,3 +1,3 @@ fixes: - | - Fixed :meth:`~.SparsePauliOp.apply_layout` to work correctly with zero-qubit operators. + Fixed :meth:`.SparsePauliOp.apply_layout` to work correctly with zero-qubit operators. diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index 89324e8212e..a1dda6c00ee 100644 --- a/test/python/quantum_info/operators/symplectic/test_pauli.py +++ b/test/python/quantum_info/operators/symplectic/test_pauli.py @@ -618,6 +618,29 @@ def test_apply_layout_duplicate_indices(self): with self.assertRaises(QiskitError): op.apply_layout(layout=[0, 0], num_qubits=3) + def test_apply_layout_zero_qubit(self): + """Test apply_layout with a zero-qubit operator""" + with self.subTest("default"): + op = Pauli("") + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(Pauli("IIIII"), res) + with self.subTest("phase -1j"): + op = Pauli("-i") + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(Pauli("-iIIIII"), res) + with self.subTest("phase -1"): + op = Pauli("-") + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(Pauli("-IIIII"), res) + with self.subTest("phase 1j"): + op = Pauli("i") + res = op.apply_layout(layout=None, num_qubits=5) + self.assertEqual(Pauli("iIIIII"), res) + with self.subTest("layout"): + op = Pauli("") + res = op.apply_layout(layout=[], num_qubits=5) + self.assertEqual(Pauli("IIIII"), res) + if __name__ == "__main__": unittest.main() From 44a18115e526ad89d66dda1a83923f7de9f0602f Mon Sep 17 00:00:00 2001 From: Takashi Imamichi Date: Tue, 14 May 2024 10:33:51 +0900 Subject: [PATCH 3/4] use combine and apply isort --- .../operators/symplectic/test_pauli.py | 59 +++++++------------ .../symplectic/test_sparse_pauli_op.py | 28 ++++----- 2 files changed, 33 insertions(+), 54 deletions(-) diff --git a/test/python/quantum_info/operators/symplectic/test_pauli.py b/test/python/quantum_info/operators/symplectic/test_pauli.py index a1dda6c00ee..35acd46a4d0 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 ( - IGate, - XGate, - YGate, - ZGate, - HGate, - SGate, - SdgGate, CXGate, - CZGate, CYGate, - SwapGate, + CZGate, ECRGate, EfficientSU2, + HGate, + IGate, + SdgGate, + SGate, + SwapGate, + XGate, + YGate, + ZGate, ) 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,28 +617,12 @@ def test_apply_layout_duplicate_indices(self): with self.assertRaises(QiskitError): op.apply_layout(layout=[0, 0], num_qubits=3) - def test_apply_layout_zero_qubit(self): + @combine(phase=["", "-i", "-", "i"], layout=[None, []]) + def test_apply_layout_zero_qubit(self, phase, layout): """Test apply_layout with a zero-qubit operator""" - with self.subTest("default"): - op = Pauli("") - res = op.apply_layout(layout=None, num_qubits=5) - self.assertEqual(Pauli("IIIII"), res) - with self.subTest("phase -1j"): - op = Pauli("-i") - res = op.apply_layout(layout=None, num_qubits=5) - self.assertEqual(Pauli("-iIIIII"), res) - with self.subTest("phase -1"): - op = Pauli("-") - res = op.apply_layout(layout=None, num_qubits=5) - self.assertEqual(Pauli("-IIIII"), res) - with self.subTest("phase 1j"): - op = Pauli("i") - res = op.apply_layout(layout=None, num_qubits=5) - self.assertEqual(Pauli("iIIIII"), res) - with self.subTest("layout"): - op = Pauli("") - res = op.apply_layout(layout=[], num_qubits=5) - self.assertEqual(Pauli("IIIII"), res) + op = Pauli(phase) + res = op.apply_layout(layout=layout, num_qubits=5) + self.assertEqual(Pauli(phase + "IIIII"), res) if __name__ == "__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 358e58fe304..e7a9b89b731 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,23 +1190,20 @@ def test_apply_layout_duplicate_indices(self): with self.assertRaises(QiskitError): op.apply_layout(layout=[0, 0], num_qubits=3) - def test_apply_layout_zero_qubit(self): + @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=None, num_qubits=5) + 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=None, num_qubits=5) + res = op.apply_layout(layout=layout, num_qubits=5) self.assertEqual(SparsePauliOp("IIIII", 2), res) - with self.subTest("layout"): - op = SparsePauliOp("") - res = op.apply_layout(layout=[], num_qubits=5) - self.assertEqual(SparsePauliOp("IIIII"), res) with self.subTest("multiple ops"): op = SparsePauliOp.from_list([("", 1), ("", 2)]) - res = op.apply_layout(layout=None, num_qubits=5) + res = op.apply_layout(layout=layout, num_qubits=5) self.assertEqual(SparsePauliOp.from_list([("IIIII", 1), ("IIIII", 2)]), res) From f0239ab38fa5805c05ea5efbc05586b8d5735cba Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Tue, 14 May 2024 10:47:14 -0400 Subject: [PATCH 4/4] Update releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml --- ...sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml | 7 +++++++ 1 file changed, 7 insertions(+) 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 index 551fc245bb3..117230aee53 100644 --- a/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml +++ b/releasenotes/notes/fix-sparse-pauli-op-apply-layout-zero-43b9e70f0d1536a6.yaml @@ -1,3 +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])``