def test_termination_in_local_minimum(): grid_2x5 = cirq.GridQubit.rect(2, 5) q = list(cirq.NamedQubit(f'q{i}') for i in range(6)) # The initial mapping looks like: # _|_0_|_1_|_2_|_3_|_4_| # 0|q0 | | | |q4 | # 1|q1 |q2 | |q3 |q5 | initial_mapping = { q[0]: cirq.GridQubit(0, 0), q[1]: cirq.GridQubit(1, 0), q[2]: cirq.GridQubit(1, 1), q[3]: cirq.GridQubit(1, 3), q[4]: cirq.GridQubit(0, 4), q[5]: cirq.GridQubit(1, 4), } # Here's the idea: # * there are two "clumps" of qubits: (q0,q1,q2) and (q3,q4,q5) # * the active gate(s) span the two clumps # * there are also gates on qubits within clumps # * intra-clump gate cost contribution outweighs inter-clump gate cost # In that case, we need to swap qubits away from their respective clumps in # order to progress beyond any of the active gates. But we never will # because doing so would increase the overall cost due to the intra-clump # contributions. In fact, no greedy algorithm would be able to make progress # in this case. circuit = cirq.Circuit() # Cross-clump active gates circuit.append( [cirq.CNOT(q[0], q[3]), cirq.CNOT(q[1], q[4]), cirq.CNOT(q[2], q[5])]) # Intra-clump q0,q1,q2 circuit.append( [cirq.CNOT(q[0], q[1]), cirq.CNOT(q[0], q[2]), cirq.CNOT(q[1], q[2])]) # Intra-clump q3,q4,q5 circuit.append( [cirq.CNOT(q[3], q[4]), cirq.CNOT(q[3], q[5]), cirq.CNOT(q[4], q[5])]) updater = SwapUpdater(circuit, grid_2x5, initial_mapping, lambda q1, q2: [cirq.SWAP(q1, q2)]) # Iterate until the SwapUpdater is finished or an assertion fails, keeping # track of the ops generated by the previous iteration. prev_it = list(updater.update_iteration()) while not updater.dlists.all_empty(): cur_it = list(updater.update_iteration()) # If the current iteration adds a SWAP, it better not be the same SWAP # as the previous iteration... # If we pick the same SWAP twice in a row, then we're going in a loop # forever without making any progress! def _is_swap(ops): return len(ops) == 1 and ops[0] == cirq.SWAP(*ops[0].qubits) if _is_swap(prev_it) and _is_swap(cur_it): assert set(prev_it[0].qubits) != set(cur_it[0].qubits) prev_it = cur_it
def test_decomposed_swaps(): Q = FIGURE_9A_PHYSICAL_QUBITS initial_mapping = dict(zip(q, Q)) updater = SwapUpdater(FIGURE_9A_CIRCUIT, Q, initial_mapping, generate_decomposed_swap) # First iteration adds a swap between Q0 and Q1. # generate_decomposed_swap decomposes that into sqrt-iswap operations. assert list(updater.update_iteration()) == list( generate_decomposed_swap(Q[0], Q[1])) # Whatever the decomposed operations are, they'd better be equivalent to a # SWAP. swap_unitary = cirq.unitary(cirq.Circuit(cirq.SWAP(Q[0], Q[1]))) generated_unitary = cirq.unitary( cirq.Circuit(generate_decomposed_swap(Q[0], Q[1]))) cirq.testing.assert_allclose_up_to_global_phase(swap_unitary, generated_unitary, atol=1e-8)
def test_example_9_iterations(): Q = FIGURE_9A_PHYSICAL_QUBITS initial_mapping = dict(zip(q, Q)) updater = SwapUpdater(FIGURE_9A_CIRCUIT, Q, initial_mapping, lambda q1, q2: [cirq.SWAP(q1, q2)]) # First iteration adds a swap between Q0 and Q1. assert list(updater.update_iteration()) == [cirq.SWAP(Q[0], Q[1])] # Next two iterations add the active gates as-is. assert list(updater.update_iteration()) == [cirq.CNOT(Q[1], Q[2])] assert list(updater.update_iteration()) == [cirq.CNOT(Q[5], Q[2])] # Next iteration adds a swap between Q1 and Q4. assert list(updater.update_iteration()) == [cirq.SWAP(Q[1], Q[4])] # Remaining gates are added as-is. assert list(updater.update_iteration()) == [cirq.CNOT(Q[4], Q[5])] assert list(updater.update_iteration()) == [cirq.CNOT(Q[1], Q[4])] assert list(updater.update_iteration()) == [cirq.CNOT(Q[4], Q[3])] # The final two gates are added in the same iteration, since they operate on # mutually exclusive qubits and are both simultaneously active. assert set(updater.update_iteration()) == { cirq.CNOT(Q[5], Q[4]), cirq.CNOT(Q[3], Q[0]) }