@@ -196,3 +196,65 @@ def test_disconnected_components():
196196 cirq .Circuit (
197197 SwapUpdater (circuit , allowed_qubits ,
198198 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