bloqade-circuit 0.6.1__py3-none-any.whl → 0.6.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,166 @@
1
+ import cirq
2
+ import numpy as np
3
+
4
+
5
+ class OneZoneConflictGraph:
6
+ """
7
+ Representation of the AOD conflict graph for qubits to more to their entangling partners in a single zone setup.
8
+
9
+ Assumes the qubits are specified as cirq.GridQubits with a chosen geometry.
10
+ """
11
+
12
+ def __init__(self, moment: cirq.Moment):
13
+ """
14
+ Initializes the conflict graph for a given moment of a cirq circuit.
15
+
16
+ :param moment: A cirq.Moment object containing operations (gates) to be analyzed.
17
+ """
18
+
19
+ self.moment = moment
20
+ self.gates_in_moment = [op for op in moment.operations if len(op.qubits) == 2]
21
+
22
+ def _get_nodes(self):
23
+ """Each qubit participating in a two-qubit gate is a node.
24
+
25
+ Sets the self.nodes attribute.
26
+ """
27
+ nodes = set()
28
+ for gate in self.gates_in_moment:
29
+ nodes.add(gate.qubits[0])
30
+ nodes.add(gate.qubits[1])
31
+ self.nodes = nodes
32
+
33
+ def _get_edges(self):
34
+ """
35
+ Generate the edges of the conflict graph for a given moment.
36
+
37
+ Defines self.edges as a set of tuples, where each tuple represents an edge between two qubits.
38
+ """
39
+
40
+ edges = set()
41
+ for idx1, gate1 in enumerate(self.gates_in_moment):
42
+ edges.add(gate1.qubits)
43
+ for gate2 in self.gates_in_moment[idx1 + 1 :]:
44
+ # X one-to-many, ie. we can't split/merge AOD tones
45
+ if (gate1.qubits[0].row == gate2.qubits[0].row) ^ (
46
+ gate1.qubits[1].row == gate2.qubits[1].row
47
+ ):
48
+ edges.add((gate1.qubits[0], gate2.qubits[0]))
49
+ edges.add((gate1.qubits[0], gate2.qubits[1]))
50
+ edges.add((gate1.qubits[1], gate2.qubits[0]))
51
+ edges.add((gate1.qubits[1], gate2.qubits[1]))
52
+ # Y one-to-many
53
+ if (gate1.qubits[0].col == gate2.qubits[0].col) ^ (
54
+ gate1.qubits[1].col == gate2.qubits[1].col
55
+ ):
56
+ edges.add((gate1.qubits[0], gate2.qubits[0]))
57
+ edges.add((gate1.qubits[0], gate2.qubits[1]))
58
+ edges.add((gate1.qubits[1], gate2.qubits[0]))
59
+ edges.add((gate1.qubits[1], gate2.qubits[1]))
60
+ # X ordering, ie. the ordering of AOD tones must be preserved.
61
+ if (gate1.qubits[0].row < gate2.qubits[0].row) ^ (
62
+ gate1.qubits[1].row < gate2.qubits[1].row
63
+ ):
64
+ edges.add((gate1.qubits[0], gate2.qubits[0]))
65
+ edges.add((gate1.qubits[1], gate2.qubits[1]))
66
+ if (gate1.qubits[1].row < gate2.qubits[0].row) ^ (
67
+ gate1.qubits[0].row < gate2.qubits[1].row
68
+ ):
69
+ edges.add((gate1.qubits[0], gate2.qubits[1]))
70
+ edges.add((gate1.qubits[1], gate2.qubits[0]))
71
+ # Y ordering
72
+ if (gate1.qubits[0].col < gate2.qubits[0].col) ^ (
73
+ gate1.qubits[1].col < gate2.qubits[1].col
74
+ ):
75
+ edges.add((gate1.qubits[0], gate2.qubits[0]))
76
+ edges.add((gate1.qubits[1], gate2.qubits[1]))
77
+ if (gate1.qubits[1].col < gate2.qubits[0].col) ^ (
78
+ gate1.qubits[0].col < gate2.qubits[1].col
79
+ ):
80
+ edges.add((gate1.qubits[0], gate2.qubits[1]))
81
+ edges.add((gate1.qubits[1], gate2.qubits[0]))
82
+
83
+ self.edges = edges
84
+
85
+ def _get_node_degrees(self):
86
+ """Sets the self.degrees attribute."""
87
+
88
+ deg_dict = {}
89
+ for node in self.nodes:
90
+ deg_dict[node] = np.sum([node in edge for edge in self.edges])
91
+ self.degrees = deg_dict
92
+
93
+ def get_move_schedule(self, mover_limit: int = 10000):
94
+ """Generates a move schedule by coloring the conflict graph greedily, first coloring nodes of highest degree.
95
+
96
+ Qubits that are the arguments of a single CZ gate are 'partners'. Only one partner need be moved to arrange the
97
+ atoms for the 2Q gate. Thus, in coloring the conflict graph, as soon as one partner is colored, the other can be
98
+ disregarded for the purpose of coloring the rest of the graph.
99
+
100
+ This sets the self.move_schedule attribute, which is a dictionary where the keys are the indices of the move moments.
101
+
102
+ :param mover_limit: The maximum number of qubits that can be moved in a single moment. Added as a constraint
103
+ when coloring the conflict graph.
104
+ :returns a dictionary of idx:[cirq.Qid] where idx indexes the move moment where the list of qubits move.
105
+ """
106
+
107
+ self._get_nodes()
108
+ self._get_edges()
109
+ self._get_node_degrees()
110
+
111
+ self.ordered_nodes = sorted(self.degrees, key=self.degrees.get, reverse=True)
112
+
113
+ move_schedule = {}
114
+ colored_nodes = set()
115
+ partner_node = None
116
+ for node in self.ordered_nodes:
117
+ colored = False
118
+ for gate in self.gates_in_moment:
119
+ if node in gate.qubits:
120
+ partners = set(gate.qubits)
121
+ partners.remove(node)
122
+ partner_node = list(partners)[0]
123
+ if node in colored_nodes:
124
+ # NOTE: if a node is colored, both it and its partner are added to colored_nodes
125
+ continue
126
+ else:
127
+ connected_nodes = set()
128
+ for edge in self.edges:
129
+ if node in edge:
130
+ connected_nodes.add(edge[0])
131
+ connected_nodes.add(edge[1])
132
+ connected_nodes.remove(node)
133
+ for color in move_schedule.keys():
134
+ has_colored_neighbor = False
135
+ for connected_node in connected_nodes:
136
+ # NOTE: loop through to make sure none of the connected nodes are already assigned to color.
137
+ if connected_node in move_schedule[color]:
138
+ has_colored_neighbor = True
139
+ break
140
+ else:
141
+ continue
142
+ mover_limit_reached = len(move_schedule[color]) >= mover_limit
143
+ if not (has_colored_neighbor or mover_limit_reached):
144
+ # NOTE: node needs color
145
+ move_schedule[color].add(node)
146
+ colored = True
147
+
148
+ # NOTE: add this node and it's partner to the solved nodes.
149
+ colored_nodes.add(node)
150
+
151
+ if partner_node is not None:
152
+ colored_nodes.add(partner_node)
153
+ break
154
+ if not colored:
155
+ move_schedule[len(move_schedule)] = {node}
156
+ colored = True
157
+ colored_nodes.add(node)
158
+
159
+ if partner_node is not None:
160
+ colored_nodes.add(partner_node)
161
+
162
+ assert set(colored_nodes) == set(self.ordered_nodes)
163
+
164
+ self.move_schedule = move_schedule
165
+
166
+ return self.move_schedule