mirror of https://github.com/Qiskit/qiskit.git
1216 lines
51 KiB
Python
1216 lines
51 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, PauliLindbladMap
|
|
from qiskit.transpiler import Target
|
|
|
|
from test import QiskitTestCase # pylint: disable=wrong-import-order
|
|
|
|
|
|
def single_cases():
|
|
return [
|
|
PauliLindbladMap.identity(0),
|
|
PauliLindbladMap.identity(10),
|
|
PauliLindbladMap.from_list([("YIXZII", -0.25), ("ZZYYXX", 0.25)]),
|
|
# Includes a duplicate entry.
|
|
PauliLindbladMap.from_list([("IXZ", -0.25), ("ZZI", 0.25), ("IXZ", 0.75)]),
|
|
]
|
|
|
|
|
|
@ddt.ddt
|
|
class TestPauliLindbladMap(QiskitTestCase):
|
|
|
|
def test_default_constructor_list(self):
|
|
data = [("IXIIZ", 0.5), ("XIXII", 1.0), ("IIXYI", -0.75)]
|
|
self.assertEqual(PauliLindbladMap(data), PauliLindbladMap.from_list(data))
|
|
self.assertEqual(PauliLindbladMap(data, num_qubits=5), PauliLindbladMap.from_list(data))
|
|
with self.assertRaisesRegex(ValueError, "label with length 5 cannot be added"):
|
|
PauliLindbladMap(data, num_qubits=4)
|
|
with self.assertRaisesRegex(ValueError, "label with length 5 cannot be added"):
|
|
PauliLindbladMap(data, num_qubits=6)
|
|
self.assertEqual(
|
|
PauliLindbladMap([], num_qubits=5), PauliLindbladMap.from_list([], num_qubits=5)
|
|
)
|
|
|
|
def test_default_constructor_sparse_list(self):
|
|
data = [("ZX", (0, 3), 0.5), ("XY", (2, 4), 1.0), ("ZY", (2, 1), -0.75)]
|
|
self.assertEqual(
|
|
PauliLindbladMap(data, num_qubits=5),
|
|
PauliLindbladMap.from_sparse_list(data, num_qubits=5),
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap(data, num_qubits=10),
|
|
PauliLindbladMap.from_sparse_list(data, num_qubits=10),
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "'num_qubits' must be provided"):
|
|
PauliLindbladMap(data)
|
|
self.assertEqual(
|
|
PauliLindbladMap([], num_qubits=5), PauliLindbladMap.from_sparse_list([], num_qubits=5)
|
|
)
|
|
|
|
def test_default_constructor_copy(self):
|
|
base = PauliLindbladMap.from_list([("IXIZIY", 1.0), ("XYZIII", -1.0)])
|
|
copied = PauliLindbladMap(base)
|
|
self.assertEqual(base, copied)
|
|
self.assertIsNot(base, copied)
|
|
|
|
with self.assertRaisesRegex(ValueError, "explicitly given 'num_qubits'"):
|
|
PauliLindbladMap(base, num_qubits=base.num_qubits + 1)
|
|
|
|
def test_default_constructor_term(self):
|
|
expected = PauliLindbladMap.from_list([("IIZXII", 2)])
|
|
self.assertEqual(PauliLindbladMap(expected[0]), expected)
|
|
|
|
def test_default_constructor_term_iterable(self):
|
|
expected = PauliLindbladMap.from_list([("IIZXII", 2), ("IIIIII", 0.5)])
|
|
terms = [expected[0], expected[1]]
|
|
self.assertEqual(PauliLindbladMap(list(terms)), expected)
|
|
self.assertEqual(PauliLindbladMap(tuple(terms)), expected)
|
|
self.assertEqual(PauliLindbladMap(term for term in terms), expected)
|
|
|
|
def test_from_list(self):
|
|
label = "IXYIZZY"
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_list([(label, 1.0)]),
|
|
PauliLindbladMap.from_components([1.0], QubitSparsePauliList.from_label(label)),
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_list([(label, 1.0)], num_qubits=len(label)),
|
|
PauliLindbladMap.from_components([1.0], QubitSparsePauliList.from_label(label)),
|
|
)
|
|
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_list([("IIIXZI", 1.0), ("XXIIII", -0.5)]),
|
|
PauliLindbladMap.from_components(
|
|
[1.0, -0.5], QubitSparsePauliList.from_list(["IIIXZI", "XXIIII"])
|
|
),
|
|
)
|
|
|
|
self.assertEqual(PauliLindbladMap.from_list([], num_qubits=5), PauliLindbladMap.identity(5))
|
|
self.assertEqual(PauliLindbladMap.from_list([], num_qubits=0), PauliLindbladMap.identity(0))
|
|
|
|
def test_from_list_failures(self):
|
|
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
|
|
# Bad letters that are still ASCII.
|
|
PauliLindbladMap.from_list([("XZIIZY", 0.5), ("I+-$%I", 1.0)])
|
|
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
|
|
# Unicode shenangigans.
|
|
PauliLindbladMap.from_list([("🐍", 0.5)])
|
|
with self.assertRaisesRegex(ValueError, "label with length 4 cannot be added"):
|
|
PauliLindbladMap.from_list([("IIZ", 0.5), ("IIXI", 1.0)])
|
|
with self.assertRaisesRegex(ValueError, "label with length 2 cannot be added"):
|
|
PauliLindbladMap.from_list([("IIZ", 0.5), ("II", 1.0)])
|
|
with self.assertRaisesRegex(ValueError, "label with length 3 cannot be added"):
|
|
PauliLindbladMap.from_list([("IIZ", 0.5), ("IXI", 1.0)], num_qubits=2)
|
|
with self.assertRaisesRegex(ValueError, "label with length 3 cannot be added"):
|
|
PauliLindbladMap.from_list([("IIZ", 0.5), ("IXI", 1.0)], num_qubits=4)
|
|
with self.assertRaisesRegex(ValueError, "cannot construct.*without knowing `num_qubits`"):
|
|
PauliLindbladMap.from_list([])
|
|
|
|
def test_from_components(self):
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_components(
|
|
[0.5, -0.25, 1.0],
|
|
QubitSparsePauliList.from_sparse_list(
|
|
[
|
|
(
|
|
"XY",
|
|
(0, 1),
|
|
),
|
|
(
|
|
"XX",
|
|
(1, 3),
|
|
),
|
|
("YYZ", (0, 2, 4)),
|
|
],
|
|
num_qubits=5,
|
|
),
|
|
),
|
|
PauliLindbladMap.from_list([("IIIYX", 0.5), ("IXIXI", -0.25), ("ZIYIY", 1.0)]),
|
|
)
|
|
|
|
# The indices should be allowed to be given in unsorted order, but they should be term-wise
|
|
# sorted in the output.
|
|
from_unsorted = PauliLindbladMap.from_components(
|
|
[1.5, -0.5],
|
|
QubitSparsePauliList.from_sparse_list(
|
|
[
|
|
("XYZ", (2, 1, 0)),
|
|
("XYY", (2, 0, 1)),
|
|
],
|
|
num_qubits=3,
|
|
),
|
|
)
|
|
self.assertEqual(from_unsorted, PauliLindbladMap.from_list([("XYZ", 1.5), ("XYY", -0.5)]))
|
|
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 = PauliLindbladMap.from_components(
|
|
[1.0, -0.5],
|
|
QubitSparsePauliList.from_sparse_list(
|
|
[
|
|
("ZXI", (0, 1, 2)),
|
|
("XYIII", (0, 1, 2, 3, 8)),
|
|
],
|
|
num_qubits=10,
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
explicit_identity,
|
|
PauliLindbladMap.from_sparse_list(
|
|
[("XZ", (1, 0), 1.0), ("YX", (1, 0), -0.5)], 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(
|
|
PauliLindbladMap.from_components([], QubitSparsePauliList.empty(1_000_000)),
|
|
PauliLindbladMap.identity(1_000_000),
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_components([], QubitSparsePauliList.empty(0)),
|
|
PauliLindbladMap.identity(0),
|
|
)
|
|
|
|
def test_from_components_failures(self):
|
|
with self.assertRaisesRegex(
|
|
ValueError, r"`rates` \(1\) must be the same length as `qubit_sparse_pauli_list` \(2\)"
|
|
):
|
|
PauliLindbladMap.from_components([1.0], QubitSparsePauliList(["II", "XX"]))
|
|
|
|
def test_from_sparse_list(self):
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("XY", (0, 1), 0.5),
|
|
("XX", (1, 3), -0.25),
|
|
("YYZ", (0, 2, 4), 1.0),
|
|
],
|
|
num_qubits=5,
|
|
),
|
|
PauliLindbladMap.from_list([("IIIYX", 0.5), ("IXIXI", -0.25), ("ZIYIY", 1.0)]),
|
|
)
|
|
|
|
# The indices should be allowed to be given in unsorted order, but they should be term-wise
|
|
# sorted in the output.
|
|
from_unsorted = PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("XYZ", (2, 1, 0), 1.5),
|
|
("XYY", (2, 0, 1), -0.5),
|
|
],
|
|
num_qubits=3,
|
|
)
|
|
self.assertEqual(from_unsorted, PauliLindbladMap.from_list([("XYZ", 1.5), ("XYY", -0.5)]))
|
|
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 = PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("ZXI", (0, 1, 2), 1.0),
|
|
("XYIII", (0, 1, 2, 3, 8), -0.5),
|
|
],
|
|
num_qubits=10,
|
|
)
|
|
self.assertEqual(
|
|
explicit_identity,
|
|
PauliLindbladMap.from_sparse_list(
|
|
[("XZ", (1, 0), 1.0), ("YX", (1, 0), -0.5)], 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(
|
|
PauliLindbladMap.from_sparse_list([], num_qubits=1_000_000),
|
|
PauliLindbladMap.identity(1_000_000),
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list([], num_qubits=0),
|
|
PauliLindbladMap.identity(0),
|
|
)
|
|
|
|
def test_from_sparse_list_failures(self):
|
|
with self.assertRaisesRegex(ValueError, "labels must only contain letters from"):
|
|
# Bad letters that are still ASCII.
|
|
PauliLindbladMap.from_sparse_list(
|
|
[("XZZY", (5, 3, 1, 0), 0.5), ("+$", (2, 1), 1.0)], 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):
|
|
PauliLindbladMap.from_sparse_list([("\xff", (1,), 0.5)], num_qubits=5)
|
|
with self.assertRaises(ValueError):
|
|
PauliLindbladMap.from_sparse_list([("\xff", (1, 2), 0.5)], num_qubits=5)
|
|
|
|
with self.assertRaisesRegex(ValueError, "label with length 2 does not match indices"):
|
|
PauliLindbladMap.from_sparse_list([("XZ", (0,), 1.0)], num_qubits=5)
|
|
with self.assertRaisesRegex(ValueError, "label with length 2 does not match indices"):
|
|
PauliLindbladMap.from_sparse_list([("XZ", (0, 1, 2), 1.0)], num_qubits=5)
|
|
|
|
with self.assertRaisesRegex(ValueError, "index 3 is out of range for a 3-qubit operator"):
|
|
PauliLindbladMap.from_sparse_list([("XZY", (0, 1, 3), 1.0)], num_qubits=3)
|
|
with self.assertRaisesRegex(ValueError, "index 4 is out of range for a 3-qubit operator"):
|
|
PauliLindbladMap.from_sparse_list([("XZY", (0, 1, 4), 1.0)], 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.
|
|
PauliLindbladMap.from_sparse_list([("XXI", (0, 1, 3), 1.0)], num_qubits=3)
|
|
|
|
with self.assertRaisesRegex(ValueError, "index 3 is duplicated"):
|
|
PauliLindbladMap.from_sparse_list([("XZ", (3, 3), 1.0)], num_qubits=5)
|
|
with self.assertRaisesRegex(ValueError, "index 3 is duplicated"):
|
|
PauliLindbladMap.from_sparse_list([("XYZXZ", (3, 0, 1, 2, 3), 1.0)], num_qubits=5)
|
|
|
|
def test_from_terms(self):
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_terms([], num_qubits=5), PauliLindbladMap.identity(5)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_terms((), num_qubits=0), PauliLindbladMap.identity(0)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_terms((None for _ in []), num_qubits=3),
|
|
PauliLindbladMap.identity(3),
|
|
)
|
|
|
|
expected = PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("XYZ", (4, 2, 1), 1),
|
|
("XXYY", (8, 5, 3, 2), 0.5),
|
|
("ZZ", (5, 0), 2.0),
|
|
],
|
|
num_qubits=10,
|
|
)
|
|
self.assertEqual(PauliLindbladMap.from_terms(list(expected)), expected)
|
|
self.assertEqual(PauliLindbladMap.from_terms(tuple(expected)), expected)
|
|
self.assertEqual(PauliLindbladMap.from_terms(term for term in expected), expected)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_terms(
|
|
(term for term in expected), num_qubits=expected.num_qubits
|
|
),
|
|
expected,
|
|
)
|
|
|
|
def test_from_terms_failures(self):
|
|
with self.assertRaisesRegex(ValueError, "cannot construct.*without knowing `num_qubits`"):
|
|
PauliLindbladMap.from_terms([])
|
|
|
|
left, right = (
|
|
PauliLindbladMap([("IIXYI", 1.0)])[0],
|
|
PauliLindbladMap([("IIIIIIIIX", 1.0)])[0],
|
|
)
|
|
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits"):
|
|
PauliLindbladMap.from_terms([left, right])
|
|
with self.assertRaisesRegex(ValueError, "mismatched numbers of qubits"):
|
|
PauliLindbladMap.from_terms([left], num_qubits=100)
|
|
|
|
def test_default_constructor_failed_inference(self):
|
|
with self.assertRaises(TypeError):
|
|
# Mixed dense/sparse list.
|
|
PauliLindbladMap([("IIXIZ", 1.0), ("IZ", (2, 3), -1.0)], num_qubits=5)
|
|
|
|
def test_num_qubits(self):
|
|
self.assertEqual(PauliLindbladMap.identity(0).num_qubits, 0)
|
|
self.assertEqual(PauliLindbladMap.identity(10).num_qubits, 10)
|
|
|
|
def test_num_terms(self):
|
|
self.assertEqual(PauliLindbladMap.identity(0).num_terms, 0)
|
|
self.assertEqual(PauliLindbladMap.identity(10).num_terms, 0)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_list([("IIIXIZ", 1.0), ("YYXXII", 0.5)]).num_terms, 2
|
|
)
|
|
|
|
def test_identity(self):
|
|
identity_5 = PauliLindbladMap.identity(5)
|
|
self.assertEqual(identity_5.num_qubits, 5)
|
|
self.assertEqual(identity_5.gamma(), 1.0)
|
|
self.assertEqual(identity_5.num_terms, 0.0)
|
|
np.testing.assert_equal(identity_5.rates, np.array([], dtype=float))
|
|
np.testing.assert_equal(identity_5.probabilities(), np.array([], dtype=float))
|
|
|
|
identity_0 = PauliLindbladMap.identity(0)
|
|
self.assertEqual(identity_0.num_qubits, 0)
|
|
self.assertEqual(identity_0.gamma(), 1.0)
|
|
self.assertEqual(identity_0.num_terms, 0.0)
|
|
np.testing.assert_equal(identity_0.rates, np.array([], dtype=float))
|
|
np.testing.assert_equal(identity_0.probabilities(), np.array([], dtype=float))
|
|
|
|
def test_len(self):
|
|
self.assertEqual(len(PauliLindbladMap.identity(0)), 0)
|
|
self.assertEqual(len(PauliLindbladMap.identity(10)), 0)
|
|
self.assertEqual(len(PauliLindbladMap.from_list([("IIIXIZ", 1.0), ("YYXXII", 0.5)])), 2)
|
|
|
|
@ddt.idata(single_cases())
|
|
def test_pickle(self, pauli_lindblad_map):
|
|
self.assertEqual(pauli_lindblad_map, copy.copy(pauli_lindblad_map))
|
|
self.assertIsNot(pauli_lindblad_map, copy.copy(pauli_lindblad_map))
|
|
self.assertEqual(pauli_lindblad_map, copy.deepcopy(pauli_lindblad_map))
|
|
self.assertEqual(pauli_lindblad_map, pickle.loads(pickle.dumps(pauli_lindblad_map)))
|
|
|
|
@ddt.data(
|
|
# This is every combination of (0, 1, many) for (terms, qubits, non-identites per term).
|
|
PauliLindbladMap.identity(0),
|
|
PauliLindbladMap.identity(1),
|
|
PauliLindbladMap.identity(10),
|
|
PauliLindbladMap.from_list([("YIXZII", -0.25)]),
|
|
PauliLindbladMap.from_list([("YIXZII", -0.25), ("ZZYYXX", 0.25)]),
|
|
)
|
|
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("PauliLindbladMap", repr(data))
|
|
|
|
@ddt.idata(single_cases())
|
|
def test_copy(self, pauli_lindblad_map):
|
|
self.assertEqual(pauli_lindblad_map, pauli_lindblad_map.copy())
|
|
self.assertIsNot(pauli_lindblad_map, pauli_lindblad_map.copy())
|
|
|
|
def test_equality(self):
|
|
sparse_data = [("XZ", (1, 0), 0.5), ("XYY", (3, 1, 0), -0.25)]
|
|
op = PauliLindbladMap.from_sparse_list(sparse_data, num_qubits=5)
|
|
self.assertEqual(op, op.copy())
|
|
# Take care that Rust space allows multiple views onto the same object.
|
|
self.assertEqual(op, op)
|
|
|
|
# Comparison to some other object shouldn't fail.
|
|
self.assertNotEqual(op, None)
|
|
|
|
# No costly automatic simplification (mathematically, these operators _are_ the same).
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("X", 2.0), ("X", -1.0)]), PauliLindbladMap([("X", 1.0)])
|
|
)
|
|
|
|
# Difference in qubit count.
|
|
self.assertNotEqual(
|
|
op, PauliLindbladMap.from_sparse_list(sparse_data, num_qubits=op.num_qubits + 1)
|
|
)
|
|
self.assertNotEqual(PauliLindbladMap.identity(2), PauliLindbladMap.identity(3))
|
|
|
|
# Difference in rates.
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYZ", -0.5)]),
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYZ", 0.5)]),
|
|
)
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYZ", -0.5)]),
|
|
PauliLindbladMap.from_list([("IIXZI", -1.0), ("XXYYZ", -0.5)]),
|
|
)
|
|
|
|
# Difference in bit terms.
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYZ", -0.5)]),
|
|
PauliLindbladMap.from_list([("IIYZI", 1.0), ("XXYYZ", -0.5)]),
|
|
)
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYZ", -0.5)]),
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYY", -0.5)]),
|
|
)
|
|
|
|
# Difference in indices.
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XXYYZ", -0.5)]),
|
|
PauliLindbladMap.from_list([("IXIZI", 1.0), ("XXYYZ", -0.5)]),
|
|
)
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("XIYYZ", -0.5)]),
|
|
PauliLindbladMap.from_list([("IIXZI", 1.0), ("IXYYZ", -0.5)]),
|
|
)
|
|
|
|
# Difference in boundaries.
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.from_sparse_list(
|
|
[("XZ", (0, 1), 1.5), ("XX", (2, 3), -0.5)], num_qubits=5
|
|
),
|
|
PauliLindbladMap.from_sparse_list(
|
|
[("XZX", (0, 1, 2), 1.5), ("X", (3,), -0.5)], num_qubits=5
|
|
),
|
|
)
|
|
|
|
def test_attributes_immutable(self):
|
|
pauli_lindblad_map = PauliLindbladMap.from_list([("XZY", 1.5), ("XXY", -0.5)])
|
|
with self.assertRaisesRegex(AttributeError, "attribute 'rates'"):
|
|
pauli_lindblad_map.rates = 1.0
|
|
with self.assertRaisesRegex(ValueError, "assignment destination is read-only"):
|
|
pauli_lindblad_map.rates[0] = 1.0
|
|
|
|
@ddt.idata(single_cases())
|
|
def test_clear(self, pauli_lindblad_map):
|
|
num_qubits = pauli_lindblad_map.num_qubits
|
|
pauli_lindblad_map.clear()
|
|
self.assertEqual(pauli_lindblad_map, PauliLindbladMap.identity(num_qubits))
|
|
|
|
def test_iteration(self):
|
|
self.assertEqual(list(PauliLindbladMap.identity(5)), [])
|
|
self.assertEqual(tuple(PauliLindbladMap.identity(0)), ())
|
|
|
|
pauli_lindblad_map = PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("XYY", (4, 2, 1), 2),
|
|
("", (), 0.5),
|
|
("ZZ", (3, 0), -0.25),
|
|
("XX", (2, 1), 1.0),
|
|
("YZ", (4, 1), 1),
|
|
],
|
|
num_qubits=5,
|
|
)
|
|
expected = [
|
|
PauliLindbladMap.GeneratorTerm(2, QubitSparsePauli(("YYX", [1, 2, 4]), 5)),
|
|
PauliLindbladMap.GeneratorTerm(0.5, QubitSparsePauli(("", []), 5)),
|
|
PauliLindbladMap.GeneratorTerm(-0.25, QubitSparsePauli(("ZZ", [0, 3]), 5)),
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("XX", [1, 2]), 5)),
|
|
PauliLindbladMap.GeneratorTerm(1, QubitSparsePauli(("ZY", [1, 4]), 5)),
|
|
]
|
|
self.assertEqual(list(pauli_lindblad_map), expected)
|
|
|
|
def test_apply_layout_list(self):
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(5).apply_layout([4, 3, 2, 1, 0]), PauliLindbladMap.identity(5)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(3).apply_layout([0, 2, 1], 8), PauliLindbladMap.identity(8)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(2).apply_layout([1, 0]), PauliLindbladMap.identity(2)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(3).apply_layout([100, 10_000, 3], 100_000_000),
|
|
PauliLindbladMap.identity(100_000_000),
|
|
)
|
|
|
|
terms = [
|
|
("ZYX", (4, 2, 1), 1),
|
|
("", (), -0.5),
|
|
("XXYYZZ", (10, 8, 6, 4, 2, 0), 2.0),
|
|
]
|
|
|
|
def map_indices(terms, layout):
|
|
return [
|
|
(terms, tuple(layout[bit] for bit in bits), coeff) for terms, bits, coeff in terms
|
|
]
|
|
|
|
identity = list(range(12))
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12).apply_layout(identity),
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12),
|
|
)
|
|
# We've already tested elsewhere that `PauliLindbladMap.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(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12).apply_layout(backwards),
|
|
PauliLindbladMap.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(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12).apply_layout(shuffled),
|
|
PauliLindbladMap.from_sparse_list(map_indices(terms, shuffled), num_qubits=12),
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12).apply_layout(shuffled, 100),
|
|
PauliLindbladMap.from_sparse_list(map_indices(terms, shuffled), num_qubits=100),
|
|
)
|
|
expanded = [78, 69, 82, 68, 32, 97, 108, 101, 114, 116, 33]
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=11).apply_layout(expanded, 120),
|
|
PauliLindbladMap.from_sparse_list(map_indices(terms, expanded), num_qubits=120),
|
|
)
|
|
|
|
def test_apply_layout_transpiled(self):
|
|
base = PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("ZYX", (4, 2, 1), 1),
|
|
("", (), -0.5),
|
|
("XXY", (3, 2, 0), 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(
|
|
PauliLindbladMap.identity(0).apply_layout(None), PauliLindbladMap.identity(0)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(0).apply_layout(None, 3), PauliLindbladMap.identity(3)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(5).apply_layout(None), PauliLindbladMap.identity(5)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(3).apply_layout(None, 8), PauliLindbladMap.identity(8)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(0).apply_layout(None), PauliLindbladMap.identity(0)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(0).apply_layout(None, 8), PauliLindbladMap.identity(8)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(2).apply_layout(None), PauliLindbladMap.identity(2)
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(3).apply_layout(None, 100_000_000),
|
|
PauliLindbladMap.identity(100_000_000),
|
|
)
|
|
|
|
terms = [
|
|
("ZYX", (2, 1, 0), 1),
|
|
("", (), -0.5),
|
|
("XXYYZZ", (10, 8, 6, 4, 2, 0), 2.0),
|
|
]
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12).apply_layout(None),
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12),
|
|
)
|
|
self.assertEqual(
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=12).apply_layout(
|
|
None, num_qubits=200
|
|
),
|
|
PauliLindbladMap.from_sparse_list(terms, num_qubits=200),
|
|
)
|
|
|
|
def test_apply_layout_failures(self):
|
|
obs = PauliLindbladMap.from_list([("IIYI", 2.0), ("IIIX", -1)])
|
|
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_indexing(self):
|
|
pauli_lindblad_map = PauliLindbladMap.from_sparse_list(
|
|
[
|
|
("XYY", (4, 2, 1), 2),
|
|
("", (), 0.5),
|
|
("ZZ", (3, 0), -0.25),
|
|
("XX", (2, 1), 1.0),
|
|
("YZ", (4, 1), 1),
|
|
],
|
|
num_qubits=5,
|
|
)
|
|
expected = [
|
|
PauliLindbladMap.GeneratorTerm(2, QubitSparsePauli(("YYX", [1, 2, 4]), 5)),
|
|
PauliLindbladMap.GeneratorTerm(0.5, QubitSparsePauli(("", []), 5)),
|
|
PauliLindbladMap.GeneratorTerm(-0.25, QubitSparsePauli(("ZZ", [0, 3]), 5)),
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("XX", [1, 2]), 5)),
|
|
PauliLindbladMap.GeneratorTerm(1, QubitSparsePauli(("ZY", [1, 4]), 5)),
|
|
]
|
|
self.assertEqual(pauli_lindblad_map[0], expected[0])
|
|
self.assertEqual(pauli_lindblad_map[-2], expected[-2])
|
|
self.assertEqual(pauli_lindblad_map[2:4], PauliLindbladMap(expected[2:4]))
|
|
self.assertEqual(pauli_lindblad_map[1::2], PauliLindbladMap(expected[1::2]))
|
|
self.assertEqual(pauli_lindblad_map[:], PauliLindbladMap(expected))
|
|
self.assertEqual(pauli_lindblad_map[-1:-4:-1], PauliLindbladMap(expected[-1:-4:-1]))
|
|
|
|
@ddt.data(
|
|
PauliLindbladMap.from_sparse_list([("YXZ", [2, 3, 5], -0.25)], num_qubits=6),
|
|
PauliLindbladMap.from_list([("YIXZII", -0.25)]),
|
|
)
|
|
def test_term_repr(self, pauli_lindblad_map):
|
|
# The purpose of this is just to test that the `repr` doesn't crash, rather than asserting
|
|
# that it has any particular form.
|
|
term = pauli_lindblad_map[0]
|
|
self.assertIsInstance(repr(term), str)
|
|
self.assertIn("PauliLindbladMap.GeneratorTerm", repr(term))
|
|
|
|
@ddt.data(
|
|
PauliLindbladMap.from_sparse_list([("YXZ", [2, 3, 5], -0.25)], num_qubits=6),
|
|
PauliLindbladMap.from_list([("YIXZII", -0.25)]),
|
|
)
|
|
def test_term_to_pauli_lindblad_map(self, pauli_lindblad_map):
|
|
self.assertEqual(pauli_lindblad_map[0].to_pauli_lindblad_map(), pauli_lindblad_map)
|
|
self.assertIsNot(pauli_lindblad_map[0].to_pauli_lindblad_map(), pauli_lindblad_map)
|
|
|
|
def test_term_equality(self):
|
|
self.assertEqual(
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("", []), 5)),
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("", []), 5)),
|
|
)
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("", []), 5)),
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("", []), 8)),
|
|
)
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("", []), 5)),
|
|
PauliLindbladMap.GeneratorTerm(2.0, QubitSparsePauli(("", []), 5)),
|
|
)
|
|
self.assertNotEqual(
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("", []), 5)),
|
|
PauliLindbladMap.GeneratorTerm(-1, QubitSparsePauli(("", []), 8)),
|
|
)
|
|
|
|
pauli_lindblad_map = PauliLindbladMap.from_list(
|
|
[
|
|
("IIXIZ", 2),
|
|
("IIZIX", 2),
|
|
("XXIII", -1.5),
|
|
("XYIII", -1.5),
|
|
("IYIYI", 0.5),
|
|
("IIYIY", 0.5),
|
|
]
|
|
)
|
|
self.assertEqual(pauli_lindblad_map[0], pauli_lindblad_map[0])
|
|
self.assertEqual(pauli_lindblad_map[1], pauli_lindblad_map[1])
|
|
self.assertNotEqual(pauli_lindblad_map[0], pauli_lindblad_map[1])
|
|
self.assertEqual(pauli_lindblad_map[2], pauli_lindblad_map[2])
|
|
self.assertEqual(pauli_lindblad_map[3], pauli_lindblad_map[3])
|
|
self.assertNotEqual(pauli_lindblad_map[2], pauli_lindblad_map[3])
|
|
self.assertEqual(pauli_lindblad_map[4], pauli_lindblad_map[4])
|
|
self.assertEqual(pauli_lindblad_map[5], pauli_lindblad_map[5])
|
|
self.assertNotEqual(pauli_lindblad_map[4], pauli_lindblad_map[5])
|
|
|
|
@ddt.data(
|
|
PauliLindbladMap.from_sparse_list([("YXZ", [2, 3, 5], -0.25)], num_qubits=6),
|
|
PauliLindbladMap.from_list([("YIXZII", -0.25)]),
|
|
)
|
|
def test_term_pickle(self, pauli_lindblad_map):
|
|
term = pauli_lindblad_map[0]
|
|
self.assertEqual(pickle.loads(pickle.dumps(term)), term)
|
|
self.assertEqual(copy.copy(term), term)
|
|
self.assertEqual(copy.deepcopy(term), term)
|
|
|
|
def test_term_attributes(self):
|
|
term = PauliLindbladMap([("IIXIIXZ", 5.0)])[0]
|
|
self.assertEqual(term.num_qubits, 7)
|
|
self.assertEqual(term.rate, 5.0)
|
|
np.testing.assert_equal(
|
|
term.paulis,
|
|
np.array(
|
|
[
|
|
QubitSparsePauli.Pauli.Z,
|
|
QubitSparsePauli.Pauli.X,
|
|
QubitSparsePauli.Pauli.X,
|
|
],
|
|
dtype=np.uint8,
|
|
),
|
|
)
|
|
np.testing.assert_equal(term.indices, np.array([0, 1, 4], dtype=np.uintp))
|
|
|
|
term = PauliLindbladMap.from_list([("IIXYZ", 0.5)])[0]
|
|
self.assertEqual(term.num_qubits, 5)
|
|
self.assertEqual(term.rate, 0.5)
|
|
self.assertEqual(
|
|
list(term.paulis),
|
|
[
|
|
QubitSparsePauli.Pauli.Z,
|
|
QubitSparsePauli.Pauli.Y,
|
|
QubitSparsePauli.Pauli.X,
|
|
],
|
|
)
|
|
self.assertEqual(list(term.indices), [0, 1, 2])
|
|
|
|
self.assertEqual(term.qubit_sparse_pauli, QubitSparsePauli.from_label("IIXYZ"))
|
|
|
|
def test_term_new(self):
|
|
expected = PauliLindbladMap([("IIIXXZIII", 1.0)])[0]
|
|
|
|
self.assertEqual(
|
|
PauliLindbladMap.GeneratorTerm(1.0, QubitSparsePauli(("ZXX", [3, 4, 5]), 9)),
|
|
expected,
|
|
)
|
|
|
|
def test_to_sparse_list(self):
|
|
"""Test converting to a sparse list."""
|
|
with self.subTest(msg="identity"):
|
|
pauli_lindblad_map = PauliLindbladMap.identity(100)
|
|
expected = []
|
|
self.assertEqual(expected, pauli_lindblad_map.to_sparse_list())
|
|
|
|
with self.subTest(msg="IXYZ"):
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZ", 1.0)])
|
|
expected = [("ZYX", [0, 1, 2], 1)]
|
|
self.assertEqual(
|
|
canonicalize_sparse_list(expected),
|
|
canonicalize_sparse_list(pauli_lindblad_map.to_sparse_list()),
|
|
)
|
|
|
|
with self.subTest(msg="multiple"):
|
|
pauli_lindblad_map = PauliLindbladMap.from_list([("XXIZ", 0.5), ("YYIZ", -1)])
|
|
expected = [("XXZ", [3, 2, 0], 0.5), ("ZYY", [0, 2, 3], -1)]
|
|
self.assertEqual(
|
|
canonicalize_sparse_list(expected),
|
|
canonicalize_sparse_list(pauli_lindblad_map.to_sparse_list()),
|
|
)
|
|
|
|
def test_sparse_term_pauli_labels(self):
|
|
"""Test getting the bit labels of a SparseTerm."""
|
|
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZXYZXYZ", 1.0)])
|
|
term = pauli_lindblad_map[0]
|
|
indices = term.indices
|
|
labels = term.pauli_labels()
|
|
|
|
label_dict = dict(zip(indices, labels))
|
|
expected = dict(enumerate("ZYXZYXZYX"))
|
|
|
|
for i, label in expected.items():
|
|
self.assertEqual(label, label_dict[i])
|
|
|
|
reconstructed = PauliLindbladMap.from_sparse_list(
|
|
[(labels, indices, 1)], pauli_lindblad_map.num_qubits
|
|
)
|
|
self.assertEqual(pauli_lindblad_map, reconstructed)
|
|
|
|
def test_attributes(self):
|
|
pauli_lindblad_map = PauliLindbladMap.from_components(
|
|
[1.0, 2.0], QubitSparsePauliList(["II", "XX"])
|
|
)
|
|
self.assertEqual(
|
|
pauli_lindblad_map.get_qubit_sparse_pauli_list_copy(),
|
|
QubitSparsePauliList.from_list(["II", "XX"]),
|
|
)
|
|
|
|
def test_derived_properties(self):
|
|
"""Test whether gamma and probabilities are correctly calculated."""
|
|
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZXYZXYZ", 1.0)])
|
|
w = 0.5 * (1 + np.exp(-2 * 1.0))
|
|
self.assertTrue(np.allclose(w, pauli_lindblad_map.probabilities()[0]))
|
|
self.assertTrue(np.allclose(1.0, pauli_lindblad_map.gamma()))
|
|
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZXYZXYZ", -1.0)])
|
|
w = 0.5 * (1 + np.exp(-2 * -1.0))
|
|
gamma = w + np.abs(1 - w)
|
|
prob = w / gamma
|
|
self.assertTrue(np.allclose(prob, pauli_lindblad_map.probabilities()[0]))
|
|
self.assertTrue(np.allclose(gamma, pauli_lindblad_map.gamma()))
|
|
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZXYZXYZ", -0.5)])
|
|
w = 0.5 * (1 + np.exp(-2 * -0.5))
|
|
gamma = w + np.abs(1 - w)
|
|
prob = w / gamma
|
|
self.assertTrue(np.allclose(prob, pauli_lindblad_map.probabilities()[0]))
|
|
self.assertTrue(np.allclose(gamma, pauli_lindblad_map.gamma()))
|
|
|
|
pauli_lindblad_map = PauliLindbladMap(
|
|
[("IXYZXYZXYZ", -1.0), ("IXYZXYZXYZ", 1.0), ("IXYZXYZXYZ", -0.5)]
|
|
)
|
|
rates = np.array([-1.0, 1.0, -0.5])
|
|
w = 0.5 * (1 + np.exp(-2 * rates))
|
|
gammas = w + np.abs(1 - w)
|
|
probs = w / gammas
|
|
gamma = np.prod(gammas)
|
|
self.assertTrue(np.allclose(probs, pauli_lindblad_map.probabilities()))
|
|
self.assertTrue(np.allclose(gamma, pauli_lindblad_map.gamma()))
|
|
|
|
def test_drop_paulis(self):
|
|
"""Test the `drop_paulis` method."""
|
|
pauli_map_in = PauliLindbladMap.from_list(
|
|
[("XXIZI", 2.0), ("IIIYZ", 0.5), ("ZIIXY", -0.25)]
|
|
)
|
|
self.assertEqual(pauli_map_in, pauli_map_in.drop_paulis([]))
|
|
|
|
pauli_map_out = pauli_map_in.drop_paulis([0])
|
|
expected = PauliLindbladMap.from_list([("XXIZI", 2.0), ("IIIYI", 0.5), ("ZIIXI", -0.25)])
|
|
self.assertEqual(pauli_map_out, expected)
|
|
|
|
pauli_map_out = pauli_map_in.drop_paulis([0, 3])
|
|
expected = PauliLindbladMap.from_list([("XIIZI", 2.0), ("IIIYI", 0.5), ("ZIIXI", -0.25)])
|
|
self.assertEqual(pauli_map_out, expected)
|
|
|
|
pauli_map_out = pauli_map_in.drop_paulis([0, 4])
|
|
expected = PauliLindbladMap.from_list([("IXIZI", 2.0), ("IIIYI", 0.5), ("IIIXI", -0.25)])
|
|
self.assertEqual(pauli_map_out, expected)
|
|
|
|
def test_drop_paulis_raises(self):
|
|
"""Test that `drop_paulis` raises."""
|
|
pauli_map_in = PauliLindbladMap.from_list(
|
|
[("XXIZI", 2.0), ("IIIYZ", 0.5), ("ZIIXY", -0.25)]
|
|
)
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError, "cannot drop Paulis for index 5 in a 5-qubit PauliLindbladMap"
|
|
):
|
|
pauli_map_in.drop_paulis([0, 5])
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError, "cannot drop Paulis for index 8 in a 5-qubit PauliLindbladMap"
|
|
):
|
|
pauli_map_in.drop_paulis([0, 8])
|
|
|
|
def test_keep_paulis(self):
|
|
"""Test the `keep_paulis` method."""
|
|
pauli_map_in = PauliLindbladMap.from_list(
|
|
[("XXIZI", 2.0), ("IIIYZ", 0.5), ("ZIIXY", -0.25)]
|
|
)
|
|
self.assertEqual(pauli_map_in, pauli_map_in.keep_paulis(range(5)))
|
|
|
|
pauli_map_out = pauli_map_in.keep_paulis(range(1, 5))
|
|
expected = PauliLindbladMap.from_list([("XXIZI", 2.0), ("IIIYI", 0.5), ("ZIIXI", -0.25)])
|
|
self.assertEqual(pauli_map_out, expected)
|
|
|
|
pauli_map_out = pauli_map_in.keep_paulis([4, 1, 2])
|
|
expected = PauliLindbladMap.from_list([("XIIZI", 2.0), ("IIIYI", 0.5), ("ZIIXI", -0.25)])
|
|
self.assertEqual(pauli_map_out, expected)
|
|
|
|
pauli_map_out = pauli_map_in.keep_paulis([1, 2, 3])
|
|
expected = PauliLindbladMap.from_list([("IXIZI", 2.0), ("IIIYI", 0.5), ("IIIXI", -0.25)])
|
|
self.assertEqual(pauli_map_out, expected)
|
|
|
|
def test_keep_paulis_raises(self):
|
|
"""Test that `keep_paulis` raises."""
|
|
pauli_map_in = PauliLindbladMap.from_list(
|
|
[("XXIZI", 2.0), ("IIIYZ", 0.5), ("ZIIXY", -0.25)]
|
|
)
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError, "cannot keep Paulis for index 5 in a 5-qubit PauliLindbladMap"
|
|
):
|
|
pauli_map_in.keep_paulis([5])
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError, "cannot keep Paulis for index 8 in a 5-qubit PauliLindbladMap"
|
|
):
|
|
pauli_map_in.keep_paulis([0, 8])
|
|
|
|
def test_simplify(self):
|
|
duplicate = PauliLindbladMap([("IXYZXYZXYZ", 1.0), ("IXYZXYZXYZ", 2.0)])
|
|
self.assertEqual(duplicate.simplify(), PauliLindbladMap([("IXYZXYZXYZ", 3.0)]))
|
|
|
|
cancel = PauliLindbladMap([("IXYZXYZXYZ", 1.0), ("IXYZXYZXYZ", -1.0)])
|
|
self.assertEqual(cancel.simplify(), PauliLindbladMap.identity(10))
|
|
|
|
drop_identities = PauliLindbladMap([("IXY", 1.0), ("III", -1.0)])
|
|
self.assertEqual(drop_identities.simplify(), PauliLindbladMap([("IXY", 1.0)]))
|
|
|
|
# note: some calls to simplify() are to enforce canonical ordering
|
|
threshold = PauliLindbladMap([("X", 1e-9), ("Z", -2), ("Y", 1.0)]).simplify(1e-10)
|
|
self.assertEqual(threshold.simplify(), PauliLindbladMap([("Y", 1.0), ("Z", -2)]).simplify())
|
|
self.assertEqual(threshold.simplify(1e-10), threshold)
|
|
self.assertEqual(threshold.simplify(1.1), PauliLindbladMap([("Z", -2)]))
|
|
|
|
def test_scale_rates(self):
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZXYZXYZ", 1.0)])
|
|
self.assertEqual(
|
|
pauli_lindblad_map.scale_rates(12.32), PauliLindbladMap([("IXYZXYZXYZ", 12.32)])
|
|
)
|
|
|
|
pauli_lindblad_map = PauliLindbladMap(
|
|
[("IXYZXYZXYZ", -1.0), ("IXYZXYZXYZ", 1.0), ("IXYZXYZXYZ", -0.5)]
|
|
)
|
|
self.assertEqual(
|
|
pauli_lindblad_map.scale_rates(3.2),
|
|
PauliLindbladMap([("IXYZXYZXYZ", -3.2), ("IXYZXYZXYZ", 3.2), ("IXYZXYZXYZ", -1.6)]),
|
|
)
|
|
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(5).scale_rates(5.0), PauliLindbladMap.identity(5)
|
|
)
|
|
|
|
def test_inverse(self):
|
|
|
|
pauli_lindblad_map = PauliLindbladMap([("IXYZXYZXYZ", 1.0)])
|
|
self.assertEqual(pauli_lindblad_map.inverse(), PauliLindbladMap([("IXYZXYZXYZ", -1.0)]))
|
|
|
|
pauli_lindblad_map = PauliLindbladMap(
|
|
[("IXYZXYZXYZ", -1.0), ("IXYZXYZXYZ", 1.0), ("IXYZXYZXYZ", -0.5)]
|
|
)
|
|
self.assertEqual(
|
|
pauli_lindblad_map.inverse(),
|
|
PauliLindbladMap([("IXYZXYZXYZ", 1.0), ("IXYZXYZXYZ", -1.0), ("IXYZXYZXYZ", 0.5)]),
|
|
)
|
|
|
|
self.assertEqual(PauliLindbladMap.identity(5).inverse(), PauliLindbladMap.identity(5))
|
|
|
|
def test_compose(self):
|
|
"""Test compose method."""
|
|
p0 = PauliLindbladMap.from_sparse_list([("XYZ", [3, 2, 1], 2.1)], 4)
|
|
expected = PauliLindbladMap.from_sparse_list(
|
|
[("XYZ", [3, 2, 1], 2.1), ("XYZ", [3, 2, 1], 2.1)], 4
|
|
)
|
|
self.assertEqual(p0.compose(p0), expected)
|
|
|
|
# validate original object unchanged
|
|
self.assertEqual(p0, PauliLindbladMap.from_sparse_list([("XYZ", [3, 2, 1], 2.1)], 4))
|
|
|
|
p0 = PauliLindbladMap.from_sparse_list([("XYZ", [3, 2, 1], 2.1), ("Y", [0], 0.1)], 4)
|
|
p1 = PauliLindbladMap.from_sparse_list([("X", [3], 0.2), ("Z", [1], 0.1)], 4)
|
|
expected = PauliLindbladMap.from_sparse_list(
|
|
[("XYZ", [3, 2, 1], 2.1), ("Y", [0], 0.1), ("X", [3], 0.2), ("Z", [1], 0.1)], 4
|
|
)
|
|
self.assertEqual(p0 @ p1, expected)
|
|
|
|
# validate original objects unchanged
|
|
self.assertEqual(
|
|
p0, PauliLindbladMap.from_sparse_list([("XYZ", [3, 2, 1], 2.1), ("Y", [0], 0.1)], 4)
|
|
)
|
|
self.assertEqual(
|
|
p1, PauliLindbladMap.from_sparse_list([("X", [3], 0.2), ("Z", [1], 0.1)], 4)
|
|
)
|
|
|
|
# test composition with identity map
|
|
p0 = PauliLindbladMap.from_sparse_list([("XYZ", [3, 2, 1], 2.1)], 4)
|
|
p1 = PauliLindbladMap.identity(4)
|
|
self.assertEqual(p0 @ p1, p0)
|
|
self.assertEqual(p1 @ p0, p0)
|
|
|
|
p0 = PauliLindbladMap.identity(20)
|
|
self.assertEqual(p0 @ p0, p0)
|
|
|
|
def test_compose_errors(self):
|
|
|
|
p0 = PauliLindbladMap.from_sparse_list([("XYZ", [3, 2, 1], 2.1)], 4)
|
|
p1 = PauliLindbladMap.identity(3)
|
|
|
|
with self.assertRaisesRegex(ValueError, r"mismatched numbers of qubits: 4, 3"):
|
|
p0.compose(p1)
|
|
|
|
with self.assertRaisesRegex(TypeError, r"unknown type for compose"):
|
|
p0.compose(1.0)
|
|
|
|
def test_pauli_fidelity(self):
|
|
|
|
pauli_lindblad_map = PauliLindbladMap(
|
|
[("XY", [0, 1], 1.23), ("Z", [1], -0.23), ("X", [2], 0.3)], num_qubits=4
|
|
)
|
|
self.assertEqual(pauli_lindblad_map.pauli_fidelity(QubitSparsePauli(("X", [0]), 4)), 1.0)
|
|
self.assertEqual(
|
|
pauli_lindblad_map.pauli_fidelity(QubitSparsePauli(("Y", [0]), 4)), np.exp(-2 * 1.23)
|
|
)
|
|
self.assertEqual(
|
|
pauli_lindblad_map.pauli_fidelity(QubitSparsePauli(("X", [1]), 4)), np.exp(-2 * 1.0)
|
|
)
|
|
self.assertEqual(pauli_lindblad_map.pauli_fidelity(QubitSparsePauli(("Z", [3]), 4)), 1.0)
|
|
# np.allclose needed for machine precision
|
|
self.assertTrue(
|
|
np.allclose(
|
|
pauli_lindblad_map.pauli_fidelity(QubitSparsePauli(("ZXY", [0, 1, 2]), 4)),
|
|
np.exp(-2 * 0.07),
|
|
atol=1e-12,
|
|
rtol=1e-12,
|
|
)
|
|
)
|
|
|
|
self.assertEqual(
|
|
PauliLindbladMap.identity(5).pauli_fidelity(QubitSparsePauli("IXXYZ")), 1.0
|
|
)
|
|
|
|
def test_pauli_fidelity_errors(self):
|
|
|
|
pauli_lindblad_map = PauliLindbladMap(
|
|
[("XY", [0, 1], 1.23), ("Z", [1], -0.23), ("X", [2], 0.3)], num_qubits=4
|
|
)
|
|
|
|
with self.assertRaisesRegex(ValueError, r"mismatched numbers of qubits: 5, 4"):
|
|
pauli_lindblad_map.pauli_fidelity(QubitSparsePauli(("X", [0]), 5))
|
|
|
|
def test_signed_sample(self):
|
|
|
|
# test all negative rates
|
|
pauli_lindblad_map = PauliLindbladMap([("X", -1.0), ("Y", -1.0)])
|
|
probs = pauli_lindblad_map.probabilities()
|
|
probs_dict = {
|
|
"I": probs[0] * probs[1],
|
|
"X": probs[0] * (1 - probs[1]),
|
|
"Y": (1 - probs[0]) * probs[1],
|
|
"Z": (1 - probs[0]) * (1 - probs[1]),
|
|
}
|
|
expected_signs = {"I": True, "X": False, "Y": False, "Z": True}
|
|
|
|
num_samples = 10000
|
|
signs, qubit_sparse_pauli_list = pauli_lindblad_map.signed_sample(num_samples, 12312)
|
|
|
|
counts = {"I": 0, "X": 0, "Y": 0, "Z": 0}
|
|
for sign, q in zip(signs, qubit_sparse_pauli_list):
|
|
for symbol in counts:
|
|
if q == QubitSparsePauli(symbol):
|
|
counts[symbol] += 1
|
|
self.assertEqual(expected_signs[symbol], sign)
|
|
|
|
for symbol, count in counts.items():
|
|
self.assertTrue(np.abs(count / num_samples - probs_dict[symbol]) < 1e-2)
|
|
|
|
# test all positive rates
|
|
pauli_lindblad_map = PauliLindbladMap([("X", 1.0), ("Y", 1.0)])
|
|
probs = pauli_lindblad_map.probabilities()
|
|
probs_dict = {
|
|
"I": probs[0] * probs[1],
|
|
"X": probs[0] * (1 - probs[1]),
|
|
"Y": (1 - probs[0]) * probs[1],
|
|
"Z": (1 - probs[0]) * (1 - probs[1]),
|
|
}
|
|
expected_signs = {"I": True, "X": True, "Y": True, "Z": True}
|
|
|
|
num_samples = 10000
|
|
signs, qubit_sparse_pauli_list = pauli_lindblad_map.signed_sample(num_samples, 12312)
|
|
|
|
counts = {"I": 0, "X": 0, "Y": 0, "Z": 0}
|
|
for sign, q in zip(signs, qubit_sparse_pauli_list):
|
|
for symbol in counts:
|
|
if q == QubitSparsePauli(symbol):
|
|
counts[symbol] += 1
|
|
self.assertEqual(expected_signs[symbol], sign)
|
|
for symbol, count in counts.items():
|
|
self.assertTrue(np.abs(count / num_samples - probs_dict[symbol]) < 1e-2)
|
|
|
|
# test mix of positive and negative rates
|
|
pauli_lindblad_map = PauliLindbladMap([("X", 1.0), ("Y", -1.0)])
|
|
probs = pauli_lindblad_map.probabilities()
|
|
probs_dict = {
|
|
"I": probs[0] * probs[1],
|
|
"X": probs[0] * (1 - probs[1]),
|
|
"Y": (1 - probs[0]) * probs[1],
|
|
"Z": (1 - probs[0]) * (1 - probs[1]),
|
|
}
|
|
expected_signs = {"I": True, "X": True, "Y": False, "Z": False}
|
|
|
|
num_samples = 10000
|
|
signs, qubit_sparse_pauli_list = pauli_lindblad_map.signed_sample(num_samples, 12312)
|
|
|
|
counts = {"I": 0, "X": 0, "Y": 0, "Z": 0}
|
|
for sign, q in zip(signs, qubit_sparse_pauli_list):
|
|
for symbol in counts:
|
|
if q == QubitSparsePauli(symbol):
|
|
counts[symbol] += 1
|
|
self.assertEqual(expected_signs[symbol], sign)
|
|
|
|
for symbol, count in counts.items():
|
|
self.assertTrue(np.abs(count / num_samples - probs_dict[symbol]) < 1e-2)
|
|
|
|
# test callable without seed
|
|
signs, qubit_sparse_pauli_list = pauli_lindblad_map.signed_sample(5)
|
|
self.assertTrue(isinstance(qubit_sparse_pauli_list, QubitSparsePauliList))
|
|
self.assertEqual(len(qubit_sparse_pauli_list), 5)
|
|
self.assertEqual(len(signs), 5)
|
|
|
|
def test_sample(self):
|
|
pauli_lindblad_map = PauliLindbladMap([("X", 1.0), ("Y", 1.0)])
|
|
probs = pauli_lindblad_map.probabilities()
|
|
probs_dict = {
|
|
"I": probs[0] * probs[1],
|
|
"X": probs[0] * (1 - probs[1]),
|
|
"Y": (1 - probs[0]) * probs[1],
|
|
"Z": (1 - probs[0]) * (1 - probs[1]),
|
|
}
|
|
|
|
num_samples = 10000
|
|
qubit_sparse_pauli_list = pauli_lindblad_map.sample(num_samples, 12312)
|
|
|
|
counts = {"I": 0, "X": 0, "Y": 0, "Z": 0}
|
|
for q in qubit_sparse_pauli_list:
|
|
for symbol in counts:
|
|
if q == QubitSparsePauli(symbol):
|
|
counts[symbol] += 1
|
|
for symbol, count in counts.items():
|
|
self.assertTrue(np.abs(count / num_samples - probs_dict[symbol]) < 1e-2)
|
|
|
|
# test callable without seed
|
|
qubit_sparse_pauli_list = pauli_lindblad_map.sample(5)
|
|
self.assertTrue(isinstance(qubit_sparse_pauli_list, QubitSparsePauliList))
|
|
self.assertEqual(len(qubit_sparse_pauli_list), 5)
|
|
|
|
def test_sample_errors(self):
|
|
pauli_lindblad_map = PauliLindbladMap([("X", 1.0), ("Y", -1.0)])
|
|
|
|
with self.assertRaisesRegex(
|
|
ValueError, "PauliLindbladMap.sample called for a map with negative rates"
|
|
):
|
|
pauli_lindblad_map.sample(1)
|
|
|
|
|
|
def canonicalize_term(pauli, indices, rate):
|
|
# 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(), float(rate))
|
|
|
|
|
|
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
|