def run(self, dag): """Run the RemoveFinalMeasurements pass on `dag`. Args: dag (DAGCircuit): the DAG to be optimized. Returns: DAGCircuit: the optimized DAG. """ final_op_types = ['measure', 'barrier'] final_ops = [] cregs_to_remove = dict() clbits_with_final_measures = set() for candidate_node in dag.named_nodes(*final_op_types): is_final_op = True for _, child_successors in dag.bfs_successors(candidate_node): if any(suc.type == 'op' and suc.name not in final_op_types for suc in child_successors): is_final_op = False break if is_final_op: final_ops.append(candidate_node) if not final_ops: return dag new_dag = DAGCircuit() for node in final_ops: for carg in node.cargs: # Add the clbit that was attached to the measure we are removing clbits_with_final_measures.add(carg) dag.remove_op_node(node) # If the clbit is idle, add its register to list of registers we may remove for clbit in clbits_with_final_measures: if clbit in dag.idle_wires(): if clbit.register in cregs_to_remove: cregs_to_remove[clbit.register] += 1 else: cregs_to_remove[clbit.register] = 1 # Remove creg if all of its clbits were added above for key, val in zip(list(dag.cregs.keys()), list(dag.cregs.values())): if val in cregs_to_remove and cregs_to_remove[val] == val.size: del dag.cregs[key] # Fill new DAGCircuit for qreg in dag.qregs.values(): new_dag.add_qreg(qreg) for creg in dag.cregs.values(): new_dag.add_creg(creg) for node in dag.topological_op_nodes(): # copy the condition over too if node.condition: new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs, condition=node.condition) else: new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs) return new_dag
def swap_mapper(circuit_graph, coupling_graph, initial_layout=None, trials=20, seed=None): """Map a DAGCircuit onto a CouplingGraph using swap gates. Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingGraph): coupling graph to map onto initial_layout (Layout): dict {(str, int): (str, int)} from qubits of circuit_graph to qubits of coupling_graph (optional) trials (int): number of trials. seed (int): initial seed. Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_graph, and a layout dict 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. Raises: MapperError: if there was any error during the mapping or with the parameters. """ if circuit_graph.width() > coupling_graph.size(): raise MapperError("Not enough qubits in CouplingGraph") # Schedule the input circuit layerlist = list(circuit_graph.layers()) logger.debug("schedule:") for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) if initial_layout is not None: # update initial_layout from a user given dict{(regname,idx): (regname,idx)} # to an expected dict{(reg,idx): (reg,idx)} device_register = QuantumRegister(coupling_graph.size(), 'q') initial_layout = {(circuit_graph.qregs[k[0]], k[1]): (device_register, v[1]) for k, v in initial_layout.items()} # Check the input layout circ_qubits = circuit_graph.get_qubits() coup_qubits = [(QuantumRegister(coupling_graph.size(), 'q'), wire) for wire in coupling_graph.physical_qubits] qubit_subset = [] for k, v in initial_layout.items(): qubit_subset.append(v) if k not in circ_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "DAGCircuit" % (k[0].name, k[1])) if v not in coup_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "CouplingGraph" % (v[0].name, v[1])) else: # Supply a default layout qubit_subset = [(QuantumRegister(coupling_graph.size(), 'q'), wire) for wire in coupling_graph.physical_qubits] qubit_subset = qubit_subset[0:circuit_graph.width()] initial_layout = { a: b for a, b in zip(circuit_graph.get_qubits(), qubit_subset) } # Find swap circuit to preceed to each layer of input circuit layout = initial_layout.copy() # Construct an empty DAGCircuit with one qreg "q" # and the same set of cregs as the input circuit dagcircuit_output = DAGCircuit() dagcircuit_output.name = circuit_graph.name dagcircuit_output.add_qreg(QuantumRegister(coupling_graph.size(), "q")) for creg in circuit_graph.cregs.values(): dagcircuit_output.add_creg(creg) # Make a trivial wire mapping between the subcircuits # returned by swap_mapper_layer_update and the circuit # we are building identity_wire_map = {} q = QuantumRegister(coupling_graph.size(), 'q') for j in range(coupling_graph.size()): identity_wire_map[(q, j)] = (q, j) for creg in circuit_graph.cregs.values(): for j in range(creg.size): identity_wire_map[(creg, j)] = (creg, j) 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_circ, best_d, best_layout, trivial_flag \ = layer_permutation(layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d", i) logger.debug("swap_mapper: success_flag=%s,best_d=%s,trivial_flag=%s", success_flag, str(best_d), trivial_flag) # If this fails, try one gate at a time in this layer if not success_flag: logger.debug( "swap_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_circ, best_d, best_layout, trivial_flag \ = layer_permutation(serial_layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d, sublayer %d", i, j) logger.debug( "swap_mapper: success_flag=%s,best_d=%s," "trivial_flag=%s", success_flag, str(best_d), trivial_flag) # Give up if we fail again if not success_flag: raise MapperError("swap_mapper failed: " + "layer %d, sublayer %d" % (i, j) + ", \"%s\"" % serial_layer["graph"].qasm( no_decls=True, aliases=layout)) # 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("swap_mapper: skip to next sublayer") continue # Update the record of qubit positions for each inner iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(j, first_layer, best_layout, best_d, best_circ, serial_layerlist, coupling_graph), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False else: # Update the record of qubit positions for each iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(i, first_layer, best_layout, best_d, best_circ, layerlist, coupling_graph), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False # 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 if first_layer: layout = initial_layout for i, layer in enumerate(layerlist): dagcircuit_output.compose_back(layer["graph"], layout) return dagcircuit_output, initial_layout
def run(self, dag): """Return a new circuit that has been optimized.""" runs = dag.collect_runs(["u1", "u2", "u3", "id"]) for run in runs: right_name = "u1" right_parameters = (0, 0, 0) # (theta, phi, lambda) for current_node in run: left_name = current_node.name if (current_node.condition is not None or len(current_node.qargs) != 1 or left_name not in ["u1", "u2", "u3", "id"]): raise TranspilerError("internal error") if left_name == "u1": left_parameters = (0, 0, current_node.op.params[0]) elif left_name == "u2": left_parameters = (np.pi / 2, current_node.op.params[0], current_node.op.params[1]) elif left_name == "u3": left_parameters = tuple(current_node.op.params) else: left_name = "u1" # replace id with u1 left_parameters = (0, 0, 0) # If there are any sympy objects coming from the gate convert # to numpy. left_parameters = tuple([float(x) for x in left_parameters]) # Compose gates name_tuple = (left_name, right_name) if name_tuple == ("u1", "u1"): # u1(lambda1) * u1(lambda2) = u1(lambda1 + lambda2) right_parameters = (0, 0, right_parameters[2] + left_parameters[2]) elif name_tuple == ("u1", "u2"): # u1(lambda1) * u2(phi2, lambda2) = u2(phi2 + lambda1, lambda2) right_parameters = (np.pi / 2, right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple == ("u2", "u1"): # u2(phi1, lambda1) * u1(lambda2) = u2(phi1, lambda1 + lambda2) right_name = "u2" right_parameters = (np.pi / 2, left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u1", "u3"): # u1(lambda1) * u3(theta2, phi2, lambda2) = # u3(theta2, phi2 + lambda1, lambda2) right_parameters = (right_parameters[0], right_parameters[1] + left_parameters[2], right_parameters[2]) elif name_tuple == ("u3", "u1"): # u3(theta1, phi1, lambda1) * u1(lambda2) = # u3(theta1, phi1, lambda1 + lambda2) right_name = "u3" right_parameters = (left_parameters[0], left_parameters[1], right_parameters[2] + left_parameters[2]) elif name_tuple == ("u2", "u2"): # Using Ry(pi/2).Rz(2*lambda).Ry(pi/2) = # Rz(pi/2).Ry(pi-2*lambda).Rz(pi/2), # u2(phi1, lambda1) * u2(phi2, lambda2) = # u3(pi - lambda1 - phi2, phi1 + pi/2, lambda2 + pi/2) right_name = "u3" right_parameters = (np.pi - left_parameters[2] - right_parameters[1], left_parameters[1] + np.pi / 2, right_parameters[2] + np.pi / 2) elif name_tuple[1] == "nop": right_name = left_name right_parameters = left_parameters else: # For composing u3's or u2's with u3's, use # u2(phi, lambda) = u3(pi/2, phi, lambda) # together with the qiskit.mapper.compose_u3 method. right_name = "u3" # Evaluate the symbolic expressions for efficiency right_parameters = Optimize1qGates.compose_u3( left_parameters[0], left_parameters[1], left_parameters[2], right_parameters[0], right_parameters[1], right_parameters[2]) # Why evalf()? This program: # OPENQASM 2.0; # include "qelib1.inc"; # qreg q[2]; # creg c[2]; # u3(0.518016983430947*pi,1.37051598592907*pi,1.36816383603222*pi) q[0]; # u3(1.69867232277986*pi,0.371448347747471*pi,0.461117217930936*pi) q[0]; # u3(0.294319836336836*pi,0.450325871124225*pi,1.46804720442555*pi) q[0]; # measure q -> c; # took >630 seconds (did not complete) to optimize without # calling evalf() at all, 19 seconds to optimize calling # evalf() AFTER compose_u3, and 1 second to optimize # calling evalf() BEFORE compose_u3. # 1. Here down, when we simplify, we add f(theta) to lambda to # correct the global phase when f(theta) is 2*pi. This isn't # necessary but the other steps preserve the global phase, so # we continue in that manner. # 2. The final step will remove Z rotations by 2*pi. # 3. Note that is_zero is true only if the expression is exactly # zero. If the input expressions have already been evaluated # then these final simplifications will not occur. # TODO After we refactor, we should have separate passes for # exact and approximate rewriting. # Y rotation is 0 mod 2*pi, so the gate is a u1 if np.mod(right_parameters[0], (2 * np.pi)) == 0 \ and right_name != "u1": right_name = "u1" right_parameters = (0, 0, right_parameters[1] + right_parameters[2] + right_parameters[0]) # Y rotation is pi/2 or -pi/2 mod 2*pi, so the gate is a u2 if right_name == "u3": # theta = pi/2 + 2*k*pi if np.mod((right_parameters[0] - np.pi / 2), (2 * np.pi)) == 0: right_name = "u2" right_parameters = (np.pi / 2, right_parameters[1], right_parameters[2] + (right_parameters[0] - np.pi / 2)) # theta = -pi/2 + 2*k*pi if np.mod((right_parameters[0] + np.pi / 2), (2 * np.pi)) == 0: right_name = "u2" right_parameters = (np.pi / 2, right_parameters[1] + np.pi, right_parameters[2] - np.pi + (right_parameters[0] + np.pi / 2)) # u1 and lambda is 0 mod 2*pi so gate is nop (up to a global phase) if right_name == "u1" and np.mod(right_parameters[2], (2 * np.pi)) == 0: right_name = "nop" # Replace the the first node in the run with a dummy DAG which contains a dummy # qubit. The name is irrelevant, because substitute_node_with_dag will take care of # putting it in the right place. run_qarg = (QuantumRegister(1, 'q'), 0) new_op = Gate(name="", num_qubits=1, params=[]) if right_name == "u1": new_op = U1Gate(right_parameters[2]) if right_name == "u2": new_op = U2Gate(right_parameters[1], right_parameters[2]) if right_name == "u3": new_op = U3Gate(*right_parameters) if right_name != 'nop': new_dag = DAGCircuit() new_dag.add_qreg(run_qarg[0]) new_dag.apply_operation_back(new_op, [run_qarg], []) dag.substitute_node_with_dag(run[0], new_dag) # Delete the other nodes in the run for current_node in run[1:]: dag.remove_op_node(current_node) if right_name == "nop": dag.remove_op_node(run[0]) return dag
def test_add_reg_bad_type(self): """add_qreg with a classical register is not allowed.""" dag = DAGCircuit() cr = ClassicalRegister(2) self.assertRaises(DAGCircuitError, dag.add_qreg, cr)
def run(self, dag): """ Runs the BasicSwap pass on `dag`. Args: dag (DAGCircuit): DAG to map. Returns: DAGCircuit: A mapped DAG. Raises: TranspilerError: if the coupling map or the layout are not compatible with the DAG """ new_dag = DAGCircuit() if self.initial_layout is None: if self.property_set["layout"]: self.initial_layout = self.property_set["layout"] else: self.initial_layout = Layout.generate_trivial_layout( *dag.qregs.values()) if len(dag.qubits()) != len(self.initial_layout): raise TranspilerError( 'The layout does not match the amount of qubits in the DAG') if len(self.coupling_map.physical_qubits) != len(self.initial_layout): raise TranspilerError( "Mappers require to have the layout to be the same size as the coupling map" ) current_layout = self.initial_layout.copy() for layer in dag.serial_layers(): subdag = layer['graph'] for gate in subdag.twoQ_gates(): physical_q0 = current_layout[gate.qargs[0]] physical_q1 = current_layout[gate.qargs[1]] if self.coupling_map.distance(physical_q0, physical_q1) != 1: # Insert a new layer with the SWAP(s). swap_layer = DAGCircuit() path = self.coupling_map.shortest_undirected_path( physical_q0, physical_q1) for swap in range(len(path) - 2): connected_wire_1 = path[swap] connected_wire_2 = path[swap + 1] qubit_1 = current_layout[connected_wire_1] qubit_2 = current_layout[connected_wire_2] # create qregs for qreg in current_layout.get_registers(): if qreg not in swap_layer.qregs.values(): swap_layer.add_qreg(qreg) # create the swap operation swap_layer.apply_operation_back( SwapGate(), qargs=[qubit_1, qubit_2], cargs=[]) # layer insertion edge_map = current_layout.combine_into_edge_map( self.initial_layout) new_dag.compose_back(swap_layer, edge_map) # update current_layout for swap in range(len(path) - 2): current_layout.swap(path[swap], path[swap + 1]) edge_map = current_layout.combine_into_edge_map( self.initial_layout) new_dag.extend_back(subdag, edge_map) return new_dag
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 = 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 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, 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: logger.debug("mapper: skip to next sublayer") continue # Update the record of qubit positions # for each inner iteration layout = best_layout # Update the DAG dagcircuit_output.extend_back( self._layer_update(j, best_layout, best_depth, best_circuit, serial_layerlist), identity_wire_map) else: # Update the record of qubit positions for each iteration layout = best_layout # Update the DAG dagcircuit_output.extend_back( self._layer_update(i, best_layout, best_depth, best_circuit, layerlist), identity_wire_map) # 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", pformat(self.trivial_layout)) logger.debug("mapper: layout = %s", pformat(layout)) last_edgemap = layout.combine_into_edge_map(self.trivial_layout) logger.debug("mapper: last_edgemap = %s", pformat(last_edgemap)) return dagcircuit_output
def swap_mapper(circuit_graph, coupling_graph, initial_layout=None, basis="cx,u1,u2,u3,id", trials=20, seed=None): """Map a DAGCircuit onto a CouplingGraph using swap gates. Args: circuit_graph (DAGCircuit): input DAG circuit coupling_graph (CouplingGraph): coupling graph to map onto initial_layout (dict): dict from qubits of circuit_graph to qubits of coupling_graph (optional) basis (str): basis string specifying basis of output DAGCircuit trials (int): number of trials. seed (int): initial seed. Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_graph, and a layout dict 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. Finally, returned is the final layer qubit permutation that is needed to add measurements back in. Raises: MapperError: if there was any error during the mapping or with the parameters. """ if circuit_graph.width() > coupling_graph.size(): raise MapperError("Not enough qubits in CouplingGraph") # Schedule the input circuit layerlist = list(circuit_graph.layers()) logger.debug("schedule:") for i, v in enumerate(layerlist): logger.debug(" %d: %s", i, v["partition"]) if initial_layout is not None: # Check the input layout circ_qubits = circuit_graph.get_qubits() coup_qubits = coupling_graph.get_qubits() qubit_subset = [] for k, v in initial_layout.items(): qubit_subset.append(v) if k not in circ_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "DAGCircuit" % (k[0], k[1])) if v not in coup_qubits: raise MapperError("initial_layout qubit %s[%d] not in input " "CouplingGraph" % (v[0], v[1])) else: # Supply a default layout qubit_subset = coupling_graph.get_qubits() qubit_subset = qubit_subset[0:circuit_graph.width()] initial_layout = { a: b for a, b in zip(circuit_graph.get_qubits(), qubit_subset) } # Find swap circuit to preceed to each layer of input circuit layout = initial_layout.copy() layout_max_index = max(map(lambda x: x[1] + 1, layout.values())) # Construct an empty DAGCircuit with one qreg "q" # and the same set of cregs as the input circuit dagcircuit_output = DAGCircuit() dagcircuit_output.name = circuit_graph.name dagcircuit_output.add_qreg("q", layout_max_index) for name, size in circuit_graph.cregs.items(): dagcircuit_output.add_creg(name, size) # Make a trivial wire mapping between the subcircuits # returned by swap_mapper_layer_update and the circuit # we are building identity_wire_map = {} for j in range(layout_max_index): identity_wire_map[("q", j)] = ("q", j) for name, size in circuit_graph.cregs.items(): for j in range(size): identity_wire_map[(name, j)] = (name, j) 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_circ, best_d, best_layout, trivial_flag \ = layer_permutation(layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d", i) logger.debug("swap_mapper: success_flag=%s,best_d=%s,trivial_flag=%s", success_flag, str(best_d), trivial_flag) # If this fails, try one gate at a time in this layer if not success_flag: logger.debug( "swap_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_circ, best_d, best_layout, trivial_flag \ = layer_permutation(serial_layer["partition"], layout, qubit_subset, coupling_graph, trials, seed) logger.debug("swap_mapper: layer %d, sublayer %d", i, j) logger.debug( "swap_mapper: success_flag=%s,best_d=%s," "trivial_flag=%s", success_flag, str(best_d), trivial_flag) # Give up if we fail again if not success_flag: raise MapperError("swap_mapper failed: " + "layer %d, sublayer %d" % (i, j) + ", \"%s\"" % serial_layer["graph"].qasm( no_decls=True, aliases=layout)) # 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("swap_mapper: skip to next sublayer") continue # Update the record of qubit positions for each inner iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(j, first_layer, best_layout, best_d, best_circ, serial_layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False else: # Update the record of qubit positions for each iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( swap_mapper_layer_update(i, first_layer, best_layout, best_d, best_circ, layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False # This is the final layout that we need to correctly replace # any measurements that needed to be removed before the swap last_layout = layout # 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 if first_layer: layout = initial_layout for i, layer in enumerate(layerlist): dagcircuit_output.compose_back(layer["graph"], layout) # Parse openqasm_output into DAGCircuit object dag_unrolled = DagUnroller(dagcircuit_output, DAGBackend(basis.split(","))) dagcircuit_output = dag_unrolled.expand_gates() return dagcircuit_output, initial_layout, last_layout
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. TranspilerError: if conditional bit is added to non-supported instruction. """ 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) idle_before = {q: 0 for q in dag.qubits + dag.clbits} bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in reversed(list(dag.topological_op_nodes())): op_duration = self._get_node_duration(node, bit_indices, dag) # compute t0, t1: instruction interval, note that # t0: start time of instruction # t1: end time of instruction # since this is alap scheduling, node is scheduled in reversed topological ordering # and nodes are packed from the very end of the circuit. # the physical meaning of t0 and t1 is flipped here. if isinstance(node.op, self.CONDITIONAL_SUPPORTED): t0q = max(idle_before[q] for q in node.qargs) if node.op.condition_bits: # conditional is bit tricky due to conditional_latency t0c = max(idle_before[c] for c in node.op.condition_bits) # Assume following case (t0c > t0q): # # |t0q # Q ░░░░░░░░░░░░░▒▒▒ # C ░░░░░░░░▒▒▒▒▒▒▒▒ # |t0c # # In this case, there is no actual clbit read before gate. # # |t0q' = t0c - conditional_latency # Q ░░░░░░░░▒▒▒░░▒▒▒ # C ░░░░░░▒▒▒▒▒▒▒▒▒▒ # |t1c' = t0c + conditional_latency # # rather than naively doing # # |t1q' = t0c + duration # Q ░░░░░▒▒▒░░░░░▒▒▒ # C ░░▒▒░░░░▒▒▒▒▒▒▒▒ # |t1c' = t0c + duration + conditional_latency # t0 = max(t0q, t0c - op_duration) t1 = t0 + op_duration for clbit in node.op.condition_bits: idle_before[clbit] = t1 + self.conditional_latency else: t0 = t0q t1 = t0 + op_duration else: if node.op.condition_bits: raise TranspilerError( f"Conditional instruction {node.op.name} is not supported in ALAP scheduler." ) if isinstance(node.op, Measure): # clbit time is always right (alap) justified t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) t1 = t0 + op_duration # # |t1 = t0 + duration # Q ░░░░░▒▒▒▒▒▒▒▒▒▒▒ # C ░░░░░░░░░▒▒▒▒▒▒▒ # |t0 + (duration - clbit_write_latency) # for clbit in node.cargs: idle_before[clbit] = t0 + (op_duration - self.clbit_write_latency) else: # It happens to be directives such as barrier t0 = max(idle_before[bit] for bit in node.qargs + node.cargs) t1 = t0 + op_duration for bit in node.qargs: delta = t0 - idle_before[bit] if delta > 0: new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) idle_before[bit] = t1 new_dag.apply_operation_front(node.op, node.qargs, node.cargs) circuit_duration = max(idle_before.values()) for bit, before in idle_before.items(): delta = circuit_duration - before if not (delta > 0 and isinstance(bit, Qubit)): continue new_dag.apply_operation_front(Delay(delta, time_unit), [bit], []) new_dag.name = dag.name new_dag.metadata = dag.metadata new_dag.calibrations = dag.calibrations # 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 BasicSwap pass on `dag`. Args: dag (DAGCircuit): DAG to map. Returns: DAGCircuit: A mapped DAG. Raises: TranspilerError: if the coupling map or the layout are not compatible with the DAG. """ new_dag = DAGCircuit() if len(dag.qregs) != 1 or dag.qregs.get('q', None) is None: raise TranspilerError('Basic swap runs on physical circuits only') if len(dag.qubits()) > len(self.coupling_map.physical_qubits): raise TranspilerError('The layout does not match the amount of qubits in the DAG') canonical_register = dag.qregs['q'] trivial_layout = Layout.generate_trivial_layout(canonical_register) current_layout = trivial_layout.copy() for layer in dag.serial_layers(): subdag = layer['graph'] for gate in subdag.twoQ_gates(): physical_q0 = current_layout[gate.qargs[0]] physical_q1 = current_layout[gate.qargs[1]] if self.coupling_map.distance(physical_q0, physical_q1) != 1: # Insert a new layer with the SWAP(s). swap_layer = DAGCircuit() swap_layer.add_qreg(canonical_register) path = self.coupling_map.shortest_undirected_path(physical_q0, physical_q1) for swap in range(len(path) - 2): connected_wire_1 = path[swap] connected_wire_2 = path[swap + 1] qubit_1 = current_layout[connected_wire_1] qubit_2 = current_layout[connected_wire_2] # create the swap operation swap_layer.apply_operation_back(SwapGate(), qargs=[qubit_1, qubit_2], cargs=[]) # layer insertion edge_map = current_layout.combine_into_edge_map(trivial_layout) new_dag.compose_back(swap_layer, edge_map) # update current_layout for swap in range(len(path) - 2): current_layout.swap(path[swap], path[swap + 1]) edge_map = current_layout.combine_into_edge_map(trivial_layout) new_dag.extend_back(subdag, edge_map) return new_dag
def run(self, dag): """ Runs the BasicMapper pass on `dag`. Args: dag (DAGCircuit): DAG to map. Returns: DAGCircuit: A mapped DAG. """ new_dag = DAGCircuit() if self.initial_layout is None: # create a one-to-one layout self.initial_layout = Layout() physical_qubit = 0 for qreg in dag.qregs.values(): for index in range(qreg.size): self.initial_layout[(qreg, index)] = physical_qubit physical_qubit += 1 current_layout = copy(self.initial_layout) for layer in dag.serial_layers(): subdag = layer['graph'] for a_cx in subdag.get_cnot_nodes(): physical_q0 = current_layout[a_cx['qargs'][0]] physical_q1 = current_layout[a_cx['qargs'][1]] if self.coupling_map.distance(physical_q0, physical_q1) != 1: # Insert a new layer with the SWAP(s). swap_layer = DAGCircuit() path = self.coupling_map.shortest_undirected_path( physical_q0, physical_q1) for swap in range(len(path) - 2): connected_wire_1 = path[swap] connected_wire_2 = path[swap + 1] qubit_1 = current_layout[connected_wire_1] qubit_2 = current_layout[connected_wire_2] # create the involved registers if qubit_1[0] not in swap_layer.qregs.values(): swap_layer.add_qreg(qubit_1[0]) if qubit_2[0] not in swap_layer.qregs.values(): swap_layer.add_qreg(qubit_2[0]) # create the swap operation swap_layer.add_basis_element('swap', 2, 0, 0) swap_layer.apply_operation_back( self.swap_gate(qubit_1, qubit_2), qargs=[qubit_1, qubit_2]) # layer insertion edge_map = current_layout.combine_into_edge_map( self.initial_layout) new_dag.compose_back(swap_layer, edge_map) # update current_layout for swap in range(len(path) - 2): current_layout.swap(path[swap], path[swap + 1]) edge_map = current_layout.combine_into_edge_map( self.initial_layout) new_dag.extend_back(subdag, edge_map) return new_dag
def run(self, dag): """Run the Unroller pass on `dag`. Args: dag (DAGCircuit): input dag Raises: QiskitError: if unable to unroll given the basis due to undefined decomposition rules (such as a bad basis) or excessive recursion. Returns: DAGCircuit: output unrolled dag """ if self.basis is None: return dag # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): basic_insts = ['measure', 'reset', 'barrier', 'snapshot'] if node.name in basic_insts: # TODO: this is legacy behavior.Basis_insts should be removed that these # instructions should be part of the device-reported basis. Currently, no # backend reports "measure", for example. continue if node.name in self.basis: # If already a base, ignore. if isinstance(node.op, ControlledGate) and node.op._open_ctrl: pass else: continue # TODO: allow choosing other possible decompositions try: rule = node.op.definition except TypeError as err: raise QiskitError('Error decomposing node {}: {}'.format( node.name, err)) # Isometry gates definitions can have widths smaller than that of the # original gate, in which case substitute_node will raise. Fall back # to substitute_node_with_dag if an the width of the definition is # different that the width of the node. while rule and len(rule) == 1 and len(node.qargs) == len( rule[0][1]): if rule[0][0].name in self.basis: dag.substitute_node(node, rule[0][0], inplace=True) break try: rule = rule[0][0].definition except TypeError as err: raise QiskitError('Error decomposing node {}: {}'.format( node.name, err)) else: if not rule: if rule == []: # empty node dag.remove_op_node(node) continue # opaque node raise QiskitError( "Cannot unroll the circuit to the given basis, %s. " "No rule to expand instruction %s." % (str(self.basis), node.op.name)) # hacky way to build a dag on the same register as the rule is defined # TODO: need anonymous rules to address wires by index decomposition = DAGCircuit() qregs = {qb.register for inst in rule for qb in inst[1]} cregs = {cb.register for inst in rule for cb in inst[2]} for qreg in qregs: decomposition.add_qreg(qreg) for creg in cregs: decomposition.add_creg(creg) for inst in rule: decomposition.apply_operation_back(*inst) unrolled_dag = self.run( decomposition) # recursively unroll ops dag.substitute_node_with_dag(node, unrolled_dag) return dag
def _layer_permutation(self, layer_partition, layout, qubit_subset, coupling, trials, seed=None): """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. 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. seed (int): Optional seed for the random number generator. If it is None we do not reseed. Returns: Tuple: success_flag, best_circuit, best_depth, best_layout, trivial_flag 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. The trivial_flag is set if the layer has no multi-qubit gates. Raises: TranspilerError: if anything went wrong. """ if seed is not None: np.random.seed(seed) 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]) circ.add_basis_element("swap", 2) 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_circuit = None # initialize best swap circuit best_layout = None # initialize best final layout for trial in range(trials): logger.debug("layer_permutation: trial %s", trial) trial_layout = layout.copy() trial_circuit = DAGCircuit() # SWAP circuit for this trial for register in trial_layout.get_virtual_bits().keys(): if register[0] not in trial_circuit.qregs.values(): trial_circuit.add_qreg(register[0]) # Compute randomized distance xi = {} # pylint: disable=invalid-name for i in range(num_qubits): xi[i] = {} for i in range(num_qubits): for j in range(i, num_qubits): scale = 1 + np.random.normal(0, 1 / num_qubits) xi[i][j] = scale * coupling.distance(i, j) ** 2 xi[j][i] = xi[i][j] slice_circuit = DAGCircuit() # circuit for this swap slice for register in trial_layout.get_virtual_bits().keys(): if register[0] not in slice_circuit.qregs.values(): slice_circuit.add_qreg(register[0]) slice_circuit.add_basis_element("swap", 2) # Loop over depths from 1 up to a maximum depth depth_step = 1 depth_max = 2 * num_qubits + 1 while depth_step < depth_max: qubit_set = set(qubit_subset) # While there are still qubits available while qubit_set: # Compute the objective function min_cost = sum([xi[trial_layout[g[0]]][trial_layout[g[1]]] for g in gates]) # Try to decrease objective function cost_reduced = False # Loop over edges of coupling graph for edge in coupling.get_edges(): qubits = [trial_layout[e] for e in edge] # Are the qubits available? if qubits[0] in qubit_set and qubits[1] in qubit_set: # Try this edge to reduce the cost new_layout = trial_layout.copy() new_layout.swap(edge[0], edge[1]) # Compute the objective function new_cost = sum([xi[new_layout[g[0]]][new_layout[g[1]]] for g in gates]) # Record progress if we succceed if new_cost < min_cost: logger.debug("layer_permutation: min_cost " "improved to %s", min_cost) cost_reduced = True min_cost = new_cost optimal_layout = new_layout optimal_edge = qubits # Were there any good swap choices? if cost_reduced: qubit_set.remove(optimal_edge[0]) qubit_set.remove(optimal_edge[1]) trial_layout = optimal_layout slice_circuit.apply_operation_back( SwapGate(optimal_edge[0], optimal_edge[1])) logger.debug("layer_permutation: swap the pair %s", pformat(optimal_edge)) else: break # We have either run out of swap pairs to try or # failed to improve the cost. # Compute the coupling graph distance dist = sum([coupling.distance(trial_layout[g[0]], trial_layout[g[1]]) for g in gates]) logger.debug("layer_permutation: new swap distance = %s", dist) # If all gates can be applied now, we are finished. # Otherwise we need to consider a deeper swap circuit if dist == len(gates): logger.debug("layer_permutation: all gates can be " "applied now in this layer") trial_circuit.extend_back(slice_circuit) break # Increment the depth depth_step += 1 logger.debug("layer_permutation: increment depth to %s", depth_step) # Either we have succeeded at some depth d < dmax or failed dist = sum([coupling.distance(trial_layout[g[0]], trial_layout[g[1]]) for g in gates]) logger.debug("layer_permutation: final distance for this trial = %s", dist) if dist == len(gates): if depth_step < best_depth: logger.debug("layer_permutation: got circuit with improved depth %s", depth_step) best_circuit = trial_circuit 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_circuit is None: logger.debug("layer_permutation: failed!") return False, None, None, None, False # Otherwise, we return our result for this layer logger.debug("layer_permutation: success!") return True, best_circuit, best_depth, best_layout, False
def run(self, dag): """ Apply this transpiler pass to a circuit in directed acyclic graph representation. :param qiskit.dagcircuit.DAGCircuit dag: The circuit to transpile. :returns: The transpiled circuit. :rtype: qiskit.dagcircuit.DAGCircuit :raises qiskit.exceptions.QiskitError: If the circuit contains a parametrized non-basis gate, or contains a gate that cannot be unrolled. """ # Walk through the DAG and expand each non-basis node for node in dag.op_nodes(): basic_insts = ["measure", "reset", "barrier", "snapshot"] if node.name in basic_insts: # TODO: this is legacy behavior.Basis_insts should be removed that these # instructions should be part of the device-reported basis. Currently, no # backend reports "measure", for example. continue if node.name in [ "id", "r", "sx", "sy", "x", "y", "rz", "ms2", ]: # If already a base, ignore. continue try: rule = self._get_rule(node) except TypeError as err: if any( isinstance(p, ParameterExpression) for p in node.op.params): raise QiskitError( "Unrolling gates parameterized by expressions " "is currently unsupported.") raise QiskitError("Error decomposing node {}: {}".format( node.name, err)) if not rule: raise QiskitError( "Cannot unroll the circuit to trapped ion gates. " "No rule to expand instruction %s." % node.op.name) # hacky way to build a dag on the same register as the rule is defined # TODO: need anonymous rules to address wires by index decomposition = DAGCircuit() qregs = {qb.register for inst in rule for qb in inst[1]} cregs = {cb.register for inst in rule for cb in inst[2]} for qreg in qregs: decomposition.add_qreg(qreg) for creg in cregs: decomposition.add_creg(creg) for inst in rule: decomposition.apply_operation_back(*inst) unrolled_dag = self.run(decomposition) # recursively unroll ops dag.substitute_node_with_dag(node, unrolled_dag) return dag
def run(self, dag): """Return a circuit with a barrier before last measurments.""" # Collect DAG nodes which are followed only by barriers or other measures. final_op_types = ['measure', 'barrier'] final_ops = [] for candidate_op in dag.named_nodes(*final_op_types): is_final_op = True for _, child_successors in dag.bfs_successors(candidate_op): if any(dag.multi_graph.node[suc]['type'] == 'op' and dag.multi_graph.node[suc]['op'].name not in final_op_types for suc in child_successors): is_final_op = False break if is_final_op: final_ops.append(candidate_op) if not final_ops: return dag # Create a layer with the barrier and add registers from the original dag. barrier_layer = DAGCircuit() for qreg in dag.qregs.values(): barrier_layer.add_qreg(qreg) for creg in dag.cregs.values(): barrier_layer.add_creg(creg) final_qubits = set(dag.multi_graph.node[final_op]['qargs'][0] for final_op in final_ops) new_barrier_id = barrier_layer.apply_operation_back(Barrier(qubits=final_qubits)) # Preserve order of final ops collected earlier from the original DAG. ordered_node_ids = [node_id for node_id in dag.node_nums_in_topological_order() if node_id in set(final_ops)] ordered_final_nodes = [dag.multi_graph.node[node] for node in ordered_node_ids] # Move final ops to the new layer and append the new layer to the DAG. for final_node in ordered_final_nodes: barrier_layer.apply_operation_back(final_node['op']) for final_op in final_ops: dag._remove_op_node(final_op) # Check to see if the new barrier added to the DAG is equivalent to any # existing barriers, and if so, consolidate the two. our_ancestors = barrier_layer.ancestors(new_barrier_id) our_descendants = barrier_layer.descendants(new_barrier_id) our_qubits = final_qubits existing_barriers = barrier_layer.named_nodes('barrier') existing_barriers.remove(new_barrier_id) for candidate_barrier in existing_barriers: their_ancestors = barrier_layer.ancestors(candidate_barrier) their_descendants = barrier_layer.descendants(candidate_barrier) their_qubits = set(barrier_layer.multi_graph.nodes[candidate_barrier]['op'].qargs) if ( not our_qubits.isdisjoint(their_qubits) and our_ancestors.isdisjoint(their_descendants) and our_descendants.isdisjoint(their_ancestors) ): merge_barrier = Barrier(qubits=(our_qubits | their_qubits)) merge_barrier_id = barrier_layer.apply_operation_front(merge_barrier) our_ancestors = our_ancestors | their_ancestors our_descendants = our_descendants | their_descendants barrier_layer._remove_op_node(candidate_barrier) barrier_layer._remove_op_node(new_barrier_id) new_barrier_id = merge_barrier_id dag.extend_back(barrier_layer) return dag
def run(self, dag): """iterate over each block and replace it with an equivalent Unitary on the same wires. """ 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) # compute ordered indices for the global circuit wires global_index_map = {wire: idx for idx, wire in enumerate(dag.qubits())} blocks = self.property_set['block_list'] # just to make checking if a node is in any block easier all_block_nodes = {nd for bl in blocks for nd in bl} for node in dag.topological_op_nodes(): if node not in all_block_nodes: # need to add this node to find out where in the list it goes preds = [ nd for nd in dag.predecessors(node) if nd.type == 'op' ] block_count = 0 while preds: if block_count < len(blocks): block = blocks[block_count] # if any of the predecessors are in the block, remove them preds = [p for p in preds if p not in block] else: # should never occur as this would mean not all # nodes before this one topologically had been added # so not all predecessors were removed raise TranspilerError( "Not all predecessors removed due to error" " in topological order") block_count += 1 # we have now seen all predecessors # so update the blocks list to include this block blocks = blocks[:block_count] + [[node]] + blocks[block_count:] # create the dag from the updated list of blocks basis_gate_name = self.decomposer.gate.name for block in blocks: if len(block) == 1 and block[0].name != 'cx': # an intermediate node that was added into the overall list new_dag.apply_operation_back(block[0].op, block[0].qargs, block[0].cargs, block[0].condition) else: # find the qubits involved in this block block_qargs = set() for nd in block: block_qargs |= set(nd.qargs) # convert block to a sub-circuit, then simulate unitary and add block_width = len(block_qargs) q = QuantumRegister(block_width) subcirc = QuantumCircuit(q) block_index_map = self._block_qargs_to_indices( block_qargs, global_index_map) basis_count = 0 for nd in block: if nd.op.name == basis_gate_name: basis_count += 1 subcirc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) unitary = UnitaryGate( Operator(subcirc)) # simulates the circuit if self.force_consolidate or unitary.num_qubits > 2 or \ self.decomposer.num_basis_gates(unitary) != basis_count: new_dag.apply_operation_back( unitary, sorted(block_qargs, key=lambda x: block_index_map[x])) else: for nd in block: new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, nd.condition) return new_dag
def run(self, dag): """Run the CommutativeCancellation pass on `dag`. Args: dag (DAGCircuit): the DAG to be optimized. Returns: DAGCircuit: the optimized DAG. Raises: TranspilerError: when the 1-qubit rotation gates are not found """ var_z_gate = None z_var_gates = [ gate for gate in dag.count_ops().keys() if gate in self._var_z_map ] if z_var_gates: # priortize z gates in circuit var_z_gate = self._var_z_map[next(iter(z_var_gates))] else: z_var_gates = [ gate for gate in self.basis if gate in self._var_z_map ] if z_var_gates: var_z_gate = self._var_z_map[next(iter(z_var_gates))] # Now the gates supported are hard-coded q_gate_list = ['cx', 'cy', 'cz', 'h', 'y'] # Gate sets to be cancelled cancellation_sets = defaultdict(lambda: []) # Traverse each qubit to generate the cancel dictionaries # Cancel dictionaries: # - For 1-qubit gates the key is (gate_type, qubit_id, commutation_set_id), # the value is the list of gates that share the same gate type, qubit, commutation set. # - For 2qbit gates the key: (gate_type, first_qbit, sec_qbit, first commutation_set_id, # sec_commutation_set_id), the value is the list gates that share the same gate type, # qubits and commutation sets. for wire in dag.wires: wire_commutation_set = self.property_set['commutation_set'][wire] for com_set_idx, com_set in enumerate(wire_commutation_set): if com_set[0].type in ['in', 'out']: continue for node in com_set: num_qargs = len(node.qargs) if num_qargs == 1 and node.name in q_gate_list: cancellation_sets[(node.name, wire, com_set_idx)].append(node) if num_qargs == 1 and node.name in [ 'p', 'z', 'u1', 'rz', 't', 's' ]: cancellation_sets[('z_rotation', wire, com_set_idx)].append(node) if num_qargs == 1 and node.name in ['rx', 'x']: cancellation_sets[('x_rotation', wire, com_set_idx)].append(node) # Don't deal with Y rotation, because Y rotation doesn't commute with CNOT, so # it should be dealt with by optimized1qgate pass elif num_qargs == 2 and node.qargs[0] == wire: second_qarg = node.qargs[1] q2_key = (node.name, wire, second_qarg, com_set_idx, self.property_set['commutation_set'][( node, second_qarg)]) cancellation_sets[q2_key].append(node) for cancel_set_key in cancellation_sets: if cancel_set_key[0] == 'z_rotation' and var_z_gate is None: continue set_len = len(cancellation_sets[cancel_set_key]) if set_len > 1 and cancel_set_key[0] in q_gate_list: gates_to_cancel = cancellation_sets[cancel_set_key] for c_node in gates_to_cancel[:(set_len // 2) * 2]: dag.remove_op_node(c_node) elif set_len > 1 and cancel_set_key[0] in [ 'z_rotation', 'x_rotation' ]: run = cancellation_sets[cancel_set_key] run_qarg = run[0].qargs[0] total_angle = 0.0 # lambda total_phase = 0.0 for current_node in run: if (current_node.condition is not None or len(current_node.qargs) != 1 or current_node.qargs[0] != run_qarg): raise TranspilerError("internal error") if current_node.name in ['p', 'u1', 'rz', 'rx']: current_angle = float(current_node.op.params[0]) elif current_node.name in ['z', 'x']: current_angle = np.pi elif current_node.name == 't': current_angle = np.pi / 4 elif current_node.name == 's': current_angle = np.pi / 2 # Compose gates total_angle = current_angle + total_angle if current_node.op.definition: total_phase += current_node.op.definition.global_phase # Replace the data of the first node in the run if cancel_set_key[0] == 'z_rotation': new_op = var_z_gate(total_angle) elif cancel_set_key[0] == 'x_rotation': new_op = RXGate(total_angle) new_op_phase = 0 if np.mod(total_angle, (2 * np.pi)) > _CUTOFF_PRECISION: new_qarg = QuantumRegister(1, 'q') new_dag = DAGCircuit() new_dag.add_qreg(new_qarg) new_dag.apply_operation_back(new_op, [new_qarg[0]]) dag.substitute_node_with_dag(run[0], new_dag) if new_op.definition: new_op_phase = new_op.definition.global_phase dag.global_phase = total_phase - new_op_phase # Delete the other nodes in the run for current_node in run[1:]: dag.remove_op_node(current_node) if np.mod(total_angle, (2 * np.pi)) < _CUTOFF_PRECISION: dag.remove_op_node(run[0]) return dag
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, node.condition) 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.") 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 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) 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_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())): # 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 {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 = 0 if isinstance(node.op, Measure) else node.op.duration # 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_front(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] = start_time for c in node.op.condition_bits: # conditional op clbit_writeable[c] = max(stop_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 _layer_permutation(layer_partition, layout, qubit_subset, coupling, trials, 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). 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. 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) # 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", 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() circ.add_qreg(canonical_register) 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 qubit in layout.get_virtual_bits().keys(): if qubit.register not in trial_circuit.qregs.values(): trial_circuit.add_qreg(qubit.register) slice_circuit = DAGCircuit() # circuit for this swap slice for qubit in layout.get_virtual_bits().keys(): if qubit.register not in slice_circuit.qregs.values(): slice_circuit.add_qreg(qubit.register) 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() trivial_layout = Layout.generate_trivial_layout(canonical_register) for idx in range(best_edges.size // 2): slice_circuit.apply_operation_back( SwapGate(), [trivial_layout[edgs[2 * idx]], trivial_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 _compose_transforms(basis_transforms, source_basis, source_dag): """Compose a set of basis transforms into a set of replacements. Args: basis_transforms (List[Tuple[gate_name, params, equiv]]): List of transforms to compose. source_basis (Set[Tuple[gate_name: str, gate_num_qubits: int]]): Names of gates which need to be translated. source_dag (DAGCircuit): DAG with example gates from source_basis. (Used to determine num_params for gate in source_basis.) Returns: Dict[gate_name, Tuple(params, dag)]: Dictionary mapping between each gate in source_basis and a DAGCircuit instance to replace it. Gates in source_basis but not affected by basis_transforms will be included as a key mapping to itself. """ example_gates = {(node.op.name, node.op.num_qubits): node.op for node in source_dag.op_nodes()} mapped_instrs = {} for gate_name, gate_num_qubits in source_basis: # Need to grab a gate instance to find num_qubits and num_params. # Can be removed following https://github.com/Qiskit/qiskit-terra/pull/3947 . example_gate = example_gates[gate_name, gate_num_qubits] num_params = len(example_gate.params) placeholder_params = ParameterVector(gate_name, num_params) placeholder_gate = Gate(gate_name, gate_num_qubits, list(placeholder_params)) placeholder_gate.params = list(placeholder_params) dag = DAGCircuit() qr = QuantumRegister(gate_num_qubits) dag.add_qreg(qr) dag.apply_operation_back(placeholder_gate, qr[:], []) mapped_instrs[gate_name, gate_num_qubits] = placeholder_params, dag for gate_name, gate_num_qubits, equiv_params, equiv in basis_transforms: logger.debug('Composing transform step: %s/%s %s =>\n%s', gate_name, gate_num_qubits, equiv_params, equiv) for mapped_instr_name, (dag_params, dag) in mapped_instrs.items(): doomed_nodes = [ node for node in dag.op_nodes() if (node.op.name, node.op.num_qubits) == (gate_name, gate_num_qubits) ] if doomed_nodes and logger.isEnabledFor(logging.DEBUG): from qiskit.converters import dag_to_circuit logger.debug( 'Updating transform for mapped instr %s %s from \n%s', mapped_instr_name, dag_params, dag_to_circuit(dag)) for node in doomed_nodes: from qiskit.converters import circuit_to_dag replacement = equiv.assign_parameters( dict(zip_longest(equiv_params, node.op.params))) replacement_dag = circuit_to_dag(replacement) dag.substitute_node_with_dag(node, replacement_dag) if doomed_nodes and logger.isEnabledFor(logging.DEBUG): from qiskit.converters import dag_to_circuit logger.debug('Updated transform for mapped instr %s %s to\n%s', mapped_instr_name, dag_params, dag_to_circuit(dag)) return mapped_instrs
def run(self, dag): """iterate over each block and replace it with an equivalent Unitary on the same wires. """ 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) # compute ordered indices for the global circuit wires global_index_map = {} for wire in dag.wires: if not isinstance(wire[0], QuantumRegister): continue global_qregs = list(dag.qregs.values()) global_index_map[wire] = global_qregs.index(wire[0]) + wire[1] blocks = self.property_set['block_list'] nodes_seen = set() for node in dag.topological_op_nodes(): # skip already-visited nodes or input/output nodes if node in nodes_seen or node.type == 'in' or node.type == 'out': continue # check if the node belongs to the next block if blocks and node in blocks[0]: block = blocks[0] # find the qubits involved in this block block_qargs = set() for nd in block: block_qargs |= set(nd.qargs) # convert block to a sub-circuit, then simulate unitary and add block_width = len(block_qargs) q = QuantumRegister(block_width) subcirc = QuantumCircuit(q) block_index_map = self._block_qargs_to_indices( block_qargs, global_index_map) for nd in block: nodes_seen.add(nd) subcirc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) unitary = UnitaryGate( Operator(subcirc)) # simulates the circuit new_dag.apply_operation_back( unitary, sorted(block_qargs, key=lambda x: block_index_map[x])) del blocks[0] else: # the node could belong to some future block, but in that case # we simply skip it. It is guaranteed that we will revisit that # future block, via its other nodes for block in blocks[1:]: if node in block: break # freestanding nodes can just be added else: nodes_seen.add(node) new_dag.apply_operation_back(node.op, node.qargs, node.cargs) return new_dag
def layer_permutation(self, layer_partition, layout, qubit_subset): """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 Sergey Bravyi's algorithm. The layer_partition is a list of (qu)bit lists and each qubit is a tuple (qreg, index). The layout is a dict mapping qubits in the circuit to qubits in the coupling graph and represents the current positions of the data. The qubit_subset is the subset of qubits in the coupling graph that we have chosen to map into. The coupling is a CouplingGraph. TRIALS is the number of attempts the randomized algorithm makes. Returns: success_flag, best_circ, best_d, best_layout, trivial_flag If success_flag is True, then best_circ contains a DAGCircuit with the swap circuit, best_d 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. The trivial_flag is set if the layer has no multi-qubit gates. """ if self.seed is None: self.seed = np.random.randint(0, np.iinfo(np.int32).max) rng = np.random.RandomState(self.seed) rev_layout = {b: a for a, b in layout.items()} gates = [] for layer in layer_partition: if len(layer) > 2: raise TranspilerError("Layer contains >2 qubit gates") if len(layer) == 2: gates.append(tuple(layer)) # Can we already apply the gates? dist = sum([ self.coupling_map.distance(layout[g[0]][1], layout[g[1]][1]) for g in gates ]) if dist == len(gates): circ = DAGCircuit() circ.add_qreg(QuantumRegister(self.coupling_map.size(), "q")) return True, circ, 0, layout, bool(gates) # Begin loop over trials of randomized algorithm n = self.coupling_map.size() best_d = sys.maxsize # initialize best depth best_circ = None # initialize best swap circuit best_layout = None # initialize best final layout QR = QuantumRegister(self.coupling_map.size(), "q") for _ in range(self.trials): trial_layout = layout.copy() rev_trial_layout = rev_layout.copy() # SWAP circuit constructed this trial trial_circ = DAGCircuit() trial_circ.add_qreg(QR) # Compute Sergey's randomized distance xi = {} for i in self.coupling_map.physical_qubits: xi[(QR, i)] = {} for i in self.coupling_map.physical_qubits: i = (QR, i) for j in self.coupling_map.physical_qubits: j = (QR, j) scale = 1 + rng.normal(0, 1 / n) xi[i][j] = scale * self.coupling_map.distance(i[1], j[1])**2 xi[j][i] = xi[i][j] # Loop over depths d up to a max depth of 2n+1 d = 1 # Circuit for this swap slice circ = DAGCircuit() circ.add_qreg(QR) # Identity wire-map for composing the circuits identity_wire_map = {(QR, j): (QR, j) for j in range(n)} while d < 2 * n + 1: # Set of available qubits qubit_set = set(qubit_subset) # While there are still qubits available while qubit_set: # Compute the objective function min_cost = sum([ xi[trial_layout[g[0]]][trial_layout[g[1]]] for g in gates ]) # Try to decrease objective function progress_made = False # Loop over edges of coupling graph for e in self.coupling_map.get_edges(): e = [(QR, edge) for edge in e] # Are the qubits available? if e[0] in qubit_set and e[1] in qubit_set: # Try this edge to reduce the cost new_layout = trial_layout.copy() new_layout[rev_trial_layout[e[0]]] = e[1] new_layout[rev_trial_layout[e[1]]] = e[0] rev_new_layout = rev_trial_layout.copy() rev_new_layout[e[0]] = rev_trial_layout[e[1]] rev_new_layout[e[1]] = rev_trial_layout[e[0]] # Compute the objective function new_cost = sum([ xi[new_layout[g[0]]][new_layout[g[1]]] for g in gates ]) # Record progress if we succeed if new_cost < min_cost: progress_made = True min_cost = new_cost opt_layout = new_layout rev_opt_layout = rev_new_layout opt_edge = e # Were there any good choices? if progress_made: qubit_set.remove(opt_edge[0]) qubit_set.remove(opt_edge[1]) trial_layout = opt_layout rev_trial_layout = rev_opt_layout circ.apply_operation_back( SwapGate(), [(opt_edge[0][0], opt_edge[0][1]), (opt_edge[1][0], opt_edge[1][1])], []) else: break # We have either run out of qubits or failed to improve # Compute the coupling graph distance_qubits dist = sum([ self.coupling_map.distance(trial_layout[g[0]][1], trial_layout[g[1]][1]) for g in gates ]) # If all gates can be applied now, we are finished # Otherwise we need to consider a deeper swap circuit if dist == len(gates): trial_circ.compose_back(circ, identity_wire_map) break # Increment the depth d += 1 # Either we have succeeded at some depth d < dmax or failed dist = sum([ self.coupling_map.distance(trial_layout[g[0]][1], trial_layout[g[1]][1]) for g in gates ]) if dist == len(gates): if d < best_d: best_circ = trial_circ best_layout = trial_layout best_d = min(best_d, d) if best_circ is None: return False, None, None, None, False return True, best_circ, best_d, best_layout, False
def run(self, dag): """ Run the CNOTCascadesTransform pass over a dag circuit. After the transformation, proceeds to check for possible one-qubit gates optimizations and CNOT cancellations, as subsequent CNOT nearest-neighbor sequences could create the opportunity for useful circuit simplifications. Args: dag (DAGCircuit): the dag circuit to be searched for CNOT cascades. Returns: new_dag (DAGCircuit): a new dag where all CNOT cascades have been transformed. """ # prepare new dag new_dag = DAGCircuit() new_dag.name = dag.name self._num_qubits = dag.num_qubits() for q_reg in dag.qregs.values(): new_dag.add_qreg(q_reg) for c_reg in dag.cregs.values(): new_dag.add_creg(c_reg) i = 0 for q_reg in dag.qregs.values(): for q in q_reg: self._wires_to_id[q] = i self._id_to_wires[i] = q i += 1 depth = new_dag.depth() while True: new_dag = Optimize1qGates().run(new_dag) new_dag = CXCancellation().run(new_dag) new_depth = new_dag.depth() if new_depth < depth: depth = new_depth else: break # get dag layers self._layers = [layer['graph'] for layer in dag.layers()] # this is the list of new layers for the nearest-neighbor CNOT sequences self._extra_layers = {l: [] for l in range(len(self._layers))} # loop through all layers for i, layer in enumerate(self._layers): if i != 0: # add nearest-neighbor CNOT sequences in the right layer for gate in self._extra_layers[i - 1]: new_dag.apply_operation_back(*gate) # check all gates in the layer for gate in layer.op_nodes(): temp = None # do not add gates that have been used in the transformation process if gate in self._skip: continue # every cnot could be the starting point for a CNOT cascade elif gate.name == 'cx': logger.debug('Check Cascade %s with qargs: %s\n' % (gate.name, gate.qargs)) # check for a CNOT cascade temp = self.check_cascade(gate, i) if temp is not None: logger.info('Cascade Starts at %s with qargs: %s\n' % (gate.name, gate.qargs)) self._skip.extend(temp) else: logger.debug( 'Check Inverse Cascade at %s with qargs: %s\n' % (gate.name, gate.qargs)) # check for an inverted CNOT cascade temp = self.check_inverse_cascade(gate, i) if temp is not None: logger.info( 'Inverse Cascade Starts at %s with qargs: %s\n' % (gate.name, gate.qargs)) self._skip.extend(temp) else: # apply the CNOT if no cascade was found self._skip.append(gate) logger.debug( 'Found Nothing at %s with qargs: %s\n' % (gate.name, gate.qargs)) new_dag.apply_operation_back(gate.op, gate.qargs, gate.cargs, gate.condition) else: self._skip.append(gate) new_dag.apply_operation_back(gate.op, gate.qargs, gate.cargs, gate.condition) logger.debug('Cascades found: %s' % str(self._extra_layers)) # optimize dag after transformation depth = new_dag.depth() while True: new_dag = Optimize1qGates().run(new_dag) new_dag = CXCancellation().run(new_dag) new_depth = new_dag.depth() if new_depth < depth: depth = new_depth else: break return new_dag
def run(self, dag): """Map a DAGCircuit onto a CouplingGraph using swap gates. Args: dag (DAGCircuit): input DAG circuit Returns: DAGCircuit: object containing a circuit equivalent to circuit_graph that respects couplings in coupling_map, and a layout dict mapping qubits of circuit_graph into qubits of coupling_map. The layout may differ from the initial_layout if the first layer of gates cannot be executed on the initial_layout. Raises: TranspilerError: if there was any error during the mapping or with the parameters. """ if dag.width() > self.coupling_map.size(): raise TranspilerError("Not enough qubits in CouplingGraph") # Schedule the input circuit layerlist = list(dag.layers()) if self.initial_layout is None and self.property_set["layout"]: self.initial_layout = self.property_set["layout"] if self.initial_layout is not None: # update initial_layout from a user given dict{(regname,idx): (regname,idx)} # to an expected dict{(reg,idx): (reg,idx)} virtual_qubits = self.initial_layout.get_virtual_bits() self.initial_layout = {(v[0].name, v[1]): ('q', self.initial_layout[v]) for v in virtual_qubits} device_register = QuantumRegister(self.coupling_map.size(), 'q') initial_layout = {(dag.qregs[k[0]], k[1]): (device_register, v[1]) for k, v in self.initial_layout.items()} # Check the input layout circ_qubits = dag.qubits() coup_qubits = [(QuantumRegister(self.coupling_map.size(), 'q'), wire) for wire in self.coupling_map.physical_qubits] qubit_subset = [] for k, v in initial_layout.items(): qubit_subset.append(v) if k not in circ_qubits: raise TranspilerError( "initial_layout qubit %s[%d] not in input " "DAGCircuit" % (k[0].name, k[1])) if v not in coup_qubits: raise TranspilerError( "initial_layout qubit %s[%d] not in input " "CouplingGraph" % (v[0].name, v[1])) else: # Supply a default layout qubit_subset = [(QuantumRegister(self.coupling_map.size(), 'q'), wire) for wire in self.coupling_map.physical_qubits] qubit_subset = qubit_subset[0:dag.width()] initial_layout = {a: b for a, b in zip(dag.qubits(), qubit_subset)} # Find swap circuit to preceed to each layer of input circuit layout = initial_layout.copy() # Construct an empty DAGCircuit with one qreg "q" # and the same set of cregs as the input circuit dagcircuit_output = DAGCircuit() dagcircuit_output.name = dag.name dagcircuit_output.add_qreg( QuantumRegister(self.coupling_map.size(), "q")) for creg in dag.cregs.values(): dagcircuit_output.add_creg(creg) # Make a trivial wire mapping between the subcircuits # returned by swap_mapper_layer_update and the circuit # we are building identity_wire_map = {} q = QuantumRegister(self.coupling_map.size(), 'q') for j in range(self.coupling_map.size()): identity_wire_map[(q, j)] = (q, j) for creg in dag.cregs.values(): for j in range(creg.size): identity_wire_map[(creg, j)] = (creg, j) first_layer = True # True until first layer is output # Iterate over layers for i, layer in enumerate(layerlist): # Attempt to find a permutation for this layer success_flag, best_circ, best_d, best_layout, trivial_flag \ = self.layer_permutation(layer["partition"], layout, qubit_subset) # If this fails, try one gate at a time in this layer if not success_flag: 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_circ, best_d, best_layout, trivial_flag \ = self.layer_permutation(serial_layer["partition"], layout, qubit_subset) # 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: continue # Update the record of qubit positions for each inner iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( self.swap_mapper_layer_update(j, first_layer, best_layout, best_d, best_circ, serial_layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False else: # Update the record of qubit positions for each iteration layout = best_layout # Update the QASM dagcircuit_output.compose_back( self.swap_mapper_layer_update(i, first_layer, best_layout, best_d, best_circ, layerlist), identity_wire_map) # Update initial layout if first_layer: initial_layout = layout first_layer = False # 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 if first_layer: layout = initial_layout for i, layer in enumerate(layerlist): dagcircuit_output.compose_back(layer["graph"], layout) return dagcircuit_output
def layer_permutation(layer_partition, layout, qubit_subset, coupling, trials, seed=None): """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 Sergey Bravyi's algorithm. The layer_partition is a list of (qu)bit lists and each qubit is a tuple (qreg, index). The layout is a dict mapping qubits in the circuit to qubits in the coupling graph and represents the current positions of the data. The qubit_subset is the subset of qubits in the coupling graph that we have chosen to map into. The coupling is a CouplingGraph. TRIALS is the number of attempts the randomized algorithm makes. Returns: success_flag, best_circ, best_d, best_layout, trivial_flag If success_flag is True, then best_circ contains a DAGCircuit with the swap circuit, best_d 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. The trivial_flag is set if the layer has no multi-qubit gates. """ if seed is not None: np.random.seed(seed) logger.debug("layer_permutation: ----- enter -----") logger.debug("layer_permutation: layer_partition = %s", pprint.pformat(layer_partition)) logger.debug("layer_permutation: layout = %s", pprint.pformat(layout)) logger.debug("layer_permutation: qubit_subset = %s", pprint.pformat(qubit_subset)) logger.debug("layer_permutation: trials = %s", trials) rev_layout = {b: a for a, b in layout.items()} gates = [] for layer in layer_partition: if len(layer) > 2: raise MapperError("Layer contains >2 qubit gates") elif len(layer) == 2: gates.append(tuple(layer)) logger.debug("layer_permutation: gates = %s", pprint.pformat(gates)) # Can we already apply the gates? dist = sum( [coupling.distance(layout[g[0]][1], layout[g[1]][1]) for g in gates]) logger.debug("layer_permutation: dist = %s", dist) if dist == len(gates): logger.debug("layer_permutation: done already") logger.debug("layer_permutation: ----- exit -----") circ = DAGCircuit() circ.add_qreg(QuantumRegister(coupling.size(), "q")) circ.add_basis_element("CX", 2) circ.add_basis_element("cx", 2) circ.add_basis_element("swap", 2) circ.add_gate_data("cx", cx_data) circ.add_gate_data("swap", swap_data) return True, circ, 0, layout, bool(gates) # Begin loop over trials of randomized algorithm n = coupling.size() best_d = sys.maxsize # initialize best depth best_circ = None # initialize best swap circuit best_layout = None # initialize best final layout for trial in range(trials): logger.debug("layer_permutation: trial %s", trial) trial_layout = layout.copy() rev_trial_layout = rev_layout.copy() # SWAP circuit constructed this trial trial_circ = DAGCircuit() trial_circ.add_qreg(QuantumRegister(coupling.size(), "q")) # Compute Sergey's randomized distance xi = {} for i in coupling.physical_qubits: xi[(QuantumRegister(coupling.size(), 'q'), i)] = {} for i in coupling.physical_qubits: i = (QuantumRegister(coupling.size(), 'q'), i) for j in coupling.physical_qubits: j = (QuantumRegister(coupling.size(), 'q'), j) scale = 1 + np.random.normal(0, 1 / n) xi[i][j] = scale * coupling.distance(i[1], j[1])**2 xi[j][i] = xi[i][j] # Loop over depths d up to a max depth of 2n+1 d = 1 # Circuit for this swap slice circ = DAGCircuit() circ.add_qreg(QuantumRegister(coupling.size(), "q")) circ.add_basis_element("CX", 2) circ.add_basis_element("cx", 2) circ.add_basis_element("swap", 2) circ.add_gate_data("cx", cx_data) circ.add_gate_data("swap", swap_data) # Identity wire-map for composing the circuits q = QuantumRegister(coupling.size(), 'q') identity_wire_map = {(q, j): (q, j) for j in range(coupling.size())} while d < 2 * n + 1: # Set of available qubits qubit_set = set(qubit_subset) # While there are still qubits available while qubit_set: # Compute the objective function min_cost = sum([ xi[trial_layout[g[0]]][trial_layout[g[1]]] for g in gates ]) # Try to decrease objective function progress_made = False # Loop over edges of coupling graph for e in coupling.get_edges(): e = [(QuantumRegister(coupling.size(), 'q'), edge) for edge in e] # Are the qubits available? if e[0] in qubit_set and e[1] in qubit_set: # Try this edge to reduce the cost new_layout = trial_layout.copy() new_layout[rev_trial_layout[e[0]]] = e[1] new_layout[rev_trial_layout[e[1]]] = e[0] rev_new_layout = rev_trial_layout.copy() rev_new_layout[e[0]] = rev_trial_layout[e[1]] rev_new_layout[e[1]] = rev_trial_layout[e[0]] # Compute the objective function new_cost = sum([ xi[new_layout[g[0]]][new_layout[g[1]]] for g in gates ]) # Record progress if we succceed if new_cost < min_cost: logger.debug( "layer_permutation: progress! " "min_cost = %s", min_cost) progress_made = True min_cost = new_cost opt_layout = new_layout rev_opt_layout = rev_new_layout opt_edge = e # Were there any good choices? if progress_made: qubit_set.remove(opt_edge[0]) qubit_set.remove(opt_edge[1]) trial_layout = opt_layout rev_trial_layout = rev_opt_layout circ.apply_operation_back( SwapGate((opt_edge[0][0], opt_edge[0][1]), (opt_edge[1][0], opt_edge[1][1]))) logger.debug("layer_permutation: chose pair %s", pprint.pformat(opt_edge)) else: break # We have either run out of qubits or failed to improve # Compute the coupling graph distance_qubits dist = sum([ coupling.distance(trial_layout[g[0]][1], trial_layout[g[1]][1]) for g in gates ]) logger.debug("layer_permutation: dist = %s", dist) # If all gates can be applied now, we are finished # Otherwise we need to consider a deeper swap circuit if dist == len(gates): logger.debug("layer_permutation: all can be applied now") trial_circ.compose_back(circ, identity_wire_map) break # Increment the depth d += 1 logger.debug("layer_permutation: increment depth to %s", d) # Either we have succeeded at some depth d < dmax or failed dist = sum([ coupling.distance(trial_layout[g[0]][1], trial_layout[g[1]][1]) for g in gates ]) logger.debug("layer_permutation: dist = %s", dist) if dist == len(gates): if d < best_d: logger.debug("layer_permutation: got circuit with depth %s", d) best_circ = trial_circ best_layout = trial_layout best_d = min(best_d, d) if best_circ is None: logger.debug("layer_permutation: failed!") logger.debug("layer_permutation: ----- exit -----") return False, None, None, None, False logger.debug("layer_permutation: done") logger.debug("layer_permutation: ----- exit -----") return True, best_circ, best_d, best_layout, False
def run(self, dag): """Run the CommutativeCancellation pass on a dag Args: dag (DAGCircuit): the DAG to be optimized. Returns: DAGCircuit: the optimized DAG. Raises: TranspilerError: when the 1 qubit rotation gates are not found """ q_gate_list = ['cx', 'cy', 'cz', 'h', 'x', 'y', 'z'] # Gate sets to be cancelled cancellation_sets = defaultdict(lambda: []) for wire in dag.wires: wire_name = "{0}[{1}]".format(str(wire[0].name), str(wire[1])) wire_commutation_set = self.property_set['commutation_set'][wire_name] for com_set_idx, com_set in enumerate(wire_commutation_set): if com_set[0].type in ['in', 'out']: continue for node in com_set: num_qargs = len(node.qargs) if num_qargs == 1 and node.name in q_gate_list: cancellation_sets[(node.name, wire_name, com_set_idx)].append(node) if num_qargs == 1 and node.name in ['u1', 'rz', 't', 's']: cancellation_sets[('z_rotation', wire_name, com_set_idx)].append(node) elif num_qargs == 2 and node.qargs[0] == wire: second_op_name = "{0}[{1}]".format(str(node.qargs[1][0].name), str(node.qargs[1][1])) q2_key = (node.name, wire_name, second_op_name, self.property_set['commutation_set'][(node, second_op_name)]) cancellation_sets[q2_key].append(node) for cancel_set_key in cancellation_sets: set_len = len(cancellation_sets[cancel_set_key]) if ((set_len) > 1 and cancel_set_key[0] in q_gate_list): gates_to_cancel = cancellation_sets[cancel_set_key] for c_node in gates_to_cancel[:(set_len // 2) * 2]: dag.remove_op_node(c_node) elif((set_len) > 1 and cancel_set_key[0] == 'z_rotation'): run = cancellation_sets[cancel_set_key] run_qarg = run[0].qargs[0] total_angle = 0.0 # lambda for current_node in run: if (current_node.condition is not None or len(current_node.qargs) != 1 or current_node.qargs[0] != run_qarg): raise TranspilerError("internal error") if current_node.name in ['u1', 'rz']: current_angle = float(current_node.op.params[0]) elif current_node.name == 't': current_angle = sympy.pi / 4 elif current_node.name == 's': current_angle = sympy.pi / 2 # Compose gates total_angle = current_angle + total_angle # Replace the data of the first node in the run new_op = U1Gate(total_angle) new_qarg = (QuantumRegister(1, 'q'), 0) new_dag = DAGCircuit() new_dag.add_qreg(new_qarg[0]) new_dag.apply_operation_back(new_op, [new_qarg]) dag.substitute_node_with_dag(run[0], new_dag) # Delete the other nodes in the run for current_node in run[1:]: dag.remove_op_node(current_node) return dag
def depGraphToDagWithLinking(self, dep_graph, registers_in_order): # insert deallocation nodes (to avoid linking a_1 to a_2 if there is a dependency edge between the 2) for var in dep_graph.nodes: list_nodes = dep_graph.nodes[var] if list_nodes and list_nodes[0]: cur_node = list_nodes[0][-1] if isinstance(cur_node.register, AncillaRegister): if cur_node.consume_edge_out is None: de_node = Node(cur_node.register, cur_node.index, DeallocateGate(), -1, 0) dep_graph.addNode(de_node) dep_graph.connectConsumedNodes(cur_node, de_node) # now the topo order and add registers to dag as they come: # we take nodes that have no parents (and remove them from the graph, giving new orphan nodes) # + we try to avoid taking ancillasFirstNodes as much as possible, and when we have to, we pick the ones with oldest date of birth not_ancilla_alloc = [ ] # parentless nodes that are not an ancilla alloc, simple list ancilla_alloc = PriorityQueue( ) #when pop: element with least prio comes # we first fill up both of those lists for var in dep_graph.nodes: # no need to look into the deallocate nodes, they all have a parent for now list_nodes = dep_graph.nodes[var] if list_nodes and list_nodes[0]: node = list_nodes[0][0] if node.consume_edge_in is None and not node.ctrl_edges_in: if isinstance(node.register, AncillaRegister): ancilla_alloc.put( (node.register._allocation_date, node)) else: not_ancilla_alloc.append(node) # now we go through the nodes in order, creating the dag and when possible reusing ancilla spots dag = DAGCircuit() already_added = { } # registers may yield mutliple wires, we don't want to add them multiple times # we add non ancilla registers already to have them in the right order for qubit in registers_in_order: if not isinstance(qubit.register, AncillaRegister): if already_added.get(qubit.register) is None: already_added[qubit.register] = True dag.add_qreg(qubit.register) available_ancilla_slots = [ ] # ancillas that have already been deallocated ancilla_correspondance = { } # a_c[a] = b if ancilla b can reuse ancilla a wire removed_edges = { } # for each node: remove_edges[node] = [nb_removed_consume_edges, nb_removed_ctrl_edges, nb_removed_non_Ctrl_edges] const_consume = 0 const_ctrls = 1 const_non_ctrls = 2 # because we can't remove them really because they're needed to build the dag while not_ancilla_alloc or not ancilla_alloc.empty(): cur_node = None # get the node, and deal with ancilla spots if not_ancilla_alloc: cur_node = not_ancilla_alloc.pop() if cur_node.gate.name == 'deallocate': available_ancilla_slots.append( ancilla_correspondance[(cur_node.register, cur_node.index)]) else: (allocation_date, cur_node) = ancilla_alloc.get() if available_ancilla_slots: ancilla_correspondance[( cur_node.register, cur_node.index)] = available_ancilla_slots.pop() else: # we don't want to keep ancilla registers as is, because they might have way too many qubits => new single qubits for them anc_reg = QuantumRegister(1, name="anc" + cur_node.register.name + str(cur_node.index)) already_added[anc_reg] = True dag.add_qreg(anc_reg) ancilla_correspondance[(cur_node.register, cur_node.index)] = (anc_reg, 0) # add node to dag if cur_node.gate.name == 'init': register = cur_node.register if ancilla_correspondance.get( (cur_node.register, cur_node.index)) is not None: (register, ind) = ancilla_correspondance[(cur_node.register, cur_node.index)] if already_added.get(register) is None: already_added[register] = True dag.add_qreg(register) elif cur_node.gate.name != 'deallocate': #get the arguments of the gate: again assuming ctrls first, target last # we have to replace ancilla registers ctrl_reg = [ self._getQubit(ctrl_edge.node_from, ancilla_correspondance) for ctrl_edge in cur_node.ctrl_edges_in ] target_reg = self._getQubit(cur_node.consume_edge_in.node_from, ancilla_correspondance) ctrl_reg.append(target_reg) # if gate is on an ancilla, all CCXs can be replaced by RCCXs, so we go recursively into the gate definition and replace all CCXs there if isinstance(cur_node.register, AncillaRegister): cur_node.gate = _replaceCCXs(cur_node.gate) dag.apply_operation_back(cur_node.gate, ctrl_reg) # remove all outgoing edges from this node form dep_graph, and add new orphan nodes to lists if cur_node.consume_edge_out is not None: consuming_node = cur_node.consume_edge_out.node_to if removed_edges.get(consuming_node) is None: removed_edges[consuming_node] = [0, 0, 0] removed_edges[consuming_node][const_consume] += 1 if removed_edges[consuming_node][const_ctrls] == len( consuming_node.ctrl_edges_in ) and removed_edges[consuming_node][const_non_ctrls] == len( consuming_node.non_ctrl_edges_in): not_ancilla_alloc.append( consuming_node ) # it consumes some node, so not ancilla alloc for e in cur_node.edges_out: node_to = e.node_to if removed_edges.get(node_to) is None: removed_edges[node_to] = [0, 0, 0] if e.type == 'd': removed_edges[node_to][const_ctrls] += 1 else: removed_edges[node_to][const_non_ctrls] += 1 if removed_edges[node_to][const_ctrls] == len( node_to.ctrl_edges_in ) and removed_edges[node_to][const_non_ctrls] == len( node_to.non_ctrl_edges_in): if removed_edges[node_to][const_consume] == 1: not_ancilla_alloc.append( node_to ) # it consumes some node, so not ancilla alloc elif node_to.consume_edge_in is None: if isinstance(node_to.register, AncillaRegister): ancilla_alloc.put( (node_to.register._allocation_date, node_to)) else: not_ancilla_alloc.append(node_to) return dag
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 test_add_reg_duplicate(self): """ add_qreg with the same register twice is not allowed.""" dag = DAGCircuit() qr = QuantumRegister(2) dag.add_qreg(qr) self.assertRaises(DAGCircuitError, dag.add_qreg, qr)
def run(self, dag, time_unit=None): # pylint: disable=arguments-differ """Run the ASAPSchedule pass on `dag`. Args: dag (DAGCircuit): DAG to schedule. time_unit (str): Time unit to be used in scheduling: 'dt' or 's'. 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') if not time_unit: 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_back(Delay(idle_duration, unit), [q]) for node in 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) duration = self.durations.get(node.op, node.qargs, unit=time_unit) # set duration for each instruction (tricky but necessary) new_op = node.op.copy( ) # need different op instance to store duration new_op.duration = duration new_op.unit = time_unit new_dag.apply_operation_back(new_op, node.qargs, node.cargs, node.condition) stop_time = start_time + 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.duration = circuit_duration new_dag.unit = time_unit return new_dag