def swap_mapper_layer_update(i, first_layer, best_layout, best_d, best_circ, layer_list): """Update the QASM string for an iteration of swap_mapper. i = layer number first_layer = True if this is the first layer with multi-qubit gates best_layout = layout returned from swap algorithm best_d = depth returned from swap algorithm best_circ = swap circuit returned from swap algorithm layer_list = list of circuit objects for each layer Return DAGCircuit object to append to the output DAGCircuit. """ layout = best_layout layout_max_index = max(map(lambda x: x[1]+1, layout.values())) dagcircuit_output = DAGCircuit() dagcircuit_output.add_qreg("q", layout_max_index) # Identity wire-map for composing the circuits identity_wire_map = {('q', j): ('q', j) for j in range(layout_max_index)} # If this is the first layer with multi-qubit gates, # output all layers up to this point and ignore any # swap gates. Set the initial layout. if first_layer: logger.debug("update_qasm_and_layout: first multi-qubit gate layer") # Output all layers up to this point for j in range(i + 1): dagcircuit_output.compose_back(layer_list[j]["graph"], layout) # Otherwise, we output the current layer and the associated swap gates. else: # Output any swaps if best_d > 0: logger.debug("update_qasm_and_layout: swaps in this layer, " "depth %d", best_d) dagcircuit_output.compose_back(best_circ, identity_wire_map) else: logger.debug("update_qasm_and_layout: no swaps in this layer") # Output this layer dagcircuit_output.compose_back(layer_list[i]["graph"], layout) return dagcircuit_output
def __init__(self, basis=None): """Setup this backend. basis is a list of operation name strings. """ super().__init__(basis) self.prec = 15 self.creg = None self.cval = None self.circuit = DAGCircuit() if basis: self.basis = basis else: self.basis = [] self.listen = True self.in_gate = "" self.gates = OrderedDict()
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_nodes(): 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[0] not in swap_layer.qregs.values(): swap_layer.add_qreg(qreg[0]) # create the swap operation 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): """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) sim = UnitarySimulatorPy() # 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]) qobj = assemble_circuits(subcirc) unitary_matrix = sim.run(qobj).result().get_unitary() unitary = Unitary(unitary_matrix) 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 run(self, dag): """Run the MergeAdjacentBarriers pass on `dag`.""" # sorted to so that they are in the order they appear in the DAG # so ancestors/descendants makes sense barriers = [ nd for nd in dag.topological_op_nodes() if nd.name == 'barrier' ] # get dict of barrier merges node_to_barrier_qubits = MergeAdjacentBarriers._collect_potential_merges( dag, barriers) if not node_to_barrier_qubits: return dag # add the merged barriers to a new DAG 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) # go over current nodes, and add them to the new dag for node in dag.topological_op_nodes(): if node.name == 'barrier': if node in node_to_barrier_qubits: qubits = node_to_barrier_qubits[node] # qubits are stored as a set, need to convert to a list new_dag.apply_operation_back(Barrier(len(qubits)), qargs=list(qubits)) else: # 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 _layer_update(self, i, first_layer, best_layout, best_depth, best_circuit, layer_list): """Provide a DAGCircuit for a new mapped layer. i (int) = layer number first_layer (bool) = True if this is the first layer in the circuit with any multi-qubit gates best_layout (Layout) = layout returned from _layer_permutation best_depth (int) = depth returned from _layer_permutation best_circuit (DAGCircuit) = swap circuit returned from _layer_permutation layer_list (list) = list of DAGCircuit objects for each layer, output of DAGCircuit layers() method Return a DAGCircuit object to append to the output DAGCircuit that the _mapper method is building. """ layout = best_layout logger.debug("layer_update: layout = %s", pformat(layout)) logger.debug("layer_update: self.initial_layout = %s", pformat(self.initial_layout)) dagcircuit_output = DAGCircuit() for register in layout.get_virtual_bits().keys(): if register[0] not in dagcircuit_output.qregs.values(): dagcircuit_output.add_qreg(register[0]) # If this is the first layer with multi-qubit gates, # output all layers up to this point and ignore any # swap gates. Set the initial layout. if first_layer: logger.debug("layer_update: first multi-qubit gate layer") # Output all layers up to this point for j in range(i + 1): # Make qubit edge map and extend by classical bits edge_map = layout.combine_into_edge_map(self.initial_layout) for bit in dagcircuit_output.clbits(): edge_map[bit] = bit dagcircuit_output.compose_back(layer_list[j]["graph"], edge_map) # Otherwise, we output the current layer and the associated swap gates. else: # Output any swaps if best_depth > 0: logger.debug("layer_update: there are swaps in this layer, " "depth %d", best_depth) dagcircuit_output.extend_back(best_circuit) else: logger.debug("layer_update: there are no swaps in this layer") # Make qubit edge map and extend by classical bits edge_map = layout.combine_into_edge_map(self.initial_layout) for bit in dagcircuit_output.clbits(): edge_map[bit] = bit # Output this layer dagcircuit_output.compose_back(layer_list[i]["graph"], edge_map) return dagcircuit_output
def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the LocalNoisePass pass on `dag`. Args: dag: DAG to be changed. Returns: A changed DAG. Raises: TranspilerError: if generated operation is not valid. """ qubit_indices = {qubit: idx for idx, qubit in enumerate(dag.qubits)} for node in dag.topological_op_nodes(): if self._ops and not isinstance(node.op, self._ops): continue qubits = [qubit_indices[q] for q in node.qargs] new_op = self._func(node.op, qubits) if new_op is None: # Edge case where we are replacing a node with nothing (removing node) if self._method == "replace": dag.remove_op_node(node) continue if isinstance(new_op, ReadoutError): raise TranspilerError( "Insertions of ReadoutError is not yet supported.") # Initialize new node dag new_dag = DAGCircuit() new_dag.add_qubits(node.qargs) new_dag.add_clbits(node.cargs) # If appending re-apply original op node first if self._method == "append": new_dag.apply_operation_back(node.op, qargs=node.qargs) # If the new op is not a QuantumCircuit or Instruction, attempt # to conver to an Instruction if not isinstance(new_op, (QuantumCircuit, Instruction)): try: new_op = new_op.to_instruction() except AttributeError as att_err: raise TranspilerError( "Function must return an object implementing 'to_instruction' method." ) from att_err # Validate the instruction matches the number of qubits and clbits of the node if new_op.num_qubits != len(node.qargs): raise TranspilerError( f"Number of qubits of generated op {new_op.num_qubits} != " f"{len(node.qargs)} that of a reference op {node.name}") # Add the noise op returned by the function if isinstance(new_op, QuantumCircuit): # If the new op is a quantum circuit, compose its DAG with the new dag # so that it is unrolled rather than added as an opaque instruction new_dag.compose(circuit_to_dag(new_op), qubits=node.qargs, clbits=node.cargs) else: # Otherwise append the instruction returned by the function new_dag.apply_operation_back(new_op, qargs=node.qargs, cargs=node.cargs) # If prepending reapply original op node last if self._method == "prepend": new_dag.apply_operation_back(node.op, qargs=node.qargs, cargs=node.cargs) dag.substitute_node_with_dag(node, new_dag) return dag
def direction_mapper(circuit_graph, coupling_graph): """Change the direction of CNOT gates to conform to CouplingGraph. circuit_graph = input DAGCircuit coupling_graph = corresponding CouplingGraph Adds "h" to the circuit basis. Returns a DAGCircuit object containing a circuit equivalent to circuit_graph but with CNOT gate directions matching the edges of coupling_graph. Raises an exception if the circuit_graph does not conform to the coupling_graph. """ if "cx" not in circuit_graph.basis: return circuit_graph if circuit_graph.basis["cx"] != (2, 0, 0): raise MapperError("cx gate has unexpected signature %s" % circuit_graph.basis["cx"]) flipped_cx_circuit = DAGCircuit() flipped_cx_circuit.add_qreg('q', 2) flipped_cx_circuit.add_basis_element("CX", 2) flipped_cx_circuit.add_basis_element("U", 1, 0, 3) flipped_cx_circuit.add_basis_element("cx", 2) flipped_cx_circuit.add_basis_element("u2", 1, 0, 2) flipped_cx_circuit.add_basis_element("h", 1) flipped_cx_circuit.add_gate_data("cx", cx_data) flipped_cx_circuit.add_gate_data("u2", u2_data) flipped_cx_circuit.add_gate_data("h", h_data) flipped_cx_circuit.apply_operation_back("h", [("q", 0)]) flipped_cx_circuit.apply_operation_back("h", [("q", 1)]) flipped_cx_circuit.apply_operation_back("cx", [("q", 1), ("q", 0)]) flipped_cx_circuit.apply_operation_back("h", [("q", 0)]) flipped_cx_circuit.apply_operation_back("h", [("q", 1)]) cg_edges = coupling_graph.get_edges() for cx_node in circuit_graph.get_named_nodes("cx"): nd = circuit_graph.multi_graph.node[cx_node] cxedge = tuple(nd["qargs"]) if cxedge in cg_edges: logger.debug("cx %s[%d], %s[%d] -- OK", cxedge[0][0], cxedge[0][1], cxedge[1][0], cxedge[1][1]) continue elif (cxedge[1], cxedge[0]) in cg_edges: circuit_graph.substitute_circuit_one(cx_node, flipped_cx_circuit, wires=[("q", 0), ("q", 1)]) logger.debug("cx %s[%d], %s[%d] -FLIP", cxedge[0][0], cxedge[0][1], cxedge[1][0], cxedge[1][1]) else: raise MapperError("circuit incompatible with CouplingGraph: " "cx on %s" % pprint.pformat(cxedge)) return circuit_graph
class TestDagOperations(QiskitTestCase): """Test ops inside the dag""" def setUp(self): self.dag = DAGCircuit() qreg = QuantumRegister(3, 'qr') creg = ClassicalRegister(2, 'cr') self.dag.add_qreg(qreg) self.dag.add_creg(creg) self.qubit0 = qreg[0] self.qubit1 = qreg[1] self.qubit2 = qreg[2] self.clbit0 = creg[0] self.clbit1 = creg[1] self.condition = (creg, 3) def test_apply_operation_back(self): """The apply_operation_back() method.""" self.dag.apply_operation_back(HGate(self.qubit0), condition=None) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1), condition=None) self.dag.apply_operation_back(Measure(self.qubit1, self.clbit1), condition=None) self.dag.apply_operation_back(XGate(self.qubit1), condition=self.condition) self.dag.apply_operation_back(Measure(self.qubit0, self.clbit0), condition=None) self.dag.apply_operation_back(Measure(self.qubit1, self.clbit1), condition=None) self.assertEqual(len(self.dag.multi_graph.nodes), 16) self.assertEqual(len(self.dag.multi_graph.edges), 17) def test_apply_operation_front(self): """The apply_operation_front() method""" self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_front(Reset(self.qubit0)) h_node = self.dag.op_nodes(op=HGate).pop() reset_node = self.dag.op_nodes(op=Reset).pop() self.assertIn(reset_node, set(self.dag.multi_graph.predecessors(h_node))) def test_get_op_nodes_all(self): """The method dag.op_nodes() returns all op nodes""" self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(Reset(self.qubit0)) op_nodes = self.dag.op_nodes() self.assertEqual(len(op_nodes), 3) for node in op_nodes: self.assertIsInstance(self.dag.multi_graph.nodes[node]["op"], Instruction) def test_get_op_nodes_particular(self): """The method dag.gates_nodes(op=AGate) returns all the AGate nodes""" self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(HGate(self.qubit1)) self.dag.apply_operation_back(Reset(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) op_nodes = self.dag.op_nodes(op=HGate) self.assertEqual(len(op_nodes), 2) op_node_1 = op_nodes.pop() op_node_2 = op_nodes.pop() self.assertIsInstance(self.dag.multi_graph.nodes[op_node_1]["op"], HGate) self.assertIsInstance(self.dag.multi_graph.nodes[op_node_2]["op"], HGate) def test_quantum_successors(self): """The method dag.quantum_successors() returns successors connected by quantum edges""" self.dag.apply_operation_back(Measure(self.qubit1, self.clbit1)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(Reset(self.qubit0)) successor_measure = self.dag.quantum_successors( self.dag.named_nodes('measure').pop()) self.assertEqual(len(successor_measure), 1) cnot_node = successor_measure[0] self.assertIsInstance(self.dag.multi_graph.nodes[cnot_node]["op"], CnotGate) successor_cnot = self.dag.quantum_successors(cnot_node) self.assertEqual(len(successor_cnot), 2) self.assertEqual(self.dag.multi_graph.nodes[successor_cnot[0]]["type"], 'out') self.assertIsInstance( self.dag.multi_graph.nodes[successor_cnot[1]]["op"], Reset) def test_get_gates_nodes(self): """The method dag.gate_nodes() returns all gate nodes""" self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(Barrier((self.qubit0, self.qubit1))) self.dag.apply_operation_back(Reset(self.qubit0)) op_nodes = self.dag.gate_nodes() self.assertEqual(len(op_nodes), 2) op_node_1 = op_nodes.pop() op_node_2 = op_nodes.pop() self.assertIsInstance(self.dag.multi_graph.nodes[op_node_1]["op"], Gate) self.assertIsInstance(self.dag.multi_graph.nodes[op_node_2]["op"], Gate) def test_two_q_gates(self): """The method dag.twoQ_gates() returns all 2Q gate nodes""" self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(Barrier((self.qubit0, self.qubit1))) self.dag.apply_operation_back(Reset(self.qubit0)) op_nodes = self.dag.twoQ_gates() self.assertEqual(len(op_nodes), 1) op_node = op_nodes.pop() self.assertIsInstance(op_node["op"], Gate) self.assertEqual(len(op_node['qargs']), 2) def test_get_named_nodes(self): """The get_named_nodes(AName) method returns all the nodes with name AName""" self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit2, self.qubit1)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit2)) self.dag.apply_operation_back(HGate(self.qubit2)) # The ordering is not assured, so we only compare the output (unordered) sets. # We use tuples because lists aren't hashable. named_nodes = self.dag.named_nodes('cx') node_qargs = { tuple(self.dag.node(node_id)["op"].qargs) for node_id in named_nodes } expected_qargs = {(self.qubit0, self.qubit1), (self.qubit2, self.qubit1), (self.qubit0, self.qubit2)} self.assertEqual(expected_qargs, node_qargs) def test_nodes_in_topological_order(self): """ The node_nums_in_topological_order() method""" self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit2, self.qubit1)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit2)) self.dag.apply_operation_back(HGate(self.qubit2)) named_nodes = self.dag.node_nums_in_topological_order() self.assertEqual([1, 3, 5, 7, 8, 9, 10, 11, 12, 13, 4, 14, 2, 15, 6], [i for i in named_nodes]) def test_dag_has_edge(self): """ Test that existence of edges between nodes is correctly identified""" self.assertTrue(self.dag.has_edge(1, 2)) self.assertTrue(self.dag.has_edge(1, 2, (QuantumRegister(3, 'qr'), 0))) self.assertFalse(self.dag.has_edge(1, 2, (QuantumRegister(3, 'qr'), 1))) self.assertFalse(self.dag.has_edge(1, 3)) self.assertFalse(self.dag.has_edge(1, 3, (QuantumRegister(3, 'qr'), 0))) def test_dag_remove_edge(self): """ Test that removing an edge as specified by a wire removes the correct edge""" q = QuantumRegister(2, 'qr') qc = QuantumCircuit(q) qc.cx(q[0], q[1]) qc.cx(q[0], q[1]) dag = circuit_to_dag(qc) node1 = 5 node2 = 6 wire = (QuantumRegister(2, 'qr'), 0) self.assertTrue(dag.has_edge(node1, node2, wire)) dag.remove_edge(node1, node2, wire) self.assertFalse(dag.has_edge(node1, node2, wire)) self.assertRaises(DAGCircuitError, dag.remove_edge, node1, node2, wire)
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, Qubit): continue global_qregs = list(dag.qregs.values()) global_index_map[wire] = global_qregs.index( wire.register) + wire.index 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) 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) return new_dag
def test_create(self): qubit0 = ('qr', 0) qubit1 = ('qr', 1) clbit0 = ('cr', 0) clbit1 = ('cr', 1) condition = None dag = DAGCircuit() dag.add_basis_element('h', 1, number_classical=0, number_parameters=0) dag.add_basis_element('cx', 2) dag.add_basis_element('x', 1) dag.add_basis_element('measure', 1, number_classical=1, number_parameters=0) dag.add_qreg('qr', 2) dag.add_creg('cr', 2) dag.apply_operation_back('h', [qubit0], [], [], condition) dag.apply_operation_back('cx', [qubit0, qubit1], [], [], condition) dag.apply_operation_back('measure', [qubit1], [clbit1], [], condition) dag.apply_operation_back('x', [qubit1], [], [], ('cr', 1)) dag.apply_operation_back('measure', [qubit0], [clbit0], [], condition) dag.apply_operation_back('measure', [qubit1], [clbit1], [], condition)
class DAGBackend(UnrollerBackend): """Backend for the unroller that creates a DAGCircuit object. Example:: qasm = Qasm(filename = "teleport.qasm").parse() dagcircuit = Unroller(qasm, DAGBackend()).execute() print(dagcircuit.qasm()) """ def __init__(self, basis=None): """Setup this backend. basis is a list of operation name strings. """ super().__init__(basis) self.prec = 15 self.creg = None self.cval = None self.circuit = DAGCircuit() if basis: self.basis = basis else: self.basis = [] self.listen = True self.in_gate = "" self.gates = OrderedDict() def set_basis(self, basis): """Declare the set of user-defined gates to emit.""" self.basis = basis def define_gate(self, name, gatedata): """Record and pass down the data for this gate.""" self.gates[name] = gatedata self.circuit.add_gate_data(name, gatedata) def version(self, version): """Accept the version string. v is a version number. """ pass def new_qreg(self, name, size): """Create a new quantum register. name = name of the register sz = size of the register """ self.circuit.add_qreg(name, size) def new_creg(self, name, size): """Create a new classical register. name = name of the register sz = size of the register """ self.circuit.add_creg(name, size) def u(self, arg, qubit, nested_scope=None): """Fundamental single qubit gate. arg is 3-tuple of Node expression objects. qubit is (regname,idx) tuple. nested_scope is a list of dictionaries mapping expression variables to Node expression objects in order of increasing nesting depth. """ if self.listen: if self.creg is not None: condition = (self.creg, self.cval) else: condition = None if "U" not in self.basis: self.basis.append("U") self.circuit.add_basis_element("U", 1, 0, 3) self.circuit.apply_operation_back( "U", [qubit], [], list(map(lambda x: x.sym(nested_scope), arg)), condition) def cx(self, qubit0, qubit1): """Fundamental two-qubit gate. qubit0 is (regname, idx) tuple for the control qubit. qubit1 is (regname, idx) tuple for the target qubit. """ if self.listen: if self.creg is not None: condition = (self.creg, self.cval) else: condition = None if "CX" not in self.basis: self.basis.append("CX") self.circuit.add_basis_element("CX", 2) self.circuit.apply_operation_back("CX", [qubit0, qubit1], [], [], condition) def measure(self, qubit, bit): """Measurement operation. qubit is (regname, idx) tuple for the input qubit. bit is (regname, idx) tuple for the output bit. """ if self.creg is not None: condition = (self.creg, self.cval) else: condition = None if "measure" not in self.basis: self.basis.append("measure") if "measure" not in self.circuit.basis: self.circuit.add_basis_element("measure", 1, 1) self.circuit.apply_operation_back( "measure", [qubit], [bit], [], condition) def barrier(self, qubitlists): """Barrier instruction. qubitlists is a list of lists of (regname, idx) tuples. """ if self.listen: names = [] for qubit in qubitlists: for j, _ in enumerate(qubit): names.append(qubit[j]) if "barrier" not in self.basis: self.basis.append("barrier") self.circuit.add_basis_element("barrier", -1) self.circuit.apply_operation_back("barrier", names) def reset(self, qubit): """Reset instruction. qubit is a (regname, idx) tuple. """ if self.creg is not None: condition = (self.creg, self.cval) else: condition = None if "reset" not in self.basis: self.basis.append("reset") self.circuit.add_basis_element("reset", 1) self.circuit.apply_operation_back("reset", [qubit], [], [], condition) def set_condition(self, creg, cval): """Attach a current condition. creg is a name string. cval is the integer value for the test. """ self.creg = creg self.cval = cval def drop_condition(self): """Drop the current condition.""" self.creg = None self.cval = None def start_gate(self, name, args, qubits, nested_scope=None): """Begin a custom gate. name is name string. args is list of Node expression objects. qubits is list of (regname, idx) tuples. nested_scope is a list of dictionaries mapping expression variables to Node expression objects in order of increasing nesting depth. """ if self.listen and name not in self.basis \ and self.gates[name]["opaque"]: raise BackendError("opaque gate %s not in basis" % name) if self.listen and name in self.basis: if self.creg is not None: condition = (self.creg, self.cval) else: condition = None self.in_gate = name self.listen = False self.circuit.add_basis_element(name, len(qubits), 0, len(args)) self.circuit.apply_operation_back( name, qubits, [], list(map(lambda x: x.sym(nested_scope), args)), condition) def end_gate(self, name, args, qubits, nested_scope=None): """End a custom gate. name is name string. args is list of Node expression objects. qubits is list of (regname, idx) tuples. nested_scope is a list of dictionaries mapping expression variables to Node expression objects in order of increasing nesting depth. """ if name == self.in_gate: self.in_gate = "" self.listen = True def get_output(self): """Returns the generated circuit.""" return self.circuit
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. 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.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 # 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_unrrolled = DagUnroller(dagcircuit_output, DAGBackend(basis.split(","))) dagcircuit_output = dag_unrrolled.expand_gates() return dagcircuit_output, initial_layout
def test_dag_get_qubits(self): """ get_qubits() method """ dag = DAGCircuit() dag.add_qreg(QuantumRegister(1, 'qr1')) dag.add_qreg(QuantumRegister(1, 'qr10')) dag.add_qreg(QuantumRegister(1, 'qr0')) dag.add_qreg(QuantumRegister(1, 'qr3')) dag.add_qreg(QuantumRegister(1, 'qr4')) dag.add_qreg(QuantumRegister(1, 'qr6')) self.assertListEqual(dag.qubits(), [(QuantumRegister(1, 'qr1'), 0), (QuantumRegister(1, 'qr10'), 0), (QuantumRegister(1, 'qr0'), 0), (QuantumRegister(1, 'qr3'), 0), (QuantumRegister(1, 'qr4'), 0), (QuantumRegister(1, 'qr6'), 0)])
def _units_used_in_delays(dag: DAGCircuit) -> Set[str]: units_used = set() for node in dag.op_nodes(op=Delay): units_used.add(node.op.unit) return units_used
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): """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. """ if self.fake_run: return self.fake_run(dag) new_dag = dag.copy_empty_like() 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.two_qubit_ops(): 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 order = current_layout.reorder_bits(new_dag.qubits) new_dag.compose(swap_layer, qubits=order) # update current_layout for swap in range(len(path) - 2): current_layout.swap(path[swap], path[swap + 1]) order = current_layout.reorder_bits(new_dag.qubits) new_dag.compose(subdag, qubits=order) return new_dag
def direction_mapper(circuit_graph, coupling_graph): """Change the direction of CNOT gates to conform to CouplingGraph. circuit_graph = input DAGCircuit coupling_graph = corresponding CouplingGraph Adds "h" to the circuit basis. Returns a DAGCircuit object containing a circuit equivalent to circuit_graph but with CNOT gate directions matching the edges of coupling_graph. Raises an exception if the circuit_graph does not conform to the coupling_graph. """ if "cx" not in circuit_graph.basis: return circuit_graph if circuit_graph.basis["cx"] != (2, 0, 0): raise MapperError("cx gate has unexpected signature %s" % circuit_graph.basis["cx"]) flipped_cx_circuit = DAGCircuit() flipped_cx_circuit.add_qreg('q', 2) flipped_cx_circuit.add_basis_element("CX", 2) flipped_cx_circuit.add_basis_element("U", 1, 0, 3) flipped_cx_circuit.add_basis_element("cx", 2) flipped_cx_circuit.add_basis_element("u2", 1, 0, 2) flipped_cx_circuit.add_basis_element("h", 1) flipped_cx_circuit.add_gate_data("cx", cx_data) flipped_cx_circuit.add_gate_data("u2", u2_data) flipped_cx_circuit.add_gate_data("h", h_data) flipped_cx_circuit.apply_operation_back("h", [("q", 0)]) flipped_cx_circuit.apply_operation_back("h", [("q", 1)]) flipped_cx_circuit.apply_operation_back("cx", [("q", 1), ("q", 0)]) flipped_cx_circuit.apply_operation_back("h", [("q", 0)]) flipped_cx_circuit.apply_operation_back("h", [("q", 1)]) cx_node_list = circuit_graph.get_named_nodes("cx") cg_edges = coupling_graph.get_edges() for cx_node in cx_node_list: nd = circuit_graph.multi_graph.node[cx_node] cxedge = tuple(nd["qargs"]) if cxedge in cg_edges: logger.debug("cx %s[%d], %s[%d] -- OK", cxedge[0][0], cxedge[0][1], cxedge[1][0], cxedge[1][1]) continue elif (cxedge[1], cxedge[0]) in cg_edges: circuit_graph.substitute_circuit_one(cx_node, flipped_cx_circuit, wires=[("q", 0), ("q", 1)]) logger.debug("cx %s[%d], %s[%d] -FLIP", cxedge[0][0], cxedge[0][1], cxedge[1][0], cxedge[1][1]) else: raise MapperError("circuit incompatible with CouplingGraph: " "cx on %s" % pprint.pformat(cxedge)) return circuit_graph
def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the RemoveBarriers pass on `dag`.""" dag.remove_all_ops_named("barrier") return dag
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_unrrolled = DagUnroller(dagcircuit_output, DAGBackend(basis.split(","))) dagcircuit_output = dag_unrrolled.expand_gates() return dagcircuit_output, initial_layout, last_layout
def run(self, dag): """Run the ConsolidateBlocks pass on `dag`. Iterate over each block and replace it with an equivalent Unitary on the same wires. """ if self.decomposer is None: return dag 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 != basis_gate_name or block[0].op.is_parameterized()): # an intermediate node that was added into the overall list new_dag.apply_operation_back(block[0].op, block[0].qargs, block[0].cargs) else: # find the qubits involved in this block block_qargs = set() block_cargs = set() for nd in block: block_qargs |= set(nd.qargs) if nd.condition: block_cargs |= set(nd.condition[0]) # convert block to a sub-circuit, then simulate unitary and add q = QuantumRegister(len(block_qargs)) # if condition in node, add clbits to circuit if len(block_cargs) > 0: c = ClassicalRegister(len(block_cargs)) subcirc = QuantumCircuit(q, c) else: 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 max_2q_depth = 20 # If depth > 20, there will be 1q gates to consolidate. if ( # pylint: disable=too-many-boolean-expressions self.force_consolidate or unitary.num_qubits > 2 or self.decomposer.num_basis_gates(unitary) < basis_count or len(subcirc) > max_2q_depth or (self.basis_gates is not None and not set(subcirc.count_ops()).issubset(self.basis_gates))): new_dag.apply_operation_back( UnitaryGate(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) return new_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]) 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 cdist2 = coupling._dist_matrix**2 # Scaling matrix scale = np.zeros((num_qubits, num_qubits)) utri_idx = np.triu_indices(num_qubits) 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 data = 1 + np.random.normal(0, 1/num_qubits, size=num_qubits*(num_qubits+1)//2) scale[utri_idx] = data xi = (scale+scale.T)*cdist2 # pylint: disable=invalid-name 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]) # 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 need_copy = True for edge in coupling.get_edges(): qubits = (trial_layout[edge[0]], trial_layout[edge[1]]) # Are the qubits available? if qubits[0] in qubit_set and qubits[1] in qubit_set: # Try this edge to reduce the cost if need_copy: new_layout = trial_layout.copy() need_copy = False 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 = (self.initial_layout[edge[0]], self.initial_layout[edge[1]]) optimal_qubits = qubits need_copy = True else: new_layout.swap(edge[0], edge[1]) # Were there any good swap choices? if cost_reduced: qubit_set.remove(optimal_qubits[0]) qubit_set.remove(optimal_qubits[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): """Run the ApplyLayout pass on `dag`. Args: dag (DAGCircuit): DAG to map. Returns: DAGCircuit: A mapped DAG (with physical qubits). Raises: TranspilerError: if no layout is found in `property_set` or no full physical qubits. """ layout = self.property_set["layout"] if not layout: raise TranspilerError( "No 'layout' is found in property_set. Please run a Layout pass in advance." ) if len(layout) != (1 + max(layout.get_physical_bits())): raise TranspilerError("The 'layout' must be full (with ancilla).") for qreg in dag.qregs.values(): self.property_set["layout"].add_register(qreg) q = QuantumRegister(len(layout), "q") new_dag = DAGCircuit() new_dag.add_qreg(q) new_dag.metadata = dag.metadata new_dag.add_clbits(dag.clbits) for creg in dag.cregs.values(): new_dag.add_creg(creg) virtual_phsyical_map = layout.get_virtual_bits() for node in dag.topological_op_nodes(): qargs = [q[virtual_phsyical_map[qarg]] for qarg in node.qargs] new_dag.apply_operation_back(node.op, qargs, node.cargs) new_dag._global_phase = dag._global_phase return new_dag
def _mapper(self, circuit_graph, coupling_graph, trials=20, seed=None): """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. seed (int): initial seed. 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, seed) 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, seed) 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 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 isinstance(com_set[0], (DAGInNode, DAGOutNode)): 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 (getattr(current_node.op, "condition", None) 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 _compile_single_circuit(circuit, backend, config=None, basis_gates=None, coupling_map=None, initial_layout=None, seed=None, pass_manager=None): """Compile a single circuit into a QobjExperiment. Args: circuit (QuantumCircuit): circuit to compile backend (BaseBackend): a backend to compile for config (dict): dictionary of parameters (e.g. noise) used by runner basis_gates (str): comma-separated basis gate set to compile to coupling_map (list): coupling map (perhaps custom) to target in mapping initial_layout (list): initial layout of qubits in mapping seed (int): random seed for simulators pass_manager (PassManager): a pass_manager for the transpiler stage Returns: QobjExperiment: the QobjExperiment to be run on the backends """ # TODO: A better solution is to have options to enable/disable optimizations num_qubits = sum((len(qreg) for qreg in circuit.get_qregs().values())) if num_qubits == 1 or coupling_map == "all-to-all": coupling_map = None # Step 2a: circuit -> dag dag_circuit = DAGCircuit.fromQuantumCircuit(circuit) # TODO: move this inside the mapper pass # pick a good initial layout if coupling_map is not already satisfied # otherwise keep it as q[i]->q[i] if (initial_layout is None and not backend.configuration['simulator'] and not _matches_coupling_map(circuit.data, coupling_map)): initial_layout = _pick_best_layout(backend, num_qubits, circuit.get_qregs()) # Step 2b: transpile (dag -> dag) dag_circuit, final_layout = transpile(dag_circuit, basis_gates=basis_gates, coupling_map=coupling_map, initial_layout=initial_layout, get_layout=True, seed=seed, pass_manager=pass_manager) # Step 2c: dag -> json # the compiled circuit to be run saved as a dag # we assume that transpile() has already expanded gates # to the target basis, so we just need to generate json list_layout = [[k, v] for k, v in final_layout.items()] if final_layout else None json_circuit = DagUnroller(dag_circuit, JsonBackend(dag_circuit.basis)).execute() # Step 3a: create the Experiment based on json_circuit experiment = QobjExperiment.from_dict(json_circuit) # Step 3b: populate the Experiment configuration and header experiment.header.name = circuit.name # TODO: place in header or config? experiment_config = deepcopy(config or {}) experiment_config.update({ 'coupling_map': coupling_map, 'basis_gates': basis_gates, 'layout': list_layout, 'memory_slots': sum(register.size for register in circuit.get_cregs().values()) }) experiment.config = QobjItem(**experiment_config) # set eval_symbols=True to evaluate each symbolic expression # TODO after transition to qobj, we can drop this experiment.header.compiled_circuit_qasm = dag_circuit.qasm( qeflag=True, eval_symbols=True) return experiment
def _layer_permutation(layer_partition, initial_layout, layout, qubit_subset, coupling, trials, qregs, rng): """Find a swap circuit that implements a permutation for this layer. Args: layer_partition (list): The layer_partition is a list of (qu)bit lists and each qubit is a tuple (qreg, index). initial_layout (Layout): The initial layout passed. layout (Layout): The layout is a Layout object mapping virtual qubits in the input circuit to physical qubits in the coupling graph. It reflects the current positions of the data. qubit_subset (list): The qubit_subset is the set of qubits in the coupling graph that we have chosen to map into, as tuples (Register, index). coupling (CouplingMap): Directed graph representing a coupling map. This coupling map should be one that was provided to the stochastic mapper. trials (int): Number of attempts the randomized algorithm makes. qregs (OrderedDict): Ordered dict of registers from input DAG. rng (RandomState): Random number generator. Returns: Tuple: success_flag, best_circuit, best_depth, best_layout, trivial_flag Raises: TranspilerError: if anything went wrong. """ logger.debug("layer_permutation: layer_partition = %s", pformat(layer_partition)) logger.debug("layer_permutation: layout = %s", pformat(layout.get_virtual_bits())) logger.debug("layer_permutation: qubit_subset = %s", pformat(qubit_subset)) logger.debug("layer_permutation: trials = %s", trials) gates = [] # list of lists of tuples [[(register, index), ...], ...] for gate_args in layer_partition: if len(gate_args) > 2: raise TranspilerError("Layer contains > 2-qubit gates") 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() for register in layout.get_virtual_bits().keys(): if register.register not in circ.qregs.values(): circ.add_qreg(register.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() for idx in range(best_edges.size // 2): slice_circuit.apply_operation_back( SwapGate(), [initial_layout[edgs[2 * idx]], initial_layout[edgs[2 * idx + 1]]], []) trial_circuit.extend_back(slice_circuit) best_circuit = trial_circuit # Otherwise, we return our result for this layer logger.debug("layer_permutation: success!") best_lay = best_layout.to_layout(qregs) return True, best_circuit, best_depth, best_lay, False
def _define_decompositions(self): """ gate cy a,b { sdg b; cx a,b; s b; } """ decomposition = DAGCircuit() q = QuantumRegister(2, "q") decomposition.add_qreg(q) decomposition.add_basis_element("s", 1, 0, 0) decomposition.add_basis_element("sdg", 1, 0, 0) decomposition.add_basis_element("cx", 2, 0, 0) rule = [SdgGate(q[1]), CnotGate(q[0], q[1]), SGate(q[1])] for inst in rule: decomposition.apply_operation_back(inst) self._decompositions = [decomposition]
def run(self, dag): """ Runs the BasicSwap pass on `dag`. Args: dag (DAGCircuit): DAG to map. Returns: DAGCircuit: A mapped 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()) current_layout = copy(self.initial_layout) for layer in dag.serial_layers(): subdag = layer['graph'] for gate in subdag.get_2q_nodes(): 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[0] not in swap_layer.qregs.values(): swap_layer.add_qreg(qreg[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 test_layers_basic(self): """ The layers() method returns a list of layers, each of them with a list of nodes.""" qreg = QuantumRegister(2, 'qr') creg = ClassicalRegister(2, 'cr') qubit0 = qreg[0] qubit1 = qreg[1] clbit0 = creg[0] clbit1 = creg[1] condition = (creg, 3) dag = DAGCircuit() dag.add_qreg(qreg) dag.add_creg(creg) dag.apply_operation_back(HGate(qubit0)) dag.apply_operation_back(CnotGate(qubit0, qubit1), condition=None) dag.apply_operation_back(Measure(qubit1, clbit1), condition=None) dag.apply_operation_back(XGate(qubit1), condition=condition) dag.apply_operation_back(Measure(qubit0, clbit0), condition=None) dag.apply_operation_back(Measure(qubit1, clbit1), condition=None) layers = list(dag.layers()) self.assertEqual(5, len(layers)) name_layers = [[ node[1]["op"].name for node in layer["graph"].multi_graph.nodes(data=True) if node[1]["type"] == "op" ] for layer in layers] self.assertEqual( [['h'], ['cx'], ['measure'], ['x'], ['measure', 'measure']], name_layers)
def run(self, dag): 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) for node in dag.op_nodes(): new_dag.apply_operation_back(node.op, node.qargs, node.cargs) logger.info('SequentialPass: adding node {node.name}') if node.name in ['barrier', 'measure']: continue new_dag.apply_operation_back(Barrier(new_dag.num_qubits()), list(new_dag.qubits), []) return new_dag
class TestDagSubstitute(QiskitTestCase): """Test substitutuing a dag node with a sub-dag""" def setUp(self): self.dag = DAGCircuit() qreg = QuantumRegister(3, 'qr') creg = ClassicalRegister(2, 'cr') self.dag.add_qreg(qreg) self.dag.add_creg(creg) self.qubit0 = qreg[0] self.qubit1 = qreg[1] self.qubit2 = qreg[2] self.clbit0 = creg[0] self.clbit1 = creg[1] self.condition = (creg, 3) self.dag.apply_operation_back(HGate(self.qubit0)) self.dag.apply_operation_back(CnotGate(self.qubit0, self.qubit1)) self.dag.apply_operation_back(XGate(self.qubit1)) def test_substitute_circuit_one_middle(self): """The method substitute_node_with_dag() replaces a in-the-middle node with a DAG.""" cx_node = self.dag.op_nodes(op=CnotGate).pop() flipped_cx_circuit = DAGCircuit() v = QuantumRegister(2, "v") flipped_cx_circuit.add_qreg(v) flipped_cx_circuit.apply_operation_back(HGate(v[0])) flipped_cx_circuit.apply_operation_back(HGate(v[1])) flipped_cx_circuit.apply_operation_back(CnotGate(v[1], v[0])) flipped_cx_circuit.apply_operation_back(HGate(v[0])) flipped_cx_circuit.apply_operation_back(HGate(v[1])) self.dag.substitute_node_with_dag(cx_node, flipped_cx_circuit, wires=[v[0], v[1]]) self.assertEqual(self.dag.count_ops()['h'], 5) def test_substitute_circuit_one_front(self): """The method substitute_node_with_dag() replaces a leaf-in-the-front node with a DAG.""" pass def test_substitute_circuit_one_back(self): """The method substitute_node_with_dag() replaces a leaf-in-the-back node with a DAG.""" pass
def run(self, dag): 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) for ii, layer in enumerate(dag.layers()): gates_1q = [] gates_2q = [] other_gates = [] for node in layer['graph'].op_nodes(): if len(node.qargs) == 2: gates_2q.append(node) elif len(node.qargs) == 1: gates_1q.append(node) else: logging.info(f'layer {ii}: other type of node {node}') other_gates.append(node) even = [] odd = [] for node in gates_1q: if node.qargs[0].index % 2 == 0: even.append(node) else: odd.append(node) logging.info( f'layer {ii}: 2q gates {len(gates_2q)}, even {len(even)} odd {len(odd)}, other {len(other_gates)}' ) if len(even) > 0: for node in even: new_dag.apply_operation_back(node.op, node.qargs, node.cargs) if not isinstance(node.op, Barrier): new_dag.apply_operation_back(Barrier(new_dag.num_qubits()), list(new_dag.qubits), []) if len(odd) > 0: for node in odd: new_dag.apply_operation_back(node.op, node.qargs, node.cargs) if not isinstance(node.op, Barrier): new_dag.apply_operation_back(Barrier(new_dag.num_qubits()), list(new_dag.qubits), []) for node in gates_2q: new_dag.apply_operation_back(node.op, node.qargs, node.cargs) if not isinstance(node.op, Barrier): new_dag.apply_operation_back(Barrier(new_dag.num_qubits()), list(new_dag.qubits), []) for node in other_gates: new_dag.apply_operation_back(node.op, node.qargs, node.cargs) if not isinstance(node.op, Barrier): new_dag.apply_operation_back(Barrier(new_dag.num_qubits()), list(new_dag.qubits), []) return new_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): """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_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 # 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(final_op.qargs[0] for final_op in final_ops) new_barrier_node = barrier_layer.apply_operation_back( Barrier(qubits=final_qubits)) # Preserve order of final ops collected earlier from the original DAG. ordered_final_nodes = [ node for node in dag.nodes_in_topological_order() if node in set(final_ops) ] # 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_node) our_descendants = barrier_layer.descendants(new_barrier_node) our_qubits = final_qubits existing_barriers = sorted(barrier_layer.named_nodes('barrier')) # remove element from the list for i, node in enumerate(existing_barriers): if node == new_barrier_node: del existing_barriers[i] break for candidate_barrier in existing_barriers: their_ancestors = barrier_layer.ancestors(candidate_barrier) their_descendants = barrier_layer.descendants(candidate_barrier) their_qubits = set(candidate_barrier.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_node = 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_node) new_barrier_node = merge_barrier_node dag.extend_back(barrier_layer) return dag
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)) # Find layout maximum index layout_max_index = max(map(lambda x: x[1] + 1, layout.values())) # 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(layout_max_index)} 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 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)) # Find layout maximum index layout_max_index = max(map(lambda x: x[1]+1, layout.values())) # Can we already apply the gates? dist = sum([coupling.distance(layout[g[0]], layout[g[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('q', layout_max_index) 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('q', layout_max_index) # Compute Sergey's randomized distance xi = {} for i in coupling.get_qubits(): xi[i] = {} for i in coupling.get_qubits(): for j in coupling.get_qubits(): scale = 1 + np.random.normal(0, 1 / n) xi[i][j] = scale * coupling.distance(i, j) ** 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('q', layout_max_index) 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 identity_wire_map = {('q', j): ('q', j) for j in range(layout_max_index)} 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(): # 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("swap", [(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 dist = sum([coupling.distance(trial_layout[g[0]], trial_layout[g[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]], trial_layout[g[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