diff --git a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py index 34d51a17fe4a..c05d5f023b44 100644 --- a/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py +++ b/qiskit/transpiler/passes/optimization/collect_multiqubit_blocks.py @@ -33,13 +33,18 @@ class CollectMultiQBlocks(AnalysisPass): Some gates may not be present in any block (e.g. if the number of operands is greater than ``max_block_size``) + By default, blocks are collected in the direction from the inputs towards the + outputs of the DAG. The option ``collect_from_back`` allows to change this + direction, that is to collect blocks from the outputs towards the inputs. + Note that the blocks are still reported in a valid topological order. + A Disjoint Set Union data structure (DSU) is used to maintain blocks as gates are processed. This data structure points each qubit to a set at all times and the sets correspond to current blocks. These change over time and the data structure allows these changes to be done quickly. """ - def __init__(self, max_block_size=2): + def __init__(self, max_block_size=2, collect_from_back=False): super().__init__() self.parent = {} # parent array for the union @@ -49,6 +54,7 @@ def __init__(self, max_block_size=2): self.gate_groups = {} # current gate lists for the groups self.max_block_size = max_block_size # maximum block size + self.collect_from_back = collect_from_back # backward collection def find_set(self, index): """DSU function for finding root of set of items @@ -127,6 +133,10 @@ def collect_key(x): op_nodes = dag.topological_op_nodes(key=collect_key) + # When collecting from the back, the order of nodes is reversed + if self.collect_from_back: + op_nodes = reversed(list(op_nodes)) + for nd in op_nodes: can_process = True makes_too_big = False @@ -222,6 +232,11 @@ def collect_key(x): if item == index and len(self.gate_groups[index]) != 0: block_list.append(self.gate_groups[index][:]) + # When collecting from the back, both the order of the blocks + # and the order of nodes in each block should be reversed. + if self.collect_from_back: + block_list = [block[::-1] for block in block_list[::-1]] + self.property_set["block_list"] = block_list return dag diff --git a/releasenotes/notes/add-option-collect-from-back-cde10ee5e2e4ea9f.yaml b/releasenotes/notes/add-option-collect-from-back-cde10ee5e2e4ea9f.yaml new file mode 100644 index 000000000000..684b095905a0 --- /dev/null +++ b/releasenotes/notes/add-option-collect-from-back-cde10ee5e2e4ea9f.yaml @@ -0,0 +1,11 @@ +--- +features_transpiler: + - | + Added a new option, ``collect_from_back``, to + :class:`~qiskit.transpiler.passes.CollectMultiQBlocks`. + When set to ``True``, the blocks are collected in the reverse direction, + from the outputs towards the inputs of the circuit. The blocks are still + reported following the normal topological order. + This leads to an additional flexibility provided by the pass, and + additional optimization opportunities when combined with a circuit + resynthesis method. diff --git a/test/python/transpiler/test_collect_multiq_blocks.py b/test/python/transpiler/test_collect_multiq_blocks.py index 2d4bc8783764..0f7eb5dd9539 100644 --- a/test/python/transpiler/test_collect_multiq_blocks.py +++ b/test/python/transpiler/test_collect_multiq_blocks.py @@ -290,6 +290,46 @@ def test_larger_blocks(self): pass_manager.run(qc) + def test_collect_from_back(self): + """Test the option to collect blocks from the outputs towards + the inputs. + ┌───┐ + q_0: ┤ H ├──■────■────■─────── + └───┘┌─┴─┐ │ │ + q_1: ─────┤ X ├──┼────┼─────── + └───┘┌─┴─┐ │ + q_2: ──────────┤ X ├──┼─────── + └───┘┌─┴─┐┌───┐ + q_3: ───────────────┤ X ├┤ H ├ + └───┘└───┘ + """ + qc = QuantumCircuit(4) + qc.h(0) + qc.cx(0, 1) + qc.cx(0, 2) + qc.cx(0, 3) + qc.h(3) + + dag = circuit_to_dag(qc) + # For the circuit above, the topological order is unique + topo_ops = list(dag.topological_op_nodes()) + + # When collecting blocks of size-3 using the default direction, + # the first block should contain the H-gate and two CX-gates, + # and the second block should contain a single CX-gate and an H-gate. + pass_ = CollectMultiQBlocks(max_block_size=3, collect_from_back=False) + pass_.run(dag) + expected_blocks = [[topo_ops[0], topo_ops[1], topo_ops[2]], [topo_ops[3], topo_ops[4]]] + self.assertEqual(pass_.property_set["block_list"], expected_blocks) + + # When collecting blocks of size-3 using the opposite direction, + # the first block should contain the H-gate and a single CX-gate, + # and the second block should contain two CX-gates and an H-gate. + pass_ = CollectMultiQBlocks(max_block_size=3, collect_from_back=True) + pass_.run(dag) + expected_blocks = [[topo_ops[0], topo_ops[1]], [topo_ops[2], topo_ops[3], topo_ops[4]]] + self.assertEqual(pass_.property_set["block_list"], expected_blocks) + if __name__ == "__main__": unittest.main()