mirror of https://github.com/Qiskit/qiskit.git
1256 lines
52 KiB
Python
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
|