qiskit/test/python/quantum_info/test_pauli_lindblad_map.py

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