bloqade-circuit 0.6.0__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.
- bloqade/cirq_utils/__init__.py +2 -0
- bloqade/cirq_utils/noise/__init__.py +11 -0
- bloqade/cirq_utils/noise/_two_zone_utils.py +531 -0
- bloqade/cirq_utils/noise/conflict_graph.py +166 -0
- bloqade/cirq_utils/noise/model.py +544 -0
- bloqade/cirq_utils/noise/transform.py +57 -0
- bloqade/cirq_utils/parallelize.py +4 -2
- bloqade/qasm2/_qasm_loading.py +4 -1
- bloqade/qasm2/parse/lowering.py +11 -3
- bloqade/squin/cirq/lowering.py +53 -5
- bloqade/squin/lowering.py +29 -2
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/METADATA +1 -1
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/RECORD +15 -10
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/WHEEL +0 -0
- {bloqade_circuit-0.6.0.dist-info → bloqade_circuit-0.6.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|