|
17 | 17 | import unittest |
18 | 18 |
|
19 | 19 | from qiskit.transpiler import CouplingMap |
20 | | -from qiskit_paulice.layout import get_low_overhead_ancillas |
| 20 | +from qiskit_paulice.layout import get_check_qubits, get_low_overhead_ancillas |
21 | 21 |
|
22 | 22 |
|
23 | 23 | class TestGetLowOverheadAncillas(unittest.TestCase): |
@@ -58,3 +58,69 @@ def test_ancilla_shared_by_multiple_layout_qubits(self): |
58 | 58 | self.assertEqual(list(result.keys()), [1]) |
59 | 59 | # Order tracks the layout iteration order. |
60 | 60 | self.assertEqual(result[1], [0, 2]) |
| 61 | + |
| 62 | + def test_key_order_is_deterministic(self): |
| 63 | + """Keys are sorted, so `neighbors` iteration instability can't leak out.""" |
| 64 | + # Edges added so a single layout qubit's neighbors are not in index order. |
| 65 | + cm = CouplingMap([(5, 2), (5, 8), (5, 1), (1, 5), (2, 5), (8, 5)]) |
| 66 | + self.assertEqual(list(get_low_overhead_ancillas(cm, [5])), [1, 2, 8]) |
| 67 | + |
| 68 | + |
| 69 | +class _FakeBackend: |
| 70 | + """Minimal stand-in exposing the ``coupling_map`` attribute the wrapper reads.""" |
| 71 | + |
| 72 | + def __init__(self, coupling_map: CouplingMap): |
| 73 | + self.coupling_map = coupling_map |
| 74 | + |
| 75 | + |
| 76 | +class TestGetCheckQubits(unittest.TestCase): |
| 77 | + """Tests covering :func:`get_check_qubits`.""" |
| 78 | + |
| 79 | + def test_line_pairs_each_target_with_its_ancilla(self): |
| 80 | + # Line 0-1-2-3-4, payload on the interior: 0 checks 1, 4 checks 3. |
| 81 | + targets, ancillas = get_check_qubits(CouplingMap.from_line(5), [1, 2, 3]) |
| 82 | + self.assertEqual(targets, [1, 3]) |
| 83 | + self.assertEqual(ancillas, [0, 4]) |
| 84 | + |
| 85 | + def test_accepts_backend_like_object(self): |
| 86 | + # Anything exposing `.coupling_map` works the same as passing the map. |
| 87 | + cm = CouplingMap.from_line(5) |
| 88 | + self.assertEqual( |
| 89 | + get_check_qubits(_FakeBackend(cm), [1, 2, 3]), |
| 90 | + get_check_qubits(cm, [1, 2, 3]), |
| 91 | + ) |
| 92 | + |
| 93 | + def test_each_ancilla_takes_a_distinct_target(self): |
| 94 | + # Ancilla 0 neighbors only target 1; ancilla 3 neighbors targets 1 and 2. |
| 95 | + # Walking ancillas in order, 0 claims 1 and 3 falls through to 2, so both |
| 96 | + # targets get a check rather than competing for target 1. |
| 97 | + cm = CouplingMap([(0, 1), (1, 0), (3, 1), (1, 3), (3, 2), (2, 3)]) |
| 98 | + targets, ancillas = get_check_qubits(cm, [1, 2]) |
| 99 | + self.assertEqual(targets, [1, 2]) |
| 100 | + self.assertEqual(ancillas, [0, 3]) |
| 101 | + |
| 102 | + def test_pairs_are_valid_and_unique(self): |
| 103 | + # Star centered on 2 plus a tail: every pair must use a distinct ancilla |
| 104 | + # outside the layout that genuinely neighbors its target. |
| 105 | + cm = CouplingMap.from_line(6) |
| 106 | + layout = [1, 2, 3, 4] |
| 107 | + targets, ancillas = get_check_qubits(cm, layout) |
| 108 | + self.assertEqual(len(targets), len(ancillas)) |
| 109 | + self.assertEqual(len(set(targets)), len(targets)) |
| 110 | + self.assertEqual(len(set(ancillas)), len(ancillas)) |
| 111 | + for t, a in zip(targets, ancillas, strict=True): |
| 112 | + self.assertIn(t, layout) |
| 113 | + self.assertNotIn(a, layout) |
| 114 | + self.assertIn(a, list(cm.neighbors(t))) |
| 115 | + |
| 116 | + def test_ancilla_with_no_free_target_is_dropped(self): |
| 117 | + # Ancillas 0 and 2 both border only target 1; the first claims it and the |
| 118 | + # second is left unmatched, so only one pair comes back. |
| 119 | + cm = CouplingMap([(0, 1), (1, 0), (2, 1), (1, 2)]) |
| 120 | + self.assertEqual(get_check_qubits(cm, [1]), ([1], [0])) |
| 121 | + |
| 122 | + def test_empty_layout_returns_empty_lists(self): |
| 123 | + self.assertEqual(get_check_qubits(CouplingMap.from_line(5), []), ([], [])) |
| 124 | + |
| 125 | + def test_full_layout_returns_empty_lists(self): |
| 126 | + self.assertEqual(get_check_qubits(CouplingMap.from_line(5), list(range(5))), ([], [])) |
0 commit comments