@@ -196,3 +196,65 @@ def test_disconnected_components():
196
196
cirq .Circuit (
197
197
SwapUpdater (circuit , allowed_qubits ,
198
198
initial_mapping ).add_swaps ()))
199
+
200
+
201
+ def test_termination_in_local_minimum ():
202
+ grid_2x5 = cirq .GridQubit .rect (2 , 5 )
203
+ q = list (cirq .NamedQubit (f'q{ i } ' ) for i in range (6 ))
204
+ # The initial mapping looks like:
205
+ # _|_0_|_1_|_2_|_3_|_4_|
206
+ # 0|q0 | | | |q4 |
207
+ # 1|q1 |q2 | |q3 |q5 |
208
+ initial_mapping = {
209
+ q [0 ]: cirq .GridQubit (0 , 0 ),
210
+ q [1 ]: cirq .GridQubit (1 , 0 ),
211
+ q [2 ]: cirq .GridQubit (1 , 1 ),
212
+ q [3 ]: cirq .GridQubit (1 , 3 ),
213
+ q [4 ]: cirq .GridQubit (0 , 4 ),
214
+ q [5 ]: cirq .GridQubit (1 , 4 ),
215
+ }
216
+ # Here's the idea:
217
+ # * there are two "clumps" of qubits: (q0,q1,q2) and (q3,q4,q5)
218
+ # * the active gate(s) span the two clumps
219
+ # * there are also gates on qubits within clumps
220
+ # * intra-clump gate cost contribution outweighs inter-clump gate cost
221
+ # In that case, we need to swap qubits away from their respective clumps in
222
+ # order to progress beyond any of the active gates. But we never will
223
+ # because doing so would increase the overall cost due to the intra-clump
224
+ # contributions. In fact, no greedy algorithm would be able to make progress
225
+ # in this case.
226
+ circuit = cirq .Circuit ()
227
+ # Cross-clump active gates
228
+ circuit .append (
229
+ [cirq .CNOT (q [0 ], q [3 ]),
230
+ cirq .CNOT (q [1 ], q [4 ]),
231
+ cirq .CNOT (q [2 ], q [5 ])])
232
+ # Intra-clump q0,q1,q2
233
+ circuit .append (
234
+ [cirq .CNOT (q [0 ], q [1 ]),
235
+ cirq .CNOT (q [0 ], q [2 ]),
236
+ cirq .CNOT (q [1 ], q [2 ])])
237
+ # Intra-clump q3,q4,q5
238
+ circuit .append (
239
+ [cirq .CNOT (q [3 ], q [4 ]),
240
+ cirq .CNOT (q [3 ], q [5 ]),
241
+ cirq .CNOT (q [4 ], q [5 ])])
242
+
243
+ updater = SwapUpdater (circuit , grid_2x5 , initial_mapping ,
244
+ lambda q1 , q2 : [cirq .SWAP (q1 , q2 )])
245
+ # Iterate until the SwapUpdater is finished or an assertion fails, keeping
246
+ # track of the ops generated by the previous iteration.
247
+ prev_it = list (updater .update_iteration ())
248
+ while not updater .dlists .all_empty ():
249
+ cur_it = list (updater .update_iteration ())
250
+
251
+ # If the current iteration adds a SWAP, it better not be the same SWAP
252
+ # as the previous iteration...
253
+ # If we pick the same SWAP twice in a row, then we're going in a loop
254
+ # forever without making any progress!
255
+ def _is_swap (ops ):
256
+ return len (ops ) == 1 and ops [0 ] == cirq .SWAP (* ops [0 ].qubits )
257
+
258
+ if _is_swap (prev_it ) and _is_swap (cur_it ):
259
+ assert set (prev_it [0 ].qubits ) != set (cur_it [0 ].qubits )
260
+ prev_it = cur_it
0 commit comments