Coverage for qml_essentials / topologies.py: 98%
45 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-30 11:43 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-30 11:43 +0000
1from typing import List, Callable, Union
2import logging
4log = logging.getLogger(__name__)
7class Topology:
8 """
9 Generates [control, target] wire-pair lists for two-qubit gates.
11 All public methods are static and share a small set of private
12 helpers so that related topologies (e.g. ``linear`` / ``circular``,
13 ``brick_layer`` / ``brick_layer_wrap``) re-use the same core logic.
15 Raises
16 ------
17 ValueError
18 If ``n_qubits < 2`` is passed to any topology method.
19 """
21 @classmethod
22 def stairs(
23 cls,
24 n_qubits: int,
25 offset: Union[int, Callable] = 0,
26 wrap=False,
27 reverse: bool = True,
28 mirror: bool = True,
29 span: Union[int, Callable] = 1,
30 stride: int = 1,
31 modulo: bool = True,
32 ) -> List[List[int]]:
33 """
34 Unified generator for nearest-neighbour and spand pair topologies.
35 Produces ``[control, target]`` pairs of qubits.
37 The default values, produce an "upstairs" entangling sequence
38 without wrapping around the last gate.
40 Parameters
41 ----------
42 n_qubits : int
43 Number of qubits.
44 offset : Union[int, Callable]
45 Offset for starting the entangling sequence.
46 Can either be a integer or a callable that takes n_qubits as input.
47 wrap : bool
48 Wraps around the entangling gates.
49 reverse : bool
50 Reverses both the iteration direction (upstairs/ downstairs)
51 mirror: bool
52 Flip target/ control qubit
53 span : int
54 Offset between control and target qubit. Defaults to 1
55 stride : int
56 Step size for entangling gates. Defaults to 1, meaning a stair
57 pattern will be generated.
58 modulo : bool
59 If a gate should be placed when the iterator decreases below 0
60 or exceeds n_qubits. Defaults to True
62 Returns
63 -------
64 List[List[int]]
65 """
66 ctrls = []
67 targets = []
69 n_gates = n_qubits if wrap else n_qubits - 1
70 _offset = offset(n_qubits) if callable(offset) else offset
71 _span = span(n_qubits) if callable(span) else span
73 for q in range(0, n_gates, stride):
74 _target = q + _offset + _span
75 if _target >= n_qubits and not modulo:
76 continue
77 _control = q + _offset
78 if _control < 0 and not modulo:
79 continue
81 _target = _target % n_qubits
82 _control = _control % n_qubits
84 if _target == _control:
85 log.warning("Skipping gate where control == target")
86 continue
88 targets += [_target]
89 ctrls += [_control]
91 if reverse:
92 ctrls = reversed(ctrls)
93 targets = reversed(targets)
95 if mirror:
96 ctrls, targets = targets, ctrls
98 pairs = list(zip(ctrls, targets, strict=True))
100 return pairs
102 @classmethod
103 def bricks(cls, n_qubits: int, **kwargs) -> List[List[int]]:
104 kwargs.setdefault("stride", 2)
105 kwargs.setdefault("modulo", False)
106 return cls.stairs(n_qubits=n_qubits, **kwargs)
108 @classmethod
109 def all_to_all(cls, n_qubits: int) -> List[List[int]]:
110 """Every ordered pair ``(i, j)`` with ``i ≠ j``."""
111 pairs: List[List[int]] = []
112 for ql in range(n_qubits):
113 for q in range(n_qubits):
114 if q != ql:
115 pairs.append(
116 [
117 n_qubits - ql - 1,
118 (n_qubits - q - 1) % n_qubits,
119 ]
120 )
121 return pairs