From 4e456c1f797876eb24e21768e4f271b44e145be8 Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Sat, 16 Apr 2022 22:11:06 +0530 Subject: [PATCH 1/7] make passes target aware --- .../optimization/optimize_1q_commutation.py | 14 ++- .../optimization/optimize_1q_decomposition.py | 98 +++++++++++++++---- 2 files changed, 92 insertions(+), 20 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py index 1836d5112088..63d1f5d8b9d6 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_commutation.py @@ -60,18 +60,22 @@ class Optimize1qGatesSimpleCommutation(TransformationPass): # NOTE: A run from `dag.collect_1q_runs` is always nonempty, so we sometimes use an empty list # to signify the absence of a run. - def __init__(self, basis=None, run_to_completion=False): + def __init__(self, basis=None, run_to_completion=False, target=None): """ Args: basis (List[str]): See also `Optimize1qGatesDecomposition`. run_to_completion (bool): If `True`, this pass retries until it is unable to do any more work. If `False`, it finds and performs one optimization, and for full optimization the user is obligated to re-call the pass until the output stabilizes. + target (Target): The target representing the backend. If specified + this will be used instead of the ``basis_gates`` parameter + """ super().__init__() self._basis = basis - self._optimize1q = Optimize1qGatesDecomposition(basis) + self._target = target + self._optimize1q = Optimize1qGatesDecomposition(basis, target) self._run_to_completion = run_to_completion @staticmethod @@ -213,6 +217,11 @@ def _step(self, dag): new_basis, new_run = self._resynthesize(run_clone) # perform the replacement if it was indeed a good idea + qubit_map = None + + if self._target: + qubit_map = {qubit: index for index, qubit in enumerate(dag.qubits)} + if self._optimize1q._substitution_checks( dag, (preceding_run or []) + run + (succeeding_run or []), @@ -222,6 +231,7 @@ def _step(self, dag): + (new_succeeding_run or QuantumCircuit(1)).data ), new_basis + new_preceding_basis + new_succeeding_basis, + qubit_map, ): if preceding_run and new_preceding_run is not None: self._replace_subdag(dag, preceding_run, new_preceding_run) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 04295fa498fb..6c465771ed52 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -28,23 +28,39 @@ class Optimize1qGatesDecomposition(TransformationPass): """Optimize chains of single-qubit gates by combining them into a single gate.""" - def __init__(self, basis=None): + def __init__(self, basis=None, target=None): """Optimize1qGatesDecomposition initializer. Args: basis (list[str]): Basis gates to consider, e.g. `['u3', 'cx']`. For the effects of this pass, the basis is the set intersection between the `basis` parameter and the Euler basis. + target (Target): The target representing the backend. If specified + this will be used instead of the ``basis_gates`` parameter + """ super().__init__() - self._target_basis = basis + # change name so as to not confuse with target basis + self._basis_set = basis self._decomposers = None + self._target = target - if basis: + # try to get the op names of the target to see what are + # the needed decomposers + if basis or target: self._decomposers = {} - basis_set = set(basis) euler_basis_gates = one_qubit_decompose.ONE_QUBIT_EULER_BASIS_GATES + + if basis: + basis_set = set(basis) + # get all the possible operations that could be + # performed in the target + + # target basis supersedes original + if target: + basis_set = target.operation_names + for euler_basis_name, gates in euler_basis_gates.items(): if set(gates).issubset(basis_set): basis_copy = copy.copy(self._decomposers) @@ -76,14 +92,13 @@ def _resynthesize_run(self, run): operator = gate.op.to_matrix().dot(operator) new_circs = {k: v._decompose(operator) for k, v in self._decomposers.items()} - new_basis, new_circ = None, None if len(new_circs) > 0: new_basis, new_circ = min(new_circs.items(), key=lambda x: len(x[1])) return new_basis, new_circ - def _substitution_checks(self, dag, old_run, new_circ, new_basis): + def _substitution_checks(self, dag, old_run, new_circ, new_basis, qubit_map): """ Returns `True` when it is recommended to replace `old_run` with `new_circ`. """ @@ -101,9 +116,28 @@ def _substitution_checks(self, dag, old_run, new_circ, new_basis): # does this run have uncalibrated gates? uncalibrated_p = not has_cals_p or any(not dag.has_calibration_for(g) for g in old_run) # does this run have gates not in the image of ._decomposers _and_ uncalibrated? + def _not_in_basis(gate): + """To check if gate in basis or not + + Args: + gate (Gate): gate to check + + Returns: + Bool : whether Gate is supported by target or in basis set + """ + if self._target: + in_target = self._target.instruction_supported( + gate.name, tuple(qubit_map[bit] for bit in gate.qargs) + ) + return not in_target + if self._basis_set: + in_basis = gate.name in self._basis_set + return not in_basis + + return True # basis is None and target is None too + uncalibrated_and_not_basis_p = any( - g.name not in self._target_basis and (not has_cals_p or not dag.has_calibration_for(g)) - for g in old_run + _not_in_basis(g) and (not has_cals_p or not dag.has_calibration_for(g)) for g in old_run ) if rewriteable_and_in_basis_p and len(old_run) < len(new_circ): @@ -114,7 +148,7 @@ def _substitution_checks(self, dag, old_run, new_circ, new_basis): + "\n".join([str(node.op) for node in old_run]) + "\n\nand got\n\n" + "\n".join([str(node[0]) for node in new_circ]) - + f"\n\nbut the original was native (for {self._target_basis}) and the new value " + + f"\n\nbut the original was native (for {self._basis_set}) and the new value " "is longer. This indicates an efficiency bug in synthesis. Please report it by " "opening an issue here: " "https://github.com/Qiskit/qiskit-terra/issues/new/choose", @@ -153,21 +187,49 @@ def run(self, dag): return dag runs = dag.collect_1q_runs() + + # required for checking instruction support + qubit_map = None + if self._target: + qubit_map = {qubit: index for index, qubit in enumerate(dag.qubits)} + for run in runs: # SPECIAL CASE: Don't bother to optimize single U3 gates which are in the basis set. # The U3 decomposer is only going to emit a sequence of length 1 anyhow. - if "u3" in self._target_basis and len(run) == 1 and isinstance(run[0].op, U3Gate): - # Toss U3 gates equivalent to the identity; there we get off easy. - if np.allclose(run[0].op.to_matrix(), np.eye(2), 1e-15, 0): - dag.remove_op_node(run[0]) - continue - # We might rewrite into lower `u`s if they're available. - if "u2" not in self._target_basis and "u1" not in self._target_basis: - continue + if isinstance(run[0].op, U3Gate) and len(run) == 1: + # try to optimize + if self._target: + if self._target.instruction_supported( + "u3", tuple(qubit_map[bit] for bit in run[0].qargs) + ): + if np.allclose(run[0].op.to_matrix(), np.eye(2), 1e-15, 0): + dag.remove_op_node(run[0]) + continue + # if u2 or u1 supported on this qubit, we may try decomposition + u21_in_target = "u2" in self._target.instruction_supported( + "u2", tuple(qubit_map[bit] for bit in run[0].qargs) + ) or "u1" in self._target.instruction_supported( + "u1", tuple(qubit_map[bit] for bit in run[0].qargs) + ) + if not u21_in_target: + continue + + elif "u3" in self._basis_set: + # Toss U3 gates equivalent to the identity; there we get off easy. + if np.allclose(run[0].op.to_matrix(), np.eye(2), 1e-15, 0): + dag.remove_op_node(run[0]) + continue + # if u21 supported, try to decompose, else continue + u21_in_basis = "u2" in self._basis_set or "u1" in self._basis_set + + if not u21_in_basis: + continue new_basis, new_circ = self._resynthesize_run(run) - if new_circ is not None and self._substitution_checks(dag, run, new_circ, new_basis): + if new_circ is not None and self._substitution_checks( + dag, run, new_circ, new_basis, qubit_map + ): new_dag = circuit_to_dag(new_circ) dag.substitute_node_with_dag(run[0], new_dag) # Delete the other nodes in the run From 35d293c1d2c4b3d4bc7aa06eb9ed043669b417d6 Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Sat, 16 Apr 2022 22:47:49 +0530 Subject: [PATCH 2/7] add docs --- .../passes/optimization/optimize_1q_decomposition.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py index 6c465771ed52..5c66680aa891 100644 --- a/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py +++ b/qiskit/transpiler/passes/optimization/optimize_1q_decomposition.py @@ -117,13 +117,10 @@ def _substitution_checks(self, dag, old_run, new_circ, new_basis, qubit_map): uncalibrated_p = not has_cals_p or any(not dag.has_calibration_for(g) for g in old_run) # does this run have gates not in the image of ._decomposers _and_ uncalibrated? def _not_in_basis(gate): - """To check if gate in basis or not - - Args: - gate (Gate): gate to check + """ + To check if gate in basis or not - Returns: - Bool : whether Gate is supported by target or in basis set + Returns `True` when Gate is not supported by target or in basis set. """ if self._target: in_target = self._target.instruction_supported( From 30882b66143ecafc8bfc3a37c6c15eca864a4805 Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Thu, 21 Apr 2022 23:21:59 +0530 Subject: [PATCH 3/7] change logic for decompose pass --- qiskit/transpiler/passes/basis/decompose.py | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index ffbb80152be1..d3a69fc59646 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -44,6 +44,7 @@ def __init__( self.gates_to_decompose = gate else: self.gates_to_decompose = gates_to_decompose + self._make_gate_lists() @property def gate(self) -> Gate: @@ -74,6 +75,19 @@ def gate(self, value): stacklevel=2, ) self.gates_to_decompose = value + self._make_gate_lists() + + def _make_gate_lists(self): + """Make comparison lists for the gates""" + if not isinstance(self.gates_to_decompose, list): + gates = [self.gates_to_decompose] + else: + gates = self.gates_to_decompose + + self._strings_list, self._gate_type_list = [], [] + if gates is not None: + self._strings_list = [s for s in gates if isinstance(s, str)] + self._gate_type_list = [g for g in gates if isinstance(g, type)] def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the Decompose pass on `dag`. @@ -114,22 +128,19 @@ def _should_decompose(self, node) -> bool: else: gates = self.gates_to_decompose - strings_list = [s for s in gates if isinstance(s, str)] - gate_type_list = [g for g in gates if isinstance(g, type)] - if hasattr(node.op, "label") and node.op.label is not None: has_label = True if has_label and ( # check if label or label wildcard is given - node.op.label in gates or any(fnmatch(node.op.label, p) for p in strings_list) + node.op.label in gates or any(fnmatch(node.op.label, p) for p in self._strings_list) ): return True elif not has_label and ( # check if name or name wildcard is given - node.name in gates or any(fnmatch(node.name, p) for p in strings_list) + node.name in gates or any(fnmatch(node.name, p) for p in self._strings_list) ): return True elif not has_label and ( # check if Gate type given - any(isinstance(node.op, op) for op in gate_type_list) + any(isinstance(node.op, op) for op in self._gate_type_list) ): return True else: From 08a2b0dbea02407ff3ba47b15a963a48f2f35f78 Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Mon, 25 Apr 2022 22:45:49 +0530 Subject: [PATCH 4/7] add delete before deepcopy --- qiskit/transpiler/passes/utils/dag_fixed_point.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/utils/dag_fixed_point.py b/qiskit/transpiler/passes/utils/dag_fixed_point.py index 2f9f486545b5..dd3a89acd4cd 100644 --- a/qiskit/transpiler/passes/utils/dag_fixed_point.py +++ b/qiskit/transpiler/passes/utils/dag_fixed_point.py @@ -32,5 +32,5 @@ def run(self, dag): else: fixed_point_reached = self.property_set["_dag_fixed_point_previous_dag"] == dag self.property_set["dag_fixed_point"] = fixed_point_reached - + del self.property_set["_dag_fixed_point_previous_dag"] self.property_set["_dag_fixed_point_previous_dag"] = deepcopy(dag) From 554bfeb0291433cd321729b88c1b84fb9ebf5b1a Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Tue, 26 Apr 2022 23:02:20 +0530 Subject: [PATCH 5/7] change list to set in Adjacent Barriers --- qiskit/transpiler/passes/utils/merge_adjacent_barriers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py index 129fce9469db..8620d5340e20 100644 --- a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py +++ b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py @@ -101,7 +101,7 @@ def _collect_potential_merges(dag, barriers): # Start from the first barrier current_barrier = barriers[0] end_of_barrier = current_barrier - current_barrier_nodes = [current_barrier] + current_barrier_nodes = set([current_barrier]) current_qubits = set(current_barrier.qargs) current_ancestors = dag.ancestors(current_barrier) @@ -140,7 +140,7 @@ def _collect_potential_merges(dag, barriers): barrier_to_add = Barrier(len(current_qubits)) end_of_barrier = next_barrier - current_barrier_nodes.append(end_of_barrier) + current_barrier_nodes.add(end_of_barrier) continue @@ -156,10 +156,10 @@ def _collect_potential_merges(dag, barriers): current_descendants = dag.descendants(next_barrier) barrier_to_add = Barrier(len(current_qubits)) - current_barrier_nodes = [] + current_barrier_nodes = set() end_of_barrier = next_barrier - current_barrier_nodes.append(end_of_barrier) + current_barrier_nodes.add(end_of_barrier) if barrier_to_add: node_to_barrier_qubits[end_of_barrier] = current_qubits From a46d54ecbaf21afbea1b53e068a114b2ccfdda2d Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Wed, 27 Apr 2022 17:05:11 +0530 Subject: [PATCH 6/7] testing branch --- qiskit/transpiler/passes/basis/decompose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index d3a69fc59646..869b2654ae6b 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -78,7 +78,7 @@ def gate(self, value): self._make_gate_lists() def _make_gate_lists(self): - """Make comparison lists for the gates""" + """Make comparison lists for the gate list""" if not isinstance(self.gates_to_decompose, list): gates = [self.gates_to_decompose] else: From 1987eb9eb096181eb6a1822f71d3a6dbcbd5a2a4 Mon Sep 17 00:00:00 2001 From: Harshit Gupta Date: Wed, 27 Apr 2022 17:21:08 +0530 Subject: [PATCH 7/7] restore other branch files --- qiskit/transpiler/passes/basis/decompose.py | 23 +++++-------------- .../passes/utils/merge_adjacent_barriers.py | 8 +++---- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/qiskit/transpiler/passes/basis/decompose.py b/qiskit/transpiler/passes/basis/decompose.py index 869b2654ae6b..ffbb80152be1 100644 --- a/qiskit/transpiler/passes/basis/decompose.py +++ b/qiskit/transpiler/passes/basis/decompose.py @@ -44,7 +44,6 @@ def __init__( self.gates_to_decompose = gate else: self.gates_to_decompose = gates_to_decompose - self._make_gate_lists() @property def gate(self) -> Gate: @@ -75,19 +74,6 @@ def gate(self, value): stacklevel=2, ) self.gates_to_decompose = value - self._make_gate_lists() - - def _make_gate_lists(self): - """Make comparison lists for the gate list""" - if not isinstance(self.gates_to_decompose, list): - gates = [self.gates_to_decompose] - else: - gates = self.gates_to_decompose - - self._strings_list, self._gate_type_list = [], [] - if gates is not None: - self._strings_list = [s for s in gates if isinstance(s, str)] - self._gate_type_list = [g for g in gates if isinstance(g, type)] def run(self, dag: DAGCircuit) -> DAGCircuit: """Run the Decompose pass on `dag`. @@ -128,19 +114,22 @@ def _should_decompose(self, node) -> bool: else: gates = self.gates_to_decompose + strings_list = [s for s in gates if isinstance(s, str)] + gate_type_list = [g for g in gates if isinstance(g, type)] + if hasattr(node.op, "label") and node.op.label is not None: has_label = True if has_label and ( # check if label or label wildcard is given - node.op.label in gates or any(fnmatch(node.op.label, p) for p in self._strings_list) + node.op.label in gates or any(fnmatch(node.op.label, p) for p in strings_list) ): return True elif not has_label and ( # check if name or name wildcard is given - node.name in gates or any(fnmatch(node.name, p) for p in self._strings_list) + node.name in gates or any(fnmatch(node.name, p) for p in strings_list) ): return True elif not has_label and ( # check if Gate type given - any(isinstance(node.op, op) for op in self._gate_type_list) + any(isinstance(node.op, op) for op in gate_type_list) ): return True else: diff --git a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py index 8620d5340e20..129fce9469db 100644 --- a/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py +++ b/qiskit/transpiler/passes/utils/merge_adjacent_barriers.py @@ -101,7 +101,7 @@ def _collect_potential_merges(dag, barriers): # Start from the first barrier current_barrier = barriers[0] end_of_barrier = current_barrier - current_barrier_nodes = set([current_barrier]) + current_barrier_nodes = [current_barrier] current_qubits = set(current_barrier.qargs) current_ancestors = dag.ancestors(current_barrier) @@ -140,7 +140,7 @@ def _collect_potential_merges(dag, barriers): barrier_to_add = Barrier(len(current_qubits)) end_of_barrier = next_barrier - current_barrier_nodes.add(end_of_barrier) + current_barrier_nodes.append(end_of_barrier) continue @@ -156,10 +156,10 @@ def _collect_potential_merges(dag, barriers): current_descendants = dag.descendants(next_barrier) barrier_to_add = Barrier(len(current_qubits)) - current_barrier_nodes = set() + current_barrier_nodes = [] end_of_barrier = next_barrier - current_barrier_nodes.add(end_of_barrier) + current_barrier_nodes.append(end_of_barrier) if barrier_to_add: node_to_barrier_qubits[end_of_barrier] = current_qubits