class ConsolidateBlocks(TransformationPass): """Replace each block of consecutive gates by a single Unitary node. Pass to consolidate sequences of uninterrupted gates acting on the same qubits into a Unitary node, to be resynthesized later, to a potentially more optimal subcircuit. Notes: This pass assumes that the 'blocks_list' property that it reads is given such that blocks are in topological order. The blocks are collected by a previous pass, such as `Collect2qBlocks`. """ def __init__(self, kak_basis_gate=CXGate(), force_consolidate=False): """ConsolidateBlocks initializer. Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. force_consolidate (bool): Force block consolidation """ super().__init__() self.force_consolidate = force_consolidate self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) 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. """ new_dag = DAGCircuit() for qreg in dag.qregs.values(): new_dag.add_qreg(qreg) for creg in dag.cregs.values(): new_dag.add_creg(creg) # compute ordered indices for the global circuit wires global_index_map = {wire: idx for idx, wire in enumerate(dag.qubits)} blocks = self.property_set['block_list'] # just to make checking if a node is in any block easier all_block_nodes = {nd for bl in blocks for nd in bl} for node in dag.topological_op_nodes(): if node not in all_block_nodes: # need to add this node to find out where in the list it goes preds = [ nd for nd in dag.predecessors(node) if nd.type == 'op' ] block_count = 0 while preds: if block_count < len(blocks): block = blocks[block_count] # if any of the predecessors are in the block, remove them preds = [p for p in preds if p not in block] else: # should never occur as this would mean not all # nodes before this one topologically had been added # so not all predecessors were removed raise TranspilerError( "Not all predecessors removed due to error" " in topological order") block_count += 1 # we have now seen all predecessors # so update the blocks list to include this block blocks = blocks[:block_count] + [[node]] + blocks[block_count:] # create the dag from the updated list of blocks basis_gate_name = self.decomposer.gate.name for block in blocks: if len(block) == 1 and block[0].name != 'cx': # an intermediate node that was added into the overall list new_dag.apply_operation_back(block[0].op, block[0].qargs, block[0].cargs, block[0].condition) else: # find the qubits involved in this block block_qargs = set() for nd in block: block_qargs |= set(nd.qargs) # convert block to a sub-circuit, then simulate unitary and add block_width = len(block_qargs) q = QuantumRegister(block_width) subcirc = QuantumCircuit(q) block_index_map = self._block_qargs_to_indices( block_qargs, global_index_map) basis_count = 0 for nd in block: if nd.op.name == basis_gate_name: basis_count += 1 subcirc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) unitary = UnitaryGate( Operator(subcirc)) # simulates the circuit if self.force_consolidate or unitary.num_qubits > 2 or \ self.decomposer.num_basis_gates(unitary) != basis_count: new_dag.apply_operation_back( unitary, sorted(block_qargs, key=lambda x: block_index_map[x])) else: for nd in block: new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs, nd.condition) return new_dag def _block_qargs_to_indices(self, block_qargs, global_index_map): """Map each qubit in block_qargs to its wire position among the block's wires. Args: block_qargs (list): list of qubits that a block acts on global_index_map (dict): mapping from each qubit in the circuit to its wire position within that circuit Returns: dict: mapping from qarg to position in block """ block_indices = [global_index_map[q] for q in block_qargs] ordered_block_indices = sorted(block_indices) block_positions = { q: ordered_block_indices.index(global_index_map[q]) for q in block_qargs } return block_positions
class ConsolidateBlocks(TransformationPass): """Replace each block of consecutive gates by a single Unitary node. Pass to consolidate sequences of uninterrupted gates acting on the same qubits into a Unitary node, to be resynthesized later, to a potentially more optimal subcircuit. Notes: This pass assumes that the 'blocks_list' property that it reads is given such that blocks are in topological order. The blocks are collected by a previous pass, such as `Collect2qBlocks`. """ def __init__(self, kak_basis_gate=None, force_consolidate=False, basis_gates=None): """ConsolidateBlocks initializer. Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. force_consolidate (bool): Force block consolidation basis_gates (List(str)): Basis gates from which to choose a KAK gate. """ super().__init__() self.basis_gates = basis_gates self.force_consolidate = force_consolidate if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) elif basis_gates is not None: kak_basis_gate = unitary_synthesis._choose_kak_gate(basis_gates) if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) else: self.decomposer = None else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) 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 = dag._copy_circuit_metadata() # 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: # pylint: disable=too-many-boolean-expressions if block[0].type == 'op' \ and self.basis_gates \ and block[0].name not in self.basis_gates \ and len(block[0].cargs) == 0 and block[0].condition is None \ and isinstance(block[0].op, Gate) \ and hasattr(block[0].op, '__array__') \ and not block[0].op.is_parameterized(): new_dag.apply_operation_back(UnitaryGate(block[0].op.to_matrix()), block[0].qargs, block[0].cargs) else: # 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 _block_qargs_to_indices(self, block_qargs, global_index_map): """Map each qubit in block_qargs to its wire position among the block's wires. Args: block_qargs (list): list of qubits that a block acts on global_index_map (dict): mapping from each qubit in the circuit to its wire position within that circuit Returns: dict: mapping from qarg to position in block """ block_indices = [global_index_map[q] for q in block_qargs] ordered_block_indices = sorted(block_indices) block_positions = {q: ordered_block_indices.index(global_index_map[q]) for q in block_qargs} return block_positions
class ConsolidateBlocks(TransformationPass): """Replace each block of consecutive gates by a single Unitary node. Pass to consolidate sequences of uninterrupted gates acting on the same qubits into a Unitary node, to be resynthesized later, to a potentially more optimal subcircuit. Notes: This pass assumes that the 'blocks_list' property that it reads is given such that blocks are in topological order. The blocks are collected by a previous pass, such as `Collect2qBlocks`. """ def __init__(self, kak_basis_gate=None, force_consolidate=False, basis_gates=None): """ConsolidateBlocks initializer. Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. force_consolidate (bool): Force block consolidation basis_gates (List(str)): Basis gates from which to choose a KAK gate. """ super().__init__() self.basis_gates = None if basis_gates is not None: self.basis_gates = set(basis_gates) self.force_consolidate = force_consolidate if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) elif basis_gates is not None: kak_basis_gate = unitary_synthesis._choose_kak_gate(basis_gates) if kak_basis_gate is not None: self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) else: self.decomposer = None else: self.decomposer = TwoQubitBasisDecomposer(CXGate()) 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 # 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"] basis_gate_name = self.decomposer.gate.name all_block_gates = set() for block in blocks: if len(block) == 1 and (self.basis_gates and block[0].name not in self.basis_gates): all_block_gates.add(block[0]) dag.substitute_node(block[0], UnitaryGate(block[0].op.to_matrix())) else: basis_count = 0 outside_basis = False block_qargs = set() block_cargs = set() for nd in block: block_qargs |= set(nd.qargs) if isinstance(nd, DAGOpNode) and nd.op.condition: block_cargs |= set(nd.op.condition[0]) all_block_gates.add(nd) q = QuantumRegister(len(block_qargs)) qc = QuantumCircuit(q) if block_cargs: c = ClassicalRegister(len(block_cargs)) qc.add_register(c) block_index_map = self._block_qargs_to_indices(block_qargs, global_index_map) for nd in block: if nd.op.name == basis_gate_name: basis_count += 1 if self.basis_gates and nd.op.name not in self.basis_gates: outside_basis = True qc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) unitary = UnitaryGate(Operator(qc)) 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(block) > max_2q_depth or (self.basis_gates is not None and outside_basis) ): dag.replace_block_with_op(block, unitary, block_index_map, cycle_check=False) # If 1q runs are collected before consolidate those too runs = self.property_set["run_list"] or [] for run in runs: if run[0] in all_block_gates: continue if len(run) == 1 and self.basis_gates and run[0].name not in self.basis_gates: dag.substitute_node(run[0], UnitaryGate(run[0].op.to_matrix())) else: qubit = run[0].qargs[0] operator = run[0].op.to_matrix() already_in_block = False for gate in run[1:]: if gate in all_block_gates: already_in_block = True operator = gate.op.to_matrix().dot(operator) if already_in_block: continue unitary = UnitaryGate(operator) dag.replace_block_with_op(run, unitary, {qubit: 0}, cycle_check=False) return dag def _block_qargs_to_indices(self, block_qargs, global_index_map): """Map each qubit in block_qargs to its wire position among the block's wires. Args: block_qargs (list): list of qubits that a block acts on global_index_map (dict): mapping from each qubit in the circuit to its wire position within that circuit Returns: dict: mapping from qarg to position in block """ block_indices = [global_index_map[q] for q in block_qargs] ordered_block_indices = {bit: index for index, bit in enumerate(sorted(block_indices))} block_positions = {q: ordered_block_indices[global_index_map[q]] for q in block_qargs} return block_positions
class ConsolidateBlocks(TransformationPass): """ Pass to consolidate sequences of uninterrupted gates acting on the same qubits into a Unitary node, to be resynthesized later, to a potentially more optimal subcircuit. Important note: this pass assumes that the 'blocks_list' property that it reads is given such that blocks are in topological order. """ def __init__(self, kak_basis_gate=CnotGate(), force_consolidate=False): """ Args: kak_basis_gate (Gate): Basis gate for KAK decomposition. force_consolidate (bool): Force block consolidation """ super().__init__() self.force_consolidate = force_consolidate self.decomposer = TwoQubitBasisDecomposer(kak_basis_gate) 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'] nodes_seen = set() basis_gate_name = self.decomposer.gate.name 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) basis_count = 0 for nd in block: if nd.op.name == basis_gate_name: basis_count += 1 nodes_seen.add(nd) subcirc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) unitary = UnitaryGate(Operator(subcirc)) # simulates the circuit 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: nodes_seen.add(nd) new_dag.apply_operation_back(nd.op, nd.qargs, nd.cargs) 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 _block_qargs_to_indices(self, block_qargs, global_index_map): """ Map each qubit in block_qargs to its wire position among the block's wires. Args: block_qargs (list): list of qubits that a block acts on global_index_map (dict): mapping from each qubit in the circuit to its wire position within that circuit Returns: dict: mapping from qarg to position in block """ block_indices = [global_index_map[q] for q in block_qargs] ordered_block_indices = sorted(block_indices) block_positions = {q: ordered_block_indices.index(global_index_map[q]) for q in block_qargs} return block_positions