From 08a23c55e5a3b28607daec740877bf53e816bd81 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 4 Jan 2024 06:55:00 -0800 Subject: [PATCH 01/18] Fix typing-extensions --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6402e9a6549f..da632a885ce7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,5 +6,5 @@ sympy>=1.3 dill>=0.3 python-dateutil>=2.8.0 stevedore>=3.0.0 -typing-extensions; python_version<'3.11' +typing-extensions symengine>=0.9,!=0.10.0 From e92eaee3e9de80ccd5434f4419cb75f375d91dc0 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 25 Apr 2024 08:09:49 -0700 Subject: [PATCH 02/18] Initial setup --- .../template_matching_v2/__init__.py | 19 + .../template_matching_v2/backward_match_v2.py | 749 ++++++++++++++++++ .../template_matching_v2/forward_match_v2.py | 454 +++++++++++ .../maximal_matches_v2.py | 77 ++ .../template_matching_v2.py | 374 +++++++++ .../template_substitution_v2.py | 638 +++++++++++++++ .../optimization/template_optimization_v2.py | 159 ++++ 7 files changed, 2470 insertions(+) create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/maximal_matches_v2.py create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py create mode 100644 qiskit/transpiler/passes/optimization/template_optimization_v2.py diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py new file mode 100644 index 000000000000..b2ea3de2d786 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py @@ -0,0 +1,19 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Module containing template matching methods.""" + +from .forward_match import ForwardMatch +from .backward_match import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList +from .template_matching import TemplateMatching +from .maximal_matches import MaximalMatches +from .template_substitution import SubstitutionConfig, TemplateSubstitution diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py new file mode 100644 index 000000000000..a4b11a33de2d --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -0,0 +1,749 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Template matching in the backward direction, it takes an initial match, a +configuration of qubit, both circuit and template as inputs and the list +obtained from forward match. The result is a list of matches between the +template and the circuit. + + +**Reference:** + +[1] Iten, R., Moyard, R., Metger, T., Sutter, D. and Woerner, S., 2020. +Exact and practical pattern matching for quantum circuit optimization. +`arXiv:1909.05270 `_ + +""" +import heapq + +from qiskit.circuit.controlledgate import ControlledGate + + +class Match: + """ + Object to represent a match and its qubit configurations. + """ + + def __init__(self, match, qubit, clbit): + """ + Create a Match class with necessary arguments. + Args: + match (list): list of matched gates. + qubit (list): list of qubits configuration. + clbit (list): list of clbits configuration. + + """ + # Match list + self.match = match + # Qubits list for circuit + self.qubit = [qubit] + # Clbits for template + self.clbit = [clbit] + + +class MatchingScenarios: + """ + Class to represent a matching scenario. + """ + + def __init__( + self, circuit_matched, circuit_blocked, template_matched, template_blocked, matches, counter + ): + """ + Create a MatchingScenarios class with necessary arguments. + Args: + circuit_matched (list): list of matchedwith attributes in the circuit. + circuit_blocked (list): list of isblocked attributes in the circuit. + template_matched (list): list of matchedwith attributes in the template. + template_blocked (list): list of isblocked attributes in the template. + matches (list): list of matches. + counter (int): counter of the number of circuit gates already considered. + """ + self.circuit_matched = circuit_matched + self.template_matched = template_matched + self.circuit_blocked = circuit_blocked + self.template_blocked = template_blocked + self.matches = matches + self.counter = counter + + +class MatchingScenariosList: + """ + Object to define a list of MatchingScenarios, with method to append + and pop elements. + """ + + def __init__(self): + """ + Create an empty MatchingScenariosList. + """ + self.matching_scenarios_list = [] + + def append_scenario(self, matching): + """ + Append a scenario to the list. + Args: + matching (MatchingScenarios): a scenario of match. + """ + self.matching_scenarios_list.append(matching) + + def pop_scenario(self): + """ + Pop the first scenario of the list. + Returns: + MatchingScenarios: a scenario of match. + """ + # Pop the first MatchingScenario and returns it + first = self.matching_scenarios_list[0] + self.matching_scenarios_list.pop(0) + return first + + +class BackwardMatch: + """ + Class BackwardMatch allows to run backward direction part of template + matching algorithm. + """ + + def __init__( + self, + circuit_dag_dep, + template_dag_dep, + forward_matches, + node_id_c, + node_id_t, + qubits, + clbits=None, + heuristics_backward_param=None, + ): + """ + Create a ForwardMatch class with necessary arguments. + Args: + circuit_dag_dep (DAGDependency): circuit in the dag dependency form. + template_dag_dep (DAGDependency): template in the dag dependency form. + forward_matches (list): list of match obtained in the forward direction. + node_id_c (int): index of the first gate matched in the circuit. + node_id_t (int): index of the first gate matched in the template. + qubits (list): list of considered qubits in the circuit. + clbits (list): list of considered clbits in the circuit. + heuristics_backward_param (list): list that contains the two parameters for + applying the heuristics (length and survivor). + """ + self.circuit_dag_dep = circuit_dag_dep.copy() + self.template_dag_dep = template_dag_dep.copy() + self.qubits = qubits + self.clbits = clbits if clbits is not None else [] + self.node_id_c = node_id_c + self.node_id_t = node_id_t + self.forward_matches = forward_matches + self.match_final = [] + self.heuristics_backward_param = ( + heuristics_backward_param if heuristics_backward_param is not None else [] + ) + self.matching_list = MatchingScenariosList() + + def _gate_indices(self): + """ + Function which returns the list of gates that are not match and not + blocked for the first scenario. + Returns: + list: list of gate id. + """ + gate_indices = [] + + current_dag = self.circuit_dag_dep + + for node in current_dag.get_nodes(): + if (not node.matchedwith) and (not node.isblocked): + gate_indices.append(node.node_id) + gate_indices.reverse() + return gate_indices + + def _find_backward_candidates(self, template_blocked, matches): + """ + Function which returns the list possible backward candidates in the template dag. + Args: + template_blocked (list): list of attributes isblocked in the template circuit. + matches (list): list of matches. + Returns: + list: list of backward candidates (id). + """ + template_block = [] + + for node_id in range(self.node_id_t, self.template_dag_dep.size()): + if template_blocked[node_id]: + template_block.append(node_id) + + matches_template = sorted(match[0] for match in matches) + + successors = self.template_dag_dep.get_node(self.node_id_t).successors + potential = [] + for index in range(self.node_id_t + 1, self.template_dag_dep.size()): + if (index not in successors) and (index not in template_block): + potential.append(index) + + candidates_indices = list(set(potential) - set(matches_template)) + candidates_indices = sorted(candidates_indices) + candidates_indices.reverse() + + return candidates_indices + + def _update_qarg_indices(self, qarg): + """ + Change qubits indices of the current circuit node in order to + be comparable the indices of the template qubits list. + Args: + qarg (list): list of qubits indices from the circuit for a given gate. + Returns: + list: circuit indices update for qubits. + """ + qarg_indices = [] + for q in qarg: + if q in self.qubits: + qarg_indices.append(self.qubits.index(q)) + if len(qarg) != len(qarg_indices): + qarg_indices = [] + return qarg_indices + + def _update_carg_indices(self, carg): + """ + Change clbits indices of the current circuit node in order to + be comparable the indices of the template qubits list. + Args: + carg (list): list of clbits indices from the circuit for a given gate. + Returns: + list: circuit indices update for clbits. + """ + carg_indices = [] + if carg: + for q in carg: + if q in self.clbits: + carg_indices.append(self.clbits.index(q)) + if len(carg) != len(carg_indices): + carg_indices = [] + return carg_indices + + def _is_same_op(self, node_circuit, node_template): + """ + Check if two instructions are the same. + Args: + node_circuit (DAGDepNode): node in the circuit. + node_template (DAGDepNode): node in the template. + Returns: + bool: True if the same, False otherwise. + """ + return node_circuit.op == node_template.op + + def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): + """ + Check if the qubits configurations are compatible. + Args: + node_circuit (DAGDepNode): node in the circuit. + node_template (DAGDepNode): node in the template. + qarg_circuit (list): qubits configuration for the Instruction in the circuit. + Returns: + bool: True if possible, False otherwise. + """ + # If the gate is controlled, then the control qubits have to be compared as sets. + if isinstance(node_circuit.op, ControlledGate): + + c_template = node_template.op.num_ctrl_qubits + + if c_template == 1: + return qarg_circuit == node_template.qindices + + else: + control_qubits_template = node_template.qindices[:c_template] + control_qubits_circuit = qarg_circuit[:c_template] + + if set(control_qubits_circuit) == set(control_qubits_template): + + target_qubits_template = node_template.qindices[c_template::] + target_qubits_circuit = qarg_circuit[c_template::] + + if node_template.op.base_gate.name in [ + "rxx", + "ryy", + "rzz", + "swap", + "iswap", + "ms", + ]: + return set(target_qubits_template) == set(target_qubits_circuit) + else: + return target_qubits_template == target_qubits_circuit + else: + return False + # For non controlled gates, the qubits indices for symmetric gates can be compared as sets + # But for non-symmetric gates the qubits indices have to be compared as lists. + else: + if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: + return set(qarg_circuit) == set(node_template.qindices) + else: + return qarg_circuit == node_template.qindices + + def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): + """ + Check if the clbits configurations are compatible. + Args: + node_circuit (DAGDepNode): node in the circuit. + node_template (DAGDepNode): node in the template. + carg_circuit (list): clbits configuration for the Instruction in the circuit. + Returns: + bool: True if possible, False otherwise. + """ + if ( + node_circuit.type == "op" + and getattr(node_circuit.op, "condition", None) + and node_template.type == "op" + and getattr(node_template.op, "condition", None) + ): + if set(carg_circuit) != set(node_template.cindices): + return False + if ( + getattr(node_circuit.op, "condition", None)[1] + != getattr(node_template.op, "condition", None)[1] + ): + return False + return True + + def _init_matched_blocked_list(self): + """ + Initialize the list of blocked and matchedwith attributes. + Returns: + Tuple[list, list, list, list]: + First list contains the attributes matchedwith in the circuit, + second list contains the attributes isblocked in the circuit, + third list contains the attributes matchedwith in the template, + fourth list contains the attributes isblocked in the template. + """ + circuit_matched = [] + circuit_blocked = [] + + for node in self.circuit_dag_dep.get_nodes(): + circuit_matched.append(node.matchedwith) + circuit_blocked.append(node.isblocked) + + template_matched = [] + template_blocked = [] + + for node in self.template_dag_dep.get_nodes(): + template_matched.append(node.matchedwith) + template_blocked.append(node.isblocked) + + return circuit_matched, circuit_blocked, template_matched, template_blocked + + def _backward_heuristics(self, gate_indices, length, survivor): + """ + Heuristics to cut the tree in the backward match algorithm + Args: + gate_indices (list): list of candidates in the circuit. + length (int): depth for cutting the tree, cutting operation is repeated every length. + survivor (int): number of survivor branches. + """ + # Set the list of the counter for the different scenarios. + list_counter = [] + + for scenario in self.matching_list.matching_scenarios_list: + list_counter.append(scenario.counter) + + metrics = [] + # If all scenarios have the same counter and the counter is divisible by length. + if list_counter.count(list_counter[0]) == len(list_counter) and list_counter[0] <= len( + gate_indices + ): + if (list_counter[0] - 1) % length == 0: + # The list metrics contains metric results for each scenarios. + for scenario in self.matching_list.matching_scenarios_list: + metrics.append(self._backward_metrics(scenario)) + # Select only the scenarios with higher metrics for the given number of survivors. + largest = heapq.nlargest(survivor, range(len(metrics)), key=lambda x: metrics[x]) + self.matching_list.matching_scenarios_list = [ + i + for j, i in enumerate(self.matching_list.matching_scenarios_list) + if j in largest + ] + + def _backward_metrics(self, scenario): + """ + Heuristics to cut the tree in the backward match algorithm. + Args: + scenario (MatchingScenarios): scenario for the given match. + Returns: + int: length of the match for the given scenario. + """ + return len(scenario.matches) + + def run_backward_match(self): + """ + Apply the forward match algorithm and returns the list of matches given an initial match + and a circuit qubits configuration. + + """ + match_store_list = [] + + counter = 1 + + # Initialize the list of attributes matchedwith and isblocked. + ( + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + ) = self._init_matched_blocked_list() + + # First Scenario is stored in the MatchingScenariosList(). + first_match = MatchingScenarios( + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + self.forward_matches, + counter, + ) + + self.matching_list = MatchingScenariosList() + self.matching_list.append_scenario(first_match) + + # Set the circuit indices that can be matched. + gate_indices = self._gate_indices() + + number_of_gate_to_match = ( + self.template_dag_dep.size() - (self.node_id_t - 1) - len(self.forward_matches) + ) + + # While the scenario stack is not empty. + while self.matching_list.matching_scenarios_list: + + # If parameters are given, the heuristics is applied. + if self.heuristics_backward_param: + self._backward_heuristics( + gate_indices, + self.heuristics_backward_param[0], + self.heuristics_backward_param[1], + ) + + scenario = self.matching_list.pop_scenario() + + circuit_matched = scenario.circuit_matched + circuit_blocked = scenario.circuit_blocked + template_matched = scenario.template_matched + template_blocked = scenario.template_blocked + matches_scenario = scenario.matches + counter_scenario = scenario.counter + + # Part of the match list coming from the backward match. + match_backward = [ + match for match in matches_scenario if match not in self.forward_matches + ] + + # Matches are stored if the counter is bigger than the length of the list of + # candidates in the circuit. Or if number of gate left to match is the same as + # the length of the backward part of the match. + if ( + counter_scenario > len(gate_indices) + or len(match_backward) == number_of_gate_to_match + ): + matches_scenario.sort(key=lambda x: x[0]) + match_store_list.append(Match(matches_scenario, self.qubits, self.clbits)) + continue + + # First circuit candidate. + circuit_id = gate_indices[counter_scenario - 1] + node_circuit = self.circuit_dag_dep.get_node(circuit_id) + + # If the circuit candidate is blocked, only the counter is changed. + if circuit_blocked[circuit_id]: + matching_scenario = MatchingScenarios( + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + matches_scenario, + counter_scenario + 1, + ) + self.matching_list.append_scenario(matching_scenario) + continue + + # The candidates in the template. + candidates_indices = self._find_backward_candidates(template_blocked, matches_scenario) + # Update of the qubits/clbits indices in the circuit in order to be + # comparable with the one in the template. + qarg1 = node_circuit.qindices + carg1 = node_circuit.cindices + + qarg1 = self._update_qarg_indices(qarg1) + carg1 = self._update_carg_indices(carg1) + + global_match = False + global_broken = [] + + # Loop over the template candidates. + for template_id in candidates_indices: + + node_template = self.template_dag_dep.get_node(template_id) + qarg2 = self.template_dag_dep.get_node(template_id).qindices + + # Necessary but not sufficient conditions for a match to happen. + if ( + len(qarg1) != len(qarg2) + or set(qarg1) != set(qarg2) + or node_circuit.name != node_template.name + ): + continue + + # Check if the qubit, clbit configuration are compatible for a match, + # also check if the operation are the same. + if ( + self._is_same_q_conf(node_circuit, node_template, qarg1) + and self._is_same_c_conf(node_circuit, node_template, carg1) + and self._is_same_op(node_circuit, node_template) + ): + + # If there is a match the attributes are copied. + circuit_matched_match = circuit_matched.copy() + circuit_blocked_match = circuit_blocked.copy() + + template_matched_match = template_matched.copy() + template_blocked_match = template_blocked.copy() + + matches_scenario_match = matches_scenario.copy() + + block_list = [] + broken_matches_match = [] + + # Loop to check if the match is not connected, in this case + # the successors matches are blocked and unmatched. + for potential_block in self.template_dag_dep.successors(template_id): + if not template_matched_match[potential_block]: + template_blocked_match[potential_block] = True + block_list.append(potential_block) + for block_id in block_list: + for succ_id in self.template_dag_dep.successors(block_id): + template_blocked_match[succ_id] = True + if template_matched_match[succ_id]: + new_id = template_matched_match[succ_id][0] + circuit_matched_match[new_id] = [] + template_matched_match[succ_id] = [] + broken_matches_match.append(succ_id) + + if broken_matches_match: + global_broken.append(True) + else: + global_broken.append(False) + + new_matches_scenario_match = [ + elem + for elem in matches_scenario_match + if elem[0] not in broken_matches_match + ] + + condition = True + + for back_match in match_backward: + if back_match not in new_matches_scenario_match: + condition = False + break + + # First option greedy match. + if ([self.node_id_t, self.node_id_c] in new_matches_scenario_match) and ( + condition or not match_backward + ): + template_matched_match[template_id] = [circuit_id] + circuit_matched_match[circuit_id] = [template_id] + new_matches_scenario_match.append([template_id, circuit_id]) + + new_matching_scenario = MatchingScenarios( + circuit_matched_match, + circuit_blocked_match, + template_matched_match, + template_blocked_match, + new_matches_scenario_match, + counter_scenario + 1, + ) + self.matching_list.append_scenario(new_matching_scenario) + + global_match = True + + if global_match: + circuit_matched_block_s = circuit_matched.copy() + circuit_blocked_block_s = circuit_blocked.copy() + + template_matched_block_s = template_matched.copy() + template_blocked_block_s = template_blocked.copy() + + matches_scenario_block_s = matches_scenario.copy() + + circuit_blocked_block_s[circuit_id] = True + + broken_matches = [] + + # Second option, not a greedy match, block all successors (push the gate + # to the right). + for succ in self.circuit_dag_dep.get_node(circuit_id).successors: + circuit_blocked_block_s[succ] = True + if circuit_matched_block_s[succ]: + broken_matches.append(succ) + new_id = circuit_matched_block_s[succ][0] + template_matched_block_s[new_id] = [] + circuit_matched_block_s[succ] = [] + + new_matches_scenario_block_s = [ + elem for elem in matches_scenario_block_s if elem[1] not in broken_matches + ] + + condition_not_greedy = True + + for back_match in match_backward: + if back_match not in new_matches_scenario_block_s: + condition_not_greedy = False + break + + if ([self.node_id_t, self.node_id_c] in new_matches_scenario_block_s) and ( + condition_not_greedy or not match_backward + ): + new_matching_scenario = MatchingScenarios( + circuit_matched_block_s, + circuit_blocked_block_s, + template_matched_block_s, + template_blocked_block_s, + new_matches_scenario_block_s, + counter_scenario + 1, + ) + self.matching_list.append_scenario(new_matching_scenario) + + # Third option: if blocking the succesors breaks a match, we consider + # also the possibility to block all predecessors (push the gate to the left). + if broken_matches and all(global_broken): + + circuit_matched_block_p = circuit_matched.copy() + circuit_blocked_block_p = circuit_blocked.copy() + + template_matched_block_p = template_matched.copy() + template_blocked_block_p = template_blocked.copy() + + matches_scenario_block_p = matches_scenario.copy() + + circuit_blocked_block_p[circuit_id] = True + + for pred in self.circuit_dag_dep.get_node(circuit_id).predecessors: + circuit_blocked_block_p[pred] = True + + matching_scenario = MatchingScenarios( + circuit_matched_block_p, + circuit_blocked_block_p, + template_matched_block_p, + template_blocked_block_p, + matches_scenario_block_p, + counter_scenario + 1, + ) + self.matching_list.append_scenario(matching_scenario) + + # If there is no match then there are three options. + if not global_match: + + circuit_blocked[circuit_id] = True + + following_matches = [] + + successors = self.circuit_dag_dep.get_node(circuit_id).successors + for succ in successors: + if circuit_matched[succ]: + following_matches.append(succ) + + # First option, the circuit gate is not disturbing because there are no + # following match and no predecessors. + predecessors = self.circuit_dag_dep.get_node(circuit_id).predecessors + + if not predecessors or not following_matches: + + matching_scenario = MatchingScenarios( + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + matches_scenario, + counter_scenario + 1, + ) + self.matching_list.append_scenario(matching_scenario) + + else: + + circuit_matched_nomatch = circuit_matched.copy() + circuit_blocked_nomatch = circuit_blocked.copy() + + template_matched_nomatch = template_matched.copy() + template_blocked_nomatch = template_blocked.copy() + + matches_scenario_nomatch = matches_scenario.copy() + + # Second option, all predecessors are blocked (circuit gate is + # moved to the left). + for pred in predecessors: + circuit_blocked[pred] = True + + matching_scenario = MatchingScenarios( + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + matches_scenario, + counter_scenario + 1, + ) + self.matching_list.append_scenario(matching_scenario) + + # Third option, all successors are blocked (circuit gate is + # moved to the right). + + broken_matches = [] + + successors = self.circuit_dag_dep.get_node(circuit_id).successors + + for succ in successors: + circuit_blocked_nomatch[succ] = True + if circuit_matched_nomatch[succ]: + broken_matches.append(succ) + circuit_matched_nomatch[succ] = [] + + new_matches_scenario_nomatch = [ + elem for elem in matches_scenario_nomatch if elem[1] not in broken_matches + ] + + condition_block = True + + for back_match in match_backward: + if back_match not in new_matches_scenario_nomatch: + condition_block = False + break + + if ([self.node_id_t, self.node_id_c] in matches_scenario_nomatch) and ( + condition_block or not match_backward + ): + new_matching_scenario = MatchingScenarios( + circuit_matched_nomatch, + circuit_blocked_nomatch, + template_matched_nomatch, + template_blocked_nomatch, + new_matches_scenario_nomatch, + counter_scenario + 1, + ) + self.matching_list.append_scenario(new_matching_scenario) + + length = max(len(m.match) for m in match_store_list) + + # Store the matches with maximal length. + for scenario in match_store_list: + if (len(scenario.match) == length) and not any( + scenario.match == x.match for x in self.match_final + ): + self.match_final.append(scenario) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py new file mode 100644 index 000000000000..cfc1bf6f6de7 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -0,0 +1,454 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Template matching in the forward direction, it takes an initial +match, a configuration of qubit and both circuit and template as inputs. The +result is a list of match between the template and the circuit. + + +**Reference:** + +[1] Iten, R., Moyard, R., Metger, T., Sutter, D. and Woerner, S., 2020. +Exact and practical pattern matching for quantum circuit optimization. +`arXiv:1909.05270 `_ + +""" + +from qiskit.circuit.controlledgate import ControlledGate + + +class ForwardMatch: + """ + Object to apply template matching in the forward direction. + """ + + def __init__( + self, circuit_dag_dep, template_dag_dep, node_id_c, node_id_t, qubits, clbits=None, tm_helpers=None, + ): + """ + Create a ForwardMatch class with necessary arguments. + Args: + circuit_dag_dep (DAGDependency): circuit in the dag dependency form. + template_dag_dep (DAGDependency): template in the dag dependency form. + node_id_c (int): index of the first gate matched in the circuit. + node_id_t (int): index of the first gate matched in the template. + qubits (list): list of considered qubits in the circuit. + clbits (list): list of considered clbits in the circuit. + """ + + # The dag dependency representation of the circuit + self.circuit_dag_dep = circuit_dag_dep.copy() + + # The dag dependency representation of the template + self.template_dag_dep = template_dag_dep.copy() + + # List of qubit on which the node of the circuit is acting on + self.qubits = qubits + + # List of qubit on which the node of the circuit is acting on + self.clbits = clbits if clbits is not None else [] + + # Id of the node in the circuit + self.node_id_c = node_id_c + + # Id of the node in the template + self.node_id_t = node_id_t + + # List of match + self.match = [] + + # List of candidates for the forward match + self.candidates = [] + + # List of nodes in circuit which are matched + self.matched_nodes_list = [] + + # Transformation of the qarg indices of the circuit to be adapted to the template indices + self.qarg_indices = [] + + # Transformation of the carg indices of the circuit to be adapted to the template indices + self.carg_indices = [] + + self.tm_helpers = tm_helpers + + def _init_successors_to_visit(self): + """ + Initialize the attribute list 'SuccessorsToVisit' + """ + for i in range(0, self.circuit_dag_dep.size()): + if i == self.node_id_c: + self.circuit_dag_dep.get_node(i).successorstovisit = ( + self.circuit_dag_dep.direct_successors(i) + ) + + def _init_matched_with_circuit(self): + """ + Initialize the attribute 'MatchedWith' in the template DAG dependency. + """ + for i in range(0, self.circuit_dag_dep.size()): + if i == self.node_id_c: + self.circuit_dag_dep.get_node(i).matchedwith = [self.node_id_t] + else: + self.circuit_dag_dep.get_node(i).matchedwith = [] + + def _init_matched_with_template(self): + """ + Initialize the attribute 'MatchedWith' in the circuit DAG dependency. + """ + for i in range(0, self.template_dag_dep.size()): + if i == self.node_id_t: + self.template_dag_dep.get_node(i).matchedwith = [self.node_id_c] + else: + self.template_dag_dep.get_node(i).matchedwith = [] + + def _init_is_blocked_circuit(self): + """ + Initialize the attribute 'IsBlocked' in the circuit DAG dependency. + """ + for i in range(0, self.circuit_dag_dep.size()): + self.circuit_dag_dep.get_node(i).isblocked = False + + def _init_is_blocked_template(self): + """ + Initialize the attribute 'IsBlocked' in the template DAG dependency. + """ + for i in range(0, self.template_dag_dep.size()): + self.template_dag_dep.get_node(i).isblocked = False + + def _init_list_match(self): + """ + Initialize the list of matched nodes between the circuit and the template + with the first match found. + """ + self.match.append([self.node_id_t, self.node_id_c]) + + def _find_forward_candidates(self, node_id_t): + """ + Find the candidate nodes to be matched in the template for a given node. + Args: + node_id_t (int): considered node id. + """ + matches = [] + + for i in range(0, len(self.match)): + matches.append(self.match[i][0]) + + pred = matches.copy() + if len(pred) > 1: + pred.sort() + pred.remove(node_id_t) + + if self.template_dag_dep.direct_successors(node_id_t): + maximal_index = self.template_dag_dep.direct_successors(node_id_t)[-1] + pred = [elem for elem in pred if elem <= maximal_index] + + block = [] + for node_id in pred: + for dir_succ in self.template_dag_dep.direct_successors(node_id): + if dir_succ not in matches: + succ = self.template_dag_dep.successors(dir_succ) + block = block + succ + self.candidates = list( + set(self.template_dag_dep.direct_successors(node_id_t)) - set(matches) - set(block) + ) + + def _init_matched_nodes(self): + """ + Initialize the list of current matched nodes. + """ + self.matched_nodes_list.append( + [self.node_id_c, self.circuit_dag_dep.get_node(self.node_id_c)] + ) + + def _get_node_forward(self, list_id): + """ + Return a node from the matched_node_list for a given list id. + Args: + list_id (int): considered list id of the desired node. + + Returns: + DAGDepNode: DAGDepNode object corresponding to i-th node of the matched_node_list. + """ + node = self.matched_nodes_list[list_id][1] + return node + + def _remove_node_forward(self, list_id): + """ + Remove a node of the current matched list for a given list id. + Args: + list_id (int): considered list id of the desired node. + """ + self.matched_nodes_list.pop(list_id) + + def _update_successor(self, node, successor_id): + """ + Return a node with an updated attribute 'SuccessorToVisit'. + Args: + node (DAGDepNode): current node. + successor_id (int): successor id to remove. + + Returns: + DAGOpNode or DAGOutNode: Node with updated attribute 'SuccessorToVisit'. + """ + node_update = node + node_update.successorstovisit.pop(successor_id) + return node_update + + def _get_successors_to_visit(self, node, list_id): + """ + Return the successor for a given node and id. + Args: + node (DAGOpNode or DAGOutNode): current node. + list_id (int): id in the list for the successor to get. + + Returns: + int: id of the successor to get. + """ + successor_id = node.successorstovisit[list_id] + return successor_id + + def _update_qarg_indices(self, qarg): + """ + Change qubits indices of the current circuit node in order to + be comparable with the indices of the template qubits list. + Args: + qarg (list): list of qubits indices from the circuit for a given node. + """ + self.qarg_indices = [] + for q in qarg: + if q in self.qubits: + self.qarg_indices.append(self.qubits.index(q)) + if len(qarg) != len(self.qarg_indices): + self.qarg_indices = [] + + def _update_carg_indices(self, carg): + """ + Change clbits indices of the current circuit node in order to + be comparable with the indices of the template qubits list. + Args: + carg (list): list of clbits indices from the circuit for a given node. + """ + self.carg_indices = [] + if carg: + for q in carg: + if q in self.clbits: + self.carg_indices.append(self.clbits.index(q)) + if len(carg) != len(self.carg_indices): + self.carg_indices = [] + + def _is_same_op(self, node_circuit, node_template): + """ + Check if two instructions are the same. + Args: + node_circuit (DAGDepNode): node in the circuit. + node_template (DAGDepNode): node in the template. + Returns: + bool: True if the same, False otherwise. + """ + return node_circuit.op.soft_compare(node_template.op) + + def _is_same_q_conf(self, node_circuit, node_template): + """ + Check if the qubits configurations are compatible. + Args: + node_circuit (DAGDepNode): node in the circuit. + node_template (DAGDepNode): node in the template. + Returns: + bool: True if possible, False otherwise. + """ + + if isinstance(node_circuit.op, ControlledGate): + + c_template = node_template.op.num_ctrl_qubits + + if c_template == 1: + return self.qarg_indices == node_template.qindices + + else: + control_qubits_template = node_template.qindices[:c_template] + control_qubits_circuit = self.qarg_indices[:c_template] + + if set(control_qubits_circuit) == set(control_qubits_template): + + target_qubits_template = node_template.qindices[c_template::] + target_qubits_circuit = self.qarg_indices[c_template::] + + if node_template.op.base_gate.name in [ + "rxx", + "ryy", + "rzz", + "swap", + "iswap", + "ms", + ]: + return set(target_qubits_template) == set(target_qubits_circuit) + else: + return target_qubits_template == target_qubits_circuit + else: + return False + else: + if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: + return set(self.qarg_indices) == set(node_template.qindices) + else: + return self.qarg_indices == node_template.qindices + + def _is_same_c_conf(self, node_circuit, node_template): + """ + Check if the clbits configurations are compatible. + Args: + node_circuit (DAGDepNode): node in the circuit. + node_template (DAGDepNode): node in the template. + Returns: + bool: True if possible, False otherwise. + """ + if ( + node_circuit.type == "op" + and getattr(node_circuit.op, "condition", None) + and node_template.type == "op" + and getattr(node_template.op, "condition", None) + ): + if set(self.carg_indices) != set(node_template.cindices): + return False + if ( + getattr(node_circuit.op, "condition", None)[1] + != getattr(node_template.op, "condition", None)[1] + ): + return False + return True + + def run_forward_match(self): + """ + Apply the forward match algorithm and returns the list of matches given an initial match + and a circuit qubits configuration. + """ + + # Initialize the new attributes of the DAGDepNodes of the DAGDependency object + self._init_successors_to_visit() + + self._init_matched_with_circuit() + self._init_matched_with_template() + + self._init_is_blocked_circuit() + self._init_is_blocked_template() + + # Initialize the list of matches and the stack of matched nodes (circuit) + self._init_list_match() + self._init_matched_nodes() + + # While the list of matched nodes is not empty + while self.matched_nodes_list: + + # Return first element of the matched_nodes_list and removes it from the list + v_first = self._get_node_forward(0) + self._remove_node_forward(0) + + # If there is no successors to visit go to the end + if not v_first.successorstovisit: + continue + + # Get the label and the node of the first successor to visit + label = self._get_successors_to_visit(v_first, 0) + v = [label, self.circuit_dag_dep.get_node(label)] + + # Update of the SuccessorsToVisit attribute + v_first = self._update_successor(v_first, 0) + + # Update the matched_nodes_list with new attribute successor to visit and sort the list. + self.matched_nodes_list.append([v_first.node_id, v_first]) + self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) + + # If the node is blocked and already matched go to the end + if v[1].isblocked | (v[1].matchedwith != []): + continue + + # Search for potential candidates in the template + self._find_forward_candidates(v_first.matchedwith[0]) + + qarg1 = self.circuit_dag_dep.get_node(label).qindices + carg1 = self.circuit_dag_dep.get_node(label).cindices + + # Update the indices for both qubits and clbits in order to be comparable with the + # indices in the template circuit. + self._update_qarg_indices(qarg1) + self._update_carg_indices(carg1) + + match = False + + # For loop over the candidates (template) to find a match. + for i in self.candidates: + + # Break the for loop if a match is found. + if match: + break + + # Compare the indices of qubits and the operation, + # if True; a match is found + node_circuit = self.circuit_dag_dep.get_node(label) + node_template = self.template_dag_dep.get_node(i) + + # Necessary but not sufficient conditions for a match to happen. + if ( + len(self.qarg_indices) != len(node_template.qindices) + or set(self.qarg_indices) != set(node_template.qindices) + or node_circuit.name != node_template.name + ): + continue + + # Check if the qubit, clbit configuration are compatible for a match, + # also check if the operation are the same. + if ( + self._is_same_q_conf(node_circuit, node_template) + and self._is_same_c_conf(node_circuit, node_template) + and self._is_same_op(node_circuit, node_template) + ): + + v[1].matchedwith = [i] + + self.template_dag_dep.get_node(i).matchedwith = [label] + + # Append the new match to the list of matches. + self.match.append([i, label]) + + # Potential successors to visit (circuit) for a given match. + potential = self.circuit_dag_dep.direct_successors(label) + + # If the potential successors to visit are blocked or match, it is removed. + for potential_id in potential: + if self.circuit_dag_dep.get_node(potential_id).isblocked | ( + self.circuit_dag_dep.get_node(potential_id).matchedwith != [] + ): + potential.remove(potential_id) + + sorted_potential = sorted(potential) + + # Update the successor to visit attribute + v[1].successorstovisit = sorted_potential + + # Add the updated node to the stack. + self.matched_nodes_list.append([v[0], v[1]]) + self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) + match = True + continue + + # If no match is found, block the node and all the successors. + if not match: + v[1].isblocked = True + for succ in v[1].successors: + self.circuit_dag_dep.get_node(succ).isblocked = True + if self.circuit_dag_dep.get_node(succ).matchedwith: + self.match.remove( + [self.circuit_dag_dep.get_node(succ).matchedwith[0], succ] + ) + match_id = self.circuit_dag_dep.get_node(succ).matchedwith[0] + self.template_dag_dep.get_node(match_id).matchedwith = [] + self.circuit_dag_dep.get_node(succ).matchedwith = [] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/maximal_matches_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/maximal_matches_v2.py new file mode 100644 index 000000000000..24268784e248 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/maximal_matches_v2.py @@ -0,0 +1,77 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +It stores all maximal matches from the given matches obtained by the template +matching algorithm. +""" + + +class Match: + """ + Class Match is an object to store a list of match with its qubits and + clbits configuration. + """ + + def __init__(self, match, qubit, clbit): + """ + Create a Match with necessary arguments. + Args: + match (list): list of a match. + qubit (list): list of qubits configuration. + clbit (list): list of clbits configuration. + """ + + self.match = match + self.qubit = qubit + self.clbit = clbit + + +class MaximalMatches: + """ + Class MaximalMatches allows to sort and store the maximal matches from the list + of matches obtained with the template matching algorithm. + """ + + def __init__(self, template_matches): + """ + Initialize MaximalMatches with the necessary arguments. + Args: + template_matches (list): list of matches obtained from running the algorithm. + """ + self.template_matches = template_matches + + self.max_match_list = [] + + def run_maximal_matches(self): + """ + Method that extracts and stores maximal matches in decreasing length order. + """ + + self.max_match_list = [ + Match( + sorted(self.template_matches[0].match), + self.template_matches[0].qubit, + self.template_matches[0].clbit, + ) + ] + + for matches in self.template_matches[1::]: + present = False + for max_match in self.max_match_list: + for elem in matches.match: + if elem in max_match.match and len(matches.match) <= len(max_match.match): + present = True + if not present: + self.max_match_list.append( + Match(sorted(matches.match), matches.qubit, matches.clbit) + ) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py new file mode 100644 index 000000000000..f31e67d21dd5 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -0,0 +1,374 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Template matching for all possible qubit configurations and initial matches. It +returns the list of all matches obtained from this algorithm. + + +**Reference:** + +[1] Iten, R., Moyard, R., Metger, T., Sutter, D. and Woerner, S., 2020. +Exact and practical pattern matching for quantum circuit optimization. +`arXiv:1909.05270 `_ + +""" + +import itertools + +from qiskit.circuit.controlledgate import ControlledGate +from qiskit.transpiler.passes.optimization.template_matching.forward_match import ForwardMatch +from qiskit.transpiler.passes.optimization.template_matching.backward_match import BackwardMatch + + +class TemplateMatching: + """ + Class TemplatingMatching allows to apply the full template matching algorithm. + """ + + def __init__( + self, + circuit_dag_dep, + template_dag_dep, + heuristics_qubits_param=None, + heuristics_backward_param=None, + ): + """ + Create a TemplateMatching object with necessary arguments. + Args: + circuit_dag_dep (QuantumCircuit): circuit. + template_dag_dep (QuantumCircuit): template. + heuristics_backward_param (list[int]): [length, survivor] + heuristics_qubits_param (list[int]): [length] + """ + self.circuit_dag_dep = circuit_dag_dep + self.template_dag_dep = template_dag_dep + self.match_list = [] + self.heuristics_qubits_param = ( + heuristics_qubits_param if heuristics_qubits_param is not None else [] + ) + self.heuristics_backward_param = ( + heuristics_backward_param if heuristics_backward_param is not None else [] + ) + + def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbits_t): + """ + Returns the list of qubit for circuit given the first match, the unknown qubit are + replaced by -1. + Args: + node_circuit (DAGDepNode): First match node in the circuit. + node_template (DAGDepNode): First match node in the template. + n_qubits_t (int): number of qubit in the template. + n_clbits_t (int): number of classical bit in the template. + Returns: + list: list of qubits to consider in circuit (with specific order). + """ + l_q = [] + + # Controlled gate + if isinstance(node_circuit.op, ControlledGate) and node_template.op.num_ctrl_qubits > 1: + control = node_template.op.num_ctrl_qubits + control_qubits_circuit = node_circuit.qindices[:control] + not_control_qubits_circuit = node_circuit.qindices[control::] + + # Symmetric base gate + if node_template.op.base_gate.name not in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: + for control_perm_q in itertools.permutations(control_qubits_circuit): + control_perm_q = list(control_perm_q) + l_q_sub = [-1] * n_qubits_t + for q in node_template.qindices: + node_circuit_perm = control_perm_q + not_control_qubits_circuit + l_q_sub[q] = node_circuit_perm[node_template.qindices.index(q)] + l_q.append(l_q_sub) + # Not symmetric base gate + else: + for control_perm_q in itertools.permutations(control_qubits_circuit): + control_perm_q = list(control_perm_q) + for not_control_perm_q in itertools.permutations(not_control_qubits_circuit): + not_control_perm_q = list(not_control_perm_q) + l_q_sub = [-1] * n_qubits_t + for q in node_template.qindices: + node_circuit_perm = control_perm_q + not_control_perm_q + l_q_sub[q] = node_circuit_perm[node_template.qindices.index(q)] + l_q.append(l_q_sub) + # Not controlled + else: + # Symmetric gate + if node_template.op.name not in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: + l_q_sub = [-1] * n_qubits_t + for q in node_template.qindices: + l_q_sub[q] = node_circuit.qindices[node_template.qindices.index(q)] + l_q.append(l_q_sub) + # Not symmetric + else: + for perm_q in itertools.permutations(node_circuit.qindices): + l_q_sub = [-1] * n_qubits_t + for q in node_template.qindices: + l_q_sub[q] = perm_q[node_template.qindices.index(q)] + l_q.append(l_q_sub) + + # Classical control + if not node_template.cindices or not node_circuit.cindices: + l_c = [] + else: + l_c = [-1] * n_clbits_t + for c in node_template.cindices: + l_c[c] = node_circuit[node_template.cindices.index(c)] + + return l_q, l_c + + def _sublist(self, lst, exclude, length): + """ + Function that returns all possible combinations of a given length, considering an + excluded list of elements. + Args: + lst (list): list of qubits indices from the circuit. + exclude (list): list of qubits from the first matched circuit gate. + length (int): length of the list to be returned (number of template qubit - + number of qubit from the first matched template gate). + Yield: + iterator: Iterator of the possible lists. + """ + for sublist in itertools.combinations([e for e in lst if e not in exclude], length): + yield list(sublist) + + def _list_qubit_clbit_circuit(self, list_first_match, permutation): + """ + Function that returns the list of the circuit qubits and clbits give a permutation + and an initial match. + Args: + list_first_match (list): list of qubits indices for the initial match. + permutation (list): possible permutation for the circuit qubit. + Returns: + list: list of circuit qubit for the given permutation and initial match. + """ + list_circuit = [] + + counter = 0 + + for elem in list_first_match: + if elem == -1: + list_circuit.append(permutation[counter]) + counter = counter + 1 + else: + list_circuit.append(elem) + + return list_circuit + + def _add_match(self, backward_match_list): + """ + Method to add a match in list only if it is not already in it. + If the match is already in the list, the qubit configuration + is append to the existing match. + Args: + backward_match_list (list): match from the backward part of the + algorithm. + """ + + already_in = False + + for b_match in backward_match_list: + for l_match in self.match_list: + if b_match.match == l_match.match: + index = self.match_list.index(l_match) + self.match_list[index].qubit.append(b_match.qubit[0]) + already_in = True + + if not already_in: + self.match_list.append(b_match) + + def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): + """ + Explore the successors of the node_id_c (up to the given length). + Args: + node_id_c (int): first match id in the circuit. + node_id_t (int): first match id in the template. + n_qubits_t (int): number of qubits in the template. + length (int): length for exploration of the successors. + Returns: + list: qubits configuration for the 'length' successors of node_id_c. + """ + template_nodes = range(node_id_t + 1, self.template_dag_dep.size()) + circuit_nodes = range(0, self.circuit_dag_dep.size()) + successors_template = self.template_dag_dep.get_node(node_id_t).successors + + counter = 1 + qubit_set = set(self.circuit_dag_dep.get_node(node_id_c).qindices) + if 2 * len(successors_template) > len(template_nodes): + successors = self.circuit_dag_dep.get_node(node_id_c).successors + for succ in successors: + qarg = self.circuit_dag_dep.get_node(succ).qindices + if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: + qubit_set = qubit_set | set(qarg) + counter += 1 + elif (len(qubit_set | set(qarg))) > n_qubits_t: + return list(qubit_set) + return list(qubit_set) + + else: + not_successors = list( + set(circuit_nodes) - set(self.circuit_dag_dep.get_node(node_id_c).successors) + ) + candidate = [ + not_successors[j] + for j in range(len(not_successors) - 1, len(not_successors) - 1 - length, -1) + ] + + for not_succ in candidate: + qarg = self.circuit_dag_dep.get_node(not_succ).qindices + if counter <= length and (len(qubit_set | set(qarg))) <= n_qubits_t: + qubit_set = qubit_set | set(qarg) + counter += 1 + elif (len(qubit_set | set(qarg))) > n_qubits_t: + return list(qubit_set) + return list(qubit_set) + + def get_node(self, dag, node_id): + return dag._multi_graph[node_id] + + def run_template_matching(self): + """ + Run the complete algorithm for finding all maximal matches for the given template and + circuit. First it fixes the configuration of the circuit due to the first match. + Then it explores all compatible qubit configurations of the circuit. For each + qubit configurations, we apply first the Forward part of the algorithm and then + the Backward part of the algorithm. The longest matches for the given configuration + are stored. Finally, the list of stored matches is sorted. + """ + + # Get the number of qubits/clbits for both circuit and template. + n_qubits_c = len(self.circuit_dag_dep.qubits) + n_clbits_c = len(self.circuit_dag_dep.clbits) + + n_qubits_t = len(self.template_dag_dep.qubits) + n_clbits_t = len(self.template_dag_dep.clbits) + + # Loop over the indices of both template and circuit. + for template_index in range(0, self.template_dag_dep.size()): + for circuit_index in range(0, self.circuit_dag_dep.size()): + # Operations match up to ParameterExpressions. + if self.circuit_dag_dep.get_node(circuit_index).op.soft_compare( + self.template_dag_dep.get_node(template_index).op + ): + + qarg_c = self.circuit_dag_dep.get_node(circuit_index).qindices + carg_c = self.circuit_dag_dep.get_node(circuit_index).cindices + + qarg_t = self.template_dag_dep.get_node(template_index).qindices + carg_t = self.template_dag_dep.get_node(template_index).cindices + + node_id_c = circuit_index + node_id_t = template_index + + # Fix the qubits and clbits configuration given the first match. + + all_list_first_match_q, list_first_match_c = self._list_first_match_new( + self.circuit_dag_dep.get_node(circuit_index), + self.template_dag_dep.get_node(template_index), + n_qubits_t, + n_clbits_t, + ) + + list_circuit_q = list(range(0, n_qubits_c)) + list_circuit_c = list(range(0, n_clbits_c)) + + # If the parameter for qubits heuristics is given then extracts + # the list of qubits for the successors (length(int)) in the circuit. + + if self.heuristics_qubits_param: + heuristics_qubits = self._explore_circuit( + node_id_c, node_id_t, n_qubits_t, self.heuristics_qubits_param[0] + ) + else: + heuristics_qubits = [] + + for sub_q in self._sublist(list_circuit_q, qarg_c, n_qubits_t - len(qarg_t)): + # If the heuristics qubits are a subset of the given qubits configuration, + # then this configuration is accepted. + if set(heuristics_qubits).issubset(set(sub_q) | set(qarg_c)): + # Permute the qubit configuration. + for perm_q in itertools.permutations(sub_q): + perm_q = list(perm_q) + for list_first_match_q in all_list_first_match_q: + list_qubit_circuit = self._list_qubit_clbit_circuit( + list_first_match_q, perm_q + ) + + # Check for clbits configurations if there are clbits. + if list_circuit_c: + for sub_c in self._sublist( + list_circuit_c, carg_c, n_clbits_t - len(carg_t) + ): + for perm_c in itertools.permutations(sub_c): + perm_c = list(perm_c) + + list_clbit_circuit = self._list_qubit_clbit_circuit( + list_first_match_c, perm_c + ) + + # Apply the forward match part of the algorithm. + forward = ForwardMatch( + self.circuit_dag_dep, + self.template_dag_dep, + node_id_c, + node_id_t, + list_qubit_circuit, + list_clbit_circuit, + self, + ) + forward.run_forward_match() + + # Apply the backward match part of the algorithm. + backward = BackwardMatch( + forward.circuit_dag_dep, + forward.template_dag_dep, + forward.match, + node_id_c, + node_id_t, + list_qubit_circuit, + list_clbit_circuit, + self.heuristics_backward_param, + ) + + backward.run_backward_match() + + # Add the matches to the list. + self._add_match(backward.match_final) + else: + # Apply the forward match part of the algorithm. + forward = ForwardMatch( + self.circuit_dag_dep, + self.template_dag_dep, + node_id_c, + node_id_t, + list_qubit_circuit, + ) + forward.run_forward_match() + + # Apply the backward match part of the algorithm. + backward = BackwardMatch( + forward.circuit_dag_dep, + forward.template_dag_dep, + forward.match, + node_id_c, + node_id_t, + list_qubit_circuit, + [], + self.heuristics_backward_param, + ) + backward.run_backward_match() + + # Add the matches to the list. + self._add_match(backward.match_final) + + # Sort the list of matches according to the length of the matches (decreasing order). + self.match_list.sort(key=lambda x: len(x.match), reverse=True) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py new file mode 100644 index 000000000000..06c5186d284c --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -0,0 +1,638 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Template matching substitution, given a list of maximal matches it substitutes +them in circuit and creates a new optimized dag version of the circuit. +""" +import collections +import copy +import itertools + +from qiskit.circuit import Parameter, ParameterExpression +from qiskit.dagcircuit.dagcircuit import DAGCircuit +from qiskit.dagcircuit.dagdependency import DAGDependency +from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag +from qiskit.utils import optionals as _optionals + + +class SubstitutionConfig: + """ + Class to store the configuration of a given match substitution, which circuit + gates, template gates, qubits, and clbits and predecessors of the match + in the circuit. + """ + + def __init__( + self, + circuit_config, + template_config, + pred_block, + qubit_config, + template_dag_dep, + clbit_config=None, + ): + self.template_dag_dep = template_dag_dep + self.circuit_config = circuit_config + self.template_config = template_config + self.qubit_config = qubit_config + self.clbit_config = clbit_config if clbit_config is not None else [] + self.pred_block = pred_block + + def has_parameters(self): + """Ensure that the template does not have parameters.""" + for node in self.template_dag_dep.get_nodes(): + for param in node.op.params: + if isinstance(param, ParameterExpression): + return True + + return False + + +class TemplateSubstitution: + """ + Class to run the substitution algorithm from the list of maximal matches. + """ + + def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dict=None): + """ + Initialize TemplateSubstitution with necessary arguments. + Args: + max_matches (list): list of maximal matches obtained from the running + the template matching algorithm. + circuit_dag_dep (DAGDependency): circuit in the dag dependency form. + template_dag_dep (DAGDependency): template in the dag dependency form. + user_cost_dict (Optional[dict]): user provided cost dictionary that will override + the default cost dictionary. + """ + + self.match_stack = max_matches + self.circuit_dag_dep = circuit_dag_dep + self.template_dag_dep = template_dag_dep + + self.substitution_list = [] + self.unmatched_list = [] + self.dag_dep_optimized = DAGDependency() + self.dag_optimized = DAGCircuit() + + if user_cost_dict is not None: + self.cost_dict = dict(user_cost_dict) + else: + self.cost_dict = { + "id": 0, + "x": 1, + "y": 1, + "z": 1, + "h": 1, + "t": 1, + "tdg": 1, + "s": 1, + "sdg": 1, + "u1": 1, + "u2": 2, + "u3": 2, + "rx": 1, + "ry": 1, + "rz": 1, + "r": 2, + "cx": 2, + "cy": 4, + "cz": 4, + "ch": 8, + "swap": 6, + "iswap": 8, + "rxx": 9, + "ryy": 9, + "rzz": 5, + "rzx": 7, + "ms": 9, + "cu3": 10, + "crx": 10, + "cry": 10, + "crz": 10, + "ccx": 21, + "rccx": 12, + "c3x": 96, + "rc3x": 24, + "c4x": 312, + "p": 1, + } + + def _pred_block(self, circuit_sublist, index): + """ + It returns the predecessors of a given part of the circuit. + Args: + circuit_sublist (list): list of the gates matched in the circuit. + index (int): Index of the group of matches. + Returns: + list: List of predecessors of the current match circuit configuration. + """ + predecessors = set() + for node_id in circuit_sublist: + predecessors = predecessors | set(self.circuit_dag_dep.get_node(node_id).predecessors) + + exclude = set() + for elem in self.substitution_list[:index]: + exclude = exclude | set(elem.circuit_config) | set(elem.pred_block) + + pred = list(predecessors - set(circuit_sublist) - exclude) + pred.sort() + + return pred + + def _quantum_cost(self, left, right): + """ + Compare the two parts of the template and returns True if the quantum cost is reduced. + Args: + left (list): list of matched nodes in the template. + right (list): list of nodes to be replaced. + Returns: + bool: True if the quantum cost is reduced + """ + cost_left = 0 + for i in left: + cost_left += self.cost_dict[self.template_dag_dep.get_node(i).name] + + cost_right = 0 + for j in right: + cost_right += self.cost_dict[self.template_dag_dep.get_node(j).name] + + return cost_left > cost_right + + def _rules(self, circuit_sublist, template_sublist, template_complement): + """ + Set of rules to decide whether the match is to be substitute or not. + Args: + circuit_sublist (list): list of the gates matched in the circuit. + template_sublist (list): list of matched nodes in the template. + template_complement (list): list of gates not matched in the template. + Returns: + bool: True if the match respects the given rule for replacement, False otherwise. + """ + if self._quantum_cost(template_sublist, template_complement): + for elem in circuit_sublist: + for config in self.substitution_list: + if any(elem == x for x in config.circuit_config): + return False + return True + else: + return False + + def _template_inverse(self, template_list, template_sublist, template_complement): + """ + The template circuit realizes the identity operator, then given the list of + matches in the template, it returns the inverse part of the template that + will be replaced. + Args: + template_list (list): list of all gates in the template. + template_sublist (list): list of the gates matched in the circuit. + template_complement (list): list of gates not matched in the template. + Returns: + list: the template inverse part that will substitute the circuit match. + """ + inverse = template_complement + left = [] + right = [] + + pred = set() + for index in template_sublist: + pred = pred | set(self.template_dag_dep.get_node(index).predecessors) + pred = list(pred - set(template_sublist)) + + succ = set() + for index in template_sublist: + succ = succ | set(self.template_dag_dep.get_node(index).successors) + succ = list(succ - set(template_sublist)) + + comm = list(set(template_list) - set(pred) - set(succ)) + + for elem in inverse: + if elem in pred: + left.append(elem) + elif elem in succ: + right.append(elem) + elif elem in comm: + right.append(elem) + + left.sort() + right.sort() + + left.reverse() + right.reverse() + + total = left + right + return total + + def _substitution_sort(self): + """ + Sort the substitution list. + """ + ordered = False + while not ordered: + ordered = self._permutation() + + def _permutation(self): + """ + Permute two groups of matches if first one has predecessors in the second one. + Returns: + bool: True if the matches groups are in the right order, False otherwise. + """ + for scenario in self.substitution_list: + predecessors = set() + for match in scenario.circuit_config: + predecessors = predecessors | set(self.circuit_dag_dep.get_node(match).predecessors) + predecessors = predecessors - set(scenario.circuit_config) + index = self.substitution_list.index(scenario) + for scenario_b in self.substitution_list[index::]: + if set(scenario_b.circuit_config) & predecessors: + + index1 = self.substitution_list.index(scenario) + index2 = self.substitution_list.index(scenario_b) + + scenario_pop = self.substitution_list.pop(index2) + self.substitution_list.insert(index1, scenario_pop) + return False + return True + + def _remove_impossible(self): + """ + Remove matched groups if they both have predecessors in the other one, they are not + compatible. + """ + list_predecessors = [] + remove_list = [] + + # Initialize predecessors for each group of matches. + for scenario in self.substitution_list: + predecessors = set() + for index in scenario.circuit_config: + predecessors = predecessors | set(self.circuit_dag_dep.get_node(index).predecessors) + list_predecessors.append(predecessors) + + # Check if two groups of matches are incompatible. + for scenario_a in self.substitution_list: + if scenario_a in remove_list: + continue + index_a = self.substitution_list.index(scenario_a) + circuit_a = scenario_a.circuit_config + for scenario_b in self.substitution_list[index_a + 1 : :]: + if scenario_b in remove_list: + continue + index_b = self.substitution_list.index(scenario_b) + circuit_b = scenario_b.circuit_config + if (set(circuit_a) & list_predecessors[index_b]) and ( + set(circuit_b) & list_predecessors[index_a] + ): + remove_list.append(scenario_b) + + # Remove the incompatible groups from the list. + if remove_list: + self.substitution_list = [ + scenario for scenario in self.substitution_list if scenario not in remove_list + ] + + def _substitution(self): + """ + From the list of maximal matches, it chooses which one will be used and gives the necessary + details for each substitution(template inverse, predecessors of the match). + """ + + while self.match_stack: + + # Get the first match scenario of the list + current = self.match_stack.pop(0) + + current_match = current.match + current_qubit = current.qubit + current_clbit = current.clbit + + template_sublist = [x[0] for x in current_match] + circuit_sublist = [x[1] for x in current_match] + circuit_sublist.sort() + + # Fake bind any parameters in the template + template = self._attempt_bind(template_sublist, circuit_sublist) + if template is None or self._incr_num_parameters(template): + continue + + template_list = range(0, self.template_dag_dep.size()) + template_complement = list(set(template_list) - set(template_sublist)) + + # If the match obey the rule then it is added to the list. + if self._rules(circuit_sublist, template_sublist, template_complement): + template_sublist_inverse = self._template_inverse( + template_list, template_sublist, template_complement + ) + + config = SubstitutionConfig( + circuit_sublist, + template_sublist_inverse, + [], + current_qubit, + template, + current_clbit, + ) + self.substitution_list.append(config) + + # Remove incompatible matches. + self._remove_impossible() + + # First sort the matches according to the smallest index in the matches (circuit). + self.substitution_list.sort(key=lambda x: x.circuit_config[0]) + + # Change position of the groups due to predecessors of other groups. + self._substitution_sort() + + for scenario in self.substitution_list: + index = self.substitution_list.index(scenario) + scenario.pred_block = self._pred_block(scenario.circuit_config, index) + + circuit_list = [] + for elem in self.substitution_list: + circuit_list = circuit_list + elem.circuit_config + elem.pred_block + + # Unmatched gates that are not predecessors of any group of matches. + self.unmatched_list = sorted(set(range(0, self.circuit_dag_dep.size())) - set(circuit_list)) + + def run_dag_opt(self): + """ + It runs the substitution algorithm and creates the optimized DAGCircuit(). + """ + self._substitution() + + dag_dep_opt = DAGDependency() + + dag_dep_opt.name = self.circuit_dag_dep.name + + qregs = list(self.circuit_dag_dep.qregs.values()) + cregs = list(self.circuit_dag_dep.cregs.values()) + + for register in qregs: + dag_dep_opt.add_qreg(register) + + for register in cregs: + dag_dep_opt.add_creg(register) + + already_sub = [] + + if self.substitution_list: + # Loop over the different matches. + for group in self.substitution_list: + + circuit_sub = group.circuit_config + template_inverse = group.template_config + + pred = group.pred_block + + qubit = group.qubit_config[0] + + if group.clbit_config: + clbit = group.clbit_config[0] + else: + clbit = [] + + # First add all the predecessors of the given match. + for elem in pred: + node = self.circuit_dag_dep.get_node(elem) + inst = node.op.copy() + dag_dep_opt.add_op_node(inst, node.qargs, node.cargs) + already_sub.append(elem) + + already_sub = already_sub + circuit_sub + + # Then add the inverse of the template. + for index in template_inverse: + all_qubits = self.circuit_dag_dep.qubits + qarg_t = group.template_dag_dep.get_node(index).qindices + qarg_c = [qubit[x] for x in qarg_t] + qargs = [all_qubits[x] for x in qarg_c] + + all_clbits = self.circuit_dag_dep.clbits + carg_t = group.template_dag_dep.get_node(index).cindices + + if all_clbits and clbit: + carg_c = [clbit[x] for x in carg_t] + cargs = [all_clbits[x] for x in carg_c] + else: + cargs = [] + node = group.template_dag_dep.get_node(index) + inst = node.op.copy() + dag_dep_opt.add_op_node(inst.inverse(), qargs, cargs) + + # Add the unmatched gates. + for node_id in self.unmatched_list: + node = self.circuit_dag_dep.get_node(node_id) + inst = node.op.copy() + dag_dep_opt.add_op_node(inst, node.qargs, node.cargs) + + dag_dep_opt._add_predecessors() + dag_dep_opt._add_successors() + # If there is no valid match, it returns the original dag. + else: + dag_dep_opt = self.circuit_dag_dep + + self.dag_dep_optimized = dag_dep_opt + self.dag_optimized = dagdependency_to_dag(dag_dep_opt) + + def _attempt_bind(self, template_sublist, circuit_sublist): + """ + Copies the template and attempts to bind any parameters, + i.e. attempts to solve for a valid parameter assignment. + template_sublist and circuit_sublist match up to the + assignment of the parameters. For example the template + + .. parsed-literal:: + + ┌───────────┐ ┌────────┐ + q_0: ┤ P(-1.0*β) ├──■────────────■──┤0 ├ + ├───────────┤┌─┴─┐┌──────┐┌─┴─┐│ CZ(β) │ + q_1: ┤ P(-1.0*β) ├┤ X ├┤ P(β) ├┤ X ├┤1 ├ + └───────────┘└───┘└──────┘└───┘└────────┘ + + should only maximally match once in the circuit + + .. parsed-literal:: + + ┌───────┐ + q_0: ┤ P(-2) ├──■────────────■──────────────────────────── + ├───────┤┌─┴─┐┌──────┐┌─┴─┐┌──────┐ + q_1: ┤ P(-2) ├┤ X ├┤ P(2) ├┤ X ├┤ P(3) ├──■────────────■── + └┬──────┤└───┘└──────┘└───┘└──────┘┌─┴─┐┌──────┐┌─┴─┐ + q_2: ─┤ P(3) ├──────────────────────────┤ X ├┤ P(3) ├┤ X ├ + └──────┘ └───┘└──────┘└───┘ + + However, up until attempt bind is called, the soft matching + will have found two matches due to the parameters. + The first match can be satisfied with β=2. However, the + second match would imply both β=3 and β=-3 which is impossible. + Attempt bind detects inconsistencies by solving a system of equations + given by the parameter expressions in the sub-template and the + value of the parameters in the gates of the sub-circuit. If a + solution is found then the match is valid and the parameters + are assigned. If not, None is returned. + + In order to resolve the conflict of the same parameter names in the + circuit and template, each variable in the template sublist is + re-assigned to a new dummy parameter with a completely separate name + if it clashes with one that exists in an input circuit. + + Args: + template_sublist (list): part of the matched template. + circuit_sublist (list): part of the matched circuit. + + Returns: + DAGDependency: A deep copy of the template with + the parameters bound. If no binding satisfies the + parameter constraints, returns None. + """ + import sympy as sym + from sympy.parsing.sympy_parser import parse_expr + + if _optionals.HAS_SYMENGINE: + import symengine + + # Converts Sympy expressions to Symengine ones. + to_native_symbolic = symengine.sympify + else: + # Our native form is sympy, so we don't need to do anything. + to_native_symbolic = lambda x: x + + circuit_params, template_params = [], [] + # Set of all parameter names that are present in the circuits to be optimised. + circuit_params_set = set() + + template_dag_dep = copy.deepcopy(self.template_dag_dep) + + # add parameters from circuit to circuit_params + for idx, _ in enumerate(template_sublist): + qc_idx = circuit_sublist[idx] + parameters = self.circuit_dag_dep.get_node(qc_idx).op.params + circuit_params += parameters + for parameter in parameters: + if isinstance(parameter, ParameterExpression): + circuit_params_set.update(x.name for x in parameter.parameters) + + _dummy_counter = itertools.count() + + def dummy_parameter(): + # Strictly not _guaranteed_ to avoid naming clashes, but if someone's calling their + # parameters this then that's their own fault. + return Parameter(f"_qiskit_template_dummy_{next(_dummy_counter)}") + + # Substitutions for parameters that have clashing names between the input circuits and the + # defined templates. + template_clash_substitutions = collections.defaultdict(dummy_parameter) + + # add parameters from template to template_params, replacing parameters with names that + # clash with those in the circuit. + for t_idx in template_sublist: + node = template_dag_dep.get_node(t_idx) + sub_node_params = [] + for t_param_exp in node.op.params: + if isinstance(t_param_exp, ParameterExpression): + for t_param in t_param_exp.parameters: + if t_param.name in circuit_params_set: + new_param = template_clash_substitutions[t_param.name] + t_param_exp = t_param_exp.assign(t_param, new_param) + sub_node_params.append(t_param_exp) + template_params.append(t_param_exp) + if not node.op.mutable: + node.op = node.op.to_mutable() + node.op.params = sub_node_params + + for node in template_dag_dep.get_nodes(): + sub_node_params = [] + for param_exp in node.op.params: + if isinstance(param_exp, ParameterExpression): + for param in param_exp.parameters: + if param.name in template_clash_substitutions: + param_exp = param_exp.assign( + param, template_clash_substitutions[param.name] + ) + sub_node_params.append(param_exp) + + if not node.op.mutable: + node.op = node.op.to_mutable() + node.op.params = sub_node_params + + # Create the fake binding dict and check + equations, circ_dict, temp_symbols = [], {}, {} + for circuit_param, template_param in zip(circuit_params, template_params): + if isinstance(template_param, ParameterExpression): + if isinstance(circuit_param, ParameterExpression): + circ_param_sym = circuit_param.sympify() + else: + circ_param_sym = parse_expr(str(circuit_param)) + equations.append(sym.Eq(template_param.sympify(), circ_param_sym)) + + for param in template_param.parameters: + temp_symbols[param] = param.sympify() + + if isinstance(circuit_param, ParameterExpression): + for param in circuit_param.parameters: + circ_dict[param] = param.sympify() + elif template_param != circuit_param: + # Both are numeric parameters, but aren't equal. + return None + + if not temp_symbols: + return template_dag_dep + + # Check compatibility by solving the resulting equation. `dict=True` (surprisingly) forces + # the output to always be a list, even if there's exactly one solution. + sym_sol = sym.solve(equations, set(temp_symbols.values()), dict=True) + if not sym_sol: + # No solutions. + return None + # If there's multiple solutions, arbitrarily pick the first one. + sol = { + param.name: ParameterExpression(circ_dict, to_native_symbolic(expr)) + for param, expr in sym_sol[0].items() + } + fake_bind = {key: sol[key.name] for key in temp_symbols} + + for node in template_dag_dep.get_nodes(): + bound_params = [] + for param_exp in node.op.params: + if isinstance(param_exp, ParameterExpression): + for param in param_exp.parameters: + if param in fake_bind: + if fake_bind[param] not in bound_params: + param_exp = param_exp.assign(param, fake_bind[param]) + else: + param_exp = float(param_exp) + bound_params.append(param_exp) + + if not node.op.mutable: + node.op = node.op.to_mutable() + node.op.params = bound_params + + return template_dag_dep + + def _incr_num_parameters(self, template): + """ + Checks if template substitution would increase the number of + parameters in the circuit. + """ + template_params = set() + for param_list in (node.op.params for node in template.get_nodes()): + for param_exp in param_list: + if isinstance(param_exp, ParameterExpression): + template_params.update(param_exp.parameters) + + circuit_params = set() + for param_list in (node.op.params for node in self.circuit_dag_dep.get_nodes()): + for param_exp in param_list: + if isinstance(param_exp, ParameterExpression): + circuit_params.update(param_exp.parameters) + + return len(template_params) > len(circuit_params) diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py new file mode 100644 index 000000000000..a2c7b5ba59c9 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -0,0 +1,159 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Given a template and a circuit: it applies template matching and substitutes +all compatible maximal matches that reduces the size of the circuit. + +**Reference:** + +[1] Iten, R., Moyard, R., Metger, T., Sutter, D. and Woerner, S., 2020. +Exact and practical pattern matching for quantum circuit optimization. +`arXiv:1909.05270 `_ +""" +import numpy as np + +from qiskit.circuit.quantumcircuit import QuantumCircuit +from qiskit.dagcircuit import DAGDependency +from qiskit.dagcircuit.dagdependency_v2 import DAGDependencyV2 +from qiskit.converters.circuit_to_dagdependency_v2 import _circuit_to_dagdependency_v2 +from qiskit.converters.dagdependency_to_circuit import dagdependency_to_circuit +from qiskit.converters.dag_to_dagdependency_v2 import _dag_to_dagdependency +from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.circuit.library.templates import template_nct_2a_1, template_nct_2a_2, template_nct_2a_3 +from qiskit.quantum_info.operators.operator import Operator +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.passes.optimization.template_matching import ( + TemplateMatching, + TemplateSubstitution, + MaximalMatches, +) + + +class TemplateOptimization(TransformationPass): + """ + Class for the template optimization pass. + """ + + def __init__( + self, + template_list=None, + heuristics_qubits_param=None, + heuristics_backward_param=None, + user_cost_dict=None, + ): + """ + Args: + template_list (list[QuantumCircuit()]): list of the different template circuit to apply. + heuristics_backward_param (list[int]): [length, survivor] Those are the parameters for + applying heuristics on the backward part of the algorithm. This part of the + algorithm creates a tree of matching scenario. This tree grows exponentially. The + heuristics evaluate which scenarios have the longest match and keep only those. + The length is the interval in the tree for cutting it and survivor is the number + of scenarios that are kept. We advise to use l=3 and s=1 to have serious time + advantage. We remind that the heuristics implies losing a part of the maximal + matches. Check reference for more details. + heuristics_qubits_param (list[int]): [length] The heuristics for the qubit choice make + guesses from the dag dependency of the circuit in order to limit the number of + qubit configurations to explore. The length is the number of successors or not + predecessors that will be explored in the dag dependency of the circuit, each + qubits of the nodes are added to the set of authorized qubits. We advise to use + length=1. Check reference for more details. + user_cost_dict (Dict[str, int]): quantum cost dictionary passed to TemplateSubstitution + to configure its behavior. This will override any default values if None + is not given. The key is the name of the gate and the value its quantum cost. + """ + super().__init__() + # If no template is given; the template are set as x-x, cx-cx, ccx-ccx. + if template_list is None: + template_list = [template_nct_2a_1(), template_nct_2a_2(), template_nct_2a_3()] + self.template_list = template_list + self.heuristics_qubits_param = ( + heuristics_qubits_param if heuristics_qubits_param is not None else [] + ) + self.heuristics_backward_param = ( + heuristics_backward_param if heuristics_backward_param is not None else [] + ) + + self.user_cost_dict = user_cost_dict + + def run(self, dag): + """ + Args: + dag(DAGCircuit): DAG circuit. + Returns: + DAGCircuit: optimized DAG circuit. + Raises: + TranspilerError: If the template has not the right form or + if the output circuit acts differently as the input circuit. + """ + circuit_dag = dag + circuit_dag_dep = dag_to_dagdependency(circuit_dag) + + for template in self.template_list: + if not isinstance(template, (QuantumCircuit, DAGDependencyV2)): + raise TranspilerError("A template is a Quantumciruit or a DAGDependency.") + + if len(template.qubits) > len(circuit_dag_dep.qubits): + continue + + identity = np.identity(2 ** len(template.qubits), dtype=complex) + try: + if isinstance(template, DAGDependencyV2): + data = Operator(dagdependency_to_circuit(template)).data + else: + data = Operator(template).data + + comparison = np.allclose(data, identity) + + if not comparison: + raise TranspilerError( + "A template is a Quantumciruit() that performs the identity." + ) + except TypeError: + pass + + if isinstance(template, QuantumCircuit): + template_dag_dep = circuit_to_dagdependency(template) + else: + template_dag_dep = template + + template_m = TemplateMatching( + circuit_dag_dep, + template_dag_dep, + self.heuristics_qubits_param, + self.heuristics_backward_param, + ) + + template_m.run_template_matching() + + matches = template_m.match_list + + if matches: + maximal = MaximalMatches(matches) + maximal.run_maximal_matches() + max_matches = maximal.max_match_list + + substitution = TemplateSubstitution( + max_matches, + template_m.circuit_dag_dep, + template_m.template_dag_dep, + self.user_cost_dict, + ) + substitution.run_dag_opt() + + circuit_dag_dep = substitution.dag_dep_optimized + else: + continue + circuit_dag = dagdependency_to_dag(circuit_dag_dep) + return circuit_dag From fdae1911ad9d2d920211dd096eccd86a87f952d1 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 25 Apr 2024 13:50:23 -0700 Subject: [PATCH 03/18] Finish phase 1 of tempate_matching and forward_match --- .../template_matching_v2/__init__.py | 10 +- .../template_matching_v2/backward_match_v2.py | 10 +- .../template_matching_v2/forward_match_v2.py | 295 ++++--- .../template_matching_v2.py | 77 +- .../optimization/template_optimization_v2.py | 16 +- .../transpiler/test_template_matching_v2.py | 800 ++++++++++++++++++ 6 files changed, 1056 insertions(+), 152 deletions(-) create mode 100644 test/python/transpiler/test_template_matching_v2.py diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py index b2ea3de2d786..c966fb362bf8 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py @@ -12,8 +12,8 @@ """Module containing template matching methods.""" -from .forward_match import ForwardMatch -from .backward_match import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList -from .template_matching import TemplateMatching -from .maximal_matches import MaximalMatches -from .template_substitution import SubstitutionConfig, TemplateSubstitution +from .forward_match_v2 import ForwardMatch +from .backward_match_v2 import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList +from .template_matching_v2 import TemplateMatching +from .maximal_matches_v2 import MaximalMatches +from .template_substitution_v2 import SubstitutionConfig, TemplateSubstitution diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index a4b11a33de2d..5a361b490a24 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -122,6 +122,9 @@ def __init__( forward_matches, node_id_c, node_id_t, + temp_match_class, + matchedwith, + isblocked, qubits, clbits=None, heuristics_backward_param=None, @@ -139,8 +142,8 @@ def __init__( heuristics_backward_param (list): list that contains the two parameters for applying the heuristics (length and survivor). """ - self.circuit_dag_dep = circuit_dag_dep.copy() - self.template_dag_dep = template_dag_dep.copy() + self.circuit_dag_dep = circuit_dag_dep + self.template_dag_dep = template_dag_dep self.qubits = qubits self.clbits = clbits if clbits is not None else [] self.node_id_c = node_id_c @@ -151,6 +154,9 @@ def __init__( heuristics_backward_param if heuristics_backward_param is not None else [] ) self.matching_list = MatchingScenariosList() + self.temp_match_class = temp_match_class + self.matchedwith = matchedwith + self.isblocked = isblocked def _gate_indices(self): """ diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index cfc1bf6f6de7..3655d23f3620 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -33,7 +33,7 @@ class ForwardMatch: """ def __init__( - self, circuit_dag_dep, template_dag_dep, node_id_c, node_id_t, qubits, clbits=None, tm_helpers=None, + self, circuit_dag_dep, template_dag_dep, node_id_c, node_id_t, temp_match_class, qubits, clbits=None, ): """ Create a ForwardMatch class with necessary arguments. @@ -47,10 +47,10 @@ def __init__( """ # The dag dependency representation of the circuit - self.circuit_dag_dep = circuit_dag_dep.copy() + self.circuit_dag_dep = circuit_dag_dep # The dag dependency representation of the template - self.template_dag_dep = template_dag_dep.copy() + self.template_dag_dep = template_dag_dep # List of qubit on which the node of the circuit is acting on self.qubits = qubits @@ -79,51 +79,47 @@ def __init__( # Transformation of the carg indices of the circuit to be adapted to the template indices self.carg_indices = [] - self.tm_helpers = tm_helpers + self.temp_match_class = temp_match_class - def _init_successors_to_visit(self): + self.successorstovisit = {} + self.matchedwith = {} + self.isblocked = {} + + def _init_successorstovisit(self): """ Initialize the attribute list 'SuccessorsToVisit' """ for i in range(0, self.circuit_dag_dep.size()): if i == self.node_id_c: - self.circuit_dag_dep.get_node(i).successorstovisit = ( - self.circuit_dag_dep.direct_successors(i) + self.successorstovisit[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = ( + self.temp_match_class.get_successors(self.circuit_dag_dep, i) ) - def _init_matched_with_circuit(self): + def _init_matchedwith(self): """ Initialize the attribute 'MatchedWith' in the template DAG dependency. """ for i in range(0, self.circuit_dag_dep.size()): if i == self.node_id_c: - self.circuit_dag_dep.get_node(i).matchedwith = [self.node_id_t] + self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = [self.node_id_t] else: - self.circuit_dag_dep.get_node(i).matchedwith = [] + self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = [] - def _init_matched_with_template(self): - """ - Initialize the attribute 'MatchedWith' in the circuit DAG dependency. - """ for i in range(0, self.template_dag_dep.size()): if i == self.node_id_t: - self.template_dag_dep.get_node(i).matchedwith = [self.node_id_c] + self.matchedwith[self.temp_match_class.get_node(self.template_dag_dep, i)] = [self.node_id_c] else: - self.template_dag_dep.get_node(i).matchedwith = [] + self.matchedwith[self.temp_match_class.get_node(self.template_dag_dep, i)] = [] - def _init_is_blocked_circuit(self): + def _init_isblocked(self): """ Initialize the attribute 'IsBlocked' in the circuit DAG dependency. """ for i in range(0, self.circuit_dag_dep.size()): - self.circuit_dag_dep.get_node(i).isblocked = False + self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = False - def _init_is_blocked_template(self): - """ - Initialize the attribute 'IsBlocked' in the template DAG dependency. - """ for i in range(0, self.template_dag_dep.size()): - self.template_dag_dep.get_node(i).isblocked = False + self.isblocked[self.temp_match_class.get_node(self.template_dag_dep, i)] = False def _init_list_match(self): """ @@ -148,27 +144,26 @@ def _find_forward_candidates(self, node_id_t): pred.sort() pred.remove(node_id_t) - if self.template_dag_dep.direct_successors(node_id_t): - maximal_index = self.template_dag_dep.direct_successors(node_id_t)[-1] + temp_succs = self.temp_match_class.get_successors(self.template_dag_dep, node_id_t) + if temp_succs: + maximal_index = temp_succs[-1] pred = [elem for elem in pred if elem <= maximal_index] block = [] for node_id in pred: - for dir_succ in self.template_dag_dep.direct_successors(node_id): + for dir_succ in self.temp_match_class.get_successors(self.template_dag_dep, node_id): if dir_succ not in matches: - succ = self.template_dag_dep.successors(dir_succ) + succ = self.temp_match_class.get_descendants(self.template_dag_dep, dir_succ) block = block + succ self.candidates = list( - set(self.template_dag_dep.direct_successors(node_id_t)) - set(matches) - set(block) + set(temp_succs) - set(matches) - set(block) ) def _init_matched_nodes(self): """ Initialize the list of current matched nodes. """ - self.matched_nodes_list.append( - [self.node_id_c, self.circuit_dag_dep.get_node(self.node_id_c)] - ) + self.matched_nodes_list.append(self.temp_match_class.get_node(self.circuit_dag_dep, self.node_id_c)) def _get_node_forward(self, list_id): """ @@ -179,30 +174,7 @@ def _get_node_forward(self, list_id): Returns: DAGDepNode: DAGDepNode object corresponding to i-th node of the matched_node_list. """ - node = self.matched_nodes_list[list_id][1] - return node - - def _remove_node_forward(self, list_id): - """ - Remove a node of the current matched list for a given list id. - Args: - list_id (int): considered list id of the desired node. - """ - self.matched_nodes_list.pop(list_id) - - def _update_successor(self, node, successor_id): - """ - Return a node with an updated attribute 'SuccessorToVisit'. - Args: - node (DAGDepNode): current node. - successor_id (int): successor id to remove. - - Returns: - DAGOpNode or DAGOutNode: Node with updated attribute 'SuccessorToVisit'. - """ - node_update = node - node_update.successorstovisit.pop(successor_id) - return node_update + return self.matched_nodes_list.pop(list_id)[1] def _get_successors_to_visit(self, node, list_id): """ @@ -214,8 +186,7 @@ def _get_successors_to_visit(self, node, list_id): Returns: int: id of the successor to get. """ - successor_id = node.successorstovisit[list_id] - return successor_id + return self.successorstovisit[node].pop(list_id) def _update_qarg_indices(self, qarg): """ @@ -267,20 +238,21 @@ def _is_same_q_conf(self, node_circuit, node_template): bool: True if possible, False otherwise. """ + node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) if isinstance(node_circuit.op, ControlledGate): c_template = node_template.op.num_ctrl_qubits if c_template == 1: - return self.qarg_indices == node_template.qindices + return self.qarg_indices == node_temp_qind else: - control_qubits_template = node_template.qindices[:c_template] + control_qubits_template = node_temp_qind[:c_template] control_qubits_circuit = self.qarg_indices[:c_template] if set(control_qubits_circuit) == set(control_qubits_template): - target_qubits_template = node_template.qindices[c_template::] + target_qubits_template = node_temp_qind[c_template::] target_qubits_circuit = self.qarg_indices[c_template::] if node_template.op.base_gate.name in [ @@ -300,7 +272,7 @@ def _is_same_q_conf(self, node_circuit, node_template): if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: return set(self.qarg_indices) == set(node_template.qindices) else: - return self.qarg_indices == node_template.qindices + return self.qarg_indices == node_temp_qind def _is_same_c_conf(self, node_circuit, node_template): """ @@ -312,8 +284,7 @@ def _is_same_c_conf(self, node_circuit, node_template): bool: True if possible, False otherwise. """ if ( - node_circuit.type == "op" - and getattr(node_circuit.op, "condition", None) + getattr(node_circuit.op, "condition", None) and node_template.type == "op" and getattr(node_template.op, "condition", None) ): @@ -333,13 +304,9 @@ def run_forward_match(self): """ # Initialize the new attributes of the DAGDepNodes of the DAGDependency object - self._init_successors_to_visit() - - self._init_matched_with_circuit() - self._init_matched_with_template() - - self._init_is_blocked_circuit() - self._init_is_blocked_template() + self._init_successorstovisit() + self._init_matchedwith() + self._init_isblocked() # Initialize the list of matches and the stack of matched nodes (circuit) self._init_list_match() @@ -348,34 +315,28 @@ def run_forward_match(self): # While the list of matched nodes is not empty while self.matched_nodes_list: - # Return first element of the matched_nodes_list and removes it from the list - v_first = self._get_node_forward(0) - self._remove_node_forward(0) - - # If there is no successors to visit go to the end - if not v_first.successorstovisit: + first_matched = self.matched_nodes_list.pop(0) + print("\n\nFIRST MATCH", first_matched) + if self.successorstovisit[first_matched] == []: continue - # Get the label and the node of the first successor to visit - label = self._get_successors_to_visit(v_first, 0) - v = [label, self.circuit_dag_dep.get_node(label)] - - # Update of the SuccessorsToVisit attribute - v_first = self._update_successor(v_first, 0) + # Get the id and the node of the first successor to visit + trial_successor_id = self.successorstovisit[first_matched].pop(0) + trial_successor = self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) # Update the matched_nodes_list with new attribute successor to visit and sort the list. - self.matched_nodes_list.append([v_first.node_id, v_first]) - self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) + self.matched_nodes_list.append(first_matched) + self.matched_nodes_list.sort(key=lambda x: self.successorstovisit[x]) # If the node is blocked and already matched go to the end - if v[1].isblocked | (v[1].matchedwith != []): + if self.isblocked[trial_successor] | (self.matchedwith[trial_successor] != []): continue # Search for potential candidates in the template - self._find_forward_candidates(v_first.matchedwith[0]) + self._find_forward_candidates(self.matchedwith[first_matched][0]) - qarg1 = self.circuit_dag_dep.get_node(label).qindices - carg1 = self.circuit_dag_dep.get_node(label).cindices + qarg1 = self.temp_match_class.qindices(self.circuit_dag_dep, self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id)) + carg1 = self.temp_match_class.cindices(self.circuit_dag_dep, self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id)) # Update the indices for both qubits and clbits in order to be comparable with the # indices in the template circuit. @@ -391,15 +352,16 @@ def run_forward_match(self): if match: break - # Compare the indices of qubits and the operation, - # if True; a match is found - node_circuit = self.circuit_dag_dep.get_node(label) - node_template = self.template_dag_dep.get_node(i) + node_circuit = self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) + node_template = self.temp_match_class.get_node(self.template_dag_dep, i) + # Compare the indices of qubits and the operation name. # Necessary but not sufficient conditions for a match to happen. + node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) if ( - len(self.qarg_indices) != len(node_template.qindices) - or set(self.qarg_indices) != set(node_template.qindices) + len(self.qarg_indices) != len(node_temp_qind) + or set(self.qarg_indices) + != set(node_temp_qind) or node_circuit.name != node_template.name ): continue @@ -411,44 +373,149 @@ def run_forward_match(self): and self._is_same_c_conf(node_circuit, node_template) and self._is_same_op(node_circuit, node_template) ): - - v[1].matchedwith = [i] - - self.template_dag_dep.get_node(i).matchedwith = [label] + self.matchedwith[trial_successor] = [i] + self.matchedwith[node_template] = [trial_successor_id] # Append the new match to the list of matches. - self.match.append([i, label]) + self.match.append([i, trial_successor_id]) # Potential successors to visit (circuit) for a given match. - potential = self.circuit_dag_dep.direct_successors(label) + potential = self.temp_match_class.get_successors(self.circuit_dag_dep, trial_successor_id) - # If the potential successors to visit are blocked or match, it is removed. + # If the potential successors to visit are blocked or matched, it is removed. for potential_id in potential: - if self.circuit_dag_dep.get_node(potential_id).isblocked | ( - self.circuit_dag_dep.get_node(potential_id).matchedwith != [] + if self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, potential_id)] | ( + self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, potential_id)] != [] ): potential.remove(potential_id) sorted_potential = sorted(potential) # Update the successor to visit attribute - v[1].successorstovisit = sorted_potential + self.successorstovisit[trial_successor] = sorted_potential # Add the updated node to the stack. - self.matched_nodes_list.append([v[0], v[1]]) - self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) + self.matched_nodes_list.append(trial_successor) + self.matched_nodes_list.sort(key=lambda x: self.successorstovisit[x]) match = True continue # If no match is found, block the node and all the successors. if not match: - v[1].isblocked = True - for succ in v[1].successors: - self.circuit_dag_dep.get_node(succ).isblocked = True - if self.circuit_dag_dep.get_node(succ).matchedwith: + self.isblocked[trial_successor] = True + if self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) not in self.descendants: + self.temp_match_class.descendants[trial_successor_id] = self.temp_match_class.get_descendants(self.circuit_dag_dep, trial_successor) + for desc in self.temp_match_class.descendants[trial_successor_id]: + self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, desc)] = True + if self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)]: self.match.remove( - [self.circuit_dag_dep.get_node(succ).matchedwith[0], succ] + [self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)][0], desc] ) - match_id = self.circuit_dag_dep.get_node(succ).matchedwith[0] - self.template_dag_dep.get_node(match_id).matchedwith = [] - self.circuit_dag_dep.get_node(succ).matchedwith = [] + match_id = self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)][0] + self.matchedwith[self.temp_match_class.get_node(template_dag_dep, match_id)] = [] + self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)] = [] + return (self.matchedwith, self.isblocked) + + # # Return first element of the matched_nodes_list and removes it from the list + # v_first = self._get_node_forward(0) + + # # If there is no successors to visit go to the end + # if not self.successorstovisit[v_first]: + # continue + + # # Get the label and the node of the first successor to visit + # label = self._get_successors_to_visit(v_first, 0) + # v = [label, self.circuit_dag_dep.get_node(label)] + + # # Update of the SuccessorsToVisit attribute + # #v_first = self._update_successor(v_first, 0) + + # # Update the matched_nodes_list with new attribute successor to visit and sort the list. + # self.matched_nodes_list.append([v_first.node_id, v_first]) + # self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) + + # # If the node is blocked and already matched go to the end + # if v[1].isblocked | (v[1].matchedwith != []): + # continue + + # # Search for potential candidates in the template + # self._find_forward_candidates(v_first.matchedwith[0]) + + # qarg1 = self.circuit_dag_dep.get_node(label).qindices + # carg1 = self.circuit_dag_dep.get_node(label).cindices + + # # Update the indices for both qubits and clbits in order to be comparable with the + # # indices in the template circuit. + # self._update_qarg_indices(qarg1) + # self._update_carg_indices(carg1) + + # match = False + + # # For loop over the candidates (template) to find a match. + # for i in self.candidates: + + # # Break the for loop if a match is found. + # if match: + # break + + # # Compare the indices of qubits and the operation, + # # if True; a match is found + # node_circuit = self.circuit_dag_dep.get_node(label) + # node_template = self.template_dag_dep.get_node(i) + + # # Necessary but not sufficient conditions for a match to happen. + # if ( + # len(self.qarg_indices) != len(node_template.qindices) + # or set(self.qarg_indices) != set(node_template.qindices) + # or node_circuit.name != node_template.name + # ): + # continue + + # # Check if the qubit, clbit configuration are compatible for a match, + # # also check if the operation are the same. + # if ( + # self._is_same_q_conf(node_circuit, node_template) + # and self._is_same_c_conf(node_circuit, node_template) + # and self._is_same_op(node_circuit, node_template) + # ): + + # v[1].matchedwith = [i] + + # self.template_dag_dep.get_node(i).matchedwith = [label] + + # # Append the new match to the list of matches. + # self.match.append([i, label]) + + # # Potential successors to visit (circuit) for a given match. + # potential = self.circuit_dag_dep.direct_successors(label) + + # # If the potential successors to visit are blocked or match, it is removed. + # for potential_id in potential: + # if self.circuit_dag_dep.get_node(potential_id).isblocked | ( + # self.circuit_dag_dep.get_node(potential_id).matchedwith != [] + # ): + # potential.remove(potential_id) + + # sorted_potential = sorted(potential) + + # # Update the successor to visit attribute + # v[1].successorstovisit = sorted_potential + + # # Add the updated node to the stack. + # self.matched_nodes_list.append([v[0], v[1]]) + # self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) + # match = True + # continue + + # # If no match is found, block the node and all the successors. + # if not match: + # v[1].isblocked = True + # for succ in v[1].successors: + # self.circuit_dag_dep.get_node(succ).isblocked = True + # if self.circuit_dag_dep.get_node(succ).matchedwith: + # self.match.remove( + # [self.circuit_dag_dep.get_node(succ).matchedwith[0], succ] + # ) + # match_id = self.circuit_dag_dep.get_node(succ).matchedwith[0] + # self.template_dag_dep.get_node(match_id).matchedwith = [] + # self.circuit_dag_dep.get_node(succ).matchedwith = [] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index f31e67d21dd5..90878ab62c47 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -25,9 +25,11 @@ import itertools +import rustworkx as rx + from qiskit.circuit.controlledgate import ControlledGate -from qiskit.transpiler.passes.optimization.template_matching.forward_match import ForwardMatch -from qiskit.transpiler.passes.optimization.template_matching.backward_match import BackwardMatch +from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch +from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import BackwardMatch class TemplateMatching: @@ -59,6 +61,8 @@ def __init__( self.heuristics_backward_param = ( heuristics_backward_param if heuristics_backward_param is not None else [] ) + self.descendants = {} + self.ancestors = {} def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbits_t): """ @@ -105,24 +109,24 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit # Symmetric gate if node_template.op.name not in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: l_q_sub = [-1] * n_qubits_t - for q in node_template.qindices: - l_q_sub[q] = node_circuit.qindices[node_template.qindices.index(q)] + for q in self.qindices(self.template_dag_dep, node_template): + l_q_sub[q] = self.qindices(self.circuit_dag_dep, node_circuit)[self.qindices(self.template_dag_dep, node_template).index(q)] l_q.append(l_q_sub) # Not symmetric else: - for perm_q in itertools.permutations(node_circuit.qindices): + for perm_q in itertools.permutations(self.qindices(self.circuit_dag_dep, node_circuit)): l_q_sub = [-1] * n_qubits_t for q in node_template.qindices: - l_q_sub[q] = perm_q[node_template.qindices.index(q)] + l_q_sub[q] = perm_q[self.qindices(self.template_dag_dep, node_template).index(q)] l_q.append(l_q_sub) # Classical control - if not node_template.cindices or not node_circuit.cindices: + if not self.cindices(self.template_dag_dep, node_template) or not self.cindices(self.circuit_dag_dep, node_circuit): l_c = [] else: l_c = [-1] * n_clbits_t - for c in node_template.cindices: - l_c[c] = node_circuit[node_template.cindices.index(c)] + for c in self.cindices(self.template_dag_dep, node_template): + l_c[c] = node_circuit[self.cindices(self.template_dag_dep, node_template).index(c)] return l_q, l_c @@ -199,12 +203,13 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): """ template_nodes = range(node_id_t + 1, self.template_dag_dep.size()) circuit_nodes = range(0, self.circuit_dag_dep.size()) - successors_template = self.template_dag_dep.get_node(node_id_t).successors + if self.template_dag_dep.get_node(node_id_t) not in self.descendants: + self.descendants[node_id_t] = self.get_descendants(self.template_dag_dep, self.template_dag_dep.get_node(node_id_t)) counter = 1 qubit_set = set(self.circuit_dag_dep.get_node(node_id_c).qindices) - if 2 * len(successors_template) > len(template_nodes): - successors = self.circuit_dag_dep.get_node(node_id_c).successors + if 2 * len(self.descendants[node_id_t]) > len(template_nodes): + successors = self.get_descendants(self.circuit_dag_dep, self.circuit_dag_dep.get_node(node_id_c)) for succ in successors: qarg = self.circuit_dag_dep.get_node(succ).qindices if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: @@ -215,8 +220,10 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): return list(qubit_set) else: + if self.get_node(self.circuit_dag_dep, node_id_c) not in self.descendants: + self.descendants[node_id_c] = self.get_descendants(self.circuit_dag_dep, self.get_node(circuit_dag_dep, node_id_c)) not_successors = list( - set(circuit_nodes) - set(self.circuit_dag_dep.get_node(node_id_c).successors) + set(circuit_nodes) - set(self.descendants[node_id_c]) ) candidate = [ not_successors[j] @@ -235,6 +242,24 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): def get_node(self, dag, node_id): return dag._multi_graph[node_id] + def qindices(self, dag, node): + return [dag.find_bit(qarg).index for qarg in node.qargs] + + def cindices(self, dag, node): + return [dag.find_bit(carg).index for carg in node.cargs] + + def get_descendants(self, dag, node): + return list(rx.descendants(dag._multi_graph, node_id)) + + def get_ancestors(self, dag, node): + return list(rx.ancestors(dag._multi_graph, node_id)) + + def get_successors(self, dag, node): + return [succ._node_id for succ in dag._multi_graph.successors(node)] + + def get_predecessors(self, dag, node): + return [pred._node_id for pred in dag._multi_graph.predecessors(node)] + def run_template_matching(self): """ Run the complete algorithm for finding all maximal matches for the given template and @@ -256,15 +281,16 @@ def run_template_matching(self): for template_index in range(0, self.template_dag_dep.size()): for circuit_index in range(0, self.circuit_dag_dep.size()): # Operations match up to ParameterExpressions. - if self.circuit_dag_dep.get_node(circuit_index).op.soft_compare( - self.template_dag_dep.get_node(template_index).op + #if self.circuit_dag_dep.get_node(circuit_index).op.soft_compare( + if self.get_node(self.circuit_dag_dep, circuit_index).op.soft_compare( + self.get_node(self.template_dag_dep, template_index).op ): - qarg_c = self.circuit_dag_dep.get_node(circuit_index).qindices - carg_c = self.circuit_dag_dep.get_node(circuit_index).cindices + qarg_c = self.qindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, circuit_index)) + carg_c = self.cindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, circuit_index)) - qarg_t = self.template_dag_dep.get_node(template_index).qindices - carg_t = self.template_dag_dep.get_node(template_index).cindices + qarg_t = self.qindices(self.template_dag_dep, self.get_node(self.template_dag_dep, template_index)) + carg_t = self.cindices(self.template_dag_dep, self.get_node(self.template_dag_dep, template_index)) node_id_c = circuit_index node_id_t = template_index @@ -272,8 +298,8 @@ def run_template_matching(self): # Fix the qubits and clbits configuration given the first match. all_list_first_match_q, list_first_match_c = self._list_first_match_new( - self.circuit_dag_dep.get_node(circuit_index), - self.template_dag_dep.get_node(template_index), + self.get_node(self.circuit_dag_dep, circuit_index), + self.get_node(self.template_dag_dep, template_index), n_qubits_t, n_clbits_t, ) @@ -321,9 +347,9 @@ def run_template_matching(self): self.template_dag_dep, node_id_c, node_id_t, + self, list_qubit_circuit, list_clbit_circuit, - self, ) forward.run_forward_match() @@ -334,6 +360,7 @@ def run_template_matching(self): forward.match, node_id_c, node_id_t, + self, list_qubit_circuit, list_clbit_circuit, self.heuristics_backward_param, @@ -350,9 +377,10 @@ def run_template_matching(self): self.template_dag_dep, node_id_c, node_id_t, + self, list_qubit_circuit, ) - forward.run_forward_match() + matchedwith, isblocked = forward.run_forward_match() # Apply the backward match part of the algorithm. backward = BackwardMatch( @@ -361,6 +389,9 @@ def run_template_matching(self): forward.match, node_id_c, node_id_t, + self, + matchedwith, + isblocked, list_qubit_circuit, [], self.heuristics_backward_param, diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index a2c7b5ba59c9..fc69a8280662 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -24,23 +24,23 @@ from qiskit.circuit.quantumcircuit import QuantumCircuit from qiskit.dagcircuit import DAGDependency -from qiskit.dagcircuit.dagdependency_v2 import DAGDependencyV2 +from qiskit.dagcircuit.dagdependency_v2 import _DAGDependencyV2 from qiskit.converters.circuit_to_dagdependency_v2 import _circuit_to_dagdependency_v2 from qiskit.converters.dagdependency_to_circuit import dagdependency_to_circuit -from qiskit.converters.dag_to_dagdependency_v2 import _dag_to_dagdependency +from qiskit.converters.dag_to_dagdependency_v2 import _dag_to_dagdependency_v2 from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag from qiskit.transpiler.basepasses import TransformationPass from qiskit.circuit.library.templates import template_nct_2a_1, template_nct_2a_2, template_nct_2a_3 from qiskit.quantum_info.operators.operator import Operator from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes.optimization.template_matching import ( +from qiskit.transpiler.passes.optimization.template_matching_v2 import ( TemplateMatching, TemplateSubstitution, MaximalMatches, ) -class TemplateOptimization(TransformationPass): +class TemplateOptimizationV2(TransformationPass): """ Class for the template optimization pass. """ @@ -98,10 +98,10 @@ def run(self, dag): if the output circuit acts differently as the input circuit. """ circuit_dag = dag - circuit_dag_dep = dag_to_dagdependency(circuit_dag) + circuit_dag_dep = _dag_to_dagdependency_v2(circuit_dag) for template in self.template_list: - if not isinstance(template, (QuantumCircuit, DAGDependencyV2)): + if not isinstance(template, (QuantumCircuit, _DAGDependencyV2)): raise TranspilerError("A template is a Quantumciruit or a DAGDependency.") if len(template.qubits) > len(circuit_dag_dep.qubits): @@ -109,7 +109,7 @@ def run(self, dag): identity = np.identity(2 ** len(template.qubits), dtype=complex) try: - if isinstance(template, DAGDependencyV2): + if isinstance(template, _DAGDependencyV2): data = Operator(dagdependency_to_circuit(template)).data else: data = Operator(template).data @@ -124,7 +124,7 @@ def run(self, dag): pass if isinstance(template, QuantumCircuit): - template_dag_dep = circuit_to_dagdependency(template) + template_dag_dep = _circuit_to_dagdependency_v2(template) else: template_dag_dep = template diff --git a/test/python/transpiler/test_template_matching_v2.py b/test/python/transpiler/test_template_matching_v2.py new file mode 100644 index 000000000000..577583d7b989 --- /dev/null +++ b/test/python/transpiler/test_template_matching_v2.py @@ -0,0 +1,800 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + + +"""Test the TemplateOptimizationV2 pass.""" + +import unittest + +import numpy as np +from qiskit.circuit.commutation_library import SessionCommutationChecker as scc +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.circuit import Parameter +from qiskit.quantum_info import Operator +from qiskit.circuit.library.templates.nct import template_nct_2a_2, template_nct_5a_3 +from qiskit.circuit.library.templates.clifford import ( + clifford_2_1, + clifford_2_2, + clifford_2_3, + clifford_2_4, + clifford_3_1, + clifford_4_1, + clifford_4_2, +) +from qiskit.converters.circuit_to_dag import circuit_to_dag +from qiskit.converters.circuit_to_dagdependency_v2 import _circuit_to_dagdependency_v2 +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes.optimization.template_optimization_v2 import TemplateOptimizationV2 +from qiskit.transpiler.passes.calibration.rzx_templates import rzx_templates +from qiskit.transpiler.exceptions import TranspilerError +from test.python.quantum_info.operators.symplectic.test_clifford import ( # pylint: disable=wrong-import-order + random_clifford_circuit, +) +from test import QiskitTestCase # pylint: disable=wrong-import-order + + +def _ry_to_rz_template_pass(parameter: Parameter = None, extra_costs=None): + """Create a simple pass manager that runs a template optimisation with a single transformation. + It turns ``RX(pi/2).RY(parameter).RX(-pi/2)`` into the equivalent virtual ``RZ`` rotation, where + if ``parameter`` is given, it will be the instance used in the template.""" + if parameter is None: + parameter = Parameter("_ry_rz_template_inner") + template = QuantumCircuit(1) + template.rx(-np.pi / 2, 0) + template.ry(parameter, 0) + template.rx(np.pi / 2, 0) + template.rz(-parameter, 0) # pylint: disable=invalid-unary-operand-type + + costs = {"rx": 16, "ry": 16, "rz": 0} + if extra_costs is not None: + costs.update(extra_costs) + + return PassManager(TemplateOptimizationV2([template], user_cost_dict=costs)) + + +class TestTemplateMatching(QiskitTestCase): + """Test the TemplateOptimizationV2 pass.""" + + def test_pass_cx_cancellation_no_template_given(self): + """ + Check the cancellation of CX gates for the apply of the three basic + template x-x, cx-cx. ccx-ccx. + """ + qr = QuantumRegister(3) + circuit_in = QuantumCircuit(qr) + circuit_in.h(qr[0]) + circuit_in.h(qr[0]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[1], qr[0]) + circuit_in.cx(qr[1], qr[0]) + + pass_manager = PassManager() + pass_manager.append(TemplateOptimizationV2()) + circuit_in_opt = pass_manager.run(circuit_in) + + circuit_out = QuantumCircuit(qr) + circuit_out.h(qr[0]) + circuit_out.h(qr[0]) + + self.assertEqual(circuit_in_opt, circuit_out) + + def test_pass_cx_cancellation_own_template(self): + """ + Check the cancellation of CX gates for the apply of a self made template cx-cx. + """ + qr = QuantumRegister(2, "qr") + circuit_in = QuantumCircuit(qr) + circuit_in.h(qr[0]) + circuit_in.h(qr[0]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[1], qr[0]) + circuit_in.cx(qr[1], qr[0]) + dag_in = circuit_to_dag(circuit_in) + + qrt = QuantumRegister(2, "qrc") + qct = QuantumCircuit(qrt) + qct.cx(0, 1) + qct.cx(0, 1) + + template_list = [qct] + pass_ = TemplateOptimizationV2(template_list) + dag_opt = pass_.run(dag_in) + + circuit_expected = QuantumCircuit(qr) + circuit_expected.h(qr[0]) + circuit_expected.h(qr[0]) + dag_expected = circuit_to_dag(circuit_expected) + + self.assertEqual(dag_opt, dag_expected) + + def test_pass_cx_cancellation_template_from_library(self): + """ + Check the cancellation of CX gates for the apply of the library template cx-cx (2a_2). + """ + qr = QuantumRegister(2, "qr") + circuit_in = QuantumCircuit(qr) + circuit_in.h(qr[0]) + circuit_in.h(qr[0]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[1], qr[0]) + circuit_in.cx(qr[1], qr[0]) + dag_in = circuit_to_dag(circuit_in) + + template_list = [template_nct_2a_2()] + pass_ = TemplateOptimizationV2(template_list) + dag_opt = pass_.run(dag_in) + + circuit_expected = QuantumCircuit(qr) + circuit_expected.h(qr[0]) + circuit_expected.h(qr[0]) + dag_expected = circuit_to_dag(circuit_expected) + + self.assertEqual(dag_opt, dag_expected) + + def test_pass_template_nct_5a(self): + """ + Verify the result of template matching and substitution with the template 5a_3. + q_0: ───────■─────────■────■── + ┌─┴─┐ ┌─┴─┐ │ + q_1: ──■──┤ X ├──■──┤ X ├──┼── + ┌─┴─┐└───┘┌─┴─┐└───┘┌─┴─┐ + q_2: ┤ X ├─────┤ X ├─────┤ X ├ + └───┘ └───┘ └───┘ + The circuit before optimization is: + ┌───┐ ┌───┐ + qr_0: ┤ X ├───────────────┤ X ├───── + └─┬─┘ ┌───┐┌───┐└─┬─┘ + qr_1: ──┼────■──┤ X ├┤ Z ├──┼────■── + │ │ └─┬─┘└───┘ │ │ + qr_2: ──┼────┼────■────■────■────┼── + │ │ ┌───┐┌─┴─┐ │ │ + qr_3: ──■────┼──┤ H ├┤ X ├──■────┼── + │ ┌─┴─┐└───┘└───┘ ┌─┴─┐ + qr_4: ──■──┤ X ├───────────────┤ X ├ + └───┘ └───┘ + + The match is given by [0,1][1,2][2,7], after substitution the circuit becomes: + + ┌───┐ ┌───┐ + qr_0: ┤ X ├───────────────┤ X ├ + └─┬─┘ ┌───┐┌───┐└─┬─┘ + qr_1: ──┼───────┤ X ├┤ Z ├──┼── + │ └─┬─┘└───┘ │ + qr_2: ──┼────■────■────■────■── + │ │ ┌───┐┌─┴─┐ │ + qr_3: ──■────┼──┤ H ├┤ X ├──■── + │ ┌─┴─┐└───┘└───┘ + qr_4: ──■──┤ X ├─────────────── + └───┘ + """ + qr = QuantumRegister(5, "qr") + circuit_in = QuantumCircuit(qr) + circuit_in.ccx(qr[3], qr[4], qr[0]) + circuit_in.cx(qr[1], qr[4]) + circuit_in.cx(qr[2], qr[1]) + circuit_in.h(qr[3]) + circuit_in.z(qr[1]) + circuit_in.cx(qr[2], qr[3]) + circuit_in.ccx(qr[2], qr[3], qr[0]) + circuit_in.cx(qr[1], qr[4]) + dag_in = circuit_to_dag(circuit_in) + + template_list = [template_nct_5a_3()] + pass_ = TemplateOptimizationV2(template_list) + dag_opt = pass_.run(dag_in) + + # note: cx(2, 1) commutes both with ccx(3, 4, 0) and with cx(2, 4), + # so there is no real difference with the circuit drawn on the picture above. + circuit_expected = QuantumCircuit(qr) + circuit_expected.cx(qr[2], qr[1]) + circuit_expected.ccx(qr[3], qr[4], qr[0]) + circuit_expected.cx(qr[2], qr[4]) + circuit_expected.z(qr[1]) + circuit_expected.h(qr[3]) + circuit_expected.cx(qr[2], qr[3]) + circuit_expected.ccx(qr[2], qr[3], qr[0]) + + dag_expected = circuit_to_dag(circuit_expected) + + self.assertEqual(dag_opt, dag_expected) + + def test_pass_template_wrong_type(self): + """ + If a template is not equivalent to the identity, it raises an error. + """ + qr = QuantumRegister(2, "qr") + circuit_in = QuantumCircuit(qr) + circuit_in.h(qr[0]) + circuit_in.h(qr[0]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[0], qr[1]) + circuit_in.cx(qr[1], qr[0]) + circuit_in.cx(qr[1], qr[0]) + dag_in = circuit_to_dag(circuit_in) + + qrt = QuantumRegister(2, "qrc") + qct = QuantumCircuit(qrt) + qct.cx(0, 1) + qct.x(0) + qct.h(1) + + template_list = [qct] + pass_ = TemplateOptimizationV2(template_list) + + self.assertRaises(TranspilerError, pass_.run, dag_in) + + def test_accept_dagdependency_v2(self): + """ + Check that users can supply DAGDependency in the template list. + """ + circuit_in = QuantumCircuit(2) + circuit_in.cx(0, 1) + circuit_in.cx(0, 1) + + templates = [_circuit_to_dagdependency_v2(circuit_in)] + + pass_ = TemplateOptimizationV2(template_list=templates) + circuit_out = PassManager(pass_).run(circuit_in) + + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + self.assertTrue(Operator(circuit_in).equiv(circuit_out)) + + def test_parametric_template(self): + """ + Check matching where template has parameters. + ┌───────────┐ ┌────────┐ + q_0: ┤ P(-1.0*β) ├──■────────────■──┤0 ├ + ├───────────┤┌─┴─┐┌──────┐┌─┴─┐│ CU(2β)│ + q_1: ┤ P(-1.0*β) ├┤ X ├┤ P(β) ├┤ X ├┤1 ├ + └───────────┘└───┘└──────┘└───┘└────────┘ + First test try match on + ┌───────┐ + q_0: ┤ P(-2) ├──■────────────■───────────────────────────── + ├───────┤┌─┴─┐┌──────┐┌─┴─┐┌───────┐ + q_1: ┤ P(-2) ├┤ X ├┤ P(2) ├┤ X ├┤ P(-3) ├──■────────────■── + ├───────┤└───┘└──────┘└───┘└───────┘┌─┴─┐┌──────┐┌─┴─┐ + q_2: ┤ P(-3) ├───────────────────────────┤ X ├┤ P(3) ├┤ X ├ + └───────┘ └───┘└──────┘└───┘ + Second test try match on + ┌───────┐ + q_0: ┤ P(-2) ├──■────────────■──────────────────────────── + ├───────┤┌─┴─┐┌──────┐┌─┴─┐┌──────┐ + q_1: ┤ P(-2) ├┤ X ├┤ P(2) ├┤ X ├┤ P(3) ├──■────────────■── + └┬──────┤└───┘└──────┘└───┘└──────┘┌─┴─┐┌──────┐┌─┴─┐ + q_2: ─┤ P(3) ├──────────────────────────┤ X ├┤ P(3) ├┤ X ├ + └──────┘ └───┘└──────┘└───┘ + """ + + beta = Parameter("β") + template = QuantumCircuit(2) + template.p(-beta, 0) + template.p(-beta, 1) + template.cx(0, 1) + template.p(beta, 1) + template.cx(0, 1) + template.cu(0, 2.0 * beta, 0, 0, 0, 1) + + def count_cx(qc): + """Counts the number of CX gates for testing.""" + return qc.count_ops().get("cx", 0) + + circuit_in = QuantumCircuit(3) + circuit_in.p(-2, 0) + circuit_in.p(-2, 1) + circuit_in.cx(0, 1) + circuit_in.p(2, 1) + circuit_in.cx(0, 1) + circuit_in.p(-3, 1) + circuit_in.p(-3, 2) + circuit_in.cx(1, 2) + circuit_in.p(3, 2) + circuit_in.cx(1, 2) + + pass_ = TemplateOptimizationV2( + template_list=[template], + user_cost_dict={"cx": 6, "p": 0, "cu": 8}, + ) + circuit_out = PassManager(pass_).run(circuit_in) + + np.testing.assert_almost_equal(Operator(circuit_out).data[3, 3], np.exp(-4.0j)) + np.testing.assert_almost_equal(Operator(circuit_out).data[7, 7], np.exp(-10.0j)) + self.assertEqual(count_cx(circuit_out), 0) # Two matches => no CX gates. + np.testing.assert_almost_equal(Operator(circuit_in).data, Operator(circuit_out).data) + + circuit_in = QuantumCircuit(3) + circuit_in.p(-2, 0) + circuit_in.p(-2, 1) + circuit_in.cx(0, 1) + circuit_in.p(2, 1) + circuit_in.cx(0, 1) + circuit_in.p(3, 1) + circuit_in.p(3, 2) + circuit_in.cx(1, 2) + circuit_in.p(3, 2) + circuit_in.cx(1, 2) + + pass_ = TemplateOptimizationV2( + template_list=[template], + user_cost_dict={"cx": 6, "p": 0, "cu": 8}, + ) + circuit_out = PassManager(pass_).run(circuit_in) + + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + self.assertTrue(Operator(circuit_in).equiv(circuit_out)) + + def test_output_symbolic_library_equal(self): + """Test that the template matcher returns parameter expressions that use the same symbolic + library as the default; it should not coerce everything to Sympy when playing with the + `ParameterExpression` internals.""" + + a, b = Parameter("a"), Parameter("b") + + template = QuantumCircuit(1) + template.p(a, 0) + template.p(-a, 0) + template.rz(a, 0) + template.rz(-a, 0) + + circuit = QuantumCircuit(1) + circuit.p(-b, 0) + circuit.p(b, 0) + + pass_ = TemplateOptimizationV2(template_list=[template], user_cost_dict={"p": 100, "rz": 1}) + out = pass_(circuit) + + expected = QuantumCircuit(1) + expected.rz(-b, 0) + expected.rz(b, 0) + self.assertEqual(out, expected) + + def symbolic_library(expr): + """Get the symbolic library of the expression - 'sympy' or 'symengine'.""" + return type(expr._symbol_expr).__module__.split(".")[0] + + out_exprs = [expr for instruction in out.data for expr in instruction.operation.params] + self.assertEqual( + [symbolic_library(b)] * len(out_exprs), [symbolic_library(expr) for expr in out_exprs] + ) + + # Assert that the result still works with parametric assignment. + self.assertEqual(out.assign_parameters({b: 1.5}), expected.assign_parameters({b: 1.5})) + + def test_optimizer_does_not_replace_unbound_partial_match(self): + """ + Test that partial matches with parameters will not raise errors. + This tests that if parameters are still in the temporary template after + _attempt_bind then they will not be used. + """ + + beta = Parameter("β") + template = QuantumCircuit(2) + template.cx(1, 0) + template.cx(1, 0) + template.p(beta, 1) + template.cu(0, 0, 0, -beta, 0, 1) + + circuit_in = QuantumCircuit(2) + circuit_in.cx(1, 0) + circuit_in.cx(1, 0) + pass_ = TemplateOptimizationV2( + template_list=[template], + user_cost_dict={"cx": 6, "p": 0, "cu": 8}, + ) + + circuit_out = PassManager(pass_).run(circuit_in) + + # The template optimisation should not have replaced anything, because + # that would require it to leave dummy parameters in place without + # binding them. + self.assertEqual(circuit_in, circuit_out) + + def test_unbound_parameters_in_rzx_template(self): + """ + Test that rzx template ('zz2') functions correctly for a simple + circuit with an unbound ParameterExpression. This uses the same + Parameter (theta) as the template, so this also checks that template + substitution handle this correctly. + """ + + theta = Parameter("ϴ") + circuit_in = QuantumCircuit(2) + circuit_in.cx(0, 1) + circuit_in.p(2 * theta, 1) + circuit_in.cx(0, 1) + + pass_ = TemplateOptimizationV2(**rzx_templates(["zz2"])) + circuit_out = PassManager(pass_).run(circuit_in) + + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + theta_set = 0.42 + self.assertTrue( + Operator(circuit_in.assign_parameters({theta: theta_set})).equiv( + circuit_out.assign_parameters({theta: theta_set}) + ) + ) + + def test_two_parameter_template(self): + """ + Test a two-Parameter template based on rzx_templates(["zz3"]), + + ┌───┐┌───────┐┌───┐┌────────────┐» + q_0: ──■─────────────■──┤ X ├┤ Rz(φ) ├┤ X ├┤ Rz(-1.0*φ) ├» + ┌─┴─┐┌───────┐┌─┴─┐└─┬─┘└───────┘└─┬─┘└────────────┘» + q_1: ┤ X ├┤ Rz(θ) ├┤ X ├──■─────────────■────────────────» + └───┘└───────┘└───┘ + « ┌─────────┐┌─────────┐┌─────────┐┌───────────┐┌──────────────┐» + «q_0: ┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├┤ Rx(1.0*φ) ├┤1 ├» + « └─────────┘└─────────┘└─────────┘└───────────┘│ Rzx(-1.0*φ) │» + «q_1: ──────────────────────────────────────────────┤0 ├» + « └──────────────┘» + « ┌─────────┐ ┌─────────┐┌─────────┐ » + «q_0: ─┤ Rz(π/2) ├──┤ Rx(π/2) ├┤ Rz(π/2) ├────────────────────────» + « ┌┴─────────┴─┐├─────────┤├─────────┤┌─────────┐┌───────────┐» + «q_1: ┤ Rz(-1.0*θ) ├┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├┤ Rx(1.0*θ) ├» + « └────────────┘└─────────┘└─────────┘└─────────┘└───────────┘» + « ┌──────────────┐ + «q_0: ┤0 ├───────────────────────────────── + « │ Rzx(-1.0*θ) │┌─────────┐┌─────────┐┌─────────┐ + «q_1: ┤1 ├┤ Rz(π/2) ├┤ Rx(π/2) ├┤ Rz(π/2) ├ + « └──────────────┘└─────────┘└─────────┘└─────────┘ + + correctly template matches into a unique circuit, but that it is + equivalent to the input circuit when the Parameters are bound to floats + and checked with Operator equivalence. + """ + theta = Parameter("θ") + phi = Parameter("φ") + + template = QuantumCircuit(2) + template.cx(0, 1) + template.rz(theta, 1) + template.cx(0, 1) + template.cx(1, 0) + template.rz(phi, 0) + template.cx(1, 0) + template.rz(-phi, 0) + template.rz(np.pi / 2, 0) + template.rx(np.pi / 2, 0) + template.rz(np.pi / 2, 0) + template.rx(phi, 0) + template.rzx(-phi, 1, 0) + template.rz(np.pi / 2, 0) + template.rz(-theta, 1) + template.rx(np.pi / 2, 0) + template.rz(np.pi / 2, 1) + template.rz(np.pi / 2, 0) + template.rx(np.pi / 2, 1) + template.rz(np.pi / 2, 1) + template.rx(theta, 1) + template.rzx(-theta, 0, 1) + template.rz(np.pi / 2, 1) + template.rx(np.pi / 2, 1) + template.rz(np.pi / 2, 1) + + alpha = Parameter("$\\alpha$") + beta = Parameter("$\\beta$") + + circuit_in = QuantumCircuit(2) + circuit_in.cx(0, 1) + circuit_in.rz(2 * alpha, 1) + circuit_in.cx(0, 1) + circuit_in.cx(1, 0) + circuit_in.rz(3 * beta, 0) + circuit_in.cx(1, 0) + + pass_ = TemplateOptimizationV2( + [template], + user_cost_dict={"cx": 6, "rz": 0, "rx": 1, "rzx": 0}, + ) + circuit_out = PassManager(pass_).run(circuit_in) + + # these are NOT equal if template optimization works + self.assertNotEqual(circuit_in, circuit_out) + + # however these are equivalent if the operators are the same + alpha_set = 0.37 + beta_set = 0.42 + self.assertTrue( + Operator(circuit_in.assign_parameters({alpha: alpha_set, beta: beta_set})).equiv( + circuit_out.assign_parameters({alpha: alpha_set, beta: beta_set}) + ) + ) + + def test_exact_substitution_numeric_parameter(self): + """Test that a template match produces the expected value for numeric parameters.""" + circuit_in = QuantumCircuit(1) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(1.45, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(1) + expected.rz(1.45, 0) + self.assertEqual(circuit_out, expected) + + def test_exact_substitution_symbolic_parameter(self): + """Test that a template match produces the expected value for numeric parameters.""" + a_circuit = Parameter("a") + circuit_in = QuantumCircuit(1) + circuit_in.h(0) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(a_circuit, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass(extra_costs={"h": 1}).run(circuit_in) + + expected = QuantumCircuit(1) + expected.h(0) + expected.rz(a_circuit, 0) + self.assertEqual(circuit_out, expected) + + def test_naming_clash(self): + """Test that the template matching works and correctly replaces a template if there is a + naming clash between it and the circuit. This should include binding a partial match with a + parameter.""" + # Two instances of parameters with the same name---this is how naming clashes might occur. + a_template = Parameter("a") + a_circuit = Parameter("a") + circuit_in = QuantumCircuit(1) + circuit_in.h(0) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(a_circuit, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass(a_template, extra_costs={"h": 1}).run(circuit_in) + + expected = QuantumCircuit(1) + expected.h(0) + expected.rz(a_circuit, 0) + self.assertEqual(circuit_out, expected) + # Ensure that the bound parameter in the output is referentially the same as the one we put + # in the input circuit.. + self.assertEqual(len(circuit_out.parameters), 1) + self.assertIs(circuit_in.parameters[0], a_circuit) + self.assertIs(circuit_out.parameters[0], a_circuit) + + def test_naming_clash_in_expression(self): + """Test that the template matching works and correctly replaces a template if there is a + naming clash between it and the circuit. This should include binding a partial match with a + parameter.""" + a_template = Parameter("a") + a_circuit = Parameter("a") + circuit_in = QuantumCircuit(1) + circuit_in.h(0) + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(2 * a_circuit, 0) + circuit_in.rx(np.pi / 2, 0) + circuit_out = _ry_to_rz_template_pass(a_template, extra_costs={"h": 1}).run(circuit_in) + + expected = QuantumCircuit(1) + expected.h(0) + expected.rz(2 * a_circuit, 0) + self.assertEqual(circuit_out, expected) + # Ensure that the bound parameter in the output is referentially the same as the one we put + # in the input circuit.. + self.assertEqual(len(circuit_out.parameters), 1) + self.assertIs(circuit_in.parameters[0], a_circuit) + self.assertIs(circuit_out.parameters[0], a_circuit) + + def test_template_match_with_uninvolved_parameter(self): + """Test that the template matching algorithm succeeds at matching a circuit that contains an + unbound parameter that is not involved in the subcircuit that matches.""" + b_circuit = Parameter("b") + circuit_in = QuantumCircuit(2) + circuit_in.rz(b_circuit, 0) + circuit_in.rx(-np.pi / 2, 1) + circuit_in.ry(1.45, 1) + circuit_in.rx(np.pi / 2, 1) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(2) + expected.rz(b_circuit, 0) + expected.rz(1.45, 1) + self.assertEqual(circuit_out, expected) + + def test_multiple_numeric_matches_same_template(self): + """Test that the template matching will change both instances of a partial match within a + longer circuit.""" + circuit_in = QuantumCircuit(2) + # Qubit 0 + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(1.32, 0) + circuit_in.rx(np.pi / 2, 0) + # Qubit 1 + circuit_in.rx(-np.pi / 2, 1) + circuit_in.ry(2.54, 1) + circuit_in.rx(np.pi / 2, 1) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(2) + expected.rz(1.32, 0) + expected.rz(2.54, 1) + self.assertEqual(circuit_out, expected) + + def test_multiple_symbolic_matches_same_template(self): + """Test that the template matching will change both instances of a partial match within a + longer circuit.""" + a, b = Parameter("a"), Parameter("b") + circuit_in = QuantumCircuit(2) + # Qubit 0 + circuit_in.rx(-np.pi / 2, 0) + circuit_in.ry(a, 0) + circuit_in.rx(np.pi / 2, 0) + # Qubit 1 + circuit_in.rx(-np.pi / 2, 1) + circuit_in.ry(b, 1) + circuit_in.rx(np.pi / 2, 1) + circuit_out = _ry_to_rz_template_pass().run(circuit_in) + + expected = QuantumCircuit(2) + expected.rz(a, 0) + expected.rz(b, 1) + self.assertEqual(circuit_out, expected) + + def test_template_match_multiparameter(self): + """Test that the template matching works on instructions that take more than one + parameter.""" + a = Parameter("a") + b = Parameter("b") + template = QuantumCircuit(1) + template.u(0, a, b, 0) + template.rz(-a - b, 0) + + circuit_in = QuantumCircuit(1) + circuit_in.u(0, 1.23, 2.45, 0) + pm = PassManager(TemplateOptimizationV2([template], user_cost_dict={"u": 16, "rz": 0})) + circuit_out = pm.run(circuit_in) + + expected = QuantumCircuit(1) + expected.rz(1.23 + 2.45, 0) + + self.assertEqual(circuit_out, expected) + + def test_naming_clash_multiparameter(self): + """Test that the naming clash prevention mechanism works with instructions that take + multiple parameters.""" + a_template = Parameter("a") + b_template = Parameter("b") + template = QuantumCircuit(1) + template.u(0, a_template, b_template, 0) + template.rz(-a_template - b_template, 0) + + a_circuit = Parameter("a") + b_circuit = Parameter("b") + circuit_in = QuantumCircuit(1) + circuit_in.u(0, a_circuit, b_circuit, 0) + pm = PassManager(TemplateOptimizationV2([template], user_cost_dict={"u": 16, "rz": 0})) + circuit_out = pm.run(circuit_in) + + expected = QuantumCircuit(1) + expected.rz(a_circuit + b_circuit, 0) + + self.assertEqual(circuit_out, expected) + + def test_consecutive_templates_apply(self): + """Test the scenario where one template optimization creates an opportunity for + another template optimization. + + This is the original circuit: + + ┌───┐ + q_0: ┤ X ├──■───X───────■─ + └─┬─┘┌─┴─┐ │ ┌───┐ │ + q_1: ──■──┤ X ├─X─┤ H ├─■─ + └───┘ └───┘ + + The clifford_4_1 template allows to replace the two CNOTs followed by the SWAP by a + single CNOT: + + q_0: ──■────────■─ + ┌─┴─┐┌───┐ │ + q_1: ┤ X ├┤ H ├─■─ + └───┘└───┘ + + At these point, the clifford_4_2 template allows to replace the circuit by a single + Hadamard gate: + + q_0: ───── + ┌───┐ + q_1: ┤ H ├ + └───┘ + + The second optimization would not have been possible without the applying the first + optimization. + """ + qc = QuantumCircuit(2) + qc.cx(1, 0) + qc.cx(0, 1) + qc.swap(0, 1) + qc.h(1) + qc.cz(0, 1) + + qc_expected = QuantumCircuit(2) + qc_expected.h(1) + + costs = {"h": 1, "cx": 2, "cz": 2, "swap": 3} + + # Check that consecutively applying both templates leads to the expected circuit. + qc_opt = TemplateOptimizationV2( + template_list=[clifford_4_1(), clifford_4_2()], user_cost_dict=costs + )(qc) + self.assertEqual(qc_opt, qc_expected) + + # Also check that applying the second template by itself does not do anything. + qc_non_opt = TemplateOptimizationV2(template_list=[clifford_4_2()], user_cost_dict=costs)( + qc + ) + self.assertEqual(qc, qc_non_opt) + + def test_consecutive_templates_do_not_apply(self): + """Test that applying one template optimization does not allow incorrectly + applying other templates (which could happen if the DagDependency graph is + not constructed correctly after the optimization). + """ + template_list = [ + clifford_2_2(), + clifford_2_3(), + ] + pm = PassManager(TemplateOptimizationV2(template_list=template_list)) + qc = QuantumCircuit(2) + qc.cx(0, 1) + qc.cx(0, 1) + qc.h(0) + qc.swap(0, 1) + qc.h(0) + qc_opt = pm.run(qc) + self.assertTrue(Operator(qc) == Operator(qc_opt)) + + def test_clifford_templates(self): + """Tests TemplateOptimizationV2 pass on several larger examples.""" + template_list = [ + clifford_2_1(), + clifford_2_2(), + clifford_2_3(), + clifford_2_4(), + clifford_3_1(), + ] + pm = PassManager(TemplateOptimizationV2(template_list=template_list)) + scc.clear_cached_commutations() + for seed in range(10): + qc = random_clifford_circuit( + num_qubits=5, + num_gates=100, + gates=["x", "y", "z", "h", "s", "sdg", "cx", "cz", "swap"], + seed=seed, + ) + qc_opt = pm.run(qc) + self.assertTrue(Operator(qc) == Operator(qc_opt)) + # All of these gates are in the commutation library, i.e. the cache should not be used + self.assertEqual(scc.num_cached_entries(), 0) + + +if __name__ == "__main__": + unittest.main() From 2b650b2688fa27079927e91051861f003fa2c195 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 25 Apr 2024 16:10:03 -0700 Subject: [PATCH 04/18] Finish phase 1 of backward match --- .../template_matching_v2/backward_match_v2.py | 63 +++++----- .../template_matching_v2/forward_match_v2.py | 112 +----------------- .../template_matching_v2.py | 4 +- 3 files changed, 39 insertions(+), 140 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index 5a361b490a24..dd08528f390d 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -169,9 +169,9 @@ def _gate_indices(self): current_dag = self.circuit_dag_dep - for node in current_dag.get_nodes(): - if (not node.matchedwith) and (not node.isblocked): - gate_indices.append(node.node_id) + for node in current_dag.op_nodes(): + if (not self.matchedwith[node]) and (not self.isblocked[node]): + gate_indices.append(node._node_id) gate_indices.reverse() return gate_indices @@ -192,7 +192,7 @@ def _find_backward_candidates(self, template_blocked, matches): matches_template = sorted(match[0] for match in matches) - successors = self.template_dag_dep.get_node(self.node_id_t).successors + successors = self.temp_match_class.get_successors(self.template_dag_dep, self.node_id_t) potential = [] for index in range(self.node_id_t + 1, self.template_dag_dep.size()): if (index not in successors) and (index not in template_block): @@ -261,20 +261,22 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): bool: True if possible, False otherwise. """ # If the gate is controlled, then the control qubits have to be compared as sets. + node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) if isinstance(node_circuit.op, ControlledGate): c_template = node_template.op.num_ctrl_qubits if c_template == 1: - return qarg_circuit == node_template.qindices + return qarg_circuit == node_temp_qind else: - control_qubits_template = node_template.qindices[:c_template] + node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) + control_qubits_template = node_temp_qind[:c_template] control_qubits_circuit = qarg_circuit[:c_template] if set(control_qubits_circuit) == set(control_qubits_template): - target_qubits_template = node_template.qindices[c_template::] + target_qubits_template = node_temp_qind[c_template::] target_qubits_circuit = qarg_circuit[c_template::] if node_template.op.base_gate.name in [ @@ -294,9 +296,9 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): # But for non-symmetric gates the qubits indices have to be compared as lists. else: if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: - return set(qarg_circuit) == set(node_template.qindices) + return set(qarg_circuit) == set(node_temp_qind) else: - return qarg_circuit == node_template.qindices + return qarg_circuit == node_temp_qind def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): """ @@ -309,12 +311,11 @@ def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): bool: True if possible, False otherwise. """ if ( - node_circuit.type == "op" - and getattr(node_circuit.op, "condition", None) + getattr(node_circuit.op, "condition", None) and node_template.type == "op" and getattr(node_template.op, "condition", None) ): - if set(carg_circuit) != set(node_template.cindices): + if set(carg_circuit) != set(self.temp_match_class.cindices(self.template_dag_dep, node_template)): return False if ( getattr(node_circuit.op, "condition", None)[1] @@ -336,16 +337,16 @@ def _init_matched_blocked_list(self): circuit_matched = [] circuit_blocked = [] - for node in self.circuit_dag_dep.get_nodes(): - circuit_matched.append(node.matchedwith) - circuit_blocked.append(node.isblocked) + for node in self.circuit_dag_dep.op_nodes(): + circuit_matched.append(self.matchedwith[node]) + circuit_blocked.append(self.isblocked[node]) template_matched = [] template_blocked = [] - for node in self.template_dag_dep.get_nodes(): - template_matched.append(node.matchedwith) - template_blocked.append(node.isblocked) + for node in self.template_dag_dep.op_nodes(): + template_matched.append(self.matchedwith[node]) + template_blocked.append(self.isblocked[node]) return circuit_matched, circuit_blocked, template_matched, template_blocked @@ -466,7 +467,7 @@ def run_backward_match(self): # First circuit candidate. circuit_id = gate_indices[counter_scenario - 1] - node_circuit = self.circuit_dag_dep.get_node(circuit_id) + node_circuit = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) # If the circuit candidate is blocked, only the counter is changed. if circuit_blocked[circuit_id]: @@ -485,8 +486,8 @@ def run_backward_match(self): candidates_indices = self._find_backward_candidates(template_blocked, matches_scenario) # Update of the qubits/clbits indices in the circuit in order to be # comparable with the one in the template. - qarg1 = node_circuit.qindices - carg1 = node_circuit.cindices + qarg1 = self.temp_match_class.qindices(self.circuit_dag_dep, node_circuit) + carg1 = self.temp_match_class.cindices(self.circuit_dag_dep, node_circuit) qarg1 = self._update_qarg_indices(qarg1) carg1 = self._update_carg_indices(carg1) @@ -497,8 +498,8 @@ def run_backward_match(self): # Loop over the template candidates. for template_id in candidates_indices: - node_template = self.template_dag_dep.get_node(template_id) - qarg2 = self.template_dag_dep.get_node(template_id).qindices + node_template = self.temp_match_class.get_node(self.template_dag_dep, template_id) + qarg2 = self.temp_match_class.qindices(self.template_dag_dep, self.temp_match_class.get_node(self.template_dag_dep, template_id)) # Necessary but not sufficient conditions for a match to happen. if ( @@ -530,12 +531,14 @@ def run_backward_match(self): # Loop to check if the match is not connected, in this case # the successors matches are blocked and unmatched. - for potential_block in self.template_dag_dep.successors(template_id): + template_descs = self.temp_match_class.get_descendants(self.template_dag_dep, template_id) + for potential_block in template_descs: if not template_matched_match[potential_block]: template_blocked_match[potential_block] = True block_list.append(potential_block) for block_id in block_list: - for succ_id in self.template_dag_dep.successors(block_id): + block_descs = self.temp_match_class.get_descendants(self.template_dag_dep, block_id) + for succ_id in block_descs: template_blocked_match[succ_id] = True if template_matched_match[succ_id]: new_id = template_matched_match[succ_id][0] @@ -596,7 +599,7 @@ def run_backward_match(self): # Second option, not a greedy match, block all successors (push the gate # to the right). - for succ in self.circuit_dag_dep.get_node(circuit_id).successors: + for succ in self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id): circuit_blocked_block_s[succ] = True if circuit_matched_block_s[succ]: broken_matches.append(succ) @@ -642,7 +645,7 @@ def run_backward_match(self): circuit_blocked_block_p[circuit_id] = True - for pred in self.circuit_dag_dep.get_node(circuit_id).predecessors: + for pred in self.temp_match_class.get_ancestors(circuit_dag_dep, circuit_id): circuit_blocked_block_p[pred] = True matching_scenario = MatchingScenarios( @@ -662,14 +665,14 @@ def run_backward_match(self): following_matches = [] - successors = self.circuit_dag_dep.get_node(circuit_id).successors + successors = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) for succ in successors: if circuit_matched[succ]: following_matches.append(succ) # First option, the circuit gate is not disturbing because there are no # following match and no predecessors. - predecessors = self.circuit_dag_dep.get_node(circuit_id).predecessors + predecessors = self.temp_match_class.get_ancestors(self.circuit_dag_dep, circuit_id) if not predecessors or not following_matches: @@ -713,7 +716,7 @@ def run_backward_match(self): broken_matches = [] - successors = self.circuit_dag_dep.get_node(circuit_id).successors + successors = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) for succ in successors: circuit_blocked_nomatch[succ] = True diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index 3655d23f3620..3d6f3f6a328e 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -316,7 +316,6 @@ def run_forward_match(self): while self.matched_nodes_list: first_matched = self.matched_nodes_list.pop(0) - print("\n\nFIRST MATCH", first_matched) if self.successorstovisit[first_matched] == []: continue @@ -400,11 +399,11 @@ def run_forward_match(self): match = True continue - # If no match is found, block the node and all the successors. + # If no match is found, block the node and all the descendants. if not match: self.isblocked[trial_successor] = True - if self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) not in self.descendants: - self.temp_match_class.descendants[trial_successor_id] = self.temp_match_class.get_descendants(self.circuit_dag_dep, trial_successor) + if self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) not in self.temp_match_class.descendants: + self.temp_match_class.descendants[trial_successor_id] = self.temp_match_class.get_descendants(self.circuit_dag_dep, trial_successor_id) for desc in self.temp_match_class.descendants[trial_successor_id]: self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, desc)] = True if self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)]: @@ -414,108 +413,5 @@ def run_forward_match(self): match_id = self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)][0] self.matchedwith[self.temp_match_class.get_node(template_dag_dep, match_id)] = [] self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)] = [] + return (self.matchedwith, self.isblocked) - - # # Return first element of the matched_nodes_list and removes it from the list - # v_first = self._get_node_forward(0) - - # # If there is no successors to visit go to the end - # if not self.successorstovisit[v_first]: - # continue - - # # Get the label and the node of the first successor to visit - # label = self._get_successors_to_visit(v_first, 0) - # v = [label, self.circuit_dag_dep.get_node(label)] - - # # Update of the SuccessorsToVisit attribute - # #v_first = self._update_successor(v_first, 0) - - # # Update the matched_nodes_list with new attribute successor to visit and sort the list. - # self.matched_nodes_list.append([v_first.node_id, v_first]) - # self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) - - # # If the node is blocked and already matched go to the end - # if v[1].isblocked | (v[1].matchedwith != []): - # continue - - # # Search for potential candidates in the template - # self._find_forward_candidates(v_first.matchedwith[0]) - - # qarg1 = self.circuit_dag_dep.get_node(label).qindices - # carg1 = self.circuit_dag_dep.get_node(label).cindices - - # # Update the indices for both qubits and clbits in order to be comparable with the - # # indices in the template circuit. - # self._update_qarg_indices(qarg1) - # self._update_carg_indices(carg1) - - # match = False - - # # For loop over the candidates (template) to find a match. - # for i in self.candidates: - - # # Break the for loop if a match is found. - # if match: - # break - - # # Compare the indices of qubits and the operation, - # # if True; a match is found - # node_circuit = self.circuit_dag_dep.get_node(label) - # node_template = self.template_dag_dep.get_node(i) - - # # Necessary but not sufficient conditions for a match to happen. - # if ( - # len(self.qarg_indices) != len(node_template.qindices) - # or set(self.qarg_indices) != set(node_template.qindices) - # or node_circuit.name != node_template.name - # ): - # continue - - # # Check if the qubit, clbit configuration are compatible for a match, - # # also check if the operation are the same. - # if ( - # self._is_same_q_conf(node_circuit, node_template) - # and self._is_same_c_conf(node_circuit, node_template) - # and self._is_same_op(node_circuit, node_template) - # ): - - # v[1].matchedwith = [i] - - # self.template_dag_dep.get_node(i).matchedwith = [label] - - # # Append the new match to the list of matches. - # self.match.append([i, label]) - - # # Potential successors to visit (circuit) for a given match. - # potential = self.circuit_dag_dep.direct_successors(label) - - # # If the potential successors to visit are blocked or match, it is removed. - # for potential_id in potential: - # if self.circuit_dag_dep.get_node(potential_id).isblocked | ( - # self.circuit_dag_dep.get_node(potential_id).matchedwith != [] - # ): - # potential.remove(potential_id) - - # sorted_potential = sorted(potential) - - # # Update the successor to visit attribute - # v[1].successorstovisit = sorted_potential - - # # Add the updated node to the stack. - # self.matched_nodes_list.append([v[0], v[1]]) - # self.matched_nodes_list.sort(key=lambda x: x[1].successorstovisit) - # match = True - # continue - - # # If no match is found, block the node and all the successors. - # if not match: - # v[1].isblocked = True - # for succ in v[1].successors: - # self.circuit_dag_dep.get_node(succ).isblocked = True - # if self.circuit_dag_dep.get_node(succ).matchedwith: - # self.match.remove( - # [self.circuit_dag_dep.get_node(succ).matchedwith[0], succ] - # ) - # match_id = self.circuit_dag_dep.get_node(succ).matchedwith[0] - # self.template_dag_dep.get_node(match_id).matchedwith = [] - # self.circuit_dag_dep.get_node(succ).matchedwith = [] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 90878ab62c47..20f12b33b7cb 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -248,10 +248,10 @@ def qindices(self, dag, node): def cindices(self, dag, node): return [dag.find_bit(carg).index for carg in node.cargs] - def get_descendants(self, dag, node): + def get_descendants(self, dag, node_id): return list(rx.descendants(dag._multi_graph, node_id)) - def get_ancestors(self, dag, node): + def get_ancestors(self, dag, node_id): return list(rx.ancestors(dag._multi_graph, node_id)) def get_successors(self, dag, node): From 1ec16af5bf60867c5ac414ccde6673a923db81b7 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 Apr 2024 09:03:17 -0700 Subject: [PATCH 05/18] Cleared all errors - next convert subst to v2 --- .../template_matching_v2/backward_match_v2.py | 37 +++++++---- .../template_matching_v2/forward_match_v2.py | 25 +++++--- .../template_matching_v2.py | 18 +++--- .../template_substitution_v2.py | 63 ++++++++++++------- .../optimization/template_optimization_v2.py | 1 + 5 files changed, 93 insertions(+), 51 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index dd08528f390d..a3b119d5c4d3 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -192,7 +192,10 @@ def _find_backward_candidates(self, template_blocked, matches): matches_template = sorted(match[0] for match in matches) - successors = self.temp_match_class.get_successors(self.template_dag_dep, self.node_id_t) + node = self.temp_match_class.get_node(self.template_dag_dep, self.node_id_t) + if node not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.template_dag_dep, self.node_id_t) + successors = self.temp_match_class.descendants[node] potential = [] for index in range(self.node_id_t + 1, self.template_dag_dep.size()): if (index not in successors) and (index not in template_block): @@ -531,14 +534,12 @@ def run_backward_match(self): # Loop to check if the match is not connected, in this case # the successors matches are blocked and unmatched. - template_descs = self.temp_match_class.get_descendants(self.template_dag_dep, template_id) - for potential_block in template_descs: + for potential_block in self.temp_match_class.get_descendants(self.template_dag_dep, template_id): if not template_matched_match[potential_block]: template_blocked_match[potential_block] = True block_list.append(potential_block) for block_id in block_list: - block_descs = self.temp_match_class.get_descendants(self.template_dag_dep, block_id) - for succ_id in block_descs: + for succ_id in self.temp_match_class.get_descendants(self.template_dag_dep, block_id): template_blocked_match[succ_id] = True if template_matched_match[succ_id]: new_id = template_matched_match[succ_id][0] @@ -599,7 +600,10 @@ def run_backward_match(self): # Second option, not a greedy match, block all successors (push the gate # to the right). - for succ in self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id): + node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + if node not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + for succ in self.temp_match_class.descendants[node]: circuit_blocked_block_s[succ] = True if circuit_matched_block_s[succ]: broken_matches.append(succ) @@ -645,7 +649,10 @@ def run_backward_match(self): circuit_blocked_block_p[circuit_id] = True - for pred in self.temp_match_class.get_ancestors(circuit_dag_dep, circuit_id): + node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + if node not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, circuit_id) + for pred in self.temp_match_class.ancestors[node]: circuit_blocked_block_p[pred] = True matching_scenario = MatchingScenarios( @@ -665,14 +672,19 @@ def run_backward_match(self): following_matches = [] - successors = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) - for succ in successors: + node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + if node not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + for succ in self.temp_match_class.descendants[node]: if circuit_matched[succ]: following_matches.append(succ) # First option, the circuit gate is not disturbing because there are no # following match and no predecessors. - predecessors = self.temp_match_class.get_ancestors(self.circuit_dag_dep, circuit_id) + node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + if node not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, circuit_id) + predecessors = self.temp_match_class.ancestors[node] if not predecessors or not following_matches: @@ -716,7 +728,10 @@ def run_backward_match(self): broken_matches = [] - successors = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + if node not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + successors = self.temp_match_class.descendants[node] for succ in successors: circuit_blocked_nomatch[succ] = True diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index 3d6f3f6a328e..0e190029ce49 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -153,7 +153,10 @@ def _find_forward_candidates(self, node_id_t): for node_id in pred: for dir_succ in self.temp_match_class.get_successors(self.template_dag_dep, node_id): if dir_succ not in matches: - succ = self.temp_match_class.get_descendants(self.template_dag_dep, dir_succ) + node = self.temp_match_class.get_node(self.template_dag_dep, dir_succ) + if node not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.template_dag_dep, dir_succ) + succ = self.temp_match_class.descendants[node] block = block + succ self.candidates = list( set(temp_succs) - set(matches) - set(block) @@ -270,7 +273,7 @@ def _is_same_q_conf(self, node_circuit, node_template): return False else: if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: - return set(self.qarg_indices) == set(node_template.qindices) + return set(self.qarg_indices) == set(self.temp_match_class.qindices(self.template_dag_dep, node_template)) else: return self.qarg_indices == node_temp_qind @@ -402,16 +405,18 @@ def run_forward_match(self): # If no match is found, block the node and all the descendants. if not match: self.isblocked[trial_successor] = True - if self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) not in self.temp_match_class.descendants: - self.temp_match_class.descendants[trial_successor_id] = self.temp_match_class.get_descendants(self.circuit_dag_dep, trial_successor_id) - for desc in self.temp_match_class.descendants[trial_successor_id]: + node = self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) + if node not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, trial_successor_id) + + for desc in self.temp_match_class.descendants[node]: self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, desc)] = True - if self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)]: + if self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)]: self.match.remove( - [self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)][0], desc] + [self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)][0], desc] ) - match_id = self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)][0] - self.matchedwith[self.temp_match_class.get_node(template_dag_dep, match_id)] = [] - self.matchedwith[self.temp_match_class.get_node(circuit_dag_dep, desc)] = [] + match_id = self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)][0] + self.matchedwith[self.temp_match_class.get_node(self.template_dag_dep, match_id)] = [] + self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)] = [] return (self.matchedwith, self.isblocked) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 20f12b33b7cb..bbeecf0719f9 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -203,14 +203,17 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): """ template_nodes = range(node_id_t + 1, self.template_dag_dep.size()) circuit_nodes = range(0, self.circuit_dag_dep.size()) - if self.template_dag_dep.get_node(node_id_t) not in self.descendants: - self.descendants[node_id_t] = self.get_descendants(self.template_dag_dep, self.template_dag_dep.get_node(node_id_t)) + node = self.get_node(self.template_dag_dep, node_id_t) + if node not in self.descendants: + self.descendants[node] = self.get_descendants(self.template_dag_dep, node) counter = 1 qubit_set = set(self.circuit_dag_dep.get_node(node_id_c).qindices) if 2 * len(self.descendants[node_id_t]) > len(template_nodes): - successors = self.get_descendants(self.circuit_dag_dep, self.circuit_dag_dep.get_node(node_id_c)) - for succ in successors: + node = self.get_node(self.circuit_dag_dep, node_id_c) + if node not in self.descendants: + self.descendants[node] = self.get_descendants(self.circuit_dag_dep, node) + for succ in self.descendants[node]: qarg = self.circuit_dag_dep.get_node(succ).qindices if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: qubit_set = qubit_set | set(qarg) @@ -220,10 +223,11 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): return list(qubit_set) else: - if self.get_node(self.circuit_dag_dep, node_id_c) not in self.descendants: - self.descendants[node_id_c] = self.get_descendants(self.circuit_dag_dep, self.get_node(circuit_dag_dep, node_id_c)) + node = self.get_node(self.circuit_dag_dep, node_id_c) + if node not in self.descendants: + self.descendants[node] = self.get_descendants(self.circuit_dag_dep, node) not_successors = list( - set(circuit_nodes) - set(self.descendants[node_id_c]) + set(circuit_nodes) - set(self.descendants[node]) ) candidate = [ not_successors[j] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 06c5186d284c..6c781d2e2fc3 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -20,7 +20,7 @@ from qiskit.circuit import Parameter, ParameterExpression from qiskit.dagcircuit.dagcircuit import DAGCircuit -from qiskit.dagcircuit.dagdependency import DAGDependency +from qiskit.dagcircuit.dagdependency_v2 import _DAGDependencyV2 from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag from qiskit.utils import optionals as _optionals @@ -50,7 +50,7 @@ def __init__( def has_parameters(self): """Ensure that the template does not have parameters.""" - for node in self.template_dag_dep.get_nodes(): + for node in self.template_dag_dep.op_nodes(): for param in node.op.params: if isinstance(param, ParameterExpression): return True @@ -63,7 +63,7 @@ class TemplateSubstitution: Class to run the substitution algorithm from the list of maximal matches. """ - def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dict=None): + def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, temp_match_class, user_cost_dict=None): """ Initialize TemplateSubstitution with necessary arguments. Args: @@ -78,10 +78,11 @@ def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dic self.match_stack = max_matches self.circuit_dag_dep = circuit_dag_dep self.template_dag_dep = template_dag_dep + self.temp_match_class = temp_match_class self.substitution_list = [] self.unmatched_list = [] - self.dag_dep_optimized = DAGDependency() + self.dag_dep_optimized = _DAGDependencyV2() self.dag_optimized = DAGCircuit() if user_cost_dict is not None: @@ -138,7 +139,10 @@ def _pred_block(self, circuit_sublist, index): """ predecessors = set() for node_id in circuit_sublist: - predecessors = predecessors | set(self.circuit_dag_dep.get_node(node_id).predecessors) + node = self.temp_match_class.get_node(self.circuit_dag_dep, node_id) + if node not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, node) + predecessors = predecessors | set(self.temp_match_class.ancestors[node]) exclude = set() for elem in self.substitution_list[:index]: @@ -160,11 +164,11 @@ def _quantum_cost(self, left, right): """ cost_left = 0 for i in left: - cost_left += self.cost_dict[self.template_dag_dep.get_node(i).name] + cost_left += self.cost_dict[self.temp_match_class.get_node(self.template_dag_dep, i).name] cost_right = 0 for j in right: - cost_right += self.cost_dict[self.template_dag_dep.get_node(j).name] + cost_right += self.cost_dict[self.temp_match_class.get_node(self.template_dag_dep, j).name] return cost_left > cost_right @@ -205,12 +209,18 @@ def _template_inverse(self, template_list, template_sublist, template_complement pred = set() for index in template_sublist: - pred = pred | set(self.template_dag_dep.get_node(index).predecessors) + node = self.temp_match_class.get_node(self.template_dag_dep, index) + if node not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.template_dag_dep, index) + pred = pred | set(self.temp_match_class.ancestors[node]) pred = list(pred - set(template_sublist)) succ = set() for index in template_sublist: - succ = succ | set(self.template_dag_dep.get_node(index).successors) + node = self.temp_match_class.get_node(self.template_dag_dep, index) + if node not in self.temp_match_class.descendants: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_descendants(self.template_dag_dep, index) + succ = succ | set(self.temp_match_class.descendants[node]) succ = list(succ - set(template_sublist)) comm = list(set(template_list) - set(pred) - set(succ)) @@ -249,7 +259,10 @@ def _permutation(self): for scenario in self.substitution_list: predecessors = set() for match in scenario.circuit_config: - predecessors = predecessors | set(self.circuit_dag_dep.get_node(match).predecessors) + node = self.temp_match_class.get_node(self.circuit_dag_dep, match) + if node not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, match) + predecessors = predecessors | set(self.temp_match_class.ancestors[node]) predecessors = predecessors - set(scenario.circuit_config) index = self.substitution_list.index(scenario) for scenario_b in self.substitution_list[index::]: @@ -275,7 +288,10 @@ def _remove_impossible(self): for scenario in self.substitution_list: predecessors = set() for index in scenario.circuit_config: - predecessors = predecessors | set(self.circuit_dag_dep.get_node(index).predecessors) + node = self.temp_match_class.get_node(self.circuit_dag_dep, index) + if node not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, index) + predecessors = predecessors | set(self.temp_match_class.ancestors[node]) list_predecessors.append(predecessors) # Check if two groups of matches are incompatible. @@ -369,7 +385,7 @@ def run_dag_opt(self): """ self._substitution() - dag_dep_opt = DAGDependency() + dag_dep_opt = _DAGDependencyV2() dag_dep_opt.name = self.circuit_dag_dep.name @@ -402,7 +418,7 @@ def run_dag_opt(self): # First add all the predecessors of the given match. for elem in pred: - node = self.circuit_dag_dep.get_node(elem) + node = self.temp_match_class.get_node(self.circuit_dag_dep, elem) inst = node.op.copy() dag_dep_opt.add_op_node(inst, node.qargs, node.cargs) already_sub.append(elem) @@ -412,25 +428,26 @@ def run_dag_opt(self): # Then add the inverse of the template. for index in template_inverse: all_qubits = self.circuit_dag_dep.qubits - qarg_t = group.template_dag_dep.get_node(index).qindices + node = self.temp_match_class.get_node(group.template_dag_dep, index) + qarg_t = self.temp_match_class.qindices(group.template_dag_dep, node) qarg_c = [qubit[x] for x in qarg_t] qargs = [all_qubits[x] for x in qarg_c] all_clbits = self.circuit_dag_dep.clbits - carg_t = group.template_dag_dep.get_node(index).cindices + carg_t = self.temp_match_class.cindices(group.template_dag_dep, node) if all_clbits and clbit: carg_c = [clbit[x] for x in carg_t] cargs = [all_clbits[x] for x in carg_c] else: cargs = [] - node = group.template_dag_dep.get_node(index) + node = self.temp_match_class.get_node(group.template_dag_dep, index) inst = node.op.copy() dag_dep_opt.add_op_node(inst.inverse(), qargs, cargs) # Add the unmatched gates. for node_id in self.unmatched_list: - node = self.circuit_dag_dep.get_node(node_id) + node = self.temp_match_class.get_node(self.circuit_dag_dep, node_id) inst = node.op.copy() dag_dep_opt.add_op_node(inst, node.qargs, node.cargs) @@ -515,7 +532,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): # add parameters from circuit to circuit_params for idx, _ in enumerate(template_sublist): qc_idx = circuit_sublist[idx] - parameters = self.circuit_dag_dep.get_node(qc_idx).op.params + parameters = self.temp_match_class.get_node(self.circuit_dag_dep, qc_idx).op.params circuit_params += parameters for parameter in parameters: if isinstance(parameter, ParameterExpression): @@ -535,7 +552,7 @@ def dummy_parameter(): # add parameters from template to template_params, replacing parameters with names that # clash with those in the circuit. for t_idx in template_sublist: - node = template_dag_dep.get_node(t_idx) + node = self.temp_match_class.get_node(self.template_dag_dep, t_idx) sub_node_params = [] for t_param_exp in node.op.params: if isinstance(t_param_exp, ParameterExpression): @@ -549,7 +566,7 @@ def dummy_parameter(): node.op = node.op.to_mutable() node.op.params = sub_node_params - for node in template_dag_dep.get_nodes(): + for node in template_dag_dep.op_nodes(): sub_node_params = [] for param_exp in node.op.params: if isinstance(param_exp, ParameterExpression): @@ -600,7 +617,7 @@ def dummy_parameter(): } fake_bind = {key: sol[key.name] for key in temp_symbols} - for node in template_dag_dep.get_nodes(): + for node in template_dag_dep.op_nodes(): bound_params = [] for param_exp in node.op.params: if isinstance(param_exp, ParameterExpression): @@ -624,13 +641,13 @@ def _incr_num_parameters(self, template): parameters in the circuit. """ template_params = set() - for param_list in (node.op.params for node in template.get_nodes()): + for param_list in (node.op.params for node in template.op_nodes()): for param_exp in param_list: if isinstance(param_exp, ParameterExpression): template_params.update(param_exp.parameters) circuit_params = set() - for param_list in (node.op.params for node in self.circuit_dag_dep.get_nodes()): + for param_list in (node.op.params for node in self.circuit_dag_dep.op_nodes()): for param_exp in param_list: if isinstance(param_exp, ParameterExpression): circuit_params.update(param_exp.parameters) diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index fc69a8280662..4209fe0c4b92 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -148,6 +148,7 @@ def run(self, dag): max_matches, template_m.circuit_dag_dep, template_m.template_dag_dep, + template_m, self.user_cost_dict, ) substitution.run_dag_opt() From 1c0ef7d463e1ffe3f1ba04a2c4025e5b15dbaabb Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 26 Apr 2024 09:23:10 -0700 Subject: [PATCH 06/18] Finish errors --- .../template_matching_v2.py | 20 +++++++++---------- .../template_substitution_v2.py | 12 +++++------ 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index bbeecf0719f9..1df79a7520fe 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -81,17 +81,17 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit # Controlled gate if isinstance(node_circuit.op, ControlledGate) and node_template.op.num_ctrl_qubits > 1: control = node_template.op.num_ctrl_qubits - control_qubits_circuit = node_circuit.qindices[:control] - not_control_qubits_circuit = node_circuit.qindices[control::] + control_qubits_circuit = self.qindices(self.circuit_dag_dep, node_circuit)[:control] + not_control_qubits_circuit = self.qindices(self.circuit_dag_dep, node_circuit)[control::] # Symmetric base gate if node_template.op.base_gate.name not in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: for control_perm_q in itertools.permutations(control_qubits_circuit): control_perm_q = list(control_perm_q) l_q_sub = [-1] * n_qubits_t - for q in node_template.qindices: + for q in self.qindices(self.template_dag_dep, node_template): node_circuit_perm = control_perm_q + not_control_qubits_circuit - l_q_sub[q] = node_circuit_perm[node_template.qindices.index(q)] + l_q_sub[q] = node_circuit_perm[self.qindices(self.template_dag_dep, node_template).index(q)] l_q.append(l_q_sub) # Not symmetric base gate else: @@ -100,9 +100,9 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit for not_control_perm_q in itertools.permutations(not_control_qubits_circuit): not_control_perm_q = list(not_control_perm_q) l_q_sub = [-1] * n_qubits_t - for q in node_template.qindices: + for q in self.qindices(self.template_dag_dep, node_template): node_circuit_perm = control_perm_q + not_control_perm_q - l_q_sub[q] = node_circuit_perm[node_template.qindices.index(q)] + l_q_sub[q] = node_circuit_perm[self.qindices(self.template_dag_dep, node_template).index(q)] l_q.append(l_q_sub) # Not controlled else: @@ -116,7 +116,7 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit else: for perm_q in itertools.permutations(self.qindices(self.circuit_dag_dep, node_circuit)): l_q_sub = [-1] * n_qubits_t - for q in node_template.qindices: + for q in self.qindices(self.template_dag_dep, node_template): l_q_sub[q] = perm_q[self.qindices(self.template_dag_dep, node_template).index(q)] l_q.append(l_q_sub) @@ -208,13 +208,13 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): self.descendants[node] = self.get_descendants(self.template_dag_dep, node) counter = 1 - qubit_set = set(self.circuit_dag_dep.get_node(node_id_c).qindices) + qubit_set = set(self.qindices(circuit_dag_dep, self.get_node(self.circuit_dag_dep, node_id_c))) if 2 * len(self.descendants[node_id_t]) > len(template_nodes): node = self.get_node(self.circuit_dag_dep, node_id_c) if node not in self.descendants: self.descendants[node] = self.get_descendants(self.circuit_dag_dep, node) for succ in self.descendants[node]: - qarg = self.circuit_dag_dep.get_node(succ).qindices + qarg = self.qindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, succ)) if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: qubit_set = qubit_set | set(qarg) counter += 1 @@ -235,7 +235,7 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): ] for not_succ in candidate: - qarg = self.circuit_dag_dep.get_node(not_succ).qindices + qarg = self.qindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, not_succ)) if counter <= length and (len(qubit_set | set(qarg))) <= n_qubits_t: qubit_set = qubit_set | set(qarg) counter += 1 diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 6c781d2e2fc3..07161c55ee17 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -219,7 +219,7 @@ def _template_inverse(self, template_list, template_sublist, template_complement for index in template_sublist: node = self.temp_match_class.get_node(self.template_dag_dep, index) if node not in self.temp_match_class.descendants: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_descendants(self.template_dag_dep, index) + self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.template_dag_dep, index) succ = succ | set(self.temp_match_class.descendants[node]) succ = list(succ - set(template_sublist)) @@ -420,7 +420,7 @@ def run_dag_opt(self): for elem in pred: node = self.temp_match_class.get_node(self.circuit_dag_dep, elem) inst = node.op.copy() - dag_dep_opt.add_op_node(inst, node.qargs, node.cargs) + dag_dep_opt.apply_operation_back(inst, node.qargs, node.cargs) already_sub.append(elem) already_sub = already_sub + circuit_sub @@ -443,16 +443,16 @@ def run_dag_opt(self): cargs = [] node = self.temp_match_class.get_node(group.template_dag_dep, index) inst = node.op.copy() - dag_dep_opt.add_op_node(inst.inverse(), qargs, cargs) + dag_dep_opt.apply_operation_back(inst.inverse(), qargs, cargs) # Add the unmatched gates. for node_id in self.unmatched_list: node = self.temp_match_class.get_node(self.circuit_dag_dep, node_id) inst = node.op.copy() - dag_dep_opt.add_op_node(inst, node.qargs, node.cargs) + dag_dep_opt.apply_operation_back(inst, node.qargs, node.cargs) - dag_dep_opt._add_predecessors() - dag_dep_opt._add_successors() + # dag_dep_opt._add_predecessors() + # dag_dep_opt._add_successors() # If there is no valid match, it returns the original dag. else: dag_dep_opt = self.circuit_dag_dep From b3e0e36774a3e372991b4e0596ffb3c9802b4e16 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 27 Apr 2024 14:00:53 -0700 Subject: [PATCH 07/18] Further cleanup and refactor --- qiskit/dagcircuit/dagdependency_v2.py | 87 +++------ .../template_matching_v2/__init__.py | 10 +- .../template_matching_v2/backward_match_v2.py | 108 +++++++---- .../template_matching_v2/forward_match_v2.py | 138 +++++++++----- .../template_matching_v2.py | 176 +++++++++--------- .../template_substitution_v2.py | 80 +++++--- .../optimization/template_optimization_v2.py | 6 +- 7 files changed, 357 insertions(+), 248 deletions(-) diff --git a/qiskit/dagcircuit/dagdependency_v2.py b/qiskit/dagcircuit/dagdependency_v2.py index cb5d447162cb..6206de323fbd 100644 --- a/qiskit/dagcircuit/dagdependency_v2.py +++ b/qiskit/dagcircuit/dagdependency_v2.py @@ -117,6 +117,8 @@ def __init__(self): self.duration = None self.unit = "dt" + self.leaves = set() + self.comm_checker = CommutationChecker() @property @@ -333,75 +335,44 @@ def find_bit(self, bit: Bit) -> BitLocations: ) from err def apply_operation_back(self, operation, qargs=(), cargs=()): - """Add a DAGOpNode to the graph and update the edges. - - Args: - operation (qiskit.circuit.Operation): operation as a quantum gate - qargs (list[~qiskit.circuit.Qubit]): list of qubits on which the operation acts - cargs (list[Clbit]): list of classical wires to attach to - """ - new_node = DAGOpNode( - op=operation, - qargs=qargs, - cargs=cargs, - dag=self, - ) - new_node._node_id = self._multi_graph.add_node(new_node) - self._update_edges() - self._increment_op(new_node.op) - - def _update_edges(self): """ - Updates DagDependencyV2 by adding edges to the newly added node (max_node) - from the previously added nodes. - For each previously added node (prev_node), an edge from prev_node to max_node - is added if max_node is "reachable" from prev_node (this means that the two + Updates DagDependencyV2 by adding edges to the newly added node (new_node) from + the previously added nodes. This update assumes the new_node was added to the end of + the graph. + For each previously added node (prev_node), an edge from prev_node to new_node + is added if new_node is not "unreachable" from prev_node (this means that the two nodes can be made adjacent by commuting them with other nodes), but the two nodes themselves do not commute. - - Currently. this function is only used when creating a new _DAGDependencyV2 from another - representation of a circuit, and hence there are no removed nodes (this is why - iterating over all nodes is fine). """ - max_node_id = len(self._multi_graph) - 1 - max_node = self._get_node(max_node_id) + order = rx.TopologicalSorter( + self._multi_graph, + check_cycle=False, + reverse=True, + initial=self.leaves, + check_args=False, + ) - reachable = [True] * max_node_id + new_node = DAGOpNode(op=operation, qargs=qargs, cargs=cargs, dag=self) + new_node._node_id = self._multi_graph.add_node(new_node) - # Analyze nodes in the reverse topological order. - # An improvement to the original algorithm is to consider only direct predecessors - # and to avoid constructing the lists of forward and backward reachable predecessors - # for every node when not required. - for prev_node_id in range(max_node_id - 1, -1, -1): - if reachable[prev_node_id]: - prev_node = self._get_node(prev_node_id) + self.leaves.add(new_node._node_id) + self._increment_op(new_node.op) - if not self.comm_checker.commute( + while available := order.get_ready(): + for prev_node_id in available: + prev_node = self._multi_graph[prev_node_id] + if self.comm_checker.commute( prev_node.op, prev_node.qargs, prev_node.cargs, - max_node.op, - max_node.qargs, - max_node.cargs, + new_node.op, + new_node.qargs, + new_node.cargs, ): - # If prev_node and max_node do not commute, then we add an edge - # between the two, and mark all direct predecessors of prev_node - # as not reaching max_node. - self._multi_graph.add_edge(prev_node_id, max_node_id, {"commute": False}) - - predecessor_ids = sorted( - [node._node_id for node in self.predecessors(self._get_node(prev_node_id))] - ) - for predecessor_id in predecessor_ids: - reachable[predecessor_id] = False - else: - # If prev_node cannot reach max_node, then none of its predecessors can - # reach max_node either. - predecessor_ids = sorted( - [node._node_id for node in self.predecessors(self._get_node(prev_node_id))] - ) - for predecessor_id in predecessor_ids: - reachable[predecessor_id] = False + order.done(prev_node_id) + else: + self.leaves.discard(prev_node_id) + self._multi_graph.add_edge(prev_node_id, new_node._node_id, None) def _get_node(self, node_id): """ diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py index c966fb362bf8..9a864ebf2e33 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py @@ -12,8 +12,8 @@ """Module containing template matching methods.""" -from .forward_match_v2 import ForwardMatch -from .backward_match_v2 import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList -from .template_matching_v2 import TemplateMatching -from .maximal_matches_v2 import MaximalMatches -from .template_substitution_v2 import SubstitutionConfig, TemplateSubstitution +# from .forward_match_v2 import ForwardMatch +# from .backward_match_v2 import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList +# from .template_matching_v2 import TemplateMatching +# from .maximal_matches_v2 import MaximalMatches +# from .template_substitution_v2 import SubstitutionConfig, TemplateSubstitution diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index a3b119d5c4d3..07808969de88 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -26,6 +26,8 @@ """ import heapq +import rustworkx as rx + from qiskit.circuit.controlledgate import ControlledGate @@ -120,8 +122,8 @@ def __init__( circuit_dag_dep, template_dag_dep, forward_matches, - node_id_c, - node_id_t, + node_c, + node_t, temp_match_class, matchedwith, isblocked, @@ -146,8 +148,8 @@ def __init__( self.template_dag_dep = template_dag_dep self.qubits = qubits self.clbits = clbits if clbits is not None else [] - self.node_id_c = node_id_c - self.node_id_t = node_id_t + self.node_c = node_c + self.node_t = node_t self.forward_matches = forward_matches self.match_final = [] self.heuristics_backward_param = ( @@ -186,18 +188,20 @@ def _find_backward_candidates(self, template_blocked, matches): """ template_block = [] - for node_id in range(self.node_id_t, self.template_dag_dep.size()): + for node_id in range(self.node_t._node_id, self.template_dag_dep.size()): if template_blocked[node_id]: template_block.append(node_id) matches_template = sorted(match[0] for match in matches) - node = self.temp_match_class.get_node(self.template_dag_dep, self.node_id_t) + node = get_node(self.template_dag_dep, self.node_t._node_id) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.template_dag_dep, self.node_id_t) + self.temp_match_class.descendants[node] = get_descendants( + self.template_dag_dep, self.node_t._node_id + ) successors = self.temp_match_class.descendants[node] potential = [] - for index in range(self.node_id_t + 1, self.template_dag_dep.size()): + for index in range(self.node_t._node_id + 1, self.template_dag_dep.size()): if (index not in successors) and (index not in template_block): potential.append(index) @@ -264,7 +268,7 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): bool: True if possible, False otherwise. """ # If the gate is controlled, then the control qubits have to be compared as sets. - node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) + node_temp_qind = get_qindices(self.template_dag_dep, node_template) if isinstance(node_circuit.op, ControlledGate): c_template = node_template.op.num_ctrl_qubits @@ -273,7 +277,7 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): return qarg_circuit == node_temp_qind else: - node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) + node_temp_qind = get_qindices(self.template_dag_dep, node_template) control_qubits_template = node_temp_qind[:c_template] control_qubits_circuit = qarg_circuit[:c_template] @@ -318,7 +322,7 @@ def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): and node_template.type == "op" and getattr(node_template.op, "condition", None) ): - if set(carg_circuit) != set(self.temp_match_class.cindices(self.template_dag_dep, node_template)): + if set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_template)): return False if ( getattr(node_circuit.op, "condition", None)[1] @@ -429,7 +433,7 @@ def run_backward_match(self): gate_indices = self._gate_indices() number_of_gate_to_match = ( - self.template_dag_dep.size() - (self.node_id_t - 1) - len(self.forward_matches) + self.template_dag_dep.size() - (self.node_t._node_id - 1) - len(self.forward_matches) ) # While the scenario stack is not empty. @@ -470,7 +474,7 @@ def run_backward_match(self): # First circuit candidate. circuit_id = gate_indices[counter_scenario - 1] - node_circuit = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + node_circuit = get_node(self.circuit_dag_dep, circuit_id) # If the circuit candidate is blocked, only the counter is changed. if circuit_blocked[circuit_id]: @@ -489,8 +493,8 @@ def run_backward_match(self): candidates_indices = self._find_backward_candidates(template_blocked, matches_scenario) # Update of the qubits/clbits indices in the circuit in order to be # comparable with the one in the template. - qarg1 = self.temp_match_class.qindices(self.circuit_dag_dep, node_circuit) - carg1 = self.temp_match_class.cindices(self.circuit_dag_dep, node_circuit) + qarg1 = get_qindices(self.circuit_dag_dep, node_circuit) + carg1 = get_cindices(self.circuit_dag_dep, node_circuit) qarg1 = self._update_qarg_indices(qarg1) carg1 = self._update_carg_indices(carg1) @@ -501,8 +505,10 @@ def run_backward_match(self): # Loop over the template candidates. for template_id in candidates_indices: - node_template = self.temp_match_class.get_node(self.template_dag_dep, template_id) - qarg2 = self.temp_match_class.qindices(self.template_dag_dep, self.temp_match_class.get_node(self.template_dag_dep, template_id)) + node_template = get_node(self.template_dag_dep, template_id) + qarg2 = get_qindices( + self.template_dag_dep, get_node(self.template_dag_dep, template_id) + ) # Necessary but not sufficient conditions for a match to happen. if ( @@ -534,12 +540,12 @@ def run_backward_match(self): # Loop to check if the match is not connected, in this case # the successors matches are blocked and unmatched. - for potential_block in self.temp_match_class.get_descendants(self.template_dag_dep, template_id): + for potential_block in get_descendants(self.template_dag_dep, template_id): if not template_matched_match[potential_block]: template_blocked_match[potential_block] = True block_list.append(potential_block) for block_id in block_list: - for succ_id in self.temp_match_class.get_descendants(self.template_dag_dep, block_id): + for succ_id in get_descendants(self.template_dag_dep, block_id): template_blocked_match[succ_id] = True if template_matched_match[succ_id]: new_id = template_matched_match[succ_id][0] @@ -566,7 +572,7 @@ def run_backward_match(self): break # First option greedy match. - if ([self.node_id_t, self.node_id_c] in new_matches_scenario_match) and ( + if ([self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_match) and ( condition or not match_backward ): template_matched_match[template_id] = [circuit_id] @@ -600,9 +606,11 @@ def run_backward_match(self): # Second option, not a greedy match, block all successors (push the gate # to the right). - node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + self.temp_match_class.descendants[node] = get_descendants( + self.circuit_dag_dep, circuit_id + ) for succ in self.temp_match_class.descendants[node]: circuit_blocked_block_s[succ] = True if circuit_matched_block_s[succ]: @@ -622,7 +630,7 @@ def run_backward_match(self): condition_not_greedy = False break - if ([self.node_id_t, self.node_id_c] in new_matches_scenario_block_s) and ( + if ([self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_block_s) and ( condition_not_greedy or not match_backward ): new_matching_scenario = MatchingScenarios( @@ -649,9 +657,11 @@ def run_backward_match(self): circuit_blocked_block_p[circuit_id] = True - node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, circuit_id) + self.temp_match_class.ancestors[node] = get_ancestors( + self.circuit_dag_dep, circuit_id + ) for pred in self.temp_match_class.ancestors[node]: circuit_blocked_block_p[pred] = True @@ -672,18 +682,22 @@ def run_backward_match(self): following_matches = [] - node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + self.temp_match_class.descendants[node] = get_descendants( + self.circuit_dag_dep, circuit_id + ) for succ in self.temp_match_class.descendants[node]: if circuit_matched[succ]: following_matches.append(succ) # First option, the circuit gate is not disturbing because there are no # following match and no predecessors. - node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, circuit_id) + self.temp_match_class.ancestors[node] = get_ancestors( + self.circuit_dag_dep, circuit_id + ) predecessors = self.temp_match_class.ancestors[node] if not predecessors or not following_matches: @@ -728,9 +742,11 @@ def run_backward_match(self): broken_matches = [] - node = self.temp_match_class.get_node(self.circuit_dag_dep, circuit_id) + node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, circuit_id) + self.temp_match_class.descendants[node] = get_descendants( + self.circuit_dag_dep, circuit_id + ) successors = self.temp_match_class.descendants[node] for succ in successors: @@ -750,7 +766,7 @@ def run_backward_match(self): condition_block = False break - if ([self.node_id_t, self.node_id_c] in matches_scenario_nomatch) and ( + if ([self.node_t._node_id, self.node_c._node_id] in matches_scenario_nomatch) and ( condition_block or not match_backward ): new_matching_scenario = MatchingScenarios( @@ -771,3 +787,31 @@ def run_backward_match(self): scenario.match == x.match for x in self.match_final ): self.match_final.append(scenario) + + +def get_node(dag, node_id): + return dag._multi_graph[node_id] + + +def get_qindices(dag, node): + return [dag.find_bit(qarg).index for qarg in node.qargs] + + +def get_cindices(dag, node): + return [dag.find_bit(carg).index for carg in node.cargs] + + +def get_descendants(dag, node_id): + return list(rx.descendants(dag._multi_graph, node_id)) + + +def get_ancestors(dag, node_id): + return list(rx.ancestors(dag._multi_graph, node_id)) + + +def get_successors(dag, node): + return [succ._node_id for succ in dag._multi_graph.successors(node)] + + +def get_predecessors(dag, node): + return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index 0e190029ce49..cef8a5f597af 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -24,6 +24,8 @@ """ +import rustworkx as rx + from qiskit.circuit.controlledgate import ControlledGate @@ -33,15 +35,22 @@ class ForwardMatch: """ def __init__( - self, circuit_dag_dep, template_dag_dep, node_id_c, node_id_t, temp_match_class, qubits, clbits=None, + self, + circuit_dag_dep, + template_dag_dep, + node_c, + node_t, + temp_match_class, + qubits, + clbits=None, ): """ Create a ForwardMatch class with necessary arguments. Args: circuit_dag_dep (DAGDependency): circuit in the dag dependency form. template_dag_dep (DAGDependency): template in the dag dependency form. - node_id_c (int): index of the first gate matched in the circuit. - node_id_t (int): index of the first gate matched in the template. + node_c (DAGOpNode): index of the first gate matched in the circuit. + node_t (DAGOpNode): index of the first gate matched in the template. qubits (list): list of considered qubits in the circuit. clbits (list): list of considered clbits in the circuit. """ @@ -59,10 +68,10 @@ def __init__( self.clbits = clbits if clbits is not None else [] # Id of the node in the circuit - self.node_id_c = node_id_c + self.node_c = node_c # Id of the node in the template - self.node_id_t = node_id_t + self.node_t = node_t # List of match self.match = [] @@ -90,9 +99,9 @@ def _init_successorstovisit(self): Initialize the attribute list 'SuccessorsToVisit' """ for i in range(0, self.circuit_dag_dep.size()): - if i == self.node_id_c: - self.successorstovisit[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = ( - self.temp_match_class.get_successors(self.circuit_dag_dep, i) + if i == self.node_c._node_id: + self.successorstovisit[get_node(self.circuit_dag_dep, i)] = get_successors( + self.circuit_dag_dep, i ) def _init_matchedwith(self): @@ -100,33 +109,33 @@ def _init_matchedwith(self): Initialize the attribute 'MatchedWith' in the template DAG dependency. """ for i in range(0, self.circuit_dag_dep.size()): - if i == self.node_id_c: - self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = [self.node_id_t] + if i == self.node_c._node_id: + self.matchedwith[get_node(self.circuit_dag_dep, i)] = [self.node_t._node_id] else: - self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = [] + self.matchedwith[get_node(self.circuit_dag_dep, i)] = [] for i in range(0, self.template_dag_dep.size()): - if i == self.node_id_t: - self.matchedwith[self.temp_match_class.get_node(self.template_dag_dep, i)] = [self.node_id_c] + if i == self.node_t._node_id: + self.matchedwith[get_node(self.template_dag_dep, i)] = [self.node_c._node_id] else: - self.matchedwith[self.temp_match_class.get_node(self.template_dag_dep, i)] = [] + self.matchedwith[get_node(self.template_dag_dep, i)] = [] def _init_isblocked(self): """ Initialize the attribute 'IsBlocked' in the circuit DAG dependency. """ for i in range(0, self.circuit_dag_dep.size()): - self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, i)] = False + self.isblocked[get_node(self.circuit_dag_dep, i)] = False for i in range(0, self.template_dag_dep.size()): - self.isblocked[self.temp_match_class.get_node(self.template_dag_dep, i)] = False + self.isblocked[get_node(self.template_dag_dep, i)] = False def _init_list_match(self): """ Initialize the list of matched nodes between the circuit and the template with the first match found. """ - self.match.append([self.node_id_t, self.node_id_c]) + self.match.append([self.node_t._node_id, self.node_c._node_id]) def _find_forward_candidates(self, node_id_t): """ @@ -144,29 +153,29 @@ def _find_forward_candidates(self, node_id_t): pred.sort() pred.remove(node_id_t) - temp_succs = self.temp_match_class.get_successors(self.template_dag_dep, node_id_t) + temp_succs = get_successors(self.template_dag_dep, node_id_t) if temp_succs: maximal_index = temp_succs[-1] pred = [elem for elem in pred if elem <= maximal_index] block = [] for node_id in pred: - for dir_succ in self.temp_match_class.get_successors(self.template_dag_dep, node_id): + for dir_succ in get_successors(self.template_dag_dep, node_id): if dir_succ not in matches: - node = self.temp_match_class.get_node(self.template_dag_dep, dir_succ) + node = get_node(self.template_dag_dep, dir_succ) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.template_dag_dep, dir_succ) + self.temp_match_class.descendants[node] = get_descendants( + self.template_dag_dep, dir_succ + ) succ = self.temp_match_class.descendants[node] block = block + succ - self.candidates = list( - set(temp_succs) - set(matches) - set(block) - ) + self.candidates = list(set(temp_succs) - set(matches) - set(block)) def _init_matched_nodes(self): """ Initialize the list of current matched nodes. """ - self.matched_nodes_list.append(self.temp_match_class.get_node(self.circuit_dag_dep, self.node_id_c)) + self.matched_nodes_list.append(get_node(self.circuit_dag_dep, self.node_c._node_id)) def _get_node_forward(self, list_id): """ @@ -241,7 +250,7 @@ def _is_same_q_conf(self, node_circuit, node_template): bool: True if possible, False otherwise. """ - node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) + node_temp_qind = get_qindices(self.template_dag_dep, node_template) if isinstance(node_circuit.op, ControlledGate): c_template = node_template.op.num_ctrl_qubits @@ -273,7 +282,9 @@ def _is_same_q_conf(self, node_circuit, node_template): return False else: if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: - return set(self.qarg_indices) == set(self.temp_match_class.qindices(self.template_dag_dep, node_template)) + return set(self.qarg_indices) == set( + get_qindices(self.template_dag_dep, node_template) + ) else: return self.qarg_indices == node_temp_qind @@ -324,7 +335,7 @@ def run_forward_match(self): # Get the id and the node of the first successor to visit trial_successor_id = self.successorstovisit[first_matched].pop(0) - trial_successor = self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) + trial_successor = get_node(self.circuit_dag_dep, trial_successor_id) # Update the matched_nodes_list with new attribute successor to visit and sort the list. self.matched_nodes_list.append(first_matched) @@ -337,8 +348,12 @@ def run_forward_match(self): # Search for potential candidates in the template self._find_forward_candidates(self.matchedwith[first_matched][0]) - qarg1 = self.temp_match_class.qindices(self.circuit_dag_dep, self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id)) - carg1 = self.temp_match_class.cindices(self.circuit_dag_dep, self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id)) + qarg1 = get_qindices( + self.circuit_dag_dep, get_node(self.circuit_dag_dep, trial_successor_id) + ) + carg1 = get_cindices( + self.circuit_dag_dep, get_node(self.circuit_dag_dep, trial_successor_id) + ) # Update the indices for both qubits and clbits in order to be comparable with the # indices in the template circuit. @@ -354,16 +369,15 @@ def run_forward_match(self): if match: break - node_circuit = self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) - node_template = self.temp_match_class.get_node(self.template_dag_dep, i) + node_circuit = get_node(self.circuit_dag_dep, trial_successor_id) + node_template = get_node(self.template_dag_dep, i) # Compare the indices of qubits and the operation name. # Necessary but not sufficient conditions for a match to happen. - node_temp_qind = self.temp_match_class.qindices(self.template_dag_dep, node_template) + node_temp_qind = get_qindices(self.template_dag_dep, node_template) if ( len(self.qarg_indices) != len(node_temp_qind) - or set(self.qarg_indices) - != set(node_temp_qind) + or set(self.qarg_indices) != set(node_temp_qind) or node_circuit.name != node_template.name ): continue @@ -382,12 +396,12 @@ def run_forward_match(self): self.match.append([i, trial_successor_id]) # Potential successors to visit (circuit) for a given match. - potential = self.temp_match_class.get_successors(self.circuit_dag_dep, trial_successor_id) + potential = get_successors(self.circuit_dag_dep, trial_successor_id) # If the potential successors to visit are blocked or matched, it is removed. for potential_id in potential: - if self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, potential_id)] | ( - self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, potential_id)] != [] + if self.isblocked[get_node(self.circuit_dag_dep, potential_id)] | ( + self.matchedwith[get_node(self.circuit_dag_dep, potential_id)] != [] ): potential.remove(potential_id) @@ -405,18 +419,48 @@ def run_forward_match(self): # If no match is found, block the node and all the descendants. if not match: self.isblocked[trial_successor] = True - node = self.temp_match_class.get_node(self.circuit_dag_dep, trial_successor_id) + node = get_node(self.circuit_dag_dep, trial_successor_id) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.circuit_dag_dep, trial_successor_id) + self.temp_match_class.descendants[node] = get_descendants( + self.circuit_dag_dep, trial_successor_id + ) for desc in self.temp_match_class.descendants[node]: - self.isblocked[self.temp_match_class.get_node(self.circuit_dag_dep, desc)] = True - if self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)]: + self.isblocked[get_node(self.circuit_dag_dep, desc)] = True + if self.matchedwith[get_node(self.circuit_dag_dep, desc)]: self.match.remove( - [self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)][0], desc] + [self.matchedwith[get_node(self.circuit_dag_dep, desc)][0], desc] ) - match_id = self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)][0] - self.matchedwith[self.temp_match_class.get_node(self.template_dag_dep, match_id)] = [] - self.matchedwith[self.temp_match_class.get_node(self.circuit_dag_dep, desc)] = [] - + match_id = self.matchedwith[get_node(self.circuit_dag_dep, desc)][0] + self.matchedwith[get_node(self.template_dag_dep, match_id)] = [] + self.matchedwith[get_node(self.circuit_dag_dep, desc)] = [] + return (self.matchedwith, self.isblocked) + + +def get_node(dag, node_id): + return dag._multi_graph[node_id] + + +def get_qindices(dag, node): + return [dag.find_bit(qarg).index for qarg in node.qargs] + + +def get_cindices(dag, node): + return [dag.find_bit(carg).index for carg in node.cargs] + + +def get_descendants(dag, node_id): + return list(rx.descendants(dag._multi_graph, node_id)) + + +def get_ancestors(dag, node_id): + return list(rx.ancestors(dag._multi_graph, node_id)) + + +def get_successors(dag, node): + return [succ._node_id for succ in dag._multi_graph.successors(node)] + + +def get_predecessors(dag, node): + return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 1df79a7520fe..9ec8c59acfec 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -29,7 +29,9 @@ from qiskit.circuit.controlledgate import ControlledGate from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch -from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import BackwardMatch +from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import ( + BackwardMatch, +) class TemplateMatching: @@ -81,17 +83,19 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit # Controlled gate if isinstance(node_circuit.op, ControlledGate) and node_template.op.num_ctrl_qubits > 1: control = node_template.op.num_ctrl_qubits - control_qubits_circuit = self.qindices(self.circuit_dag_dep, node_circuit)[:control] - not_control_qubits_circuit = self.qindices(self.circuit_dag_dep, node_circuit)[control::] + control_qubits_circuit = get_qindices(self.circuit_dag_dep, node_circuit)[:control] + not_control_qubits_circuit = get_qindices(self.circuit_dag_dep, node_circuit)[control::] # Symmetric base gate if node_template.op.base_gate.name not in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: for control_perm_q in itertools.permutations(control_qubits_circuit): control_perm_q = list(control_perm_q) l_q_sub = [-1] * n_qubits_t - for q in self.qindices(self.template_dag_dep, node_template): + for q in get_qindices(self.template_dag_dep, node_template): node_circuit_perm = control_perm_q + not_control_qubits_circuit - l_q_sub[q] = node_circuit_perm[self.qindices(self.template_dag_dep, node_template).index(q)] + l_q_sub[q] = node_circuit_perm[ + get_qindices(self.template_dag_dep, node_template).index(q) + ] l_q.append(l_q_sub) # Not symmetric base gate else: @@ -100,33 +104,43 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit for not_control_perm_q in itertools.permutations(not_control_qubits_circuit): not_control_perm_q = list(not_control_perm_q) l_q_sub = [-1] * n_qubits_t - for q in self.qindices(self.template_dag_dep, node_template): + for q in get_qindices(self.template_dag_dep, node_template): node_circuit_perm = control_perm_q + not_control_perm_q - l_q_sub[q] = node_circuit_perm[self.qindices(self.template_dag_dep, node_template).index(q)] + l_q_sub[q] = node_circuit_perm[ + get_qindices(self.template_dag_dep, node_template).index(q) + ] l_q.append(l_q_sub) # Not controlled else: # Symmetric gate if node_template.op.name not in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: l_q_sub = [-1] * n_qubits_t - for q in self.qindices(self.template_dag_dep, node_template): - l_q_sub[q] = self.qindices(self.circuit_dag_dep, node_circuit)[self.qindices(self.template_dag_dep, node_template).index(q)] + for q in get_qindices(self.template_dag_dep, node_template): + l_q_sub[q] = get_qindices(self.circuit_dag_dep, node_circuit)[ + get_qindices(self.template_dag_dep, node_template).index(q) + ] l_q.append(l_q_sub) # Not symmetric else: - for perm_q in itertools.permutations(self.qindices(self.circuit_dag_dep, node_circuit)): + for perm_q in itertools.permutations( + get_qindices(self.circuit_dag_dep, node_circuit) + ): l_q_sub = [-1] * n_qubits_t - for q in self.qindices(self.template_dag_dep, node_template): - l_q_sub[q] = perm_q[self.qindices(self.template_dag_dep, node_template).index(q)] + for q in get_qindices(self.template_dag_dep, node_template): + l_q_sub[q] = perm_q[ + get_qindices(self.template_dag_dep, node_template).index(q) + ] l_q.append(l_q_sub) # Classical control - if not self.cindices(self.template_dag_dep, node_template) or not self.cindices(self.circuit_dag_dep, node_circuit): + if not get_cindices(self.template_dag_dep, node_template) or not get_cindices( + self.circuit_dag_dep, node_circuit + ): l_c = [] else: l_c = [-1] * n_clbits_t - for c in self.cindices(self.template_dag_dep, node_template): - l_c[c] = node_circuit[self.cindices(self.template_dag_dep, node_template).index(c)] + for c in get_cindices(self.template_dag_dep, node_template): + l_c[c] = node_circuit[get_cindices(self.template_dag_dep, node_template).index(c)] return l_q, l_c @@ -190,31 +204,29 @@ def _add_match(self, backward_match_list): if not already_in: self.match_list.append(b_match) - def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): + def _explore_circuit(self, node_c, node_t, n_qubits_t, length): """ Explore the successors of the node_id_c (up to the given length). Args: - node_id_c (int): first match id in the circuit. - node_id_t (int): first match id in the template. + node_c (DAGOpNode): first match in the circuit. + node_t (DAGOpNode): first match in the template. n_qubits_t (int): number of qubits in the template. length (int): length for exploration of the successors. Returns: list: qubits configuration for the 'length' successors of node_id_c. """ - template_nodes = range(node_id_t + 1, self.template_dag_dep.size()) + template_nodes = range(node_t._node_id + 1, self.template_dag_dep.size()) circuit_nodes = range(0, self.circuit_dag_dep.size()) - node = self.get_node(self.template_dag_dep, node_id_t) - if node not in self.descendants: - self.descendants[node] = self.get_descendants(self.template_dag_dep, node) + if node_t not in self.descendants: + self.descendants[node_t] = get_descendants(self.template_dag_dep, node_t) + if node_c not in self.descendants: + self.descendants[node_c] = get_descendants(self.circuit_dag_dep, node_c) counter = 1 - qubit_set = set(self.qindices(circuit_dag_dep, self.get_node(self.circuit_dag_dep, node_id_c))) - if 2 * len(self.descendants[node_id_t]) > len(template_nodes): - node = self.get_node(self.circuit_dag_dep, node_id_c) - if node not in self.descendants: - self.descendants[node] = self.get_descendants(self.circuit_dag_dep, node) - for succ in self.descendants[node]: - qarg = self.qindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, succ)) + qubit_set = set(get_qindices(self.circuit_dag_dep, node_c)) + if 2 * len(self.descendants[node_t]) > len(template_nodes): + for succ in self.descendants[node_c]: + qarg = get_qindices(get_node(self.circuit_dag_dep, succ)) if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: qubit_set = qubit_set | set(qarg) counter += 1 @@ -223,19 +235,14 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): return list(qubit_set) else: - node = self.get_node(self.circuit_dag_dep, node_id_c) - if node not in self.descendants: - self.descendants[node] = self.get_descendants(self.circuit_dag_dep, node) - not_successors = list( - set(circuit_nodes) - set(self.descendants[node]) - ) + not_successors = list(set(circuit_nodes) - set(self.descendants[node_c])) candidate = [ not_successors[j] for j in range(len(not_successors) - 1, len(not_successors) - 1 - length, -1) ] for not_succ in candidate: - qarg = self.qindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, not_succ)) + qarg = get_qindices(get_node(self.circuit_dag_dep, not_succ)) if counter <= length and (len(qubit_set | set(qarg))) <= n_qubits_t: qubit_set = qubit_set | set(qarg) counter += 1 @@ -243,27 +250,6 @@ def _explore_circuit(self, node_id_c, node_id_t, n_qubits_t, length): return list(qubit_set) return list(qubit_set) - def get_node(self, dag, node_id): - return dag._multi_graph[node_id] - - def qindices(self, dag, node): - return [dag.find_bit(qarg).index for qarg in node.qargs] - - def cindices(self, dag, node): - return [dag.find_bit(carg).index for carg in node.cargs] - - def get_descendants(self, dag, node_id): - return list(rx.descendants(dag._multi_graph, node_id)) - - def get_ancestors(self, dag, node_id): - return list(rx.ancestors(dag._multi_graph, node_id)) - - def get_successors(self, dag, node): - return [succ._node_id for succ in dag._multi_graph.successors(node)] - - def get_predecessors(self, dag, node): - return [pred._node_id for pred in dag._multi_graph.predecessors(node)] - def run_template_matching(self): """ Run the complete algorithm for finding all maximal matches for the given template and @@ -282,32 +268,26 @@ def run_template_matching(self): n_clbits_t = len(self.template_dag_dep.clbits) # Loop over the indices of both template and circuit. - for template_index in range(0, self.template_dag_dep.size()): - for circuit_index in range(0, self.circuit_dag_dep.size()): - # Operations match up to ParameterExpressions. - #if self.circuit_dag_dep.get_node(circuit_index).op.soft_compare( - if self.get_node(self.circuit_dag_dep, circuit_index).op.soft_compare( - self.get_node(self.template_dag_dep, template_index).op - ): - - qarg_c = self.qindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, circuit_index)) - carg_c = self.cindices(self.circuit_dag_dep, self.get_node(self.circuit_dag_dep, circuit_index)) + for node_id_t in range(0, self.template_dag_dep.size()): + for node_id_c in range(0, self.circuit_dag_dep.size()): + node_t = get_node(self.template_dag_dep, node_id_t) + node_c = get_node(self.circuit_dag_dep, node_id_c) - qarg_t = self.qindices(self.template_dag_dep, self.get_node(self.template_dag_dep, template_index)) - carg_t = self.cindices(self.template_dag_dep, self.get_node(self.template_dag_dep, template_index)) + # Operations match up to ParameterExpressions. + if node_c.op.soft_compare(node_t.op): + qarg_c = get_qindices(self.circuit_dag_dep, node_c) + carg_c = get_cindices(self.circuit_dag_dep, node_c) - node_id_c = circuit_index - node_id_t = template_index + qarg_t = get_qindices(self.template_dag_dep, node_t) + carg_t = get_cindices(self.template_dag_dep, node_t) # Fix the qubits and clbits configuration given the first match. - all_list_first_match_q, list_first_match_c = self._list_first_match_new( - self.get_node(self.circuit_dag_dep, circuit_index), - self.get_node(self.template_dag_dep, template_index), + node_c, + node_t, n_qubits_t, n_clbits_t, ) - list_circuit_q = list(range(0, n_qubits_c)) list_circuit_c = list(range(0, n_clbits_c)) @@ -316,7 +296,7 @@ def run_template_matching(self): if self.heuristics_qubits_param: heuristics_qubits = self._explore_circuit( - node_id_c, node_id_t, n_qubits_t, self.heuristics_qubits_param[0] + node_c, node_t, n_qubits_t, self.heuristics_qubits_param[0] ) else: heuristics_qubits = [] @@ -349,8 +329,8 @@ def run_template_matching(self): forward = ForwardMatch( self.circuit_dag_dep, self.template_dag_dep, - node_id_c, - node_id_t, + node_c, + node_t, self, list_qubit_circuit, list_clbit_circuit, @@ -362,8 +342,8 @@ def run_template_matching(self): forward.circuit_dag_dep, forward.template_dag_dep, forward.match, - node_id_c, - node_id_t, + node_c, + node_t, self, list_qubit_circuit, list_clbit_circuit, @@ -379,8 +359,8 @@ def run_template_matching(self): forward = ForwardMatch( self.circuit_dag_dep, self.template_dag_dep, - node_id_c, - node_id_t, + node_c, + node_t, self, list_qubit_circuit, ) @@ -391,8 +371,8 @@ def run_template_matching(self): forward.circuit_dag_dep, forward.template_dag_dep, forward.match, - node_id_c, - node_id_t, + node_c, + node_t, self, matchedwith, isblocked, @@ -407,3 +387,31 @@ def run_template_matching(self): # Sort the list of matches according to the length of the matches (decreasing order). self.match_list.sort(key=lambda x: len(x.match), reverse=True) + + +def get_node(dag, node_id): + return dag._multi_graph[node_id] + + +def get_qindices(dag, node): + return [dag.find_bit(qarg).index for qarg in node.qargs] + + +def get_cindices(dag, node): + return [dag.find_bit(carg).index for carg in node.cargs] + + +def get_descendants(dag, node_id): + return list(rx.descendants(dag._multi_graph, node_id)) + + +def get_ancestors(dag, node_id): + return list(rx.ancestors(dag._multi_graph, node_id)) + + +def get_successors(dag, node): + return [succ._node_id for succ in dag._multi_graph.successors(node)] + + +def get_predecessors(dag, node): + return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 07161c55ee17..69455c3222ac 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -18,6 +18,8 @@ import copy import itertools +import rustworkx as rx + from qiskit.circuit import Parameter, ParameterExpression from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.dagcircuit.dagdependency_v2 import _DAGDependencyV2 @@ -63,7 +65,9 @@ class TemplateSubstitution: Class to run the substitution algorithm from the list of maximal matches. """ - def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, temp_match_class, user_cost_dict=None): + def __init__( + self, max_matches, circuit_dag_dep, template_dag_dep, temp_match_class, user_cost_dict=None + ): """ Initialize TemplateSubstitution with necessary arguments. Args: @@ -139,9 +143,9 @@ def _pred_block(self, circuit_sublist, index): """ predecessors = set() for node_id in circuit_sublist: - node = self.temp_match_class.get_node(self.circuit_dag_dep, node_id) + node = get_node(self.circuit_dag_dep, node_id) if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, node) + self.temp_match_class.ancestors[node] = get_ancestors(self.circuit_dag_dep, node) predecessors = predecessors | set(self.temp_match_class.ancestors[node]) exclude = set() @@ -164,11 +168,11 @@ def _quantum_cost(self, left, right): """ cost_left = 0 for i in left: - cost_left += self.cost_dict[self.temp_match_class.get_node(self.template_dag_dep, i).name] + cost_left += self.cost_dict[get_node(self.template_dag_dep, i).name] cost_right = 0 for j in right: - cost_right += self.cost_dict[self.temp_match_class.get_node(self.template_dag_dep, j).name] + cost_right += self.cost_dict[get_node(self.template_dag_dep, j).name] return cost_left > cost_right @@ -209,17 +213,19 @@ def _template_inverse(self, template_list, template_sublist, template_complement pred = set() for index in template_sublist: - node = self.temp_match_class.get_node(self.template_dag_dep, index) + node = get_node(self.template_dag_dep, index) if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.template_dag_dep, index) + self.temp_match_class.ancestors[node] = get_ancestors(self.template_dag_dep, index) pred = pred | set(self.temp_match_class.ancestors[node]) pred = list(pred - set(template_sublist)) succ = set() for index in template_sublist: - node = self.temp_match_class.get_node(self.template_dag_dep, index) + node = get_node(self.template_dag_dep, index) if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = self.temp_match_class.get_descendants(self.template_dag_dep, index) + self.temp_match_class.descendants[node] = get_descendants( + self.template_dag_dep, index + ) succ = succ | set(self.temp_match_class.descendants[node]) succ = list(succ - set(template_sublist)) @@ -259,9 +265,11 @@ def _permutation(self): for scenario in self.substitution_list: predecessors = set() for match in scenario.circuit_config: - node = self.temp_match_class.get_node(self.circuit_dag_dep, match) + node = get_node(self.circuit_dag_dep, match) if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, match) + self.temp_match_class.ancestors[node] = get_ancestors( + self.circuit_dag_dep, match + ) predecessors = predecessors | set(self.temp_match_class.ancestors[node]) predecessors = predecessors - set(scenario.circuit_config) index = self.substitution_list.index(scenario) @@ -288,9 +296,11 @@ def _remove_impossible(self): for scenario in self.substitution_list: predecessors = set() for index in scenario.circuit_config: - node = self.temp_match_class.get_node(self.circuit_dag_dep, index) + node = get_node(self.circuit_dag_dep, index) if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = self.temp_match_class.get_ancestors(self.circuit_dag_dep, index) + self.temp_match_class.ancestors[node] = get_ancestors( + self.circuit_dag_dep, index + ) predecessors = predecessors | set(self.temp_match_class.ancestors[node]) list_predecessors.append(predecessors) @@ -418,7 +428,7 @@ def run_dag_opt(self): # First add all the predecessors of the given match. for elem in pred: - node = self.temp_match_class.get_node(self.circuit_dag_dep, elem) + node = get_node(self.circuit_dag_dep, elem) inst = node.op.copy() dag_dep_opt.apply_operation_back(inst, node.qargs, node.cargs) already_sub.append(elem) @@ -428,26 +438,26 @@ def run_dag_opt(self): # Then add the inverse of the template. for index in template_inverse: all_qubits = self.circuit_dag_dep.qubits - node = self.temp_match_class.get_node(group.template_dag_dep, index) - qarg_t = self.temp_match_class.qindices(group.template_dag_dep, node) + node = get_node(group.template_dag_dep, index) + qarg_t = get_qindices(group.template_dag_dep, node) qarg_c = [qubit[x] for x in qarg_t] qargs = [all_qubits[x] for x in qarg_c] all_clbits = self.circuit_dag_dep.clbits - carg_t = self.temp_match_class.cindices(group.template_dag_dep, node) + carg_t = get_cindices(group.template_dag_dep, node) if all_clbits and clbit: carg_c = [clbit[x] for x in carg_t] cargs = [all_clbits[x] for x in carg_c] else: cargs = [] - node = self.temp_match_class.get_node(group.template_dag_dep, index) + node = get_node(group.template_dag_dep, index) inst = node.op.copy() dag_dep_opt.apply_operation_back(inst.inverse(), qargs, cargs) # Add the unmatched gates. for node_id in self.unmatched_list: - node = self.temp_match_class.get_node(self.circuit_dag_dep, node_id) + node = get_node(self.circuit_dag_dep, node_id) inst = node.op.copy() dag_dep_opt.apply_operation_back(inst, node.qargs, node.cargs) @@ -532,7 +542,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): # add parameters from circuit to circuit_params for idx, _ in enumerate(template_sublist): qc_idx = circuit_sublist[idx] - parameters = self.temp_match_class.get_node(self.circuit_dag_dep, qc_idx).op.params + parameters = get_node(self.circuit_dag_dep, qc_idx).op.params circuit_params += parameters for parameter in parameters: if isinstance(parameter, ParameterExpression): @@ -552,7 +562,7 @@ def dummy_parameter(): # add parameters from template to template_params, replacing parameters with names that # clash with those in the circuit. for t_idx in template_sublist: - node = self.temp_match_class.get_node(self.template_dag_dep, t_idx) + node = get_node(self.template_dag_dep, t_idx) sub_node_params = [] for t_param_exp in node.op.params: if isinstance(t_param_exp, ParameterExpression): @@ -653,3 +663,31 @@ def _incr_num_parameters(self, template): circuit_params.update(param_exp.parameters) return len(template_params) > len(circuit_params) + + +def get_node(dag, node_id): + return dag._multi_graph[node_id] + + +def get_qindices(dag, node): + return [dag.find_bit(qarg).index for qarg in node.qargs] + + +def get_cindices(dag, node): + return [dag.find_bit(carg).index for carg in node.cargs] + + +def get_descendants(dag, node_id): + return list(rx.descendants(dag._multi_graph, node_id)) + + +def get_ancestors(dag, node_id): + return list(rx.ancestors(dag._multi_graph, node_id)) + + +def get_successors(dag, node): + return [succ._node_id for succ in dag._multi_graph.successors(node)] + + +def get_predecessors(dag, node): + return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index 4209fe0c4b92..28450e1bd024 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -33,9 +33,13 @@ from qiskit.circuit.library.templates import template_nct_2a_1, template_nct_2a_2, template_nct_2a_3 from qiskit.quantum_info.operators.operator import Operator from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes.optimization.template_matching_v2 import ( +from qiskit.transpiler.passes.optimization.template_matching_v2.template_matching_v2 import ( TemplateMatching, +) +from qiskit.transpiler.passes.optimization.template_matching_v2.template_substitution_v2 import ( TemplateSubstitution, +) +from qiskit.transpiler.passes.optimization.template_matching_v2.maximal_matches_v2 import ( MaximalMatches, ) From baa192283e62e74e5616f2b64c581445bcba5d44 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Tue, 30 Apr 2024 16:32:13 -0700 Subject: [PATCH 08/18] Cleanup id vs objects and docs through forward, and add utils --- .../template_matching_v2/backward_match_v2.py | 69 +++---- .../template_matching_v2/forward_match_v2.py | 175 ++++++------------ .../template_matching_v2.py | 98 ++++------ .../template_substitution_v2.py | 39 +--- .../template_matching_v2/template_utils_v2.py | 55 ++++++ .../optimization/template_optimization_v2.py | 15 +- 6 files changed, 193 insertions(+), 258 deletions(-) create mode 100644 qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index 07808969de88..dcc0f92f08be 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -26,8 +26,15 @@ """ import heapq -import rustworkx as rx - +from qiskit.transpiler.passes.optimization.template_matching_v2.template_utils_v2 import ( + get_node, + get_qindices, + get_cindices, + get_descendants, + get_ancestors, + get_successors, + get_predecessors, +) from qiskit.circuit.controlledgate import ControlledGate @@ -66,7 +73,7 @@ def __init__( Args: circuit_matched (list): list of matchedwith attributes in the circuit. circuit_blocked (list): list of isblocked attributes in the circuit. - template_matched (list): list of matchedwith attributes in the template. + template_matched (list): list of self.matchedwith.get(node)self.matchedwith.get(node) attributes in the template. template_blocked (list): list of isblocked attributes in the template. matches (list): list of matches. counter (int): counter of the number of circuit gates already considered. @@ -172,7 +179,7 @@ def _gate_indices(self): current_dag = self.circuit_dag_dep for node in current_dag.op_nodes(): - if (not self.matchedwith[node]) and (not self.isblocked[node]): + if (not self.matchedwith.get(node)) and (not self.isblocked.get(node)): gate_indices.append(node._node_id) gate_indices.reverse() return gate_indices @@ -345,15 +352,15 @@ def _init_matched_blocked_list(self): circuit_blocked = [] for node in self.circuit_dag_dep.op_nodes(): - circuit_matched.append(self.matchedwith[node]) - circuit_blocked.append(self.isblocked[node]) + circuit_matched.append(self.matchedwith.get(node)) + circuit_blocked.append(self.isblocked.get(node)) template_matched = [] template_blocked = [] for node in self.template_dag_dep.op_nodes(): - template_matched.append(self.matchedwith[node]) - template_blocked.append(self.isblocked[node]) + template_matched.append(self.matchedwith.get(node)) + template_blocked.append(self.isblocked.get(node)) return circuit_matched, circuit_blocked, template_matched, template_blocked @@ -572,9 +579,9 @@ def run_backward_match(self): break # First option greedy match. - if ([self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_match) and ( - condition or not match_backward - ): + if ( + [self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_match + ) and (condition or not match_backward): template_matched_match[template_id] = [circuit_id] circuit_matched_match[circuit_id] = [template_id] new_matches_scenario_match.append([template_id, circuit_id]) @@ -630,9 +637,9 @@ def run_backward_match(self): condition_not_greedy = False break - if ([self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_block_s) and ( - condition_not_greedy or not match_backward - ): + if ( + [self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_block_s + ) and (condition_not_greedy or not match_backward): new_matching_scenario = MatchingScenarios( circuit_matched_block_s, circuit_blocked_block_s, @@ -766,9 +773,9 @@ def run_backward_match(self): condition_block = False break - if ([self.node_t._node_id, self.node_c._node_id] in matches_scenario_nomatch) and ( - condition_block or not match_backward - ): + if ( + [self.node_t._node_id, self.node_c._node_id] in matches_scenario_nomatch + ) and (condition_block or not match_backward): new_matching_scenario = MatchingScenarios( circuit_matched_nomatch, circuit_blocked_nomatch, @@ -787,31 +794,3 @@ def run_backward_match(self): scenario.match == x.match for x in self.match_final ): self.match_final.append(scenario) - - -def get_node(dag, node_id): - return dag._multi_graph[node_id] - - -def get_qindices(dag, node): - return [dag.find_bit(qarg).index for qarg in node.qargs] - - -def get_cindices(dag, node): - return [dag.find_bit(carg).index for carg in node.cargs] - - -def get_descendants(dag, node_id): - return list(rx.descendants(dag._multi_graph, node_id)) - - -def get_ancestors(dag, node_id): - return list(rx.ancestors(dag._multi_graph, node_id)) - - -def get_successors(dag, node): - return [succ._node_id for succ in dag._multi_graph.successors(node)] - - -def get_predecessors(dag, node): - return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index cef8a5f597af..accb882ae6bd 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020-2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -12,8 +12,8 @@ """ Template matching in the forward direction, it takes an initial -match, a configuration of qubit and both circuit and template as inputs. The -result is a list of match between the template and the circuit. +match, a configuration of qubits and both circuit and template as inputs. The +result is a list of matches between the template and the circuit. **Reference:** @@ -24,8 +24,13 @@ """ -import rustworkx as rx - +from qiskit.transpiler.passes.optimization.template_matching_v2.template_utils_v2 import ( + get_node, + get_qindices, + get_cindices, + get_descendants, + get_successors, +) from qiskit.circuit.controlledgate import ControlledGate @@ -47,10 +52,10 @@ def __init__( """ Create a ForwardMatch class with necessary arguments. Args: - circuit_dag_dep (DAGDependency): circuit in the dag dependency form. - template_dag_dep (DAGDependency): template in the dag dependency form. - node_c (DAGOpNode): index of the first gate matched in the circuit. - node_t (DAGOpNode): index of the first gate matched in the template. + circuit_dag_dep (DAGDependencyV2): circuit in the dag dependency form. + template_dag_dep (DAGDependencyV2): template in the dag dependency form. + node_c (DAGOpNode): node of first gate matched in the circuit. + node_t (DAGOpNode): node of first gate matched in the template. qubits (list): list of considered qubits in the circuit. clbits (list): list of considered clbits in the circuit. """ @@ -94,42 +99,6 @@ def __init__( self.matchedwith = {} self.isblocked = {} - def _init_successorstovisit(self): - """ - Initialize the attribute list 'SuccessorsToVisit' - """ - for i in range(0, self.circuit_dag_dep.size()): - if i == self.node_c._node_id: - self.successorstovisit[get_node(self.circuit_dag_dep, i)] = get_successors( - self.circuit_dag_dep, i - ) - - def _init_matchedwith(self): - """ - Initialize the attribute 'MatchedWith' in the template DAG dependency. - """ - for i in range(0, self.circuit_dag_dep.size()): - if i == self.node_c._node_id: - self.matchedwith[get_node(self.circuit_dag_dep, i)] = [self.node_t._node_id] - else: - self.matchedwith[get_node(self.circuit_dag_dep, i)] = [] - - for i in range(0, self.template_dag_dep.size()): - if i == self.node_t._node_id: - self.matchedwith[get_node(self.template_dag_dep, i)] = [self.node_c._node_id] - else: - self.matchedwith[get_node(self.template_dag_dep, i)] = [] - - def _init_isblocked(self): - """ - Initialize the attribute 'IsBlocked' in the circuit DAG dependency. - """ - for i in range(0, self.circuit_dag_dep.size()): - self.isblocked[get_node(self.circuit_dag_dep, i)] = False - - for i in range(0, self.template_dag_dep.size()): - self.isblocked[get_node(self.template_dag_dep, i)] = False - def _init_list_match(self): """ Initialize the list of matched nodes between the circuit and the template @@ -160,15 +129,15 @@ def _find_forward_candidates(self, node_id_t): block = [] for node_id in pred: - for dir_succ in get_successors(self.template_dag_dep, node_id): - if dir_succ not in matches: - node = get_node(self.template_dag_dep, dir_succ) + for succ in get_successors(self.template_dag_dep, node_id): + if succ not in matches: + node = get_node(self.template_dag_dep, succ) if node not in self.temp_match_class.descendants: self.temp_match_class.descendants[node] = get_descendants( - self.template_dag_dep, dir_succ + self.template_dag_dep, succ ) - succ = self.temp_match_class.descendants[node] - block = block + succ + desc = self.temp_match_class.descendants[node] + block = block + desc self.candidates = list(set(temp_succs) - set(matches) - set(block)) def _init_matched_nodes(self): @@ -184,7 +153,7 @@ def _get_node_forward(self, list_id): list_id (int): considered list id of the desired node. Returns: - DAGDepNode: DAGDepNode object corresponding to i-th node of the matched_node_list. + DAGOpNode: DAGOpNode object corresponding to i-th node of the matched_node_list. """ return self.matched_nodes_list.pop(list_id)[1] @@ -192,7 +161,7 @@ def _get_successors_to_visit(self, node, list_id): """ Return the successor for a given node and id. Args: - node (DAGOpNode or DAGOutNode): current node. + node (DAGOpNode): current node. list_id (int): id in the list for the successor to get. Returns: @@ -202,10 +171,10 @@ def _get_successors_to_visit(self, node, list_id): def _update_qarg_indices(self, qarg): """ - Change qubits indices of the current circuit node in order to + Change qubit indices of the current circuit node in order to be comparable with the indices of the template qubits list. Args: - qarg (list): list of qubits indices from the circuit for a given node. + qarg (list): list of qubit indices from the circuit for a given node. """ self.qarg_indices = [] for q in qarg: @@ -216,10 +185,10 @@ def _update_qarg_indices(self, qarg): def _update_carg_indices(self, carg): """ - Change clbits indices of the current circuit node in order to - be comparable with the indices of the template qubits list. + Change clbit indices of the current circuit node in order to + be comparable with the indices of the template qubit list. Args: - carg (list): list of clbits indices from the circuit for a given node. + carg (list): list of clbit indices from the circuit for a given node. """ self.carg_indices = [] if carg: @@ -233,8 +202,8 @@ def _is_same_op(self, node_circuit, node_template): """ Check if two instructions are the same. Args: - node_circuit (DAGDepNode): node in the circuit. - node_template (DAGDepNode): node in the template. + node_circuit (DAGOpNode): node in the circuit. + node_template (DAGOpNode): node in the template. Returns: bool: True if the same, False otherwise. """ @@ -242,10 +211,10 @@ def _is_same_op(self, node_circuit, node_template): def _is_same_q_conf(self, node_circuit, node_template): """ - Check if the qubits configurations are compatible. + Check if the qubit configurations are compatible. Args: - node_circuit (DAGDepNode): node in the circuit. - node_template (DAGDepNode): node in the template. + node_circuit (DAGOpNode): node in the circuit. + node_template (DAGOpNode): node in the template. Returns: bool: True if possible, False otherwise. """ @@ -290,10 +259,10 @@ def _is_same_q_conf(self, node_circuit, node_template): def _is_same_c_conf(self, node_circuit, node_template): """ - Check if the clbits configurations are compatible. + Check if the clbit configurations are compatible. Args: - node_circuit (DAGDepNode): node in the circuit. - node_template (DAGDepNode): node in the template. + node_circuit (DAGOpNode): node in the circuit. + node_template (DAGOpNode): node in the template. Returns: bool: True if possible, False otherwise. """ @@ -313,14 +282,16 @@ def _is_same_c_conf(self, node_circuit, node_template): def run_forward_match(self): """ - Apply the forward match algorithm and returns the list of matches given an initial match - and a circuit qubits configuration. + Apply the forward match algorithm and return the list of matches given an initial match + and a circuit qubit configuration. """ - # Initialize the new attributes of the DAGDepNodes of the DAGDependency object - self._init_successorstovisit() - self._init_matchedwith() - self._init_isblocked() + # Initialize certain attributes + for node in self.circuit_dag_dep.topological_nodes(): + self.successorstovisit[node] = get_successors(self.circuit_dag_dep, node._node_id) + + self.matchedwith[self.node_c] = [self.node_t._node_id] + self.matchedwith[self.node_t] = [self.node_c._node_id] # Initialize the list of matches and the stack of matched nodes (circuit) self._init_list_match() @@ -342,7 +313,7 @@ def run_forward_match(self): self.matched_nodes_list.sort(key=lambda x: self.successorstovisit[x]) # If the node is blocked and already matched go to the end - if self.isblocked[trial_successor] | (self.matchedwith[trial_successor] != []): + if self.isblocked.get(trial_successor) or self.matchedwith.get(trial_successor): continue # Search for potential candidates in the template @@ -355,7 +326,7 @@ def run_forward_match(self): self.circuit_dag_dep, get_node(self.circuit_dag_dep, trial_successor_id) ) - # Update the indices for both qubits and clbits in order to be comparable with the + # Update the indices for both qubits and clbits in order to be comparable with the # indices in the template circuit. self._update_qarg_indices(qarg1) self._update_carg_indices(carg1) @@ -382,8 +353,8 @@ def run_forward_match(self): ): continue - # Check if the qubit, clbit configuration are compatible for a match, - # also check if the operation are the same. + # Check if the qubit, clbit configurations are compatible for a match, + # also check if the operations are the same. if ( self._is_same_q_conf(node_circuit, node_template) and self._is_same_c_conf(node_circuit, node_template) @@ -400,8 +371,8 @@ def run_forward_match(self): # If the potential successors to visit are blocked or matched, it is removed. for potential_id in potential: - if self.isblocked[get_node(self.circuit_dag_dep, potential_id)] | ( - self.matchedwith[get_node(self.circuit_dag_dep, potential_id)] != [] + if self.isblocked.get(get_node(self.circuit_dag_dep, potential_id)) or ( + self.matchedwith.get(get_node(self.circuit_dag_dep, potential_id)) ): potential.remove(potential_id) @@ -419,48 +390,18 @@ def run_forward_match(self): # If no match is found, block the node and all the descendants. if not match: self.isblocked[trial_successor] = True - node = get_node(self.circuit_dag_dep, trial_successor_id) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( + if trial_successor not in self.temp_match_class.descendants: + self.temp_match_class.descendants[trial_successor] = get_descendants( self.circuit_dag_dep, trial_successor_id ) - for desc in self.temp_match_class.descendants[node]: - self.isblocked[get_node(self.circuit_dag_dep, desc)] = True - if self.matchedwith[get_node(self.circuit_dag_dep, desc)]: - self.match.remove( - [self.matchedwith[get_node(self.circuit_dag_dep, desc)][0], desc] - ) - match_id = self.matchedwith[get_node(self.circuit_dag_dep, desc)][0] + for desc_id in self.temp_match_class.descendants[trial_successor]: + desc = get_node(self.circuit_dag_dep, desc_id) + self.isblocked[desc] = True + if self.matchedwith.get(desc): + match_id = self.matchedwith[desc][0] + self.match.remove([match_id, desc]) self.matchedwith[get_node(self.template_dag_dep, match_id)] = [] - self.matchedwith[get_node(self.circuit_dag_dep, desc)] = [] + self.matchedwith[desc] = [] return (self.matchedwith, self.isblocked) - - -def get_node(dag, node_id): - return dag._multi_graph[node_id] - - -def get_qindices(dag, node): - return [dag.find_bit(qarg).index for qarg in node.qargs] - - -def get_cindices(dag, node): - return [dag.find_bit(carg).index for carg in node.cargs] - - -def get_descendants(dag, node_id): - return list(rx.descendants(dag._multi_graph, node_id)) - - -def get_ancestors(dag, node_id): - return list(rx.ancestors(dag._multi_graph, node_id)) - - -def get_successors(dag, node): - return [succ._node_id for succ in dag._multi_graph.successors(node)] - - -def get_predecessors(dag, node): - return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 9ec8c59acfec..ac0468f38812 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020-2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -25,8 +25,12 @@ import itertools -import rustworkx as rx - +from qiskit.transpiler.passes.optimization.template_matching_v2.template_utils_v2 import ( + get_node, + get_qindices, + get_cindices, + get_descendants, +) from qiskit.circuit.controlledgate import ControlledGate from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import ( @@ -49,8 +53,8 @@ def __init__( """ Create a TemplateMatching object with necessary arguments. Args: - circuit_dag_dep (QuantumCircuit): circuit. - template_dag_dep (QuantumCircuit): template. + circuit_dag_dep (DAGDependencyV2): circuit dag. + template_dag_dep (DAGDependencyV2): template dag. heuristics_backward_param (list[int]): [length, survivor] heuristics_qubits_param (list[int]): [length] """ @@ -68,11 +72,11 @@ def __init__( def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbits_t): """ - Returns the list of qubit for circuit given the first match, the unknown qubit are + Returns the list of qubits for the circuit given the first match, the unknown qubits are replaced by -1. Args: - node_circuit (DAGDepNode): First match node in the circuit. - node_template (DAGDepNode): First match node in the template. + node_circuit (DAGOpNode): First match node in the circuit. + node_template (DAGOpNode): First match node in the template. n_qubits_t (int): number of qubit in the template. n_clbits_t (int): number of classical bit in the template. Returns: @@ -144,30 +148,32 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit return l_q, l_c - def _sublist(self, lst, exclude, length): + def _sublist(self, qubit_id_list, exclude, length): """ Function that returns all possible combinations of a given length, considering an excluded list of elements. Args: - lst (list): list of qubits indices from the circuit. + qubit_id_list (list): list of qubits indices from the circuit. exclude (list): list of qubits from the first matched circuit gate. - length (int): length of the list to be returned (number of template qubit - - number of qubit from the first matched template gate). + length (int): length of the list to be returned (number of template qubits - + number of qubits from the first matched template gate). Yield: iterator: Iterator of the possible lists. """ - for sublist in itertools.combinations([e for e in lst if e not in exclude], length): + for sublist in itertools.combinations( + [e for e in qubit_id_list if e not in exclude], length + ): yield list(sublist) def _list_qubit_clbit_circuit(self, list_first_match, permutation): """ - Function that returns the list of the circuit qubits and clbits give a permutation + Function that returns the list of the circuit qubits and clbits given a permutation and an initial match. Args: - list_first_match (list): list of qubits indices for the initial match. - permutation (list): possible permutation for the circuit qubit. + list_first_match (list): list of qubit indices for the initial match. + permutation (list): possible permutation for the circuit qubits. Returns: - list: list of circuit qubit for the given permutation and initial match. + list: list of circuit qubits for the given permutation and initial match. """ list_circuit = [] @@ -184,9 +190,9 @@ def _list_qubit_clbit_circuit(self, list_first_match, permutation): def _add_match(self, backward_match_list): """ - Method to add a match in list only if it is not already in it. + Method to add a match in the list only if it is not already in it. If the match is already in the list, the qubit configuration - is append to the existing match. + is appended to the existing match. Args: backward_match_list (list): match from the backward part of the algorithm. @@ -206,14 +212,14 @@ def _add_match(self, backward_match_list): def _explore_circuit(self, node_c, node_t, n_qubits_t, length): """ - Explore the successors of the node_id_c (up to the given length). + Explore the descendants of the node_c (up to the given length). Args: node_c (DAGOpNode): first match in the circuit. node_t (DAGOpNode): first match in the template. n_qubits_t (int): number of qubits in the template. length (int): length for exploration of the successors. Returns: - list: qubits configuration for the 'length' successors of node_id_c. + list: qubit configuration for the 'length' descendants of node_c. """ template_nodes = range(node_t._node_id + 1, self.template_dag_dep.size()) circuit_nodes = range(0, self.circuit_dag_dep.size()) @@ -268,10 +274,16 @@ def run_template_matching(self): n_clbits_t = len(self.template_dag_dep.clbits) # Loop over the indices of both template and circuit. - for node_id_t in range(0, self.template_dag_dep.size()): - for node_id_c in range(0, self.circuit_dag_dep.size()): - node_t = get_node(self.template_dag_dep, node_id_t) - node_c = get_node(self.circuit_dag_dep, node_id_c) + # for node_id_t in range(0, self.template_dag_dep.size()): + # for node_id_c in range(0, self.circuit_dag_dep.size()): + for ( + node_t + ) in self.template_dag_dep.topological_nodes(): # range(0, self.template_dag_dep.size()): + for ( + node_c + ) in self.circuit_dag_dep.topological_nodes(): # range(0, self.circuit_dag_dep.size()): + # node_t = get_node(self.template_dag_dep, node_id_t) + # node_c = get_node(self.circuit_dag_dep, node_id_c) # Operations match up to ParameterExpressions. if node_c.op.soft_compare(node_t.op): @@ -291,8 +303,8 @@ def run_template_matching(self): list_circuit_q = list(range(0, n_qubits_c)) list_circuit_c = list(range(0, n_clbits_c)) - # If the parameter for qubits heuristics is given then extracts - # the list of qubits for the successors (length(int)) in the circuit. + # If the parameter for qubit heuristics is given then extract + # the list of qubits for the descendants (length(int)) in the circuit. if self.heuristics_qubits_param: heuristics_qubits = self._explore_circuit( @@ -302,7 +314,7 @@ def run_template_matching(self): heuristics_qubits = [] for sub_q in self._sublist(list_circuit_q, qarg_c, n_qubits_t - len(qarg_t)): - # If the heuristics qubits are a subset of the given qubits configuration, + # If the heuristics qubits are a subset of the given qubit configuration, # then this configuration is accepted. if set(heuristics_qubits).issubset(set(sub_q) | set(qarg_c)): # Permute the qubit configuration. @@ -313,7 +325,7 @@ def run_template_matching(self): list_first_match_q, perm_q ) - # Check for clbits configurations if there are clbits. + # Check for clbit configurations if there are clbits. if list_circuit_c: for sub_c in self._sublist( list_circuit_c, carg_c, n_clbits_t - len(carg_t) @@ -387,31 +399,3 @@ def run_template_matching(self): # Sort the list of matches according to the length of the matches (decreasing order). self.match_list.sort(key=lambda x: len(x.match), reverse=True) - - -def get_node(dag, node_id): - return dag._multi_graph[node_id] - - -def get_qindices(dag, node): - return [dag.find_bit(qarg).index for qarg in node.qargs] - - -def get_cindices(dag, node): - return [dag.find_bit(carg).index for carg in node.cargs] - - -def get_descendants(dag, node_id): - return list(rx.descendants(dag._multi_graph, node_id)) - - -def get_ancestors(dag, node_id): - return list(rx.ancestors(dag._multi_graph, node_id)) - - -def get_successors(dag, node): - return [succ._node_id for succ in dag._multi_graph.successors(node)] - - -def get_predecessors(dag, node): - return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 69455c3222ac..0e0616a7aec8 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020-2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -18,8 +18,13 @@ import copy import itertools -import rustworkx as rx - +from qiskit.transpiler.passes.optimization.template_matching_v2.template_utils_v2 import ( + get_node, + get_qindices, + get_cindices, + get_descendants, + get_ancestors, +) from qiskit.circuit import Parameter, ParameterExpression from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.dagcircuit.dagdependency_v2 import _DAGDependencyV2 @@ -663,31 +668,3 @@ def _incr_num_parameters(self, template): circuit_params.update(param_exp.parameters) return len(template_params) > len(circuit_params) - - -def get_node(dag, node_id): - return dag._multi_graph[node_id] - - -def get_qindices(dag, node): - return [dag.find_bit(qarg).index for qarg in node.qargs] - - -def get_cindices(dag, node): - return [dag.find_bit(carg).index for carg in node.cargs] - - -def get_descendants(dag, node_id): - return list(rx.descendants(dag._multi_graph, node_id)) - - -def get_ancestors(dag, node_id): - return list(rx.ancestors(dag._multi_graph, node_id)) - - -def get_successors(dag, node): - return [succ._node_id for succ in dag._multi_graph.successors(node)] - - -def get_predecessors(dag, node): - return [pred._node_id for pred in dag._multi_graph.predecessors(node)] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py new file mode 100644 index 000000000000..3031ca0ccd30 --- /dev/null +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -0,0 +1,55 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020-2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Template matching in the forward direction, it takes an initial +match, a configuration of qubits and both circuit and template as inputs. The +result is a list of matches between the template and the circuit. + + +**Reference:** + +[1] Iten, R., Moyard, R., Metger, T., Sutter, D. and Woerner, S., 2020. +Exact and practical pattern matching for quantum circuit optimization. +`arXiv:1909.05270 `_ + +""" + +import rustworkx as rx + + +def get_node(dag, node_id): + return dag._multi_graph[node_id] + + +def get_qindices(dag, node): + return [dag.find_bit(qarg).index for qarg in node.qargs] + + +def get_cindices(dag, node): + return [dag.find_bit(carg).index for carg in node.cargs] + + +def get_descendants(dag, node_id): + return list(rx.descendants(dag._multi_graph, node_id)) + + +def get_ancestors(dag, node_id): + return list(rx.ancestors(dag._multi_graph, node_id)) + + +def get_successors(dag, node_id): + return [succ._node_id for succ in dag._multi_graph.successors(node_id)] + + +def get_predecessors(dag, node_id): + return [pred._node_id for pred in dag._multi_graph.predecessors(node_id)] diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index 28450e1bd024..ebc2631b86a0 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020-2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -58,7 +58,7 @@ def __init__( ): """ Args: - template_list (list[QuantumCircuit()]): list of the different template circuit to apply. + template_list (list[QuantumCircuit()]): list of the different template circuits to apply. heuristics_backward_param (list[int]): [length, survivor] Those are the parameters for applying heuristics on the backward part of the algorithm. This part of the algorithm creates a tree of matching scenario. This tree grows exponentially. The @@ -69,8 +69,8 @@ def __init__( matches. Check reference for more details. heuristics_qubits_param (list[int]): [length] The heuristics for the qubit choice make guesses from the dag dependency of the circuit in order to limit the number of - qubit configurations to explore. The length is the number of successors or not - predecessors that will be explored in the dag dependency of the circuit, each + qubit configurations to explore. The length is the number of descendants or not + ancestors that will be explored in the dag dependency of the circuit. Each of the qubits of the nodes are added to the set of authorized qubits. We advise to use length=1. Check reference for more details. user_cost_dict (Dict[str, int]): quantum cost dictionary passed to TemplateSubstitution @@ -78,7 +78,7 @@ def __init__( is not given. The key is the name of the gate and the value its quantum cost. """ super().__init__() - # If no template is given; the template are set as x-x, cx-cx, ccx-ccx. + # If no template is given; the templates are set as x-x, cx-cx, ccx-ccx. if template_list is None: template_list = [template_nct_2a_1(), template_nct_2a_2(), template_nct_2a_3()] self.template_list = template_list @@ -88,7 +88,6 @@ def __init__( self.heuristics_backward_param = ( heuristics_backward_param if heuristics_backward_param is not None else [] ) - self.user_cost_dict = user_cost_dict def run(self, dag): @@ -99,14 +98,14 @@ def run(self, dag): DAGCircuit: optimized DAG circuit. Raises: TranspilerError: If the template has not the right form or - if the output circuit acts differently as the input circuit. + if the output circuit acts differently as the input circuit. """ circuit_dag = dag circuit_dag_dep = _dag_to_dagdependency_v2(circuit_dag) for template in self.template_list: if not isinstance(template, (QuantumCircuit, _DAGDependencyV2)): - raise TranspilerError("A template is a Quantumciruit or a DAGDependency.") + raise TranspilerError("A template is a Quantumciruit or a DAGDependencyV2.") if len(template.qubits) > len(circuit_dag_dep.qubits): continue From a5a4ff633d3ecf951e3cab812b4ca8cde561d6c4 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 1 May 2024 08:37:41 -0700 Subject: [PATCH 09/18] More cleanup and finish backward --- .../template_matching_v2/__init__.py | 10 +- .../template_matching_v2/backward_match_v2.py | 280 ++++++------------ .../template_matching_v2/forward_match_v2.py | 67 +---- .../template_matching_v2.py | 5 +- .../optimization/template_optimization_v2.py | 6 +- 5 files changed, 108 insertions(+), 260 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py index 9a864ebf2e33..c966fb362bf8 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/__init__.py @@ -12,8 +12,8 @@ """Module containing template matching methods.""" -# from .forward_match_v2 import ForwardMatch -# from .backward_match_v2 import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList -# from .template_matching_v2 import TemplateMatching -# from .maximal_matches_v2 import MaximalMatches -# from .template_substitution_v2 import SubstitutionConfig, TemplateSubstitution +from .forward_match_v2 import ForwardMatch +from .backward_match_v2 import BackwardMatch, Match, MatchingScenarios, MatchingScenariosList +from .template_matching_v2 import TemplateMatching +from .maximal_matches_v2 import MaximalMatches +from .template_substitution_v2 import SubstitutionConfig, TemplateSubstitution diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index dcc0f92f08be..6e7fa2b60aab 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2020. +# (C) Copyright IBM 2020-2024. # # This code is licensed under the Apache License, Version 2.0. You may # obtain a copy of this license in the LICENSE.txt file in the root directory @@ -32,8 +32,6 @@ get_cindices, get_descendants, get_ancestors, - get_successors, - get_predecessors, ) from qiskit.circuit.controlledgate import ControlledGate @@ -73,7 +71,7 @@ def __init__( Args: circuit_matched (list): list of matchedwith attributes in the circuit. circuit_blocked (list): list of isblocked attributes in the circuit. - template_matched (list): list of self.matchedwith.get(node)self.matchedwith.get(node) attributes in the template. + template_matched (list): list of matchedwith attributes in the template. template_blocked (list): list of isblocked attributes in the template. matches (list): list of matches. counter (int): counter of the number of circuit gates already considered. @@ -89,7 +87,6 @@ def __init__( class MatchingScenariosList: """ Object to define a list of MatchingScenarios, with method to append - and pop elements. """ def __init__(self): @@ -106,17 +103,6 @@ def append_scenario(self, matching): """ self.matching_scenarios_list.append(matching) - def pop_scenario(self): - """ - Pop the first scenario of the list. - Returns: - MatchingScenarios: a scenario of match. - """ - # Pop the first MatchingScenario and returns it - first = self.matching_scenarios_list[0] - self.matching_scenarios_list.pop(0) - return first - class BackwardMatch: """ @@ -139,17 +125,20 @@ def __init__( heuristics_backward_param=None, ): """ - Create a ForwardMatch class with necessary arguments. + Create a BackwardMatch class with necessary arguments. Args: - circuit_dag_dep (DAGDependency): circuit in the dag dependency form. - template_dag_dep (DAGDependency): template in the dag dependency form. + circuit_dag_dep (_DAGDependencyV2): circuit in the dag dependency form. + template_dag_dep (_DAGDependencyV2): template in the dag dependency form. forward_matches (list): list of match obtained in the forward direction. - node_id_c (int): index of the first gate matched in the circuit. - node_id_t (int): index of the first gate matched in the template. + node_c (DAGOpNode): node of the first gate matched in the circuit. + node_id_t (DAGOpNode): node of the first gate matched in the template. + temp_match_class (TemplateMatchingV2): instance of the calling class + matchedwith (dict): per node list of matches + isblocked (dict): per node indicator of blocked node qubits (list): list of considered qubits in the circuit. clbits (list): list of considered clbits in the circuit. heuristics_backward_param (list): list that contains the two parameters for - applying the heuristics (length and survivor). + applying the heuristics (length and survivor). """ self.circuit_dag_dep = circuit_dag_dep self.template_dag_dep = template_dag_dep @@ -167,26 +156,9 @@ def __init__( self.matchedwith = matchedwith self.isblocked = isblocked - def _gate_indices(self): - """ - Function which returns the list of gates that are not match and not - blocked for the first scenario. - Returns: - list: list of gate id. - """ - gate_indices = [] - - current_dag = self.circuit_dag_dep - - for node in current_dag.op_nodes(): - if (not self.matchedwith.get(node)) and (not self.isblocked.get(node)): - gate_indices.append(node._node_id) - gate_indices.reverse() - return gate_indices - def _find_backward_candidates(self, template_blocked, matches): """ - Function which returns the list possible backward candidates in the template dag. + Function which returns the list of possible backward candidates in the template dag. Args: template_blocked (list): list of attributes isblocked in the template circuit. matches (list): list of matches. @@ -201,15 +173,14 @@ def _find_backward_candidates(self, template_blocked, matches): matches_template = sorted(match[0] for match in matches) - node = get_node(self.template_dag_dep, self.node_t._node_id) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( + if self.node_t not in self.temp_match_class.descendants: + self.temp_match_class.descendants[self.node_t] = get_descendants( self.template_dag_dep, self.node_t._node_id ) - successors = self.temp_match_class.descendants[node] + descendants = self.temp_match_class.descendants[self.node_t] potential = [] for index in range(self.node_t._node_id + 1, self.template_dag_dep.size()): - if (index not in successors) and (index not in template_block): + if (index not in descendants) and (index not in template_block): potential.append(index) candidates_indices = list(set(potential) - set(matches_template)) @@ -218,78 +189,29 @@ def _find_backward_candidates(self, template_blocked, matches): return candidates_indices - def _update_qarg_indices(self, qarg): - """ - Change qubits indices of the current circuit node in order to - be comparable the indices of the template qubits list. - Args: - qarg (list): list of qubits indices from the circuit for a given gate. - Returns: - list: circuit indices update for qubits. - """ - qarg_indices = [] - for q in qarg: - if q in self.qubits: - qarg_indices.append(self.qubits.index(q)) - if len(qarg) != len(qarg_indices): - qarg_indices = [] - return qarg_indices - - def _update_carg_indices(self, carg): - """ - Change clbits indices of the current circuit node in order to - be comparable the indices of the template qubits list. - Args: - carg (list): list of clbits indices from the circuit for a given gate. - Returns: - list: circuit indices update for clbits. - """ - carg_indices = [] - if carg: - for q in carg: - if q in self.clbits: - carg_indices.append(self.clbits.index(q)) - if len(carg) != len(carg_indices): - carg_indices = [] - return carg_indices - - def _is_same_op(self, node_circuit, node_template): - """ - Check if two instructions are the same. - Args: - node_circuit (DAGDepNode): node in the circuit. - node_template (DAGDepNode): node in the template. - Returns: - bool: True if the same, False otherwise. - """ - return node_circuit.op == node_template.op - def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): """ - Check if the qubits configurations are compatible. + Check if the qubit configurations are compatible. Args: - node_circuit (DAGDepNode): node in the circuit. - node_template (DAGDepNode): node in the template. - qarg_circuit (list): qubits configuration for the Instruction in the circuit. + node_circuit (DAGOpNode): node in the circuit. + node_template (DAGOpNode): node in the template. + qarg_circuit (list): qubit configuration for the Instruction in the circuit. Returns: bool: True if possible, False otherwise. """ # If the gate is controlled, then the control qubits have to be compared as sets. node_temp_qind = get_qindices(self.template_dag_dep, node_template) if isinstance(node_circuit.op, ControlledGate): - c_template = node_template.op.num_ctrl_qubits if c_template == 1: return qarg_circuit == node_temp_qind else: - node_temp_qind = get_qindices(self.template_dag_dep, node_template) control_qubits_template = node_temp_qind[:c_template] control_qubits_circuit = qarg_circuit[:c_template] if set(control_qubits_circuit) == set(control_qubits_template): - target_qubits_template = node_temp_qind[c_template::] target_qubits_circuit = qarg_circuit[c_template::] @@ -306,7 +228,7 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): return target_qubits_template == target_qubits_circuit else: return False - # For non controlled gates, the qubits indices for symmetric gates can be compared as sets + # For non controlled gates, the qubit indices for symmetric gates can be compared as sets # But for non-symmetric gates the qubits indices have to be compared as lists. else: if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: @@ -316,17 +238,16 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): """ - Check if the clbits configurations are compatible. + Check if the clbit configurations are compatible. Args: - node_circuit (DAGDepNode): node in the circuit. - node_template (DAGDepNode): node in the template. - carg_circuit (list): clbits configuration for the Instruction in the circuit. + node_circuit (DAGOpNode): node in the circuit. + node_template (DAGOpNode): node in the template. + carg_circuit (list): clbit configuration for the Instruction in the circuit. Returns: bool: True if possible, False otherwise. """ if ( getattr(node_circuit.op, "condition", None) - and node_template.type == "op" and getattr(node_template.op, "condition", None) ): if set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_template)): @@ -338,32 +259,6 @@ def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): return False return True - def _init_matched_blocked_list(self): - """ - Initialize the list of blocked and matchedwith attributes. - Returns: - Tuple[list, list, list, list]: - First list contains the attributes matchedwith in the circuit, - second list contains the attributes isblocked in the circuit, - third list contains the attributes matchedwith in the template, - fourth list contains the attributes isblocked in the template. - """ - circuit_matched = [] - circuit_blocked = [] - - for node in self.circuit_dag_dep.op_nodes(): - circuit_matched.append(self.matchedwith.get(node)) - circuit_blocked.append(self.isblocked.get(node)) - - template_matched = [] - template_blocked = [] - - for node in self.template_dag_dep.op_nodes(): - template_matched.append(self.matchedwith.get(node)) - template_blocked.append(self.isblocked.get(node)) - - return circuit_matched, circuit_blocked, template_matched, template_blocked - def _backward_heuristics(self, gate_indices, length, survivor): """ Heuristics to cut the tree in the backward match algorithm @@ -386,7 +281,7 @@ def _backward_heuristics(self, gate_indices, length, survivor): if (list_counter[0] - 1) % length == 0: # The list metrics contains metric results for each scenarios. for scenario in self.matching_list.matching_scenarios_list: - metrics.append(self._backward_metrics(scenario)) + metrics.append(len(scenario.matches)) # Select only the scenarios with higher metrics for the given number of survivors. largest = heapq.nlargest(survivor, range(len(metrics)), key=lambda x: metrics[x]) self.matching_list.matching_scenarios_list = [ @@ -395,33 +290,27 @@ def _backward_heuristics(self, gate_indices, length, survivor): if j in largest ] - def _backward_metrics(self, scenario): - """ - Heuristics to cut the tree in the backward match algorithm. - Args: - scenario (MatchingScenarios): scenario for the given match. - Returns: - int: length of the match for the given scenario. - """ - return len(scenario.matches) - def run_backward_match(self): """ - Apply the forward match algorithm and returns the list of matches given an initial match + Apply the backward match algorithm and return the list of matches given an initial match and a circuit qubits configuration. - """ match_store_list = [] counter = 1 # Initialize the list of attributes matchedwith and isblocked. - ( - circuit_matched, - circuit_blocked, - template_matched, - template_blocked, - ) = self._init_matched_blocked_list() + circuit_matched = [] + circuit_blocked = [] + for node in self.circuit_dag_dep.op_nodes(): + circuit_matched.append(self.matchedwith.get(node)) + circuit_blocked.append(self.isblocked.get(node)) + + template_matched = [] + template_blocked = [] + for node in self.template_dag_dep.op_nodes(): + template_matched.append(self.matchedwith.get(node)) + template_blocked.append(self.isblocked.get(node)) # First Scenario is stored in the MatchingScenariosList(). first_match = MatchingScenarios( @@ -437,7 +326,11 @@ def run_backward_match(self): self.matching_list.append_scenario(first_match) # Set the circuit indices that can be matched. - gate_indices = self._gate_indices() + gate_indices = [] + for node in self.circuit_dag_dep.op_nodes(): + if (not self.matchedwith.get(node)) and (not self.isblocked.get(node)): + gate_indices.append(node._node_id) + gate_indices.reverse() number_of_gate_to_match = ( self.template_dag_dep.size() - (self.node_t._node_id - 1) - len(self.forward_matches) @@ -454,7 +347,7 @@ def run_backward_match(self): self.heuristics_backward_param[1], ) - scenario = self.matching_list.pop_scenario() + scenario = self.matching_list.matching_scenarios_list.pop(0) circuit_matched = scenario.circuit_matched circuit_blocked = scenario.circuit_blocked @@ -500,11 +393,16 @@ def run_backward_match(self): candidates_indices = self._find_backward_candidates(template_blocked, matches_scenario) # Update of the qubits/clbits indices in the circuit in order to be # comparable with the one in the template. - qarg1 = get_qindices(self.circuit_dag_dep, node_circuit) - carg1 = get_cindices(self.circuit_dag_dep, node_circuit) + qarg_indices = get_qindices(self.circuit_dag_dep, node_circuit) + carg_indices = get_cindices(self.circuit_dag_dep, node_circuit) + + qarg1 = [self.qubits.index(q) for q in qarg_indices if q in self.qubits] + if len(qarg1) != len(qarg_indices): + qarg1 = [] - qarg1 = self._update_qarg_indices(qarg1) - carg1 = self._update_carg_indices(carg1) + carg1 = [self.clbits.index(c) for q in carg_indices if c in self.clbits] + if len(carg1) != len(carg_indices): + carg1 = [] global_match = False global_broken = [] @@ -525,12 +423,12 @@ def run_backward_match(self): ): continue - # Check if the qubit, clbit configuration are compatible for a match, - # also check if the operation are the same. + # Check if the qubit, clbit configurations are compatible for a match, + # also check if the operations are the same. if ( self._is_same_q_conf(node_circuit, node_template, qarg1) and self._is_same_c_conf(node_circuit, node_template, carg1) - and self._is_same_op(node_circuit, node_template) + and node_circuit.op == node_template.op ): # If there is a match the attributes are copied. @@ -546,19 +444,19 @@ def run_backward_match(self): broken_matches_match = [] # Loop to check if the match is not connected, in this case - # the successors matches are blocked and unmatched. + # the descendants matches are blocked and unmatched. for potential_block in get_descendants(self.template_dag_dep, template_id): if not template_matched_match[potential_block]: template_blocked_match[potential_block] = True block_list.append(potential_block) for block_id in block_list: - for succ_id in get_descendants(self.template_dag_dep, block_id): - template_blocked_match[succ_id] = True - if template_matched_match[succ_id]: - new_id = template_matched_match[succ_id][0] + for desc_id in get_descendants(self.template_dag_dep, block_id): + template_blocked_match[desc_id] = True + if template_matched_match[desc_id]: + new_id = template_matched_match[desc_id][0] circuit_matched_match[new_id] = [] - template_matched_match[succ_id] = [] - broken_matches_match.append(succ_id) + template_matched_match[desc_id] = [] + broken_matches_match.append(desc_id) if broken_matches_match: global_broken.append(True) @@ -611,20 +509,20 @@ def run_backward_match(self): broken_matches = [] - # Second option, not a greedy match, block all successors (push the gate + # Second option, not a greedy match, block all descendants (push the gate # to the right). node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.descendants: self.temp_match_class.descendants[node] = get_descendants( self.circuit_dag_dep, circuit_id ) - for succ in self.temp_match_class.descendants[node]: - circuit_blocked_block_s[succ] = True - if circuit_matched_block_s[succ]: - broken_matches.append(succ) - new_id = circuit_matched_block_s[succ][0] + for desc in self.temp_match_class.descendants[node]: + circuit_blocked_block_s[desc] = True + if circuit_matched_block_s[desc]: + broken_matches.append(desc) + new_id = circuit_matched_block_s[desc][0] template_matched_block_s[new_id] = [] - circuit_matched_block_s[succ] = [] + circuit_matched_block_s[desc] = [] new_matches_scenario_block_s = [ elem for elem in matches_scenario_block_s if elem[1] not in broken_matches @@ -650,8 +548,8 @@ def run_backward_match(self): ) self.matching_list.append_scenario(new_matching_scenario) - # Third option: if blocking the succesors breaks a match, we consider - # also the possibility to block all predecessors (push the gate to the left). + # Third option: if blocking the descendants breaks a match, we consider + # also the possibility to block all ancestors (push the gate to the left). if broken_matches and all(global_broken): circuit_matched_block_p = circuit_matched.copy() @@ -669,8 +567,8 @@ def run_backward_match(self): self.temp_match_class.ancestors[node] = get_ancestors( self.circuit_dag_dep, circuit_id ) - for pred in self.temp_match_class.ancestors[node]: - circuit_blocked_block_p[pred] = True + for anc in self.temp_match_class.ancestors[node]: + circuit_blocked_block_p[anc] = True matching_scenario = MatchingScenarios( circuit_matched_block_p, @@ -694,20 +592,20 @@ def run_backward_match(self): self.temp_match_class.descendants[node] = get_descendants( self.circuit_dag_dep, circuit_id ) - for succ in self.temp_match_class.descendants[node]: - if circuit_matched[succ]: - following_matches.append(succ) + for desc in self.temp_match_class.descendants[node]: + if circuit_matched[desc]: + following_matches.append(desc) # First option, the circuit gate is not disturbing because there are no - # following match and no predecessors. + # following match and no ancestors. node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.ancestors: self.temp_match_class.ancestors[node] = get_ancestors( self.circuit_dag_dep, circuit_id ) - predecessors = self.temp_match_class.ancestors[node] + ancestors = self.temp_match_class.ancestors[node] - if not predecessors or not following_matches: + if not ancestors or not following_matches: matching_scenario = MatchingScenarios( circuit_matched, @@ -729,9 +627,9 @@ def run_backward_match(self): matches_scenario_nomatch = matches_scenario.copy() - # Second option, all predecessors are blocked (circuit gate is + # Second option, all ancestors are blocked (circuit gate is # moved to the left). - for pred in predecessors: + for pred in ancestors: circuit_blocked[pred] = True matching_scenario = MatchingScenarios( @@ -744,7 +642,7 @@ def run_backward_match(self): ) self.matching_list.append_scenario(matching_scenario) - # Third option, all successors are blocked (circuit gate is + # Third option, all descendants are blocked (circuit gate is # moved to the right). broken_matches = [] @@ -754,13 +652,13 @@ def run_backward_match(self): self.temp_match_class.descendants[node] = get_descendants( self.circuit_dag_dep, circuit_id ) - successors = self.temp_match_class.descendants[node] + descendants = self.temp_match_class.descendants[node] - for succ in successors: - circuit_blocked_nomatch[succ] = True - if circuit_matched_nomatch[succ]: - broken_matches.append(succ) - circuit_matched_nomatch[succ] = [] + for desc in descendants: + circuit_blocked_nomatch[desc] = True + if circuit_matched_nomatch[desc]: + broken_matches.append(desc) + circuit_matched_nomatch[desc] = [] new_matches_scenario_nomatch = [ elem for elem in matches_scenario_nomatch if elem[1] not in broken_matches diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index accb882ae6bd..40ef2a6f5024 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -31,6 +31,7 @@ get_descendants, get_successors, ) + from qiskit.circuit.controlledgate import ControlledGate @@ -66,10 +67,10 @@ def __init__( # The dag dependency representation of the template self.template_dag_dep = template_dag_dep - # List of qubit on which the node of the circuit is acting on + # List of qubits on which the node of the circuit is acting self.qubits = qubits - # List of qubit on which the node of the circuit is acting on + # List of clbits on which the node of the circuit is acting self.clbits = clbits if clbits is not None else [] # Id of the node in the circuit @@ -78,7 +79,7 @@ def __init__( # Id of the node in the template self.node_t = node_t - # List of match + # List of matches self.match = [] # List of candidates for the forward match @@ -93,18 +94,15 @@ def __init__( # Transformation of the carg indices of the circuit to be adapted to the template indices self.carg_indices = [] + # Class instance of TemplateMatching caller self.temp_match_class = temp_match_class + # Dicts for storing lists of node ids self.successorstovisit = {} self.matchedwith = {} - self.isblocked = {} - def _init_list_match(self): - """ - Initialize the list of matched nodes between the circuit and the template - with the first match found. - """ - self.match.append([self.node_t._node_id, self.node_c._node_id]) + # Bool indicating if a node is blocked due to no match + self.isblocked = {} def _find_forward_candidates(self, node_id_t): """ @@ -136,39 +134,9 @@ def _find_forward_candidates(self, node_id_t): self.temp_match_class.descendants[node] = get_descendants( self.template_dag_dep, succ ) - desc = self.temp_match_class.descendants[node] - block = block + desc + block = block + self.temp_match_class.descendants[node] self.candidates = list(set(temp_succs) - set(matches) - set(block)) - def _init_matched_nodes(self): - """ - Initialize the list of current matched nodes. - """ - self.matched_nodes_list.append(get_node(self.circuit_dag_dep, self.node_c._node_id)) - - def _get_node_forward(self, list_id): - """ - Return a node from the matched_node_list for a given list id. - Args: - list_id (int): considered list id of the desired node. - - Returns: - DAGOpNode: DAGOpNode object corresponding to i-th node of the matched_node_list. - """ - return self.matched_nodes_list.pop(list_id)[1] - - def _get_successors_to_visit(self, node, list_id): - """ - Return the successor for a given node and id. - Args: - node (DAGOpNode): current node. - list_id (int): id in the list for the successor to get. - - Returns: - int: id of the successor to get. - """ - return self.successorstovisit[node].pop(list_id) - def _update_qarg_indices(self, qarg): """ Change qubit indices of the current circuit node in order to @@ -198,17 +166,6 @@ def _update_carg_indices(self, carg): if len(carg) != len(self.carg_indices): self.carg_indices = [] - def _is_same_op(self, node_circuit, node_template): - """ - Check if two instructions are the same. - Args: - node_circuit (DAGOpNode): node in the circuit. - node_template (DAGOpNode): node in the template. - Returns: - bool: True if the same, False otherwise. - """ - return node_circuit.op.soft_compare(node_template.op) - def _is_same_q_conf(self, node_circuit, node_template): """ Check if the qubit configurations are compatible. @@ -294,8 +251,8 @@ def run_forward_match(self): self.matchedwith[self.node_t] = [self.node_c._node_id] # Initialize the list of matches and the stack of matched nodes (circuit) - self._init_list_match() - self._init_matched_nodes() + self.match.append([self.node_t._node_id, self.node_c._node_id]) + self.matched_nodes_list.append(get_node(self.circuit_dag_dep, self.node_c._node_id)) # While the list of matched nodes is not empty while self.matched_nodes_list: @@ -358,7 +315,7 @@ def run_forward_match(self): if ( self._is_same_q_conf(node_circuit, node_template) and self._is_same_c_conf(node_circuit, node_template) - and self._is_same_op(node_circuit, node_template) + and node_circuit.op.soft_compare(node_template.op) ): self.matchedwith[trial_successor] = [i] self.matchedwith[node_template] = [trial_successor_id] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index ac0468f38812..0f0c7e4555a2 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -32,10 +32,7 @@ get_descendants, ) from qiskit.circuit.controlledgate import ControlledGate -from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch -from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import ( - BackwardMatch, -) +from qiskit.transpiler.passes.optimization.template_matching_v2 import ForwardMatch, BackwardMatch class TemplateMatching: diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index ebc2631b86a0..8213f96c48b1 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -33,13 +33,9 @@ from qiskit.circuit.library.templates import template_nct_2a_1, template_nct_2a_2, template_nct_2a_3 from qiskit.quantum_info.operators.operator import Operator from qiskit.transpiler.exceptions import TranspilerError -from qiskit.transpiler.passes.optimization.template_matching_v2.template_matching_v2 import ( +from qiskit.transpiler.passes.optimization.template_matching_v2 import ( TemplateMatching, -) -from qiskit.transpiler.passes.optimization.template_matching_v2.template_substitution_v2 import ( TemplateSubstitution, -) -from qiskit.transpiler.passes.optimization.template_matching_v2.maximal_matches_v2 import ( MaximalMatches, ) From fe1533d1959a4f387605d6714de737f16ddf5d26 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Wed, 1 May 2024 15:46:03 -0700 Subject: [PATCH 10/18] Finish substitution cleanup --- .../template_matching_v2/backward_match_v2.py | 5 +- .../template_substitution_v2.py | 201 +++++++++--------- 2 files changed, 101 insertions(+), 105 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index 6e7fa2b60aab..b2d133c66762 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -246,9 +246,8 @@ def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): Returns: bool: True if possible, False otherwise. """ - if ( - getattr(node_circuit.op, "condition", None) - and getattr(node_template.op, "condition", None) + if getattr(node_circuit.op, "condition", None) and getattr( + node_template.op, "condition", None ): if set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_template)): return False diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 0e0616a7aec8..f4929f257d9b 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -35,7 +35,7 @@ class SubstitutionConfig: """ Class to store the configuration of a given match substitution, which circuit - gates, template gates, qubits, and clbits and predecessors of the match + gates, template gates, qubits, and clbits and ancestors of the match in the circuit. """ @@ -43,7 +43,7 @@ def __init__( self, circuit_config, template_config, - pred_block, + anc_block, qubit_config, template_dag_dep, clbit_config=None, @@ -53,12 +53,12 @@ def __init__( self.template_config = template_config self.qubit_config = qubit_config self.clbit_config = clbit_config if clbit_config is not None else [] - self.pred_block = pred_block + self.anc_block = anc_block def has_parameters(self): """Ensure that the template does not have parameters.""" - for node in self.template_dag_dep.op_nodes(): - for param in node.op.params: + for node_t in self.template_dag_dep.op_nodes(): + for param in node_t.op.params: if isinstance(param, ParameterExpression): return True @@ -76,10 +76,11 @@ def __init__( """ Initialize TemplateSubstitution with necessary arguments. Args: - max_matches (list): list of maximal matches obtained from the running - the template matching algorithm. - circuit_dag_dep (DAGDependency): circuit in the dag dependency form. - template_dag_dep (DAGDependency): template in the dag dependency form. + max_matches (list): list of maximal matches obtained from running + the template matching algorithm. + circuit_dag_dep (_DAGDependencyV2): circuit in the dag dependency form. + template_dag_dep (_DAGDependencyV2): template in the dag dependency form. + temp_match_class: (TemplateMatching): class instance of previous TemplateMatching user_cost_dict (Optional[dict]): user provided cost dictionary that will override the default cost dictionary. """ @@ -137,30 +138,29 @@ def __init__( "p": 1, } - def _pred_block(self, circuit_sublist, index): + def _anc_block(self, circuit_sublist, index): """ - It returns the predecessors of a given part of the circuit. + Returns the ancestors of a given part of the circuit. Args: circuit_sublist (list): list of the gates matched in the circuit. index (int): Index of the group of matches. Returns: - list: List of predecessors of the current match circuit configuration. + list: List of ancestors of the current match circuit configuration. """ - predecessors = set() + ancestors = set() for node_id in circuit_sublist: - node = get_node(self.circuit_dag_dep, node_id) - if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors(self.circuit_dag_dep, node) - predecessors = predecessors | set(self.temp_match_class.ancestors[node]) + node_c = get_node(self.circuit_dag_dep, node_id) + if node_c not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node] = get_ancestors(self.circuit_dag_dep, node_c) + ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) exclude = set() for elem in self.substitution_list[:index]: - exclude = exclude | set(elem.circuit_config) | set(elem.pred_block) + exclude = exclude | set(elem.circuit_config) | set(elem.anc_block) - pred = list(predecessors - set(circuit_sublist) - exclude) - pred.sort() - - return pred + anc = list(ancestors - set(circuit_sublist) - exclude) + anc.sort() + return anc def _quantum_cost(self, left, right): """ @@ -183,7 +183,7 @@ def _quantum_cost(self, left, right): def _rules(self, circuit_sublist, template_sublist, template_complement): """ - Set of rules to decide whether the match is to be substitute or not. + Set of rules to decide whether the match is to be substituted or not. Args: circuit_sublist (list): list of the gates matched in the circuit. template_sublist (list): list of matched nodes in the template. @@ -216,42 +216,42 @@ def _template_inverse(self, template_list, template_sublist, template_complement left = [] right = [] - pred = set() + ancestors = set() for index in template_sublist: - node = get_node(self.template_dag_dep, index) - if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors(self.template_dag_dep, index) - pred = pred | set(self.temp_match_class.ancestors[node]) - pred = list(pred - set(template_sublist)) + node_t = get_node(self.template_dag_dep, index) + if node_t not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node_t] = get_ancestors( + self.template_dag_dep, index + ) + ancestors = ancestors | set(self.temp_match_class.ancestors[node_t]) + ancestors = list(ancestors - set(template_sublist)) - succ = set() + descendants = set() for index in template_sublist: - node = get_node(self.template_dag_dep, index) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( + node_t = get_node(self.template_dag_dep, index) + if node_t not in self.temp_match_class.descendants: + self.temp_match_class.descendants[node_t] = get_descendants( self.template_dag_dep, index ) - succ = succ | set(self.temp_match_class.descendants[node]) - succ = list(succ - set(template_sublist)) + descendants = descendants | set(self.temp_match_class.descendants[node_t]) + descendants = list(descendants - set(template_sublist)) - comm = list(set(template_list) - set(pred) - set(succ)) + common = list(set(template_list) - set(ancestors) - set(descendants)) for elem in inverse: - if elem in pred: + if elem in ancestors: left.append(elem) - elif elem in succ: + elif elem in descendants: right.append(elem) - elif elem in comm: + elif elem in common: right.append(elem) left.sort() right.sort() - left.reverse() right.reverse() - total = left + right - return total + return left + right def _substitution_sort(self): """ @@ -263,23 +263,23 @@ def _substitution_sort(self): def _permutation(self): """ - Permute two groups of matches if first one has predecessors in the second one. + Permute two groups of matches if first one has ancestors in the second one. Returns: bool: True if the matches groups are in the right order, False otherwise. """ for scenario in self.substitution_list: - predecessors = set() + ancestors = set() for match in scenario.circuit_config: - node = get_node(self.circuit_dag_dep, match) - if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors( + node_c = get_node(self.circuit_dag_dep, match) + if node_c not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node_c] = get_ancestors( self.circuit_dag_dep, match ) - predecessors = predecessors | set(self.temp_match_class.ancestors[node]) - predecessors = predecessors - set(scenario.circuit_config) + ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) + ancestors = ancestors - set(scenario.circuit_config) index = self.substitution_list.index(scenario) for scenario_b in self.substitution_list[index::]: - if set(scenario_b.circuit_config) & predecessors: + if set(scenario_b.circuit_config) & ancestors: index1 = self.substitution_list.index(scenario) index2 = self.substitution_list.index(scenario_b) @@ -291,23 +291,23 @@ def _permutation(self): def _remove_impossible(self): """ - Remove matched groups if they both have predecessors in the other one, they are not + Remove matched groups if they both have ancestors in the other one, they are not compatible. """ - list_predecessors = [] + list_ancestors = [] remove_list = [] - # Initialize predecessors for each group of matches. + # Initialize ancestors for each group of matches. for scenario in self.substitution_list: - predecessors = set() + ancestors = set() for index in scenario.circuit_config: - node = get_node(self.circuit_dag_dep, index) - if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors( + node_c = get_node(self.circuit_dag_dep, index) + if node_c not in self.temp_match_class.ancestors: + self.temp_match_class.ancestors[node_c] = get_ancestors( self.circuit_dag_dep, index ) - predecessors = predecessors | set(self.temp_match_class.ancestors[node]) - list_predecessors.append(predecessors) + ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) + list_ancestors.append(ancestors) # Check if two groups of matches are incompatible. for scenario_a in self.substitution_list: @@ -320,8 +320,8 @@ def _remove_impossible(self): continue index_b = self.substitution_list.index(scenario_b) circuit_b = scenario_b.circuit_config - if (set(circuit_a) & list_predecessors[index_b]) and ( - set(circuit_b) & list_predecessors[index_a] + if (set(circuit_a) & list_ancestors[index_b]) and ( + set(circuit_b) & list_ancestors[index_a] ): remove_list.append(scenario_b) @@ -334,7 +334,7 @@ def _remove_impossible(self): def _substitution(self): """ From the list of maximal matches, it chooses which one will be used and gives the necessary - details for each substitution(template inverse, predecessors of the match). + details for each substitution(template inverse, ancestors of the match). """ while self.match_stack: @@ -363,7 +363,6 @@ def _substitution(self): template_sublist_inverse = self._template_inverse( template_list, template_sublist, template_complement ) - config = SubstitutionConfig( circuit_sublist, template_sublist_inverse, @@ -380,23 +379,23 @@ def _substitution(self): # First sort the matches according to the smallest index in the matches (circuit). self.substitution_list.sort(key=lambda x: x.circuit_config[0]) - # Change position of the groups due to predecessors of other groups. + # Change position of the groups due to ancestors of other groups. self._substitution_sort() for scenario in self.substitution_list: index = self.substitution_list.index(scenario) - scenario.pred_block = self._pred_block(scenario.circuit_config, index) + scenario.anc_block = self._anc_block(scenario.circuit_config, index) circuit_list = [] for elem in self.substitution_list: - circuit_list = circuit_list + elem.circuit_config + elem.pred_block + circuit_list = circuit_list + elem.circuit_config + elem.anc_block - # Unmatched gates that are not predecessors of any group of matches. + # Unmatched gates that are not ancestors of any group of matches. self.unmatched_list = sorted(set(range(0, self.circuit_dag_dep.size())) - set(circuit_list)) def run_dag_opt(self): """ - It runs the substitution algorithm and creates the optimized DAGCircuit(). + Runs the substitution algorithm and creates the optimized DAGCircuit(). """ self._substitution() @@ -422,7 +421,7 @@ def run_dag_opt(self): circuit_sub = group.circuit_config template_inverse = group.template_config - pred = group.pred_block + anc_group = group.anc_block qubit = group.qubit_config[0] @@ -431,11 +430,11 @@ def run_dag_opt(self): else: clbit = [] - # First add all the predecessors of the given match. - for elem in pred: - node = get_node(self.circuit_dag_dep, elem) - inst = node.op.copy() - dag_dep_opt.apply_operation_back(inst, node.qargs, node.cargs) + # First add all the ancestors of the given match. + for elem in anc_group: + node_c = get_node(self.circuit_dag_dep, elem) + inst = node_c.op.copy() + dag_dep_opt.apply_operation_back(inst, node_c.qargs, node_c.cargs) already_sub.append(elem) already_sub = already_sub + circuit_sub @@ -443,31 +442,29 @@ def run_dag_opt(self): # Then add the inverse of the template. for index in template_inverse: all_qubits = self.circuit_dag_dep.qubits - node = get_node(group.template_dag_dep, index) - qarg_t = get_qindices(group.template_dag_dep, node) + node_t = get_node(group.template_dag_dep, index) + qarg_t = get_qindices(group.template_dag_dep, node_t) qarg_c = [qubit[x] for x in qarg_t] qargs = [all_qubits[x] for x in qarg_c] all_clbits = self.circuit_dag_dep.clbits - carg_t = get_cindices(group.template_dag_dep, node) + carg_t = get_cindices(group.template_dag_dep, node_t) if all_clbits and clbit: carg_c = [clbit[x] for x in carg_t] cargs = [all_clbits[x] for x in carg_c] else: cargs = [] - node = get_node(group.template_dag_dep, index) - inst = node.op.copy() + node_t = get_node(group.template_dag_dep, index) + inst = node_t.op.copy() dag_dep_opt.apply_operation_back(inst.inverse(), qargs, cargs) # Add the unmatched gates. for node_id in self.unmatched_list: - node = get_node(self.circuit_dag_dep, node_id) - inst = node.op.copy() - dag_dep_opt.apply_operation_back(inst, node.qargs, node.cargs) + node_c = get_node(self.circuit_dag_dep, node_id) + inst = node_c.op.copy() + dag_dep_opt.apply_operation_back(inst, node_c.qargs, node_c.cargs) - # dag_dep_opt._add_predecessors() - # dag_dep_opt._add_successors() # If there is no valid match, it returns the original dag. else: dag_dep_opt = self.circuit_dag_dep @@ -522,7 +519,7 @@ def _attempt_bind(self, template_sublist, circuit_sublist): circuit_sublist (list): part of the matched circuit. Returns: - DAGDependency: A deep copy of the template with + _DAGDependency: A deep copy of the template with the parameters bound. If no binding satisfies the parameter constraints, returns None. """ @@ -567,9 +564,9 @@ def dummy_parameter(): # add parameters from template to template_params, replacing parameters with names that # clash with those in the circuit. for t_idx in template_sublist: - node = get_node(self.template_dag_dep, t_idx) + node_t = get_node(self.template_dag_dep, t_idx) sub_node_params = [] - for t_param_exp in node.op.params: + for t_param_exp in node_t.op.params: if isinstance(t_param_exp, ParameterExpression): for t_param in t_param_exp.parameters: if t_param.name in circuit_params_set: @@ -577,13 +574,13 @@ def dummy_parameter(): t_param_exp = t_param_exp.assign(t_param, new_param) sub_node_params.append(t_param_exp) template_params.append(t_param_exp) - if not node.op.mutable: - node.op = node.op.to_mutable() - node.op.params = sub_node_params + if not node_t.op.mutable: + node_t.op = node_t.op.to_mutable() + node_t.op.params = sub_node_params - for node in template_dag_dep.op_nodes(): + for node_t in template_dag_dep.op_nodes(): sub_node_params = [] - for param_exp in node.op.params: + for param_exp in node_t.op.params: if isinstance(param_exp, ParameterExpression): for param in param_exp.parameters: if param.name in template_clash_substitutions: @@ -592,9 +589,9 @@ def dummy_parameter(): ) sub_node_params.append(param_exp) - if not node.op.mutable: - node.op = node.op.to_mutable() - node.op.params = sub_node_params + if not node_t.op.mutable: + node_t.op = node_t.op.to_mutable() + node_t.op.params = sub_node_params # Create the fake binding dict and check equations, circ_dict, temp_symbols = [], {}, {} @@ -632,9 +629,9 @@ def dummy_parameter(): } fake_bind = {key: sol[key.name] for key in temp_symbols} - for node in template_dag_dep.op_nodes(): + for node_t in template_dag_dep.op_nodes(): bound_params = [] - for param_exp in node.op.params: + for param_exp in node_t.op.params: if isinstance(param_exp, ParameterExpression): for param in param_exp.parameters: if param in fake_bind: @@ -644,9 +641,9 @@ def dummy_parameter(): param_exp = float(param_exp) bound_params.append(param_exp) - if not node.op.mutable: - node.op = node.op.to_mutable() - node.op.params = bound_params + if not node_t.op.mutable: + node_t.op = node_t.op.to_mutable() + node_t.op.params = bound_params return template_dag_dep @@ -656,13 +653,13 @@ def _incr_num_parameters(self, template): parameters in the circuit. """ template_params = set() - for param_list in (node.op.params for node in template.op_nodes()): + for param_list in (node_t.op.params for node_t in template.op_nodes()): for param_exp in param_list: if isinstance(param_exp, ParameterExpression): template_params.update(param_exp.parameters) circuit_params = set() - for param_list in (node.op.params for node in self.circuit_dag_dep.op_nodes()): + for param_list in (node_c.op.params for node_c in self.circuit_dag_dep.op_nodes()): for param_exp in param_list: if isinstance(param_exp, ParameterExpression): circuit_params.update(param_exp.parameters) From 4ae02e8e1587a3afb2a6a3ab87e7e507182f724e Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Thu, 2 May 2024 16:41:35 -0700 Subject: [PATCH 11/18] More optimizations --- .../template_matching_v2/backward_match_v2.py | 151 +++++++----------- .../template_matching_v2/forward_match_v2.py | 2 +- .../template_matching_v2.py | 17 +- .../template_substitution_v2.py | 114 ++++++------- .../template_matching_v2/template_utils_v2.py | 7 + .../optimization/template_optimization_v2.py | 9 +- 6 files changed, 122 insertions(+), 178 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index b2d133c66762..aa04f642848b 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -299,17 +299,10 @@ def run_backward_match(self): counter = 1 # Initialize the list of attributes matchedwith and isblocked. - circuit_matched = [] - circuit_blocked = [] - for node in self.circuit_dag_dep.op_nodes(): - circuit_matched.append(self.matchedwith.get(node)) - circuit_blocked.append(self.isblocked.get(node)) - - template_matched = [] - template_blocked = [] - for node in self.template_dag_dep.op_nodes(): - template_matched.append(self.matchedwith.get(node)) - template_blocked.append(self.isblocked.get(node)) + circuit_matched = [self.matchedwith.get(node) for node in self.circuit_dag_dep.op_nodes()] + circuit_blocked = [self.isblocked.get(node) for node in self.circuit_dag_dep.op_nodes()] + template_matched = [self.matchedwith.get(node) for node in self.template_dag_dep.op_nodes()] + template_blocked = [self.isblocked.get(node) for node in self.template_dag_dep.op_nodes()] # First Scenario is stored in the MatchingScenariosList(). first_match = MatchingScenarios( @@ -320,18 +313,18 @@ def run_backward_match(self): self.forward_matches, counter, ) - self.matching_list = MatchingScenariosList() self.matching_list.append_scenario(first_match) # Set the circuit indices that can be matched. - gate_indices = [] - for node in self.circuit_dag_dep.op_nodes(): - if (not self.matchedwith.get(node)) and (not self.isblocked.get(node)): - gate_indices.append(node._node_id) + gate_indices = [ + node._node_id + for node in self.circuit_dag_dep.op_nodes() + if (not self.matchedwith.get(node) and not self.isblocked.get(node)) + ] gate_indices.reverse() - number_of_gate_to_match = ( + number_of_gates_to_match = ( self.template_dag_dep.size() - (self.node_t._node_id - 1) - len(self.forward_matches) ) @@ -365,7 +358,7 @@ def run_backward_match(self): # the length of the backward part of the match. if ( counter_scenario > len(gate_indices) - or len(match_backward) == number_of_gate_to_match + or len(match_backward) == number_of_gates_to_match ): matches_scenario.sort(key=lambda x: x[0]) match_store_list.append(Match(matches_scenario, self.qubits, self.clbits)) @@ -390,6 +383,7 @@ def run_backward_match(self): # The candidates in the template. candidates_indices = self._find_backward_candidates(template_blocked, matches_scenario) + # Update of the qubits/clbits indices in the circuit in order to be # comparable with the one in the template. qarg_indices = get_qindices(self.circuit_dag_dep, node_circuit) @@ -399,7 +393,7 @@ def run_backward_match(self): if len(qarg1) != len(qarg_indices): qarg1 = [] - carg1 = [self.clbits.index(c) for q in carg_indices if c in self.clbits] + carg1 = [self.clbits.index(c) for c in carg_indices if c in self.clbits] if len(carg1) != len(carg_indices): carg1 = [] @@ -429,32 +423,22 @@ def run_backward_match(self): and self._is_same_c_conf(node_circuit, node_template, carg1) and node_circuit.op == node_template.op ): - - # If there is a match the attributes are copied. - circuit_matched_match = circuit_matched.copy() - circuit_blocked_match = circuit_blocked.copy() - - template_matched_match = template_matched.copy() - template_blocked_match = template_blocked.copy() - - matches_scenario_match = matches_scenario.copy() - block_list = [] broken_matches_match = [] # Loop to check if the match is not connected, in this case # the descendants matches are blocked and unmatched. for potential_block in get_descendants(self.template_dag_dep, template_id): - if not template_matched_match[potential_block]: - template_blocked_match[potential_block] = True + if not template_matched[potential_block]: + template_blocked[potential_block] = True block_list.append(potential_block) for block_id in block_list: for desc_id in get_descendants(self.template_dag_dep, block_id): - template_blocked_match[desc_id] = True - if template_matched_match[desc_id]: - new_id = template_matched_match[desc_id][0] - circuit_matched_match[new_id] = [] - template_matched_match[desc_id] = [] + template_blocked[desc_id] = True + if template_matched[desc_id]: + new_id = template_matched[desc_id][0] + circuit_matched[new_id] = [] + template_matched[desc_id] = [] broken_matches_match.append(desc_id) if broken_matches_match: @@ -462,33 +446,31 @@ def run_backward_match(self): else: global_broken.append(False) - new_matches_scenario_match = [ - elem - for elem in matches_scenario_match - if elem[0] not in broken_matches_match + new_matches_scenario = [ + elem for elem in matches_scenario if elem[0] not in broken_matches_match ] condition = True for back_match in match_backward: - if back_match not in new_matches_scenario_match: + if back_match not in new_matches_scenario: condition = False break # First option greedy match. - if ( - [self.node_t._node_id, self.node_c._node_id] in new_matches_scenario_match - ) and (condition or not match_backward): - template_matched_match[template_id] = [circuit_id] - circuit_matched_match[circuit_id] = [template_id] - new_matches_scenario_match.append([template_id, circuit_id]) + if ([self.node_t._node_id, self.node_c._node_id] in new_matches_scenario) and ( + condition or not match_backward + ): + template_matched[template_id] = [circuit_id] + circuit_matched[circuit_id] = [template_id] + new_matches_scenario.append([template_id, circuit_id]) new_matching_scenario = MatchingScenarios( - circuit_matched_match, - circuit_blocked_match, - template_matched_match, - template_blocked_match, - new_matches_scenario_match, + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + new_matches_scenario, counter_scenario + 1, ) self.matching_list.append_scenario(new_matching_scenario) @@ -550,16 +532,7 @@ def run_backward_match(self): # Third option: if blocking the descendants breaks a match, we consider # also the possibility to block all ancestors (push the gate to the left). if broken_matches and all(global_broken): - - circuit_matched_block_p = circuit_matched.copy() - circuit_blocked_block_p = circuit_blocked.copy() - - template_matched_block_p = template_matched.copy() - template_blocked_block_p = template_blocked.copy() - - matches_scenario_block_p = matches_scenario.copy() - - circuit_blocked_block_p[circuit_id] = True + circuit_blocked[circuit_id] = True node = get_node(self.circuit_dag_dep, circuit_id) if node not in self.temp_match_class.ancestors: @@ -567,23 +540,21 @@ def run_backward_match(self): self.circuit_dag_dep, circuit_id ) for anc in self.temp_match_class.ancestors[node]: - circuit_blocked_block_p[anc] = True + circuit_blocked[anc] = True matching_scenario = MatchingScenarios( - circuit_matched_block_p, - circuit_blocked_block_p, - template_matched_block_p, - template_blocked_block_p, - matches_scenario_block_p, + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + matches_scenario, counter_scenario + 1, ) self.matching_list.append_scenario(matching_scenario) # If there is no match then there are three options. - if not global_match: - + else: circuit_blocked[circuit_id] = True - following_matches = [] node = get_node(self.circuit_dag_dep, circuit_id) @@ -605,7 +576,6 @@ def run_backward_match(self): ancestors = self.temp_match_class.ancestors[node] if not ancestors or not following_matches: - matching_scenario = MatchingScenarios( circuit_matched, circuit_blocked, @@ -617,15 +587,6 @@ def run_backward_match(self): self.matching_list.append_scenario(matching_scenario) else: - - circuit_matched_nomatch = circuit_matched.copy() - circuit_blocked_nomatch = circuit_blocked.copy() - - template_matched_nomatch = template_matched.copy() - template_blocked_nomatch = template_blocked.copy() - - matches_scenario_nomatch = matches_scenario.copy() - # Second option, all ancestors are blocked (circuit gate is # moved to the left). for pred in ancestors: @@ -654,31 +615,31 @@ def run_backward_match(self): descendants = self.temp_match_class.descendants[node] for desc in descendants: - circuit_blocked_nomatch[desc] = True - if circuit_matched_nomatch[desc]: + circuit_blocked[desc] = True + if circuit_matched[desc]: broken_matches.append(desc) - circuit_matched_nomatch[desc] = [] + circuit_matched[desc] = [] - new_matches_scenario_nomatch = [ - elem for elem in matches_scenario_nomatch if elem[1] not in broken_matches + new_matches_scenario = [ + elem for elem in matches_scenario if elem[1] not in broken_matches ] condition_block = True for back_match in match_backward: - if back_match not in new_matches_scenario_nomatch: + if back_match not in new_matches_scenario: condition_block = False break - if ( - [self.node_t._node_id, self.node_c._node_id] in matches_scenario_nomatch - ) and (condition_block or not match_backward): + if ([self.node_t._node_id, self.node_c._node_id] in matches_scenario) and ( + condition_block or not match_backward + ): new_matching_scenario = MatchingScenarios( - circuit_matched_nomatch, - circuit_blocked_nomatch, - template_matched_nomatch, - template_blocked_nomatch, - new_matches_scenario_nomatch, + circuit_matched, + circuit_blocked, + template_matched, + template_blocked, + new_matches_scenario, counter_scenario + 1, ) self.matching_list.append_scenario(new_matching_scenario) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index 40ef2a6f5024..20896c645509 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -244,7 +244,7 @@ def run_forward_match(self): """ # Initialize certain attributes - for node in self.circuit_dag_dep.topological_nodes(): + for node in self.circuit_dag_dep.op_nodes(): self.successorstovisit[node] = get_successors(self.circuit_dag_dep, node._node_id) self.matchedwith[self.node_c] = [self.node_t._node_id] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 0f0c7e4555a2..6aaa81034c01 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -32,7 +32,8 @@ get_descendants, ) from qiskit.circuit.controlledgate import ControlledGate -from qiskit.transpiler.passes.optimization.template_matching_v2 import ForwardMatch, BackwardMatch +from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch +from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import BackwardMatch class TemplateMatching: @@ -270,17 +271,9 @@ def run_template_matching(self): n_qubits_t = len(self.template_dag_dep.qubits) n_clbits_t = len(self.template_dag_dep.clbits) - # Loop over the indices of both template and circuit. - # for node_id_t in range(0, self.template_dag_dep.size()): - # for node_id_c in range(0, self.circuit_dag_dep.size()): - for ( - node_t - ) in self.template_dag_dep.topological_nodes(): # range(0, self.template_dag_dep.size()): - for ( - node_c - ) in self.circuit_dag_dep.topological_nodes(): # range(0, self.circuit_dag_dep.size()): - # node_t = get_node(self.template_dag_dep, node_id_t) - # node_c = get_node(self.circuit_dag_dep, node_id_c) + # Loop over the nodes of both template and circuit. + for node_t in self.template_dag_dep.op_nodes(): + for node_c in self.circuit_dag_dep.op_nodes(): # Operations match up to ParameterExpressions. if node_c.op.soft_compare(node_t.op): diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index f4929f257d9b..0bbf73894e27 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -26,9 +26,7 @@ get_ancestors, ) from qiskit.circuit import Parameter, ParameterExpression -from qiskit.dagcircuit.dagcircuit import DAGCircuit from qiskit.dagcircuit.dagdependency_v2 import _DAGDependencyV2 -from qiskit.converters.dagdependency_to_dag import dagdependency_to_dag from qiskit.utils import optionals as _optionals @@ -92,8 +90,6 @@ def __init__( self.substitution_list = [] self.unmatched_list = [] - self.dag_dep_optimized = _DAGDependencyV2() - self.dag_optimized = DAGCircuit() if user_cost_dict is not None: self.cost_dict = dict(user_cost_dict) @@ -151,7 +147,7 @@ def _anc_block(self, circuit_sublist, index): for node_id in circuit_sublist: node_c = get_node(self.circuit_dag_dep, node_id) if node_c not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors(self.circuit_dag_dep, node_c) + self.temp_match_class.ancestors[node_c] = get_ancestors(self.circuit_dag_dep, node_c) ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) exclude = set() @@ -342,12 +338,8 @@ def _substitution(self): # Get the first match scenario of the list current = self.match_stack.pop(0) - current_match = current.match - current_qubit = current.qubit - current_clbit = current.clbit - - template_sublist = [x[0] for x in current_match] - circuit_sublist = [x[1] for x in current_match] + template_sublist = [x[0] for x in current.match] + circuit_sublist = [x[1] for x in current.match] circuit_sublist.sort() # Fake bind any parameters in the template @@ -367,9 +359,9 @@ def _substitution(self): circuit_sublist, template_sublist_inverse, [], - current_qubit, + current.qubit, template, - current_clbit, + current.clbit, ) self.substitution_list.append(config) @@ -395,12 +387,14 @@ def _substitution(self): def run_dag_opt(self): """ - Runs the substitution algorithm and creates the optimized DAGCircuit(). + Runs the substitution algorithm and creates the optimized _DAGDependencyV2(). """ self._substitution() - dag_dep_opt = _DAGDependencyV2() + if not self.substitution_list: + return self.circuit_dag_dep + dag_dep_opt = _DAGDependencyV2() dag_dep_opt.name = self.circuit_dag_dep.name qregs = list(self.circuit_dag_dep.qregs.values()) @@ -414,63 +408,55 @@ def run_dag_opt(self): already_sub = [] - if self.substitution_list: - # Loop over the different matches. - for group in self.substitution_list: + # Loop over the different matches. + for group in self.substitution_list: - circuit_sub = group.circuit_config - template_inverse = group.template_config + circuit_sub = group.circuit_config + template_inverse = group.template_config - anc_group = group.anc_block + qubit = group.qubit_config[0] - qubit = group.qubit_config[0] + if group.clbit_config: + clbit = group.clbit_config[0] + else: + clbit = [] - if group.clbit_config: - clbit = group.clbit_config[0] - else: - clbit = [] - - # First add all the ancestors of the given match. - for elem in anc_group: - node_c = get_node(self.circuit_dag_dep, elem) - inst = node_c.op.copy() - dag_dep_opt.apply_operation_back(inst, node_c.qargs, node_c.cargs) - already_sub.append(elem) - - already_sub = already_sub + circuit_sub - - # Then add the inverse of the template. - for index in template_inverse: - all_qubits = self.circuit_dag_dep.qubits - node_t = get_node(group.template_dag_dep, index) - qarg_t = get_qindices(group.template_dag_dep, node_t) - qarg_c = [qubit[x] for x in qarg_t] - qargs = [all_qubits[x] for x in qarg_c] - - all_clbits = self.circuit_dag_dep.clbits - carg_t = get_cindices(group.template_dag_dep, node_t) - - if all_clbits and clbit: - carg_c = [clbit[x] for x in carg_t] - cargs = [all_clbits[x] for x in carg_c] - else: - cargs = [] - node_t = get_node(group.template_dag_dep, index) - inst = node_t.op.copy() - dag_dep_opt.apply_operation_back(inst.inverse(), qargs, cargs) - - # Add the unmatched gates. - for node_id in self.unmatched_list: - node_c = get_node(self.circuit_dag_dep, node_id) + # First add all the ancestors of the given match. + for elem in group.anc_block: + node_c = get_node(self.circuit_dag_dep, elem) inst = node_c.op.copy() dag_dep_opt.apply_operation_back(inst, node_c.qargs, node_c.cargs) + already_sub.append(elem) - # If there is no valid match, it returns the original dag. - else: - dag_dep_opt = self.circuit_dag_dep + already_sub = already_sub + circuit_sub + + # Then add the inverse of the template. + for index in template_inverse: + all_qubits = self.circuit_dag_dep.qubits + node_t = get_node(group.template_dag_dep, index) + qarg_t = get_qindices(group.template_dag_dep, node_t) + qarg_c = [qubit[x] for x in qarg_t] + qargs = [all_qubits[x] for x in qarg_c] + + all_clbits = self.circuit_dag_dep.clbits + carg_t = get_cindices(group.template_dag_dep, node_t) + + if all_clbits and clbit: + carg_c = [clbit[x] for x in carg_t] + cargs = [all_clbits[x] for x in carg_c] + else: + cargs = [] + node_t = get_node(group.template_dag_dep, index) + inst = node_t.op.copy() + dag_dep_opt.apply_operation_back(inst.inverse(), qargs, cargs) + + # Add the unmatched gates. + for node_id in self.unmatched_list: + node_c = get_node(self.circuit_dag_dep, node_id) + inst = node_c.op.copy() + dag_dep_opt.apply_operation_back(inst, node_c.qargs, node_c.cargs) - self.dag_dep_optimized = dag_dep_opt - self.dag_optimized = dagdependency_to_dag(dag_dep_opt) + return dag_dep_opt def _attempt_bind(self, template_sublist, circuit_sublist): """ diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index 3031ca0ccd30..e3e15226add0 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -28,28 +28,35 @@ def get_node(dag, node_id): + """ Wrapper for rustworkx get node object from index. """ return dag._multi_graph[node_id] def get_qindices(dag, node): + """ Convert qargs to indices. """ return [dag.find_bit(qarg).index for qarg in node.qargs] def get_cindices(dag, node): + """ Convert cargs to indices. """ return [dag.find_bit(carg).index for carg in node.cargs] def get_descendants(dag, node_id): + """ Wrapper for rustworkx get all descendants of a node. """ return list(rx.descendants(dag._multi_graph, node_id)) def get_ancestors(dag, node_id): + """ Wrapper for rustworkx get all ancestors of a node. """ return list(rx.ancestors(dag._multi_graph, node_id)) def get_successors(dag, node_id): + """ Wrapper for rustworkx get all direct successors of a node. """ return [succ._node_id for succ in dag._multi_graph.successors(node_id)] def get_predecessors(dag, node_id): + """ Wrapper for rustworkx get all direct predecessors of a node. """ return [pred._node_id for pred in dag._multi_graph.predecessors(node_id)] diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index 8213f96c48b1..057d15bcc034 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -23,7 +23,6 @@ import numpy as np from qiskit.circuit.quantumcircuit import QuantumCircuit -from qiskit.dagcircuit import DAGDependency from qiskit.dagcircuit.dagdependency_v2 import _DAGDependencyV2 from qiskit.converters.circuit_to_dagdependency_v2 import _circuit_to_dagdependency_v2 from qiskit.converters.dagdependency_to_circuit import dagdependency_to_circuit @@ -150,10 +149,8 @@ def run(self, dag): template_m, self.user_cost_dict, ) - substitution.run_dag_opt() - - circuit_dag_dep = substitution.dag_dep_optimized + circuit_dag_dep = substitution.run_dag_opt() else: continue - circuit_dag = dagdependency_to_dag(circuit_dag_dep) - return circuit_dag + + return dagdependency_to_dag(circuit_dag_dep) From c2779dcb8c10a1f35107317e3e0ce5aaa3b728f3 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 3 May 2024 09:01:42 -0700 Subject: [PATCH 12/18] Even more efficiencies and cleanup --- .../template_matching_v2/backward_match_v2.py | 67 ++++---- .../template_matching_v2/forward_match_v2.py | 145 +++++++----------- .../template_matching_v2.py | 54 ++++--- .../template_substitution_v2.py | 4 +- .../template_matching_v2/template_utils_v2.py | 14 +- .../optimization/template_optimization_v2.py | 24 ++- 6 files changed, 130 insertions(+), 178 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index aa04f642848b..a9fb74970b62 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -38,7 +38,7 @@ class Match: """ - Object to represent a match and its qubit configurations. + Object to represent a match and its qubit and clbit configurations. """ def __init__(self, match, qubit, clbit): @@ -121,17 +121,17 @@ def __init__( matchedwith, isblocked, qubits, - clbits=None, - heuristics_backward_param=None, + clbits=[], + heuristics_backward_param=[], ): """ Create a BackwardMatch class with necessary arguments. Args: circuit_dag_dep (_DAGDependencyV2): circuit in the dag dependency form. template_dag_dep (_DAGDependencyV2): template in the dag dependency form. - forward_matches (list): list of match obtained in the forward direction. + forward_matches (list): list of matches obtained in the forward direction. node_c (DAGOpNode): node of the first gate matched in the circuit. - node_id_t (DAGOpNode): node of the first gate matched in the template. + node_t (DAGOpNode): node of the first gate matched in the template. temp_match_class (TemplateMatchingV2): instance of the calling class matchedwith (dict): per node list of matches isblocked (dict): per node indicator of blocked node @@ -143,14 +143,12 @@ def __init__( self.circuit_dag_dep = circuit_dag_dep self.template_dag_dep = template_dag_dep self.qubits = qubits - self.clbits = clbits if clbits is not None else [] + self.clbits = clbits self.node_c = node_c self.node_t = node_t self.forward_matches = forward_matches self.match_final = [] - self.heuristics_backward_param = ( - heuristics_backward_param if heuristics_backward_param is not None else [] - ) + self.heuristics_backward_param = heuristics_backward_param self.matching_list = MatchingScenariosList() self.temp_match_class = temp_match_class self.matchedwith = matchedwith @@ -189,20 +187,20 @@ def _find_backward_candidates(self, template_blocked, matches): return candidates_indices - def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): + def _is_same_q_conf(self, node_c, node_t, qarg_circuit): """ Check if the qubit configurations are compatible. Args: - node_circuit (DAGOpNode): node in the circuit. - node_template (DAGOpNode): node in the template. + node_c (DAGOpNode): node in the circuit. + node_t (DAGOpNode): node in the template. qarg_circuit (list): qubit configuration for the Instruction in the circuit. Returns: bool: True if possible, False otherwise. """ # If the gate is controlled, then the control qubits have to be compared as sets. - node_temp_qind = get_qindices(self.template_dag_dep, node_template) - if isinstance(node_circuit.op, ControlledGate): - c_template = node_template.op.num_ctrl_qubits + node_temp_qind = get_qindices(self.template_dag_dep, node_t) + if isinstance(node_c.op, ControlledGate): + c_template = node_t.op.num_ctrl_qubits if c_template == 1: return qarg_circuit == node_temp_qind @@ -215,7 +213,7 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): target_qubits_template = node_temp_qind[c_template::] target_qubits_circuit = qarg_circuit[c_template::] - if node_template.op.base_gate.name in [ + if node_t.op.base_gate.name in [ "rxx", "ryy", "rzz", @@ -231,31 +229,26 @@ def _is_same_q_conf(self, node_circuit, node_template, qarg_circuit): # For non controlled gates, the qubit indices for symmetric gates can be compared as sets # But for non-symmetric gates the qubits indices have to be compared as lists. else: - if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: + if node_t.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: return set(qarg_circuit) == set(node_temp_qind) else: return qarg_circuit == node_temp_qind - def _is_same_c_conf(self, node_circuit, node_template, carg_circuit): + def _is_same_c_conf(self, node_c, node_t, carg_circuit): """ Check if the clbit configurations are compatible. Args: - node_circuit (DAGOpNode): node in the circuit. - node_template (DAGOpNode): node in the template. + node_c (DAGOpNode): node in the circuit. + node_t (DAGOpNode): node in the template. carg_circuit (list): clbit configuration for the Instruction in the circuit. Returns: bool: True if possible, False otherwise. """ - if getattr(node_circuit.op, "condition", None) and getattr( - node_template.op, "condition", None + if (getattr(node_c.op, "condition", None) and getattr(node_t.op, "condition", None)) and ( + getattr(node_c.op, "condition", None)[1] != getattr(node_t.op, "condition", None)[1] + or set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_t)) ): - if set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_template)): - return False - if ( - getattr(node_circuit.op, "condition", None)[1] - != getattr(node_template.op, "condition", None)[1] - ): - return False + return False return True def _backward_heuristics(self, gate_indices, length, survivor): @@ -366,7 +359,7 @@ def run_backward_match(self): # First circuit candidate. circuit_id = gate_indices[counter_scenario - 1] - node_circuit = get_node(self.circuit_dag_dep, circuit_id) + node_c = get_node(self.circuit_dag_dep, circuit_id) # If the circuit candidate is blocked, only the counter is changed. if circuit_blocked[circuit_id]: @@ -386,8 +379,8 @@ def run_backward_match(self): # Update of the qubits/clbits indices in the circuit in order to be # comparable with the one in the template. - qarg_indices = get_qindices(self.circuit_dag_dep, node_circuit) - carg_indices = get_cindices(self.circuit_dag_dep, node_circuit) + qarg_indices = get_qindices(self.circuit_dag_dep, node_c) + carg_indices = get_cindices(self.circuit_dag_dep, node_c) qarg1 = [self.qubits.index(q) for q in qarg_indices if q in self.qubits] if len(qarg1) != len(qarg_indices): @@ -403,7 +396,7 @@ def run_backward_match(self): # Loop over the template candidates. for template_id in candidates_indices: - node_template = get_node(self.template_dag_dep, template_id) + node_t = get_node(self.template_dag_dep, template_id) qarg2 = get_qindices( self.template_dag_dep, get_node(self.template_dag_dep, template_id) ) @@ -412,16 +405,16 @@ def run_backward_match(self): if ( len(qarg1) != len(qarg2) or set(qarg1) != set(qarg2) - or node_circuit.name != node_template.name + or node_c.name != node_t.name ): continue # Check if the qubit, clbit configurations are compatible for a match, # also check if the operations are the same. if ( - self._is_same_q_conf(node_circuit, node_template, qarg1) - and self._is_same_c_conf(node_circuit, node_template, carg1) - and node_circuit.op == node_template.op + self._is_same_q_conf(node_c, node_t, qarg1) + and self._is_same_c_conf(node_c, node_t, carg1) + and node_c.op == node_t.op ): block_list = [] broken_matches_match = [] diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index 20896c645509..bd865a7f431e 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -48,7 +48,7 @@ def __init__( node_t, temp_match_class, qubits, - clbits=None, + clbits=[], ): """ Create a ForwardMatch class with necessary arguments. @@ -57,6 +57,7 @@ def __init__( template_dag_dep (DAGDependencyV2): template in the dag dependency form. node_c (DAGOpNode): node of first gate matched in the circuit. node_t (DAGOpNode): node of first gate matched in the template. + temp_match_class (TemplateMatching): class instance of caller qubits (list): list of considered qubits in the circuit. clbits (list): list of considered clbits in the circuit. """ @@ -67,18 +68,21 @@ def __init__( # The dag dependency representation of the template self.template_dag_dep = template_dag_dep - # List of qubits on which the node of the circuit is acting - self.qubits = qubits - - # List of clbits on which the node of the circuit is acting - self.clbits = clbits if clbits is not None else [] - # Id of the node in the circuit self.node_c = node_c # Id of the node in the template self.node_t = node_t + # Class instance of TemplateMatching caller + self.temp_match_class = temp_match_class + + # List of qubits on which the node of the circuit is acting + self.qubits = qubits + + # List of clbits on which the node of the circuit is acting + self.clbits = clbits + # List of matches self.match = [] @@ -94,9 +98,6 @@ def __init__( # Transformation of the carg indices of the circuit to be adapted to the template indices self.carg_indices = [] - # Class instance of TemplateMatching caller - self.temp_match_class = temp_match_class - # Dicts for storing lists of node ids self.successorstovisit = {} self.matchedwith = {} @@ -115,18 +116,18 @@ def _find_forward_candidates(self, node_id_t): for i in range(0, len(self.match)): matches.append(self.match[i][0]) - pred = matches.copy() - if len(pred) > 1: - pred.sort() - pred.remove(node_id_t) + matches_mod = matches.copy() + if len(matches_mod) > 1: + matches_mod.sort() + matches_mod.remove(node_id_t) temp_succs = get_successors(self.template_dag_dep, node_id_t) if temp_succs: maximal_index = temp_succs[-1] - pred = [elem for elem in pred if elem <= maximal_index] + matches_mod = [elem for elem in matches_mod if elem <= maximal_index] block = [] - for node_id in pred: + for node_id in matches_mod: for succ in get_successors(self.template_dag_dep, node_id): if succ not in matches: node = get_node(self.template_dag_dep, succ) @@ -137,49 +138,20 @@ def _find_forward_candidates(self, node_id_t): block = block + self.temp_match_class.descendants[node] self.candidates = list(set(temp_succs) - set(matches) - set(block)) - def _update_qarg_indices(self, qarg): - """ - Change qubit indices of the current circuit node in order to - be comparable with the indices of the template qubits list. - Args: - qarg (list): list of qubit indices from the circuit for a given node. - """ - self.qarg_indices = [] - for q in qarg: - if q in self.qubits: - self.qarg_indices.append(self.qubits.index(q)) - if len(qarg) != len(self.qarg_indices): - self.qarg_indices = [] - - def _update_carg_indices(self, carg): - """ - Change clbit indices of the current circuit node in order to - be comparable with the indices of the template qubit list. - Args: - carg (list): list of clbit indices from the circuit for a given node. - """ - self.carg_indices = [] - if carg: - for q in carg: - if q in self.clbits: - self.carg_indices.append(self.clbits.index(q)) - if len(carg) != len(self.carg_indices): - self.carg_indices = [] - - def _is_same_q_conf(self, node_circuit, node_template): + def _is_same_q_conf(self, node_c, node_t): """ Check if the qubit configurations are compatible. Args: - node_circuit (DAGOpNode): node in the circuit. - node_template (DAGOpNode): node in the template. + node_c (DAGOpNode): node in the circuit. + node_t (DAGOpNode): node in the template. Returns: bool: True if possible, False otherwise. """ - node_temp_qind = get_qindices(self.template_dag_dep, node_template) - if isinstance(node_circuit.op, ControlledGate): + node_temp_qind = get_qindices(self.template_dag_dep, node_t) + if isinstance(node_c.op, ControlledGate): - c_template = node_template.op.num_ctrl_qubits + c_template = node_t.op.num_ctrl_qubits if c_template == 1: return self.qarg_indices == node_temp_qind @@ -193,7 +165,7 @@ def _is_same_q_conf(self, node_circuit, node_template): target_qubits_template = node_temp_qind[c_template::] target_qubits_circuit = self.qarg_indices[c_template::] - if node_template.op.base_gate.name in [ + if node_t.op.base_gate.name in [ "rxx", "ryy", "rzz", @@ -207,34 +179,25 @@ def _is_same_q_conf(self, node_circuit, node_template): else: return False else: - if node_template.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: - return set(self.qarg_indices) == set( - get_qindices(self.template_dag_dep, node_template) - ) + if node_t.op.name in ["rxx", "ryy", "rzz", "swap", "iswap", "ms"]: + return set(self.qarg_indices) == set(get_qindices(self.template_dag_dep, node_t)) else: return self.qarg_indices == node_temp_qind - def _is_same_c_conf(self, node_circuit, node_template): + def _is_same_c_conf(self, node_c, node_t): """ Check if the clbit configurations are compatible. Args: - node_circuit (DAGOpNode): node in the circuit. - node_template (DAGOpNode): node in the template. + node_c (DAGOpNode): node in the circuit. + node_t (DAGOpNode): node in the template. Returns: bool: True if possible, False otherwise. """ - if ( - getattr(node_circuit.op, "condition", None) - and node_template.type == "op" - and getattr(node_template.op, "condition", None) + if (getattr(node_c.op, "condition", None) and getattr(node_t.op, "condition", None)) and ( + getattr(node_c.op, "condition", None)[1] != getattr(node_t.op, "condition", None)[1] + or set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_t)) ): - if set(self.carg_indices) != set(node_template.cindices): - return False - if ( - getattr(node_circuit.op, "condition", None)[1] - != getattr(node_template.op, "condition", None)[1] - ): - return False + return False return True def run_forward_match(self): @@ -276,52 +239,52 @@ def run_forward_match(self): # Search for potential candidates in the template self._find_forward_candidates(self.matchedwith[first_matched][0]) - qarg1 = get_qindices( - self.circuit_dag_dep, get_node(self.circuit_dag_dep, trial_successor_id) - ) - carg1 = get_cindices( - self.circuit_dag_dep, get_node(self.circuit_dag_dep, trial_successor_id) - ) + qarg1 = get_qindices(self.circuit_dag_dep, trial_successor) + carg1 = get_cindices(self.circuit_dag_dep, trial_successor) # Update the indices for both qubits and clbits in order to be comparable with the # indices in the template circuit. - self._update_qarg_indices(qarg1) - self._update_carg_indices(carg1) + self.qarg_indices = [self.qubits.index(q) for q in qarg1 if q in self.qubits] + if len(qarg1) != len(self.qarg_indices): + self.qarg_indices = [] + + self.carg_indices = [self.clbits.index(c) for c in carg1 if c in self.clbits] + if len(carg1) != len(self.carg_indices): + self.carg_indices = [] match = False # For loop over the candidates (template) to find a match. - for i in self.candidates: + for candidate_id in self.candidates: # Break the for loop if a match is found. if match: break - node_circuit = get_node(self.circuit_dag_dep, trial_successor_id) - node_template = get_node(self.template_dag_dep, i) + candidate = get_node(self.template_dag_dep, candidate_id) # Compare the indices of qubits and the operation name. # Necessary but not sufficient conditions for a match to happen. - node_temp_qind = get_qindices(self.template_dag_dep, node_template) + node_temp_qind = get_qindices(self.template_dag_dep, candidate) if ( len(self.qarg_indices) != len(node_temp_qind) or set(self.qarg_indices) != set(node_temp_qind) - or node_circuit.name != node_template.name + or trial_successor.name != candidate.name ): continue - # Check if the qubit, clbit configurations are compatible for a match, - # also check if the operations are the same. + # Check if the qubit and clbit configurations are compatible for a match, + # and check if the operations are the same. if ( - self._is_same_q_conf(node_circuit, node_template) - and self._is_same_c_conf(node_circuit, node_template) - and node_circuit.op.soft_compare(node_template.op) + self._is_same_q_conf(trial_successor, candidate) + and self._is_same_c_conf(trial_successor, candidate) + and trial_successor.op.soft_compare(candidate.op) ): - self.matchedwith[trial_successor] = [i] - self.matchedwith[node_template] = [trial_successor_id] + self.matchedwith[trial_successor] = [candidate_id] + self.matchedwith[candidate] = [trial_successor_id] # Append the new match to the list of matches. - self.match.append([i, trial_successor_id]) + self.match.append([candidate_id, trial_successor_id]) # Potential successors to visit (circuit) for a given match. potential = get_successors(self.circuit_dag_dep, trial_successor_id) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 6aaa81034c01..3b611c1250a6 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -33,7 +33,9 @@ ) from qiskit.circuit.controlledgate import ControlledGate from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch -from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import BackwardMatch +from qiskit.transpiler.passes.optimization.template_matching_v2.backward_match_v2 import ( + BackwardMatch, +) class TemplateMatching: @@ -45,26 +47,22 @@ def __init__( self, circuit_dag_dep, template_dag_dep, - heuristics_qubits_param=None, - heuristics_backward_param=None, + heuristics_qubits_param=[], + heuristics_backward_param=[], ): """ Create a TemplateMatching object with necessary arguments. Args: circuit_dag_dep (DAGDependencyV2): circuit dag. template_dag_dep (DAGDependencyV2): template dag. - heuristics_backward_param (list[int]): [length, survivor] heuristics_qubits_param (list[int]): [length] + heuristics_backward_param (list[int]): [length, survivor] """ self.circuit_dag_dep = circuit_dag_dep self.template_dag_dep = template_dag_dep self.match_list = [] - self.heuristics_qubits_param = ( - heuristics_qubits_param if heuristics_qubits_param is not None else [] - ) - self.heuristics_backward_param = ( - heuristics_backward_param if heuristics_backward_param is not None else [] - ) + self.heuristics_qubits_param = heuristics_qubits_param + self.heuristics_backward_param = heuristics_backward_param self.descendants = {} self.ancestors = {} @@ -75,8 +73,8 @@ def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbit Args: node_circuit (DAGOpNode): First match node in the circuit. node_template (DAGOpNode): First match node in the template. - n_qubits_t (int): number of qubit in the template. - n_clbits_t (int): number of classical bit in the template. + n_qubits_t (int): number of qubits in the template. + n_clbits_t (int): number of classical bits in the template. Returns: list: list of qubits to consider in circuit (with specific order). """ @@ -153,7 +151,7 @@ def _sublist(self, qubit_id_list, exclude, length): Args: qubit_id_list (list): list of qubits indices from the circuit. exclude (list): list of qubits from the first matched circuit gate. - length (int): length of the list to be returned (number of template qubits - + length (int): length of the list to be returned (number of template qubits minus number of qubits from the first matched template gate). Yield: iterator: Iterator of the possible lists. @@ -215,22 +213,22 @@ def _explore_circuit(self, node_c, node_t, n_qubits_t, length): node_c (DAGOpNode): first match in the circuit. node_t (DAGOpNode): first match in the template. n_qubits_t (int): number of qubits in the template. - length (int): length for exploration of the successors. + length (int): length for exploration of the descendants. Returns: list: qubit configuration for the 'length' descendants of node_c. """ template_nodes = range(node_t._node_id + 1, self.template_dag_dep.size()) circuit_nodes = range(0, self.circuit_dag_dep.size()) if node_t not in self.descendants: - self.descendants[node_t] = get_descendants(self.template_dag_dep, node_t) + self.descendants[node_t] = get_descendants(self.template_dag_dep, node_t._node_id) if node_c not in self.descendants: - self.descendants[node_c] = get_descendants(self.circuit_dag_dep, node_c) + self.descendants[node_c] = get_descendants(self.circuit_dag_dep, node_c._node_id) counter = 1 qubit_set = set(get_qindices(self.circuit_dag_dep, node_c)) if 2 * len(self.descendants[node_t]) > len(template_nodes): - for succ in self.descendants[node_c]: - qarg = get_qindices(get_node(self.circuit_dag_dep, succ)) + for desc in self.descendants[node_c]: + qarg = get_qindices(get_node(self.circuit_dag_dep, desc)) if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: qubit_set = qubit_set | set(qarg) counter += 1 @@ -239,14 +237,14 @@ def _explore_circuit(self, node_c, node_t, n_qubits_t, length): return list(qubit_set) else: - not_successors = list(set(circuit_nodes) - set(self.descendants[node_c])) + not_descendants = list(set(circuit_nodes) - set(self.descendants[node_c])) candidate = [ - not_successors[j] - for j in range(len(not_successors) - 1, len(not_successors) - 1 - length, -1) + not_descendants[j] + for j in range(len(not_descendants) - 1, len(not_descendants) - 1 - length, -1) ] - for not_succ in candidate: - qarg = get_qindices(get_node(self.circuit_dag_dep, not_succ)) + for not_desc in candidate: + qarg = get_qindices(self.circuit_dag_dep, get_node(self.circuit_dag_dep, not_desc)) if counter <= length and (len(qubit_set | set(qarg))) <= n_qubits_t: qubit_set = qubit_set | set(qarg) counter += 1 @@ -290,8 +288,8 @@ def run_template_matching(self): n_qubits_t, n_clbits_t, ) - list_circuit_q = list(range(0, n_qubits_c)) - list_circuit_c = list(range(0, n_clbits_c)) + list_qubits_c = list(range(0, n_qubits_c)) + list_clbits_c = list(range(0, n_clbits_c)) # If the parameter for qubit heuristics is given then extract # the list of qubits for the descendants (length(int)) in the circuit. @@ -303,7 +301,7 @@ def run_template_matching(self): else: heuristics_qubits = [] - for sub_q in self._sublist(list_circuit_q, qarg_c, n_qubits_t - len(qarg_t)): + for sub_q in self._sublist(list_qubits_c, qarg_c, n_qubits_t - len(qarg_t)): # If the heuristics qubits are a subset of the given qubit configuration, # then this configuration is accepted. if set(heuristics_qubits).issubset(set(sub_q) | set(qarg_c)): @@ -316,9 +314,9 @@ def run_template_matching(self): ) # Check for clbit configurations if there are clbits. - if list_circuit_c: + if list_clbits_c: for sub_c in self._sublist( - list_circuit_c, carg_c, n_clbits_t - len(carg_t) + list_clbits_c, carg_c, n_clbits_t - len(carg_t) ): for perm_c in itertools.permutations(sub_c): perm_c = list(perm_c) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 0bbf73894e27..8000b56c114c 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -147,7 +147,9 @@ def _anc_block(self, circuit_sublist, index): for node_id in circuit_sublist: node_c = get_node(self.circuit_dag_dep, node_id) if node_c not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node_c] = get_ancestors(self.circuit_dag_dep, node_c) + self.temp_match_class.ancestors[node_c] = get_ancestors( + self.circuit_dag_dep, node_c + ) ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) exclude = set() diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index e3e15226add0..454d8f1b77e6 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -28,35 +28,35 @@ def get_node(dag, node_id): - """ Wrapper for rustworkx get node object from index. """ + """Wrapper for rustworkx get node object from index.""" return dag._multi_graph[node_id] def get_qindices(dag, node): - """ Convert qargs to indices. """ + """Convert qargs to indices.""" return [dag.find_bit(qarg).index for qarg in node.qargs] def get_cindices(dag, node): - """ Convert cargs to indices. """ + """Convert cargs to indices.""" return [dag.find_bit(carg).index for carg in node.cargs] def get_descendants(dag, node_id): - """ Wrapper for rustworkx get all descendants of a node. """ + """Wrapper for rustworkx get all descendants of a node.""" return list(rx.descendants(dag._multi_graph, node_id)) def get_ancestors(dag, node_id): - """ Wrapper for rustworkx get all ancestors of a node. """ + """Wrapper for rustworkx get all ancestors of a node.""" return list(rx.ancestors(dag._multi_graph, node_id)) def get_successors(dag, node_id): - """ Wrapper for rustworkx get all direct successors of a node. """ + """Wrapper for rustworkx get all direct successors of a node.""" return [succ._node_id for succ in dag._multi_graph.successors(node_id)] def get_predecessors(dag, node_id): - """ Wrapper for rustworkx get all direct predecessors of a node. """ + """Wrapper for rustworkx get all direct predecessors of a node.""" return [pred._node_id for pred in dag._multi_graph.predecessors(node_id)] diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index 057d15bcc034..4b905828bf71 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -47,13 +47,19 @@ class TemplateOptimizationV2(TransformationPass): def __init__( self, template_list=None, - heuristics_qubits_param=None, - heuristics_backward_param=None, + heuristics_qubits_param=[], + heuristics_backward_param=[], user_cost_dict=None, ): """ Args: template_list (list[QuantumCircuit()]): list of the different template circuits to apply. + heuristics_qubits_param (list[int]): [length] The heuristics for the qubit choice make + guesses from the dag dependency of the circuit in order to limit the number of + qubit configurations to explore. The length is the number of descendants or not + ancestors that will be explored in the dag dependency of the circuit. Each of the + qubits of the nodes are added to the set of authorized qubits. We advise to use + length=1. Check reference for more details. heuristics_backward_param (list[int]): [length, survivor] Those are the parameters for applying heuristics on the backward part of the algorithm. This part of the algorithm creates a tree of matching scenario. This tree grows exponentially. The @@ -62,12 +68,6 @@ def __init__( of scenarios that are kept. We advise to use l=3 and s=1 to have serious time advantage. We remind that the heuristics implies losing a part of the maximal matches. Check reference for more details. - heuristics_qubits_param (list[int]): [length] The heuristics for the qubit choice make - guesses from the dag dependency of the circuit in order to limit the number of - qubit configurations to explore. The length is the number of descendants or not - ancestors that will be explored in the dag dependency of the circuit. Each of the - qubits of the nodes are added to the set of authorized qubits. We advise to use - length=1. Check reference for more details. user_cost_dict (Dict[str, int]): quantum cost dictionary passed to TemplateSubstitution to configure its behavior. This will override any default values if None is not given. The key is the name of the gate and the value its quantum cost. @@ -77,12 +77,8 @@ def __init__( if template_list is None: template_list = [template_nct_2a_1(), template_nct_2a_2(), template_nct_2a_3()] self.template_list = template_list - self.heuristics_qubits_param = ( - heuristics_qubits_param if heuristics_qubits_param is not None else [] - ) - self.heuristics_backward_param = ( - heuristics_backward_param if heuristics_backward_param is not None else [] - ) + self.heuristics_qubits_param = heuristics_qubits_param + self.heuristics_backward_param = heuristics_backward_param self.user_cost_dict = user_cost_dict def run(self, dag): From 0aacd896890c971836398611a23076c134d2e010 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 4 May 2024 11:03:22 -0700 Subject: [PATCH 13/18] Implement lru_cache and testing --- .../template_matching_v2/backward_match_v2.py | 61 +++----------- .../template_matching_v2/forward_match_v2.py | 21 +---- .../template_matching_v2.py | 18 ++-- .../template_substitution_v2.py | 82 ++++++------------- .../template_matching_v2/template_utils_v2.py | 8 +- .../optimization/template_optimization_v2.py | 1 - 6 files changed, 48 insertions(+), 143 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index a9fb74970b62..fe0762330fcd 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -117,7 +117,6 @@ def __init__( forward_matches, node_c, node_t, - temp_match_class, matchedwith, isblocked, qubits, @@ -132,7 +131,6 @@ def __init__( forward_matches (list): list of matches obtained in the forward direction. node_c (DAGOpNode): node of the first gate matched in the circuit. node_t (DAGOpNode): node of the first gate matched in the template. - temp_match_class (TemplateMatchingV2): instance of the calling class matchedwith (dict): per node list of matches isblocked (dict): per node indicator of blocked node qubits (list): list of considered qubits in the circuit. @@ -150,7 +148,6 @@ def __init__( self.match_final = [] self.heuristics_backward_param = heuristics_backward_param self.matching_list = MatchingScenariosList() - self.temp_match_class = temp_match_class self.matchedwith = matchedwith self.isblocked = isblocked @@ -171,14 +168,10 @@ def _find_backward_candidates(self, template_blocked, matches): matches_template = sorted(match[0] for match in matches) - if self.node_t not in self.temp_match_class.descendants: - self.temp_match_class.descendants[self.node_t] = get_descendants( - self.template_dag_dep, self.node_t._node_id - ) - descendants = self.temp_match_class.descendants[self.node_t] + node_t_descs = get_descendants(self.template_dag_dep, self.node_t._node_id) potential = [] for index in range(self.node_t._node_id + 1, self.template_dag_dep.size()): - if (index not in descendants) and (index not in template_block): + if (index not in node_t_descs) and (index not in template_block): potential.append(index) candidates_indices = list(set(potential) - set(matches_template)) @@ -271,7 +264,7 @@ def _backward_heuristics(self, gate_indices, length, survivor): gate_indices ): if (list_counter[0] - 1) % length == 0: - # The list metrics contains metric results for each scenarios. + # The list metrics contains metric results for each scenario. for scenario in self.matching_list.matching_scenarios_list: metrics.append(len(scenario.matches)) # Select only the scenarios with higher metrics for the given number of survivors. @@ -288,7 +281,6 @@ def run_backward_match(self): and a circuit qubits configuration. """ match_store_list = [] - counter = 1 # Initialize the list of attributes matchedwith and isblocked. @@ -478,19 +470,12 @@ def run_backward_match(self): template_blocked_block_s = template_blocked.copy() matches_scenario_block_s = matches_scenario.copy() - circuit_blocked_block_s[circuit_id] = True - broken_matches = [] # Second option, not a greedy match, block all descendants (push the gate # to the right). - node = get_node(self.circuit_dag_dep, circuit_id) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( - self.circuit_dag_dep, circuit_id - ) - for desc in self.temp_match_class.descendants[node]: + for desc in get_descendants(self.circuit_dag_dep, circuit_id): circuit_blocked_block_s[desc] = True if circuit_matched_block_s[desc]: broken_matches.append(desc) @@ -501,7 +486,6 @@ def run_backward_match(self): new_matches_scenario_block_s = [ elem for elem in matches_scenario_block_s if elem[1] not in broken_matches ] - condition_not_greedy = True for back_match in match_backward: @@ -526,13 +510,7 @@ def run_backward_match(self): # also the possibility to block all ancestors (push the gate to the left). if broken_matches and all(global_broken): circuit_blocked[circuit_id] = True - - node = get_node(self.circuit_dag_dep, circuit_id) - if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors( - self.circuit_dag_dep, circuit_id - ) - for anc in self.temp_match_class.ancestors[node]: + for anc in get_ancestors(self.circuit_dag_dep, circuit_id): circuit_blocked[anc] = True matching_scenario = MatchingScenarios( @@ -549,26 +527,15 @@ def run_backward_match(self): else: circuit_blocked[circuit_id] = True following_matches = [] - - node = get_node(self.circuit_dag_dep, circuit_id) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( - self.circuit_dag_dep, circuit_id - ) - for desc in self.temp_match_class.descendants[node]: + for desc in get_descendants(self.circuit_dag_dep, circuit_id): if circuit_matched[desc]: following_matches.append(desc) # First option, the circuit gate is not disturbing because there are no # following match and no ancestors. - node = get_node(self.circuit_dag_dep, circuit_id) - if node not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node] = get_ancestors( - self.circuit_dag_dep, circuit_id - ) - ancestors = self.temp_match_class.ancestors[node] + node_c_ancs = get_ancestors(self.circuit_dag_dep, circuit_id) - if not ancestors or not following_matches: + if not node_c_ancs or not following_matches: matching_scenario = MatchingScenarios( circuit_matched, circuit_blocked, @@ -582,7 +549,7 @@ def run_backward_match(self): else: # Second option, all ancestors are blocked (circuit gate is # moved to the left). - for pred in ancestors: + for pred in node_c_ancs: circuit_blocked[pred] = True matching_scenario = MatchingScenarios( @@ -600,14 +567,7 @@ def run_backward_match(self): broken_matches = [] - node = get_node(self.circuit_dag_dep, circuit_id) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( - self.circuit_dag_dep, circuit_id - ) - descendants = self.temp_match_class.descendants[node] - - for desc in descendants: + for desc in get_descendants(self.circuit_dag_dep, circuit_id): circuit_blocked[desc] = True if circuit_matched[desc]: broken_matches.append(desc) @@ -616,7 +576,6 @@ def run_backward_match(self): new_matches_scenario = [ elem for elem in matches_scenario if elem[1] not in broken_matches ] - condition_block = True for back_match in match_backward: diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index bd865a7f431e..7001ad150c69 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -46,7 +46,6 @@ def __init__( template_dag_dep, node_c, node_t, - temp_match_class, qubits, clbits=[], ): @@ -57,7 +56,6 @@ def __init__( template_dag_dep (DAGDependencyV2): template in the dag dependency form. node_c (DAGOpNode): node of first gate matched in the circuit. node_t (DAGOpNode): node of first gate matched in the template. - temp_match_class (TemplateMatching): class instance of caller qubits (list): list of considered qubits in the circuit. clbits (list): list of considered clbits in the circuit. """ @@ -74,9 +72,6 @@ def __init__( # Id of the node in the template self.node_t = node_t - # Class instance of TemplateMatching caller - self.temp_match_class = temp_match_class - # List of qubits on which the node of the circuit is acting self.qubits = qubits @@ -130,12 +125,8 @@ def _find_forward_candidates(self, node_id_t): for node_id in matches_mod: for succ in get_successors(self.template_dag_dep, node_id): if succ not in matches: - node = get_node(self.template_dag_dep, succ) - if node not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node] = get_descendants( - self.template_dag_dep, succ - ) - block = block + self.temp_match_class.descendants[node] + node_t_descs = get_descendants(self.template_dag_dep, succ) + block = block + list(node_t_descs) self.candidates = list(set(temp_succs) - set(matches) - set(block)) def _is_same_q_conf(self, node_c, node_t): @@ -310,12 +301,8 @@ def run_forward_match(self): # If no match is found, block the node and all the descendants. if not match: self.isblocked[trial_successor] = True - if trial_successor not in self.temp_match_class.descendants: - self.temp_match_class.descendants[trial_successor] = get_descendants( - self.circuit_dag_dep, trial_successor_id - ) - - for desc_id in self.temp_match_class.descendants[trial_successor]: + trial_descs = get_descendants(self.circuit_dag_dep, trial_successor_id) + for desc_id in trial_descs: desc = get_node(self.circuit_dag_dep, desc_id) self.isblocked[desc] = True if self.matchedwith.get(desc): diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 3b611c1250a6..6966de9110cf 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -63,8 +63,6 @@ def __init__( self.match_list = [] self.heuristics_qubits_param = heuristics_qubits_param self.heuristics_backward_param = heuristics_backward_param - self.descendants = {} - self.ancestors = {} def _list_first_match_new(self, node_circuit, node_template, n_qubits_t, n_clbits_t): """ @@ -219,15 +217,13 @@ def _explore_circuit(self, node_c, node_t, n_qubits_t, length): """ template_nodes = range(node_t._node_id + 1, self.template_dag_dep.size()) circuit_nodes = range(0, self.circuit_dag_dep.size()) - if node_t not in self.descendants: - self.descendants[node_t] = get_descendants(self.template_dag_dep, node_t._node_id) - if node_c not in self.descendants: - self.descendants[node_c] = get_descendants(self.circuit_dag_dep, node_c._node_id) + node_t_descs = get_descendants(self.template_dag_dep, node_t._node_id) + node_c_descs = get_descendants(self.circuit_dag_dep, node_c._node_id) counter = 1 qubit_set = set(get_qindices(self.circuit_dag_dep, node_c)) - if 2 * len(self.descendants[node_t]) > len(template_nodes): - for desc in self.descendants[node_c]: + if 2 * len(node_t_descs) > len(template_nodes): + for desc in node_c_descs: qarg = get_qindices(get_node(self.circuit_dag_dep, desc)) if (len(qubit_set | set(qarg))) <= n_qubits_t and counter <= length: qubit_set = qubit_set | set(qarg) @@ -237,7 +233,7 @@ def _explore_circuit(self, node_c, node_t, n_qubits_t, length): return list(qubit_set) else: - not_descendants = list(set(circuit_nodes) - set(self.descendants[node_c])) + not_descendants = list(set(circuit_nodes) - set(node_c_descs)) candidate = [ not_descendants[j] for j in range(len(not_descendants) - 1, len(not_descendants) - 1 - length, -1) @@ -331,7 +327,6 @@ def run_template_matching(self): self.template_dag_dep, node_c, node_t, - self, list_qubit_circuit, list_clbit_circuit, ) @@ -344,7 +339,6 @@ def run_template_matching(self): forward.match, node_c, node_t, - self, list_qubit_circuit, list_clbit_circuit, self.heuristics_backward_param, @@ -361,7 +355,6 @@ def run_template_matching(self): self.template_dag_dep, node_c, node_t, - self, list_qubit_circuit, ) matchedwith, isblocked = forward.run_forward_match() @@ -373,7 +366,6 @@ def run_template_matching(self): forward.match, node_c, node_t, - self, matchedwith, isblocked, list_qubit_circuit, diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index 8000b56c114c..f3eec17f4265 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -44,14 +44,14 @@ def __init__( anc_block, qubit_config, template_dag_dep, - clbit_config=None, + clbit_config=[], ): - self.template_dag_dep = template_dag_dep self.circuit_config = circuit_config self.template_config = template_config - self.qubit_config = qubit_config - self.clbit_config = clbit_config if clbit_config is not None else [] self.anc_block = anc_block + self.qubit_config = qubit_config + self.template_dag_dep = template_dag_dep + self.clbit_config = clbit_config def has_parameters(self): """Ensure that the template does not have parameters.""" @@ -69,7 +69,7 @@ class TemplateSubstitution: """ def __init__( - self, max_matches, circuit_dag_dep, template_dag_dep, temp_match_class, user_cost_dict=None + self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dict=None ): """ Initialize TemplateSubstitution with necessary arguments. @@ -78,7 +78,6 @@ def __init__( the template matching algorithm. circuit_dag_dep (_DAGDependencyV2): circuit in the dag dependency form. template_dag_dep (_DAGDependencyV2): template in the dag dependency form. - temp_match_class: (TemplateMatching): class instance of previous TemplateMatching user_cost_dict (Optional[dict]): user provided cost dictionary that will override the default cost dictionary. """ @@ -86,8 +85,6 @@ def __init__( self.match_stack = max_matches self.circuit_dag_dep = circuit_dag_dep self.template_dag_dep = template_dag_dep - self.temp_match_class = temp_match_class - self.substitution_list = [] self.unmatched_list = [] @@ -145,12 +142,7 @@ def _anc_block(self, circuit_sublist, index): """ ancestors = set() for node_id in circuit_sublist: - node_c = get_node(self.circuit_dag_dep, node_id) - if node_c not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node_c] = get_ancestors( - self.circuit_dag_dep, node_c - ) - ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) + ancestors = ancestors | set(get_ancestors(self.circuit_dag_dep, node_id)) exclude = set() for elem in self.substitution_list[:index]: @@ -216,22 +208,12 @@ def _template_inverse(self, template_list, template_sublist, template_complement ancestors = set() for index in template_sublist: - node_t = get_node(self.template_dag_dep, index) - if node_t not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node_t] = get_ancestors( - self.template_dag_dep, index - ) - ancestors = ancestors | set(self.temp_match_class.ancestors[node_t]) + ancestors = ancestors | set(get_ancestors(self.template_dag_dep, index)) ancestors = list(ancestors - set(template_sublist)) descendants = set() for index in template_sublist: - node_t = get_node(self.template_dag_dep, index) - if node_t not in self.temp_match_class.descendants: - self.temp_match_class.descendants[node_t] = get_descendants( - self.template_dag_dep, index - ) - descendants = descendants | set(self.temp_match_class.descendants[node_t]) + descendants = descendants | set(get_descendants(self.template_dag_dep, index)) descendants = list(descendants - set(template_sublist)) common = list(set(template_list) - set(ancestors) - set(descendants)) @@ -251,14 +233,6 @@ def _template_inverse(self, template_list, template_sublist, template_complement return left + right - def _substitution_sort(self): - """ - Sort the substitution list. - """ - ordered = False - while not ordered: - ordered = self._permutation() - def _permutation(self): """ Permute two groups of matches if first one has ancestors in the second one. @@ -268,13 +242,9 @@ def _permutation(self): for scenario in self.substitution_list: ancestors = set() for match in scenario.circuit_config: - node_c = get_node(self.circuit_dag_dep, match) - if node_c not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node_c] = get_ancestors( - self.circuit_dag_dep, match - ) - ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) + ancestors = ancestors | set(get_ancestors(self.circuit_dag_dep, match)) ancestors = ancestors - set(scenario.circuit_config) + index = self.substitution_list.index(scenario) for scenario_b in self.substitution_list[index::]: if set(scenario_b.circuit_config) & ancestors: @@ -299,12 +269,7 @@ def _remove_impossible(self): for scenario in self.substitution_list: ancestors = set() for index in scenario.circuit_config: - node_c = get_node(self.circuit_dag_dep, index) - if node_c not in self.temp_match_class.ancestors: - self.temp_match_class.ancestors[node_c] = get_ancestors( - self.circuit_dag_dep, index - ) - ancestors = ancestors | set(self.temp_match_class.ancestors[node_c]) + ancestors = ancestors | set(get_ancestors(self.circuit_dag_dep, index)) list_ancestors.append(ancestors) # Check if two groups of matches are incompatible. @@ -331,10 +296,9 @@ def _remove_impossible(self): def _substitution(self): """ - From the list of maximal matches, it chooses which one will be used and gives the necessary + From the list of maximal matches, this chooses which one will be used and gives the necessary details for each substitution(template inverse, ancestors of the match). """ - while self.match_stack: # Get the first match scenario of the list @@ -352,7 +316,7 @@ def _substitution(self): template_list = range(0, self.template_dag_dep.size()) template_complement = list(set(template_list) - set(template_sublist)) - # If the match obey the rule then it is added to the list. + # If the match obeys the rule then it is added to the list. if self._rules(circuit_sublist, template_sublist, template_complement): template_sublist_inverse = self._template_inverse( template_list, template_sublist, template_complement @@ -374,17 +338,21 @@ def _substitution(self): self.substitution_list.sort(key=lambda x: x.circuit_config[0]) # Change position of the groups due to ancestors of other groups. - self._substitution_sort() + ordered = False + while not ordered: + ordered = self._permutation() for scenario in self.substitution_list: index = self.substitution_list.index(scenario) scenario.anc_block = self._anc_block(scenario.circuit_config, index) - circuit_list = [] - for elem in self.substitution_list: - circuit_list = circuit_list + elem.circuit_config + elem.anc_block - # Unmatched gates that are not ancestors of any group of matches. + circuit_list = [ + y + for elem in self.substitution_list + for x in [elem.circuit_config, elem.anc_block] + for y in x + ] self.unmatched_list = sorted(set(range(0, self.circuit_dag_dep.size())) - set(circuit_list)) def run_dag_opt(self): @@ -417,11 +385,7 @@ def run_dag_opt(self): template_inverse = group.template_config qubit = group.qubit_config[0] - - if group.clbit_config: - clbit = group.clbit_config[0] - else: - clbit = [] + clbit = group.clbit_config[0] if group.clbit_config else [] # First add all the ancestors of the given match. for elem in group.anc_block: diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index 454d8f1b77e6..1ed20427e0b7 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -24,6 +24,8 @@ """ +from functools import lru_cache + import rustworkx as rx @@ -42,14 +44,16 @@ def get_cindices(dag, node): return [dag.find_bit(carg).index for carg in node.cargs] +@lru_cache(maxsize=1024) def get_descendants(dag, node_id): """Wrapper for rustworkx get all descendants of a node.""" - return list(rx.descendants(dag._multi_graph, node_id)) + return rx.descendants(dag._multi_graph, node_id) +@lru_cache(maxsize=1024) def get_ancestors(dag, node_id): """Wrapper for rustworkx get all ancestors of a node.""" - return list(rx.ancestors(dag._multi_graph, node_id)) + return rx.ancestors(dag._multi_graph, node_id) def get_successors(dag, node_id): diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index 4b905828bf71..471a7e4b0bd0 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -142,7 +142,6 @@ def run(self, dag): max_matches, template_m.circuit_dag_dep, template_m.template_dag_dep, - template_m, self.user_cost_dict, ) circuit_dag_dep = substitution.run_dag_opt() From 39de5e29a611fe939829f3850a9414d7910f3d6d Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 5 May 2024 07:55:49 -0700 Subject: [PATCH 14/18] Adjust lru values --- .../optimization/template_matching_v2/template_utils_v2.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index 1ed20427e0b7..676f5aec8e88 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -28,17 +28,19 @@ import rustworkx as rx - +@lru_cache(maxsize=1024) def get_node(dag, node_id): """Wrapper for rustworkx get node object from index.""" return dag._multi_graph[node_id] +@lru_cache(maxsize=64) def get_qindices(dag, node): """Convert qargs to indices.""" return [dag.find_bit(qarg).index for qarg in node.qargs] +@lru_cache(maxsize=64) def get_cindices(dag, node): """Convert cargs to indices.""" return [dag.find_bit(carg).index for carg in node.cargs] @@ -56,11 +58,13 @@ def get_ancestors(dag, node_id): return rx.ancestors(dag._multi_graph, node_id) +@lru_cache(maxsize=128) def get_successors(dag, node_id): """Wrapper for rustworkx get all direct successors of a node.""" return [succ._node_id for succ in dag._multi_graph.successors(node_id)] +@lru_cache(maxsize=128) def get_predecessors(dag, node_id): """Wrapper for rustworkx get all direct predecessors of a node.""" return [pred._node_id for pred in dag._multi_graph.predecessors(node_id)] From 6b6fc83dbe599d14901a228f9c1cd058989e6a8d Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sun, 5 May 2024 17:06:46 -0700 Subject: [PATCH 15/18] Final testing and adjust caches --- .../optimization/template_matching_v2/template_utils_v2.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index 676f5aec8e88..5fb54caa139f 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -58,13 +58,11 @@ def get_ancestors(dag, node_id): return rx.ancestors(dag._multi_graph, node_id) -@lru_cache(maxsize=128) def get_successors(dag, node_id): """Wrapper for rustworkx get all direct successors of a node.""" return [succ._node_id for succ in dag._multi_graph.successors(node_id)] -@lru_cache(maxsize=128) def get_predecessors(dag, node_id): """Wrapper for rustworkx get all direct predecessors of a node.""" return [pred._node_id for pred in dag._multi_graph.predecessors(node_id)] From e47e2741250686046b4c77e72f8f0aafc2a2cac0 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Sat, 8 Jun 2024 07:24:24 -0700 Subject: [PATCH 16/18] Optimize successorstovisit --- .../template_matching_v2/backward_match_v2.py | 4 ++-- .../template_matching_v2/forward_match_v2.py | 10 +++------- .../template_matching_v2/template_matching_v2.py | 13 ++++++++++++- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index fe0762330fcd..f800a5647ee3 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -549,8 +549,8 @@ def run_backward_match(self): else: # Second option, all ancestors are blocked (circuit gate is # moved to the left). - for pred in node_c_ancs: - circuit_blocked[pred] = True + for anc in node_c_ancs: + circuit_blocked[anc] = True matching_scenario = MatchingScenarios( circuit_matched, diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index 7001ad150c69..baeb29afe6c7 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -46,6 +46,7 @@ def __init__( template_dag_dep, node_c, node_t, + successorstovisit, qubits, clbits=[], ): @@ -94,7 +95,7 @@ def __init__( self.carg_indices = [] # Dicts for storing lists of node ids - self.successorstovisit = {} + self.successorstovisit = successorstovisit self.matchedwith = {} # Bool indicating if a node is blocked due to no match @@ -196,17 +197,12 @@ def run_forward_match(self): Apply the forward match algorithm and return the list of matches given an initial match and a circuit qubit configuration. """ - - # Initialize certain attributes - for node in self.circuit_dag_dep.op_nodes(): - self.successorstovisit[node] = get_successors(self.circuit_dag_dep, node._node_id) - self.matchedwith[self.node_c] = [self.node_t._node_id] self.matchedwith[self.node_t] = [self.node_c._node_id] # Initialize the list of matches and the stack of matched nodes (circuit) self.match.append([self.node_t._node_id, self.node_c._node_id]) - self.matched_nodes_list.append(get_node(self.circuit_dag_dep, self.node_c._node_id)) + self.matched_nodes_list.append(self.node_c) # While the list of matched nodes is not empty while self.matched_nodes_list: diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index 6966de9110cf..a701ab892bef 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -30,6 +30,7 @@ get_qindices, get_cindices, get_descendants, + get_successors, ) from qiskit.circuit.controlledgate import ControlledGate from qiskit.transpiler.passes.optimization.template_matching_v2.forward_match_v2 import ForwardMatch @@ -61,6 +62,7 @@ def __init__( self.circuit_dag_dep = circuit_dag_dep self.template_dag_dep = template_dag_dep self.match_list = [] + self.successorstovisit = {} self.heuristics_qubits_param = heuristics_qubits_param self.heuristics_backward_param = heuristics_backward_param @@ -200,6 +202,7 @@ def _add_match(self, backward_match_list): index = self.match_list.index(l_match) self.match_list[index].qubit.append(b_match.qubit[0]) already_in = True + break if not already_in: self.match_list.append(b_match) @@ -265,6 +268,10 @@ def run_template_matching(self): n_qubits_t = len(self.template_dag_dep.qubits) n_clbits_t = len(self.template_dag_dep.clbits) + # # Initialize successorstovisit + for node in self.circuit_dag_dep.op_nodes(): + self.successorstovisit[node] = get_successors(self.circuit_dag_dep, node._node_id) + # Loop over the nodes of both template and circuit. for node_t in self.template_dag_dep.op_nodes(): for node_c in self.circuit_dag_dep.op_nodes(): @@ -327,10 +334,11 @@ def run_template_matching(self): self.template_dag_dep, node_c, node_t, + self.successorstovisit, list_qubit_circuit, list_clbit_circuit, ) - forward.run_forward_match() + matchedwith, isblocked = forward.run_forward_match() # Apply the backward match part of the algorithm. backward = BackwardMatch( @@ -339,6 +347,8 @@ def run_template_matching(self): forward.match, node_c, node_t, + matchedwith, + isblocked, list_qubit_circuit, list_clbit_circuit, self.heuristics_backward_param, @@ -355,6 +365,7 @@ def run_template_matching(self): self.template_dag_dep, node_c, node_t, + self.successorstovisit, list_qubit_circuit, ) matchedwith, isblocked = forward.run_forward_match() From 0e84a373e950a6b012264f20c7c50ee42a16a4a2 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 14 Jun 2024 14:10:55 -0700 Subject: [PATCH 17/18] Up lru caches to 4k --- .../optimization/template_matching_v2/template_utils_v2.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index 5fb54caa139f..bd32ca566a7e 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -46,13 +46,13 @@ def get_cindices(dag, node): return [dag.find_bit(carg).index for carg in node.cargs] -@lru_cache(maxsize=1024) +@lru_cache(maxsize=4096) def get_descendants(dag, node_id): """Wrapper for rustworkx get all descendants of a node.""" return rx.descendants(dag._multi_graph, node_id) -@lru_cache(maxsize=1024) +@lru_cache(maxsize=4096) def get_ancestors(dag, node_id): """Wrapper for rustworkx get all ancestors of a node.""" return rx.ancestors(dag._multi_graph, node_id) From b09c31408ec947f783284388f7f45838ca726ce5 Mon Sep 17 00:00:00 2001 From: Edwin Navarro Date: Fri, 14 Jun 2024 14:29:56 -0700 Subject: [PATCH 18/18] Lint --- .../optimization/template_matching_v2/backward_match_v2.py | 4 ++-- .../optimization/template_matching_v2/forward_match_v2.py | 4 ++-- .../template_matching_v2/template_matching_v2.py | 4 ++-- .../template_matching_v2/template_substitution_v2.py | 6 ++---- .../optimization/template_matching_v2/template_utils_v2.py | 1 + .../passes/optimization/template_optimization_v2.py | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py index f800a5647ee3..2e2beb28824f 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/backward_match_v2.py @@ -120,8 +120,8 @@ def __init__( matchedwith, isblocked, qubits, - clbits=[], - heuristics_backward_param=[], + clbits=None, + heuristics_backward_param=None, ): """ Create a BackwardMatch class with necessary arguments. diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py index baeb29afe6c7..db29c9eb29d8 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/forward_match_v2.py @@ -48,7 +48,7 @@ def __init__( node_t, successorstovisit, qubits, - clbits=[], + clbits=None, ): """ Create a ForwardMatch class with necessary arguments. @@ -187,7 +187,7 @@ def _is_same_c_conf(self, node_c, node_t): """ if (getattr(node_c.op, "condition", None) and getattr(node_t.op, "condition", None)) and ( getattr(node_c.op, "condition", None)[1] != getattr(node_t.op, "condition", None)[1] - or set(carg_circuit) != set(get_cindices(self.template_dag_dep, node_t)) + or set(self.carg_indices) != set(get_cindices(self.template_dag_dep, node_t)) ): return False return True diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py index a701ab892bef..bbf6b7d743a4 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_matching_v2.py @@ -48,8 +48,8 @@ def __init__( self, circuit_dag_dep, template_dag_dep, - heuristics_qubits_param=[], - heuristics_backward_param=[], + heuristics_qubits_param=None, + heuristics_backward_param=None, ): """ Create a TemplateMatching object with necessary arguments. diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py index f3eec17f4265..0933828815b1 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_substitution_v2.py @@ -44,7 +44,7 @@ def __init__( anc_block, qubit_config, template_dag_dep, - clbit_config=[], + clbit_config=None, ): self.circuit_config = circuit_config self.template_config = template_config @@ -68,9 +68,7 @@ class TemplateSubstitution: Class to run the substitution algorithm from the list of maximal matches. """ - def __init__( - self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dict=None - ): + def __init__(self, max_matches, circuit_dag_dep, template_dag_dep, user_cost_dict=None): """ Initialize TemplateSubstitution with necessary arguments. Args: diff --git a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py index bd32ca566a7e..af4f9891e313 100644 --- a/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py +++ b/qiskit/transpiler/passes/optimization/template_matching_v2/template_utils_v2.py @@ -28,6 +28,7 @@ import rustworkx as rx + @lru_cache(maxsize=1024) def get_node(dag, node_id): """Wrapper for rustworkx get node object from index.""" diff --git a/qiskit/transpiler/passes/optimization/template_optimization_v2.py b/qiskit/transpiler/passes/optimization/template_optimization_v2.py index 471a7e4b0bd0..75b1d3566643 100644 --- a/qiskit/transpiler/passes/optimization/template_optimization_v2.py +++ b/qiskit/transpiler/passes/optimization/template_optimization_v2.py @@ -47,8 +47,8 @@ class TemplateOptimizationV2(TransformationPass): def __init__( self, template_list=None, - heuristics_qubits_param=[], - heuristics_backward_param=[], + heuristics_qubits_param=None, + heuristics_backward_param=None, user_cost_dict=None, ): """