def _score_heuristic(self, heuristic, front_layer, extended_set, layout, swap_qubits=None): """Return a heuristic score for a trial layout. Assuming a trial layout has resulted from a SWAP, we now assign a cost to it. The goodness of a layout is evaluated based on how viable it makes the remaining virtual gates that must be applied. """ if heuristic == 'basic': if len(front_layer) > 1: return self.coupling_map.distance_matrix[tuple( zip(*[[layout[q] for q in node.qargs] for node in front_layer]))].sum() elif len(front_layer) == 1: return self.coupling_map.distance( *[layout[q] for q in list(front_layer)[0].qargs]) else: return 0 elif heuristic == 'lookahead': first_cost = self._score_heuristic('basic', front_layer, [], layout) first_cost /= len(front_layer) second_cost = self._score_heuristic('basic', extended_set, [], layout) second_cost = 0.0 if not extended_set else second_cost / len( extended_set) return first_cost + EXTENDED_SET_WEIGHT * second_cost elif heuristic == 'decay': return max(self.qubits_decay[swap_qubits[0]], self.qubits_decay[swap_qubits[1]]) * \ self._score_heuristic('lookahead', front_layer, extended_set, layout) else: raise TranspilerError('Heuristic %s not recognized.' % heuristic)
def transpile_circuit(circuit, transpile_config): """Select a PassManager and run a single circuit through it. Args: circuit (QuantumCircuit): circuit to transpile transpile_config (TranspileConfig): configuration dictating how to transpile Returns: QuantumCircuit: transpiled circuit Raises: TranspilerError: if transpile_config is not valid or transpilation incurs error """ # if the pass manager is not already selected, choose an appropriate one. if transpile_config.pass_manager: pass_manager = transpile_config.pass_manager elif transpile_config.optimization_level is not None: level = transpile_config.optimization_level if level == 0: pass_manager = level_0_pass_manager(transpile_config) elif level == 1: pass_manager = level_1_pass_manager(transpile_config) elif level == 2: pass_manager = level_2_pass_manager(transpile_config) elif level == 3: pass_manager = level_3_pass_manager(transpile_config) else: raise TranspilerError("optimization_level can range from 0 to 3.") # legacy behavior elif transpile_config.coupling_map: pass_manager = level_1_pass_manager(transpile_config) else: pass_manager = default_pass_manager_simulator(transpile_config) # Set a callback on the pass manager if it's set if getattr(transpile_config, 'callback', None): pass_manager.callback = transpile_config.callback return pass_manager.run(circuit)
def _check_circuits_coupling_map(circuits, transpile_args, backend): # Check circuit width against number of qubits in coupling_map(s) coupling_maps_list = list(config['pass_manager_config'].coupling_map for config in transpile_args) for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list): # If coupling_map is not None or num_qubits == 1 num_qubits = len(circuit.qubits) max_qubits = None if isinstance(parsed_coupling_map, CouplingMap): max_qubits = parsed_coupling_map.size() # If coupling_map is None, the limit might be in the backend (like in 1Q devices) elif backend is not None and not backend.configuration().simulator: max_qubits = backend.configuration().n_qubits if max_qubits is not None and (num_qubits > max_qubits): raise TranspilerError( 'Number of qubits ({}) '.format(num_qubits) + 'in {} '.format(circuit.name) + 'is greater than maximum ({}) '.format(max_qubits) + 'in the coupling_map')
def run(self, dag): """ Main scheduling function """ if not HAS_Z3: raise TranspilerError('z3-solver is required to use CrosstalkAdaptiveSchedule') self.dag = dag # process input program self.assign_gate_id(self.dag) self.extract_dag_overlap_sets(self.dag) self.extract_crosstalk_relevant_sets() # setup and solve a Z3 optimization z3_result = self.solve_optimization() # post-process to insert barriers new_dag = self.enforce_schedule_on_dag(z3_result) self.reset() return new_dag
def run(self, dag): """Main run method for the noise adaptive layout.""" self._initialize_backend_prop() num_qubits = self._create_program_graph(dag) if num_qubits > len(self.swap_graph): raise TranspilerError('Number of qubits greater than device.') for end1, end2, _ in sorted(self.prog_graph.edges(data=True), key=lambda x: x[2]['weight'], reverse=True): self.pending_program_edges.append((end1, end2)) while self.pending_program_edges: edge = self._select_next_edge() q1_mapped = edge[0] in self.prog2hw q2_mapped = edge[1] in self.prog2hw if (not q1_mapped) and (not q2_mapped): best_hw_edge = self._select_best_remaining_cx() self.prog2hw[edge[0]] = best_hw_edge[0] self.prog2hw[edge[1]] = best_hw_edge[1] self.available_hw_qubits.remove(best_hw_edge[0]) self.available_hw_qubits.remove(best_hw_edge[1]) elif not q1_mapped: best_hw_qubit = self._select_best_remaining_qubit(edge[0]) self.prog2hw[edge[0]] = best_hw_qubit self.available_hw_qubits.remove(best_hw_qubit) else: best_hw_qubit = self._select_best_remaining_qubit(edge[1]) self.prog2hw[edge[1]] = best_hw_qubit self.available_hw_qubits.remove(best_hw_qubit) new_edges = [x for x in self.pending_program_edges if not (x[0] in self.prog2hw and x[1] in self.prog2hw)] self.pending_program_edges = new_edges for qid in self.qarg_to_id.values(): if qid not in self.prog2hw: self.prog2hw[qid] = self.available_hw_qubits[0] self.available_hw_qubits.remove(self.prog2hw[qid]) layout = Layout() for q in dag.qubits(): pid = self._qarg_to_id(q) hwid = self.prog2hw[pid] layout[q] = hwid self.property_set['layout'] = layout
def __init__( self, granularity: int = 1, min_length: int = 1, pulse_alignment: int = 1, acquire_alignment: int = 1, ): """Initialize a TimingConstraints object Args: granularity: An integer value representing minimum pulse gate resolution in units of ``dt``. A user-defined pulse gate should have duration of a multiple of this granularity value. min_length: An integer value representing minimum pulse gate length in units of ``dt``. A user-defined pulse gate should be longer than this length. pulse_alignment: An integer value representing a time resolution of gate instruction starting time. Gate instruction should start at time which is a multiple of the alignment value. acquire_alignment: An integer value representing a time resolution of measure instruction starting time. Measure instruction should start at time which is a multiple of the alignment value. Notes: This information will be provided by the backend configuration. Raises: TranspilerError: When any invalid constraint value is passed. """ self.granularity = granularity self.min_length = min_length self.pulse_alignment = pulse_alignment self.acquire_alignment = acquire_alignment for key, value in self.__dict__.items(): if not isinstance(value, int) or value < 1: raise TranspilerError( f"Timing constraint {key} should be nonzero integer. Not {value}." )
def run(self, dag): """ Pick a convenient layout depending on the best matching qubit connectivity, and set the property `layout`. Args: dag (DAGCircuit): DAG to find layout for. Raises: TranspilerError: if dag wider than self.coupling_map """ num_dag_qubits = sum([qreg.size for qreg in dag.qregs.values()]) if num_dag_qubits > self.coupling_map.size(): raise TranspilerError('Number of qubits greater than device.') best_sub = self._best_subset(num_dag_qubits) layout = Layout() map_iter = 0 for qreg in dag.qregs.values(): for i in range(qreg.size): layout[(qreg, i)] = int(best_sub[map_iter]) map_iter += 1 self.property_set['layout'] = layout
def get(self, inst: Union[str, Instruction], qubits: Union[int, List[int], Qubit, List[Qubit]], unit: str = 'dt') -> Union[float, int]: """Get the duration of the instruction with the name and the qubits. Args: inst: An instruction or its name to be queried. qubits: Qubits or its indices that the instruction acts on. unit: The unit of duration to be returned. It must be 's' or 'dt'. Returns: float|int: The duration of the instruction on the qubits. Raises: TranspilerError: No duration is defined for the instruction. """ if isinstance(inst, Barrier): return 0 elif isinstance(inst, Delay): return self._convert_unit(inst.duration, inst.unit, unit) if isinstance(inst, Instruction): inst_name = inst.name else: inst_name = inst if isinstance(qubits, (int, Qubit)): qubits = [qubits] if isinstance(qubits[0], Qubit): qubits = [q.index for q in qubits] try: return self._get(inst_name, qubits, unit) except TranspilerError: raise TranspilerError( "Duration of {} on qubits {} is not found.".format( inst_name, qubits))
def transpile_circuit(circuit, transpile_config): """Select a PassManager and run a single circuit through it. Args: circuit (QuantumCircuit): circuit to transpile transpile_config (TranspileConfig): configuration dictating how to transpile Returns: QuantumCircuit: transpiled circuit Raises: TranspilerError: if transpile_config is not valid or transpilation incurs error """ # either the pass manager is already selected... if transpile_config.pass_manager: pass_manager = transpile_config.pass_manager # or we choose an appropriate one based on desired optimization level (default: level 1) else: level = transpile_config.optimization_level if level is None: level = 1 if level == 0: pass_manager = level_0_pass_manager(transpile_config) elif level == 1: pass_manager = level_1_pass_manager(transpile_config) elif level == 2: pass_manager = level_2_pass_manager(transpile_config) elif level == 3: pass_manager = level_3_pass_manager(transpile_config) else: raise TranspilerError("optimization_level can range from 0 to 3.") # Set a callback on the pass manager there is one if getattr(transpile_config, 'callback', None): pass_manager.callback = transpile_config.callback return pass_manager.run(circuit)
def replace_node(node, instr_map): target_params, target_dag = instr_map[node.op.name, node.op.num_qubits] if len(node.op.params) != len(target_params): raise TranspilerError( "Translation num_params not equal to op num_params." "Op: {} {} Translation: {}\n{}".format( node.op.params, node.op.name, target_params, target_dag ) ) if node.op.params: # Convert target to circ and back to assign_parameters, since # DAGCircuits won't have a ParameterTable. from qiskit.converters import dag_to_circuit, circuit_to_dag target_circuit = dag_to_circuit(target_dag) target_circuit.assign_parameters( dict(zip_longest(target_params, node.op.params)), inplace=True ) bound_target_dag = circuit_to_dag(target_circuit) else: bound_target_dag = target_dag if len(bound_target_dag.op_nodes()) == 1 and len( bound_target_dag.op_nodes()[0].qargs ) == len(node.qargs): dag_op = bound_target_dag.op_nodes()[0].op # dag_op may be the same instance as other ops in the dag, # so if there is a condition, need to copy if node.op.condition: dag_op = dag_op.copy() dag.substitute_node(node, dag_op, inplace=True) if bound_target_dag.global_phase: dag.global_phase += bound_target_dag.global_phase else: dag.substitute_node_with_dag(node, bound_target_dag)
def run(self, dag): """ Extends `dag` with idle physical qubits in the self.property_set["layout"] (or `layout` kwarg from `__init__`). If an extension is performed, the DAG will be extended with an additional quantum register with the name "ancilla" (or "ancillaN" if the name is already taken, where N is an integer). Args: dag (DAGCircuit): DAG to extend. Returns: DAGCircuit: A extended DAG. Raises: TranspilerError: If there is not layout in the property set or not set at init time. """ if self.layout is None: if self.property_set["layout"]: self.layout = self.property_set["layout"] else: raise TranspilerError( "EnlargeWithAncilla requires self.property_set[\"layout\"] to run" ) # Idle physical qubits are those physical qubits that no virtual qubit corresponds to. # Add extra virtual qubits to make the DAG and CouplingMap the same size. num_idle_physical_qubits = len(self.layout.idle_physical_bits()) if num_idle_physical_qubits: if self.ancilla_name in dag.qregs: save_prefix = QuantumRegister.prefix QuantumRegister.prefix = self.ancilla_name dag.add_qreg(QuantumRegister(num_idle_physical_qubits)) QuantumRegister.prefix = save_prefix else: dag.add_qreg( QuantumRegister(num_idle_physical_qubits, name=self.ancilla_name)) return dag
def _check_circuits_coupling_map(circuits, cmap_conf, backend): # Check circuit width against number of qubits in coupling_map(s) coupling_maps_list = cmap_conf for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list): # If coupling_map is not None or num_qubits == 1 num_qubits = len(circuit.qubits) max_qubits = None if isinstance(parsed_coupling_map, CouplingMap): max_qubits = parsed_coupling_map.size() # If coupling_map is None, the limit might be in the backend (like in 1Q devices) elif backend is not None: backend_version = getattr(backend, "version", 0) if backend_version <= 1: if not backend.configuration().simulator: max_qubits = backend.configuration().n_qubits else: max_qubits = backend.num_qubits if max_qubits is not None and (num_qubits > max_qubits): raise TranspilerError( f"Number of qubits ({num_qubits}) in {circuit.name} " f"is greater than maximum ({max_qubits}) in the coupling_map")
def run(self, dag): try: from constraint import Problem, RecursiveBacktrackingSolver, AllDifferentConstraint except ImportError: raise TranspilerError('CSPLayout requires python-constraint to run. ' 'Run pip install python-constraint') qubits = dag.qubits() cxs = set() for gate in dag.twoQ_gates(): cxs.add((qubits.index(gate.qargs[0]), qubits.index(gate.qargs[1]))) edges = self.coupling_map.get_edges() problem = Problem(RecursiveBacktrackingSolver()) problem.addVariables(list(range(len(qubits))), self.coupling_map.physical_qubits) problem.addConstraint(AllDifferentConstraint()) # each wire is map to a single qbit if self.strict_direction: def constraint(control, target): return (control, target) in edges else: def constraint(control, target): return (control, target) in edges or (target, control) in edges for pair in cxs: problem.addConstraint(constraint, [pair[0], pair[1]]) random.seed(self.seed) solution = problem.getSolution() if solution is None: return self.property_set['layout'] = Layout({v: qubits[k] for k, v in solution.items()})
def yzy_to_zyz(xi, theta1, theta2, eps=1e-9): # pylint: disable=invalid-name """Express a Y.Z.Y single qubit gate as a Z.Y.Z gate. Solve the equation .. math:: Ry(theta1).Rz(xi).Ry(theta2) = Rz(phi).Ry(theta).Rz(lambda) for theta, phi, and lambda. Return a solution theta, phi, and lambda. """ quaternion_yzy = Quaternion.from_euler([theta1, xi, theta2], 'yzy') euler = quaternion_yzy.to_zyz() quaternion_zyz = Quaternion.from_euler(euler, 'zyz') # output order different than rotation order out_angles = (euler[1], euler[0], euler[2]) abs_inner = abs(quaternion_zyz.data.dot(quaternion_yzy.data)) if not np.allclose(abs_inner, 1, eps): raise TranspilerError('YZY and ZYZ angles do not give same rotation matrix.') out_angles = tuple(0 if np.abs(angle) < _CHOP_THRESHOLD else angle for angle in out_angles) return out_angles
def _mapper(self, circuit_graph, coupling_graph, trials=20): """Map a DAGCircuit onto a CouplingMap using swap gates. Use self.trivial_layout for the initial layout. Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingMap): coupling graph to map onto trials (int): number of trials. Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_graph Raises: TranspilerError: if there was any error during the mapping or with the parameters. """ # Schedule the input circuit by calling layers() layerlist = list(circuit_graph.layers()) logger.debug("schedule:") for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) qubit_subset = self.trivial_layout.get_virtual_bits().keys() # Find swap circuit to precede each layer of input circuit layout = self.trivial_layout.copy() # Construct an empty DAGCircuit with the same set of # qregs and cregs as the input circuit dagcircuit_output = circuit_graph._copy_circuit_metadata() logger.debug("trivial_layout = %s", layout) # Iterate over layers for i, layer in enumerate(layerlist): # Attempt to find a permutation for this layer success_flag, best_circuit, best_depth, best_layout \ = self._layer_permutation(layer["partition"], layout, qubit_subset, coupling_graph, trials) logger.debug("mapper: layer %d", i) logger.debug("mapper: success_flag=%s,best_depth=%s", success_flag, str(best_depth)) # If this fails, try one gate at a time in this layer if not success_flag: logger.debug( "mapper: failed, layer %d, " "retrying sequentially", i) serial_layerlist = list(layer["graph"].serial_layers()) # Go through each gate in the layer for j, serial_layer in enumerate(serial_layerlist): success_flag, best_circuit, best_depth, best_layout = \ self._layer_permutation( serial_layer["partition"], layout, qubit_subset, coupling_graph, trials) logger.debug("mapper: layer %d, sublayer %d", i, j) logger.debug("mapper: success_flag=%s,best_depth=%s,", success_flag, str(best_depth)) # Give up if we fail again if not success_flag: raise TranspilerError("swap mapper failed: " + "layer %d, sublayer %d" % (i, j)) # Update the record of qubit positions # for each inner iteration layout = best_layout # Update the DAG dagcircuit_output.compose( self._layer_update(j, best_layout, best_depth, best_circuit, serial_layerlist)) else: # Update the record of qubit positions for each iteration layout = best_layout # Update the DAG dagcircuit_output.compose( self._layer_update(i, best_layout, best_depth, best_circuit, layerlist)) # This is the final edgemap. We might use it to correctly replace # any measurements that needed to be removed earlier. logger.debug("mapper: self.trivial_layout = %s", self.trivial_layout) logger.debug("mapper: layout = %s", layout) return dagcircuit_output
def _layer_permutation(self, layer_partition, layout, qubit_subset, coupling, trials): """Find a swap circuit that implements a permutation for this layer. The goal is to swap qubits such that qubits in the same two-qubit gates are adjacent. Based on S. Bravyi's algorithm. Args: layer_partition (list): The layer_partition is a list of (qu)bit lists and each qubit is a tuple (qreg, index). layout (Layout): The layout is a Layout object mapping virtual qubits in the input circuit to physical qubits in the coupling graph. It reflects the current positions of the data. qubit_subset (list): The qubit_subset is the set of qubits in the coupling graph that we have chosen to map into, as tuples (Register, index). coupling (CouplingMap): Directed graph representing a coupling map. This coupling map should be one that was provided to the stochastic mapper. trials (int): Number of attempts the randomized algorithm makes. Returns: Tuple: success_flag, best_circuit, best_depth, best_layout If success_flag is True, then best_circuit contains a DAGCircuit with the swap circuit, best_depth contains the depth of the swap circuit, and best_layout contains the new positions of the data qubits after the swap circuit has been applied. Raises: TranspilerError: if anything went wrong. """ logger.debug("layer_permutation: layer_partition = %s", layer_partition) logger.debug("layer_permutation: layout = %s", layout.get_virtual_bits()) logger.debug("layer_permutation: qubit_subset = %s", qubit_subset) logger.debug("layer_permutation: trials = %s", trials) # The input dag is on a flat canonical register # TODO: cleanup the code that is general for multiple qregs below canonical_register = QuantumRegister(len(layout), 'q') qregs = OrderedDict({canonical_register.name: canonical_register}) gates = [] # list of lists of tuples [[(register, index), ...], ...] for gate_args in layer_partition: if len(gate_args) > 2: raise TranspilerError("Layer contains > 2-qubit gates") if len(gate_args) == 2: gates.append(tuple(gate_args)) logger.debug("layer_permutation: gates = %s", gates) # Can we already apply the gates? If so, there is no work to do. dist = sum( [coupling.distance(layout[g[0]], layout[g[1]]) for g in gates]) logger.debug("layer_permutation: distance = %s", dist) if dist == len(gates): logger.debug("layer_permutation: nothing to do") circ = DAGCircuit() circ.add_qreg(canonical_register) return True, circ, 0, layout # Begin loop over trials of randomized algorithm num_qubits = len(layout) best_depth = inf # initialize best depth best_edges = None # best edges found best_circuit = None # initialize best swap circuit best_layout = None # initialize best final layout cdist2 = coupling._dist_matrix**2 # Scaling matrix scale = np.zeros((num_qubits, num_qubits)) int_qubit_subset = np.fromiter( (self._qubit_indices[bit] for bit in qubit_subset), dtype=np.int32, count=len(qubit_subset)) int_gates = np.fromiter( (self._qubit_indices[bit] for gate in gates for bit in gate), dtype=np.int32, count=2 * len(gates)) int_layout = nlayout_from_layout(layout, self._qubit_indices, num_qubits, coupling.size()) trial_circuit = DAGCircuit( ) # SWAP circuit for slice of swaps in this trial trial_circuit.add_qubits(layout.get_virtual_bits()) edges = np.asarray(coupling.get_edges(), dtype=np.int32).ravel() cdist = coupling._dist_matrix for trial in range(trials): logger.debug("layer_permutation: trial %s", trial) # This is one Trial -------------------------------------- dist, optim_edges, trial_layout, depth_step = swap_trial( num_qubits, int_layout, int_qubit_subset, int_gates, cdist2, cdist, edges, scale, self.rng) logger.debug( "layer_permutation: final distance for this trial = %s", dist) if dist == len(gates) and depth_step < best_depth: logger.debug( "layer_permutation: got circuit with improved depth %s", depth_step) best_edges = optim_edges best_layout = trial_layout best_depth = min(best_depth, depth_step) # Break out of trial loop if we found a depth 1 circuit # since we can't improve it further if best_depth == 1: break # If we have no best circuit for this layer, all of the # trials have failed if best_layout is None: logger.debug("layer_permutation: failed!") return False, None, None, None edges = best_edges.edges() for idx in range(best_edges.size // 2): swap_src = self.trivial_layout[edges[2 * idx]] swap_tgt = self.trivial_layout[edges[2 * idx + 1]] trial_circuit.apply_operation_back(SwapGate(), [swap_src, swap_tgt], []) best_circuit = trial_circuit # Otherwise, we return our result for this layer logger.debug("layer_permutation: success!") best_lay = best_layout.to_layout(qregs) return True, best_circuit, best_depth, best_lay
def _layer_permutation(layer_partition, initial_layout, layout, qubit_subset, coupling, trials, qregs, rng): """Find a swap circuit that implements a permutation for this layer. Args: layer_partition (list): The layer_partition is a list of (qu)bit lists and each qubit is a tuple (qreg, index). initial_layout (Layout): The initial layout passed. layout (Layout): The layout is a Layout object mapping virtual qubits in the input circuit to physical qubits in the coupling graph. It reflects the current positions of the data. qubit_subset (list): The qubit_subset is the set of qubits in the coupling graph that we have chosen to map into, as tuples (Register, index). coupling (CouplingMap): Directed graph representing a coupling map. This coupling map should be one that was provided to the stochastic mapper. trials (int): Number of attempts the randomized algorithm makes. qregs (OrderedDict): Ordered dict of registers from input DAG. rng (RandomState): Random number generator. Returns: Tuple: success_flag, best_circuit, best_depth, best_layout, trivial_flag Raises: TranspilerError: if anything went wrong. """ logger.debug("layer_permutation: layer_partition = %s", pformat(layer_partition)) logger.debug("layer_permutation: layout = %s", pformat(layout.get_virtual_bits())) logger.debug("layer_permutation: qubit_subset = %s", pformat(qubit_subset)) logger.debug("layer_permutation: trials = %s", trials) gates = [] # list of lists of tuples [[(register, index), ...], ...] for gate_args in layer_partition: if len(gate_args) > 2: raise TranspilerError("Layer contains > 2-qubit gates") elif len(gate_args) == 2: gates.append(tuple(gate_args)) logger.debug("layer_permutation: gates = %s", pformat(gates)) # Can we already apply the gates? If so, there is no work to do. dist = sum([coupling.distance(layout[g[0]], layout[g[1]]) for g in gates]) logger.debug("layer_permutation: distance = %s", dist) if dist == len(gates): logger.debug("layer_permutation: nothing to do") circ = DAGCircuit() for register in layout.get_virtual_bits().keys(): if register[0] not in circ.qregs.values(): circ.add_qreg(register[0]) return True, circ, 0, layout, (not bool(gates)) # Begin loop over trials of randomized algorithm num_qubits = len(layout) best_depth = inf # initialize best depth best_edges = None # best edges found best_circuit = None # initialize best swap circuit best_layout = None # initialize best final layout cdist2 = coupling._dist_matrix**2 # Scaling matrix scale = np.zeros((num_qubits, num_qubits)) int_qubit_subset = regtuple_to_numeric(qubit_subset, qregs) int_gates = gates_to_idx(gates, qregs) int_layout = nlayout_from_layout(layout, qregs, coupling.size()) trial_circuit = DAGCircuit() # SWAP circuit for this trial for register in layout.get_virtual_bits().keys(): if register[0] not in trial_circuit.qregs.values(): trial_circuit.add_qreg(register[0]) slice_circuit = DAGCircuit() # circuit for this swap slice for register in layout.get_virtual_bits().keys(): if register[0] not in slice_circuit.qregs.values(): slice_circuit.add_qreg(register[0]) edges = np.asarray(coupling.get_edges(), dtype=np.int32).ravel() cdist = coupling._dist_matrix for trial in range(trials): logger.debug("layer_permutation: trial %s", trial) # This is one Trial -------------------------------------- dist, optim_edges, trial_layout, depth_step = swap_trial( num_qubits, int_layout, int_qubit_subset, int_gates, cdist2, cdist, edges, scale, rng) logger.debug("layer_permutation: final distance for this trial = %s", dist) if dist == len(gates) and depth_step < best_depth: logger.debug( "layer_permutation: got circuit with improved depth %s", depth_step) best_edges = optim_edges best_layout = trial_layout best_depth = min(best_depth, depth_step) # Break out of trial loop if we found a depth 1 circuit # since we can't improve it further if best_depth == 1: break # If we have no best circuit for this layer, all of the # trials have failed if best_layout is None: logger.debug("layer_permutation: failed!") return False, None, None, None, False edgs = best_edges.edges() for idx in range(best_edges.size // 2): slice_circuit.apply_operation_back( SwapGate(), [initial_layout[edgs[2 * idx]], initial_layout[edgs[2 * idx + 1]]], []) trial_circuit.extend_back(slice_circuit) best_circuit = trial_circuit # Otherwise, we return our result for this layer logger.debug("layer_permutation: success!") best_lay = best_layout.to_layout(qregs) return True, best_circuit, best_depth, best_lay, False
def _mapper(self, circuit_graph, coupling_graph, trials=20): """Map a DAGCircuit onto a CouplingMap using swap gates. Use self.initial_layout for the initial layout. Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingMap): coupling graph to map onto trials (int): number of trials. Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_graph Layout: a layout object mapping qubits of circuit_graph into qubits of coupling_graph. The layout may differ from the initial_layout if the first layer of gates cannot be executed on the initial_layout, since in this case it is more efficient to modify the layout instead of swapping Dict: a final-layer qubit permutation Raises: TranspilerError: if there was any error during the mapping or with the parameters. """ # Schedule the input circuit by calling layers() layerlist = list(circuit_graph.layers()) logger.debug("schedule:") for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) if self.initial_layout is not None: qubit_subset = self.initial_layout.get_virtual_bits().keys() else: # Supply a default layout for this dag self.initial_layout = Layout() physical_qubit = 0 for qreg in circuit_graph.qregs.values(): for index in range(qreg.size): self.initial_layout[(qreg, index)] = physical_qubit physical_qubit += 1 qubit_subset = self.initial_layout.get_virtual_bits().keys() # Restrict the coupling map to the image of the layout coupling_graph = coupling_graph.subgraph( self.initial_layout.get_physical_bits().keys()) if coupling_graph.size() < len(self.initial_layout): raise TranspilerError( "Coupling map too small for default layout") self.input_layout = self.initial_layout.copy() # Find swap circuit to preceed to each layer of input circuit layout = self.initial_layout.copy() # Construct an empty DAGCircuit with the same set of # qregs and cregs as the input circuit dagcircuit_output = DAGCircuit() dagcircuit_output.name = circuit_graph.name for qreg in circuit_graph.qregs.values(): dagcircuit_output.add_qreg(qreg) for creg in circuit_graph.cregs.values(): dagcircuit_output.add_creg(creg) # Make a trivial wire mapping between the subcircuits # returned by _layer_update and the circuit we build identity_wire_map = {} for qubit in circuit_graph.qubits(): identity_wire_map[qubit] = qubit for bit in circuit_graph.clbits(): identity_wire_map[bit] = bit first_layer = True # True until first layer is output logger.debug("initial_layout = %s", layout) # Iterate over layers for i, layer in enumerate(layerlist): # Attempt to find a permutation for this layer success_flag, best_circuit, best_depth, best_layout, trivial_flag \ = self._layer_permutation(layer["partition"], layout, qubit_subset, coupling_graph, trials) logger.debug("mapper: layer %d", i) logger.debug( "mapper: success_flag=%s,best_depth=%s,trivial_flag=%s", success_flag, str(best_depth), trivial_flag) # If this fails, try one gate at a time in this layer if not success_flag: logger.debug( "mapper: failed, layer %d, " "retrying sequentially", i) serial_layerlist = list(layer["graph"].serial_layers()) # Go through each gate in the layer for j, serial_layer in enumerate(serial_layerlist): success_flag, best_circuit, best_depth, best_layout, trivial_flag = \ self._layer_permutation( serial_layer["partition"], layout, qubit_subset, coupling_graph, trials) logger.debug("mapper: layer %d, sublayer %d", i, j) logger.debug( "mapper: success_flag=%s,best_depth=%s," "trivial_flag=%s", success_flag, str(best_depth), trivial_flag) # Give up if we fail again if not success_flag: raise TranspilerError("swap mapper failed: " + "layer %d, sublayer %d" % (i, j)) # If this layer is only single-qubit gates, # and we have yet to see multi-qubit gates, # continue to the next inner iteration if trivial_flag and first_layer: logger.debug("mapper: skip to next sublayer") continue if first_layer: self.initial_layout = layout # Update the record of qubit positions # for each inner iteration layout = best_layout # Update the DAG dagcircuit_output.extend_back( self._layer_update(j, first_layer, best_layout, best_depth, best_circuit, serial_layerlist), identity_wire_map) if first_layer: first_layer = False else: # Update the record of qubit positions for each iteration layout = best_layout if first_layer: self.initial_layout = layout # Update the DAG dagcircuit_output.extend_back( self._layer_update(i, first_layer, best_layout, best_depth, best_circuit, layerlist), identity_wire_map) if first_layer: first_layer = False # This is the final edgemap. We might use it to correctly replace # any measurements that needed to be removed earlier. logger.debug("mapper: self.initial_layout = %s", pformat(self.initial_layout)) logger.debug("mapper: layout = %s", pformat(layout)) last_edgemap = layout.combine_into_edge_map(self.initial_layout) logger.debug("mapper: last_edgemap = %s", pformat(last_edgemap)) # If first_layer is still set, the circuit only has single-qubit gates # so we can use the initial layout to output the entire circuit # This code is dead due to changes to first_layer above. if first_layer: logger.debug("mapper: first_layer flag still set") layout = self.initial_layout for i, layer in enumerate(layerlist): edge_map = layout.combine_into_edge_map(self.initial_layout) dagcircuit_output.compose_back(layer["graph"], edge_map) return dagcircuit_output
def _pad( self, dag: DAGCircuit, qubit: Qubit, t_start: int, t_end: int, next_node: DAGNode, prev_node: DAGNode, ): # This routine takes care of the pulse alignment constraint for the DD sequence. # Note that the alignment constraint acts on the t0 of the DAGOpNode. # Now this constrained scheduling problem is simplified to the problem of # finding a delay amount which is a multiple of the constraint value by assuming # that the duration of every DAGOpNode is also a multiple of the constraint value. # # For example, given the constraint value of 16 and XY4 with 160 dt gates. # Here we assume current interval is 992 dt. # # relative spacing := [0.125, 0.25, 0.25, 0.25, 0.125] # slack = 992 dt - 4 x 160 dt = 352 dt # # unconstraind sequence: 44dt-X1-88dt-Y2-88dt-X3-88dt-Y4-44dt # constraind sequence : 32dt-X1-80dt-Y2-80dt-X3-80dt-Y4-32dt + extra slack 48 dt # # Now we evenly split extra slack into start and end of the sequence. # The distributed slack should be multiple of 16. # Start = +16, End += 32 # # final sequence : 48dt-X1-80dt-Y2-80dt-X3-80dt-Y4-64dt / in total 992 dt # # Now we verify t0 of every node starts from multiple of 16 dt. # # X1: 48 dt (3 x 16 dt) # Y2: 48 dt + 160 dt + 80 dt = 288 dt (18 x 16 dt) # Y3: 288 dt + 160 dt + 80 dt = 528 dt (33 x 16 dt) # Y4: 368 dt + 160 dt + 80 dt = 768 dt (48 x 16 dt) # # As you can see, constraints on t0 are all satified without explicit scheduling. time_interval = t_end - t_start if self._qubits and dag.qubits.index(qubit) not in self._qubits: # Target physical qubit is not the target of this DD sequence. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return if self._skip_reset_qubits and (isinstance(prev_node, DAGInNode) or isinstance(prev_node.op, Reset)): # Previous node is the start edge or reset, i.e. qubit is ground state. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return slack = time_interval - np.sum(self._dd_sequence_lengths[qubit]) sequence_gphase = self._sequence_phase if slack <= 0: # Interval too short. self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return if len(self._dd_sequence) == 1: # Special case of using a single gate for DD u_inv = self._dd_sequence[0].inverse().to_matrix() theta, phi, lam, phase = OneQubitEulerDecomposer( ).angles_and_phase(u_inv) if isinstance(next_node, DAGOpNode) and isinstance( next_node.op, (UGate, U3Gate)): # Absorb the inverse into the successor (from left in circuit) theta_r, phi_r, lam_r = next_node.op.params next_node.op.params = Optimize1qGates.compose_u3( theta_r, phi_r, lam_r, theta, phi, lam) sequence_gphase += phase elif isinstance(prev_node, DAGOpNode) and isinstance( prev_node.op, (UGate, U3Gate)): # Absorb the inverse into the predecessor (from right in circuit) theta_l, phi_l, lam_l = prev_node.op.params prev_node.op.params = Optimize1qGates.compose_u3( theta, phi, lam, theta_l, phi_l, lam_l) sequence_gphase += phase else: # Don't do anything if there's no single-qubit gate to absorb the inverse self._apply_scheduled_op(dag, t_start, Delay(time_interval, dag.unit), qubit) return def _constrained_length(values): return self._alignment * np.floor(values / self._alignment) # (1) Compute DD intervals satisfying the constraint taus = _constrained_length(slack * np.asarray(self._spacing)) extra_slack = slack - np.sum(taus) # (2) Distribute extra slack if self._extra_slack_distribution == "middle": mid_ind = int((len(taus) - 1) / 2) to_middle = _constrained_length(extra_slack) taus[mid_ind] += to_middle if extra_slack - to_middle: # If to_middle is not a multiple value of the pulse alignment, # it is truncated to the nearlest multiple value and # the rest of slack is added to the end. taus[-1] += extra_slack - to_middle elif self._extra_slack_distribution == "edges": to_begin_edge = _constrained_length(extra_slack / 2) taus[0] += to_begin_edge taus[-1] += extra_slack - to_begin_edge else: raise TranspilerError( f"Option extra_slack_distribution = {self._extra_slack_distribution} is invalid." ) # (3) Construct DD sequence with delays num_elements = max(len(self._dd_sequence), len(taus)) idle_after = t_start for dd_ind in range(num_elements): if dd_ind < len(taus): tau = taus[dd_ind] if tau > 0: self._apply_scheduled_op(dag, idle_after, Delay(tau, dag.unit), qubit) idle_after += tau if dd_ind < len(self._dd_sequence): gate = self._dd_sequence[dd_ind] gate_length = self._dd_sequence_lengths[qubit][dd_ind] self._apply_scheduled_op(dag, idle_after, gate, qubit) idle_after += gate_length dag.global_phase = self._mod_2pi(dag.global_phase + sequence_gphase)
def _pre_runhook(self, dag: DAGCircuit): super()._pre_runhook(dag) num_pulses = len(self._dd_sequence) # Check if physical circuit is given if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError("DD runs on physical circuits only.") # Set default spacing otherwise validate user input if self._spacing is None: mid = 1 / num_pulses end = mid / 2 self._spacing = [end] + [mid] * (num_pulses - 1) + [end] else: if sum(self._spacing) != 1 or any(a < 0 for a in self._spacing): raise TranspilerError( "The spacings must be given in terms of fractions " "of the slack period and sum to 1.") # Check if DD sequence is identity if num_pulses != 1: if num_pulses % 2 != 0: raise TranspilerError( "DD sequence must contain an even number of gates (or 1).") noop = np.eye(2) for gate in self._dd_sequence: noop = noop.dot(gate.to_matrix()) if not matrix_equal(noop, IGate().to_matrix(), ignore_phase=True): raise TranspilerError( "The DD sequence does not make an identity operation.") self._sequence_phase = np.angle(noop[0][0]) # Precompute qubit-wise DD sequence length for performance for qubit in dag.qubits: physical_index = dag.qubits.index(qubit) if self._qubits and physical_index not in self._qubits: continue sequence_lengths = [] for gate in self._dd_sequence: try: # Check calibration. gate_length = dag.calibrations[gate.name][(physical_index, gate.params)] if gate_length % self._alignment != 0: # This is necessary to implement lightweight scheduling logic for this pass. # Usually the pulse alignment constraint and pulse data chunk size take # the same value, however, we can intentionally violate this pattern # at the gate level. For example, we can create a schedule consisting of # a pi-pulse of 32 dt followed by a post buffer, i.e. delay, of 4 dt # on the device with 16 dt constraint. Note that the pi-pulse length # is multiple of 16 dt but the gate length of 36 is not multiple of it. # Such pulse gate should be excluded. raise TranspilerError( f"Pulse gate {gate.name} with length non-multiple of {self._alignment} " f"is not acceptable in {self.__class__.__name__} pass." ) except KeyError: gate_length = self._durations.get(gate, physical_index) sequence_lengths.append(gate_length) # Update gate duration. This is necessary for current timeline drawer, i.e. scheduled. gate.duration = gate_length self._dd_sequence_lengths[qubit] = sequence_lengths
def run(self, dag): """ Args: dag(DAGCircuit): DAG circuit. Returns: DAGCircuit: optimized DAG circuit. Raises: TranspilerError: If the template has not the right form or if the output circuit acts differently as the input circuit. """ circuit_dag = dag circuit_dag_dep = dag_to_dagdependency(circuit_dag) for template in self.template_list: if not isinstance(template, (QuantumCircuit, DAGDependency)): raise TranspilerError( "A template is a Quantumciruit or a DAGDependency.") if len(template.qubits) > len(circuit_dag_dep.qubits): continue identity = np.identity(2**len(template.qubits), dtype=complex) try: if isinstance(template, DAGDependency): data = Operator(dagdependency_to_circuit(template)).data else: data = Operator(template).data comparison = np.allclose(data, identity) if not comparison: raise TranspilerError( "A template is a Quantumciruit() that " "performs the identity.") except TypeError: pass if isinstance(template, QuantumCircuit): template_dag_dep = circuit_to_dagdependency(template) else: template_dag_dep = template template_m = TemplateMatching( circuit_dag_dep, template_dag_dep, self.heuristics_qubits_param, self.heuristics_backward_param, ) template_m.run_template_matching() matches = template_m.match_list if matches: maximal = MaximalMatches(matches) maximal.run_maximal_matches() max_matches = maximal.max_match_list substitution = TemplateSubstitution( max_matches, template_m.circuit_dag_dep, template_m.template_dag_dep, self.user_cost_dict, ) substitution.run_dag_opt() circuit_dag_dep = substitution.dag_dep_optimized else: continue circuit_dag = dagdependency_to_dag(circuit_dag_dep) return circuit_dag
def _check_conflicting_argument(**kargs): conflicting_args = [arg for arg, value in kargs.items() if value] if conflicting_args: raise TranspilerError("The parameters pass_manager conflicts with the following " "parameter(s): {}.".format(', '.join(conflicting_args)))
def run(self, dag): """Run the ALAPSchedule pass on `dag`. Args: dag (DAGCircuit): DAG to schedule. Returns: DAGCircuit: A scheduled DAG. Raises: TranspilerError: if the circuit is not mapped on physical qubits. """ if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError( "ALAP schedule runs on physical circuits only") time_unit = self.property_set["time_unit"] new_dag = DAGCircuit() for qreg in dag.qregs.values(): new_dag.add_qreg(qreg) for creg in dag.cregs.values(): new_dag.add_creg(creg) qubit_time_available = defaultdict(int) def pad_with_delays(qubits: List[int], until, unit) -> None: """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" for q in qubits: if qubit_time_available[q] < until: idle_duration = until - qubit_time_available[q] new_dag.apply_operation_front(Delay(idle_duration, unit), [q], []) bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in reversed(list(dag.topological_op_nodes())): start_time = max(qubit_time_available[q] for q in node.qargs) pad_with_delays(node.qargs, until=start_time, unit=time_unit) new_dag.apply_operation_front(node.op, node.qargs, node.cargs) # validate node.op.duration if node.op.duration is None: indices = [bit_indices[qarg] for qarg in node.qargs] raise TranspilerError(f"Duration of {node.op.name} on qubits " f"{indices} is not found.") if isinstance(node.op.duration, ParameterExpression): indices = [bit_indices[qarg] for qarg in node.qargs] raise TranspilerError( f"Parameterized duration ({node.op.duration}) " f"of {node.op.name} on qubits {indices} is not bounded.") stop_time = start_time + node.op.duration # update time table for q in node.qargs: qubit_time_available[q] = stop_time working_qubits = qubit_time_available.keys() circuit_duration = max(qubit_time_available[q] for q in working_qubits) pad_with_delays(new_dag.qubits, until=circuit_duration, unit=time_unit) new_dag.name = dag.name new_dag.metadata = dag.metadata # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration new_dag.unit = time_unit return new_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 = 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 ASAPSchedule pass on `dag`. Args: dag (DAGCircuit): DAG to schedule. Returns: DAGCircuit: A scheduled DAG. Raises: TranspilerError: if the circuit is not mapped on physical qubits. """ if len(dag.qregs) != 1 or dag.qregs.get("q", None) is None: raise TranspilerError( "ASAP schedule runs on physical circuits only") time_unit = self.property_set["time_unit"] new_dag = DAGCircuit() for qreg in dag.qregs.values(): new_dag.add_qreg(qreg) for creg in dag.cregs.values(): new_dag.add_creg(creg) qubit_time_available = defaultdict(int) clbit_readable = defaultdict(int) clbit_writeable = defaultdict(int) def pad_with_delays(qubits: List[int], until, unit) -> None: """Pad idle time-slots in ``qubits`` with delays in ``unit`` until ``until``.""" for q in qubits: if qubit_time_available[q] < until: idle_duration = until - qubit_time_available[q] new_dag.apply_operation_back(Delay(idle_duration, unit), [q]) bit_indices = {q: index for index, q in enumerate(dag.qubits)} for node in dag.topological_op_nodes(): # validate node.op.duration if node.op.duration is None: indices = [bit_indices[qarg] for qarg in node.qargs] if dag.has_calibration_for(node): node.op.duration = dag.calibrations[node.op.name][( tuple(indices), tuple(float(p) for p in node.op.params))].duration if node.op.duration is None: raise TranspilerError( f"Duration of {node.op.name} on qubits {indices} is not found." ) if isinstance(node.op.duration, ParameterExpression): indices = [bit_indices[qarg] for qarg in node.qargs] raise TranspilerError( f"Parameterized duration ({node.op.duration}) " f"of {node.op.name} on qubits {indices} is not bounded.") # choose appropriate clbit available time depending on op clbit_time_available = (clbit_writeable if isinstance( node.op, Measure) else clbit_readable) # correction to change clbit start time to qubit start time delta = node.op.duration if isinstance(node.op, Measure) else 0 # must wait for op.condition_bits as well as node.cargs start_time = max( itertools.chain( (qubit_time_available[q] for q in node.qargs), (clbit_time_available[c] - delta for c in node.cargs + node.op.condition_bits), )) pad_with_delays(node.qargs, until=start_time, unit=time_unit) new_dag.apply_operation_back(node.op, node.qargs, node.cargs) stop_time = start_time + node.op.duration # update time table for q in node.qargs: qubit_time_available[q] = stop_time for c in node.cargs: # measure clbit_writeable[c] = clbit_readable[c] = stop_time for c in node.op.condition_bits: # conditional op clbit_writeable[c] = max(start_time, clbit_writeable[c]) working_qubits = qubit_time_available.keys() circuit_duration = max(qubit_time_available[q] for q in working_qubits) pad_with_delays(new_dag.qubits, until=circuit_duration, unit=time_unit) new_dag.name = dag.name new_dag.metadata = dag.metadata # set circuit duration and unit to indicate it is scheduled new_dag.duration = circuit_duration new_dag.unit = time_unit return new_dag
def run(self, dag): """Run the BIPMapping pass on `dag`, assuming the number of virtual qubits (defined in `dag`) and the number of physical qubits (defined in `coupling_map`) are the same. Args: dag (DAGCircuit): DAG to map. Returns: DAGCircuit: A mapped DAG. If there is no 2q-gate in DAG or it fails to map, returns the original dag. Raises: TranspilerError: if the number of virtual and physical qubits are not the same. AssertionError: if the final layout is not valid. """ if self.coupling_map is None: return dag if len(dag.qubits) > len(self.qubit_subset): raise TranspilerError("More virtual qubits exist than physical qubits.") if len(dag.qubits) != len(self.qubit_subset): raise TranspilerError( "BIPMapping requires the number of virtual and physical qubits to be the same. " "Supply 'qubit_subset' to specify physical qubits to use." ) original_dag = dag dummy_steps = math.ceil(math.sqrt(dag.num_qubits())) if self.max_swaps_inbetween_layers is not None: dummy_steps = max(0, self.max_swaps_inbetween_layers - 1) model = BIPMappingModel( dag=dag, coupling_map=self.coupling_map, qubit_subset=self.qubit_subset, dummy_timesteps=dummy_steps, ) if len(model.su4layers) == 0: logger.info("BIPMapping is skipped due to no 2q-gates.") return original_dag model.create_cpx_problem( objective=self.objective, backend_prop=self.backend_prop, depth_obj_weight=self.depth_obj_weight, default_cx_error_rate=self.default_cx_error_rate, ) status = model.solve_cpx_problem(time_limit=self.time_limit, threads=self.threads) if model.solution is None: logger.warning("Failed to solve a BIP problem. Status: %s", status) return original_dag # Get the optimized initial layout optimized_layout = model.get_layout(0) # Create a layout to track changes in layout for each layer layout = copy.deepcopy(optimized_layout) # Construct the mapped circuit canonical_qreg = QuantumRegister(self.coupling_map.size(), "q") mapped_dag = self._create_empty_dagcircuit(dag, canonical_qreg) interval = dummy_steps + 1 for k, layer in enumerate(dag.layers()): if model.is_su4layer(k): su4dep = model.to_su4layer_depth(k) # add swaps between (su4dep-1)-th and su4dep-th su4layer from_steps = max(interval * (su4dep - 1), 0) to_steps = min(interval * su4dep, model.depth - 1) for t in range(from_steps, to_steps): # pylint: disable=invalid-name for (i, j) in model.get_swaps(t): mapped_dag.apply_operation_back( op=SwapGate(), qargs=[canonical_qreg[i], canonical_qreg[j]], ) # update layout, swapping physical qubits (i, j) layout.swap(i, j) # map gates in k-th layer for node in layer["graph"].nodes(): if isinstance(node, DAGOpNode): mapped_dag.apply_operation_back( op=copy.deepcopy(node.op), qargs=[canonical_qreg[layout[q]] for q in node.qargs], cargs=node.cargs, ) # TODO: double check with y values? # Check final layout final_layout = model.get_layout(model.depth - 1) if layout != final_layout: raise AssertionError( f"Bug: final layout {final_layout} != the layout computed from swaps {layout}" ) self.property_set["layout"] = self._to_full_layout(optimized_layout) self.property_set["final_layout"] = self._to_full_layout(final_layout) return mapped_dag
def run(self, dag): """Run the CrosstalkAdaptiveLayout pass on `list of dag`.""" self._correct_xtalk_prop_keys() self._initialize_backend_prop() num_qubits = self._create_program_graphs(dag=dag) if num_qubits > len(self.available_hw_qubits): raise TranspilerError("Number of qubits greater than device.") for hwid, q in enumerate(dag.qubits): self.qarg_to_id[q.register.name + str(q.index)] = hwid for prog_graph in self.prog_graphs: # sort by weight, then edge name for determinism (since networkx on python 3.5 returns # different order of edges) """NEXT STEP! ここに、Multi-programmingするかどうかの判定関数を噛ませる """ self.pending_program_edges = sorted( prog_graph.edges(data=True), key=lambda x: [x[2]["weight"], -x[0], -x[1]], reverse=True, ) while self.pending_program_edges: edge = self._select_next_edge() q1_mapped = edge[0] in self.prog2hw q2_mapped = edge[1] in self.prog2hw if (not q1_mapped) and (not q2_mapped): best_hw_edge = self._select_best_remaining_cx() if best_hw_edge is None: raise TranspilerError( "CNOT({}, {}) could not be placed " "in selected device.".format(edge[0], edge[1])) self.prog2hw[edge[0]] = best_hw_edge[0] self.prog2hw[edge[1]] = best_hw_edge[1] self.available_hw_qubits.remove(best_hw_edge[0]) self.available_hw_qubits.remove(best_hw_edge[1]) self._crosstalk_backend_prop(edge=best_hw_edge) elif not q1_mapped: best_hw_qubit = self._select_best_remaining_qubit( edge[0], prog_graph) if best_hw_qubit is None: raise TranspilerError( "CNOT({}, {}) could not be placed in selected device. " "No qubit near qr[{}] available".format( edge[0], edge[1], edge[0])) self.prog2hw[edge[0]] = best_hw_qubit self.available_hw_qubits.remove(best_hw_qubit) self._crosstalk_backend_prop(edge=(self.prog2hw[edge[1]], best_hw_qubit)) else: best_hw_qubit = self._select_best_remaining_qubit( edge[1], prog_graph) if best_hw_qubit is None: raise TranspilerError( "CNOT({}, {}) could not be placed in selected device. " "No qubit near qr[{}] available".format( edge[0], edge[1], edge[1])) self.prog2hw[edge[1]] = best_hw_qubit self.available_hw_qubits.remove(best_hw_qubit) self._crosstalk_backend_prop(edge=(self.prog2hw[edge[0]], best_hw_qubit)) new_edges = [ x for x in self.pending_program_edges if not (x[0] in self.prog2hw and x[1] in self.prog2hw) ] self.pending_program_edges = new_edges for qid in self.qarg_to_id.values(): if qid not in self.prog2hw: self.prog2hw[qid] = self.available_hw_qubits[0] self.available_hw_qubits.remove(self.prog2hw[qid]) layout_dict = {} for q in dag.qubits: pid = self._qarg_to_id(q) hwid = self.prog2hw[pid] # layout[q] = hwid layout_dict[q] = hwid self.property_set["layout"] = Layout(input_dict=layout_dict)
def _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, translation_method, scheduling_method, instruction_durations, dt, seed_transpiler, optimization_level, callback, output_name) -> List[Dict]: """Resolve the various types of args allowed to the transpile() function through duck typing, overriding args, etc. Refer to the transpile() docstring for details on what types of inputs are allowed. Here the args are resolved by converting them to standard instances, and prioritizing them in case a transpile option is passed through multiple args (explicitly setting an arg has more priority than the arg set by backend). Returns: list[dicts]: a list of transpile parameters. Raises: TranspilerError: If instruction_durations are required but not supplied or found. """ if initial_layout is not None and layout_method is not None: warnings.warn("initial_layout provided; layout_method is ignored.", UserWarning) # Each arg could be single or a list. If list, it must be the same size as # number of circuits. If single, duplicate to create a list of that size. num_circuits = len(circuits) basis_gates = _parse_basis_gates(basis_gates, backend, circuits) faulty_qubits_map = _parse_faulty_qubits_map(backend, num_circuits) coupling_map = _parse_coupling_map(coupling_map, backend, num_circuits) backend_properties = _parse_backend_properties(backend_properties, backend, num_circuits) backend_num_qubits = _parse_backend_num_qubits(backend, num_circuits) initial_layout = _parse_initial_layout(initial_layout, circuits) layout_method = _parse_layout_method(layout_method, num_circuits) routing_method = _parse_routing_method(routing_method, num_circuits) translation_method = _parse_translation_method(translation_method, num_circuits) seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) optimization_level = _parse_optimization_level(optimization_level, num_circuits) output_name = _parse_output_name(output_name, circuits) callback = _parse_callback(callback, num_circuits) durations = _parse_instruction_durations(backend, instruction_durations, dt, circuits) scheduling_method = _parse_scheduling_method(scheduling_method, circuits) if scheduling_method and not durations: raise TranspilerError("Transpiling a circuit with a scheduling method or with delay " "instructions requires a backend or instruction_durations.") list_transpile_args = [] for args in zip(basis_gates, coupling_map, backend_properties, initial_layout, layout_method, routing_method, translation_method, scheduling_method, durations, seed_transpiler, optimization_level, output_name, callback, backend_num_qubits, faulty_qubits_map): transpile_args = {'pass_manager_config': PassManagerConfig(basis_gates=args[0], coupling_map=args[1], backend_properties=args[2], initial_layout=args[3], layout_method=args[4], routing_method=args[5], translation_method=args[6], scheduling_method=args[7], instruction_durations=args[8], seed_transpiler=args[9]), 'optimization_level': args[10], 'output_name': args[11], 'callback': args[12], 'backend_num_qubits': args[13], 'faulty_qubits_map': args[14]} list_transpile_args.append(transpile_args) return list_transpile_args
def transpile(circuits, backend=None, basis_gates=None, coupling_map=None, backend_properties=None, initial_layout=None, seed_transpiler=None, optimization_level=None, pass_manager=None): """transpile one or more circuits, according to some desired transpilation targets. All arguments may be given as either singleton or list. In case of list, the length must be equal to the number of circuits being transpiled. Transpilation is done in parallel using multiprocessing. Args: circuits (QuantumCircuit or list[QuantumCircuit]): Circuit(s) to transpile backend (BaseBackend): If set, transpiler options are automatically grabbed from backend.configuration() and backend.properties(). If any other option is explicitly set (e.g. coupling_map), it will override the backend's. Note: the backend arg is purely for convenience. The resulting circuit may be run on any backend as long as it is compatible. basis_gates (list[str]): List of basis gate names to unroll to. e.g: ['u1', 'u2', 'u3', 'cx'] If None, do not unroll. coupling_map (CouplingMap or list): Coupling map (perhaps custom) to target in mapping. Multiple formats are supported: a. CouplingMap instance b. list Must be given as an adjacency matrix, where each entry specifies all two-qubit interactions supported by backend e.g: [[0, 1], [0, 3], [1, 2], [1, 5], [2, 5], [4, 1], [5, 3]] backend_properties (BackendProperties): properties returned by a backend, including information on gate errors, readout errors, qubit coherence times, etc. For a backend that provides this information, it can be obtained with: ``backend.properties()`` initial_layout (Layout or dict or list): Initial position of virtual qubits on physical qubits. If this layout makes the circuit compatible with the coupling_map constraints, it will be used. The final layout is not guaranteed to be the same, as the transpiler may permute qubits through swaps or other means. Multiple formats are supported: a. Layout instance b. dict virtual to physical: {qr[0]: 0, qr[1]: 3, qr[2]: 5} physical to virtual: {0: qr[0], 3: qr[1], 5: qr[2]} c. list virtual to physical: [0, 3, 5] # virtual qubits are ordered (in addition to named) physical to virtual: [qr[0], None, None, qr[1], None, qr[2]] seed_transpiler (int): sets random seed for the stochastic parts of the transpiler optimization_level (int): How much optimization to perform on the circuits. Higher levels generate more optimized circuits, at the expense of longer transpilation time. 0: no optimization 1: light optimization 2: heavy optimization 3: even heavier optimization pass_manager (PassManager): The pass manager to use for a custom pipeline of transpiler passes. If this arg is present, all other args will be ignored and the pass manager will be used directly (Qiskit will not attempt to auto-select a pass manager based on transpile options). Returns: QuantumCircuit or list[QuantumCircuit]: transpiled circuit(s). Raises: TranspilerError: in case of bad inputs to transpiler or errors in passes """ # transpiling schedules is not supported yet. if isinstance(circuits, Schedule) or \ (isinstance(circuits, list) and all(isinstance(c, Schedule) for c in circuits)): return circuits if optimization_level is None: config = user_config.get_config() optimization_level = config.get('transpile_optimization_level', None) # Get TranspileConfig(s) to configure the circuit transpilation job(s) circuits = circuits if isinstance(circuits, list) else [circuits] transpile_configs = _parse_transpile_args(circuits, backend, basis_gates, coupling_map, backend_properties, initial_layout, seed_transpiler, optimization_level, pass_manager) # Check circuit width against number of qubits in coupling_map(s) coupling_maps_list = list(config.coupling_map for config in transpile_configs) for circuit, parsed_coupling_map in zip(circuits, coupling_maps_list): # If coupling_map is not None if isinstance(parsed_coupling_map, CouplingMap): n_qubits = len(circuit.qubits) max_qubits = parsed_coupling_map.size() if n_qubits > max_qubits: raise TranspilerError( 'Number of qubits ({}) '.format(n_qubits) + 'in {} '.format(circuit.name) + 'is greater than maximum ({}) '.format(max_qubits) + 'in the coupling_map') # Transpile circuits in parallel circuits = parallel_map(_transpile_circuit, list(zip(circuits, transpile_configs))) if len(circuits) == 1: return circuits[0] return circuits
def run(self, dag): """Translate an input DAGCircuit to the target basis. Args: dag (DAGCircuit): input dag Raises: TranspilerError: if the target basis cannot be reached Returns: DAGCircuit: translated circuit. """ if self._target_basis is None and self._target is None: return dag qarg_indices = {qubit: index for index, qubit in enumerate(dag.qubits)} # Names of instructions assumed to supported by any backend. if self._target is None: basic_instrs = ["measure", "reset", "barrier", "snapshot", "delay"] target_basis = set(self._target_basis) source_basis = set() for node in dag.op_nodes(): if not dag.has_calibration_for(node): source_basis.add((node.name, node.op.num_qubits)) qargs_local_source_basis = {} else: basic_instrs = ["barrier", "snapshot"] source_basis = set() target_basis = self._target.keys() - set(self._non_global_operations) qargs_local_source_basis = defaultdict(set) for node in dag.op_nodes(): qargs = tuple(qarg_indices[bit] for bit in node.qargs) if dag.has_calibration_for(node): continue # Treat the instruction as on an incomplete basis if the qargs are in the # qargs_with_non_global_operation dictionary or if any of the qubits in qargs # are a superset for a non-local operation. For example, if the qargs # are (0, 1) and that's a global (ie no non-local operations on (0, 1) # operation but there is a non-local operation on (1,) we need to # do an extra non-local search for this op to ensure we include any # single qubit operation for (1,) as valid. This pattern also holds # true for > 2q ops too (so for 4q operations we need to check for 3q, 2q, # and 1q opertaions in the same manner) if qargs in self._qargs_with_non_global_operation or any( frozenset(qargs).issuperset(incomplete_qargs) for incomplete_qargs in self._qargs_with_non_global_operation ): qargs_local_source_basis[frozenset(qargs)].add((node.name, node.op.num_qubits)) else: source_basis.add((node.name, node.op.num_qubits)) target_basis = set(target_basis).union(basic_instrs) logger.info( "Begin BasisTranslator from source basis %s to target basis %s.", source_basis, target_basis, ) # Search for a path from source to target basis. search_start_time = time.time() basis_transforms = _basis_search( self._equiv_lib, source_basis, target_basis, _basis_heuristic ) qarg_local_basis_transforms = {} for qarg, local_source_basis in qargs_local_source_basis.items(): expanded_target = target_basis | self._qargs_with_non_global_operation[qarg] # For any multiqubit operation that contains a subset of qubits that # has a non-local operation, include that non-local operation in the # search. This matches with the check we did above to include those # subset non-local operations in the check here. if len(qarg) > 1: for non_local_qarg, local_basis in self._qargs_with_non_global_operation.items(): if qarg.issuperset(non_local_qarg): expanded_target |= local_basis logger.info( "Performing BasisTranslator search from source basis %s to target " "basis %s on qarg %s.", local_source_basis, expanded_target, qarg, ) qarg_local_basis_transforms[qarg] = _basis_search( self._equiv_lib, local_source_basis, expanded_target, _basis_heuristic ) search_end_time = time.time() logger.info( "Basis translation path search completed in %.3fs.", search_end_time - search_start_time ) if basis_transforms is None: raise TranspilerError( "Unable to map source basis {} to target basis {} " "over library {}.".format(source_basis, target_basis, self._equiv_lib) ) # Compose found path into a set of instruction substitution rules. compose_start_time = time.time() instr_map = _compose_transforms(basis_transforms, source_basis, dag) extra_instr_map = { qarg: _compose_transforms(transforms, qargs_local_source_basis[qarg], dag) for qarg, transforms in qarg_local_basis_transforms.items() } compose_end_time = time.time() logger.info( "Basis translation paths composed in %.3fs.", compose_end_time - compose_start_time ) # Replace source instructions with target translations. replace_start_time = time.time() for node in dag.op_nodes(): node_qargs = tuple(qarg_indices[bit] for bit in node.qargs) qubit_set = frozenset(node_qargs) if node.name in target_basis: continue if ( node_qargs in self._qargs_with_non_global_operation and node.name in self._qargs_with_non_global_operation[node_qargs] ): continue if dag.has_calibration_for(node): continue def replace_node(node, instr_map): target_params, target_dag = instr_map[node.op.name, node.op.num_qubits] if len(node.op.params) != len(target_params): raise TranspilerError( "Translation num_params not equal to op num_params." "Op: {} {} Translation: {}\n{}".format( node.op.params, node.op.name, target_params, target_dag ) ) if node.op.params: # Convert target to circ and back to assign_parameters, since # DAGCircuits won't have a ParameterTable. from qiskit.converters import dag_to_circuit, circuit_to_dag target_circuit = dag_to_circuit(target_dag) target_circuit.assign_parameters( dict(zip_longest(target_params, node.op.params)), inplace=True ) bound_target_dag = circuit_to_dag(target_circuit) else: bound_target_dag = target_dag if len(bound_target_dag.op_nodes()) == 1 and len( bound_target_dag.op_nodes()[0].qargs ) == len(node.qargs): dag_op = bound_target_dag.op_nodes()[0].op # dag_op may be the same instance as other ops in the dag, # so if there is a condition, need to copy if node.op.condition: dag_op = dag_op.copy() dag.substitute_node(node, dag_op, inplace=True) if bound_target_dag.global_phase: dag.global_phase += bound_target_dag.global_phase else: dag.substitute_node_with_dag(node, bound_target_dag) if qubit_set in extra_instr_map: replace_node(node, extra_instr_map[qubit_set]) elif (node.op.name, node.op.num_qubits) in instr_map: replace_node(node, instr_map) else: raise TranspilerError(f"BasisTranslator did not map {node.name}.") replace_end_time = time.time() logger.info( "Basis translation instructions replaced in %.3fs.", replace_end_time - replace_start_time, ) return dag