def _hardware_aware_reset( mapping: ty.Dict[Qubit, int], circuit: QuantumCircuit, hardware: IBMQHardwareArchitecture, ) -> ty.Dict[Qubit, int]: starting_qubit = random.randint(0, hardware.qubit_number - 1) qubits: ty.List[int] = [starting_qubit] weights: ty.Dict[int, float] = dict() while len(qubits) < len(mapping): # 1. Update the weights for neighbour in hardware.neighbors(qubits[-1]): if neighbour not in qubits: if neighbour not in weights.keys(): weights[neighbour] = 0.5 * ( 1 - hardware.get_qubit_readout_error(neighbour)) else: weights[neighbour] += ( weights.get(neighbour, 0) + 1 - hardware.get_link_error_rate(qubits[-1], neighbour)) # Find the best weighted qubit best_weight, best_qubit = 0, None for qubit, weight in weights.items(): if weight > best_weight: best_qubit = qubit best_weight = weight # Insert it in the qubit list qubits.append(best_qubit) del weights[best_qubit] # Finally, return a mapping with the chosen qubits return {qubit: idx for qubit, idx in zip(mapping.keys(), qubits)}
def get_all_swap_candidates( layer: QuantumLayer, hardware: IBMQHardwareArchitecture, current_mapping: ty.Dict[Qubit, int], explored_mappings: ty.Set[str], ) -> ty.List[SwapTwoQubitGate]: # First compute all the qubits involved in the given layer qubits_involved_in_front_layer = set() for op in layer.ops: qubits_involved_in_front_layer.update(op.qargs) inverse_mapping = {val: key for key, val in current_mapping.items()} # Then for all the possible links that involve at least one of the qubits used by # the gates in the given layer, add this link as a possible SWAP. all_swaps = list() for involved_qubit in qubits_involved_in_front_layer: qubit_index = current_mapping[involved_qubit] # For all the links that involve the current qubit. for source, sink in hardware.out_edges(qubit_index): two_qubit_gate = SwapTwoQubitGate(inverse_mapping[source], inverse_mapping[sink]) # Check that the mapping has not already been explored in this # SWAP-insertion pass. if (mapping_to_str(two_qubit_gate.update_mapping(current_mapping)) not in explored_mappings): all_swaps.append(two_qubit_gate) return all_swaps
def _hardware_aware_expand( mapping: ty.Dict[Qubit, int], circuit: QuantumCircuit, hardware: IBMQHardwareArchitecture, ) -> ty.Dict[Qubit, int]: qubits = list(mapping.values()) idle_qubits = {mapping[qubit] for qubit in _get_idle_qubits(circuit)} used_qubits = list(set(qubits) - idle_qubits) if not idle_qubits: return _random_shuffle(mapping, circuit, hardware) # Compute a weight for each qubit. # A qubit with a lot of links to other qubits in the mapping is good. # A qubit with bad links is not that good. weights: ty.List[float] = list() outside_qubits_weights = dict() for qubit in used_qubits: weights.append(0.5 * (1 - hardware.get_qubit_readout_error(qubit))) for neighbour in hardware.neighbors(qubit): # Only count the neighbour if it is also in the mapping. if neighbour in used_qubits: weights[-1] += 1 - hardware.get_link_error_rate( qubit, neighbour) # Else, we keep an eye on the qubits that are not in the mapping because # we will need the best of them to add it to the mapping. else: if neighbour not in outside_qubits_weights.keys(): outside_qubits_weights[neighbour] = 0.5 * ( 1 - hardware.get_qubit_readout_error(qubit)) else: outside_qubits_weights[neighbour] += ( outside_qubits_weights.get(neighbour, 0) + 1 - hardware.get_link_error_rate(qubit, neighbour)) worst_qubit_index = _argmin(weights) best_outside_qubit_index = None best_outside_weight = 0 for neighbour, weight in outside_qubits_weights.items(): if weight > best_outside_weight: best_outside_qubit_index = neighbour best_outside_weight = weight # Now exchange the 2 qubits inverse_mapping = {v: k for k, v in mapping.items()} inverse_mapping[worst_qubit_index], inverse_mapping[ best_outside_qubit_index] = ( inverse_mapping[best_outside_qubit_index], inverse_mapping[worst_qubit_index], ) return {v: k for k, v in inverse_mapping.items()}
def get_all_bridge_candidates( layer: QuantumLayer, hardware: IBMQHardwareArchitecture, initial_mapping: ty.Dict[Qubit, int], trans_mapping: ty.Dict[Qubit, int], current_mapping: ty.Dict[Qubit, int], explored_mappings: ty.Set[str], ) -> ty.List[BridgeTwoQubitGate]: all_bridges = [] inverse_trans_mapping = {val: key for key, val in trans_mapping.items()} inverse_mapping = {val: key for key, val in initial_mapping.items()} for op in layer.ops: if len(op.qargs) < 2: # We just pass 1 qubit gates because they do not participate in the # Bridge operation continue if len(op.qargs) != 2: logger.warning( "A 3-qubit or more gate has been found in the circuit.") continue control, target = op.qargs control_index = initial_mapping[inverse_trans_mapping[ initial_mapping[control]]] target_index = initial_mapping[inverse_trans_mapping[ initial_mapping[target]]] # For each qubit q linked with control, check if target is linked with q. for _, potential_middle_index in hardware.out_edges(control_index): for _, potential_target_index in hardware.out_edges( potential_middle_index): if potential_target_index == target_index: two_qubit_gate = BridgeTwoQubitGate( inverse_trans_mapping[initial_mapping[control]], inverse_mapping[potential_middle_index], inverse_trans_mapping[initial_mapping[target]], ) # Check that the mapping has not already been explored in this # SWAP-insertion pass. if (mapping_to_str( two_qubit_gate.update_mapping(current_mapping)) not in explored_mappings): all_bridges.append(two_qubit_gate) return all_bridges
def main(): parser = argparse.ArgumentParser( "Compare the Bridge+SWAP approach to the simple SWAP one.") parser.add_argument( "N", type=int, help= "Number of initial mapping that will be explored. Should be strictly " "over 1 (i.e. 2 or more).", ) parser.add_argument("circuit_name", type=str, help="Name of the quantum circuit to map.") parser.add_argument("hardware", type=str, help="Name of the hardware to consider.") args = parser.parse_args() N = args.N if N < 1: raise RuntimeError("N should be 2 or more.") hardware = IBMQHardwareArchitecture.load(args.hardware) circuit = add_qubits_to_quantum_circuit( read_benchmark_circuit("sabre", args.circuit_name), hardware) initial_mappings = [{ qubit: i for qubit, i in zip(circuit.qubits, permutation(range(hardware.qubit_number))) } for _ in range(N)] # using_swap_bridge_strategy_tup([circuit, hardware, initial_mappings[0]]) with ProcessPoolExecutor(max_workers=cpu_count()) as executor: best_swap_results, swap_timings = separate_lists( executor.map( using_only_swap_strategy_tup, zip( itertools.repeat(circuit, N), itertools.repeat(hardware, N), initial_mappings, ), )) print_statistics("SWAP", best_swap_results, swap_timings) best_swap_bridge_results, swap_bridge_timings = separate_lists( executor.map( using_swap_bridge_strategy_tup, zip( itertools.repeat(circuit, N), itertools.repeat(hardware, N), initial_mappings, ), )) print_statistics("SWAP+Bridge", best_swap_bridge_results, swap_bridge_timings)
def main(): N = 10 hardware = IBMQHardwareArchitecture.load("ibmq_16_melbourne") circuit = add_qubits_to_quantum_circuit( read_benchmark_circuit("sabre", "ising_model_10"), hardware ) initial_mapping = get_random_mapping(circuit) for i in range(1, N): print( f"Swap number {i}: {test_iterated_sabre(circuit, hardware, i, initial_mapping)}" )
def _gate_op_cost( op: DAGNode, distance_matrix: numpy.ndarray, mapping: ty.Dict[Qubit, int], hardware: IBMQHardwareArchitecture, ) -> float: if hardware.is_ignored_operation(op): return 0 if len(op.qargs) == 1: # SABRE ignores 1-qubit gates return 0 elif len(op.qargs) == 2: # This is a CNOT source, sink = op.qargs return distance_matrix[mapping[source], mapping[sink]] else: logger.warning( f"Found a quantum operation applied on '{len(op.qargs)}' qubits. This " f"operation will be excluded from the cost computation." ) return 0
def get_distance_matrix_error_cost( hardware: IBMQHardwareArchitecture) -> numpy.ndarray: hardware.weight_function = _get_swap_error_cost return nx.floyd_warshall_numpy(hardware)
def _get_swap_error_cost(node, hardware: IBMQHardwareArchitecture) -> float: source, sink = node cnot_fidelity = 1 - hardware.get_link_error_rate(source, sink) reversed_cnot_fidelity = 1 - hardware.get_link_error_rate(sink, source) return 1 - cnot_fidelity * reversed_cnot_fidelity * max( cnot_fidelity, reversed_cnot_fidelity)
def _get_swap_execution_time_cost(node, hardware: IBMQHardwareArchitecture) -> float: source, sink = node cnot_cost = hardware.get_link_execution_time(source, sink) reversed_cnot_cost = hardware.get_link_execution_time(sink, source) return cnot_cost + reversed_cnot_cost + min(cnot_cost, reversed_cnot_cost)
def main(): parser = argparse.ArgumentParser( "Compare the annealing method to pure random.") parser.add_argument( "N", type=int, help= "Number of allowed call to the mapping procedure. Should be strictly " "over 1 (i.e. 2 or more).", ) parser.add_argument("M", type=int, help="Number of repetitions for statistics.") parser.add_argument("Nstep", type=int, help="Steps used to increase N from step " "to N.") parser.add_argument("circuit_name", type=str, help="Name of the quantum circuit to map.") parser.add_argument("hardware", type=str, help="Name of the hardware to consider.") args = parser.parse_args() N = args.N if N <= 1: raise RuntimeError("N should be 2 or more.") M = args.M Nstep = args.Nstep hardware = IBMQHardwareArchitecture.load(args.hardware) circuit = add_qubits_to_quantum_circuit( read_benchmark_circuit("sabre", args.circuit_name), hardware) results = dict() with ProcessPoolExecutor(max_workers=cpu_count()) as executor: N_values = list(range(Nstep, N + 1, Nstep)) print("Computing random...") best_random_results, random_timings = separate_lists_all_values_of_n( executor.map( random_tuple_strategy_results, itertools.repeat([N, circuit, hardware, N_values], M), )) print("Computing SABRE...") best_sabre_results, sabre_timings = separate_lists_all_values_of_n( executor.map( sabre_tuple_strategy_results, itertools.repeat([N, circuit, hardware, N_values], M), )) for i, n in enumerate(N_values): print(f"Computing for {n}:") print("\tannealing...") best_annealing_results, annealing_timings = separate_lists( executor.map( annealing_tuple_strategy_results, itertools.repeat([n, circuit, hardware], M), )) # print_statistics("Annealing", best_annealing_results, annealing_timings) print("\tSABRE-annealing...") best_sabre_annealing_results, sabre_annealing_timings = separate_lists( executor.map( sabre_annealing_tuple_strategy_results, itertools.repeat([n, circuit, hardware], M), )) # print_statistics( # "SABRE + Annealing", # best_sabre_annealing_results, # sabre_annealing_timings, # ) print("\tforward-backward...") ( best_forward_backward_results, forward_backward_timings, ) = separate_lists( executor.map( forward_backward_tuple_strategy_results, itertools.repeat([n, circuit, hardware], M), )) # print_statistics( # "Forward-backward", # best_forward_backward_results, # forward_backward_timings, # ) print("\tforward-backward annealing...") ( best_forward_backward_annealing_results, forward_backward_annealing_timings, ) = separate_lists( executor.map( forward_backward_tuple_strategy_results, itertools.repeat([n, circuit, hardware], M), )) # print_statistics( # "Forward-backward + Annealing", # best_forward_backward_annealing_results, # forward_backward_annealing_timings, # ) print("\tforward-backward neighbour...") ( best_forward_backward_neighbour_results, forward_backward_neighbour_timings, ) = separate_lists( executor.map( forward_backward_neighbour_tuple_strategy_results, itertools.repeat([n, circuit, hardware], M), )) results[n] = { "random": { "results": best_random_results[i], "times": random_timings[i], }, "annealing": { "results": best_annealing_results, "times": annealing_timings, }, "sabre": { "results": best_sabre_results[i], "times": sabre_timings[i] }, "sabre_annealing": { "results": best_sabre_annealing_results, "times": sabre_annealing_timings, }, "iterated": { "results": best_forward_backward_results, "times": forward_backward_timings, }, "iterated_annealing": { "results": best_forward_backward_annealing_results, "times": forward_backward_annealing_timings, }, "iterated_neighbour": { "results": best_forward_backward_neighbour_results, "times": forward_backward_neighbour_timings, }, } with open( f"results-{N}-{Nstep}-{M}-{args.circuit_name}-{args.hardware}.pkl", "wb") as f: pickle.dump(results, f)
def main(): parser = argparse.ArgumentParser( "Compare the annealing method to pure random.") parser.add_argument( "N", type=int, help= "Number of allowed call to the mapping procedure. Should be strictly " "over 1 (i.e. 2 or more).", ) parser.add_argument("M", type=int, help="Number of repetitions for statistics.") parser.add_argument("Nstep", type=int, help="Steps used to increase N from step to N.") parser.add_argument("circuit_name", type=str, help="Name of the quantum circuit to map.") parser.add_argument("hardware", type=str, help="Name of the hardware to consider.") args = parser.parse_args() N = args.N if N <= 1: raise RuntimeError("N should be 2 or more.") M = args.M Nstep = args.Nstep hardware = IBMQHardwareArchitecture.load(args.hardware) circuit = add_qubits_to_quantum_circuit( read_benchmark_circuit("sabre", args.circuit_name), hardware) results = dict() N_values = list(range(Nstep, N + 1, Nstep)) with ProcessPoolExecutor(max_workers=cpu_count()) as executor: for i, n in enumerate(N_values): print(f"Computing for {n}:") print("\tRandom...") best_random_results = list( executor.map( random_tuple_strategy_results, itertools.repeat([circuit, hardware, n], M), )) print("\tSABRE...") best_sabre_results = list( executor.map( sabre_tuple_strategy_results, itertools.repeat([circuit, hardware, n], M), )) print("\tAnnealing random...") best_annealing_random_results = list( executor.map( annealing_random_tuple_strategy_results, itertools.repeat([circuit, hardware, n], M), )) print("\tAnnealing hardware...") best_annealing_hardware_results = list( executor.map( annealing_hardware_aware_tuple_strategy_results, itertools.repeat([circuit, hardware, n], M), )) results[n] = { "random": { "results": deepcopy(best_random_results) }, "annealing_random": { "results": deepcopy(best_annealing_random_results) }, "sabre": { "results": deepcopy(best_sabre_results) }, "annealing_hardware": { "results": deepcopy(best_annealing_hardware_results) }, } print( f"Saving to results-{N}-{Nstep}-{M}-{args.circuit_name}-{args.hardware}.pkl" ) with open( f"results-{N}-{Nstep}-{M}-{args.circuit_name}-{args.hardware}.pkl", "wb") as f: pickle.dump(results, f)
def ha_mapping( quantum_circuit: QuantumCircuit, initial_mapping: ty.Dict[Qubit, int], hardware: IBMQHardwareArchitecture, swap_cost_heuristic: ty.Callable[ [ IBMQHardwareArchitecture, # Hardware information QuantumLayer, # Current front layer ty.List[DAGNode], # Topologically sorted list of nodes int, # Index of the first non-processed gate. ty.Dict[Qubit, int], # The mapping before applying the tested SWAP/Bridge ty.Dict[Qubit, int], # The initial mapping ty.Dict[Qubit, int], # The trans mapping numpy.ndarray, # The distance matrix between each qubits TwoQubitGate, # The SWAP/Bridge we want to rank ], float, ] = sabre_heuristic, get_candidates: ty.Callable[ [QuantumLayer, IBMQHardwareArchitecture, ty.Dict[Qubit, int], ty.Dict[Qubit, int], ty.Dict[Qubit, int], ty.Set[str],], ty.List[TwoQubitGate], ] = get_all_swap_bridge_candidates, get_distance_matrix: ty.Callable[ [IBMQHardwareArchitecture], numpy.ndarray ] = get_distance_matrix_swap_number_and_error, ) -> ty.Tuple[QuantumCircuit, ty.Dict[Qubit, int]]: """Map the given quantum circuit to the hardware topology provided. :param quantum_circuit: the quantum circuit to map. :param initial_mapping: the initial mapping used to start the iterative mapping algorithm. :param hardware: hardware data such as connectivity, gate time, gate errors, ... :param swap_cost_heuristic: the heuristic cost function that will estimate the cost of a given SWAP/Bridge according to the current state of the circuit. :param get_candidates: a function that takes as input the current front layer and the hardware description and that returns a list of tuples representing the SWAP/Bridge that should be considered by the heuristic. :param get_distance_matrix: a function that takes as first (and only) parameter the hardware representation and that outputs a numpy array containing the cost of performing a SWAP between each pair of qubits. :return: The final circuit along with the mapping obtained at the end of the iterative procedure. """ _adapt_quantum_circuit_and_mapping_arity(quantum_circuit, initial_mapping, hardware) # Creating the internal data structures that will be used in this function. dag_circuit = circuit_to_dag(quantum_circuit) distance_matrix = get_distance_matrix(hardware) resulting_dag_quantum_circuit = _create_empty_dagcircuit_from_existing(dag_circuit) current_mapping = initial_mapping explored_mappings: ty.Set[str] = set() # Sorting all the quantum operations in topological order once for all. # May require significant memory on large circuits... topological_nodes: ty.List[DAGNode] = list(dag_circuit.topological_op_nodes()) current_node_index = 0 # Creating the initial front layer. front_layer = QuantumLayer() current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) trans_mapping = initial_mapping.copy() # Start of the iterative algorithm while not front_layer.is_empty(): execute_gate_list = QuantumLayer() for op in front_layer.ops: if hardware.can_natively_execute_operation(op, current_mapping): execute_gate_list.add_operation(op) # Delaying the remove operation because we do not want to remove from # a container we are iterating on. # front_layer.remove_operation(op) if not execute_gate_list.is_empty(): front_layer.remove_operations_from_layer(execute_gate_list) execute_gate_list.apply_back_to_dag_circuit( resulting_dag_quantum_circuit, initial_mapping, trans_mapping ) # Empty the explored mappings because at least one gate has been executed. explored_mappings.clear() else: inverse_mapping = {val: key for key, val in initial_mapping.items()} # We cannot execute any gate, that means that we should insert at least # one SWAP/Bridge to make some gates executable. # First list all the SWAPs/Bridges that may help us make some gates # executable. swap_candidates = get_candidates( front_layer, hardware, initial_mapping, current_mapping, trans_mapping, explored_mappings ) # Then rank the SWAPs/Bridge and take the best one. best_swap_qubits = None best_cost = float("inf") for potential_swap in swap_candidates: cost = swap_cost_heuristic( hardware, front_layer, topological_nodes, current_node_index, current_mapping, initial_mapping, trans_mapping, distance_matrix, potential_swap, ) if cost < best_cost: best_cost = cost best_swap_qubits = potential_swap # We now have our best SWAP/Bridge, let's perform it! current_mapping = best_swap_qubits.update_mapping(current_mapping) if isinstance(best_swap_qubits, SwapTwoQubitGate): control, target = current_mapping[best_swap_qubits.left], current_mapping[best_swap_qubits.right] swap_control, swap_target = inverse_mapping[control], inverse_mapping[target] best_swap_qubits = SwapTwoQubitGate( swap_control, swap_target ) #print("swap gates is :", best_swap_qubits.left, best_swap_qubits.right) trans_mapping[best_swap_qubits.left], trans_mapping[best_swap_qubits.right] = ( trans_mapping[best_swap_qubits.right], trans_mapping[best_swap_qubits.left], ) else: #print("brige gate is :", best_swap_qubits.left, best_swap_qubits.middle, best_swap_qubits.right) pass explored_mappings.add(mapping_to_str(current_mapping)) best_swap_qubits.apply(resulting_dag_quantum_circuit, front_layer, initial_mapping, trans_mapping) # Anyway, update the current front_layer current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) # We are done here, we just need to return the results # resulting_dag_quantum_circuit.draw(scale=1, filename="qcirc.dot") resulting_circuit = dag_to_circuit(resulting_dag_quantum_circuit) return resulting_circuit, current_mapping
def ha_mapping_paper_compliant( quantum_circuit: QuantumCircuit, initial_mapping: ty.Dict[Qubit, int], hardware: IBMQHardwareArchitecture, swap_cost_and_effect_heuristic: ty.Callable[ [ IBMQHardwareArchitecture, # Hardware information QuantumLayer, # Current front layer ty.List[DAGNode], # Topologically sorted list of nodes int, # Index of the first non-processed gate. ty.Dict[Qubit, int], # The mapping before applying the tested SWAP/Bridge ty.Dict[Qubit, int], # The initial mapping ty.Dict[Qubit, int], # The trans mapping numpy.ndarray, # The distance matrix between each qubits TwoQubitGate, # The SWAP/Bridge we want to rank ], ty.Tuple[ float, # Cost of the SWAP pair float, # Effect of the SWAP pair on the other gates ], ] = sabre_heuristic_with_effect, get_candidates: ty.Callable[ [QuantumLayer, IBMQHardwareArchitecture, ty.Dict[Qubit, int], ty.Set[str],], ty.List[SwapTwoQubitGate], ] = get_all_swap_candidates, get_distance_matrix: ty.Callable[ [IBMQHardwareArchitecture], numpy.ndarray ] = get_distance_matrix_swap_number_and_error, ) -> ty.Tuple[QuantumCircuit, ty.Dict[Qubit, int]]: """Map the given quantum circuit to the hardware topology provided. This implementation uses the exact same algorithm described in the associated scientific paper. Another implementation using a different method to choose between SWAP and Bridge is available as `:py:func:`~hamap.mapping.ha_mapping`. :param quantum_circuit: the quantum circuit to map. :param initial_mapping: the initial mapping used to start the iterative mapping algorithm. :param hardware: hardware data such as connectivity, gate time, gate errors, ... :param swap_cost_and_effect_heuristic: the heuristic cost function that will estimate the cost of a given SWAP according to the current state of the circuit and the effect of the SWAP on the following quantum gates. The two floats should be returned as a tuple (cost, effect). :param get_candidates: a function that takes as input the current front layer and the hardware description and that returns a list of tuples representing the SWAP that should be considered by the heuristic. :param get_distance_matrix: a function that takes as first (and only) parameter the hardware representation and that outputs a numpy array containing the cost of performing a SWAP between each pair of qubits. :return: The final circuit along with the mapping obtained at the end of the iterative procedure. """ _adapt_quantum_circuit_and_mapping_arity(quantum_circuit, initial_mapping, hardware) # Creating the internal data structures that will be used in this function. dag_circuit = circuit_to_dag(quantum_circuit) distance_matrix = get_distance_matrix(hardware) resulting_dag_quantum_circuit = _create_empty_dagcircuit_from_existing(dag_circuit) current_mapping = initial_mapping explored_mappings: ty.Set[str] = set() # Sorting all the quantum operations in topological order once for all. # May require significant memory on large circuits... topological_nodes: ty.List[DAGNode] = list(dag_circuit.topological_op_nodes()) current_node_index = 0 # Creating the initial front layer. front_layer = QuantumLayer() current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) swap_distance_matrix = get_distance_matrix_swap_number(hardware) trans_mapping = initial_mapping.copy() # Start of the iterative algorithm while not front_layer.is_empty(): execute_gate_list = QuantumLayer() for op in front_layer.ops: if hardware.can_natively_execute_operation(op, current_mapping): execute_gate_list.add_operation(op) # Delaying the remove operation because we do not want to remove from # a container we are iterating on. # front_layer.remove_operation(op) if not execute_gate_list.is_empty(): front_layer.remove_operations_from_layer(execute_gate_list) execute_gate_list.apply_back_to_dag_circuit( resulting_dag_quantum_circuit, initial_mapping, trans_mapping ) # Empty the explored mappings because at least one gate has been executed. explored_mappings.clear() else: # We cannot execute any gate, that means that we should insert at least # one SWAP/Bridge to make some gates executable. # First list all the SWAPs/Bridges that may help us make some gates # executable. inverse_mapping = {val: key for key, val in initial_mapping.items()} swap_candidates = get_candidates( front_layer, hardware, current_mapping, explored_mappings ) # Then rank the SWAPs/Bridge and take the best one. best_swap_qubits = None best_cost = float("inf") best_effect = 0.0 for potential_swap in swap_candidates: cost, swap_effect = swap_cost_and_effect_heuristic( hardware, front_layer, topological_nodes, current_node_index, current_mapping, initial_mapping, trans_mapping, distance_matrix, potential_swap, ) if cost < best_cost: best_cost = cost best_effect = swap_effect best_swap_qubits = potential_swap # We now have our best SWAP, let's check if a Bridge is not better # if ( # best_effect < 0 # and swap_distance_matrix[best_swap_qubits.left][best_swap_qubits.right] # == 2 # ): # i, j = best_swap_qubits # common_neighbours = set(hardware.neighbors(i)) & set( # hardware.neighbors(j) # ) # if len(common_neighbours) < 1: # raise RuntimeError("Less than one common neighbour") # common_neighbour = list(common_neighbours)[0] # best_gate = BridgeTwoQubitGate(i, common_neighbour, j) if best_effect < 0: inverse_trans_mapping = {val: key for key, val in trans_mapping.items()} for op in front_layer.ops: if len(op.qargs) < 2: # We just pass 1 qubit gates because they do not participate in the # Bridge operation continue if len(op.qargs) != 2: logger.warning("A 3-qubit or more gate has been found in the circuit.") continue control, target = op.qargs control_index = initial_mapping[inverse_trans_mapping[initial_mapping[control]]] target_index = initial_mapping[inverse_trans_mapping[initial_mapping[target]]] for _, potential_middle_index in hardware.out_edges(control_index): for _, potential_target_index in hardware.out_edges(potential_middle_index): if potential_target_index == target_index: best_swap_qubits = BridgeTwoQubitGate( inverse_trans_mapping[initial_mapping[control]], inverse_mapping[potential_middle_index], inverse_trans_mapping[initial_mapping[target]], ) current_mapping = best_swap_qubits.update_mapping(current_mapping) if isinstance(best_swap_qubits, SwapTwoQubitGate): control, target = current_mapping[best_swap_qubits.left], current_mapping[best_swap_qubits.right] swap_control, swap_target = inverse_mapping[control], inverse_mapping[target] best_swap_qubits = SwapTwoQubitGate( swap_control, swap_target ) #print("swap gates is :", best_swap_qubits.left, best_swap_qubits.right) trans_mapping[best_swap_qubits.left], trans_mapping[best_swap_qubits.right] = ( trans_mapping[best_swap_qubits.right], trans_mapping[best_swap_qubits.left], ) else: #print("brige gate is :", best_swap_qubits.left, best_swap_qubits.middle, best_swap_qubits.right) pass explored_mappings.add(mapping_to_str(current_mapping)) best_swap_qubits.apply(resulting_dag_quantum_circuit, front_layer, initial_mapping, trans_mapping) # Anyway, update the current front_layer current_node_index = update_layer( front_layer, topological_nodes, current_node_index ) # We are done here, we just need to return the results # resulting_dag_quantum_circuit.draw(scale=1, filename="qcirc.dot") resulting_circuit = dag_to_circuit(resulting_dag_quantum_circuit) return resulting_circuit, current_mapping