def _swap_ops_from_edge(edge, layout): """Generate list of ops to implement a SWAP gate along a coupling edge.""" device_qreg = QuantumRegister(len(layout.get_physical_bits()), 'q') qreg_edge = [device_qreg[i] for i in edge] # TODO shouldn't be making other nodes not by the DAG!! return [DAGNode(op=SwapGate(), qargs=qreg_edge, cargs=[], type='op')]
def _swap_ops_from_edge(edge, layout): """Generate list of ops to implement a SWAP gate along a coupling edge.""" device_qreg = QuantumRegister(len(layout.get_physical_bits()), 'q') qreg_edge = [(device_qreg, i) for i in edge] # TODO shouldn't be making other nodes not by the DAG!! return [ DAGNode({'op': SwapGate(*qreg_edge), 'qargs': qreg_edge, 'type': 'op'}) ]
def test_is_identity(self): """ The is_identity function determines whether a pair of gates forms the identity, when ignoring control qubits. """ seq = [ DAGNode({ 'type': 'op', 'op': XGate().control() }), DAGNode({ 'type': 'op', 'op': XGate().control(2) }) ] self.assertTrue(HoareOptimizer()._is_identity(seq)) seq = [ DAGNode({ 'type': 'op', 'op': RZGate(-pi / 2).control() }), DAGNode({ 'type': 'op', 'op': RZGate(pi / 2).control(2) }) ] self.assertTrue(HoareOptimizer()._is_identity(seq)) seq = [ DAGNode({ 'type': 'op', 'op': CSwapGate() }), DAGNode({ 'type': 'op', 'op': SwapGate() }) ] self.assertTrue(HoareOptimizer()._is_identity(seq)) seq = [ DAGNode({ 'type': 'op', 'op': UnitaryGate([[1, 0], [0, 1j]]).control() }), DAGNode({ 'type': 'op', 'op': UnitaryGate([[1, 0], [0, -1j]]) }) ]
def test_is_identity(self): """The is_identity function determines whether a pair of gates forms the identity, when ignoring control qubits. """ seq = [ DAGNode(type="op", op=XGate().control()), DAGNode(type="op", op=XGate().control(2)) ] self.assertTrue(HoareOptimizer()._is_identity(seq)) seq = [ DAGNode(type="op", op=RZGate(-pi / 2).control()), DAGNode(type="op", op=RZGate(pi / 2).control(2)), ] self.assertTrue(HoareOptimizer()._is_identity(seq)) seq = [ DAGNode(type="op", op=CSwapGate()), DAGNode(type="op", op=SwapGate()) ] self.assertTrue(HoareOptimizer()._is_identity(seq))
def run(self, dag): """Run the SabreSwap pass on `dag`. Args: dag (DAGCircuit): the directed acyclic graph to be mapped. Returns: DAGCircuit: A dag mapped to be compatible with the coupling_map. Raises: TranspilerError: if the coupling map or the layout are not compatible with the DAG """ if len(dag.qregs) != 1 or dag.qregs.get('q', None) is None: raise TranspilerError('Sabre swap runs on physical circuits only.') if len(dag.qubits) > self.coupling_map.size(): raise TranspilerError('More virtual qubits exist than physical.') rng = np.random.default_rng(self.seed) # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. mapped_dag = dag._copy_circuit_metadata() # Assume bidirectional couplings, fixing gate direction is easy later. self.coupling_map.make_symmetric() canonical_register = dag.qregs['q'] current_layout = Layout.generate_trivial_layout(canonical_register) # A decay factor for each qubit used to heuristically penalize recently # used qubits (to encourage parallelism). self.qubits_decay = {qubit: 1 for qubit in dag.qubits} # Start algorithm from the front layer and iterate until all gates done. num_search_steps = 0 front_layer = dag.front_layer() self.applied_gates = set() while front_layer: execute_gate_list = [] # Remove as many immediately applicable gates as possible for node in front_layer: if len(node.qargs) == 2: v0, v1 = node.qargs physical_qubits = (current_layout[v0], current_layout[v1]) if physical_qubits in self.coupling_map.get_edges(): execute_gate_list.append(node) else: # Single-qubit gates as well as barriers are free execute_gate_list.append(node) if execute_gate_list: for node in execute_gate_list: new_node = _transform_gate_for_layout(node, current_layout) mapped_dag.apply_operation_back(new_node.op, new_node.qargs, new_node.cargs, new_node.condition) front_layer.remove(node) self.applied_gates.add(node) for successor in dag.quantum_successors(node): if successor.type != 'op': continue if self._is_resolved(successor, dag): front_layer.append(successor) if node.qargs: self._reset_qubits_decay() # Diagnostics logger.debug('free! %s', [(n.name, n.qargs) for n in execute_gate_list]) logger.debug('front_layer: %s', [(n.name, n.qargs) for n in front_layer]) continue # After all free gates are exhausted, heuristically find # the best swap and insert it. When two or more swaps tie # for best score, pick one randomly. extended_set = self._obtain_extended_set(dag, front_layer) swap_candidates = self._obtain_swaps(front_layer, current_layout) swap_scores = dict.fromkeys(swap_candidates, 0) for swap_qubits in swap_scores: trial_layout = current_layout.copy() trial_layout.swap(*swap_qubits) score = self._score_heuristic(self.heuristic, front_layer, extended_set, trial_layout, swap_qubits) swap_scores[swap_qubits] = score min_score = min(swap_scores.values()) best_swaps = [k for k, v in swap_scores.items() if v == min_score] best_swaps.sort(key=lambda x: (x[0].index, x[1].index)) best_swap = rng.choice(best_swaps) swap_node = DAGNode(op=SwapGate(), qargs=best_swap, type='op') swap_node = _transform_gate_for_layout(swap_node, current_layout) mapped_dag.apply_operation_back(swap_node.op, swap_node.qargs) current_layout.swap(*best_swap) num_search_steps += 1 if num_search_steps % DECAY_RESET_INTERVAL == 0: self._reset_qubits_decay() else: self.qubits_decay[best_swap[0]] += DECAY_RATE self.qubits_decay[best_swap[1]] += DECAY_RATE # Diagnostics logger.debug('SWAP Selection...') logger.debug('extended_set: %s', [(n.name, n.qargs) for n in extended_set]) logger.debug('swap scores: %s', swap_scores) logger.debug('best swap: %s', best_swap) logger.debug('qubits decay: %s', self.qubits_decay) self.property_set['final_layout'] = current_layout return mapped_dag
def run(self, dag): """Run the SabreSwap pass on `dag`. Args: dag (DAGCircuit): the directed acyclic graph to be mapped. Returns: DAGCircuit: A dag mapped to be compatible with the coupling_map. Raises: TranspilerError: if the coupling map or the layout are not compatible with the DAG """ if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("Sabre swap runs on physical circuits only.") if len(dag.qubits) > self.coupling_map.size(): raise TranspilerError("More virtual qubits exist than physical.") rng = np.random.default_rng(self.seed) # Preserve input DAG's name, regs, wire_map, etc. but replace the graph. mapped_dag = None if not self.fake_run: mapped_dag = dag._copy_circuit_metadata() canonical_register = dag.qregs["q"] current_layout = Layout.generate_trivial_layout(canonical_register) self._bit_indices = { bit: idx for idx, bit in enumerate(canonical_register) } # A decay factor for each qubit used to heuristically penalize recently # used qubits (to encourage parallelism). self.qubits_decay = {qubit: 1 for qubit in dag.qubits} # Start algorithm from the front layer and iterate until all gates done. num_search_steps = 0 front_layer = dag.front_layer() self.applied_predecessors = defaultdict(int) for _, input_node in dag.input_map.items(): for successor in self._successors(input_node, dag): self.applied_predecessors[successor] += 1 while front_layer: execute_gate_list = [] # Remove as many immediately applicable gates as possible for node in front_layer: if len(node.qargs) == 2: v0, v1 = node.qargs if self.coupling_map.graph.has_edge( current_layout[v0], current_layout[v1]): execute_gate_list.append(node) else: # Single-qubit gates as well as barriers are free execute_gate_list.append(node) if execute_gate_list: for node in execute_gate_list: self._apply_gate(mapped_dag, node, current_layout, canonical_register) front_layer.remove(node) for successor in self._successors(node, dag): self.applied_predecessors[successor] += 1 if self._is_resolved(successor): front_layer.append(successor) if node.qargs: self._reset_qubits_decay() # Diagnostics logger.debug("free! %s", [(n.name, n.qargs) for n in execute_gate_list]) logger.debug("front_layer: %s", [(n.name, n.qargs) for n in front_layer]) continue # After all free gates are exhausted, heuristically find # the best swap and insert it. When two or more swaps tie # for best score, pick one randomly. extended_set = self._obtain_extended_set(dag, front_layer) swap_candidates = self._obtain_swaps(front_layer, current_layout) swap_scores = dict.fromkeys(swap_candidates, 0) for swap_qubits in swap_scores: trial_layout = current_layout.copy() trial_layout.swap(*swap_qubits) score = self._score_heuristic(self.heuristic, front_layer, extended_set, trial_layout, swap_qubits) swap_scores[swap_qubits] = score min_score = min(swap_scores.values()) best_swaps = [k for k, v in swap_scores.items() if v == min_score] best_swaps.sort(key=lambda x: (self._bit_indices[x[0]], self._bit_indices[x[1]])) best_swap = rng.choice(best_swaps) swap_node = DAGNode(op=SwapGate(), qargs=best_swap, type="op") self._apply_gate(mapped_dag, swap_node, current_layout, canonical_register) current_layout.swap(*best_swap) num_search_steps += 1 if num_search_steps % DECAY_RESET_INTERVAL == 0: self._reset_qubits_decay() else: self.qubits_decay[best_swap[0]] += DECAY_RATE self.qubits_decay[best_swap[1]] += DECAY_RATE # Diagnostics logger.debug("SWAP Selection...") logger.debug("extended_set: %s", [(n.name, n.qargs) for n in extended_set]) logger.debug("swap scores: %s", swap_scores) logger.debug("best swap: %s", best_swap) logger.debug("qubits decay: %s", self.qubits_decay) self.property_set["final_layout"] = current_layout if not self.fake_run: return mapped_dag return dag