qiskit/test/python/quantum_info/test_qubit_sparse_pauli.py

1256 lines
52 KiB
Python

# This code is part of Qiskit.
#
# (C) Copyright IBM 2025
#
# 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
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring
import copy
import pickle
import itertools
import random
import ddt
import numpy as np
from qiskit import transpile
from qiskit.circuit import Measure, Parameter, library, QuantumCircuit
from qiskit.quantum_info import (
QubitSparsePauli,
QubitSparsePauliList,
Pauli,
)
from qiskit.transpiler import Target
from test import QiskitTestCase # pylint: disable=wrong-import-order
def single_cases():
return [
QubitSparsePauli(""),
QubitSparsePauli("I" * 10),
QubitSparsePauli.from_label("IIXIZI"),
QubitSparsePauli.from_label("ZZYYXX"),
]
def single_cases_list():
return [
QubitSparsePauliList.empty(0),
QubitSparsePauliList.empty(10),
QubitSparsePauliList.from_label("IIXIZI"),
QubitSparsePauliList.from_list(["YIXZII", "ZZYYXX"]),
# Includes a duplicate entry.
QubitSparsePauliList.from_list(["IXZ", "ZZI", "IXZ"]),
]
@ddt.ddt
class TestQubitSparsePauli(QiskitTestCase):
def test_default_constructor_pauli(self):
data = Pauli("IXYIZ")
self.assertEqual(QubitSparsePauli(data), QubitSparsePauli.from_pauli(data))
self.assertEqual(
QubitSparsePauli(data, num_qubits=data.num_qubits), QubitSparsePauli.from_pauli(data)
)
with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"):
QubitSparsePauli(data, num_qubits=data.num_qubits + 1)
with_phase = Pauli("-jIYYXY")
self.assertEqual(QubitSparsePauli(with_phase), QubitSparsePauli.from_pauli(with_phase))
self.assertEqual(
QubitSparsePauli(with_phase, num_qubits=data.num_qubits),
QubitSparsePauli.from_pauli(with_phase),
)
self.assertEqual(QubitSparsePauli(Pauli("")), QubitSparsePauli.from_pauli(Pauli("")))
def test_default_constructor_label(self):
data = "IXIIZ"
self.assertEqual(QubitSparsePauli(data), QubitSparsePauli.from_label(data))
self.assertEqual(QubitSparsePauli(data, num_qubits=5), QubitSparsePauli.from_label(data))
with self.assertRaisesRegex(ValueError, "does not match label"):
QubitSparsePauli(data, num_qubits=4)
with self.assertRaisesRegex(ValueError, "does not match label"):
QubitSparsePauli(data, num_qubits=6)
def test_default_constructor_sparse_label(self):
data = ("ZX", (0, 3))
self.assertEqual(
QubitSparsePauli(data, num_qubits=5),
QubitSparsePauli.from_sparse_label(data, num_qubits=5),
)
self.assertEqual(
QubitSparsePauli(data, num_qubits=10),
QubitSparsePauli.from_sparse_label(data, num_qubits=10),
)
with self.assertRaisesRegex(ValueError, "'num_qubits' must be provided"):
QubitSparsePauli(data)
self.assertEqual(
QubitSparsePauli(("", []), num_qubits=5),
QubitSparsePauli.from_sparse_label(("", []), num_qubits=5),
)
def test_from_label(self):
# The label is interpreted like a bitstring, with the right-most item associated with qubit
# 0, and increasing as we move to the left (like `Pauli`, and other bitstring conventions).
self.assertEqual(
# Ruler for counting terms: dcba9876543210
QubitSparsePauli.from_label("IXXIIZZIYYIXYZ"),
QubitSparsePauli.from_raw_parts(
14,
[
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.X,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.X,
QubitSparsePauli.Pauli.X,
],
[0, 1, 2, 4, 5, 7, 8, 11, 12],
),
)
def test_from_label_failures(self):
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Bad letters that are still ASCII.
QubitSparsePauli.from_label("I+-$%I")
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Unicode shenangigans.
QubitSparsePauli.from_label("🐍")
def test_from_sparse_label(self):
self.assertEqual(
QubitSparsePauli.from_sparse_label(("XY", (0, 1)), num_qubits=5),
QubitSparsePauli.from_label("IIIYX"),
)
self.assertEqual(
QubitSparsePauli.from_sparse_label(("XX", (1, 3)), num_qubits=5),
QubitSparsePauli.from_label("IXIXI"),
)
self.assertEqual(
QubitSparsePauli.from_sparse_label(("YYZ", (0, 2, 4)), num_qubits=5),
QubitSparsePauli.from_label("ZIYIY"),
)
# The indices should be allowed to be given in unsorted order, but they should be term-wise
# sorted in the output.
from_unsorted = QubitSparsePauli.from_sparse_label(("XYZ", (2, 0, 1)), num_qubits=3)
self.assertEqual(from_unsorted, QubitSparsePauli.from_label("XZY"))
np.testing.assert_equal(from_unsorted.indices, np.array([0, 1, 2], dtype=np.uint32))
# Explicit identities should still work, just be skipped over.
explicit_identity = QubitSparsePauli.from_sparse_label(("ZXI", (0, 1, 2)), num_qubits=10)
self.assertEqual(
explicit_identity,
QubitSparsePauli.from_sparse_label(("XZ", (1, 0)), num_qubits=10),
)
np.testing.assert_equal(explicit_identity.indices, np.array([0, 1], dtype=np.uint32))
explicit_identity = QubitSparsePauli.from_sparse_label(
("XYIII", (0, 1, 2, 3, 8)), num_qubits=10
)
self.assertEqual(
explicit_identity,
QubitSparsePauli.from_sparse_label(("YX", (1, 0)), num_qubits=10),
)
np.testing.assert_equal(explicit_identity.indices, np.array([0, 1], dtype=np.uint32))
def test_from_sparse_list_failures(self):
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Bad letters that are still ASCII.
QubitSparsePauli.from_sparse_label(("+$", (2, 1)), num_qubits=8)
# Unicode shenangigans. These two should fail with a `ValueError`, but the exact message
# isn't important. "\xff" is "ÿ", which is two bytes in UTF-8 (so has a length of 2 in
# Rust), but has a length of 1 in Python, so try with both a length-1 and length-2 index
# sequence, and both should still raise `ValueError`.
with self.assertRaises(ValueError):
QubitSparsePauli.from_sparse_label(("\xff", (1,)), num_qubits=5)
with self.assertRaises(ValueError):
QubitSparsePauli.from_sparse_label(("\xff", (1, 2)), num_qubits=5)
with self.assertRaisesRegex(
ValueError, "label with length 2 does not match indices of length 1"
):
QubitSparsePauli.from_sparse_label(("XZ", (0,)), num_qubits=5)
with self.assertRaisesRegex(
ValueError, "label with length 2 does not match indices of length 3"
):
QubitSparsePauli.from_sparse_label(("XZ", (0, 1, 2)), num_qubits=5)
with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"):
QubitSparsePauli.from_sparse_label(("XZY", (0, 1, 3)), num_qubits=3)
with self.assertRaisesRegex(ValueError, "index 4 is out of range for a 3-qubit operator"):
QubitSparsePauli.from_sparse_label(("XZY", (0, 1, 4)), num_qubits=3)
with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"):
# ... even if it's for an explicit identity.
QubitSparsePauli.from_sparse_label(("XXI", (0, 1, 3)), num_qubits=3)
with self.assertRaisesRegex(ValueError, "index 3 is duplicated"):
QubitSparsePauli.from_sparse_label(("XZ", (3, 3)), num_qubits=5)
with self.assertRaisesRegex(ValueError, "index 3 is duplicated"):
QubitSparsePauli.from_sparse_label(("XYZXZ", (3, 0, 1, 2, 3)), num_qubits=5)
def test_from_pauli(self):
# This function should be infallible provided `Pauli` doesn't change its interface and the
# user doesn't violate the typing.
# Simple check that the labels are interpreted in the same order.
self.assertEqual(
QubitSparsePauli.from_pauli(Pauli("IIXZI")), QubitSparsePauli.from_label("IIXZI")
)
# `Pauli` accepts a phase in its label, which gets dropped
self.assertEqual(
QubitSparsePauli.from_pauli(Pauli("iIXZIX")),
QubitSparsePauli.from_label("IXZIX"),
)
self.assertEqual(
QubitSparsePauli.from_pauli(Pauli("-iIXZIX")),
QubitSparsePauli.from_label("IXZIX"),
)
self.assertEqual(
QubitSparsePauli.from_pauli(Pauli("-IXZIX")),
QubitSparsePauli.from_label("IXZIX"),
)
# `Pauli` has its internal phase convention for how it stores `Y`; we should get this right
# regardless of how many Ys are in the label, or if there's a phase.
paulis = {"IXYZ" * n: Pauli("IXYZ" * n) for n in range(1, 5)}
from_paulis, from_labels = zip(
*(
(QubitSparsePauli.from_pauli(pauli), QubitSparsePauli.from_label(label))
for label, pauli in paulis.items()
)
)
self.assertEqual(from_paulis, from_labels)
phased_paulis = {"IXYZ" * n: Pauli("j" + "IXYZ" * n) for n in range(1, 5)}
from_paulis, from_lists = zip(
*(
(QubitSparsePauli.from_pauli(pauli), QubitSparsePauli.from_label(label))
for label, pauli in phased_paulis.items()
)
)
self.assertEqual(from_paulis, from_lists)
def test_default_constructor_failed_inference(self):
with self.assertRaises(TypeError):
QubitSparsePauli(5, num_qubits=5)
def test_num_qubits(self):
self.assertEqual(QubitSparsePauli("").num_qubits, 0)
self.assertEqual(QubitSparsePauli("I" * 10).num_qubits, 10)
def test_pauli_enum(self):
# These are very explicit tests that effectively just duplicate magic numbers, but the point
# is that those magic numbers are required to be constant as their values are part of the
# public interface.
self.assertEqual(
set(QubitSparsePauli.Pauli),
{
QubitSparsePauli.Pauli.X,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.Z,
},
)
# All the enumeration items should also be integers.
self.assertIsInstance(QubitSparsePauli.Pauli.X, int)
values = {
"X": 0b10,
"Y": 0b11,
"Z": 0b01,
}
self.assertEqual({name: getattr(QubitSparsePauli.Pauli, name) for name in values}, values)
# The single-character label aliases can be accessed with index notation.
labels = {
"X": QubitSparsePauli.Pauli.X,
"Y": QubitSparsePauli.Pauli.Y,
"Z": QubitSparsePauli.Pauli.Z,
}
self.assertEqual({label: QubitSparsePauli.Pauli[label] for label in labels}, labels)
# The `label` property returns known values.
self.assertEqual({pauli.label: pauli for pauli in QubitSparsePauli.Pauli}, labels)
@ddt.idata(single_cases())
def test_pickle(self, qubit_sparse_pauli):
self.assertEqual(qubit_sparse_pauli, copy.copy(qubit_sparse_pauli))
self.assertIsNot(qubit_sparse_pauli, copy.copy(qubit_sparse_pauli))
self.assertEqual(qubit_sparse_pauli, copy.deepcopy(qubit_sparse_pauli))
self.assertEqual(qubit_sparse_pauli, pickle.loads(pickle.dumps(qubit_sparse_pauli)))
@ddt.data(
QubitSparsePauli.from_label("IIXIZI"),
QubitSparsePauli.from_label("X"),
QubitSparsePauli.from_label("III"),
)
def test_repr(self, data):
# The purpose of this is just to test that the `repr` doesn't crash, rather than asserting
# that it has any particular form.
self.assertIsInstance(repr(data), str)
self.assertIn("QubitSparsePauli", repr(data))
@ddt.idata(single_cases())
def test_copy(self, qubit_sparse_pauli):
self.assertEqual(qubit_sparse_pauli, qubit_sparse_pauli.copy())
self.assertIsNot(qubit_sparse_pauli, qubit_sparse_pauli.copy())
def test_equality(self):
sparse_data = ("XYY", (3, 1, 0))
pauli = QubitSparsePauli.from_sparse_label(sparse_data, num_qubits=5)
self.assertEqual(pauli, pauli.copy())
# Take care that Rust space allows multiple views onto the same object.
self.assertEqual(pauli, pauli)
# Comparison to some other object shouldn't fail.
self.assertNotEqual(pauli, None)
# Difference in qubit count.
self.assertNotEqual(
pauli, QubitSparsePauli.from_sparse_label(sparse_data, num_qubits=pauli.num_qubits + 1)
)
# Difference in bit terms.
self.assertNotEqual(
QubitSparsePauli.from_label("IIXZI"),
QubitSparsePauli.from_label("IIYZI"),
)
self.assertNotEqual(
QubitSparsePauli.from_label("XXYYZ"),
QubitSparsePauli.from_label("XXYYY"),
)
# Difference in indices.
self.assertNotEqual(
QubitSparsePauli.from_label("IIXZI"),
QubitSparsePauli.from_label("IXIZI"),
)
self.assertNotEqual(
QubitSparsePauli.from_label("XIYYZ"),
QubitSparsePauli.from_label("IXYYZ"),
)
def test_attributes_sequence(self):
"""Test attributes of the `Sequence` protocol."""
# Length
pauli = QubitSparsePauli.from_label("XZY")
self.assertEqual(len(pauli.indices), 3)
self.assertEqual(len(pauli.paulis), 3)
# Iteration
self.assertEqual(tuple(pauli.indices), (0, 1, 2))
# multiple iteration through same object
paulis = pauli.paulis
self.assertEqual(set(paulis), {QubitSparsePauli.Pauli[x] for x in "XZY"})
# Implicit iteration methods.
self.assertIn(QubitSparsePauli.Pauli.Y, pauli.paulis)
self.assertNotIn(4, pauli.indices)
# Index by scalar
self.assertEqual(pauli.indices[-1], 2)
self.assertEqual(pauli.paulis[0], QubitSparsePauli.Pauli.Y)
# Index by slice. This is API guaranteed to be a Numpy array to make it easier to
# manipulate subslices with mathematic operations.
self.assertIsInstance(pauli.indices[::-1], np.ndarray)
np.testing.assert_array_equal(
pauli.indices[::-1],
np.array([2, 1, 0], dtype=np.uint32),
strict=True,
)
self.assertIsInstance(pauli.paulis[0:2], np.ndarray)
np.testing.assert_array_equal(
pauli.paulis[0:2],
np.array([QubitSparsePauli.Pauli.Y, QubitSparsePauli.Pauli.Z], dtype=np.uint8),
strict=True,
)
def test_attributes_to_array(self):
pauli = QubitSparsePauli.from_label("XZY")
# Natural dtypes.
np.testing.assert_array_equal(
pauli.indices, np.array([0, 1, 2], dtype=np.uint32), strict=True
)
np.testing.assert_array_equal(
pauli.paulis,
np.array([QubitSparsePauli.Pauli[x] for x in "YZX"], dtype=np.uint8),
strict=True,
)
# Cast dtypes.
np.testing.assert_array_equal(
np.array(pauli.indices, dtype=np.uint8),
np.array([0, 1, 2], dtype=np.uint8),
strict=True,
)
def test_identity(self):
identity_5 = QubitSparsePauli.identity(5)
self.assertEqual(identity_5.num_qubits, 5)
self.assertEqual(len(identity_5.paulis), 0)
self.assertEqual(len(identity_5.indices), 0)
identity_0 = QubitSparsePauli.identity(0)
self.assertEqual(identity_0.num_qubits, 0)
self.assertEqual(len(identity_0.paulis), 0)
self.assertEqual(len(identity_0.indices), 0)
def test_compose(self):
p0 = QubitSparsePauli.from_label("XZY")
p1 = QubitSparsePauli.from_label("ZIY")
self.assertEqual(p0.compose(p1), QubitSparsePauli.from_label("YZI"))
self.assertEqual(p1.compose(p0), QubitSparsePauli.from_label("YZI"))
p0 = QubitSparsePauli.from_label("III")
p1 = QubitSparsePauli.from_label("ZIY")
self.assertEqual(p0 @ p1, QubitSparsePauli.from_label("ZIY"))
self.assertEqual(p1 @ p0, QubitSparsePauli.from_label("ZIY"))
p0 = QubitSparsePauli.from_label("IIIXXY")
p1 = QubitSparsePauli.from_label("ZIYIII")
self.assertEqual(p0 @ p1, QubitSparsePauli.from_label("ZIYXXY"))
self.assertEqual(p1 @ p0, QubitSparsePauli.from_label("ZIYXXY"))
p0 = QubitSparsePauli.from_label("IIIXXYZIXIZIZ")
p1 = QubitSparsePauli.from_label("ZIYIIIXYZIYIX")
self.assertEqual(p0 @ p1, QubitSparsePauli.from_label("ZIYXXYYYYIXIY"))
self.assertEqual(p1 @ p0, QubitSparsePauli.from_label("ZIYXXYYYYIXIY"))
self.assertEqual(p0 @ p0, QubitSparsePauli.from_label("I" * 13))
self.assertEqual(p1 @ p1, QubitSparsePauli.from_label("I" * 13))
def test_compose_errors(self):
p0 = QubitSparsePauli.from_label("XZYI")
p1 = QubitSparsePauli.from_label("ZIY")
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits: 4, 3"):
p0.compose(p1)
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits: 3, 4"):
p1.compose(p0)
def test_commutes(self):
p0 = QubitSparsePauli("XIY")
p1 = QubitSparsePauli("IZI")
self.assertTrue(p0.commutes(p1))
self.assertTrue(p1.commutes(p0))
p0 = QubitSparsePauli("XXY")
p1 = QubitSparsePauli("IZI")
self.assertFalse(p0.commutes(p1))
self.assertFalse(p1.commutes(p0))
p0 = QubitSparsePauli("XXY")
p1 = QubitSparsePauli("IZX")
self.assertTrue(p0.commutes(p1))
self.assertTrue(p1.commutes(p0))
p0 = QubitSparsePauli("XXYY")
p1 = QubitSparsePauli("IZXY")
self.assertTrue(p0.commutes(p1))
self.assertTrue(p1.commutes(p0))
p0 = QubitSparsePauli("XXYYZ")
p1 = QubitSparsePauli("IZXYX")
self.assertFalse(p0.commutes(p1))
self.assertFalse(p1.commutes(p0))
def test_commutes_errors(self):
p0 = QubitSparsePauli.from_label("XZYI")
p1 = QubitSparsePauli.from_label("ZIY")
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits: 4, 3"):
p0.commutes(p1)
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits: 3, 4"):
p1.commutes(p0)
@ddt.ddt
class TestQubitSparsePauliList(QiskitTestCase):
def test_default_constructor_pauli(self):
data = Pauli("IXYIZ")
self.assertEqual(QubitSparsePauliList(data), QubitSparsePauliList.from_pauli(data))
self.assertEqual(
QubitSparsePauliList(data, num_qubits=data.num_qubits),
QubitSparsePauliList.from_pauli(data),
)
with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"):
QubitSparsePauliList(data, num_qubits=data.num_qubits + 1)
with_phase = Pauli("-jIYYXY")
self.assertEqual(
QubitSparsePauliList(with_phase), QubitSparsePauliList.from_pauli(with_phase)
)
self.assertEqual(
QubitSparsePauliList(with_phase, num_qubits=data.num_qubits),
QubitSparsePauliList.from_pauli(with_phase),
)
self.assertEqual(
QubitSparsePauliList(Pauli("")), QubitSparsePauliList.from_pauli(Pauli(""))
)
def test_default_constructor_list(self):
data = ["IXIIZ", "XIXII", "IIXYI"]
self.assertEqual(QubitSparsePauliList(data), QubitSparsePauliList.from_list(data))
self.assertEqual(
QubitSparsePauliList(data, num_qubits=5), QubitSparsePauliList.from_list(data)
)
with self.assertRaisesRegex(ValueError, "label with length 5 cannot be added"):
QubitSparsePauliList(data, num_qubits=4)
with self.assertRaisesRegex(ValueError, "label with length 5 cannot be added"):
QubitSparsePauliList(data, num_qubits=6)
self.assertEqual(
QubitSparsePauliList([], num_qubits=5), QubitSparsePauliList.from_list([], num_qubits=5)
)
def test_default_constructor_sparse_list(self):
data = [("ZX", (0, 3)), ("XY", (2, 4)), ("ZY", (2, 1))]
self.assertEqual(
QubitSparsePauliList(data, num_qubits=5),
QubitSparsePauliList.from_sparse_list(data, num_qubits=5),
)
self.assertEqual(
QubitSparsePauliList(data, num_qubits=10),
QubitSparsePauliList.from_sparse_list(data, num_qubits=10),
)
with self.assertRaisesRegex(ValueError, "'num_qubits' must be provided"):
QubitSparsePauliList(data)
self.assertEqual(
QubitSparsePauliList([], num_qubits=5),
QubitSparsePauliList.from_sparse_list([], num_qubits=5),
)
def test_default_constructor_label(self):
data = "IIXIXXIZZYYIYZ"
self.assertEqual(QubitSparsePauliList(data), QubitSparsePauliList.from_label(data))
self.assertEqual(
QubitSparsePauliList(data, num_qubits=len(data)), QubitSparsePauliList.from_label(data)
)
with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"):
QubitSparsePauliList(data, num_qubits=len(data) + 1)
def test_default_constructor_copy(self):
base = QubitSparsePauliList.from_list(["IXIZIY", "XYZIII"])
copied = QubitSparsePauliList(base)
self.assertEqual(base, copied)
self.assertIsNot(base, copied)
with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"):
QubitSparsePauliList(base, num_qubits=base.num_qubits + 1)
def test_default_constructor_term(self):
expected = QubitSparsePauliList.from_list(["IIZXII"])
self.assertEqual(QubitSparsePauliList(expected[0]), expected)
def test_default_constructor_term_iterable(self):
expected = QubitSparsePauliList.from_list(["IIZXII", "IIIIII"])
terms = [expected[0], expected[1]]
self.assertEqual(QubitSparsePauliList(list(terms)), expected)
self.assertEqual(QubitSparsePauliList(tuple(terms)), expected)
self.assertEqual(QubitSparsePauliList(term for term in terms), expected)
def test_from_label(self):
# The label is interpreted like a bitstring, with the right-most item associated with qubit
# 0, and increasing as we move to the left (like `Pauli`, and other bitstring conventions).
qs_list = QubitSparsePauliList.from_label("IXXIIZZIYYIXYZ")
self.assertEqual(len(qs_list), 1)
self.assertEqual(qs_list[0], QubitSparsePauli.from_label("IXXIIZZIYYIXYZ"))
def test_from_label_failures(self):
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Bad letters that are still ASCII.
QubitSparsePauliList.from_label("I+-$%I")
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Unicode shenangigans.
QubitSparsePauliList.from_label("🐍")
def test_from_list(self):
label = "IXYIZZY"
self.assertEqual(
QubitSparsePauliList.from_list([label]), QubitSparsePauliList.from_label(label)
)
self.assertEqual(
QubitSparsePauliList.from_list([label], num_qubits=len(label)),
QubitSparsePauliList.from_label(label),
)
self.assertEqual(
QubitSparsePauliList.from_list([label])[0],
QubitSparsePauli.from_raw_parts(
len(label),
[
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.X,
],
[0, 1, 2, 4, 5],
),
)
self.assertEqual(
QubitSparsePauliList.from_list([label], num_qubits=len(label))[0],
QubitSparsePauli.from_raw_parts(
len(label),
[
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.X,
],
[0, 1, 2, 4, 5],
),
)
self.assertEqual(
QubitSparsePauliList.from_list(["IIIXZI", "XXIIII"])[0],
QubitSparsePauli.from_raw_parts(
6,
[
QubitSparsePauli.Pauli.Z,
QubitSparsePauli.Pauli.X,
],
[1, 2],
),
)
self.assertEqual(
QubitSparsePauliList.from_list(["IIIXZI", "XXIIII"])[1],
QubitSparsePauli.from_raw_parts(
6,
[
QubitSparsePauli.Pauli.X,
QubitSparsePauli.Pauli.X,
],
[4, 5],
),
)
self.assertEqual(
QubitSparsePauliList.from_list([], num_qubits=5), QubitSparsePauliList.empty(5)
)
self.assertEqual(
QubitSparsePauliList.from_list([], num_qubits=0), QubitSparsePauliList.empty(0)
)
def test_from_list_failures(self):
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Bad letters that are still ASCII.
QubitSparsePauliList.from_list(["XZIIZY", "I+-$%I"])
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Unicode shenangigans.
QubitSparsePauliList.from_list(["🐍"])
with self.assertRaisesRegex(ValueError, "label with length 4 cannot be added"):
QubitSparsePauliList.from_list(["IIZ", "IIXI"])
with self.assertRaisesRegex(ValueError, "label with length 2 cannot be added"):
QubitSparsePauliList.from_list(["IIZ", "II"])
with self.assertRaisesRegex(ValueError, "label with length 3 cannot be added"):
QubitSparsePauliList.from_list(["IIZ", "IXI"], num_qubits=2)
with self.assertRaisesRegex(ValueError, "label with length 3 cannot be added"):
QubitSparsePauliList.from_list(["IIZ", "IXI"], num_qubits=4)
with self.assertRaisesRegex(ValueError, "cannot construct.*without knowing `num_qubits`"):
QubitSparsePauliList.from_list([])
def test_from_sparse_list(self):
self.assertEqual(
QubitSparsePauliList.from_sparse_list(
[
("XY", (0, 1)),
("XX", (1, 3)),
("YYZ", (0, 2, 4)),
],
num_qubits=5,
),
QubitSparsePauliList.from_list(["IIIYX", "IXIXI", "ZIYIY"]),
)
# The indices should be allowed to be given in unsorted order, but they should be term-wise
# sorted in the output.
from_unsorted = QubitSparsePauliList.from_sparse_list(
[
("XYZ", (2, 1, 0)),
("XYZ", (2, 0, 1)),
],
num_qubits=3,
)
self.assertEqual(from_unsorted, QubitSparsePauliList.from_list(["XYZ", "XZY"]))
np.testing.assert_equal(from_unsorted[0].indices, np.array([0, 1, 2], dtype=np.uint32))
np.testing.assert_equal(from_unsorted[1].indices, np.array([0, 1, 2], dtype=np.uint32))
# Explicit identities should still work, just be skipped over.
explicit_identity = QubitSparsePauliList.from_sparse_list(
[
("ZXI", (0, 1, 2)),
("XYIII", (0, 1, 2, 3, 8)),
],
num_qubits=10,
)
self.assertEqual(
explicit_identity,
QubitSparsePauliList.from_sparse_list([("XZ", (1, 0)), ("YX", (1, 0))], num_qubits=10),
)
np.testing.assert_equal(explicit_identity[0].indices, np.array([0, 1], dtype=np.uint32))
np.testing.assert_equal(explicit_identity[1].indices, np.array([0, 1], dtype=np.uint32))
self.assertEqual(
QubitSparsePauliList.from_sparse_list([], num_qubits=1_000_000),
QubitSparsePauliList.empty(1_000_000),
)
self.assertEqual(
QubitSparsePauliList.from_sparse_list([], num_qubits=0),
QubitSparsePauliList.empty(0),
)
def test_from_sparse_list_failures(self):
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
# Bad letters that are still ASCII.
QubitSparsePauliList.from_sparse_list(
[("XZZY", (5, 3, 1, 0)), ("+$", (2, 1))], num_qubits=8
)
# Unicode shenangigans. These two should fail with a `ValueError`, but the exact message
# isn't important. "\xff" is "ÿ", which is two bytes in UTF-8 (so has a length of 2 in
# Rust), but has a length of 1 in Python, so try with both a length-1 and length-2 index
# sequence, and both should still raise `ValueError`.
with self.assertRaises(ValueError):
QubitSparsePauliList.from_sparse_list([("\xff", (1,))], num_qubits=5)
with self.assertRaises(ValueError):
QubitSparsePauliList.from_sparse_list([("\xff", (1, 2))], num_qubits=5)
with self.assertRaisesRegex(ValueError, "label with length 2 does not match indices"):
QubitSparsePauliList.from_sparse_list([("XZ", (0,))], num_qubits=5)
with self.assertRaisesRegex(ValueError, "label with length 2 does not match indices"):
QubitSparsePauliList.from_sparse_list([("XZ", (0, 1, 2))], num_qubits=5)
with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"):
QubitSparsePauliList.from_sparse_list([("XZY", (0, 1, 3))], num_qubits=3)
with self.assertRaisesRegex(ValueError, "index 4 is out of range for a 3-qubit operator"):
QubitSparsePauliList.from_sparse_list([("XZY", (0, 1, 4))], num_qubits=3)
with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"):
# ... even if it's for an explicit identity.
QubitSparsePauliList.from_sparse_list([("XXI", (0, 1, 3))], num_qubits=3)
with self.assertRaisesRegex(ValueError, "index 3 is duplicated"):
QubitSparsePauliList.from_sparse_list([("XZ", (3, 3))], num_qubits=5)
with self.assertRaisesRegex(ValueError, "index 3 is duplicated"):
QubitSparsePauliList.from_sparse_list([("XYZXZ", (3, 0, 1, 2, 3))], num_qubits=5)
def test_from_pauli(self):
# This function should be infallible provided `Pauli` doesn't change its interface and the
# user doesn't violate the typing.
# Simple check that the labels are interpreted in the same order.
self.assertEqual(
QubitSparsePauliList.from_pauli(Pauli("IIXZI")),
QubitSparsePauliList.from_label("IIXZI"),
)
# `Pauli` accepts a phase in its label, which gets dropped
self.assertEqual(
QubitSparsePauliList.from_pauli(Pauli("iIXZIX")),
QubitSparsePauliList.from_list(["IXZIX"]),
)
self.assertEqual(
QubitSparsePauliList.from_pauli(Pauli("-iIXZIX")),
QubitSparsePauliList.from_list(["IXZIX"]),
)
self.assertEqual(
QubitSparsePauliList.from_pauli(Pauli("-IXZIX")),
QubitSparsePauliList.from_list(["IXZIX"]),
)
# `Pauli` has its internal phase convention for how it stores `Y`; we should get this right
# regardless of how many Ys are in the label, or if there's a phase.
paulis = {"IXYZ" * n: Pauli("IXYZ" * n) for n in range(1, 5)}
from_paulis, from_labels = zip(
*(
(QubitSparsePauliList.from_pauli(pauli), QubitSparsePauliList.from_label(label))
for label, pauli in paulis.items()
)
)
self.assertEqual(from_paulis, from_labels)
phased_paulis = {"IXYZ" * n: Pauli("j" + "IXYZ" * n) for n in range(1, 5)}
from_paulis, from_lists = zip(
*(
(QubitSparsePauliList.from_pauli(pauli), QubitSparsePauliList.from_list([label]))
for label, pauli in phased_paulis.items()
)
)
self.assertEqual(from_paulis, from_lists)
def test_from_qubit_sparse_paulis(self):
self.assertEqual(
QubitSparsePauliList.from_qubit_sparse_paulis([], num_qubits=5),
QubitSparsePauliList.empty(5),
)
self.assertEqual(
QubitSparsePauliList.from_qubit_sparse_paulis((), num_qubits=0),
QubitSparsePauliList.empty(0),
)
self.assertEqual(
QubitSparsePauliList.from_qubit_sparse_paulis((None for _ in []), num_qubits=3),
QubitSparsePauliList.empty(3),
)
expected = QubitSparsePauliList.from_sparse_list(
[
("XYZ", (4, 2, 1)),
("XXYY", (8, 5, 3, 2)),
("ZZ", (5, 0)),
],
num_qubits=10,
)
self.assertEqual(QubitSparsePauliList.from_qubit_sparse_paulis(list(expected)), expected)
self.assertEqual(QubitSparsePauliList.from_qubit_sparse_paulis(tuple(expected)), expected)
self.assertEqual(
QubitSparsePauliList.from_qubit_sparse_paulis(term for term in expected), expected
)
self.assertEqual(
QubitSparsePauliList.from_qubit_sparse_paulis(
(term for term in expected), num_qubits=expected.num_qubits
),
expected,
)
def test_from_qubit_sparse_paulis_failures(self):
with self.assertRaisesRegex(ValueError, "cannot construct.*without knowing `num_qubits`"):
QubitSparsePauliList.from_qubit_sparse_paulis([])
left, right = (
QubitSparsePauliList(["IIXYI"])[0],
QubitSparsePauliList(["IIIIIIIIX"])[0],
)
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits"):
QubitSparsePauliList.from_qubit_sparse_paulis([left, right])
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits"):
QubitSparsePauliList.from_qubit_sparse_paulis([left], num_qubits=100)
def test_default_constructor_failed_inference(self):
with self.assertRaises(TypeError):
# Mixed dense/sparse list.
QubitSparsePauliList(["IIXIZ", ("IZ", (2, 3))], num_qubits=5)
def test_num_qubits(self):
self.assertEqual(QubitSparsePauliList.empty(0).num_qubits, 0)
self.assertEqual(QubitSparsePauliList.empty(10).num_qubits, 10)
def test_num_terms(self):
self.assertEqual(QubitSparsePauliList.empty(0).num_terms, 0)
self.assertEqual(QubitSparsePauliList.empty(10).num_terms, 0)
self.assertEqual(QubitSparsePauliList.from_list(["IIIXIZ", "YYXXII"]).num_terms, 2)
def test_empty(self):
empty_5 = QubitSparsePauliList.empty(5)
self.assertEqual(empty_5.num_qubits, 5)
self.assertEqual(len(empty_5), 0)
self.assertEqual(empty_5.to_sparse_list(), [])
empty_0 = QubitSparsePauliList.empty(0)
self.assertEqual(empty_0.num_qubits, 0)
self.assertEqual(len(empty_0), 0)
self.assertEqual(empty_0.to_sparse_list(), [])
def test_len(self):
self.assertEqual(len(QubitSparsePauliList.empty(0)), 0)
self.assertEqual(len(QubitSparsePauliList.empty(10)), 0)
self.assertEqual(len(QubitSparsePauliList.from_list(["IIIXIZ", "YYXXII"])), 2)
def test_pauli_enum(self):
# These are very explicit tests that effectively just duplicate magic numbers, but the point
# is that those magic numbers are required to be constant as their values are part of the
# public interface.
self.assertEqual(
set(QubitSparsePauli.Pauli),
{
QubitSparsePauli.Pauli.X,
QubitSparsePauli.Pauli.Y,
QubitSparsePauli.Pauli.Z,
},
)
# All the enumeration items should also be integers.
self.assertIsInstance(QubitSparsePauli.Pauli.X, int)
values = {
"X": 0b10,
"Y": 0b11,
"Z": 0b01,
}
self.assertEqual({name: getattr(QubitSparsePauli.Pauli, name) for name in values}, values)
# The single-character label aliases can be accessed with index notation.
labels = {
"X": QubitSparsePauli.Pauli.X,
"Y": QubitSparsePauli.Pauli.Y,
"Z": QubitSparsePauli.Pauli.Z,
}
self.assertEqual({label: QubitSparsePauli.Pauli[label] for label in labels}, labels)
# The `label` property returns known values.
self.assertEqual({pauli.label: pauli for pauli in QubitSparsePauli.Pauli}, labels)
@ddt.idata(single_cases_list())
def test_pickle(self, qubit_sparse_pauli_list):
self.assertEqual(qubit_sparse_pauli_list, copy.copy(qubit_sparse_pauli_list))
self.assertIsNot(qubit_sparse_pauli_list, copy.copy(qubit_sparse_pauli_list))
self.assertEqual(qubit_sparse_pauli_list, copy.deepcopy(qubit_sparse_pauli_list))
self.assertEqual(
qubit_sparse_pauli_list, pickle.loads(pickle.dumps(qubit_sparse_pauli_list))
)
@ddt.data(
QubitSparsePauliList.empty(0),
QubitSparsePauliList.empty(1),
QubitSparsePauliList.empty(10),
QubitSparsePauliList.from_label("IIXIZI"),
QubitSparsePauliList.from_label("X"),
QubitSparsePauliList.from_list(["YIXZII"]),
QubitSparsePauliList.from_list(["YIXZII", "ZZYYXX"]),
QubitSparsePauliList.from_list(["IIIIII", "ZZYYXX"]),
)
def test_repr(self, data):
# The purpose of this is just to test that the `repr` doesn't crash, rather than asserting
# that it has any particular form.
self.assertIsInstance(repr(data), str)
self.assertIn("QubitSparsePauliList", repr(data))
@ddt.idata(single_cases_list())
def test_copy(self, qubit_sparse_pauli_list):
self.assertEqual(qubit_sparse_pauli_list, qubit_sparse_pauli_list.copy())
self.assertIsNot(qubit_sparse_pauli_list, qubit_sparse_pauli_list.copy())
def test_equality(self):
sparse_data = [("XZ", (1, 0)), ("XYY", (3, 1, 0))]
pauli_list = QubitSparsePauliList.from_sparse_list(sparse_data, num_qubits=5)
self.assertEqual(pauli_list, pauli_list.copy())
# Take care that Rust space allows multiple views onto the same object.
self.assertEqual(pauli_list, pauli_list)
# Comparison to some other object shouldn't fail.
self.assertNotEqual(pauli_list, None)
# Difference in qubit count.
self.assertNotEqual(
pauli_list,
QubitSparsePauliList.from_sparse_list(
sparse_data, num_qubits=pauli_list.num_qubits + 1
),
)
self.assertNotEqual(QubitSparsePauliList.empty(2), QubitSparsePauliList.empty(3))
# Difference in bit terms.
self.assertNotEqual(
QubitSparsePauliList.from_list(["IIXZI", "XXYYZ"]),
QubitSparsePauliList.from_list(["IIYZI", "XXYYZ"]),
)
self.assertNotEqual(
QubitSparsePauliList.from_list(["IIXZI", "XXYYZ"]),
QubitSparsePauliList.from_list(["IIXZI", "XXYYY"]),
)
# Difference in indices.
self.assertNotEqual(
QubitSparsePauliList.from_list(["IIXZI", "XXYYZ"]),
QubitSparsePauliList.from_list(["IXIZI", "XXYYZ"]),
)
self.assertNotEqual(
QubitSparsePauliList.from_list(["IIXZI", "XIYYZ"]),
QubitSparsePauliList.from_list(["IIXZI", "IXYYZ"]),
)
# Difference in boundaries.
self.assertNotEqual(
QubitSparsePauliList.from_sparse_list([("XZ", (0, 1)), ("XX", (2, 3))], num_qubits=5),
QubitSparsePauliList.from_sparse_list([("XZX", (0, 1, 2)), ("X", (3,))], num_qubits=5),
)
@ddt.idata(single_cases_list())
def test_clear(self, pauli_list):
num_qubits = pauli_list.num_qubits
pauli_list.clear()
self.assertEqual(pauli_list, QubitSparsePauliList.empty(num_qubits))
def test_apply_layout_list(self):
self.assertEqual(
QubitSparsePauliList.empty(5).apply_layout([4, 3, 2, 1, 0]),
QubitSparsePauliList.empty(5),
)
self.assertEqual(
QubitSparsePauliList.empty(3).apply_layout([0, 2, 1], 8), QubitSparsePauliList.empty(8)
)
self.assertEqual(
QubitSparsePauliList.empty(2).apply_layout([1, 0]), QubitSparsePauliList.empty(2)
)
self.assertEqual(
QubitSparsePauliList.empty(3).apply_layout([100, 10_000, 3], 100_000_000),
QubitSparsePauliList.empty(100_000_000),
)
terms = [
("ZYX", (4, 2, 1)),
("", ()),
("XXYYZZ", (10, 8, 6, 4, 2, 0)),
]
def map_indices(terms, layout):
return [(terms, tuple(layout[bit] for bit in bits)) for terms, bits in terms]
identity = list(range(12))
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12).apply_layout(identity),
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12),
)
# We've already tested elsewhere that `QubitSparsePauliList.from_sparse_list` produces termwise
# sorted indices, so these tests also ensure `apply_layout` is maintaining that invariant.
backwards = list(range(12))[::-1]
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12).apply_layout(backwards),
QubitSparsePauliList.from_sparse_list(map_indices(terms, backwards), num_qubits=12),
)
shuffled = [4, 7, 1, 10, 0, 11, 3, 2, 8, 5, 6, 9]
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12).apply_layout(shuffled),
QubitSparsePauliList.from_sparse_list(map_indices(terms, shuffled), num_qubits=12),
)
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12).apply_layout(shuffled, 100),
QubitSparsePauliList.from_sparse_list(map_indices(terms, shuffled), num_qubits=100),
)
expanded = [78, 69, 82, 68, 32, 97, 108, 101, 114, 116, 33]
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=11).apply_layout(expanded, 120),
QubitSparsePauliList.from_sparse_list(map_indices(terms, expanded), num_qubits=120),
)
def test_apply_layout_transpiled(self):
base = QubitSparsePauliList.from_sparse_list(
[
("ZYX", (4, 2, 1)),
("", ()),
("XXY", (3, 2, 0)),
],
num_qubits=5,
)
qc = QuantumCircuit(5)
initial_list = [3, 4, 0, 2, 1]
no_routing = transpile(
qc, target=lnn_target(5), initial_layout=initial_list, seed_transpiler=2024_10_25_0
).layout
# It's easiest here to test against the `list` form, which we verify separately and
# explicitly.
self.assertEqual(base.apply_layout(no_routing), base.apply_layout(initial_list))
expanded = transpile(
qc, target=lnn_target(100), initial_layout=initial_list, seed_transpiler=2024_10_25_1
).layout
self.assertEqual(
base.apply_layout(expanded), base.apply_layout(initial_list, num_qubits=100)
)
qc = QuantumCircuit(5)
qargs = list(itertools.permutations(range(5), 2))
random.Random(2024_10_25_2).shuffle(qargs)
for pair in qargs:
qc.cx(*pair)
routed = transpile(qc, target=lnn_target(5), seed_transpiler=2024_10_25_3).layout
self.assertEqual(
base.apply_layout(routed),
base.apply_layout(routed.final_index_layout(filter_ancillas=True)),
)
routed_expanded = transpile(qc, target=lnn_target(20), seed_transpiler=2024_10_25_3).layout
self.assertEqual(
base.apply_layout(routed_expanded),
base.apply_layout(
routed_expanded.final_index_layout(filter_ancillas=True), num_qubits=20
),
)
def test_apply_layout_none(self):
self.assertEqual(
QubitSparsePauliList.empty(0).apply_layout(None), QubitSparsePauliList.empty(0)
)
self.assertEqual(
QubitSparsePauliList.empty(0).apply_layout(None, 3), QubitSparsePauliList.empty(3)
)
self.assertEqual(
QubitSparsePauliList.empty(5).apply_layout(None), QubitSparsePauliList.empty(5)
)
self.assertEqual(
QubitSparsePauliList.empty(3).apply_layout(None, 8), QubitSparsePauliList.empty(8)
)
self.assertEqual(
QubitSparsePauliList.empty(0).apply_layout(None), QubitSparsePauliList.empty(0)
)
self.assertEqual(
QubitSparsePauliList.empty(0).apply_layout(None, 8), QubitSparsePauliList.empty(8)
)
self.assertEqual(
QubitSparsePauliList.empty(2).apply_layout(None), QubitSparsePauliList.empty(2)
)
self.assertEqual(
QubitSparsePauliList.empty(3).apply_layout(None, 100_000_000),
QubitSparsePauliList.empty(100_000_000),
)
terms = [
("ZYX", (2, 1, 0)),
("", ()),
("XXYYZZ", (10, 8, 6, 4, 2, 0)),
]
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12).apply_layout(None),
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12),
)
self.assertEqual(
QubitSparsePauliList.from_sparse_list(terms, num_qubits=12).apply_layout(
None, num_qubits=200
),
QubitSparsePauliList.from_sparse_list(terms, num_qubits=200),
)
def test_apply_layout_failures(self):
obs = QubitSparsePauliList.from_list(["IIYI", "IIIX"])
with self.assertRaisesRegex(ValueError, "duplicate"):
obs.apply_layout([0, 0, 1, 2])
with self.assertRaisesRegex(ValueError, "does not account for all contained qubits"):
obs.apply_layout([0, 1])
with self.assertRaisesRegex(ValueError, "less than the number of qubits"):
obs.apply_layout([0, 2, 4, 6])
with self.assertRaisesRegex(ValueError, "cannot shrink"):
obs.apply_layout([0, 1], num_qubits=2)
with self.assertRaisesRegex(ValueError, "cannot shrink"):
obs.apply_layout(None, num_qubits=2)
qc = QuantumCircuit(3)
qc.cx(0, 1)
qc.cx(1, 2)
qc.cx(2, 0)
layout = transpile(qc, target=lnn_target(3), seed_transpiler=2024_10_25).layout
with self.assertRaisesRegex(ValueError, "cannot shrink"):
obs.apply_layout(layout, num_qubits=2)
def test_iteration(self):
self.assertEqual(list(QubitSparsePauliList.empty(5)), [])
self.assertEqual(tuple(QubitSparsePauliList.empty(0)), ())
pauli_list = QubitSparsePauliList.from_sparse_list(
[
("XYY", (4, 2, 1)),
("", ()),
("ZZ", (3, 0)),
("XX", (2, 1)),
("YZ", (4, 1)),
],
num_qubits=5,
)
pauli = QubitSparsePauli.Pauli
expected = [
QubitSparsePauli.from_raw_parts(5, [pauli.Y, pauli.Y, pauli.X], [1, 2, 4]),
QubitSparsePauli.from_raw_parts(5, [], []),
QubitSparsePauli.from_raw_parts(5, [pauli.Z, pauli.Z], [0, 3]),
QubitSparsePauli.from_raw_parts(5, [pauli.X, pauli.X], [1, 2]),
QubitSparsePauli.from_raw_parts(5, [pauli.Z, pauli.Y], [1, 4]),
]
self.assertEqual(list(pauli_list), expected)
def test_indexing(self):
pauli_list = QubitSparsePauliList.from_sparse_list(
[
("XYY", (4, 2, 1)),
("", ()),
("ZZ", (3, 0)),
("XX", (2, 1)),
("YZ", (4, 1)),
],
num_qubits=5,
)
pauli = QubitSparsePauli.Pauli
expected = [
QubitSparsePauli.from_raw_parts(5, [pauli.Y, pauli.Y, pauli.X], [1, 2, 4]),
QubitSparsePauli.from_raw_parts(5, [], []),
QubitSparsePauli.from_raw_parts(5, [pauli.Z, pauli.Z], [0, 3]),
QubitSparsePauli.from_raw_parts(5, [pauli.X, pauli.X], [1, 2]),
QubitSparsePauli.from_raw_parts(5, [pauli.Z, pauli.Y], [1, 4]),
]
self.assertEqual(pauli_list[0], expected[0])
self.assertEqual(pauli_list[-2], expected[-2])
self.assertEqual(pauli_list[2:4], QubitSparsePauliList(expected[2:4]))
self.assertEqual(pauli_list[1::2], QubitSparsePauliList(expected[1::2]))
self.assertEqual(pauli_list[:], QubitSparsePauliList(expected))
self.assertEqual(pauli_list[-1:-4:-1], QubitSparsePauliList(expected[-1:-4:-1]))
def test_to_sparse_list(self):
"""Test converting to a sparse list."""
with self.subTest(msg="empty"):
pauli_list = QubitSparsePauliList.empty(100)
expected = []
self.assertEqual(expected, pauli_list.to_sparse_list())
with self.subTest(msg="IXYZ"):
pauli_list = QubitSparsePauliList(["IXYZ"])
expected = [("ZYX", [0, 1, 2])]
self.assertEqual(
canonicalize_sparse_list(expected),
canonicalize_sparse_list(pauli_list.to_sparse_list()),
)
with self.subTest(msg="multiple"):
pauli_list = QubitSparsePauliList.from_list(["XXIZ", "YYIZ"])
expected = [("XXZ", [3, 2, 0]), ("ZYY", [0, 2, 3])]
self.assertEqual(
canonicalize_sparse_list(expected),
canonicalize_sparse_list(pauli_list.to_sparse_list()),
)
def canonicalize_term(pauli, indices):
# canonicalize a sparse list term by sorting by indices (which is unique as
# indices cannot be repeated)
idcs = np.argsort(indices)
sorted_paulis = "".join(pauli[i] for i in idcs)
return (sorted_paulis, np.asarray(indices)[idcs].tolist())
def canonicalize_sparse_list(sparse_list):
# sort a sparse list representation by canonicalizing the terms and then applying
# Python's built-in sort
canonicalized_terms = [canonicalize_term(*term) for term in sparse_list]
return sorted(canonicalized_terms)
def lnn_target(num_qubits):
"""Create a simple `Target` object with an arbitrary basis-gate set, and open-path
connectivity."""
out = Target()
out.add_instruction(library.RZGate(Parameter("a")), {(q,): None for q in range(num_qubits)})
out.add_instruction(library.SXGate(), {(q,): None for q in range(num_qubits)})
out.add_instruction(Measure(), {(q,): None for q in range(num_qubits)})
out.add_instruction(
library.CXGate(),
{
pair: None
for lower in range(num_qubits - 1)
for pair in [(lower, lower + 1), (lower + 1, lower)]
},
)
return out